@@ -8,9 +8,10 @@ name: integ
88# Trigger model mirrors deploy.yml: build.yml completes -> workflow_run picks it
99# up in the trusted base-repo context (secrets/OIDC available even for fork PRs)
1010# -> we resolve whether the PR touches cdk/** or agent/** -> an admin approves
11- # the `integ` environment gate -> deploy/assert/destroy runs against the shared
12- # account -> a commit status `integ-smoke` is posted back to the PR head so it
13- # shows up as a (required) check that blocks merge.
11+ # the `deploy` environment gate (reused — same gate and role as deploy.yml) ->
12+ # deploy/assert/destroy runs against the shared account -> a commit status
13+ # `integ-smoke` is posted back to the PR head so it shows up as a (required)
14+ # check that blocks merge.
1415#
1516# Local dev path is unchanged: run `mise //cdk:integ` with your own AWS creds.
1617#
@@ -19,16 +20,19 @@ name: integ
1920on :
2021 # zizmor: ignore[dangerous-triggers] — intentional; workflow_run is required so
2122 # fork PRs can run against the shared account (a fork `pull_request` job gets no
22- # secrets/OIDC). Mitigations: build-success guard, path-filter, `integ`
23- # environment approval gate (admin reviews fork test code before it runs with
24- # the privileged role), least-privilege role, status-only tokens per job.
23+ # secrets/OIDC). Mitigations: build-success guard, path-filter, fork
24+ # `safe-to-test` label gate, `deploy` environment approval gate (admin reviews
25+ # fork test code before it runs with the deploy role), status-only tokens per
26+ # job.
2527 workflow_run :
2628 workflows : [build]
2729 types : [completed]
2830 workflow_dispatch : {}
2931
30- # Only one integ run at a time against the shared account — overlapping deploys
31- # would collide on the single hardcoded `backgroundagent-integ` stack name.
32+ # Only one integ run at a time against the shared account. Stack names are now
33+ # run-unique (`int-<sha>`, see the integ job) so this is no longer a correctness
34+ # requirement — it bounds shared-account cost/parallelism rather than preventing
35+ # a name collision.
3236concurrency :
3337 group : cdk-integ
3438 cancel-in-progress : false
4145 # docs/cli-only PRs get an immediate green (skipped) status and never deadlock
4246 # the required check.
4347 resolve :
44- # Manual dispatch is restricted to main (defence in depth — the `integ `
48+ # Manual dispatch is restricted to main (defence in depth — the `deploy `
4549 # environment approval is the primary gate). PR runs come via workflow_run.
4650 if : >-
4751 (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') ||
@@ -131,7 +135,7 @@ jobs:
131135
132136 # Fork-PR safety: only run fork-authored code after a maintainer has
133137 # applied the `safe-to-test` label (defence in depth on top of the
134- # `integ ` environment approval). If it's absent, leave the status
138+ # `deploy ` environment approval). If it's absent, leave the status
135139 # pending and don't run — re-trigger once the label is added.
136140 if [[ "$WF_HEAD_REPO" != "$REPO" ]]; then
137141 if ! LABELS=$(gh api "repos/$REPO/issues/$PR_NUMBER/labels" --jq '.[].name'); then
@@ -166,15 +170,16 @@ jobs:
166170 echo "PR #$PR_NUMBER has no cdk/** or agent/** changes — integ skipped (green)."
167171 fi
168172
169- # The admin-gated deploy -> assert -> destroy. The `integ` environment's
170- # required reviewer is the approval gate; while it waits, the integ-smoke
171- # status stays pending and merge stays blocked.
173+ # The admin-gated deploy -> assert -> destroy. The `deploy` environment's
174+ # required reviewers (coding-agents-admin / coding-agents-maintainers) are the
175+ # approval gate; while it waits, the integ-smoke status stays pending and merge
176+ # stays blocked. Reuses deploy.yml's environment/role — no separate integ role.
172177 integ :
173178 needs : resolve
174179 if : needs.resolve.outputs.applicable == 'true'
175180 name : CDK integ smoke (Task API)
176181 runs-on : ubuntu-latest
177- environment : integ
182+ environment : deploy
178183 timeout-minutes : 45
179184 permissions :
180185 id-token : write
@@ -184,25 +189,25 @@ jobs:
184189 MISE_EXPERIMENTAL : " 1"
185190 steps :
186191 - name : Checkout PR head (incl. forks)
187- uses : actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 .0.3
192+ uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 .0.0
188193 with :
189- # Approving the `integ ` environment authorizes this fork-authored test
190- # code to run with the privileged role — the approver MUST review
194+ # Approving the `deploy ` environment authorizes this fork-authored test
195+ # code to run with the deploy role — the approver MUST review
191196 # cdk/test/integ/** changes before approving.
192197 repository : ${{ needs.resolve.outputs.head_repo }}
193198 ref : ${{ needs.resolve.outputs.head_sha }}
194199 persist-credentials : false
195200
196201 - name : Configure AWS credentials
197- uses : aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6.2.0
202+ uses : aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6.2.1
198203 with :
199204 role-to-assume : ${{ secrets.AWS_ROLE_TO_ASSUME }}
200205 # Fall back to us-east-1 if the repo variable is unset, so the action
201206 # never runs region-less (which would fail credential resolution).
202207 aws-region : ${{ vars.AWS_REGION || 'us-east-1' }}
203208
204209 - name : Install mise
205- uses : jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1 .0
210+ uses : jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2 .0
206211 with :
207212 cache : true
208213
@@ -214,30 +219,36 @@ jobs:
214219 - name : Install dependencies
215220 run : yarn install --immutable
216221
217- - name : Run integ tests (deploy → assert → destroy)
218- run : mise //cdk:integ
219-
220- # Safety net: integ-runner forces teardown on success and failure, but if
221- # the run is cancelled or crashes mid-deploy the stack can be stranded in
222- # the shared account. Delete it directly via CloudFormation so we never
223- # leak billable resources .
222+ # Teardown: integ-runner forces `cdk destroy` on success and failure (via
223+ # the CDK bootstrap roles, which hold the CloudFormation permissions). No
224+ # raw-CLI safety net here: the GitHub OIDC role has no
225+ # cloudformation:DeleteStack/DescribeStacks, so such a step can only fail
226+ # (#566). If a cancelled/crashed run strands the stack, reclaim is the
227+ # stranded-stack cleanup owned by #400/#72 — and the run-unique name below
228+ # means a stranded stack never blocks a later run .
224229 #
225- # NOTE: `cdk destroy backgroundagent-integ` would NOT work here — it
226- # synthesizes the main app (src/main.ts), which does not contain the integ
227- # stack, so it exits 0 having deleted nothing. Target the stack by its
228- # literal CloudFormation name instead. delete-stack is idempotent (no-op if
229- # already gone), so `|| true` only guards transient API errors.
230- - name : Ensure stack torn down
231- if : always()
230+ # INTEG_STACK_NAME maps the stack to the commit under test plus the run
231+ # number (`int-<sha>-<run>`), replacing the single fixed
232+ # `backgroundagent-integ` name. The run number makes same-commit re-runs
233+ # distinct; the `int-` prefix marks it ephemeral for #72's sweep. HEAD_SHA
234+ # is the resolved PR head (or dispatched ref) and RUN_NUMBER is a positive
235+ # integer, so both are already lowercase-alphanumeric — no sanitizing
236+ # needed to stay CloudFormation-valid.
237+ - name : Run integ tests (deploy → assert → destroy)
232238 env :
233- AWS_REGION : ${{ vars.AWS_REGION || 'us-east-1' }}
234- AWS_DEFAULT_REGION : ${{ vars.AWS_REGION || 'us-east-1' }}
239+ HEAD_SHA : ${{ needs.resolve.outputs.head_sha }}
240+ RUN_NUMBER : ${{ github.run_number }}
235241 run : |
236242 set -euo pipefail
237- aws cloudformation delete-stack --stack-name backgroundagent-integ || true
238- # No `|| true` on the wait: a DELETE_FAILED must surface loudly so we
239- # never silently leak billable resources in the shared account.
240- aws cloudformation wait stack-delete-complete --stack-name backgroundagent-integ
243+ export INTEG_STACK_NAME="int-${HEAD_SHA:0:12}-${RUN_NUMBER}"
244+ echo "Integ stack name: $INTEG_STACK_NAME"
245+ mise //cdk:integ -- --all --require-approval never \
246+ -c stackName="integ-${{ github.run_id }}-${{ github.run_attempt }}" \
247+ -c github:sha="$(git rev-parse HEAD)" \
248+ -c github:ref="$(git branch --show-current || echo "detached")" \
249+ -c github:actor="${{ github.triggering_actor }}" \
250+ -c github:repository="$(gh repo view --json nameWithOwner --jq .nameWithOwner)" \
251+ -c github:clean="$([ -z "$(git status --porcelain)" ] && echo true || echo false)"
241252
242253 # Post the final integ-smoke status back to the PR head so the check flips from
243254 # pending to success/failure. Skipped for workflow_dispatch (no PR to gate).
0 commit comments