Skip to content

feat: --profile materializes its preset; destroy sweeps leftovers - #38

Merged
Robobc merged 1 commit into
mainfrom
feat/profiles-materialize-presets
Aug 25, 2026
Merged

feat: --profile materializes its preset; destroy sweeps leftovers#38
Robobc merged 1 commit into
mainfrom
feat/profiles-materialize-presets

Conversation

@Robobc

@Robobc Robobc commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What

Usability-review blocker 1: deployment intent was not durable. PROFILE_FLAGS exported feature flags for one run and died with it, while app.py defaults A2A to on — so deploy --profile greenfield followed by a plain deploy silently added two runtimes and their CodeBuild projects. Destroy had the mirror problem: it can only see stacks the current config synthesizes, so a profile switch left the NAT gateway billing while the README claimed it was removed.

A profile IS a preset

The five profile names and the five presets/*.yaml files were already one-to-one — the same intent written twice. Now there's one copy:

  • --profile X writes presets/X.yaml to platform.yaml (the durable manifest) with a generated-from header, before config load so it participates with the right precedence (env > platform.yaml > workshop.env).
  • A hand-edited platform.yaml (no header) is refused without --yes — your manifest is not clobbered by a habit-typed --profile.
  • --dry-run never writes anything.
  • PROFILE_FLAGS is deleted, with a check that keeps it dead.
  • platform.yaml is gitignored (per-deployment; committing yours is git add -f).

Durability proven end to end: after materializing greenfield, a plain next run reads ENABLE_A2A=false from the manifest instead of resurrecting the runtimes from the legacy default.

Destroy sweeps what the config cannot see

After a full destroy, sweep_leftovers asks CloudFormation what still exists under the prefix — the stacks invisible to the current config — and lists the secrets this script creates outside CloudFormation (IdP, 3LO, API keys; prefixed names only, a bring-your-own secret is the operator's and is never swept).

Deletion policy: interactive runs ask per category; --yes deletes; NON_INTERACTIVE without --yes reports and leaves — CI does not remove resources the config doesn't declare without being told explicitly.

Checks

  • (p) materialization: fresh write, regeneration, hand-edit refusal + --yes override, unknown profile lists the real presets.
  • (q) the sweep: deletes stacks and orphaned secrets with --yes, only reports without.
  • (r) PROFILE_FLAGS must stay dead.
  • check-workshop-flow's sandbox now carries presets/ — a profile is a preset file, so validation resolves against the directory.

Verified

22 deploy-config checks, workshop-flow green, contract parity green across all 5 presets, 127 tests, shellcheck clean.

…an't see

Usability-review blocker 1: deployment intent was not durable. PROFILE_FLAGS
exported feature flags for one run and died with it, while app.py defaults
A2A to ON — so `deploy --profile greenfield` followed by a plain `deploy`
silently added two runtimes and their CodeBuild projects. destroy had the
mirror problem: it can only see stacks the CURRENT config synthesizes, so a
profile switch left the NAT gateway billing with README claiming otherwise.

The profile names and the presets/*.yaml files were already 1:1 — the same
intent written twice. Now a profile IS a preset:

- --profile X writes presets/X.yaml to platform.yaml (the durable manifest)
  with a generated-from header, BEFORE config load so it participates with
  correct precedence (env > platform.yaml > workshop.env). A hand-edited
  platform.yaml (no header) is refused without --yes. --dry-run never writes.
  PROFILE_FLAGS is deleted; profile intent lives in exactly one place.
- platform.yaml is gitignored (per-deployment; commit yours with git add -f).
- After a full destroy, sweep_leftovers asks CloudFormation what still exists
  under the prefix (the stacks the config cannot see) and lists the secrets
  this script creates outside CloudFormation (IdP/3LO/API keys, prefixed
  names only — bring-your-own secrets are the operator's and never swept).
  Interactive: ask per category. --yes: delete. NON_INTERACTIVE without
  --yes: report and leave — CI does not remove undeclared resources without
  being told explicitly.

Checks (p)(q)(r): materialization (fresh write, regeneration, hand-edit
refusal + --yes override, unknown profile lists real presets), the sweep
(deletes with --yes, only reports without), and a guard that PROFILE_FLAGS
stays dead. check-workshop-flow's sandbox now carries presets/ (a profile is
a preset file, so validation resolves against the directory).

Verified: 22 deploy-config checks, workshop-flow green, contract parity green
(5 presets), 127 tests, shellcheck clean. Durability proven end to end:
after materializing greenfield, a plain next run reads ENABLE_A2A=false from
the manifest instead of resurrecting the runtimes from the legacy default.
@github-actions

Copy link
Copy Markdown

Commit: ddb0b4e | Updated: 2026-08-25 10:40:58 UTC

Security Scan Results

Scanner S C H M L I Time Action Result Thresh
bandit 0 0 0 0 0 0 788ms 0 PASSED MED (g)
cdk-nag 0 0 0 0 0 0 6.6s 0 PASSED MED (g)
cfn-nag 0 0 0 0 0 0 7ms 0 PASSED MED (g)
checkov 0 0 0 0 0 0 4.4s 0 PASSED MED (g)
detect-secrets 0 5 0 0 0 0 777ms 5 FAILED MED (g)
grype 0 0 0 0 0 0 1m 6s 0 PASSED MED (g)
npm-audit 0 0 0 0 0 0 186ms 0 PASSED MED (g)
opengrep 0 0 0 0 0 0 17.7s 0 PASSED MED (g)
semgrep 0 0 0 0 0 0 <1ms 0 MISSING MED (g)
syft 0 0 0 0 0 0 2.2s 0 PASSED MED (g)

@Robobc
Robobc merged commit 0fe18b9 into main Aug 25, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant