Skip to content

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

Draft
scottschreckengaust wants to merge 8 commits into
mainfrom
feat/deploy-yml
Draft

feat(ci): add deploy pipeline with OIDC, dynamic stack naming, and deploy-intent artifact#98
scottschreckengaust wants to merge 8 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

scottschreckengaust and others added 2 commits May 15, 2026 00:23
… 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>
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>
Comment thread .github/workflows/deploy.yml
Comment thread .github/workflows/deploy.yml Outdated
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>
@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
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>
@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
…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>
scottschreckengaust and others added 2 commits May 15, 2026 03:14
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>
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