docs(init): validate specification — v1.24 consumer guide accuracy + env-promotion lifecycle

---ci---
project: acdl
phase: 0
milestone: v1.24
status: specify
---/ci---
This commit is contained in:
Jon Chery
2026-08-12 14:20:34 +00:00
parent afca994511
commit a7a93d95d1
4 changed files with 178 additions and 26 deletions
+168
View File
@@ -2028,3 +2028,171 @@ assert 20 main + 1 appendix.
| REQ-273 | P5 | complete |
| REQ-274 | P5 | complete |
| REQ-275 | P5 | complete |
## v1.24 — Consumer Guide Accuracy & Env-Promotion Lifecycle Enforcement
> **Feature milestone** (one `feat` phase: env-transition destroy enforcement;
> the rest are `fix`/`docs`/`test`). Tags run on the **v1.23.x** line
> (milestone v1.24 → tags v1.23.0..v1.23.N). Final patch = milestone release.
>
> Two problems, one milestone:
> 1. **Consumer guide accuracy.** A review of `docs/consumer-guide.md`
> found 5 issues: (a) Step 8 tells consumers to change `environment:` in
> their contract to promote, which (b) contradicts the same doc's
> "Per-environment deployment" section (lines 396-477) that says
> "promotion-without-editing," (c) the Step 3 contract-fields table
> lists stale fields (`uses`, `module`) that no longer exist in the
> schema (real fields: `id`, `name`, `environment`, `infrastructure`),
> (d) Step 4 caller example is inconsistent with Step 2, and (e) Step 5
> stage 8 says "(dev only)" when higher envs do apply after attestation.
> 2. **Environment-promotion lifecycle enforcement.** When a consumer
> edits `environment:` on a stable `contract.id` (Shape A promotion),
> the Terraform state key
> `spike/{stack_name}/{environment}/terraform.tfstate` (adapter.py:129)
> changes — creating a fresh state file in the new env while the prior
> env's resources remain live in AWS with no destroy ever running. This
> **orphans resources** and violates the platform's full-lifecycle-
> management mission. The platform must detect the env change and
> destroy the prior env's resources before building the new env. There
> must be **no path that orphans resources** — fail closed if the
> destroy fails.
>
> The per-environment caller-workflow path (Shape B: one caller workflow
> per env, `environment` passed as a workflow input) remains a fully
> supported alternative with no destroy needed (each env has its own state
> from day one). Both shapes are documented.
### Category: Consumer Guide Fixes (docs)
- **REQ-276:** `docs/consumer-guide.md` Step 3 "Contract fields" table is
corrected to list the real schema-enforced fields: `id`, `name`,
`environment`, `infrastructure` (matching `schemas/contract.schema.json`
`required` and the worked examples). The stale `uses` and `module` rows
are removed. The `uses` row's note about versioned tags moves to the
Step 2 caller-workflow section (where the version pin actually lives).
- **REQ-277:** `docs/consumer-guide.md` Step 4 caller workflow example is
made consistent with Step 2 — both show `environment` in `with:` or both
omit it with a "dev is the default" note. The two canonical caller
snippets no longer disagree.
- **REQ-278:** `docs/consumer-guide.md` Step 5 stage 8 "(dev only)" is
corrected to "(autonomous in dev; higher environments apply after HITL
attestation)" to match `docs/environments/index.md` autonomy table.
- **REQ-279:** `docs/consumer-guide.md` Step 8 "Promote to qa / prod" is
rewritten. It documents that editing `environment:` on a stable
`contract.id` (Shape A) **is a supported promotion path** and that the
platform **destroys the prior environment's resources before building
the new environment** — there is no orphan path; if the destroy fails,
the pipeline fails closed. It includes the worked qa example with a
note: "Changing `environment: dev` → `environment: qa` triggers a
destroy of the dev stack (state key `spike/{id}/dev/`) then an apply
against the qa stack (state key `spike/{id}/qa/`). Both emit evidence
events." It cross-references the "Per-environment deployment" section
(Shape B) as the alternative.
- **REQ-280:** `docs/consumer-guide.md` "Per-environment deployment"
section (lines 396-477) gains a lead sentence clarifying it is **Shape
B** (the alternative to Shape A's edit-and-destroy path in Step 8), and
that it avoids the destroy step because each env has its own state from
first deploy. The existing table, interpolation reference, and HITL
gate docs are preserved.
- **REQ-281:** `docs/consumer-guide.md` Reference table "sample contracts
use `@v1.19`" wording is corrected — the sample contracts no longer
carry `uses:` (the version pin lives in the caller workflow). Reword to
"used with caller workflow `@v1.19`" or the current tag.
### Category: Env-Transition Detect-and-Destroy (feat)
- **REQ-282:** New module `core/env_transition.py` provides:
`detect_prior_env(contract_id, consumer_repo, new_env) -> Optional[str]`
— queries the `nova-contracts` DynamoDB table (PK `consumerRepo`, SK
`contractId#submittedAt`, written by `core/lambda/contract_ingestor.py`)
for the last-applied environment for this consumer+contract. Returns
the prior env name if it differs from `new_env`, else `None`. Failures
to reach DynamoDB log a warning and return `None` (conservative — Shape
B legitimately has no prior record). Uses boto3 with the ABAC-scoped
deploy role; respects `core/env.py` for config.
- **REQ-283:** `core/env_transition.py` provides
`record_applied_env(contract_id, consumer_repo, env)` — called after a
successful apply to upsert the last-applied env record in the
`nova-contracts` table (SK suffix `#LAST_APPLIED`). Idempotent.
- **REQ-284:** `scripts/run_platform.sh` gains a new **Step 0b:
environment-transition check** (after Step 0 env onboarding, before
Step 1 contract validation). It reads `CONTRACT_ID` + `CONSUMER_REPO`
(from `GITHUB_REPOSITORY` / `NOVA_CONSUMER_REPO`), calls
`env_transition.py detect`, and if a prior env is returned that differs
from the new env: (a) re-resolves the contract with
`environment_override=$PRIOR_ENV` to emit the prior TF config + state
backend; (b) runs `terraform init -reconfigure` + `terraform destroy
-auto-approve` against the prior env's state key
(`spike/{id}/{prior_env}/terraform.tfstate`); (c) emits a
`nova.env.destroyed` evidence event via `core/outbox_writer.py`; (d)
**fails closed** — if the destroy exits non-zero, the pipeline exits
non-zero and no apply runs (no orphan path). If no prior env exists
(first deploy or Shape B), proceeds normally.
- **REQ-285:** `scripts/run_platform.sh` records the applied env after a
successful apply (calls `env_transition.py record` with the resolved
env). This is the source of truth for the next run's detect step.
- **REQ-286:** `.github/workflows/deploy.yml` passes
`NOVA_CONSUMER_REPO=${{ github.repository }}` to `run_platform.sh` so
`env_transition.py` can query DynamoDB with the correct PK.
- **REQ-287:** `adapters/terraform/adapter.py` state-key block
(lines 127-133) gains a doc comment clarifying the key
`spike/{stack_name}/{environment}/terraform.tfstate` is **env-scoped
precisely to support destroy-on-env-change** — the env segment lets the
detect-and-destroy step target the prior env's state without affecting
the new env. No behavior change.
### Category: Tests (test)
- **REQ-288:** `tests/test_env_transition.py` covers:
`detect_prior_env` returns `None` when no record exists (first deploy);
returns the prior env when a record exists and differs; returns `None`
when the record matches `new_env` (re-apply same env);
`record_applied_env` writes the record. Uses moto for DynamoDB mocking
(pattern from `tests/test_contract_ingestor.py`).
- **REQ-289:** `tests/test_run_platform_env_transition.py` asserts:
`run_platform.sh` has a "Step 0b: environment-transition check" block;
it calls `env_transition.py detect`; it calls `terraform destroy`
against the prior env when a transition is detected; it fails closed on
destroy failure (no apply runs); it records the applied env after a
successful apply. Pattern: `tests/test_pipeline.py:79-95` (read the
script text + assert substrings).
- **REQ-290:** `tests/test_consumer_guide_per_env_section.py`
`test_consumer_guide_states_no_field_editing` is renamed to
`test_consumer_guide_documents_both_promotion_shapes` and asserts both
shapes are present (Shape A: edit environment with destroy semantics;
Shape B: per-environment caller workflows). The other 5 assertions in
the file are preserved. A new test
`test_consumer_guide_documents_destroy_on_env_change` asserts the guide
states the platform destroys the prior env's resources when the
environment field is changed and that there is no orphan path.
### Out of Scope (v1.24)
- **Cross-account destroy.** If the prior and new envs are in different
AWS accounts (per `docs/environments/index.md`), the destroy step needs
the prior env's role credentials. The current scaffold
(`core/environments/dev.json`) uses one account. Cross-account destroy
is deferred to a future milestone; v1.24 targets the same-account case
and documents the cross-account limitation.
- **Decommission pipeline integration.** The env-transition destroy is a
direct `terraform destroy` (not the 2-step HITL decommission). The
decommission pipeline remains for explicit stack teardown with SRE
gates; env-transition is an automated lifecycle step.
- **Removing Shape B.** Both shapes stay supported. Shape B is not
deprecated.
### v1.24 Traceability
| REQ | Phase | Status |
|-----|-------|--------|
| REQ-276 | P1 | pending |
| REQ-277 | P1 | pending |
| REQ-278 | P1 | pending |
| REQ-279 | P1 | pending |
| REQ-280 | P1 | pending |
| REQ-281 | P1 | pending |
| REQ-282 | P2 | pending |
| REQ-283 | P2 | pending |
| REQ-284 | P2 | pending |
| REQ-285 | P2 | pending |
| REQ-286 | P2 | pending |
| REQ-287 | P2 | pending |
| REQ-288 | P3 | pending |
| REQ-289 | P3 | pending |
| REQ-290 | P3 | pending |