Raxx · internal docs

internal · gated

Woodpecker CI migration table — all 86 GHA workflows

Date: 2026-07-04 UTC ADR: docs/architecture/adr/0135-woodpecker-ci-full-migration-phases3-5.md Scope: Every .github/workflows/*.yml file + 5 composite actions


Wave D status — 2026-07-07 (#4019)

Metric Count
Total GHA workflows 86
Retired (moved to .github/workflows-retired/) 51
Still active in .github/workflows/ (WP gap) 35
Double-run conflicts with WP 0

Retired buckets: Phase 2 (3) + Wave A (31) + RETIRE-GHA-NATIVE (6) + DISABLE/ios-ci (1) + Wave C confirmed (4) + Wave B crons batch 1 (6) (#4067/#4068)

WP gaps remaining: Wave B crons (13 remaining) + Wave C deploy gap (22) — Wave B batch 1 (#4067/#4068) ported 2026-07-07.

Wave B crons batch 1 status (2026-07-07, #4067/#4068):

GHA workflow WP pipeline Cron entry/schedule GHA retired
audit-integrity-cron.yml .woodpecker/audit-integrity-cron.yaml audit-integrity-cron-nightly (0 2 * * ) + audit-integrity-cron-monthly (30 3 1 * ) yes
audit-reconciler-cron.yml .woodpecker/audit-reconciler-cron.yaml audit-reconciler-cron (30 3 * * *) yes
account-merge-tombstone-cron.yml .woodpecker/account-merge-tombstone-cron.yaml account-merge-tombstone-cron (0 4 * * *) yes
trace-integrity-cron.yml .woodpecker/trace-integrity-cron.yaml trace-integrity-cron (0 2 * * 0, weekly Sun) yes
billing-retention-cron.yml .woodpecker/billing-retention-cron.yaml billing-retention-cron (0 3 * * *) yes
heroku-config-health-nightly.yml .woodpecker/heroku-config-health-nightly.yaml heroku-config-health-nightly (0 5 * * *) yes
billing-collector-cron.yml .woodpecker/billing-collector-cron.yaml NOT registered — PAUSED pending #2159 no — GHA stays active

billing-collector-cron.yml active on GHA: schedule was already commented out (PAUSED since 2026-05-15). WP YAML written for event: manual only. Cron registration and GHA retirement deferred until #2159 (Postgres migration) unblocks runtime verification.

Full execution record: docs/architecture/adr/0135-woodpecker-ci-full-migration-phases3-5.md § Wave D execution record.


How to read this table

Bucket: - MIGRATE — port to a .woodpecker/*.yaml pipeline; wave column shows which execution wave the port ships in. - RETIRE-GHA-NATIVE — workflow relies on a GHA-specific trigger or action (workflow_run, workflow_call, release-please) with no direct WP equivalent. Column "Replacement in WP" shows what covers the function. - DISABLE — operator-deferred per ADR-0134. File stays; no WP equivalent created until the deferred decision is revisited. - PORT-COMPOSITE — not a workflow file; a composite action ported to a reusable scripts/ci/wpc_* script used inline by migrated pipelines.

Wave: - Phase 2 (done) — pilot WP pipeline already running and green. - Wave A — PR gates, lint, light push CI, manual-dispatch workflows, and low-risk crons. Entry criterion: Phase 2 complete (✓). - Wave B — heavy CI (postgres service containers), remaining crons, e2e/ZAP tests, and nightly-security-scan full port. Entry criterion: Wave A 7-day soak + postgres service container smoke confirmed on AL2023. - Wave C — deploy/release pipeline, gatekeeper, promote-to-main. Entry criterion: Wave B parity + Console Ops Dispatch sub-card filed and in-flight. - Wave D — decommission (disable + archive GHA files). Entry criterion: Wave C 14-day prod soak.


Section 1 — PORT-COMPOSITE (5 composite actions)

These are .github/actions/*/action.yml files, not workflow files. They have no direct WP equivalent; each is ported to an inline scripts/ci/wpc_* script copied by value into every migrated pipeline that needs it.

