CI #2485
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| # Cancel superseded runs on the same ref — a newer push makes the older build | |
| # obsolete. On main this means only the latest commit's CI (and thus its CD) | |
| # proceeds, which is what we want: deploy the newest, not a stale in-flight one. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-smoke: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| env: | |
| # Public Supabase vars are baked into the client bundle at build time. | |
| # Use repo secrets when present so P0 auth tests hit the real fixture DB. | |
| # Fall back to dummies for fork PRs where secrets are unavailable. | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL != '' && secrets.NEXT_PUBLIC_SUPABASE_URL || 'https://dummy-project.supabase.co' }} | |
| NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY != '' && secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || 'dummy' }} | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY != '' && secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'dummy' }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SECRET_KEY != '' && secrets.SUPABASE_SECRET_KEY || '' }} | |
| SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY != '' && secrets.SUPABASE_SECRET_KEY || '' }} | |
| PORT: 3000 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Every external `uses:` names a canonical owner, not a redirect | |
| # Twice in two days an owner rename stopped every merge and every deploy | |
| # in this repo: `maonakamoto` → `catomean` (2026-08-26), then the move to | |
| # the `bitbaum` org (2026-08-28, #800). Both times nothing looked wrong. | |
| # REST and git follow a rename redirect, so every normal way of checking | |
| # says the reference is fine; the Actions resolver is the one consumer | |
| # that does not follow it, and dies before any step exists with no | |
| # readable log. Pull requests stay green, clean and mergeable — the red | |
| # run is on main, under a workflow nobody opens. | |
| # | |
| # #800 fixed the second instance. This is the check, so there is no | |
| # third: it asks the API what each referenced repo is really called and | |
| # fails when that disagrees with the workflow. A static allowlist cannot | |
| # do it — after a rename the workflow and the allowlist hold the same | |
| # stale name and agree with each other. | |
| # | |
| # Deliberately NOT in `npm run verify`: verify is the offline SSOT | |
| # bundle. This needs the API, so it lives here and skips without a token. | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: node scripts/ci/check-workflow-refs.mjs | |
| - name: Verify (docs + type-check + routes + lint + unit — same as local) | |
| # SSOT for "green": the pre-build gate is defined ONCE as the `verify` | |
| # npm script and called verbatim here, so `npm run verify` locally and CI | |
| # can never drift. The chain (in order): ci:docs → type-check → | |
| # audit:routes → lint → test:unit. Rationale for each check: | |
| # - ci:docs : docs hygiene (slop scan + frontmatter). Fails hard, | |
| # never swallowed with `|| echo` (that was a no-op gate). | |
| # - type-check : tsc --noEmit. | |
| # - audit:routes : every declared route + emitted internal link must | |
| # resolve to a real route file — kills the internal-link | |
| # → 404 class of bug. Static, fast. | |
| # - lint : eslint — catches the undefined-identifier class (e.g. | |
| # the `IntegrationNote` bug that once landed on main). | |
| # - test:unit : Vitest unit suite. | |
| # (Playwright/E2E stays a separate post-build gate below — it needs a | |
| # running server + secrets, so it isn't part of the static verify bundle.) | |
| run: npm run verify | |
| - name: Install Playwright Browsers | |
| run: npx playwright install --with-deps | |
| - name: Cache Next.js build cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ github.workspace }}/.next/cache | |
| # Re-key when deps or source change; restore the newest same-deps cache | |
| # otherwise so incremental compilation is reused across runs. | |
| key: nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('src/**/*.[jt]s', 'src/**/*.[jt]sx') }} | |
| restore-keys: | | |
| nextjs-${{ hashFiles('**/package-lock.json') }}- | |
| - name: Build (standalone — the exact bundle CD ships) | |
| env: | |
| NODE_ENV: production | |
| # Build the SELF_HOST standalone here so CI tests the same artifact CD | |
| # deploys (tested == shipped). On main this artifact is uploaded and | |
| # shipped verbatim; PRs build it too (dummy env) to catch standalone-only | |
| # breakage. Bake the same client vars CD bakes — public receiving | |
| # addresses; empty on PRs/forks, which is fine for a test build. | |
| SELF_HOST: '1' | |
| NEXT_PUBLIC_LIGHTNING_ADDRESS: ${{ vars.NEXT_PUBLIC_LIGHTNING_ADDRESS }} | |
| NEXT_PUBLIC_BITCOIN_ADDRESS: ${{ vars.NEXT_PUBLIC_BITCOIN_ADDRESS }} | |
| # Cat Credits go-live flag. This build step is the one that bakes the | |
| # client bundle CD ships (artifact download, no rebuild) — a client | |
| # var set only in cd.yml never reaches production. | |
| NEXT_PUBLIC_CAT_CREDITS_LIVE: ${{ vars.NEXT_PUBLIC_CAT_CREDITS_LIVE }} | |
| # Voice input on create forms and onboarding. Same rule as the line | |
| # above, and the reason this feature was dead: the flag existed in the | |
| # code and in nobody's build env, so FEATURES.voiceInput compiled to | |
| # false in every production bundle it ever shipped in. | |
| NEXT_PUBLIC_FEATURE_VOICE_INPUT: ${{ vars.NEXT_PUBLIC_FEATURE_VOICE_INPUT }} | |
| # FleetCrown feedback widget token. Statically prerendered routes | |
| # (e.g. /dashboard/*) evaluate the root layout's env gate at BUILD | |
| # time — without this the widget bakes out of every static page and | |
| # only runtime-rendered routes get it. Public by design (it ships in | |
| # page source), hence a repo variable, not a secret. | |
| FLEETCROWN_FEEDBACK_TOKEN: ${{ vars.FLEETCROWN_FEEDBACK_TOKEN }} | |
| run: npm run build | |
| - name: Assemble standalone (static + public + content) | |
| # Copy the assets into the standalone tree so both the E2E run below and | |
| # the uploaded artifact are complete and runnable as-is. content/ holds | |
| # the blog mdx files read from the filesystem at request time — file | |
| # tracing does not follow request-time fs reads, and shipping without | |
| # them is exactly how every blog post rendered as an empty stub in prod. | |
| run: | | |
| cp -r .next/static .next/standalone/.next/static | |
| [ -d public ] && cp -r public .next/standalone/public || true | |
| [ -d content ] && cp -r content .next/standalone/content || true | |
| - name: Upload standalone artifact (main → shipped by CD) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: standalone-${{ github.sha }} | |
| path: .next/standalone | |
| include-hidden-files: true # .next/* is hidden; excluded by default | |
| retention-days: 3 | |
| - name: Start server (standalone — the shipped entrypoint) | |
| run: | | |
| PORT=3000 node .next/standalone/server.js & | |
| npx wait-on http://localhost:3000 | |
| - name: Validate required P0 E2E env | |
| id: check-secrets | |
| env: | |
| E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }} | |
| E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }} | |
| E2E_PROJECT_ID: ${{ secrets.E2E_PROJECT_ID }} | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} | |
| run: | | |
| missing=0 | |
| for key in E2E_USER_EMAIL E2E_USER_PASSWORD E2E_PROJECT_ID NEXT_PUBLIC_SUPABASE_URL; do | |
| if [ -z "${!key}" ]; then | |
| echo "⚠️ Missing secret: $key" | |
| missing=1 | |
| else | |
| echo "✅ Found $key" | |
| fi | |
| done | |
| if [ "$missing" -ne 0 ]; then | |
| # PRs (especially from forks) legitimately lack these secrets → soft-skip. | |
| # But a push/dispatch to main is what CD deploys on: a green build there | |
| # MUST mean the P0 E2E suite actually ran, never "secrets were missing so | |
| # we skipped." Fail hard so the deploy gate can't pass untested. | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| echo "⚠️ E2E secrets not configured - skipping P0 matrix (PR / fork build)" | |
| echo "has_secrets=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "❌ E2E secrets missing on a ${{ github.event_name }} to ${{ github.ref }}." | |
| echo " Refusing to report a green build with zero end-to-end coverage — CD deploys on this." | |
| exit 1 | |
| fi | |
| else | |
| echo "has_secrets=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Bootstrap E2E fixture data | |
| if: steps.check-secrets.outputs.has_secrets == 'true' | |
| env: | |
| E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }} | |
| E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }} | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} | |
| NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY }} | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SECRET_KEY }} | |
| run: | | |
| node scripts/test-setup/ensure-e2e-fixtures.mjs | |
| node scripts/test-setup/refresh-e2e-reset-tokens.mjs | |
| - name: Run P0 workflow matrix | |
| if: steps.check-secrets.outputs.has_secrets == 'true' | |
| env: | |
| E2E_BASE_URL: http://localhost:3000 | |
| BASE_URL: http://localhost:3000 | |
| E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }} | |
| E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }} | |
| E2E_PROJECT_ID: ${{ secrets.E2E_PROJECT_ID }} | |
| # E2E_RESET_* tokens are minted in the bootstrap step (single-use recovery sessions). | |
| # Legacy aliases for global-setup and older specs | |
| E2E_TEST_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }} | |
| E2E_TEST_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }} | |
| run: npm run test:e2e:matrix:p0 | |
| # Runs `verify` a SECOND time, from a git worktree, because a worktree is the | |
| # shape every agent in this fleet actually works in (`_claude_autoworktree_enter`) | |
| # and it is a shape CI otherwise never sees. | |
| # | |
| # The difference that matters: `actions/checkout` + `npm ci` puts node_modules | |
| # at the checkout root, so a script that hardcodes `join(ROOT,'node_modules',…)` | |
| # resolves and CI goes green. A worktree contains ONLY TRACKED FILES — no | |
| # node_modules of its own — and inherits the main checkout's by letting Node | |
| # walk UP the directory chain. So the same code is green here and crashes there. | |
| # | |
| # That is not hypothetical: on 2026-08-24 `npm run verify` — the repo's SSOT for | |
| # "verified" — could not run to completion from any worktree, and CI was green | |
| # throughout. Re-proven by mutation before this job was written: reintroducing | |
| # the hardcoded jscpd path passes a normal checkout (exit 0, "OK") and ENOENTs | |
| # in a worktree. | |
| # | |
| # Two rules this job encodes, both load-bearing: | |
| # 1. The worktree MUST be nested inside the checkout (.claude/worktrees/, which | |
| # is gitignored). That is where the real tooling puts it, and it is what lets | |
| # Node's upward resolution reach <checkout>/node_modules. A sibling path | |
| # would resolve nothing and would be testing a shape nobody works in. | |
| # 2. It runs `npm run verify` VERBATIM, not a hand-picked "worktree-sensitive" | |
| # subset. Of the two real 2026-08-24 failures one was a path-resolution | |
| # script and the other was a unit test — no honest subset would have | |
| # included both, and the subset would drift from `verify` besides. | |
| # | |
| # Scope, stated plainly: this catches the node_modules-location and git-dir-shape | |
| # classes. It does NOT catch "script reads an untracked file" (e.g. .env.local) — | |
| # CI has no untracked files in either shape, so that class is already caught by | |
| # the normal run and is not this job's job. | |
| # | |
| # Runs in parallel with build-and-smoke, so it costs runner minutes, not | |
| # wall-clock on the path to deploy. | |
| verify-in-worktree: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| env: | |
| # Mirror build-and-smoke's env exactly. If this job got different inputs, a | |
| # failure here would be ambiguous — env drift or worktree shape? — and the | |
| # signal this job exists to give would be worthless. | |
| NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL != '' && secrets.NEXT_PUBLIC_SUPABASE_URL || 'https://dummy-project.supabase.co' }} | |
| NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY != '' && secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || 'dummy' }} | |
| NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY != '' && secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'dummy' }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SECRET_KEY != '' && secrets.SUPABASE_SECRET_KEY || '' }} | |
| SUPABASE_SECRET_KEY: ${{ secrets.SUPABASE_SECRET_KEY != '' && secrets.SUPABASE_SECRET_KEY || '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'npm' | |
| - name: Install dependencies | |
| # Installs into <checkout>/node_modules and NOWHERE ELSE. The worktree | |
| # below deliberately gets none of its own — that asymmetry is the test. | |
| run: npm ci | |
| - name: Create a git worktree (the shape agents work in) | |
| # --detach avoids inventing a branch name that could collide on re-runs. | |
| # Works from the shallow, detached-HEAD checkout actions/checkout produces. | |
| run: | | |
| git worktree add --detach .claude/worktrees/ci-verify HEAD | |
| test ! -e .claude/worktrees/ci-verify/node_modules \ | |
| || { echo "::error::worktree has its own node_modules — this job would prove nothing"; exit 1; } | |
| - name: Verify from the worktree (same script, no local node_modules) | |
| working-directory: .claude/worktrees/ci-verify | |
| run: npm run verify | |
| # Supply-chain gate: catch committed secrets and known-vuln dependencies before | |
| # they ship. Runs in parallel with build-and-smoke (independent). The | |
| # `security:scan` npm script existed but nothing invoked it — this wires it in. | |
| security: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| # gitleaks scans git history for leaked secrets — needs the full log, | |
| # not the default shallow clone. | |
| fetch-depth: 0 | |
| - name: Secret scan (gitleaks) | |
| # The gitleaks CLI, not gitleaks-action. The action is free only for | |
| # PERSONAL accounts; under an organization it refuses to scan at all and | |
| # exits with "missing gitleaks license". Moving this repo into the | |
| # bitbaum org therefore switched secret scanning off while leaving the | |
| # job, its name, and its place in the checks list exactly as they were. | |
| # It happened to fail loudly here — but only because the licence check | |
| # errors; a wrapper that had instead skipped would have reported green | |
| # while scanning nothing. The CLI itself is MIT and unaffected. | |
| # | |
| # Scope is the incoming commit range, which is what the action scanned. | |
| # Scanning all of history instead would fail on ~173 pre-existing | |
| # findings (2025-era .env commits) that only a history rewrite can clear | |
| # — see the security audit, not CI's job. Coverage is unchanged: every | |
| # commit is still scanned in its PR run before it can merge. | |
| # | |
| # Skipped on workflow_dispatch, and only there: a dispatched run has no | |
| # incoming range. Auto-merge dispatches CI on main after each merge (a | |
| # GITHUB_TOKEN push triggers nothing), so without that skip every | |
| # automated merge would leave main red and CD would never deploy. | |
| if: github.event_name != 'workflow_dispatch' | |
| env: | |
| GITLEAKS_VERSION: '8.30.1' | |
| BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| run: | | |
| set -euo pipefail | |
| curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ | |
| | tar -xz gitleaks | |
| base="$BASE_SHA" | |
| # A branch's first push reports an all-zero "before", and a force-push | |
| # can report a sha this clone no longer has. Both would otherwise make | |
| # the range unresolvable. | |
| if [ "$base" = "0000000000000000000000000000000000000000" ] \ | |
| || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then | |
| base="${HEAD_SHA}~1" | |
| fi | |
| range="${base}..${HEAD_SHA}" | |
| # gitleaks exits 0 on an empty range — it scans nothing and reports | |
| # success, which is indistinguishable from a clean scan. Verified | |
| # against 8.30.1 before writing this. Count the commits ourselves and | |
| # refuse to call zero a pass. | |
| n=$(git rev-list --count "$range") | |
| echo "gitleaks: scanning $n commit(s) in $range" | |
| if [ "$n" -eq 0 ]; then | |
| echo "::error::empty scan range $range — refusing to report a pass" | |
| exit 1 | |
| fi | |
| ./gitleaks git . --log-opts="$range" --redact --no-banner --exit-code 1 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 24 | |
| cache: 'npm' | |
| - name: Dependency audit | |
| # ESCALATED 2026-08-25. The staged rollout this replaces blocked only on | |
| # CRITICAL and merely annotated HIGH, explicitly "until the tree is | |
| # clean". The tree is now clean — `npm audit --omit=dev` reports 0 | |
| # vulnerabilities — so the condition that comment set is met and the | |
| # warning is gone. A warning nobody is assigned to read is not a gate; it | |
| # sat non-blocking while 3 HIGH advisories accumulated (postcss ×2, | |
| # js-yaml), and none of them were even version-pinned — the lockfile had | |
| # just drifted below ranges that already allowed the patched releases. | |
| # | |
| # `--audit-level=high` and one command, not two: blocking at high already | |
| # covers critical, so a separate critical line would be dead weight. | |
| # | |
| # `--omit=dev` is deliberate — dev-only advisories do not ship, and | |
| # blocking merges on them would make this gate churn until someone | |
| # weakens it. (js-yaml above was dev-only; it was patched anyway because | |
| # the range already allowed it.) | |
| # | |
| # Tied to the `node-version` (npm major) above, and that coupling is | |
| # load-bearing. On npm 11, `npm audit --audit-level=critical` was | |
| # observed to exit 1 against a tree whose own --json output reported | |
| # {moderate:2, high:1, critical:0} — i.e. the level stops gating the | |
| # exit code: the line below silently becomes "block on ANY advisory, | |
| # including low", which will wedge every merge on something unfixable | |
| # and get this gate weakened rather than debugged. | |
| # If you bump Node here, re-check this step's exit code first. | |
| # Re-checked 2026-08-31 for the 20->24 bump (npm 11.x): the tree is | |
| # clean at every level (npm audit --omit=dev --json -> total 0), so | |
| # the command below exits 0. The npm 11 caveat still stands the day | |
| # an advisory lands: low/moderate will block too — fix or override | |
| # the advisory per the ladder below; do not lower the level. | |
| # | |
| # WHEN THIS BLOCKS YOU and no patched version exists upstream, do NOT | |
| # lower the level — that silently re-opens the whole class. In order of | |
| # preference: bump the dependency; add an `overrides` entry in | |
| # package.json pinning a patched transitive; or, if genuinely unfixable, | |
| # land the exception as its own commit naming the advisory and why, so | |
| # the decision is reviewable instead of inherited. | |
| run: npm audit --audit-level=high --omit=dev | |
| # Everything GitHub would have chained off this run, had it emitted the event. | |
| # | |
| # CD and Main Red Alert both trigger on `workflow_run` of CI. GitHub does not | |
| # emit that event when the CI run was itself created with the default | |
| # GITHUB_TOKEN — which is exactly how the auto-merge sweep re-arms CI after a | |
| # bot merge (a GITHUB_TOKEN push triggers nothing, hence the dispatch). So on | |
| # the automated path both consumers are dead, and dead *silently*: green CI, | |
| # no red check, nothing deployed. That stranded 14 verified commits on main | |
| # for eight hours on 2026-08-04 while the box served the old build. | |
| # | |
| # A dispatched run therefore does the handoff itself. The push path is left | |
| # alone — its `workflow_run` fires normally, and is the better path there | |
| # (CD downloads the exact artifact this run built). One trigger per path. | |
| post-main: | |
| # `verify-in-worktree` is DELIBERATELY absent here — it is not an oversight, | |
| # do not add it. It proves agents can run `verify` locally; it does not say | |
| # anything about whether the commit is safe to ship (build-and-smoke already | |
| # ran the same `verify` on the same code). Blocking a deploy of verified code | |
| # on a developer-experience gate would trade a real outage for a process bug. | |
| # The enforcement point is PR-time: auto-merge requires EVERY check green, so | |
| # a worktree regression cannot reach main in the first place. | |
| needs: [build-and-smoke, security] | |
| if: always() && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| actions: write # dispatch cd.yml | |
| issues: write # file/close the red-main issue | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Resolve this run's verdict | |
| id: verdict | |
| run: | | |
| if [ "${{ needs.build-and-smoke.result }}" = "success" ] \ | |
| && [ "${{ needs.security.result }}" = "success" ]; then | |
| echo "conclusion=success" >> "$GITHUB_OUTPUT" | |
| elif [ "${{ needs.build-and-smoke.result }}" = "cancelled" ] \ | |
| || [ "${{ needs.security.result }}" = "cancelled" ]; then | |
| echo "conclusion=cancelled" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "conclusion=failure" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: File or resolve the main-red issue | |
| env: | |
| CONCLUSION: ${{ steps.verdict.outputs.conclusion }} | |
| RUN_SHA: ${{ github.sha }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: bash scripts/ci/main-red-alert.sh | |
| - name: Arm CD | |
| if: steps.verdict.outputs.conclusion == 'success' | |
| run: bash scripts/ci/arm-cd.sh |