From d579fb9c3cd97fa14312c3f25bdb88a4f7d1ee57 Mon Sep 17 00:00:00 2001 From: Chummy Date: Wed, 25 Feb 2026 10:33:56 +0000 Subject: [PATCH] feat(ci): bridge canary abort to rollback guard dispatch --- .github/workflows/ci-canary-gate.yml | 84 ++++++++++++++++++++++++++ docs/ci-map.md | 6 +- docs/operations/canary-gate-runbook.md | 20 ++++++ docs/release-process.md | 6 ++ 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-canary-gate.yml b/.github/workflows/ci-canary-gate.yml index 8fe7428dc..0d8051fb3 100644 --- a/.github/workflows/ci-canary-gate.yml +++ b/.github/workflows/ci-canary-gate.yml @@ -46,6 +46,24 @@ on: required: true default: false type: boolean + trigger_rollback_on_abort: + description: "Automatically dispatch CI Rollback Guard when canary decision is abort" + required: true + default: true + type: boolean + rollback_branch: + description: "Rollback integration branch used by CI Rollback Guard dispatch" + required: true + default: dev + type: choice + options: + - dev + - main + rollback_target_ref: + description: "Optional explicit rollback target ref passed to CI Rollback Guard" + required: false + default: "" + type: string fail_on_violation: description: "Fail on policy violations" required: true @@ -71,6 +89,9 @@ jobs: mode: ${{ steps.inputs.outputs.mode }} candidate_tag: ${{ steps.inputs.outputs.candidate_tag }} candidate_sha: ${{ steps.inputs.outputs.candidate_sha }} + trigger_rollback_on_abort: ${{ steps.inputs.outputs.trigger_rollback_on_abort }} + rollback_branch: ${{ steps.inputs.outputs.rollback_branch }} + rollback_target_ref: ${{ steps.inputs.outputs.rollback_target_ref }} decision: ${{ steps.extract.outputs.decision }} ready_to_execute: ${{ steps.extract.outputs.ready_to_execute }} steps: @@ -92,6 +113,9 @@ jobs: crash_rate="0.0" p95_latency_ms="0" sample_size="0" + trigger_rollback_on_abort="true" + rollback_branch="dev" + rollback_target_ref="" fail_on_violation="true" if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then @@ -102,6 +126,9 @@ jobs: crash_rate="${{ github.event.inputs.crash_rate || '0.0' }}" p95_latency_ms="${{ github.event.inputs.p95_latency_ms || '0' }}" sample_size="${{ github.event.inputs.sample_size || '0' }}" + trigger_rollback_on_abort="${{ github.event.inputs.trigger_rollback_on_abort || 'true' }}" + rollback_branch="${{ github.event.inputs.rollback_branch || 'dev' }}" + rollback_target_ref="${{ github.event.inputs.rollback_target_ref || '' }}" fail_on_violation="${{ github.event.inputs.fail_on_violation || 'true' }}" else git fetch --tags --force origin @@ -119,6 +146,9 @@ jobs: echo "crash_rate=${crash_rate}" echo "p95_latency_ms=${p95_latency_ms}" echo "sample_size=${sample_size}" + echo "trigger_rollback_on_abort=${trigger_rollback_on_abort}" + echo "rollback_branch=${rollback_branch}" + echo "rollback_target_ref=${rollback_target_ref}" echo "fail_on_violation=${fail_on_violation}" } >> "$GITHUB_OUTPUT" @@ -205,6 +235,7 @@ jobs: timeout-minutes: 10 permissions: contents: write + actions: write steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 @@ -237,3 +268,56 @@ jobs: source_sha: context.sha } }); + + - name: Trigger rollback guard workflow on abort + if: needs.canary-plan.outputs.decision == 'abort' && needs.canary-plan.outputs.trigger_rollback_on_abort == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const rollbackBranch = "${{ needs.canary-plan.outputs.rollback_branch }}" || "dev"; + const rollbackTargetRef = `${{ needs.canary-plan.outputs.rollback_target_ref }}`.trim(); + const workflowRef = process.env.GITHUB_REF_NAME || "dev"; + + const inputs = { + branch: rollbackBranch, + mode: "execute", + allow_non_ancestor: "false", + fail_on_violation: "true", + create_marker_tag: "true", + emit_repository_dispatch: "true", + }; + + if (rollbackTargetRef.length > 0) { + inputs.target_ref = rollbackTargetRef; + } + + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: "ci-rollback.yml", + ref: workflowRef, + inputs, + }); + + - name: Publish rollback trigger summary + if: needs.canary-plan.outputs.decision == 'abort' + shell: bash + run: | + set -euo pipefail + if [ "${{ needs.canary-plan.outputs.trigger_rollback_on_abort }}" = "true" ]; then + { + echo "### Canary Abort Rollback Trigger" + echo "- CI Rollback Guard dispatch: triggered" + echo "- Rollback branch: \`${{ needs.canary-plan.outputs.rollback_branch }}\`" + if [ -n "${{ needs.canary-plan.outputs.rollback_target_ref }}" ]; then + echo "- Rollback target ref: \`${{ needs.canary-plan.outputs.rollback_target_ref }}\`" + else + echo "- Rollback target ref: _auto (latest release tag strategy)_" + fi + } >> "$GITHUB_STEP_SUMMARY" + else + { + echo "### Canary Abort Rollback Trigger" + echo "- CI Rollback Guard dispatch: skipped (trigger_rollback_on_abort=false)" + } >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/docs/ci-map.md b/docs/ci-map.md index 14302a740..d30791c96 100644 --- a/docs/ci-map.md +++ b/docs/ci-map.md @@ -53,7 +53,7 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u - `.github/workflows/ci-supply-chain-provenance.yml` (`CI Supply Chain Provenance`) - Purpose: release-fast artifact provenance statement generation + keyless signature bundle for supply-chain traceability - `.github/workflows/ci-rollback.yml` (`CI Rollback Guard`) - - Purpose: deterministic rollback plan generation with guarded execute mode (manual), marker-tag option, and rollback audit artifacts + - Purpose: deterministic rollback plan generation with guarded execute mode, marker-tag option, rollback audit artifacts, and dispatch contract for canary-abort auto-triggering - `.github/workflows/sec-vorpal-reviewdog.yml` (`Sec Vorpal Reviewdog`) - Purpose: manual secure-coding feedback scan for supported non-Rust files (`.py`, `.js`, `.jsx`, `.ts`, `.tsx`) using reviewdog annotations - Noise control: excludes common test/fixture paths and test file patterns by default (`include_tests=false`) @@ -62,7 +62,7 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u - `.github/workflows/pub-prerelease.yml` (`Pub Pre-release`) - Purpose: validate alpha/beta/rc stage transitions, enforce tag/version integrity, and optionally publish GitHub prerelease assets - `.github/workflows/ci-canary-gate.yml` (`CI Canary Gate`) - - Purpose: evaluate canary metrics against policy thresholds (`promote` / `hold` / `abort`) with auditable artifacts and guarded execute mode + - Purpose: evaluate canary metrics against policy thresholds (`promote` / `hold` / `abort`) with auditable artifacts, guarded execute mode, and optional auto-dispatch to `CI Rollback Guard` on `abort` - `.github/workflows/docs-deploy.yml` (`Docs Deploy`) - Purpose: docs quality checks + preview artifacts + GitHub Pages production deployment lane - `.github/workflows/pub-homebrew-core.yml` (`Pub Homebrew Core`) @@ -151,7 +151,7 @@ Merge-blocking checks should stay small and deterministic. Optional checks are u 18. Feature-combo regressions: inspect `.github/workflows/feature-matrix.yml` summary artifact and lane JSON reports. 19. Nightly integration drift: inspect `.github/workflows/nightly-all-features.yml` summary and lane owner mapping. 20. Pre-release stage gate failures: inspect `.github/workflows/pub-prerelease.yml` guard artifact (`prerelease-guard.json`). -21. Canary gate hold/abort decisions: inspect `.github/workflows/ci-canary-gate.yml` guard artifact (`canary-guard.json`). +21. Canary gate hold/abort decisions: inspect `.github/workflows/ci-canary-gate.yml` guard artifact (`canary-guard.json`); on `abort`, verify rollback dispatch summary and follow-up `ci-rollback` run. 22. Docs deploy failures: inspect `.github/workflows/docs-deploy.yml` quality lane + preview/deploy artifacts. ## Maintenance Rules diff --git a/docs/operations/canary-gate-runbook.md b/docs/operations/canary-gate-runbook.md index 099be9b77..0ffa7b88f 100644 --- a/docs/operations/canary-gate-runbook.md +++ b/docs/operations/canary-gate-runbook.md @@ -10,6 +10,9 @@ Policy: `.github/release/canary-policy.json` - observed crash rate - observed p95 latency - observed sample size +- `trigger_rollback_on_abort` (workflow_dispatch only, default `true`) +- `rollback_branch` (workflow_dispatch only, default `dev`) +- `rollback_target_ref` (optional explicit rollback target ref) ## Decision Model @@ -22,6 +25,22 @@ Policy: `.github/release/canary-policy.json` - `dry-run`: generate decision + artifacts only - `execute`: allow marker tag + optional repository dispatch +## Abort-to-Rollback Integration + +When `decision=abort` and `trigger_rollback_on_abort=true`, `CI Canary Gate` dispatches `.github/workflows/ci-rollback.yml` automatically with guarded execute inputs. + +Dispatched rollback defaults: + +- `branch`: workflow input `rollback_branch` (default `dev`) +- `mode`: `execute` +- `allow_non_ancestor`: `false` +- `fail_on_violation`: `true` +- `create_marker_tag`: `true` +- `emit_repository_dispatch`: `true` +- `target_ref`: optional (`rollback_target_ref`), otherwise rollback guard uses latest release tag strategy + +The canary run summary emits a `Canary Abort Rollback Trigger` section to make dispatch behavior auditable. + ## Artifacts - `canary-guard.json` @@ -33,3 +52,4 @@ Policy: `.github/release/canary-policy.json` 1. Use `dry-run` first for every candidate. 2. Never execute with sample size below policy minimum. 3. For `abort`, include root-cause summary in release issue and keep candidate blocked. +4. For `abort` with auto-trigger enabled, verify the linked `CI Rollback Guard` run completed and review `ci-rollback-plan` artifacts. diff --git a/docs/release-process.md b/docs/release-process.md index fdc0f3f2b..30186e358 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -117,6 +117,12 @@ Decision output: - `hold`: insufficient evidence or soft breach - `abort`: hard threshold breach +Abort integration: + +- In `execute` mode, if decision is `abort` and `trigger_rollback_on_abort=true`, canary gate dispatches `CI Rollback Guard` automatically. +- Default rollback branch is `dev` (override with `rollback_branch`). +- Optional explicit rollback target can be passed via `rollback_target_ref`. + ### 5.2) Pre-release stage progression (alpha/beta/rc) For staged release confidence: