5dc97673e5
---ci--- project: acdl phase: 0 milestone: v1.24 status: research ---/ci---
187 lines
9.4 KiB
Markdown
187 lines
9.4 KiB
Markdown
# Nova — v1.24 Research Findings
|
|
|
|
> Phase: research (pre-execution). Milestone: v1.24 (Consumer Guide Accuracy
|
|
> & Env-Promotion Lifecycle Enforcement). Status: research.
|
|
> Researcher: ci-researcher. Autonomy: full.
|
|
|
|
## 1. Problem domain
|
|
|
|
Two distinct problem spaces in one milestone:
|
|
|
|
### 1a. Consumer guide accuracy (docs)
|
|
|
|
The consumer guide (`docs/consumer-guide.md`, 477 lines) has 5 accuracy
|
|
defects identified in review:
|
|
|
|
1. **Step 3 contract fields table (lines 141-147)** lists `uses`, `module`,
|
|
`environment`, `inputs`. The actual schema
|
|
(`schemas/contract.schema.json:7`) requires `id`, `name`, `environment`,
|
|
`infrastructure`. The `uses` field was dropped in v1.10.2 (REQ-50
|
|
superseded) and `module` was replaced by the `infrastructure` map key.
|
|
The worked examples (lines 111-137) use the correct fields.
|
|
|
|
2. **Step 4 caller (lines 173-183)** omits `environment:` in `with:`, while
|
|
Step 2 (lines 94-101) shows `environment: dev`. The two canonical caller
|
|
snippets disagree.
|
|
|
|
3. **Step 5 stage 8 (lines 232, 253)** says "(dev only)" for the apply
|
|
stage. Higher environments *do* apply — they apply after HITL
|
|
attestation per `docs/environments/index.md:44-54`.
|
|
|
|
4. **Step 8 (lines 290-306)** says "Change `environment` in your contract"
|
|
to promote, which contradicts the same doc's "Per-environment
|
|
deployment" section (lines 398-402): "you do not edit the `environment:`
|
|
field… Promotion = running the matching job." The test
|
|
`test_consumer_guide_states_no_field_editing` asserts the no-editing
|
|
model.
|
|
|
|
5. **Reference table (lines 329-330)** says sample contracts "use `@v1.19`"
|
|
but the sample contracts (`contracts/static-assets.yml`,
|
|
`contracts/microservice.yml`) don't carry `uses:` — the version pin
|
|
lives in the caller workflow.
|
|
|
|
### 1b. Environment-promotion lifecycle enforcement (feat)
|
|
|
|
**Root cause confirmed by code inspection:**
|
|
|
|
- `adapters/terraform/adapter.py:129` sets the Terraform state key to:
|
|
`spike/{stack_name}/{environment}/terraform.tfstate`
|
|
- `stack_name` = `contract["id"]` (stable across env changes, per
|
|
`core/contract_resolver.py:584`).
|
|
- When a consumer edits `environment:` from `dev` → `qa` on the same
|
|
contract `id`, the state key changes from `spike/assets/dev/` to
|
|
`spike/assets/qa/`. Terraform initializes a **fresh state file** in the
|
|
new env's state path. The prior env's resources remain live in AWS with
|
|
their state file untouched. **No destroy ever runs.** This orphans
|
|
resources.
|
|
|
|
**The user's binding directive:** Editing `environment:` on a stable
|
|
`contract.id` is a valid promotion path (Shape A). The platform **must**
|
|
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.
|
|
|
|
## 2. Existing codebase structure (integration points)
|
|
|
|
### DynamoDB `nova-contracts` table (the prior-env source of truth)
|
|
|
|
- **Table:** `nova-contracts` (env var `CONTRACTS_TABLE`, default
|
|
`nova-contracts`). Defined in `core/lambda/contract_ingestor.py:25`.
|
|
- **Schema:** PK `consumerRepo` (S), SK `contractId#submittedAt` (S).
|
|
Attributes: `contractId`, `contract`, `environment`, `status`,
|
|
`submittedAt`.
|
|
- **Written by:** `_submit_contract()` at
|
|
`core/lambda/contract_ingestor.py:135-176`. The consumer's deploy
|
|
workflow submits the contract via the Lambda Function URL.
|
|
- **Read pattern for env-transition:** Query by PK `consumerRepo` + SK
|
|
begins_with `contractId#` + FilterExpression `status = "submitted"` →
|
|
sort by `submittedAt` desc → take the latest → read its `environment`.
|
|
This is the last-submitted env. For the last-*applied* env, a new
|
|
`#LAST_APPLIED` SK suffix is added (REQ-283).
|
|
- **Test pattern:** `tests/test_contract_ingestor.py:74-110`
|
|
(`moto_contracts_table` fixture) uses moto `mock_aws` to create the
|
|
table. The env-transition tests will mirror this pattern.
|
|
|
|
### Outbox writer (evidence events)
|
|
|
|
- `core/outbox_writer.py` writes hash-chained evidence events to
|
|
`nova-outbox` table. PK `contractId`, SK `eventType#eventTs`.
|
|
- The env-transition destroy step emits a `nova.env.destroyed` event via
|
|
this writer (REQ-284c). Pattern: build an event dict with `contractId`,
|
|
`eventType: "ENV_DESTROYED"`, `environment: <prior_env>`, `ts`, then
|
|
call `write_event()`.
|
|
|
|
### Contract resolver (environment override)
|
|
|
|
- `core/contract_resolver.py:460-483` `resolve()` accepts
|
|
`environment_override` — when set, it overrides the contract's
|
|
`environment` field **before** schema validation and interpolation
|
|
(D-088). This is the mechanism the destroy step uses to re-resolve the
|
|
contract against the prior env: `resolve(contract, env_override=prior_env)`.
|
|
- Already tested in `tests/test_deploy_workflow_env_input.py:35-52`.
|
|
|
|
### run_platform.sh (where Step 0b goes)
|
|
|
|
- `scripts/run_platform.sh` is the platform pipeline. Step 0 (lines 222-239)
|
|
is the environment onboarding check. Step 1 (lines 241-249) is contract
|
|
validation. **Step 0b goes between them** (after onboarding, before
|
|
validation).
|
|
- The destroy step mirrors the existing `--destroy` mode (lines 376-394):
|
|
`terraform init -reconfigure` + `terraform destroy -auto-approve`. The
|
|
difference: it runs against the *prior* env's state key, not the current
|
|
one.
|
|
- `CONTRACT_ID` is already set at line 217 (`NOVA_CONTRACT_ID` with a
|
|
default). `CONSUMER_REPO` needs to be derived from `GITHUB_REPOSITORY`
|
|
or a new `NOVA_CONSUMER_REPO` env var (REQ-286).
|
|
|
|
### deploy.yml (consumer repo → platform)
|
|
|
|
- `.github/workflows/deploy.yml:132` runs
|
|
`bash platform/scripts/run_platform.sh $MODE_FLAG $ENV_FLAG "${{ inputs.contract }}"`.
|
|
- To pass `NOVA_CONSUMER_REPO`, add `NOVA_CONSUMER_REPO=${{ github.repository }}`
|
|
as an env var on the "Run the platform pipeline" step (REQ-286).
|
|
|
|
### Test patterns
|
|
|
|
- **Shell script assertions:** `tests/test_pipeline.py:79-95` reads the
|
|
script text and asserts substrings. The env-transition tests follow this
|
|
pattern for `run_platform.sh`.
|
|
- **DynamoDB mocking:** `tests/test_contract_ingestor.py:74-110` uses moto
|
|
`mock_aws` + `boto3.client` + `create_table`. The env-transition tests
|
|
follow this pattern.
|
|
- **Consumer guide assertions:** `tests/test_consumer_guide_per_env_section.py`
|
|
reads `docs/consumer-guide.md` text and asserts substrings. The updated
|
|
tests follow this pattern.
|
|
|
|
## 3. Persona assessment (v1.24)
|
|
|
|
This milestone has two distinct work territories:
|
|
|
|
1. **Docs (P1):** `docs/consumer-guide.md` edits + test updates. This is
|
|
lead-developer territory (narrative/docs + test assertions).
|
|
2. **Platform code (P2):** `core/env_transition.py` (new), `scripts/run_platform.sh`
|
|
edits, `.github/workflows/deploy.yml` edit, `adapters/terraform/adapter.py`
|
|
doc comment. This is backend-engineer territory (Python + bash + YAML).
|
|
3. **Tests (P3):** `tests/test_env_transition.py` (new), `tests/test_run_platform_env_transition.py`
|
|
(new), `tests/test_consumer_guide_per_env_section.py` updates. Split:
|
|
backend-engineer for the env_transition + pipeline tests; lead-developer
|
|
for the consumer guide test updates.
|
|
|
|
**Roster:** lead-developer (docs + guide tests) + backend-engineer (Python +
|
|
bash + YAML + pipeline tests). frontend-engineer stays deactivated (no UI).
|
|
data-engineer not needed (no schema changes — the `nova-contracts` table
|
|
already exists with the right shape; we only add a new SK suffix). No new
|
|
personas.
|
|
|
|
## 4. Key decisions logged
|
|
|
|
| ID | Decision | Confidence | Source |
|
|
|----|----------|------------|--------|
|
|
| D-201 | Both promotion shapes supported (A: edit+destroy, B: per-env callers) | 0.95 | User directive |
|
|
| D-202 | Prior-env source of truth = `nova-contracts` DynamoDB table | 0.85 | User directive + code inspection |
|
|
| D-203 | Detect-and-destroy at pipeline start (Step 0b) | 0.85 | User directive |
|
|
| D-204 | Fail closed on destroy failure (no orphan path) | 0.95 | User directive |
|
|
| D-205 | Cross-account destroy out of scope (same-account only) | 0.80 | CLARIFY A3 |
|
|
| D-206 | Env-transition destroy is NOT the HITL decommission pipeline | 0.85 | CLARIFY A5 |
|
|
| D-207 | Last-applied env recorded via `#LAST_APPLIED` SK in `nova-contracts` | 0.85 | RESEARCH §2 |
|
|
| D-208 | State key `spike/{id}/{env}/` stays as-is (correct for both shapes) | 0.90 | RESEARCH §1b |
|
|
|
|
## 5. Pitfalls
|
|
|
|
1. **Destroy needs the prior env's Terraform config, not the new env's.**
|
|
The destroy step must re-resolve the contract with
|
|
`environment_override=prior_env` so the emitted TF matches the prior
|
|
env's resources. If we resolve with the new env, the destroy plan won't
|
|
match the prior state → terraform tries to create, not destroy.
|
|
2. **`terraform init -reconfigure` is required** when switching state
|
|
backends between envs (if envs use different state buckets). The
|
|
`-reconfigure` flag tells Terraform to forget the previous backend config.
|
|
3. **The `deletion_protection` NFR (REQ-86) blocks destroy.** The destroy
|
|
step must resolve with `deletion_protection: false` injected (same as
|
|
decommission Step 2 in `scripts/run_decommission.sh:34-37`). Without
|
|
this, `terraform destroy` fails on `prevent_destroy` lifecycle blocks.
|
|
4. **DynamoDB may not be reachable in local/CI mode.** The detect step
|
|
must handle `ClientError` / `EndpointNotFound` gracefully → log warning
|
|
+ return `None` (conservative). This is documented in REQ-282.
|
|
5. **The consumer guide test `test_consumer_guide_states_no_field_editing`
|
|
will fail after the Step 8 rewrite.** It must be updated in the same
|
|
phase as the guide edit (P1) or the test suite breaks. |