feat: --profile materializes its preset; destroy sweeps leftovers - #38
Merged
Conversation
…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.
|
Commit: Security Scan Results
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Usability-review blocker 1: deployment intent was not durable.
PROFILE_FLAGSexported feature flags for one run and died with it, whileapp.pydefaults A2A to on — sodeploy --profile greenfieldfollowed by a plaindeploysilently 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/*.yamlfiles were already one-to-one — the same intent written twice. Now there's one copy:--profile Xwritespresets/X.yamltoplatform.yaml(the durable manifest) with a generated-from header, before config load so it participates with the right precedence (env > platform.yaml > workshop.env).platform.yaml(no header) is refused without--yes— your manifest is not clobbered by a habit-typed--profile.--dry-runnever writes anything.PROFILE_FLAGSis deleted, with a check that keeps it dead.platform.yamlis gitignored (per-deployment; committing yours isgit add -f).Durability proven end to end: after materializing greenfield, a plain next run reads
ENABLE_A2A=falsefrom the manifest instead of resurrecting the runtimes from the legacy default.Destroy sweeps what the config cannot see
After a full destroy,
sweep_leftoversasks 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;
--yesdeletes;NON_INTERACTIVEwithout--yesreports and leaves — CI does not remove resources the config doesn't declare without being told explicitly.Checks
--yesoverride, unknown profile lists the real presets.--yes, only reports without.PROFILE_FLAGSmust stay dead.check-workshop-flow's sandbox now carriespresets/— 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.