From eead9e5e960d84eae8b8e633519e53c4c4469da5 Mon Sep 17 00:00:00 2001 From: YiWang24 Date: Sun, 3 May 2026 23:14:36 -0400 Subject: [PATCH 1/4] fix(workflows): standardize model/api-base-url for custom LLM - Replace API_BASE_URL with ANTHROPIC_BASE_URL in issue-ops.yml - Replace secrets:inherit with explicit mapping in pull-request.yml - Add model input (vars.AI_MODEL) to pull-request, issue-ops, docs --- .github/workflows/docs.yml | 1 + .github/workflows/issue-ops.yml | 17 +++++++++++++---- .github/workflows/pull-request.yml | 5 ++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a5d70a0..d935c1a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,6 +29,7 @@ jobs: site-dir: ${{ vars.DOCS_SITE_DIR || 'site' }} enable-agent: true runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} secrets: anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} diff --git a/.github/workflows/issue-ops.yml b/.github/workflows/issue-ops.yml index 94eaab5..468f54a 100644 --- a/.github/workflows/issue-ops.yml +++ b/.github/workflows/issue-ops.yml @@ -17,6 +17,11 @@ on: type: choice default: lifecycle options: [lifecycle, maintenance, ingest] + model: + required: false + type: string + default: "" + description: "AI model override (e.g. glm-4-flash). Leave empty to use vars.AI_MODEL or the reusable default." permissions: contents: write @@ -36,9 +41,10 @@ jobs: with: mode: lifecycle runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} secrets: anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.API_BASE_URL }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} sentry-token: ${{ secrets.SENTRY_TOKEN }} linear-token: ${{ secrets.LINEAR_TOKEN }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -50,9 +56,10 @@ jobs: with: mode: ingest runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} secrets: anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.API_BASE_URL }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} sentry-token: ${{ secrets.SENTRY_TOKEN }} linear-token: ${{ secrets.LINEAR_TOKEN }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -64,9 +71,10 @@ jobs: with: mode: maintenance runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} secrets: anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.API_BASE_URL }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} sentry-token: ${{ secrets.SENTRY_TOKEN }} linear-token: ${{ secrets.LINEAR_TOKEN }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} @@ -78,9 +86,10 @@ jobs: with: mode: ${{ inputs.mode }} runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ inputs.model || vars.AI_MODEL || '' }} secrets: anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.API_BASE_URL }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} sentry-token: ${{ secrets.SENTRY_TOKEN }} linear-token: ${{ secrets.LINEAR_TOKEN }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index ea2004e..a1afc56 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -33,4 +33,7 @@ jobs: enable-ai-review: true enable-eval: true runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit + model: ${{ vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} From f42d0b374b24032569920a69b0452ec898f78acc Mon Sep 17 00:00:00 2001 From: YiWang24 Date: Sun, 3 May 2026 23:22:01 -0400 Subject: [PATCH 2/4] fix(tests): update PR routing test for explicit secret mapping --- tests/actions/on-pr-routing.bats | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/actions/on-pr-routing.bats b/tests/actions/on-pr-routing.bats index 3e77222..028f023 100644 --- a/tests/actions/on-pr-routing.bats +++ b/tests/actions/on-pr-routing.bats @@ -56,8 +56,8 @@ setup() { grep -q 'runner:.*blacksmith-32vcpu-ubuntu-2404' "$ENTRY" } -@test "checks job inherits secrets" { - grep -q 'secrets: inherit' "$ENTRY" +@test "checks job passes anthropic-api-key secret" { + grep -q 'anthropic-api-key:' "$ENTRY" } # --------------------------------------------------------------------------- From acb09e236a6cc27f9087e40f9e236fc00a706d39 Mon Sep 17 00:00:00 2001 From: YiWang24 Date: Sun, 3 May 2026 23:24:22 -0400 Subject: [PATCH 3/4] chore(manifest): bump YiAgent/OpenCI SHA to be43e4e --- .github/workflows/agent.yml | 2 +- .github/workflows/agent.yml-e | 50 +++ .github/workflows/ci.yml | 2 +- .github/workflows/ci.yml-e | 52 +++ .github/workflows/dependencies.yml | 2 +- .github/workflows/dependencies.yml-e | 20 + .github/workflows/deploy.yml | 4 +- .github/workflows/deploy.yml-e | 161 +++++++ .github/workflows/docs.yml | 2 +- .github/workflows/docs.yml-e | 41 ++ .github/workflows/issue-ops.yml | 8 +- .github/workflows/issue-ops.yml-e | 96 ++++ .github/workflows/observability.yml | 6 +- .github/workflows/observability.yml-e | 57 +++ .github/workflows/on-maintenance.yml | 2 +- .github/workflows/on-maintenance.yml-e | 199 ++++++++ .github/workflows/pull-request.yml | 2 +- .github/workflows/pull-request.yml-e | 39 ++ .github/workflows/release.yml | 2 +- .github/workflows/release.yml-e | 31 ++ .github/workflows/reusable/ci.yml | 16 +- .github/workflows/reusable/ci.yml-e | 600 +++++++++++++++++++++++++ manifest.yml | 2 +- 23 files changed, 1371 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/agent.yml-e create mode 100644 .github/workflows/ci.yml-e create mode 100644 .github/workflows/dependencies.yml-e create mode 100644 .github/workflows/deploy.yml-e create mode 100644 .github/workflows/docs.yml-e create mode 100644 .github/workflows/issue-ops.yml-e create mode 100644 .github/workflows/observability.yml-e create mode 100644 .github/workflows/on-maintenance.yml-e create mode 100644 .github/workflows/pull-request.yml-e create mode 100644 .github/workflows/release.yml-e create mode 100644 .github/workflows/reusable/ci.yml-e diff --git a/.github/workflows/agent.yml b/.github/workflows/agent.yml index f64eb7d..542991f 100644 --- a/.github/workflows/agent.yml +++ b/.github/workflows/agent.yml @@ -40,7 +40,7 @@ concurrency: jobs: agent: - uses: YiAgent/OpenCI/.github/workflows/reusable/agent.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/agent.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: task: ${{ inputs.task }} prompt: ${{ inputs.prompt }} diff --git a/.github/workflows/agent.yml-e b/.github/workflows/agent.yml-e new file mode 100644 index 0000000..f64eb7d --- /dev/null +++ b/.github/workflows/agent.yml-e @@ -0,0 +1,50 @@ +# agent.yml — manual entry to the AI harness for ad-hoc tasks. +# OpenCI ships agent.yml (the harness) + skills/ (canonical prompts). +# Consumers compose their own scheduled / event-driven workflows that +# invoke agent.yml with task + prompt-path + context — see EvolveCI's +# .github/workflows/agent-{daily,weekly,heartbeat,triage}.yml for the +# pattern. This shim only exposes a manual dispatch surface for OpenCI's +# own dogfooding. +name: agent + +on: + workflow_dispatch: + inputs: + task: + required: true + type: string + default: claude-default + prompt: + required: false + type: string + default: "" + prompt-path: + required: false + type: string + default: "" + model: + required: false + type: string + default: "" + +permissions: + contents: write + issues: write + pull-requests: write + id-token: write + actions: read + +concurrency: + group: agent-${{ inputs.task }}-${{ github.run_id }} + cancel-in-progress: false + +jobs: + agent: + uses: YiAgent/OpenCI/.github/workflows/reusable/agent.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + task: ${{ inputs.task }} + prompt: ${{ inputs.prompt }} + prompt-path: ${{ inputs.prompt-path }} + model: ${{ inputs.model }} + runner: blacksmith-32vcpu-ubuntu-2404 + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86cd14b..de549da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ concurrency: jobs: ci: - uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ github.sha }} registry: ghcr.io diff --git a/.github/workflows/ci.yml-e b/.github/workflows/ci.yml-e new file mode 100644 index 0000000..86cd14b --- /dev/null +++ b/.github/workflows/ci.yml-e @@ -0,0 +1,52 @@ +# ci.yml — event entry that fires the CI pipeline on every merge to main. +# Forwards all inputs/secrets to reusable/ci.yml. External users should +# write their own ci.yml against `uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@v3`. +name: ci + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + packages: write + id-token: write + attestations: write + actions: read + security-events: write + pull-requests: write +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: false + +jobs: + ci: + uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ github.sha }} + registry: ghcr.io + image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} + enable-ai-smoke: true + runner: blacksmith-32vcpu-ubuntu-2404 + secrets: inherit + + harness-test: + name: Harness Tests (BATS) + runs-on: blacksmith-32vcpu-ubuntu-2404 + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Install bats + run: | + set -euo pipefail + if ! command -v bats >/dev/null 2>&1; then + sudo apt-get install -y bats + fi + - name: Run BATS test suite + run: bats tests/ --recursive diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 57dd8b1..3c31a91 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -15,6 +15,6 @@ concurrency: jobs: deps: - uses: YiAgent/OpenCI/.github/workflows/reusable/deps.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/deps.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/dependencies.yml-e b/.github/workflows/dependencies.yml-e new file mode 100644 index 0000000..57dd8b1 --- /dev/null +++ b/.github/workflows/dependencies.yml-e @@ -0,0 +1,20 @@ +# dependencies.yml — event entry that flips Renovate patch PRs to auto-merge. +name: dependencies + +on: + pull_request_target: + types: [opened, labeled, synchronize, reopened] + workflow_dispatch: + +permissions: + contents: write + pull-requests: write +concurrency: + group: dependencies-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: false + +jobs: + deps: + uses: YiAgent/OpenCI/.github/workflows/reusable/deps.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 614560c..5cf70a5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: && github.event.workflow_run.name == 'ci' && github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch' && inputs.mode == 'stg') - uses: YiAgent/OpenCI/.github/workflows/reusable/stg.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/stg.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: app-name: ${{ vars.APP_NAME || github.event.repository.name }} image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} @@ -54,7 +54,7 @@ jobs: && github.event.workflow_run.name == 'release' && github.event.workflow_run.conclusion == 'success') || (github.event_name == 'workflow_dispatch' && inputs.mode == 'prd') - uses: YiAgent/OpenCI/.github/workflows/reusable/prd.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/prd.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: app-name: ${{ vars.APP_NAME || github.event.repository.name }} image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} diff --git a/.github/workflows/deploy.yml-e b/.github/workflows/deploy.yml-e new file mode 100644 index 0000000..614560c --- /dev/null +++ b/.github/workflows/deploy.yml-e @@ -0,0 +1,161 @@ +# deploy.yml — event entries for the deploy domain. +# Routes CI/release completions to reusable/stg.yml and reusable/prd.yml. +# Observability triggers live in observability.yml. +name: deploy + +on: + workflow_run: + workflows: [ci, release] + types: [completed] + workflow_dispatch: + inputs: + mode: + required: false + type: string + default: stg + +permissions: + contents: write + packages: read + id-token: write + pull-requests: write + deployments: write + actions: write + +concurrency: + group: deploy-${{ github.event_name }}-${{ github.event.workflow_run.id || github.ref }} + cancel-in-progress: false + +jobs: + stg: + if: >- + (github.event_name == 'workflow_run' + && github.event.workflow_run.name == 'ci' + && github.event.workflow_run.conclusion == 'success') + || (github.event_name == 'workflow_dispatch' && inputs.mode == 'stg') + uses: YiAgent/OpenCI/.github/workflows/reusable/stg.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + app-name: ${{ vars.APP_NAME || github.event.repository.name }} + image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} + health-url: ${{ vars.STG_HEALTH_URL }} + runner: blacksmith-32vcpu-ubuntu-2404 + deploy-type: ${{ vars.DEPLOY_TYPE || 'docker' }} + ssh-host: ${{ vars.STG_SSH_HOST }} + ssh-user: ${{ vars.STG_SSH_USER || 'deploy' }} + ssh-port: ${{ vars.STG_SSH_PORT || '22' }} + deploy-mode: ${{ vars.DEPLOY_MODE || 'compose' }} + compose-file: ${{ vars.COMPOSE_FILE || 'docker-compose.yml' }} + compose-project-dir: ${{ vars.COMPOSE_PROJECT_DIR || '~' }} + secrets: inherit + + prd: + if: >- + (github.event_name == 'workflow_run' + && github.event.workflow_run.name == 'release' + && github.event.workflow_run.conclusion == 'success') + || (github.event_name == 'workflow_dispatch' && inputs.mode == 'prd') + uses: YiAgent/OpenCI/.github/workflows/reusable/prd.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + app-name: ${{ vars.APP_NAME || github.event.repository.name }} + image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} + health-url: ${{ vars.PRD_HEALTH_URL }} + runner: blacksmith-32vcpu-ubuntu-2404 + deploy-type: ${{ vars.DEPLOY_TYPE || 'docker' }} + ssh-host: ${{ vars.PRD_SSH_HOST }} + ssh-user: ${{ vars.PRD_SSH_USER || 'deploy' }} + ssh-port: ${{ vars.PRD_SSH_PORT || '22' }} + deploy-mode: ${{ vars.DEPLOY_MODE || 'compose' }} + compose-file: ${{ vars.COMPOSE_FILE || 'docker-compose.yml' }} + compose-project-dir: ${{ vars.COMPOSE_PROJECT_DIR || '~' }} + secrets: inherit + + # ────────────────────────────────────────────────────────────────────── + # stg-agent-test — autonomous L1-L4 tests after stg deploy. + # ────────────────────────────────────────────────────────────────────── + stg-agent-test: + if: >- + github.event_name == 'workflow_run' + && github.event.workflow_run.name == 'ci' + && github.event.workflow_run.conclusion == 'success' + needs: stg + name: Staging Agent Test L${{ matrix.level }} + runs-on: blacksmith-32vcpu-ubuntu-2404 + timeout-minutes: 30 + permissions: + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + level: [1, 2, 3, 4] + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - id: gate + env: + K: ${{ secrets.ANTHROPIC_API_KEY }} + run: | + if [ -z "$K" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "::notice title=Agent Test Skipped::ANTHROPIC_API_KEY missing" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - if: steps.gate.outputs.skip != 'true' + uses: ./actions/stg/agent-test + with: + level: ${{ matrix.level }} + health-url: ${{ vars.STG_HEALTH_URL }} + image-digest: ${{ vars.STG_IMAGE_DIGEST || 'unknown' }} + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} + - if: steps.gate.outputs.skip != 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: agent-test-L${{ matrix.level }} + path: triage/agent-test/ + retention-days: 14 + + # ────────────────────────────────────────────────────────────────────── + # poll — fire repository_dispatch when observation window passes. + # ────────────────────────────────────────────────────────────────────── + poll: + if: ${{ github.event_name == 'workflow_dispatch' && inputs.mode == 'poll' }} + name: Poll PRD Dispatch + runs-on: blacksmith-32vcpu-ubuntu-2404 + timeout-minutes: 5 + permissions: + contents: read + actions: write + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - id: poll + uses: ./actions/_common/poll-prd-dispatch + with: + github-token: ${{ github.token }} + - if: steps.poll.outputs.fired-count != '0' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + FIRED: ${{ steps.poll.outputs.fired }} + run: | + set -euo pipefail + echo "$FIRED" | jq -c '.[]' | while IFS= read -r entry; do + payload="$(jq -nc --argjson e "$entry" '{ + event_type: "observe-window-complete", + client_payload: { + "image-digest": $e.image_digest, + "stg-image-digest": $e.stg_image_digest, + "stg-deploy-time": $e.stg_deploy_time, + "image-name": $e.image_name, + "app-name": $e.app_name + } + }')" + gh api -X POST "repos/${REPO}/dispatches" --input - <<<"$payload" + echo "::notice title=PRD Dispatched::digest=$(jq -r '.image_digest' <<<"$entry")" + done diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d935c1a..41be760 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -22,7 +22,7 @@ concurrency: jobs: docs: - uses: YiAgent/OpenCI/.github/workflows/reusable/docs.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/docs.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: build-cmd: ${{ vars.DOCS_BUILD_CMD || '' }} docs-path: ${{ vars.DOCS_DIR || 'docs' }} diff --git a/.github/workflows/docs.yml-e b/.github/workflows/docs.yml-e new file mode 100644 index 0000000..d935c1a --- /dev/null +++ b/.github/workflows/docs.yml-e @@ -0,0 +1,41 @@ +# docs.yml — event entry for the documentation quality + agentic sync pipeline. +name: docs + +on: + pull_request: + paths: + - "docs/**" + - "**/*.md" + push: + branches: [main] + schedule: + - cron: "0 9 * * 1" # every Monday at 09:00 UTC + release: + types: [published] + workflow_dispatch: + +permissions: {} + +concurrency: + group: docs-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + docs: + uses: YiAgent/OpenCI/.github/workflows/reusable/docs.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + build-cmd: ${{ vars.DOCS_BUILD_CMD || '' }} + docs-path: ${{ vars.DOCS_DIR || 'docs' }} + site-dir: ${{ vars.DOCS_SITE_DIR || 'site' }} + enable-agent: true + runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} + permissions: + contents: write + pull-requests: write + issues: write + pages: write + id-token: write diff --git a/.github/workflows/issue-ops.yml b/.github/workflows/issue-ops.yml index 468f54a..08c4df2 100644 --- a/.github/workflows/issue-ops.yml +++ b/.github/workflows/issue-ops.yml @@ -37,7 +37,7 @@ concurrency: jobs: lifecycle: if: github.event_name == 'issues' || github.event_name == 'issue_comment' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: lifecycle runner: blacksmith-32vcpu-ubuntu-2404 @@ -52,7 +52,7 @@ jobs: ingest: if: github.event_name == 'repository_dispatch' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: ingest runner: blacksmith-32vcpu-ubuntu-2404 @@ -67,7 +67,7 @@ jobs: maintenance: if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'maintenance') - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: maintenance runner: blacksmith-32vcpu-ubuntu-2404 @@ -82,7 +82,7 @@ jobs: manual: if: github.event_name == 'workflow_dispatch' && inputs.mode != 'maintenance' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: ${{ inputs.mode }} runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/issue-ops.yml-e b/.github/workflows/issue-ops.yml-e new file mode 100644 index 0000000..468f54a --- /dev/null +++ b/.github/workflows/issue-ops.yml-e @@ -0,0 +1,96 @@ +# issue-ops.yml — thin event entry for the issue agent domain. +name: issue-ops + +on: + issues: + types: [opened, reopened, edited, closed] + issue_comment: + types: [created] + schedule: + - cron: "0 2 * * *" + repository_dispatch: + types: [linear-issue-started, sentry-issue] + workflow_dispatch: + inputs: + mode: + required: false + type: choice + default: lifecycle + options: [lifecycle, maintenance, ingest] + model: + required: false + type: string + default: "" + description: "AI model override (e.g. glm-4-flash). Leave empty to use vars.AI_MODEL or the reusable default." + +permissions: + contents: write + issues: write + pull-requests: write + id-token: write + actions: read + +concurrency: + group: issue-ops-${{ github.event.issue.number || github.event.client_payload.id || github.run_id }} + cancel-in-progress: false + +jobs: + lifecycle: + if: github.event_name == 'issues' || github.event_name == 'issue_comment' + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: lifecycle + runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} + sentry-token: ${{ secrets.SENTRY_TOKEN }} + linear-token: ${{ secrets.LINEAR_TOKEN }} + slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} + mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} + + ingest: + if: github.event_name == 'repository_dispatch' + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: ingest + runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} + sentry-token: ${{ secrets.SENTRY_TOKEN }} + linear-token: ${{ secrets.LINEAR_TOKEN }} + slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} + mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} + + maintenance: + if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'maintenance') + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: maintenance + runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} + sentry-token: ${{ secrets.SENTRY_TOKEN }} + linear-token: ${{ secrets.LINEAR_TOKEN }} + slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} + mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} + + manual: + if: github.event_name == 'workflow_dispatch' && inputs.mode != 'maintenance' + uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: ${{ inputs.mode }} + runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ inputs.model || vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} + sentry-token: ${{ secrets.SENTRY_TOKEN }} + linear-token: ${{ secrets.LINEAR_TOKEN }} + slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} + mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} diff --git a/.github/workflows/observability.yml b/.github/workflows/observability.yml index 8b61485..715720f 100644 --- a/.github/workflows/observability.yml +++ b/.github/workflows/observability.yml @@ -30,7 +30,7 @@ concurrency: jobs: observe-canary: if: ${{ github.event_name == 'schedule' && github.event.schedule == '*/15 * * * *' }} - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: canary-watch runner: blacksmith-32vcpu-ubuntu-2404 @@ -38,7 +38,7 @@ jobs: observe-drift: if: ${{ github.event_name == 'schedule' && github.event.schedule == '0 4 * * *' }} - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: terraform-drift infra-dir: ${{ vars.INFRA_DIR || 'infrastructure' }} @@ -50,7 +50,7 @@ jobs: (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || github.event_name == 'repository_dispatch' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'verify-fix') - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: verify-fix runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/observability.yml-e b/.github/workflows/observability.yml-e new file mode 100644 index 0000000..8b61485 --- /dev/null +++ b/.github/workflows/observability.yml-e @@ -0,0 +1,57 @@ +# observability.yml — event entry for post-deploy observability. +# Canary watch (every 15 min), terraform drift (daily), and verify-fix +# (after prd deploy) all route to reusable/observability.yml. +name: observability + +on: + workflow_run: + workflows: [prd] + types: [completed] + repository_dispatch: + types: [observe-window-complete] + schedule: + - cron: "*/15 * * * *" # canary-watch + - cron: "0 4 * * *" # terraform-drift + workflow_dispatch: + inputs: + mode: + required: false + type: string + default: canary-watch + +permissions: + contents: write + issues: write + +concurrency: + group: observability-${{ github.event_name }}-${{ github.event.workflow_run.id || github.event.schedule || github.run_id }} + cancel-in-progress: false + +jobs: + observe-canary: + if: ${{ github.event_name == 'schedule' && github.event.schedule == '*/15 * * * *' }} + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: canary-watch + runner: blacksmith-32vcpu-ubuntu-2404 + secrets: inherit + + observe-drift: + if: ${{ github.event_name == 'schedule' && github.event.schedule == '0 4 * * *' }} + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: terraform-drift + infra-dir: ${{ vars.INFRA_DIR || 'infrastructure' }} + runner: blacksmith-32vcpu-ubuntu-2404 + secrets: inherit + + verify-fix: + if: >- + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') + || github.event_name == 'repository_dispatch' + || (github.event_name == 'workflow_dispatch' && inputs.mode == 'verify-fix') + uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: verify-fix + runner: blacksmith-32vcpu-ubuntu-2404 + secrets: inherit diff --git a/.github/workflows/on-maintenance.yml b/.github/workflows/on-maintenance.yml index 76a3e8d..378c718 100644 --- a/.github/workflows/on-maintenance.yml +++ b/.github/workflows/on-maintenance.yml @@ -115,7 +115,7 @@ jobs: if: | !contains(fromJSON('["pr-review","flag-audit"]'), needs.resolve-mode.outputs.mode) - uses: YiAgent/OpenCI/.github/workflows/reusable/maintenance.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/maintenance.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: ${{ needs.resolve-mode.outputs.mode }} openci-ref: ${{ needs.resolve-mode.outputs.openci-ref }} diff --git a/.github/workflows/on-maintenance.yml-e b/.github/workflows/on-maintenance.yml-e new file mode 100644 index 0000000..76a3e8d --- /dev/null +++ b/.github/workflows/on-maintenance.yml-e @@ -0,0 +1,199 @@ +# ───────────────────────────────────────────────────────────────────────────── +# on-maintenance.yml — Unified entry: security sweeps + dependency intelligence. +# ───────────────────────────────────────────────────────────────────────────── +# Replaces security.yml. Trigger → mode mapping: +# +# schedule Mon 02:00 UTC → full (weekly deep sweep + agent analysis) +# schedule Mon 15:00 UTC → flag-audit (feature flag hygiene check) +# push / pull_request → pr-review (verify-sha integrity check only) +# workflow_dispatch → caller-chosen mode +# +# Why merged: Security (find CVEs) and Dependencies (fix CVEs) are two sides of +# the same problem. The agent stage sees both scan results and available updates +# in one pass, so it can write "upgrade X to Y to fix CVE-Z" instead of filing +# two disconnected items. +# +# What stays separate: dependencies.yml (pull_request_target for Renovate +# auto-merge) uses a distinct trust model and MUST remain its own file. +# ───────────────────────────────────────────────────────────────────────────── +name: maintenance + +on: + schedule: + - cron: "0 2 * * 1" # Monday 02:00 UTC — full sweep + - cron: "0 15 * * 1" # Monday 23:00 BJT — flag audit + push: + branches: [main] + paths: + - "manifest.yml" + - "actions/**/action.yml" + - ".github/workflows/**.yml" + pull_request: + paths: + - "manifest.yml" + - "actions/**/action.yml" + - ".github/workflows/**.yml" + workflow_dispatch: + inputs: + mode: + description: "Execution mode" + required: false + type: choice + default: full + options: + - full + - scan-only + - deps-only + - flag-audit + +permissions: + contents: read + security-events: write + packages: read + id-token: write + issues: write + actions: read + +concurrency: + group: maintenance-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + # ── Mode resolution ───────────────────────────────────────────────────────── + resolve-mode: + name: Resolve Mode + runs-on: blacksmith-32vcpu-ubuntu-2404 + timeout-minutes: 2 + outputs: + mode: ${{ steps.resolve.outputs.mode }} + openci-ref: ${{ steps.openci-ref.outputs.ref }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + + - name: Determine mode + id: resolve + env: + EVENT: ${{ github.event_name }} + SCHEDULE: ${{ github.event.schedule }} + INPUT_MODE: ${{ inputs.mode }} + run: | + set -euo pipefail + case "$EVENT" in + workflow_dispatch) + echo "mode=${INPUT_MODE:-full}" >> "$GITHUB_OUTPUT" + ;; + pull_request|push) + echo "mode=pr-review" >> "$GITHUB_OUTPUT" + ;; + schedule) + case "$SCHEDULE" in + "0 15 * * 1") echo "mode=flag-audit" >> "$GITHUB_OUTPUT" ;; + "0 2 * * 1") echo "mode=full" >> "$GITHUB_OUTPUT" ;; + *) echo "mode=deps-only" >> "$GITHUB_OUTPUT" ;; + esac + ;; + *) echo "mode=full" >> "$GITHUB_OUTPUT" ;; + esac + shell: bash + + - name: Resolve OpenCI ref + id: openci-ref + env: + WORKFLOW_REF: ${{ github.workflow_ref }} + shell: bash + run: | + REF="${WORKFLOW_REF##*@}" + REF="${REF#refs/heads/}" + REF="${REF#refs/tags/}" + echo "ref=$REF" >> "$GITHUB_OUTPUT" + + # ── Full maintenance sweep (full | scan-only | deps-only) ─────────────────── + maintenance: + name: Maintenance + needs: resolve-mode + if: | + !contains(fromJSON('["pr-review","flag-audit"]'), + needs.resolve-mode.outputs.mode) + uses: YiAgent/OpenCI/.github/workflows/reusable/maintenance.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: ${{ needs.resolve-mode.outputs.mode }} + openci-ref: ${{ needs.resolve-mode.outputs.openci-ref }} + image-ref: ${{ vars.IMAGE_REF || '' }} + runner: blacksmith-32vcpu-ubuntu-2404 + secrets: inherit + + # ── SHA integrity check on push / PR ──────────────────────────────────────── + verify-sha: + name: Verify SHA Consistency + needs: resolve-mode + if: needs.resolve-mode.outputs.mode == 'pr-review' + runs-on: blacksmith-32vcpu-ubuntu-2404 + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + + - name: Install yq + shell: bash + env: + YQ_VERSION: "4.44.6" + run: | + set -euo pipefail + if command -v yq >/dev/null 2>&1; then yq --version; exit 0; fi + sudo wget -qO /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64" + sudo chmod +x /usr/local/bin/yq + yq --version + + - name: Verify SHA consistency + shell: bash + run: bash .github/scripts/verify-sha-consistency.sh + + # ── Feature flag audit (Monday BJT evening) ───────────────────────────────── + flag-audit: + name: Flag Audit + needs: resolve-mode + if: needs.resolve-mode.outputs.mode == 'flag-audit' + runs-on: blacksmith-32vcpu-ubuntu-2404 + timeout-minutes: 15 + permissions: + contents: read + issues: write + id-token: write + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + + # Vendor OpenCI so the flag-audit composite can reach claude-harness. + - name: Resolve OpenCI ref + id: openci-ref + shell: bash + env: + WORKFLOW_REF: ${{ github.workflow_ref }} + run: | + REF="${WORKFLOW_REF##*@}" + REF="${REF#refs/heads/}" + REF="${REF#refs/tags/}" + echo "ref=$REF" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: + repository: YiAgent/OpenCI + ref: ${{ steps.openci-ref.outputs.ref }} + path: .openci + persist-credentials: false + + - uses: ./.openci/actions/_common/flag-audit + with: + github-token: ${{ github.token }} + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a1afc56..62cd118 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -28,7 +28,7 @@ concurrency: jobs: checks: - uses: YiAgent/OpenCI/.github/workflows/reusable/pr.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/pr.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: enable-ai-review: true enable-eval: true diff --git a/.github/workflows/pull-request.yml-e b/.github/workflows/pull-request.yml-e new file mode 100644 index 0000000..a1afc56 --- /dev/null +++ b/.github/workflows/pull-request.yml-e @@ -0,0 +1,39 @@ +# pull-request.yml — event entry that fires the PR quality gate on every PR. +# OpenCI no longer ships standalone "pr-agent" task workflows — consumers +# write their own thin scheduled / event-driven workflows that call +# agent.yml directly with task + prompt-path + context (see EvolveCI +# repo for the canonical pattern). This shim only runs the deterministic +# PR checks via pr.yml. +name: pull-request + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: + +permissions: + contents: read + actions: read + checks: write + issues: write + pull-requests: write + security-events: write + id-token: write + statuses: write + packages: read + +concurrency: + group: pull-request-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: false + +jobs: + checks: + uses: YiAgent/OpenCI/.github/workflows/reusable/pr.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + enable-ai-review: true + enable-eval: true + runner: blacksmith-32vcpu-ubuntu-2404 + model: ${{ vars.AI_MODEL || '' }} + secrets: + anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} + api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1986939..68ff6e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ concurrency: jobs: release: - uses: YiAgent/OpenCI/.github/workflows/reusable/release.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/.github/workflows/reusable/release.yml@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: mode: ${{ inputs.mode || 'both' }} image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} diff --git a/.github/workflows/release.yml-e b/.github/workflows/release.yml-e new file mode 100644 index 0000000..1986939 --- /dev/null +++ b/.github/workflows/release.yml-e @@ -0,0 +1,31 @@ +# release.yml — event entry for tagged releases. +# `git push origin v*` runs marketplace + docker release jobs by default. +name: release + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + mode: + required: false + type: string + default: both + +permissions: + contents: write + packages: write + id-token: write + attestations: write +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + uses: YiAgent/OpenCI/.github/workflows/reusable/release.yml@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + mode: ${{ inputs.mode || 'both' }} + image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} + registry: ghcr.io + runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/reusable/ci.yml b/.github/workflows/reusable/ci.yml index e19d51f..ed45eca 100644 --- a/.github/workflows/reusable/ci.yml +++ b/.github/workflows/reusable/ci.yml @@ -127,7 +127,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - name: Probe secrets @@ -155,7 +155,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - id: detect @@ -183,7 +183,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - id: build @@ -212,7 +212,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - id: scan @@ -235,7 +235,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 @@ -282,7 +282,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - uses: ./.openci/actions/ci/check-migration @@ -305,7 +305,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - uses: ./.openci/actions/ci/eval-smoke @@ -485,7 +485,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: { persist-credentials: false } - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + uses: YiAgent/OpenCI/actions/_common/resolve-openci@be43e4efd2f14f2a3da7d5264356a9e6774c8ef1 with: openci-ref: ${{ inputs.openci-ref }} - name: Download ci-context artifact diff --git a/.github/workflows/reusable/ci.yml-e b/.github/workflows/reusable/ci.yml-e new file mode 100644 index 0000000..e19d51f --- /dev/null +++ b/.github/workflows/reusable/ci.yml-e @@ -0,0 +1,600 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ci.yml — Merge-to-main build pipeline (reusable workflow, SPEC §5.3). +# ───────────────────────────────────────────────────────────────────────────── +# 4-Stage Design: +# +# Stage 1 — Build (deterministic) +# preflight → detect-language → build-docker +# +# Stage 2 — Verify (parallel, after build) +# scan-image ─┐ +# sign-image ├─ (parallel) +# verify-sha │ +# generate-sbom┤ +# check-migration (if run-migration) +# eval-smoke (if enable-ai-smoke) +# +# Stage 3 — Agent (failure-only, skipped entirely on green builds) +# enrich → aggregate Stage 2 results into ci-context.json artifact +# agent → ci-failure-analyst skill (only when has-failures == true) +# +# Stage 4 — Dispatch +# execute → evaluate deploy gate, trigger deploy workflow, write summary +# +# Outputs `image-digest` + `deploy-time` for downstream stg.yml / prd.yml. +# Concurrency does NOT cancel-in-progress: every main commit deserves a +# fully completed build (they end up tagged as :sha-<7> regardless). +# +# Security: caller-supplied `image-name` flows into docker tags via the +# composite atom; no shell interpolation in this file. +# ───────────────────────────────────────────────────────────────────────────── +name: ci + +on: + workflow_call: + inputs: + model: + description: Override the AI model name. Defaults to the Anthropic Claude model when empty. + required: false + type: string + default: "" + openci-ref: + description: OpenCI ref to vendor for ./.openci/* references (default main; use a tag like v2 in production). + required: false + type: string + default: main + runner: + description: Runner label to use for all jobs in this workflow. Defaults to ubuntu-latest for open-source compatibility. + required: false + type: string + default: ubuntu-latest + language: + type: string + required: false + default: "" + registry: + type: string + required: false + default: ghcr.io + image-name: + type: string + required: true + enable-ai-smoke: + type: boolean + required: false + default: false + run-migration: + type: boolean + required: false + default: false + enable-failure-agent: + description: Run the AI failure-analysis agent when CI failures are detected. + type: boolean + required: false + default: true + auto-deploy: + description: Automatically trigger the deploy workflow on a clean build. + type: boolean + required: false + default: false + deploy-workflow: + description: Workflow file to dispatch for deployment (used when auto-deploy is true). + type: string + required: false + default: "deploy.yml" + deploy-environment: + description: Environment name passed to the deploy workflow. + type: string + required: false + default: "staging" + secrets: + api-base-url: + description: Custom Anthropic-compatible base URL. Leave unset to use the default Anthropic endpoint. + required: false + registry-token: + description: "Token used to push to the registry. Defaults to GITHUB_TOKEN at the use site." + required: false + anthropic-api-key: + required: false + outputs: + image-digest: + description: sha256:... digest of the pushed image. + value: ${{ jobs.build-docker.outputs.image-digest }} + deploy-time: + description: ISO 8601 build completion timestamp. + value: ${{ jobs.build-docker.outputs.completed-at }} + deploy-ready: + description: "'true' when the build passed all gates and is safe to deploy." + value: ${{ jobs.execute.outputs.deploy-ready }} + +permissions: {} + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: false + +jobs: + # ── Stage 1: Build ────────────────────────────────────────────────────────── + preflight: + name: Preflight + runs-on: ${{ inputs.runner }} + timeout-minutes: 2 + permissions: + contents: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - name: Probe secrets + env: + REGISTRY_TOKEN: ${{ secrets.registry-token || github.token }} + ANTHROPIC_API_KEY: ${{ secrets.anthropic-api-key }} + run: | + bash .github/scripts/preflight-secrets.sh \ + --required "REGISTRY_TOKEN" \ + --optional "ANTHROPIC_API_KEY" + + detect-language: + name: Detect Language + needs: preflight + runs-on: ${{ inputs.runner }} + timeout-minutes: 2 + permissions: + contents: read + outputs: + language: ${{ steps.detect.outputs.language }} + package-manager: ${{ steps.detect.outputs.package-manager }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - id: detect + shell: bash + env: + OVERRIDE: ${{ inputs.language }} + run: bash ./.openci/actions/_common/detect-language/detect.sh "$GITHUB_WORKSPACE" + + build-docker: + name: Build Docker + needs: detect-language + runs-on: ${{ inputs.runner }} + timeout-minutes: 30 + permissions: + contents: read + packages: write + id-token: write + outputs: + image-digest: ${{ steps.build.outputs.image-digest }} + image-tag-sha: ${{ steps.build.outputs.image-tag-sha }} + completed-at: ${{ steps.build.outputs.completed-at }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - id: build + uses: ./.openci/actions/ci/build-docker + with: + image-name: ${{ inputs.image-name }} + registry: ${{ inputs.registry }} + registry-token: ${{ secrets.registry-token || github.token }} + + # ── Stage 2: Verify (parallel) ────────────────────────────────────────────── + scan-image: + name: Scan Image + needs: build-docker + runs-on: ${{ inputs.runner }} + timeout-minutes: 15 + permissions: + contents: read + security-events: write + outputs: + vulnerabilities-found: ${{ steps.scan.outputs.vulnerabilities-found }} + critical-count: ${{ steps.scan.outputs.critical-count }} + high-count: ${{ steps.scan.outputs.high-count }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - id: scan + uses: ./.openci/actions/ci/scan-image + with: + image-ref: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}@${{ needs.build-docker.outputs.image-digest }} + + sign-image: + name: Sign Image + needs: build-docker + runs-on: ${{ inputs.runner }} + timeout-minutes: 10 + permissions: + contents: read + packages: write + id-token: write + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ${{ inputs.registry }} + username: ${{ github.actor }} + password: ${{ secrets.registry-token || github.token }} + - uses: ./.openci/actions/ci/sign-image + with: + image-ref: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}@${{ needs.build-docker.outputs.image-digest }} + + generate-sbom: + name: Generate SBOM + needs: build-docker + runs-on: ${{ inputs.runner }} + timeout-minutes: 5 + permissions: + contents: read + outputs: + sbom-ref: ${{ steps.ref.outputs.sbom-ref }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - name: Record SBOM attestation reference + id: ref + shell: bash + env: + IMAGE_REF: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}@${{ needs.build-docker.outputs.image-digest }} + run: | + echo "sbom-ref=${IMAGE_REF}" >> "$GITHUB_OUTPUT" + echo "::notice title=SBOM::SPDX SBOM attestation attached to ${IMAGE_REF}" + + check-migration: + name: Check Migration + needs: build-docker + if: inputs.run-migration == true + runs-on: ${{ inputs.runner }} + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - uses: ./.openci/actions/ci/check-migration + with: + migration-cmd: ${{ vars.MIGRATION_DRY_RUN_CMD || 'false' }} + + eval-smoke: + name: AI Smoke Eval + needs: build-docker + if: inputs.enable-ai-smoke == true + runs-on: ${{ inputs.runner }} + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + id-token: write + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - uses: ./.openci/actions/ci/eval-smoke + with: + image-digest: ${{ needs.build-docker.outputs.image-digest }} + anthropic-api-key: ${{ secrets.anthropic-api-key }} + api-base-url: ${{ secrets.api-base-url }} + model: ${{ inputs.model }} + + verify-sha: + name: Verify SHA Consistency + needs: build-docker + runs-on: ${{ inputs.runner }} + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Install yq + shell: bash + run: | + set -euo pipefail + if ! command -v yq >/dev/null 2>&1; then + sudo wget -qO /usr/local/bin/yq \ + https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + fi + yq --version + - name: Run verify-sha-consistency.sh + shell: bash + run: bash .github/scripts/verify-sha-consistency.sh + + # ── Stage 3: Agent (skipped entirely on green builds) ─────────────────────── + enrich: + name: Enrich Failure Context + needs: + - build-docker + - scan-image + - sign-image + - verify-sha + - generate-sbom + - check-migration + - eval-smoke + if: always() + runs-on: ${{ inputs.runner }} + timeout-minutes: 5 + permissions: + contents: read + actions: read + outputs: + has-failures: ${{ steps.summarize.outputs.has-failures }} + deploy-blocked: ${{ steps.summarize.outputs.deploy-blocked }} + agent-context: ${{ steps.summarize.outputs.agent-context }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - name: Summarize Stage 2 results + id: summarize + shell: bash + env: + BUILD_RESULT: ${{ needs.build-docker.result }} + SCAN_RESULT: ${{ needs.scan-image.result }} + SIGN_RESULT: ${{ needs.sign-image.result }} + VERIFY_RESULT: ${{ needs.verify-sha.result }} + SBOM_RESULT: ${{ needs.generate-sbom.result }} + MIGRATION_RESULT: ${{ needs.check-migration.result }} + SMOKE_RESULT: ${{ needs.eval-smoke.result }} + CRITICAL_CVE: ${{ needs.scan-image.outputs.critical-count }} + HIGH_CVE: ${{ needs.scan-image.outputs.high-count }} + IMAGE_DIGEST: ${{ needs.build-docker.outputs.image-digest }} + RUN_ID: ${{ github.run_id }} + REPO: ${{ github.repository }} + COMMIT: ${{ github.sha }} + run: | + set -euo pipefail + + BUILD_PASSED="false" + [ "$BUILD_RESULT" = "success" ] && BUILD_PASSED="true" + + SHA_OK="false" + [ "$VERIFY_RESULT" = "success" ] && SHA_OK="true" + + CRITICAL=${CRITICAL_CVE:-0} + HIGH=${HIGH_CVE:-0} + + DEPLOY_BLOCKED="false" + HAS_FAILURES="false" + + if [ "$BUILD_PASSED" = "false" ]; then + HAS_FAILURES="true"; DEPLOY_BLOCKED="true" + fi + if [ "${CRITICAL}" -gt 0 ]; then + HAS_FAILURES="true"; DEPLOY_BLOCKED="true" + fi + if [ "$SCAN_RESULT" = "failure" ]; then + HAS_FAILURES="true"; DEPLOY_BLOCKED="true" + fi + if [ "$SIGN_RESULT" = "failure" ]; then + HAS_FAILURES="true"; DEPLOY_BLOCKED="true" + fi + if [ "$SBOM_RESULT" = "failure" ]; then + HAS_FAILURES="true"; DEPLOY_BLOCKED="true" + fi + if [ "$SHA_OK" = "false" ]; then + HAS_FAILURES="true" + fi + if [ "${HIGH}" -gt 0 ]; then + HAS_FAILURES="true" + fi + if [ "$MIGRATION_RESULT" = "failure" ]; then + HAS_FAILURES="true"; DEPLOY_BLOCKED="true" + fi + if [ "$SMOKE_RESULT" = "failure" ]; then + HAS_FAILURES="true" + fi + + CONTEXT=$(jq -cn \ + --arg build_result "$BUILD_RESULT" \ + --arg build_passed "$BUILD_PASSED" \ + --arg critical_cve "$CRITICAL" \ + --arg high_cve "$HIGH" \ + --arg sha_ok "$SHA_OK" \ + --arg migration_result "$MIGRATION_RESULT" \ + --arg smoke_result "$SMOKE_RESULT" \ + --arg image_digest "$IMAGE_DIGEST" \ + --arg run_id "$RUN_ID" \ + --arg repo "$REPO" \ + --arg commit "$COMMIT" \ + '{build_result:$build_result, build_passed:$build_passed, + critical_cve:$critical_cve, high_cve:$high_cve, + sha_ok:$sha_ok, migration_result:$migration_result, + smoke_result:$smoke_result, image_digest:$image_digest, + run_id:$run_id, repo:$repo, commit:$commit, + failure_context:{logs_available:false, + note:"Detailed per-job logs are available in the Actions UI at the run URL above. The analyst should use gh api to fetch logs when needed rather than relying on embedded log content."}}') + + { + echo "has-failures=${HAS_FAILURES}" + echo "deploy-blocked=${DEPLOY_BLOCKED}" + echo "agent-context=${CONTEXT}" + } >> "$GITHUB_OUTPUT" + + echo "::notice title=CI Gate::has-failures=${HAS_FAILURES} deploy-blocked=${DEPLOY_BLOCKED} critical=${CRITICAL} high=${HIGH}" + + - name: Write ci-context.json artifact + shell: bash + env: + CONTEXT: ${{ steps.summarize.outputs.agent-context }} + run: printf '%s\n' "$CONTEXT" > ci-context.json + + - name: Upload ci-context artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ci-context + path: ci-context.json + retention-days: 7 + + agent: + name: CI Failure Analyst + needs: enrich + if: >- + needs.enrich.result == 'success' && + inputs.enable-failure-agent == true && + needs.enrich.outputs.has-failures == 'true' + runs-on: ${{ inputs.runner }} + timeout-minutes: 15 + permissions: + contents: read + issues: write + actions: read + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + with: { persist-credentials: false } + - name: Resolve OpenCI ref and checkout + uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d + with: + openci-ref: ${{ inputs.openci-ref }} + - name: Download ci-context artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: ci-context + - name: Run CI failure analyst + uses: ./.openci/actions/_common/claude-harness + with: + task: "ci-failure-analyst" + anthropic-api-key: ${{ secrets.anthropic-api-key }} + api-base-url: ${{ secrets.api-base-url }} + model: ${{ inputs.model }} + allowed-tools: "Bash" + github-token: ${{ github.token }} + openci-ref: ${{ inputs.openci-ref }} + + # ── Stage 4: Dispatch ──────────────────────────────────────────────────────── + execute: + name: Execute Dispatch + needs: + - build-docker + - scan-image + - sign-image + - verify-sha + - generate-sbom + - check-migration + - eval-smoke + - enrich + - agent + if: always() + runs-on: ${{ inputs.runner }} + timeout-minutes: 5 + permissions: + actions: write + contents: read + outputs: + deploy-ready: ${{ steps.gate.outputs.deploy-ready }} + steps: + - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 + with: { egress-policy: audit } + - name: Evaluate deploy gate + id: gate + shell: bash + env: + DEPLOY_BLOCKED: ${{ needs.enrich.outputs.deploy-blocked }} + AUTO_DEPLOY: ${{ inputs.auto-deploy }} + run: | + set -euo pipefail + DEPLOY_READY="false" + if [ "${DEPLOY_BLOCKED:-true}" = "false" ] && [ "$AUTO_DEPLOY" = "true" ]; then + DEPLOY_READY="true" + fi + echo "deploy-ready=${DEPLOY_READY}" >> "$GITHUB_OUTPUT" + echo "::notice title=Deploy Gate::deploy-ready=${DEPLOY_READY}" + + - name: Trigger deploy workflow + if: steps.gate.outputs.deploy-ready == 'true' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + DEPLOY_WORKFLOW: ${{ inputs.deploy-workflow }} + DEPLOY_ENVIRONMENT: ${{ inputs.deploy-environment }} + IMAGE_DIGEST: ${{ needs.build-docker.outputs.image-digest }} + REF: ${{ github.ref_name }} + run: | + gh workflow run "$DEPLOY_WORKFLOW" \ + --repo "$REPO" \ + --ref "$REF" \ + --field "image-digest=${IMAGE_DIGEST}" \ + --field "environment=${DEPLOY_ENVIRONMENT}" + + - name: Write job summary + if: always() + shell: bash + env: + BUILD_RESULT: ${{ needs.build-docker.result }} + SCAN_RESULT: ${{ needs.scan-image.result }} + SIGN_RESULT: ${{ needs.sign-image.result }} + VERIFY_RESULT: ${{ needs.verify-sha.result }} + MIGN_RESULT: ${{ needs.check-migration.result }} + SMOKE_RESULT: ${{ needs.eval-smoke.result }} + CRITICAL_CVE: ${{ needs.scan-image.outputs.critical-count }} + HIGH_CVE: ${{ needs.scan-image.outputs.high-count }} + IMAGE_DIGEST: ${{ needs.build-docker.outputs.image-digest }} + DEPLOY_READY: ${{ steps.gate.outputs.deploy-ready }} + RUN_ID: ${{ github.run_id }} + REPO: ${{ github.repository }} + COMMIT: ${{ github.sha }} + run: | + CRITICAL=${CRITICAL_CVE:-0} + HIGH=${HIGH_CVE:-0} + + ok=":white_check_mark:"; fail=":x:"; skip=":white_circle:" + s() { case "$1" in success) printf '%s' "$ok";; skipped) printf '%s' "$skip";; *) printf '%s' "$fail";; esac; } + + { + printf "## CI Pipeline Summary\n\n" + printf "| Stage | Job | Result |\n" + printf "|-------|-----|--------|\n" + printf "| Build | build-docker | %s |\n" "$(s "$BUILD_RESULT")" + printf "| Verify | scan-image | %s (CRITICAL: %s, HIGH: %s) |\n" "$(s "$SCAN_RESULT")" "$CRITICAL" "$HIGH" + printf "| Verify | sign-image | %s |\n" "$(s "$SIGN_RESULT")" + printf "| Verify | verify-sha | %s |\n" "$(s "$VERIFY_RESULT")" + printf "| Verify | check-migration | %s |\n" "$(s "$MIGN_RESULT")" + printf "| Verify | eval-smoke | %s |\n" "$(s "$SMOKE_RESULT")" + printf "\n**Image digest:** \`%s\`\n" "$IMAGE_DIGEST" + printf "**Deploy ready:** \`%s\`\n" "$DEPLOY_READY" + printf "**Run:** [#%s](https://github.com/%s/actions/runs/%s)\n" "$RUN_ID" "$REPO" "$RUN_ID" + printf "**Commit:** \`%s\`\n" "${COMMIT:0:7}" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/manifest.yml b/manifest.yml index 4282d10..88dcfb8 100644 --- a/manifest.yml +++ b/manifest.yml @@ -101,7 +101,7 @@ deps: softprops/action-gh-release: "b4309332981a82ec1c5618f44dd2e27cc8bfbfda" # v3.0.0 # ── Self (OpenCI vendoring itself via remote action reference) ────────── - YiAgent/OpenCI: "ebe8fca3260dce68d34d51b74703169e776bc72d" # resolve-openci bootstrap + YiAgent/OpenCI: "be43e4efd2f14f2a3da7d5264356a9e6774c8ef1" # resolve-openci bootstrap # ───────────────────────────────────────────────────────────────────────────── # Reusable workflow catalog (consumed via `uses: YiAgent/OpenCI/.github/workflows/.yml@`) From 5b18a26218a2171a4e9501ac46ea4cc85804b6a5 Mon Sep 17 00:00:00 2001 From: YiWang24 Date: Sun, 3 May 2026 23:27:18 -0400 Subject: [PATCH 4/4] fix(scripts): use perl -pi for portable in-place sed on macOS/Linux --- .github/workflows/agent.yml-e | 50 --- .github/workflows/ci.yml-e | 52 --- .github/workflows/dependencies.yml-e | 20 - .github/workflows/deploy.yml-e | 161 ------- .github/workflows/docs.yml-e | 41 -- .github/workflows/issue-ops.yml-e | 96 ---- .github/workflows/observability.yml-e | 57 --- .github/workflows/on-maintenance.yml-e | 199 -------- .github/workflows/pull-request.yml-e | 39 -- .github/workflows/release.yml-e | 31 -- .github/workflows/reusable/ci.yml-e | 600 ------------------------- .gitignore | 3 +- scripts/bump-self-sha.sh | 4 +- 13 files changed, 4 insertions(+), 1349 deletions(-) delete mode 100644 .github/workflows/agent.yml-e delete mode 100644 .github/workflows/ci.yml-e delete mode 100644 .github/workflows/dependencies.yml-e delete mode 100644 .github/workflows/deploy.yml-e delete mode 100644 .github/workflows/docs.yml-e delete mode 100644 .github/workflows/issue-ops.yml-e delete mode 100644 .github/workflows/observability.yml-e delete mode 100644 .github/workflows/on-maintenance.yml-e delete mode 100644 .github/workflows/pull-request.yml-e delete mode 100644 .github/workflows/release.yml-e delete mode 100644 .github/workflows/reusable/ci.yml-e diff --git a/.github/workflows/agent.yml-e b/.github/workflows/agent.yml-e deleted file mode 100644 index f64eb7d..0000000 --- a/.github/workflows/agent.yml-e +++ /dev/null @@ -1,50 +0,0 @@ -# agent.yml — manual entry to the AI harness for ad-hoc tasks. -# OpenCI ships agent.yml (the harness) + skills/ (canonical prompts). -# Consumers compose their own scheduled / event-driven workflows that -# invoke agent.yml with task + prompt-path + context — see EvolveCI's -# .github/workflows/agent-{daily,weekly,heartbeat,triage}.yml for the -# pattern. This shim only exposes a manual dispatch surface for OpenCI's -# own dogfooding. -name: agent - -on: - workflow_dispatch: - inputs: - task: - required: true - type: string - default: claude-default - prompt: - required: false - type: string - default: "" - prompt-path: - required: false - type: string - default: "" - model: - required: false - type: string - default: "" - -permissions: - contents: write - issues: write - pull-requests: write - id-token: write - actions: read - -concurrency: - group: agent-${{ inputs.task }}-${{ github.run_id }} - cancel-in-progress: false - -jobs: - agent: - uses: YiAgent/OpenCI/.github/workflows/reusable/agent.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - task: ${{ inputs.task }} - prompt: ${{ inputs.prompt }} - prompt-path: ${{ inputs.prompt-path }} - model: ${{ inputs.model }} - runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit diff --git a/.github/workflows/ci.yml-e b/.github/workflows/ci.yml-e deleted file mode 100644 index 86cd14b..0000000 --- a/.github/workflows/ci.yml-e +++ /dev/null @@ -1,52 +0,0 @@ -# ci.yml — event entry that fires the CI pipeline on every merge to main. -# Forwards all inputs/secrets to reusable/ci.yml. External users should -# write their own ci.yml against `uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@v3`. -name: ci - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - packages: write - id-token: write - attestations: write - actions: read - security-events: write - pull-requests: write -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: false - -jobs: - ci: - uses: YiAgent/OpenCI/.github/workflows/reusable/ci.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ github.sha }} - registry: ghcr.io - image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} - enable-ai-smoke: true - runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit - - harness-test: - name: Harness Tests (BATS) - runs-on: blacksmith-32vcpu-ubuntu-2404 - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Install bats - run: | - set -euo pipefail - if ! command -v bats >/dev/null 2>&1; then - sudo apt-get install -y bats - fi - - name: Run BATS test suite - run: bats tests/ --recursive diff --git a/.github/workflows/dependencies.yml-e b/.github/workflows/dependencies.yml-e deleted file mode 100644 index 57dd8b1..0000000 --- a/.github/workflows/dependencies.yml-e +++ /dev/null @@ -1,20 +0,0 @@ -# dependencies.yml — event entry that flips Renovate patch PRs to auto-merge. -name: dependencies - -on: - pull_request_target: - types: [opened, labeled, synchronize, reopened] - workflow_dispatch: - -permissions: - contents: write - pull-requests: write -concurrency: - group: dependencies-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: false - -jobs: - deps: - uses: YiAgent/OpenCI/.github/workflows/reusable/deps.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/deploy.yml-e b/.github/workflows/deploy.yml-e deleted file mode 100644 index 614560c..0000000 --- a/.github/workflows/deploy.yml-e +++ /dev/null @@ -1,161 +0,0 @@ -# deploy.yml — event entries for the deploy domain. -# Routes CI/release completions to reusable/stg.yml and reusable/prd.yml. -# Observability triggers live in observability.yml. -name: deploy - -on: - workflow_run: - workflows: [ci, release] - types: [completed] - workflow_dispatch: - inputs: - mode: - required: false - type: string - default: stg - -permissions: - contents: write - packages: read - id-token: write - pull-requests: write - deployments: write - actions: write - -concurrency: - group: deploy-${{ github.event_name }}-${{ github.event.workflow_run.id || github.ref }} - cancel-in-progress: false - -jobs: - stg: - if: >- - (github.event_name == 'workflow_run' - && github.event.workflow_run.name == 'ci' - && github.event.workflow_run.conclusion == 'success') - || (github.event_name == 'workflow_dispatch' && inputs.mode == 'stg') - uses: YiAgent/OpenCI/.github/workflows/reusable/stg.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - app-name: ${{ vars.APP_NAME || github.event.repository.name }} - image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} - health-url: ${{ vars.STG_HEALTH_URL }} - runner: blacksmith-32vcpu-ubuntu-2404 - deploy-type: ${{ vars.DEPLOY_TYPE || 'docker' }} - ssh-host: ${{ vars.STG_SSH_HOST }} - ssh-user: ${{ vars.STG_SSH_USER || 'deploy' }} - ssh-port: ${{ vars.STG_SSH_PORT || '22' }} - deploy-mode: ${{ vars.DEPLOY_MODE || 'compose' }} - compose-file: ${{ vars.COMPOSE_FILE || 'docker-compose.yml' }} - compose-project-dir: ${{ vars.COMPOSE_PROJECT_DIR || '~' }} - secrets: inherit - - prd: - if: >- - (github.event_name == 'workflow_run' - && github.event.workflow_run.name == 'release' - && github.event.workflow_run.conclusion == 'success') - || (github.event_name == 'workflow_dispatch' && inputs.mode == 'prd') - uses: YiAgent/OpenCI/.github/workflows/reusable/prd.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - app-name: ${{ vars.APP_NAME || github.event.repository.name }} - image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} - health-url: ${{ vars.PRD_HEALTH_URL }} - runner: blacksmith-32vcpu-ubuntu-2404 - deploy-type: ${{ vars.DEPLOY_TYPE || 'docker' }} - ssh-host: ${{ vars.PRD_SSH_HOST }} - ssh-user: ${{ vars.PRD_SSH_USER || 'deploy' }} - ssh-port: ${{ vars.PRD_SSH_PORT || '22' }} - deploy-mode: ${{ vars.DEPLOY_MODE || 'compose' }} - compose-file: ${{ vars.COMPOSE_FILE || 'docker-compose.yml' }} - compose-project-dir: ${{ vars.COMPOSE_PROJECT_DIR || '~' }} - secrets: inherit - - # ────────────────────────────────────────────────────────────────────── - # stg-agent-test — autonomous L1-L4 tests after stg deploy. - # ────────────────────────────────────────────────────────────────────── - stg-agent-test: - if: >- - github.event_name == 'workflow_run' - && github.event.workflow_run.name == 'ci' - && github.event.workflow_run.conclusion == 'success' - needs: stg - name: Staging Agent Test L${{ matrix.level }} - runs-on: blacksmith-32vcpu-ubuntu-2404 - timeout-minutes: 30 - permissions: - contents: read - id-token: write - strategy: - fail-fast: false - matrix: - level: [1, 2, 3, 4] - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - id: gate - env: - K: ${{ secrets.ANTHROPIC_API_KEY }} - run: | - if [ -z "$K" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "::notice title=Agent Test Skipped::ANTHROPIC_API_KEY missing" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - if: steps.gate.outputs.skip != 'true' - uses: ./actions/stg/agent-test - with: - level: ${{ matrix.level }} - health-url: ${{ vars.STG_HEALTH_URL }} - image-digest: ${{ vars.STG_IMAGE_DIGEST || 'unknown' }} - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} - - if: steps.gate.outputs.skip != 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: agent-test-L${{ matrix.level }} - path: triage/agent-test/ - retention-days: 14 - - # ────────────────────────────────────────────────────────────────────── - # poll — fire repository_dispatch when observation window passes. - # ────────────────────────────────────────────────────────────────────── - poll: - if: ${{ github.event_name == 'workflow_dispatch' && inputs.mode == 'poll' }} - name: Poll PRD Dispatch - runs-on: blacksmith-32vcpu-ubuntu-2404 - timeout-minutes: 5 - permissions: - contents: read - actions: write - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - id: poll - uses: ./actions/_common/poll-prd-dispatch - with: - github-token: ${{ github.token }} - - if: steps.poll.outputs.fired-count != '0' - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - FIRED: ${{ steps.poll.outputs.fired }} - run: | - set -euo pipefail - echo "$FIRED" | jq -c '.[]' | while IFS= read -r entry; do - payload="$(jq -nc --argjson e "$entry" '{ - event_type: "observe-window-complete", - client_payload: { - "image-digest": $e.image_digest, - "stg-image-digest": $e.stg_image_digest, - "stg-deploy-time": $e.stg_deploy_time, - "image-name": $e.image_name, - "app-name": $e.app_name - } - }')" - gh api -X POST "repos/${REPO}/dispatches" --input - <<<"$payload" - echo "::notice title=PRD Dispatched::digest=$(jq -r '.image_digest' <<<"$entry")" - done diff --git a/.github/workflows/docs.yml-e b/.github/workflows/docs.yml-e deleted file mode 100644 index d935c1a..0000000 --- a/.github/workflows/docs.yml-e +++ /dev/null @@ -1,41 +0,0 @@ -# docs.yml — event entry for the documentation quality + agentic sync pipeline. -name: docs - -on: - pull_request: - paths: - - "docs/**" - - "**/*.md" - push: - branches: [main] - schedule: - - cron: "0 9 * * 1" # every Monday at 09:00 UTC - release: - types: [published] - workflow_dispatch: - -permissions: {} - -concurrency: - group: docs-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - docs: - uses: YiAgent/OpenCI/.github/workflows/reusable/docs.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - build-cmd: ${{ vars.DOCS_BUILD_CMD || '' }} - docs-path: ${{ vars.DOCS_DIR || 'docs' }} - site-dir: ${{ vars.DOCS_SITE_DIR || 'site' }} - enable-agent: true - runner: blacksmith-32vcpu-ubuntu-2404 - model: ${{ vars.AI_MODEL || '' }} - secrets: - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} - permissions: - contents: write - pull-requests: write - issues: write - pages: write - id-token: write diff --git a/.github/workflows/issue-ops.yml-e b/.github/workflows/issue-ops.yml-e deleted file mode 100644 index 468f54a..0000000 --- a/.github/workflows/issue-ops.yml-e +++ /dev/null @@ -1,96 +0,0 @@ -# issue-ops.yml — thin event entry for the issue agent domain. -name: issue-ops - -on: - issues: - types: [opened, reopened, edited, closed] - issue_comment: - types: [created] - schedule: - - cron: "0 2 * * *" - repository_dispatch: - types: [linear-issue-started, sentry-issue] - workflow_dispatch: - inputs: - mode: - required: false - type: choice - default: lifecycle - options: [lifecycle, maintenance, ingest] - model: - required: false - type: string - default: "" - description: "AI model override (e.g. glm-4-flash). Leave empty to use vars.AI_MODEL or the reusable default." - -permissions: - contents: write - issues: write - pull-requests: write - id-token: write - actions: read - -concurrency: - group: issue-ops-${{ github.event.issue.number || github.event.client_payload.id || github.run_id }} - cancel-in-progress: false - -jobs: - lifecycle: - if: github.event_name == 'issues' || github.event_name == 'issue_comment' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: lifecycle - runner: blacksmith-32vcpu-ubuntu-2404 - model: ${{ vars.AI_MODEL || '' }} - secrets: - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} - sentry-token: ${{ secrets.SENTRY_TOKEN }} - linear-token: ${{ secrets.LINEAR_TOKEN }} - slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} - - ingest: - if: github.event_name == 'repository_dispatch' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: ingest - runner: blacksmith-32vcpu-ubuntu-2404 - model: ${{ vars.AI_MODEL || '' }} - secrets: - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} - sentry-token: ${{ secrets.SENTRY_TOKEN }} - linear-token: ${{ secrets.LINEAR_TOKEN }} - slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} - - maintenance: - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.mode == 'maintenance') - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: maintenance - runner: blacksmith-32vcpu-ubuntu-2404 - model: ${{ vars.AI_MODEL || '' }} - secrets: - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} - sentry-token: ${{ secrets.SENTRY_TOKEN }} - linear-token: ${{ secrets.LINEAR_TOKEN }} - slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} - - manual: - if: github.event_name == 'workflow_dispatch' && inputs.mode != 'maintenance' - uses: YiAgent/OpenCI/.github/workflows/reusable/issue.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: ${{ inputs.mode }} - runner: blacksmith-32vcpu-ubuntu-2404 - model: ${{ inputs.model || vars.AI_MODEL || '' }} - secrets: - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} - sentry-token: ${{ secrets.SENTRY_TOKEN }} - linear-token: ${{ secrets.LINEAR_TOKEN }} - slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - mcp-dispatch-token: ${{ secrets.MCP_DISPATCH_TOKEN }} diff --git a/.github/workflows/observability.yml-e b/.github/workflows/observability.yml-e deleted file mode 100644 index 8b61485..0000000 --- a/.github/workflows/observability.yml-e +++ /dev/null @@ -1,57 +0,0 @@ -# observability.yml — event entry for post-deploy observability. -# Canary watch (every 15 min), terraform drift (daily), and verify-fix -# (after prd deploy) all route to reusable/observability.yml. -name: observability - -on: - workflow_run: - workflows: [prd] - types: [completed] - repository_dispatch: - types: [observe-window-complete] - schedule: - - cron: "*/15 * * * *" # canary-watch - - cron: "0 4 * * *" # terraform-drift - workflow_dispatch: - inputs: - mode: - required: false - type: string - default: canary-watch - -permissions: - contents: write - issues: write - -concurrency: - group: observability-${{ github.event_name }}-${{ github.event.workflow_run.id || github.event.schedule || github.run_id }} - cancel-in-progress: false - -jobs: - observe-canary: - if: ${{ github.event_name == 'schedule' && github.event.schedule == '*/15 * * * *' }} - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: canary-watch - runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit - - observe-drift: - if: ${{ github.event_name == 'schedule' && github.event.schedule == '0 4 * * *' }} - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: terraform-drift - infra-dir: ${{ vars.INFRA_DIR || 'infrastructure' }} - runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit - - verify-fix: - if: >- - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') - || github.event_name == 'repository_dispatch' - || (github.event_name == 'workflow_dispatch' && inputs.mode == 'verify-fix') - uses: YiAgent/OpenCI/.github/workflows/reusable/observability.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: verify-fix - runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit diff --git a/.github/workflows/on-maintenance.yml-e b/.github/workflows/on-maintenance.yml-e deleted file mode 100644 index 76a3e8d..0000000 --- a/.github/workflows/on-maintenance.yml-e +++ /dev/null @@ -1,199 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# on-maintenance.yml — Unified entry: security sweeps + dependency intelligence. -# ───────────────────────────────────────────────────────────────────────────── -# Replaces security.yml. Trigger → mode mapping: -# -# schedule Mon 02:00 UTC → full (weekly deep sweep + agent analysis) -# schedule Mon 15:00 UTC → flag-audit (feature flag hygiene check) -# push / pull_request → pr-review (verify-sha integrity check only) -# workflow_dispatch → caller-chosen mode -# -# Why merged: Security (find CVEs) and Dependencies (fix CVEs) are two sides of -# the same problem. The agent stage sees both scan results and available updates -# in one pass, so it can write "upgrade X to Y to fix CVE-Z" instead of filing -# two disconnected items. -# -# What stays separate: dependencies.yml (pull_request_target for Renovate -# auto-merge) uses a distinct trust model and MUST remain its own file. -# ───────────────────────────────────────────────────────────────────────────── -name: maintenance - -on: - schedule: - - cron: "0 2 * * 1" # Monday 02:00 UTC — full sweep - - cron: "0 15 * * 1" # Monday 23:00 BJT — flag audit - push: - branches: [main] - paths: - - "manifest.yml" - - "actions/**/action.yml" - - ".github/workflows/**.yml" - pull_request: - paths: - - "manifest.yml" - - "actions/**/action.yml" - - ".github/workflows/**.yml" - workflow_dispatch: - inputs: - mode: - description: "Execution mode" - required: false - type: choice - default: full - options: - - full - - scan-only - - deps-only - - flag-audit - -permissions: - contents: read - security-events: write - packages: read - id-token: write - issues: write - actions: read - -concurrency: - group: maintenance-${{ github.event_name }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - # ── Mode resolution ───────────────────────────────────────────────────────── - resolve-mode: - name: Resolve Mode - runs-on: blacksmith-32vcpu-ubuntu-2404 - timeout-minutes: 2 - outputs: - mode: ${{ steps.resolve.outputs.mode }} - openci-ref: ${{ steps.openci-ref.outputs.ref }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - - name: Determine mode - id: resolve - env: - EVENT: ${{ github.event_name }} - SCHEDULE: ${{ github.event.schedule }} - INPUT_MODE: ${{ inputs.mode }} - run: | - set -euo pipefail - case "$EVENT" in - workflow_dispatch) - echo "mode=${INPUT_MODE:-full}" >> "$GITHUB_OUTPUT" - ;; - pull_request|push) - echo "mode=pr-review" >> "$GITHUB_OUTPUT" - ;; - schedule) - case "$SCHEDULE" in - "0 15 * * 1") echo "mode=flag-audit" >> "$GITHUB_OUTPUT" ;; - "0 2 * * 1") echo "mode=full" >> "$GITHUB_OUTPUT" ;; - *) echo "mode=deps-only" >> "$GITHUB_OUTPUT" ;; - esac - ;; - *) echo "mode=full" >> "$GITHUB_OUTPUT" ;; - esac - shell: bash - - - name: Resolve OpenCI ref - id: openci-ref - env: - WORKFLOW_REF: ${{ github.workflow_ref }} - shell: bash - run: | - REF="${WORKFLOW_REF##*@}" - REF="${REF#refs/heads/}" - REF="${REF#refs/tags/}" - echo "ref=$REF" >> "$GITHUB_OUTPUT" - - # ── Full maintenance sweep (full | scan-only | deps-only) ─────────────────── - maintenance: - name: Maintenance - needs: resolve-mode - if: | - !contains(fromJSON('["pr-review","flag-audit"]'), - needs.resolve-mode.outputs.mode) - uses: YiAgent/OpenCI/.github/workflows/reusable/maintenance.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: ${{ needs.resolve-mode.outputs.mode }} - openci-ref: ${{ needs.resolve-mode.outputs.openci-ref }} - image-ref: ${{ vars.IMAGE_REF || '' }} - runner: blacksmith-32vcpu-ubuntu-2404 - secrets: inherit - - # ── SHA integrity check on push / PR ──────────────────────────────────────── - verify-sha: - name: Verify SHA Consistency - needs: resolve-mode - if: needs.resolve-mode.outputs.mode == 'pr-review' - runs-on: blacksmith-32vcpu-ubuntu-2404 - timeout-minutes: 5 - permissions: - contents: read - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - - name: Install yq - shell: bash - env: - YQ_VERSION: "4.44.6" - run: | - set -euo pipefail - if command -v yq >/dev/null 2>&1; then yq --version; exit 0; fi - sudo wget -qO /usr/local/bin/yq \ - "https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64" - sudo chmod +x /usr/local/bin/yq - yq --version - - - name: Verify SHA consistency - shell: bash - run: bash .github/scripts/verify-sha-consistency.sh - - # ── Feature flag audit (Monday BJT evening) ───────────────────────────────── - flag-audit: - name: Flag Audit - needs: resolve-mode - if: needs.resolve-mode.outputs.mode == 'flag-audit' - runs-on: blacksmith-32vcpu-ubuntu-2404 - timeout-minutes: 15 - permissions: - contents: read - issues: write - id-token: write - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - # Vendor OpenCI so the flag-audit composite can reach claude-harness. - - name: Resolve OpenCI ref - id: openci-ref - shell: bash - env: - WORKFLOW_REF: ${{ github.workflow_ref }} - run: | - REF="${WORKFLOW_REF##*@}" - REF="${REF#refs/heads/}" - REF="${REF#refs/tags/}" - echo "ref=$REF" >> "$GITHUB_OUTPUT" - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: - repository: YiAgent/OpenCI - ref: ${{ steps.openci-ref.outputs.ref }} - path: .openci - persist-credentials: false - - - uses: ./.openci/actions/_common/flag-audit - with: - github-token: ${{ github.token }} - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} diff --git a/.github/workflows/pull-request.yml-e b/.github/workflows/pull-request.yml-e deleted file mode 100644 index a1afc56..0000000 --- a/.github/workflows/pull-request.yml-e +++ /dev/null @@ -1,39 +0,0 @@ -# pull-request.yml — event entry that fires the PR quality gate on every PR. -# OpenCI no longer ships standalone "pr-agent" task workflows — consumers -# write their own thin scheduled / event-driven workflows that call -# agent.yml directly with task + prompt-path + context (see EvolveCI -# repo for the canonical pattern). This shim only runs the deterministic -# PR checks via pr.yml. -name: pull-request - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - workflow_dispatch: - -permissions: - contents: read - actions: read - checks: write - issues: write - pull-requests: write - security-events: write - id-token: write - statuses: write - packages: read - -concurrency: - group: pull-request-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: false - -jobs: - checks: - uses: YiAgent/OpenCI/.github/workflows/reusable/pr.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - enable-ai-review: true - enable-eval: true - runner: blacksmith-32vcpu-ubuntu-2404 - model: ${{ vars.AI_MODEL || '' }} - secrets: - anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }} - api-base-url: ${{ secrets.ANTHROPIC_BASE_URL }} diff --git a/.github/workflows/release.yml-e b/.github/workflows/release.yml-e deleted file mode 100644 index 1986939..0000000 --- a/.github/workflows/release.yml-e +++ /dev/null @@ -1,31 +0,0 @@ -# release.yml — event entry for tagged releases. -# `git push origin v*` runs marketplace + docker release jobs by default. -name: release - -on: - push: - tags: ["v*"] - workflow_dispatch: - inputs: - mode: - required: false - type: string - default: both - -permissions: - contents: write - packages: write - id-token: write - attestations: write -concurrency: - group: release-${{ github.ref }} - cancel-in-progress: false - -jobs: - release: - uses: YiAgent/OpenCI/.github/workflows/reusable/release.yml@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - mode: ${{ inputs.mode || 'both' }} - image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }} - registry: ghcr.io - runner: blacksmith-32vcpu-ubuntu-2404 diff --git a/.github/workflows/reusable/ci.yml-e b/.github/workflows/reusable/ci.yml-e deleted file mode 100644 index e19d51f..0000000 --- a/.github/workflows/reusable/ci.yml-e +++ /dev/null @@ -1,600 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# ci.yml — Merge-to-main build pipeline (reusable workflow, SPEC §5.3). -# ───────────────────────────────────────────────────────────────────────────── -# 4-Stage Design: -# -# Stage 1 — Build (deterministic) -# preflight → detect-language → build-docker -# -# Stage 2 — Verify (parallel, after build) -# scan-image ─┐ -# sign-image ├─ (parallel) -# verify-sha │ -# generate-sbom┤ -# check-migration (if run-migration) -# eval-smoke (if enable-ai-smoke) -# -# Stage 3 — Agent (failure-only, skipped entirely on green builds) -# enrich → aggregate Stage 2 results into ci-context.json artifact -# agent → ci-failure-analyst skill (only when has-failures == true) -# -# Stage 4 — Dispatch -# execute → evaluate deploy gate, trigger deploy workflow, write summary -# -# Outputs `image-digest` + `deploy-time` for downstream stg.yml / prd.yml. -# Concurrency does NOT cancel-in-progress: every main commit deserves a -# fully completed build (they end up tagged as :sha-<7> regardless). -# -# Security: caller-supplied `image-name` flows into docker tags via the -# composite atom; no shell interpolation in this file. -# ───────────────────────────────────────────────────────────────────────────── -name: ci - -on: - workflow_call: - inputs: - model: - description: Override the AI model name. Defaults to the Anthropic Claude model when empty. - required: false - type: string - default: "" - openci-ref: - description: OpenCI ref to vendor for ./.openci/* references (default main; use a tag like v2 in production). - required: false - type: string - default: main - runner: - description: Runner label to use for all jobs in this workflow. Defaults to ubuntu-latest for open-source compatibility. - required: false - type: string - default: ubuntu-latest - language: - type: string - required: false - default: "" - registry: - type: string - required: false - default: ghcr.io - image-name: - type: string - required: true - enable-ai-smoke: - type: boolean - required: false - default: false - run-migration: - type: boolean - required: false - default: false - enable-failure-agent: - description: Run the AI failure-analysis agent when CI failures are detected. - type: boolean - required: false - default: true - auto-deploy: - description: Automatically trigger the deploy workflow on a clean build. - type: boolean - required: false - default: false - deploy-workflow: - description: Workflow file to dispatch for deployment (used when auto-deploy is true). - type: string - required: false - default: "deploy.yml" - deploy-environment: - description: Environment name passed to the deploy workflow. - type: string - required: false - default: "staging" - secrets: - api-base-url: - description: Custom Anthropic-compatible base URL. Leave unset to use the default Anthropic endpoint. - required: false - registry-token: - description: "Token used to push to the registry. Defaults to GITHUB_TOKEN at the use site." - required: false - anthropic-api-key: - required: false - outputs: - image-digest: - description: sha256:... digest of the pushed image. - value: ${{ jobs.build-docker.outputs.image-digest }} - deploy-time: - description: ISO 8601 build completion timestamp. - value: ${{ jobs.build-docker.outputs.completed-at }} - deploy-ready: - description: "'true' when the build passed all gates and is safe to deploy." - value: ${{ jobs.execute.outputs.deploy-ready }} - -permissions: {} - -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: false - -jobs: - # ── Stage 1: Build ────────────────────────────────────────────────────────── - preflight: - name: Preflight - runs-on: ${{ inputs.runner }} - timeout-minutes: 2 - permissions: - contents: read - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - name: Probe secrets - env: - REGISTRY_TOKEN: ${{ secrets.registry-token || github.token }} - ANTHROPIC_API_KEY: ${{ secrets.anthropic-api-key }} - run: | - bash .github/scripts/preflight-secrets.sh \ - --required "REGISTRY_TOKEN" \ - --optional "ANTHROPIC_API_KEY" - - detect-language: - name: Detect Language - needs: preflight - runs-on: ${{ inputs.runner }} - timeout-minutes: 2 - permissions: - contents: read - outputs: - language: ${{ steps.detect.outputs.language }} - package-manager: ${{ steps.detect.outputs.package-manager }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - id: detect - shell: bash - env: - OVERRIDE: ${{ inputs.language }} - run: bash ./.openci/actions/_common/detect-language/detect.sh "$GITHUB_WORKSPACE" - - build-docker: - name: Build Docker - needs: detect-language - runs-on: ${{ inputs.runner }} - timeout-minutes: 30 - permissions: - contents: read - packages: write - id-token: write - outputs: - image-digest: ${{ steps.build.outputs.image-digest }} - image-tag-sha: ${{ steps.build.outputs.image-tag-sha }} - completed-at: ${{ steps.build.outputs.completed-at }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - id: build - uses: ./.openci/actions/ci/build-docker - with: - image-name: ${{ inputs.image-name }} - registry: ${{ inputs.registry }} - registry-token: ${{ secrets.registry-token || github.token }} - - # ── Stage 2: Verify (parallel) ────────────────────────────────────────────── - scan-image: - name: Scan Image - needs: build-docker - runs-on: ${{ inputs.runner }} - timeout-minutes: 15 - permissions: - contents: read - security-events: write - outputs: - vulnerabilities-found: ${{ steps.scan.outputs.vulnerabilities-found }} - critical-count: ${{ steps.scan.outputs.critical-count }} - high-count: ${{ steps.scan.outputs.high-count }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - id: scan - uses: ./.openci/actions/ci/scan-image - with: - image-ref: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}@${{ needs.build-docker.outputs.image-digest }} - - sign-image: - name: Sign Image - needs: build-docker - runs-on: ${{ inputs.runner }} - timeout-minutes: 10 - permissions: - contents: read - packages: write - id-token: write - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 - with: - registry: ${{ inputs.registry }} - username: ${{ github.actor }} - password: ${{ secrets.registry-token || github.token }} - - uses: ./.openci/actions/ci/sign-image - with: - image-ref: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}@${{ needs.build-docker.outputs.image-digest }} - - generate-sbom: - name: Generate SBOM - needs: build-docker - runs-on: ${{ inputs.runner }} - timeout-minutes: 5 - permissions: - contents: read - outputs: - sbom-ref: ${{ steps.ref.outputs.sbom-ref }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - name: Record SBOM attestation reference - id: ref - shell: bash - env: - IMAGE_REF: ${{ inputs.registry }}/${{ github.repository_owner }}/${{ inputs.image-name }}@${{ needs.build-docker.outputs.image-digest }} - run: | - echo "sbom-ref=${IMAGE_REF}" >> "$GITHUB_OUTPUT" - echo "::notice title=SBOM::SPDX SBOM attestation attached to ${IMAGE_REF}" - - check-migration: - name: Check Migration - needs: build-docker - if: inputs.run-migration == true - runs-on: ${{ inputs.runner }} - timeout-minutes: 10 - permissions: - contents: read - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - uses: ./.openci/actions/ci/check-migration - with: - migration-cmd: ${{ vars.MIGRATION_DRY_RUN_CMD || 'false' }} - - eval-smoke: - name: AI Smoke Eval - needs: build-docker - if: inputs.enable-ai-smoke == true - runs-on: ${{ inputs.runner }} - timeout-minutes: 15 - permissions: - contents: read - pull-requests: write - id-token: write - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - uses: ./.openci/actions/ci/eval-smoke - with: - image-digest: ${{ needs.build-docker.outputs.image-digest }} - anthropic-api-key: ${{ secrets.anthropic-api-key }} - api-base-url: ${{ secrets.api-base-url }} - model: ${{ inputs.model }} - - verify-sha: - name: Verify SHA Consistency - needs: build-docker - runs-on: ${{ inputs.runner }} - timeout-minutes: 5 - permissions: - contents: read - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Install yq - shell: bash - run: | - set -euo pipefail - if ! command -v yq >/dev/null 2>&1; then - sudo wget -qO /usr/local/bin/yq \ - https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 - sudo chmod +x /usr/local/bin/yq - fi - yq --version - - name: Run verify-sha-consistency.sh - shell: bash - run: bash .github/scripts/verify-sha-consistency.sh - - # ── Stage 3: Agent (skipped entirely on green builds) ─────────────────────── - enrich: - name: Enrich Failure Context - needs: - - build-docker - - scan-image - - sign-image - - verify-sha - - generate-sbom - - check-migration - - eval-smoke - if: always() - runs-on: ${{ inputs.runner }} - timeout-minutes: 5 - permissions: - contents: read - actions: read - outputs: - has-failures: ${{ steps.summarize.outputs.has-failures }} - deploy-blocked: ${{ steps.summarize.outputs.deploy-blocked }} - agent-context: ${{ steps.summarize.outputs.agent-context }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - name: Summarize Stage 2 results - id: summarize - shell: bash - env: - BUILD_RESULT: ${{ needs.build-docker.result }} - SCAN_RESULT: ${{ needs.scan-image.result }} - SIGN_RESULT: ${{ needs.sign-image.result }} - VERIFY_RESULT: ${{ needs.verify-sha.result }} - SBOM_RESULT: ${{ needs.generate-sbom.result }} - MIGRATION_RESULT: ${{ needs.check-migration.result }} - SMOKE_RESULT: ${{ needs.eval-smoke.result }} - CRITICAL_CVE: ${{ needs.scan-image.outputs.critical-count }} - HIGH_CVE: ${{ needs.scan-image.outputs.high-count }} - IMAGE_DIGEST: ${{ needs.build-docker.outputs.image-digest }} - RUN_ID: ${{ github.run_id }} - REPO: ${{ github.repository }} - COMMIT: ${{ github.sha }} - run: | - set -euo pipefail - - BUILD_PASSED="false" - [ "$BUILD_RESULT" = "success" ] && BUILD_PASSED="true" - - SHA_OK="false" - [ "$VERIFY_RESULT" = "success" ] && SHA_OK="true" - - CRITICAL=${CRITICAL_CVE:-0} - HIGH=${HIGH_CVE:-0} - - DEPLOY_BLOCKED="false" - HAS_FAILURES="false" - - if [ "$BUILD_PASSED" = "false" ]; then - HAS_FAILURES="true"; DEPLOY_BLOCKED="true" - fi - if [ "${CRITICAL}" -gt 0 ]; then - HAS_FAILURES="true"; DEPLOY_BLOCKED="true" - fi - if [ "$SCAN_RESULT" = "failure" ]; then - HAS_FAILURES="true"; DEPLOY_BLOCKED="true" - fi - if [ "$SIGN_RESULT" = "failure" ]; then - HAS_FAILURES="true"; DEPLOY_BLOCKED="true" - fi - if [ "$SBOM_RESULT" = "failure" ]; then - HAS_FAILURES="true"; DEPLOY_BLOCKED="true" - fi - if [ "$SHA_OK" = "false" ]; then - HAS_FAILURES="true" - fi - if [ "${HIGH}" -gt 0 ]; then - HAS_FAILURES="true" - fi - if [ "$MIGRATION_RESULT" = "failure" ]; then - HAS_FAILURES="true"; DEPLOY_BLOCKED="true" - fi - if [ "$SMOKE_RESULT" = "failure" ]; then - HAS_FAILURES="true" - fi - - CONTEXT=$(jq -cn \ - --arg build_result "$BUILD_RESULT" \ - --arg build_passed "$BUILD_PASSED" \ - --arg critical_cve "$CRITICAL" \ - --arg high_cve "$HIGH" \ - --arg sha_ok "$SHA_OK" \ - --arg migration_result "$MIGRATION_RESULT" \ - --arg smoke_result "$SMOKE_RESULT" \ - --arg image_digest "$IMAGE_DIGEST" \ - --arg run_id "$RUN_ID" \ - --arg repo "$REPO" \ - --arg commit "$COMMIT" \ - '{build_result:$build_result, build_passed:$build_passed, - critical_cve:$critical_cve, high_cve:$high_cve, - sha_ok:$sha_ok, migration_result:$migration_result, - smoke_result:$smoke_result, image_digest:$image_digest, - run_id:$run_id, repo:$repo, commit:$commit, - failure_context:{logs_available:false, - note:"Detailed per-job logs are available in the Actions UI at the run URL above. The analyst should use gh api to fetch logs when needed rather than relying on embedded log content."}}') - - { - echo "has-failures=${HAS_FAILURES}" - echo "deploy-blocked=${DEPLOY_BLOCKED}" - echo "agent-context=${CONTEXT}" - } >> "$GITHUB_OUTPUT" - - echo "::notice title=CI Gate::has-failures=${HAS_FAILURES} deploy-blocked=${DEPLOY_BLOCKED} critical=${CRITICAL} high=${HIGH}" - - - name: Write ci-context.json artifact - shell: bash - env: - CONTEXT: ${{ steps.summarize.outputs.agent-context }} - run: printf '%s\n' "$CONTEXT" > ci-context.json - - - name: Upload ci-context artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ci-context - path: ci-context.json - retention-days: 7 - - agent: - name: CI Failure Analyst - needs: enrich - if: >- - needs.enrich.result == 'success' && - inputs.enable-failure-agent == true && - needs.enrich.outputs.has-failures == 'true' - runs-on: ${{ inputs.runner }} - timeout-minutes: 15 - permissions: - contents: read - issues: write - actions: read - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - with: { persist-credentials: false } - - name: Resolve OpenCI ref and checkout - uses: YiAgent/OpenCI/actions/_common/resolve-openci@ebe8fca3260dce68d34d51b74703169e776bc72d - with: - openci-ref: ${{ inputs.openci-ref }} - - name: Download ci-context artifact - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 - with: - name: ci-context - - name: Run CI failure analyst - uses: ./.openci/actions/_common/claude-harness - with: - task: "ci-failure-analyst" - anthropic-api-key: ${{ secrets.anthropic-api-key }} - api-base-url: ${{ secrets.api-base-url }} - model: ${{ inputs.model }} - allowed-tools: "Bash" - github-token: ${{ github.token }} - openci-ref: ${{ inputs.openci-ref }} - - # ── Stage 4: Dispatch ──────────────────────────────────────────────────────── - execute: - name: Execute Dispatch - needs: - - build-docker - - scan-image - - sign-image - - verify-sha - - generate-sbom - - check-migration - - eval-smoke - - enrich - - agent - if: always() - runs-on: ${{ inputs.runner }} - timeout-minutes: 5 - permissions: - actions: write - contents: read - outputs: - deploy-ready: ${{ steps.gate.outputs.deploy-ready }} - steps: - - uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 - with: { egress-policy: audit } - - name: Evaluate deploy gate - id: gate - shell: bash - env: - DEPLOY_BLOCKED: ${{ needs.enrich.outputs.deploy-blocked }} - AUTO_DEPLOY: ${{ inputs.auto-deploy }} - run: | - set -euo pipefail - DEPLOY_READY="false" - if [ "${DEPLOY_BLOCKED:-true}" = "false" ] && [ "$AUTO_DEPLOY" = "true" ]; then - DEPLOY_READY="true" - fi - echo "deploy-ready=${DEPLOY_READY}" >> "$GITHUB_OUTPUT" - echo "::notice title=Deploy Gate::deploy-ready=${DEPLOY_READY}" - - - name: Trigger deploy workflow - if: steps.gate.outputs.deploy-ready == 'true' - shell: bash - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - DEPLOY_WORKFLOW: ${{ inputs.deploy-workflow }} - DEPLOY_ENVIRONMENT: ${{ inputs.deploy-environment }} - IMAGE_DIGEST: ${{ needs.build-docker.outputs.image-digest }} - REF: ${{ github.ref_name }} - run: | - gh workflow run "$DEPLOY_WORKFLOW" \ - --repo "$REPO" \ - --ref "$REF" \ - --field "image-digest=${IMAGE_DIGEST}" \ - --field "environment=${DEPLOY_ENVIRONMENT}" - - - name: Write job summary - if: always() - shell: bash - env: - BUILD_RESULT: ${{ needs.build-docker.result }} - SCAN_RESULT: ${{ needs.scan-image.result }} - SIGN_RESULT: ${{ needs.sign-image.result }} - VERIFY_RESULT: ${{ needs.verify-sha.result }} - MIGN_RESULT: ${{ needs.check-migration.result }} - SMOKE_RESULT: ${{ needs.eval-smoke.result }} - CRITICAL_CVE: ${{ needs.scan-image.outputs.critical-count }} - HIGH_CVE: ${{ needs.scan-image.outputs.high-count }} - IMAGE_DIGEST: ${{ needs.build-docker.outputs.image-digest }} - DEPLOY_READY: ${{ steps.gate.outputs.deploy-ready }} - RUN_ID: ${{ github.run_id }} - REPO: ${{ github.repository }} - COMMIT: ${{ github.sha }} - run: | - CRITICAL=${CRITICAL_CVE:-0} - HIGH=${HIGH_CVE:-0} - - ok=":white_check_mark:"; fail=":x:"; skip=":white_circle:" - s() { case "$1" in success) printf '%s' "$ok";; skipped) printf '%s' "$skip";; *) printf '%s' "$fail";; esac; } - - { - printf "## CI Pipeline Summary\n\n" - printf "| Stage | Job | Result |\n" - printf "|-------|-----|--------|\n" - printf "| Build | build-docker | %s |\n" "$(s "$BUILD_RESULT")" - printf "| Verify | scan-image | %s (CRITICAL: %s, HIGH: %s) |\n" "$(s "$SCAN_RESULT")" "$CRITICAL" "$HIGH" - printf "| Verify | sign-image | %s |\n" "$(s "$SIGN_RESULT")" - printf "| Verify | verify-sha | %s |\n" "$(s "$VERIFY_RESULT")" - printf "| Verify | check-migration | %s |\n" "$(s "$MIGN_RESULT")" - printf "| Verify | eval-smoke | %s |\n" "$(s "$SMOKE_RESULT")" - printf "\n**Image digest:** \`%s\`\n" "$IMAGE_DIGEST" - printf "**Deploy ready:** \`%s\`\n" "$DEPLOY_READY" - printf "**Run:** [#%s](https://github.com/%s/actions/runs/%s)\n" "$RUN_ID" "$REPO" "$RUN_ID" - printf "**Commit:** \`%s\`\n" "${COMMIT:0:7}" - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.gitignore b/.gitignore index 33f4a38..5ef728e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ gate-context/ .history # act local testing -.act.env \ No newline at end of file +.act.env*.yml-e +*.yaml-e diff --git a/scripts/bump-self-sha.sh b/scripts/bump-self-sha.sh index f869d35..7e16913 100755 --- a/scripts/bump-self-sha.sh +++ b/scripts/bump-self-sha.sh @@ -95,7 +95,7 @@ if [ -z "$old_sha" ]; then die "YiAgent/OpenCI not found in manifest.yml .deps — add it manually first." fi -sed -i'' -e "s|${old_sha}|${new_sha}|g" "$MANIFEST" +perl -pi -e "s|\Q${old_sha}\E|${new_sha}|g" "$MANIFEST" info "Updated manifest.yml" # ── 5. Update all workflow files that reference the old SHA ────────────────── @@ -103,7 +103,7 @@ info "Updated manifest.yml" updated=0 while IFS= read -r -d '' f; do if grep -q "$old_sha" "$f" 2>/dev/null; then - sed -i'' -e "s|${old_sha}|${new_sha}|g" "$f" + perl -pi -e "s|\Q${old_sha}\E|${new_sha}|g" "$f" info "Updated $f" updated=$((updated + 1)) fi