-
Notifications
You must be signed in to change notification settings - Fork 1
471 lines (437 loc) · 24.5 KB
/
Copy pathci.yml
File metadata and controls
471 lines (437 loc) · 24.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
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 pnpm
uses: pnpm/action-setup@v6
# setup-pnpm's default store nests inside its own bin dir, which breaks
# any prepare-building dep (portable-type TS2742) — pin it outside.
- name: Pin pnpm store location
run: pnpm config set store-dir ~/.pnpm-store --global
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- 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: pnpm run verify
- name: Install Playwright Browsers
run: pnpm exec 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('**/pnpm-lock.yaml') }}-${{ hashFiles('src/**/*.[jt]s', 'src/**/*.[jt]sx') }}
restore-keys: |
nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
- 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: pnpm 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 &
pnpm exec 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: pnpm 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 pnpm
uses: pnpm/action-setup@v6
# setup-pnpm's default store nests inside its own bin dir, which breaks
# any prepare-building dep (portable-type TS2742) — pin it outside.
- name: Pin pnpm store location
run: pnpm config set store-dir ~/.pnpm-store --global
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: 'pnpm'
- 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: pnpm install --frozen-lockfile
- 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: pnpm 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 pnpm
uses: pnpm/action-setup@v6
# No cache here on purpose: this job never installs, so the pnpm store
# is never created — setup-node's post-step then FAILS the job trying to
# save a cache path that does not exist (`pnpm audit` needs only the
# lockfile, not node_modules).
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: 24
- 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: pnpm audit --audit-level=high --prod
# 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