GHA composite action WP replacement Wave Notes
.github/actions/load-vault-secrets scripts/ci/wpc_load_vault_secrets.py A Infisical Universal Auth REST; same-step source pattern (NOT inter-step via WOODPECKER_ENV_FILE — not set in WP v3.16.0 step context, confirmed build #6); see ADR-0135 §1 + docs/architecture/woodpecker-wpc-step-usage.md §1
.github/actions/notify-deploy-status scripts/ci/wpc_notify_deploy_status.py A Same HMAC callback curl logic; Console deploy-modal integration unchanged
.github/actions/emit-audit-event scripts/ci/wpc_emit_audit_event.py A Same vault → Raptor audit endpoint call
.github/actions/cloudflare-retry scripts/ci/wpc_cloudflare_retry.sh A Same retry loop (5 attempts, quadratic backoff)
.github/actions/check-deploy-freeze scripts/ci/wpc_check_deploy_freeze.sh A Same console freeze-check curl; break-glass env var pattern preserved

Section 2 — DISABLE (1 workflow)

GHA workflow file Bucket Notes
ios-ci.yml DISABLE Deferred per ADR-0134 locked constraint: no macOS runners in new stack. File kept; WP pipeline not created. Re-evaluate post-migration.

Section 3 — RETIRE-GHA-NATIVE (6 workflows)

GHA workflow file Reason Replacement in Woodpecker
release.yml Uses google-github-actions/release-please-action@v4 — GHA action with no WP equivalent gh release create step appended to .woodpecker/promote-release-to-main.yaml after successful release→main push; --generate-notes replaces structured CHANGELOG
slack-notify.yml Uses workflow_run trigger (GHA-native event; no WP equivalent); also targets "CI — main" which no longer exists post-ADR-0115 Inline on_failure Slack curl steps added to each WP pipeline that previously relied on this workflow for alerting
synth-probe-waitlist.yml Disabled 2026-06-25 (sends real Postmark email; needs non-reputation send path before re-enable). Formally retired; no WP pipeline created. None until the non-reputation send path is designed (separate card)
test-cloudflare-retry-action.yml Tests the cloudflare-retry GHA composite action; that action is being ported away. No WP equivalent needed. The WP port of the action (wpc_cloudflare_retry.sh) is tested by including it in the ci-pr or lint pipeline and verifying retry behavior
test-emit-audit-event-action.yml Same reasoning — tests GHA composite action being retired Same — absorbed into pipeline-level smoke testing of the WP port
test-notify-deploy-status.yml Same reasoning — tests GHA composite action being retired Same

Section 4 — MIGRATE: full per-workflow table

Phase 2 pilots (already running in WP; GHA counterpart still active during soak)

GHA workflow file WP target file WP trigger Wave Notes
alembic-version-cron.yml .woodpecker/alembic-version-check.yaml (exists) event: cronalembic-version-check (08:30 UTC daily) Phase 2 done Pilot complete; close out GHA version in Wave A decommission step
maxmind-asn-refresh.yml .woodpecker/maxmind-asn-refresh.yaml (exists) event: cronmaxmind-asn-refresh (02:15 UTC daily) Phase 2 done Pilot complete
nightly-security-scan.yml .woodpecker/security-scan-nightly.yaml (exists — partial) event: cronsecurity-scan-nightly (08:07 UTC daily) Wave B (full port) Pilot runs scanner steps only; issue-filing, GH API calls, Slack-on-failure, and raxx-ops-bot token mint not yet ported. Wave B completes the port.

Wave A — PR gates, lint, push CI, low-risk crons, manual dispatch

GHA workflow file WP target file Trigger mapping Vault secrets? Notes
antlers-next-ci.yml .woodpecker/antlers-next-ci.yaml GHA pull_request → WP event: pull_request; GHA push branches:[develop] → WP event: push, branch: develop No Vitest + TSC + lint for frontend/raxx-next/
bcp-vault-snapshot-daily.yml .woodpecker/bcp-vault-snapshot-daily.yaml GHA schedule → WP event: cron (02:00 UTC daily) Yes (vault export token) No postgres; vault access only
ci-boundary.yml .woodpecker/ci-boundary.yaml GHA push branches:[develop] → WP event: push, branch: develop No Boundary lint checks; no postgres
ci-console.yml .woodpecker/ci-console.yaml GHA push branches:[develop] + pull_request → WP event: [push,pull_request], branch: develop No Console Python lint + tests; no postgres service
ci-digest-cron.yml .woodpecker/ci-digest-cron.yaml GHA schedule → WP event: cron (09:00 UTC daily) Yes (Slack webhook) GitHub API calls only; no postgres
ci-pr.yml .woodpecker/ci-pr.yaml GHA pull_request → WP event: pull_request Yes (Alpaca secrets for smoke) Critical path — required status check swap in Wave A §4. Postgres service step split to Wave B; non-postgres steps migrate in Wave A
ci.yml .woodpecker/ci-develop.yaml GHA push branches:[develop] → WP event: push, branch: develop Yes (Alpaca secrets) Critical path. Postgres backend-tests-postgres job deferred to Wave B. Non-postgres jobs (detect-changes, frontend-tests, velvet-tests) migrate in Wave A
console-degraded-auto-file.yml .woodpecker/console-degraded-auto-file.yaml GHA schedule → WP event: cron (every 15 min) Yes (CF Access, GH token) Status check + auto-file; no postgres
cron-heartbeat-monitor.yml .woodpecker/cron-heartbeat-monitor.yaml GHA schedule → WP event: cron (hourly) No GitHub API heartbeat; no postgres
daily-bot-token-smoke.yml .woodpecker/daily-bot-token-smoke.yaml GHA schedule → WP event: cron (08:00 UTC daily) Yes (bot App keys) Bot token validation; no postgres
daily-card-groomer.yml .woodpecker/daily-card-groomer.yaml GHA schedule → WP event: cron (10:00 UTC daily) Yes (PM bot keys) GitHub API + vault; no postgres
drift-orchestrator-cron.yml .woodpecker/drift-orchestrator-cron.yaml GHA schedule → WP event: cron (04:00 UTC daily) Yes (vault) Config drift check; no postgres service
flag-drift-check.yml .woodpecker/flag-drift-check.yaml GHA schedule → WP event: cron (06:00 UTC daily) Yes (CF, console token) Flag reconciliation check; no postgres
heroku-admin.yml .woodpecker/heroku-admin.yaml GHA workflow_dispatch → WP event: manual Yes (Heroku key) Operator admin tool; no postgres
infra-ci-validate.yml .woodpecker/infra-ci-validate.yaml GHA pull_request + push → WP event: [pull_request,push] No Terraform validate; no postgres
launch-readiness-check.yml .woodpecker/launch-readiness-check.yaml GHA workflow_dispatch → WP event: manual Yes (multiple) Manual launch gate; no postgres
lint-cf-access-headers.yml .woodpecker/lint-cf-access-headers.yaml GHA pull_request → WP event: pull_request No Static lint
lint-cf-pages-deploy-uniqueness.yml .woodpecker/lint-cf-pages-deploy-uniqueness.yaml GHA pull_request → WP event: pull_request No Static lint
lint-cf-tokens.yml .woodpecker/lint-cf-tokens.yaml GHA pull_request → WP event: pull_request Yes (CF token check) CF token validation lint
lint-workflow-secret-names.yml .woodpecker/lint-workflow-secret-names.yaml GHA pull_request → WP event: pull_request No Static lint; note: when GHA workflows are retired, this lint target changes to .woodpecker/*.yaml files
migration-collision-check.yml .woodpecker/migration-collision-check.yaml GHA pull_request → WP event: pull_request No Required status check; see §4 swap
ops-mint-preview-invite.yml .woodpecker/ops-mint-preview-invite.yaml GHA workflow_dispatch → WP event: manual Yes (Velvet token) Manual operator tool
pii-scan.yml .woodpecker/pii-scan.yaml GHA pull_request → WP event: pull_request No PII pattern scan
postmark-delivery-monitor.yml .woodpecker/postmark-delivery-monitor.yaml GHA schedule → WP event: cron (08:45 UTC daily) Yes (Postmark token) Delivery monitor; was a Phase 2 pilot candidate
pr-preview.yml .woodpecker/pr-preview.yaml GHA pull_request → WP event: pull_request Yes (CF Pages token) Path-aware CF Pages preview deploys; deployments: write GHA permission replaced by explicit gh + CF API calls with bot token
queue-zero-dyno-monitor.yml .woodpecker/queue-zero-dyno-monitor.yaml GHA schedule → WP event: cron (every 30 min) Yes (Heroku key) Was a Phase 2 pilot candidate
reminders-second-factor-nightly.yml .woodpecker/reminders-second-factor-nightly.yaml GHA schedule → WP event: cron (09:00 UTC daily) Yes (Postmark) Second-factor reminder emails; no postgres
review-app-console.yml .woodpecker/review-app-console.yaml GHA pull_request → WP event: pull_request Yes (Heroku key) Heroku review app lifecycle; PR open/close events
sc-ident-6-smoke.yml .woodpecker/sc-ident-6-smoke.yaml GHA workflow_dispatch → WP event: manual Yes (bot App keys) Bot identity smoke; create-github-app-token Action replaced by inline wpc_load_vault_secrets.py + gh call
terraform-validate.yml .woodpecker/terraform-validate.yaml GHA pull_request + push → WP event: [pull_request,push] No terraform validate; no postgres
vcpkg-manifest-check.yml .woodpecker/vcpkg-manifest-check.yaml GHA pull_request → WP event: pull_request No vcpkg lockfile lint

Wave A total: 30 workflows + 2 Phase 2 pilot completions (alembic-version-cron, maxmind-asn-refresh)


Wave B — heavy CI, postgres service, remaining crons, e2e/ZAP

Entry criterion: Wave A 7-day soak + postgres:15 service container smoke green on Amazon Linux 2023 Docker agent.

GHA workflow file WP target file Trigger mapping Postgres? Notes
account-merge-tombstone-cron.yml .woodpecker/account-merge-tombstone-cron.yaml event: cron (04:00 UTC daily) Via Heroku run PORTED+RETIRED 2026-07-07 (#4068)
audit-integrity-cron.yml .woodpecker/audit-integrity-cron.yaml event: cron (02:00 UTC daily + 03:30 UTC 1st) Via Heroku run PORTED+RETIRED 2026-07-07 (#4067); two cron entries
audit-reconciler-cron.yml .woodpecker/audit-reconciler-cron.yaml event: cron (03:30 UTC daily) Via Heroku run PORTED+RETIRED 2026-07-07 (#4067)
bcp-smoke-monthly.yml .woodpecker/bcp-smoke-monthly.yaml event: cron (1st of month, 06:00 UTC) Via Heroku run
billing-collector-cron.yml .woodpecker/billing-collector-cron.yaml event: manual only Via Heroku run WP YAML written 2026-07-07 (#4068); cron NOT registered; GHA NOT retired — PAUSED pending #2159
billing-retention-cron.yml .woodpecker/billing-retention-cron.yaml event: cron (03:00 UTC daily) Via Heroku run PORTED+RETIRED 2026-07-07 (#4068)
ci-pr.yml (postgres step) .woodpecker/ci-pr.yaml (backend-tests-postgres step added) Part of Wave A WP file Local service Wave A ships the pipeline without the postgres step; Wave B adds services: postgres:15 block and the backend-tests-postgres step
ci.yml (backend-tests-postgres) .woodpecker/ci-develop.yaml (backend-tests-postgres step added) Part of Wave A WP file Local service Same as above — Wave B adds the services: block
e2e-smoke.yml .woodpecker/e2e-smoke.yaml event: cron (06:00 UTC daily) Via staging URL Playwright E2E; staging URL; no local postgres
freescout-backup.yml .woodpecker/freescout-backup.yaml event: cron (04:00 UTC daily) Via Heroku run
heroku-config-health-nightly.yml .woodpecker/heroku-config-health-nightly.yaml event: cron (05:00 UTC daily) Via Heroku run PORTED+RETIRED 2026-07-07 (#4068)
historical-bars-1min-warm-nightly.yml .woodpecker/historical-bars-1min-warm-nightly.yaml event: cron (05:30 UTC daily) Via Heroku run Market data API + Heroku; no local postgres
mbt-assignment-at-expiry-cron.yml .woodpecker/mbt-assignment-at-expiry-cron.yaml event: cron (weekly Mon 01:00 UTC) Via Heroku run
mbt-dividend-split-cron.yml .woodpecker/mbt-dividend-split-cron.yaml event: cron (weekly Sun 02:00 UTC) Via Heroku run
mbt-drift-daily.yml .woodpecker/mbt-drift-daily.yaml event: cron (04:30 UTC daily) Via Heroku run
mbt-drift-per-symbol-weekly.yml .woodpecker/mbt-drift-per-symbol-weekly.yaml event: cron (weekly Sat 03:00 UTC) Via Heroku run
mbt-resting-orders-cron.yml .woodpecker/mbt-resting-orders-cron.yaml event: cron (hourly) Via Heroku run
nightly-security-scan.yml .woodpecker/security-scan-nightly.yaml (expand existing) event: cron (08:07 UTC daily) — already registered Yes (partial) Add raxx-ops-bot token mint, GH issue-filing (scan_file_issues.py), PR creation, Slack-on-failure to existing pilot pipeline
queue-docker-smoke.yml .woodpecker/queue-docker-smoke.yaml event: push, branch: develop No Queue C++ Docker build smoke; size:heavy label recommended (multi-stage build)
security-zap.yml .woodpecker/security-zap.yaml event: cron (weekly) No ZAP against staging URL; requires live staging network access
tickets-e2e-smoke.yml .woodpecker/tickets-e2e-smoke.yaml event: cron (06:30 UTC daily) No E2E against FreeScout staging
trace-integrity-cron.yml .woodpecker/trace-integrity-cron.yaml event: cron (02:00 UTC Sun weekly) Via Heroku run PORTED+RETIRED 2026-07-07 (#4067)

Wave B total: 22 workflows (20 new + 2 postgres step additions to Wave A pipelines)


Wave C — deploy, release, gatekeeper pipelines

Entry criterion: Wave B 7-day parity soak; Console Ops Dispatch sub-card filed and in active development; branch-protection WP checks are sole required gate on develop.

Deploy pipelines are migrated staging-first within Wave C: each pipeline is enabled for branch: release (staging) first, soaked for 5 days, then branch: main (production) is enabled. The gatekeeper and promote-to-main pipelines go last (after all staging deploys are proven).

GHA workflow file WP target file Trigger mapping Vault? Notes
deploy-antlers-cutover.yml .woodpecker/deploy-antlers-cutover.yaml event: manual Yes (CF token) One-shot cutover; manual only
deploy-antlers-next-prod.yml .woodpecker/deploy-antlers-next-prod.yaml event: push, branch: main Yes (CF Pages token) Cloudflare Pages deploy via wrangler; NEXT_PUBLIC_* env vars injected at build
deploy-antlers-next-staging.yml .woodpecker/deploy-antlers-next-staging.yaml event: push, branch: release Yes (CF Pages token)
deploy-console-shim.yml .woodpecker/deploy-console-shim.yaml event: push, branch: main Yes (Heroku key)
deploy-console.yml .woodpecker/deploy-console.yaml event: push, branch: [release,main] Yes (Heroku key) notify-deploy-statuswpc_notify_deploy_status.py; check-deploy-freezewpc_check_deploy_freeze.sh
deploy-customer-docs.yml .woodpecker/deploy-customer-docs.yaml event: push, branch: main Yes (CF token)
deploy-failure-streak-alert.yml .woodpecker/deploy-failure-streak-alert.yaml event: cron (every 30 min) Yes (Slack, GH token)
deploy-flag-docs.yml .woodpecker/deploy-flag-docs.yaml event: push, branch: main Yes (CF token)
deploy-getraxx.yml .woodpecker/deploy-getraxx.yaml event: push, branch: main Yes (CF Pages token) Vite build + CF Pages deploy
deploy-heroku.yml .woodpecker/deploy-heroku.yaml event: push, branch: [release,main] + event: manual Yes (Heroku key — prod key restricted to event:tag,manual) Biggest deploy pipeline. Inline synthetic gate steps for staging (replaces synthetic-gate.yml workflow_call). notify-deploy-statuswpc_notify_deploy_status.py. check-deploy-freezewpc_check_deploy_freeze.sh.
deploy-internal-docs.yml .woodpecker/deploy-internal-docs.yaml event: push, branch: main Yes (CF token)
deploy-mockups.yml .woodpecker/deploy-mockups.yaml event: push, branch: main Yes (CF Pages token)
deploy-queue-failure-monitor.yml .woodpecker/deploy-queue-failure-monitor.yaml event: cron + event: push, branch: main Yes
deploy-queue.yml .woodpecker/deploy-queue.yaml event: push, branch: [release,main] Yes (Heroku key)
deploy-status-page.yml .woodpecker/deploy-status-page.yaml event: push, branch: main Yes (CF Pages token)
deploy-status-worker.yml .woodpecker/deploy-status-worker.yaml event: push, branch: main Yes (CF Worker token)
deploy-support.yml .woodpecker/deploy-support.yaml event: push, branch: main Yes (CF token) FreeScout deploy
deploy-velvet.yml .woodpecker/deploy-velvet.yaml event: push, branch: [release,main] Yes (Heroku key)
deploy-worker-waf-log-shipper.yml .woodpecker/deploy-worker-waf-log-shipper.yaml event: push, branch: main Yes (CF Worker token)
freescout-apply.yml .woodpecker/freescout-apply.yaml event: manual Yes (FreeScout token) Applies FreeScout config; manual only
gatekeeper-develop-to-release.yml .woodpecker/gatekeeper-develop-to-release.yaml event: tag, tag: release-* Yes (raxx-ops-bot App key) Highest-risk pipeline. See ADR-0135 §3 for full design. Port cut-release-candidate.yml in same wave.
cut-release-candidate.yml .woodpecker/cut-release-candidate.yaml event: manual Yes (raxx-ops-bot App key) Creates release-* tag on develop HEAD; fires gatekeeper
promote-release-to-main.yml .woodpecker/promote-release-to-main.yaml event: tag, tag: v*.*.* Yes (raxx-ops-bot App key) Highest-risk. Heavy scan suite + merge release→main + gh release create. See ADR-0135 §3 and §5.
synthetic-gate.yml .woodpecker/synthetic-gate-cron.yaml (standalone cron only) event: cron (weekly) Yes (CF Access synthetic) workflow_call uses replaced by inline steps in deploy-heroku.yaml; standalone cron becomes a separate WP pipeline
terraform-email-delivery-stack.yml .woodpecker/terraform-email-delivery-stack.yaml event: [pull_request,push] Yes (AWS creds via vault) Terraform apply for email delivery stack
waf-synthetic-probe.yml .woodpecker/waf-synthetic-probe.yaml event: cron (hourly) Yes (CF, probe token) Probes live WAF endpoints; depends on prod being live

Wave C total: 26 workflows


Wave D — decommission (not a migration wave; no new WP pipelines)

All remaining GHA workflows are disabled and archived. No new .woodpecker/*.yaml files are created in this wave.

Action Detail
Bulk disable all GHA workflows gh workflow disable for each file; bulk script in Wave D sub-card D4
Retain .github/workflows/ 30 days Incident reference; do not delete
Archive after 30-day soak git mv .github/workflows .github/workflows-retired
GHA spending limit → $20/month iOS macOS buffer per ADR-0134
Build /onboard-ci skill scripts/skills/onboard-ci.sh; Woodpecker-native version (POST /api/repos to activate; org-secret inheritance; canary pipeline; see ADR-0134 §9 for sketch)
Close #728, update #726 Ubicloud card superseded; billing posture updated

Section 5 — Trigger mapping reference

Quick reference for translating GHA trigger syntax to Woodpecker v3 when: blocks:

GHA trigger WP equivalent
on: push: branches: [develop] when: event: push, branch: develop
on: push: branches: [release, main] when: event: push, branch: [release, main]
on: push: tags: ['release-*'] when: event: tag, tag: release-*
on: push: tags: ['v*.*.*'] when: event: tag, tag: v*.*.*
on: pull_request: types: [opened, synchronize, reopened] when: event: pull_request
on: schedule: cron: '7 8 * * *' WP cron registered via API + when: event: cron, cron: <name>
on: workflow_dispatch when: event: manual
on: workflow_call No equivalent — steps inlined in calling pipeline
on: workflow_run No equivalent — use inline on_failure step or cron
services: postgres:15 services: postgres: image: postgres:15 ... (same Docker Compose-style syntax in WP)
uses: actions/checkout@v4 fetch-depth: 0 clone: - name: clone image: woodpecker-ci/plugin-git settings: depth: 0
uses: actions/setup-python@v5 Replace with image: python:3.11 on the step
uses: actions/setup-node@v4 Replace with image: node:20-slim on the step
uses: actions/cache@v4 WP has no native cache action; use WP Docker layer cache or a shared volume step
${{ github.sha }} $CI_COMMIT_SHA
${{ github.ref_name }} $CI_COMMIT_TAG (for tag events) or $CI_COMMIT_BRANCH
${{ github.run_id }} $CI_BUILD_NUMBER
${{ github.token }} Not available; mint raxx-ops-bot token via vault init step
>> "$GITHUB_ENV" Same-step source patternWOODPECKER_ENV_FILE is NOT set in step context on WP v3.16.0 (confirmed build #6). Use wpc_load_vault_secrets.py + . /tmp/vault-env.sh && rm -f /tmp/vault-env.sh within the SAME step. See §1 of docs/architecture/woodpecker-wpc-step-usage.md.
>> "$GITHUB_OUTPUT" Write to a file, then read in subsequent steps via commands:
::error::message echo "message"; exit 1 (WP marks step as failed on non-zero exit)
if: always() when: status: [success, failure]
if: failure() when: status: failure
if: success() when: status: success (WP default; can be omitted)
concurrency: cancel-in-progress: true WP does not have a native concurrency group; use depends_on + pipeline-level when: to sequence manually

Section 6 — Wave entry criteria and rollback stances

Wave A

Entry: Phase 2 complete (✓). Composite action scripts (wpc_*.py/wpc_*.sh) merged. WP org-level secrets set. Gate to declare A complete: 7-day soak; all 30 Wave A WP pipelines run successfully at least twice; WP branch-protection checks added alongside GHA (PRs pass both). Rollback: Disable WP pipelines in WP admin UI. GHA workflows remain active; branch-protection GHA checks remain required. Zero blast radius.

Wave B

Entry: Wave A gate passed. postgres:15 service container smoke green on a c6a.xlarge AL2023 agent (run once manually; confirm pg_isready passes before enabling the step in ci-pr.yaml and ci-develop.yaml). Gate to declare B complete: 7-day soak; 20+ successful backend-tests-postgres WP runs; all remaining crons green twice. Branch protection swapped to WP-only on develop. Rollback: Re-enable GHA check names in branch protection (5-minute operation); disable WP backend-postgres step via pipeline YAML PR.

Wave C

Entry: Wave B gate passed. Console Ops Dispatch sub-card (C-console) in active development (does not need to be merged; just filed and understood). Operator has confirmed prod deploy authorization design (WP secret event restriction) is acceptable. Sequencing within Wave C: 1. Enable staging-only deploys (branch: release) for all deploy pipelines. 2. 5-day staging soak: run 3+ full release-* tag → staging deploy cycles via WP. 3. Enable gatekeeper and promote-to-main pipelines. 4. Enable prod deploys (branch: main). Merge Console Ops Dispatch sub-card. Gate to declare C complete: 2+ prod deploys via WP; no GHA fallback needed; Console Ops Dispatch calls WP API successfully. Rollback: Re-enable GHA deploy workflows by removing the if: false disable flag (or via gh workflow enable). Each workflow is independently reversible in < 5 minutes.

Wave D

Entry: Wave C 14-day prod soak. No deploy incidents requiring GHA fallback. Work: Bulk disable GHA workflows; set GHA spending limit; build /onboard-ci. Rollback: GHA workflows are disabled, not deleted, for 30 days. Re-enable via gh workflow enable <file> if an emergency rollback is needed. After 30-day archive, rollback requires a git revert of the archive commit — operationally acceptable given the 30-day soak.


Section 7 — Branch protection API changes

develop branch (changes during Wave A + Wave B)

Wave A addition (add WP checks alongside existing GHA checks):

PATCH /repos/raxx-app/TradeMasterAPI/branches/develop/protection
{
  "required_status_checks": {
    "strict": true,
    "checks": [
      {"context": "PR Gates / Backend tests"},
      {"context": "CI — develop / Backend tests"},
      {"context": "ci/woodpecker/ci-pr/backend-tests-postgres"},
      {"context": "ci/woodpecker/ci-pr/frontend-tests"},
      {"context": "ci/woodpecker/ci-pr/sprint-readiness-gate"},
      {"context": "ci/woodpecker/ci-pr/base-branch-lint"},
      {"context": "ci/woodpecker/migration-collision-check/check"},
      {"context": "ci/woodpecker/ci-develop/backend-tests-postgres"}
    ]
  }
}

Wave B removal (remove GHA checks; keep only WP):

{
  "required_status_checks": {
    "strict": true,
    "checks": [
      {"context": "ci/woodpecker/ci-pr/backend-tests-postgres"},
      {"context": "ci/woodpecker/ci-pr/frontend-tests"},
      {"context": "ci/woodpecker/ci-pr/sprint-readiness-gate"},
      {"context": "ci/woodpecker/ci-pr/base-branch-lint"},
      {"context": "ci/woodpecker/migration-collision-check/check"},
      {"context": "ci/woodpecker/ci-develop/backend-tests-postgres"}
    ]
  }
}

Note: The exact WP check context names depend on the .woodpecker/*.yaml pipeline filenames and step names written in Wave A. Wave A sub-card A7 audits the actual check names reported by WP after the first successful run before executing the branch protection API call. Do not hardcode these names before verifying.

release and main branches

Existing required checks on release and main are likely gatekeeper-related checks, not PR-CI checks (since merges are automated via bot). Audit in Wave C sub-card C-bp before touching. Pattern is the same: add WP check names alongside GHA during Wave C staging soak, swap to WP-only after prod cutover confirmed.