Skip to content

feat(ci): add deploy pipeline with OIDC, dynamic stack naming, and deploy-intent artifact#98

Merged
krokoko merged 15 commits into
mainfrom
feat/deploy-yml
Jun 2, 2026
Merged

feat(ci): add deploy pipeline with OIDC, dynamic stack naming, and deploy-intent artifact#98
krokoko merged 15 commits into
mainfrom
feat/deploy-yml

Conversation

@scottschreckengaust
Copy link
Copy Markdown
Contributor

@scottschreckengaust scottschreckengaust commented May 15, 2026

Summary

Adds the deployment pipeline (Phase 3 of #73) on top of PR #97 (compute_type rename).

Architecture

build.yml (single entry point for build + deploy intent)
  ├─ workflow_dispatch input: deploy = "-" (default) | "agentcore"
  │   └─ GitHub enforces choice values — no injection possible
  ├─ Resolve stack name (trigger-aware, sanitized)
  ├─ Generate cdk.context.json (stackName + compute_type + github:* tags)
  ├─ mise run build → synth → upload cdk-<type>-out artifact
  ├─ Write deploy-intent.json (validated against ALLOWED_COMPUTE_TYPES)
  └─ Upload deploy-intent artifact

deploy.yml (pure consumer — no direct triggers)
  ├─ workflow_run: fires after build succeeds
  ├─ resolve-targets: download deploy-intent.json
  │   ├─ intent = "-"      → skip (no approval prompt, instant no-op)
  │   ├─ intent = "labels" → check PR labels, validate against allowlist
  │   └─ intent = "<type>" → validate against allowlist, deploy
  └─ deploy job:
      ├─ environment: deploy (manual approval, prevent self-review)
      ├─ cancel-in-progress: false (non-cancellable once started)
      ├─ max-parallel: 3
      ├─ download exact cdk-<type>-out artifact from build run
      ├─ OIDC → CDK bootstrap role
      └─ npx cdk deploy --app cdk/cdk.out --all --require-approval never

Commits

# Commit Description
1 compute_type rename computeVariantcompute_type in build.yml, context, CDK tag (from PR #97)
2 deploy.yml base workflow_run trigger, artifact download, OIDC, cdk deploy
3 Dynamic stack naming Trigger-aware naming with sanitization
4 Deploy-intent pattern Move dispatch input to build.yml, deploy.yml becomes pure consumer
5 Security hardening Allowlist validation, jq for JSON, numeric PR check, letter-prefix stacks

Stack Naming

Trigger Pattern Example
Push to main main-<type> main-agentcore
Push to branch <branch>-<type> feat-deploy-yml-agentcore
Pull request pr<N>-<type> pr42-agentcore
Merge group mg<N>-<type> mg42-agentcore
workflow_dispatch <branch>-<type> main-agentcore
Fallback <type>-<sha7> agentcore-abc1234

Sanitization: lowercase, /_. → -, strip non-[a-z0-9-], collapse hyphens, 60-char cap, prefix s- if starts with digit.

Deploy Intent Flow

Build trigger Intent value Deploy result
Push to main agentcore Deploy agentcore
Push to non-main branch - No deploy
workflow_dispatch with - - No deploy
workflow_dispatch with agentcore agentcore Deploy agentcore
pull_request labels Check PR labels
Unknown - No deploy

Label-Driven Deploy (PR only)

Label Types deployed
deploy All registered types
deploy:agentcore agentcore only
deploy:* All registered types
No deploy* label Nothing

Label values are validated against ALLOWED_COMPUTE_TYPES. Invalid labels emit a warning and are silently dropped.

Depends on: PR #97 — must merge first.

Security

Control Location Mechanism
Input validation build.yml dispatch GitHub choice type — enforced at UI and API
Compute type allowlist Both workflows ALLOWED_COMPUTE_TYPES env var; validate_compute_type()
Label filtering deploy.yml filter_valid_types() rejects invalid deploy:<type> labels
PR number validation build.yml naming Regex ^[0-9]+$ check before use
Stack name sanitization build.yml naming Lowercase, strip unsafe chars, letter-prefix, 60-char cap
JSON encoding build.yml intent jq -n --arg (not shell interpolation)
No expression injection Both workflows All untrusted input via env:, never in run: directly
Permissions Both workflows permissions: {} at top; least-privilege per job
OIDC deploy.yml No long-lived credentials; assumes CDK bootstrap roles
Environment gate deploy.yml deploy environment with manual approval, prevent self-review
Non-cancellable deploys deploy.yml cancel-in-progress: false once deployment starts
zizmor Both workflows Passes with 1 documented intentional suppression

Test plan

  • YAML validates on both files
  • zizmor passes on both files (0 findings, 1 ignored, 8 suppressed)
  • Pre-commit hooks pass (eslint, gitleaks, yaml check)
  • github-tags.test.ts — 5/5 pass (compute_type tag defaults + explicit)
  • TypeScript compiles clean
  • End-to-end: workflow_dispatch build with deploy: agentcore
  • End-to-end: PR with deploy label
  • Verify stack naming for each trigger type
  • Verify invalid deploy:foo label emits warning and is ignored

Not in scope (future work)

  • Baseline diff step (download from latest release, compare resource types)
  • cdk diff output to step summary
  • Release flow (draft → deploy → tag → publish)
  • Cleanup workflow (cleanup.yml)

🤖 Generated with Claude Code

Comment thread .github/workflows/deploy.yml
Comment thread .github/workflows/deploy.yml Outdated
@scottschreckengaust scottschreckengaust changed the title feat(ci): add deploy.yml workflow with OIDC and approval gate feat(ci): add deploy.yml with OIDC, dynamic stack naming, and approval gate May 15, 2026
@scottschreckengaust scottschreckengaust changed the title feat(ci): add deploy.yml with OIDC, dynamic stack naming, and approval gate feat(ci): add deploy pipeline with OIDC, dynamic stack naming, and deploy-intent artifact May 15, 2026
Comment thread .github/workflows/build.yml
@krokoko
Copy link
Copy Markdown
Contributor

krokoko commented May 28, 2026

Review summary

Thanks for this — the architecture is sound and the security framing is genuinely strong (SHA-pinned actions, env-var-only untrusted input, deny-by-default permissions, OIDC, approval gate, synth-once/deploy-exact-artifact). It aligns cleanly with the Phase 3 goals in #73. A few items I'd like to see addressed before merge, none requiring a redesign.

🔴 Blocking

B1 — filter_valid_types emits multi-line JSON, corrupting the matrix output (deploy:<type> path is broken). deploy.yml:55

valid_json=$(echo "$valid_json" | jq --arg t "$type" '. + [$t]')   # missing -c

VALIDATED becomes pretty-printed multi-line JSON, then deploy.yml:88 does echo "matrix=$VALIDATED" >> "$GITHUB_OUTPUT". GitHub Actions parses one key=value per line, so matrix captures only [ and fromJson(...outputs.matrix) in the deploy job fails — the deploy:agentcore-style label path never deploys. (The COUNT check on line 86 masks it because jq 'length' tolerates multi-line input.)
Fix: jq -c --arg t "$type" '. + [$t]', and add a CI test exercising a deploy:agentcore label since this path has no coverage today.

B2 — Deploy job runs a PR-synthesized cloud assembly with --require-approval never; manual approval is the only defense against malicious CloudFormation. deploy.yml:166 (+ fork guard at deploy.yml:12-14)
build.yml runs on pull_request (untrusted PR-head code), cdk synth produces the cdk-<type>-out artifact, and deploy.yml downloads that exact artifact and runs npx cdk deploy --app cdk/cdk.out --all --require-approval never. The head_repository.full_name == github.repository guard blocks forks but not same-repo branch PRs — any contributor who can push a branch could author a PR whose synth emits arbitrary CloudFormation (e.g. an over-broad IAM role), and a human approving the deploy environment is reviewing an opaque generated template rather than the diff.
Fix (pick one): (a) restrict the deploy path to push-to-main/merged intent and drop PR-label deploys; or (b) re-synthesize from the checked-out trusted ref inside the deploy job rather than trusting the uploaded cdk.out; or (c) at minimum drop --require-approval never and surface a cdk diff in the step summary for the approver. (The RFC's Phase 3 checklist already lists the cdk diff step as unchecked — that's exactly the compensating control here.)

B3 — Stack-name sanitize() can produce CloudFormation-invalid names. build.yml:134-138

  • cut -c1-60 runs after the trailing-hyphen trim, so a hyphen at position 60 survives → trailing hyphen.
  • An all-special-char ref sanitizes to empty → STACK_NAME="-agentcore". The guard only checks ^[0-9], so a leading hyphen isn't caught and CloudFormation rejects it (names must start with a letter). Affects the push / workflow_dispatch paths only.

Fix:

result=$(... | cut -c1-60 | sed 's/-$//')
if [[ ! "$result" =~ ^[a-z] ]]; then result="s-${result}"; fi

🟡 Non-blocking

  • index("deploy:*") is a literal match, not a glob (deploy.yml:80). It only matches a label named exactly deploy:*; the bare deploy branch already means "all types." This branch is dead/misleading and is a surprise full-deploy vector if such a label is ever created — remove it or rename to an explicit deploy:all sentinel.
  • PR_NUMBER_FROM_EVENT from workflow_run.pull_requests[0].number (deploy.yml:36) is empty for forks and often for same-repo indirect runs. It fails safe (no deploy), so no security issue, but a label-driven deploy may silently no-op — worth documenting or resolving via gh api on head_sha.
  • ALLOWED_COMPUTE_TYPES is duplicated across both workflows plus the matrix; drift silently disables a type. A follow-up to source it from one place would be nice.
  • exit 1 vs return 1 between build.yml and deploy.yml is correct in both cases — a one-line comment explaining why they differ would help future maintainers.

📄 Docs

Nicely done — DEPLOYMENT_GUIDE.md and its Starlight mirror were both regenerated in the same diff, so the mutation check passes. One thing: the ROADMAP wasn't touched — if this completes/advances a CI/CD item there, please update docs/guides/ROADMAP.md and re-run mise //docs:sync.

✅ Tests & CI

All checks green. Main gap is that the new bash logic (sanitize, intent resolution, label filtering) has no test coverage — B1 is exactly the kind of bug one end-to-end deploy:agentcore test would have caught. The PR's own test plan leaves the four end-to-end items unchecked.

Governance notes (per ADR-003)


Bottom line: B1 is a one-character fix that unblocks the label path, B2 is the trust gap worth closing (at least cdk diff + dropping --require-approval never), and B3 covers two name-sanitization edge cases. With those addressed this is a solid, well-secured pipeline. 🚀

Reviewed as Principal Architect; security + shell-logic analysis run by specialized review agents.

krokoko pushed a commit that referenced this pull request May 29, 2026
Deploy CI belongs in PR #98 (feat/deploy-yml), not the roadmap docs PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
github-merge-queue Bot pushed a commit that referenced this pull request May 29, 2026
* feat(ci): rename computeVariant to compute_type and apply as resource tag

Aligns CI and CDK terminology with the existing ComputeType union in
repo-config.ts. build.yml matrix key, env var, and cdk.context.json key
are all renamed from computeVariant to compute_type. The CDK app now
reads compute_type from context (default: agentcore) and applies it as
a resource tag for per-type baseline diffs and cost attribution.

Closes phase 2 items in #73.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ci): add deploy.yml workflow triggered by build completion

Adds a deploy workflow that:
- Fires on workflow_run after build.yml succeeds
- Resolves deploy targets from PR labels (deploy=all, deploy:<type>=one)
  or defaults to all registered types on push to main
- Skips entirely (no approval prompt) when no deploy labels are present
- Downloads the exact cdk-<compute_type>-out artifact from the build run
- Uses OIDC to assume the CDK bootstrap deploy role
- Deploys via `cdk deploy --app cdk/cdk.out --all --require-approval never`
- Protected by the `deploy` GitHub environment (manual approval required)
- Concurrency: non-cancellable once started, max-parallel 3

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(ci): dynamic stack naming and workflow_dispatch deploy trigger

build.yml: Replace hardcoded stackName with trigger-aware naming:
  - push to main: main-<compute_type>
  - pull_request: pr<number>-<compute_type>
  - merge_group: mg<pr_number>-<compute_type>
  - workflow_dispatch: <branch>-<compute_type>
  - fallback: <compute_type>-<sha7>
All inputs sanitized (alphanumeric + hyphens, 60-char branch cap).

deploy.yml: Add workflow_dispatch trigger with compute_type choice
input (all, agentcore). Handle non-PR triggers (push to main,
workflow_dispatch on build) by deploying all registered types.
Label-based resolution only applies to PR triggers.

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(ci): move deploy input to build.yml, use deploy-intent artifact

build.yml now owns the deploy decision via workflow_dispatch choice input:
  - "-" (default): no deploy
  - "agentcore": deploy agentcore after build

Build always writes a deploy-intent.json artifact encoding the decision:
  - push to main: intent = compute_type (deploy)
  - workflow_dispatch with choice: intent = selected value
  - pull_request: intent = "labels" (defer to deploy.yml label check)
  - anything else: intent = "-" (no deploy)

deploy.yml simplified to a pure consumer:
  - Removes workflow_dispatch trigger (single entry point is build.yml)
  - Downloads deploy-intent.json from triggering build run
  - Reads intent: "-" = skip, "labels" = check PR labels, else = deploy

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add input validation and allowlist enforcement for compute types

Addresses 5 security findings:

1. CRITICAL: deploy.yml wildcard case now validates intent against
   ALLOWED_COMPUTE_TYPES before passing to matrix. Invalid values
   cause the workflow to fail with an error annotation.

2. MEDIUM: PR label deploy:<type> values are filtered through
   validate_compute_type(). Invalid labels emit a warning and are
   ignored rather than passed to the deploy matrix.

3. MEDIUM: sanitize() now lowercases input and prefixes "s-" if the
   result starts with a digit (CloudFormation requires letter start).

4. LOW: deploy-intent.json is now written with jq (safe JSON encoding)
   instead of shell string interpolation.

5. LOW: PR_NUMBER is validated as numeric before use in stack names.

The ALLOWED_COMPUTE_TYPES allowlist is defined as an env var in each
step that performs validation. When new compute types are added to the
matrix, this allowlist must be updated in both build.yml and deploy.yml.

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add deploy-intent.json to .gitignore

The file is generated during build and was being picked up by the
mutation detection step, causing the build to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(security): skip deploy pipeline for fork PRs + document CI/CD flow

Adds `head_repository.full_name == github.repository` guard to
deploy.yml's resolve-targets job. Prevents fork PRs from triggering
deploy approval prompts — a fork could modify build.yml to produce
a deploy-intent artifact that would otherwise reach the approval gate.

Also documents the full deploy.yml pipeline in DEPLOYMENT_GUIDE.md
(build → deploy stages, security controls, fork exclusion rationale,
administrator setup). This makes the deploy flow explicit for
contributors, reviewers, agents, and administrators.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(docs): update roadmap to reflect recent features

* chore(ci): remove deploy pipeline from roadmap branch

Deploy CI belongs in PR #98 (feat/deploy-yml), not the roadmap docs PR.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: scottschreckengaust <345885+scottschreckengaust@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Scott Schreckengaust <scottschreckengaust@users.noreply.github.com>
Co-authored-by: bgagent <bgagent@noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@scottschreckengaust
Copy link
Copy Markdown
Contributor Author

**B1

Will fix

**B2 (c) at minimum drop --require-approval never and surface a cdk diff in the step summary for the approver. (The RFC's Phase 3 checklist already lists the cdk diff step as unchecked — that's exactly the compensating control here.)

But without the --require-approval never the CI/CD process will not deploy, the other (a) and (b) options do not accomplish the needs. Would that suffice (including the ability to review the cdk diff before approval)?

**B3

Yes

@scottschreckengaust
Copy link
Copy Markdown
Contributor Author

Re: Governance notes

scottschreckengaust and others added 7 commits June 1, 2026 18:32
Adds a deploy workflow that:
- Fires on workflow_run after build.yml succeeds
- Resolves deploy targets from PR labels (deploy=all, deploy:<type>=one)
  or defaults to all registered types on push to main
- Skips entirely (no approval prompt) when no deploy labels are present
- Downloads the exact cdk-<compute_type>-out artifact from the build run
- Uses OIDC to assume the CDK bootstrap deploy role
- Deploys via `cdk deploy --app cdk/cdk.out --all --require-approval never`
- Protected by the `deploy` GitHub environment (manual approval required)
- Concurrency: non-cancellable once started, max-parallel 3

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build.yml: Replace hardcoded stackName with trigger-aware naming:
  - push to main: main-<compute_type>
  - pull_request: pr<number>-<compute_type>
  - merge_group: mg<pr_number>-<compute_type>
  - workflow_dispatch: <branch>-<compute_type>
  - fallback: <compute_type>-<sha7>
All inputs sanitized (alphanumeric + hyphens, 60-char branch cap).

deploy.yml: Add workflow_dispatch trigger with compute_type choice
input (all, agentcore). Handle non-PR triggers (push to main,
workflow_dispatch on build) by deploying all registered types.
Label-based resolution only applies to PR triggers.

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build.yml now owns the deploy decision via workflow_dispatch choice input:
  - "-" (default): no deploy
  - "agentcore": deploy agentcore after build

Build always writes a deploy-intent.json artifact encoding the decision:
  - push to main: intent = compute_type (deploy)
  - workflow_dispatch with choice: intent = selected value
  - pull_request: intent = "labels" (defer to deploy.yml label check)
  - anything else: intent = "-" (no deploy)

deploy.yml simplified to a pure consumer:
  - Removes workflow_dispatch trigger (single entry point is build.yml)
  - Downloads deploy-intent.json from triggering build run
  - Reads intent: "-" = skip, "labels" = check PR labels, else = deploy

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ypes

Addresses 5 security findings:

1. CRITICAL: deploy.yml wildcard case now validates intent against
   ALLOWED_COMPUTE_TYPES before passing to matrix. Invalid values
   cause the workflow to fail with an error annotation.

2. MEDIUM: PR label deploy:<type> values are filtered through
   validate_compute_type(). Invalid labels emit a warning and are
   ignored rather than passed to the deploy matrix.

3. MEDIUM: sanitize() now lowercases input and prefixes "s-" if the
   result starts with a digit (CloudFormation requires letter start).

4. LOW: deploy-intent.json is now written with jq (safe JSON encoding)
   instead of shell string interpolation.

5. LOW: PR_NUMBER is validated as numeric before use in stack names.

The ALLOWED_COMPUTE_TYPES allowlist is defined as an env var in each
step that performs validation. When new compute types are added to the
matrix, this allowlist must be updated in both build.yml and deploy.yml.

Part of #73 Phase 3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The file is generated during build and was being picked up by the
mutation detection step, causing the build to fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `head_repository.full_name == github.repository` guard to
deploy.yml's resolve-targets job. Prevents fork PRs from triggering
deploy approval prompts — a fork could modify build.yml to produce
a deploy-intent artifact that would otherwise reach the approval gate.

Also documents the full deploy.yml pipeline in DEPLOYMENT_GUIDE.md
(build → deploy stages, security controls, fork exclusion rationale,
administrator setup). This makes the deploy flow explicit for
contributors, reviewers, agents, and administrators.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-line matrix corruption

Without -c, jq pretty-prints the JSON array across multiple lines.
GitHub Actions parses GITHUB_OUTPUT as one key=value per line, so
`matrix` captured only `[` and fromJson() in the deploy job failed —
the deploy:<type> label path never deployed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
scottschreckengaust and others added 5 commits June 1, 2026 18:32
… empty/leading-hyphen results

Two bugs in stack-name sanitize():
1. cut -c1-60 ran after trailing-hyphen trim, so a hyphen at
   position 60 survived → CloudFormation rejects trailing hyphens.
   Fix: reorder to cut first, then trim.
2. All-special-char refs sanitized to empty → STACK_NAME="-agentcore"
   (leading hyphen). The guard only checked ^[0-9], not ^[a-z].
   Fix: guard checks for empty or any non-letter start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jq's index("deploy:*") matches only a label literally named "deploy:*",
not a wildcard. The bare "deploy" label already triggers all-types
deployment. This dead branch was misleading and could become a surprise
full-deploy vector if such a label were ever created.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t risk

ALLOWED_COMPUTE_TYPES was duplicated across both workflows plus the
matrix; adding a new compute type required updating 5 places. Now
deploy.yml derives ALL_TYPES dynamically from ALLOWED_COMPUTE_TYPES,
and cross-reference comments link the remaining sync points.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…flows

build.yml uses exit 1 (no recovery — invalid dispatch input aborts step).
deploy.yml uses return 1 (callers handle gracefully — skip type or set
empty matrix). One-line comments prevent future confusion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…empty

workflow_run.pull_requests[0].number is empty for forks and often for
indirect workflow_run triggers. Previously the labels deploy path would
silently no-op. Now we fall back to gh api commits/{sha}/pulls to
resolve the PR number, with a warning if neither source yields a result.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@scottschreckengaust
Copy link
Copy Markdown
Contributor Author

Review follow-up — status of all items

Branch rebased onto latest main (includes PR #217 TMPDIR fix).

Blocking

Item Status
B1filter_valid_types multi-line JSON ✅ Fixed (jq -c)
B2 — PR-synthesized artifact trust gap ⏳ Pending design decision (options a/b/c from review)
B3sanitize() edge cases ✅ Fixed (reorder cut/trim, guard empty + leading non-letter)

Non-blocking

Item Status
deploy:* dead literal match ✅ Removed
ALLOWED_COMPUTE_TYPES duplication ALL_TYPES derived dynamically; cross-reference comments added
exit 1 vs return 1 ✅ One-line comments explaining the design choice in each workflow
PR_NUMBER_FROM_EVENT empty for forks ✅ Fallback via gh api commits/{sha}/pulls with warning on failure

Docs / Governance

Item Status
ROADMAP update Not needed — the deploy workflow is internal CI/CD tooling, not a user-facing platform capability tracked in the roadmap
Branch naming Acknowledged (not renaming — would disrupt open PR)
approved label on #73 ✅ Added

All code-actionable items from the review are addressed. Only B2 remains as a design decision.

@scottschreckengaust scottschreckengaust force-pushed the feat/deploy-yml branch 3 times, most recently from 51de804 to 27386f9 Compare June 1, 2026 19:18
…ng control)

Adds a `diff` job that runs immediately after resolve-targets (no
environment gate). It downloads the same CDK artifact, assumes AWS
credentials, and runs `cdk diff` — surfacing all CloudFormation
changes in the GitHub step summary. The reviewer can inspect the
diff before clicking "Approve" on the deploy environment gate.

The deploy job now `needs: [resolve-targets, diff]` ensuring the
diff completes before approval is requested. `--require-approval never`
is retained on the deploy step because CDK hard-fails without TTY
in CI; the GitHub environment approval gate is the human checkpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@krokoko krokoko added this pull request to the merge queue Jun 2, 2026
Merged via the queue into main with commit 125e205 Jun 2, 2026
6 checks passed
@krokoko krokoko deleted the feat/deploy-yml branch June 2, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants