docs(P1): consumer guide accuracy fixes — REQ-276..281,290
- Step 3 contract fields table: stale uses/module → real id/name/environment/infrastructure (REQ-276) - Step 4 caller: add environment: dev to match Step 2 (REQ-277) - Step 5 stage 8: (dev only) → (autonomous in dev; higher envs apply after HITL) (REQ-278) - Step 8: rewrite with Shape A destroy-then-rebuild + Shape B cross-ref (REQ-279) - Per-env section: add Shape B lead sentence (REQ-280) - Reference table: @v1.19 wording + .yaml→.yml extension fix (REQ-281) - Tests: rename no-field-editing → both-promotion-shapes + new destroy-on-env-change test (REQ-290) ---ci--- project: acdl phase: 1 milestone: v1.24 status: execute requirements: [REQ-276,REQ-277,REQ-278,REQ-279,REQ-280,REQ-281,REQ-290] ---/ci---
This commit is contained in:
+47
-11
@@ -140,10 +140,10 @@ name: microservice
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `uses` | string | yes | Reference to the central deployment pipeline, **versioned** with a floating MAJOR+MINOR tag (e.g. `nova/pipelines/contract.yml@v1.19`). Bare or `@main` references are discouraged. See [Versioning](pipeline/versioning). |
|
||||
| `module` | string | yes | Module name from the registry — any primitive or module (e.g. `static-assets`, `microservice`, `s3`). See the [module catalog](modules/). |
|
||||
| `environment` | string | yes | The platform-managed environment to deploy to (e.g. `dev`). See [Environments](environments/). |
|
||||
| `inputs` | object | yes | Module-specific inputs (see the module's README). |
|
||||
| `id` | string | yes | Short operational acronym (3-6 chars, lowercase + digits + hyphens). Becomes `stack.name`: the Terraform state key (`spike/<id>/<env>/terraform.tfstate`), the outbox event identity, and the resource naming prefix. Stable across deploys and environment promotions. |
|
||||
| `name` | string | yes | Full human-readable stack name. Becomes `stack.title`: the display name in PR comments, evidence records, and dashboards. |
|
||||
| `environment` | string | yes | The platform-managed environment to deploy to (`dev`, `qa`, `prod`, or `dr`). See [Environments](environments/). |
|
||||
| `infrastructure` | object | yes | Map of modules to deploy, keyed by module name (matching a registry key in `modules/registry.json`). Each entry carries an optional `version` (defaults to latest published) and per-module `inputs`. One entry = single-module deploy; N entries = multi-module manifest. |
|
||||
|
||||
### Module inputs
|
||||
|
||||
@@ -180,6 +180,7 @@ jobs:
|
||||
uses: nova/.github/workflows/deploy.yml@v1.19
|
||||
with:
|
||||
contract: .nova/contract.yml
|
||||
environment: dev
|
||||
```
|
||||
|
||||
That is the entire consumer-side workflow. When you push to `main`:
|
||||
@@ -229,7 +230,7 @@ flowchart TD
|
||||
S5["policy checks<br/>(adapter -> PolicyCheckResult)"] --> S6
|
||||
S6["confidence<br/>score + band (dev >= 0.50)"] --> S7
|
||||
S7["evidence event<br/>to the audit outbox"] --> S8
|
||||
S8["infrastructure apply<br/>(dev only)"]
|
||||
S8["infrastructure apply<br/>(autonomous in dev;<br/>higher envs apply after HITL)"]
|
||||
```
|
||||
|
||||
1. **validate-contract** — validates your contract YAML against the contract
|
||||
@@ -250,9 +251,10 @@ flowchart TD
|
||||
threshold is ≥ 0.50. If the band is `pass`, the pipeline proceeds.
|
||||
7. **evidence event** — a hash-chained evidence event is written to the
|
||||
audit outbox.
|
||||
8. **infrastructure apply** (dev only) — the infrastructure plan is applied,
|
||||
creating the resources in your AWS account. An evidence event for the
|
||||
apply is recorded.
|
||||
8. **infrastructure apply** (autonomous in dev; higher environments apply
|
||||
after HITL attestation) — the infrastructure plan is applied, creating
|
||||
the resources in your AWS account. An evidence event for the apply is
|
||||
recorded.
|
||||
|
||||
## Step 6 — What gets created
|
||||
|
||||
@@ -289,7 +291,14 @@ push your container image to the ECR repo the platform created.
|
||||
|
||||
## Step 8 — Promote to qa / prod
|
||||
|
||||
Change `environment` in your contract (the infrastructure stays the same):
|
||||
There are **two supported promotion shapes**. Both are valid; pick the one
|
||||
that fits your repo's workflow.
|
||||
|
||||
### Shape A — edit the environment field (destroy-then-rebuild)
|
||||
|
||||
Change `environment` in your contract (the infrastructure stays the same).
|
||||
The contract `id` stays stable, so the platform knows this is the same
|
||||
stack moving to a new environment:
|
||||
|
||||
```yaml
|
||||
id: assets
|
||||
@@ -301,10 +310,32 @@ infrastructure:
|
||||
inputs: { ... }
|
||||
```
|
||||
|
||||
**What happens when you change `environment: dev` → `environment: qa`:**
|
||||
the platform detects that the environment changed on a known contract `id`.
|
||||
Before building the new environment, it **destroys the prior environment's
|
||||
resources** (Terraform state key `spike/{id}/dev/`) and records an evidence
|
||||
event for the destroy. Only then does it apply the new environment (state
|
||||
key `spike/{id}/qa/`). **There is no orphan path** — if the destroy fails,
|
||||
the pipeline fails closed (no apply runs, no resources are left behind).
|
||||
This is full lifecycle management: the platform never creates a state
|
||||
where prior-environment resources are abandoned.
|
||||
|
||||
Higher environments require human attestation (a platform-runner deployment
|
||||
approval) and higher confidence thresholds. See [Environments](environments/)
|
||||
for the full table.
|
||||
|
||||
> **Note:** the destroy-then-rebuild runs within the same AWS account (the
|
||||
> current platform scaffold uses one account). Cross-account promotion
|
||||
> (separate accounts per env) is a future milestone.
|
||||
|
||||
### Shape B — per-environment caller workflows (no editing)
|
||||
|
||||
Alternatively, keep one contract per environment (or one contract + the
|
||||
`environment` workflow input) and run the matching CI job to promote. This
|
||||
avoids the destroy step because each environment has its own state from the
|
||||
first deploy. See [Per-environment deployment](#per-environment-deployment)
|
||||
below for the full pattern.
|
||||
|
||||
## Step 9 — Compliance extensions
|
||||
|
||||
Each module lists compliance extension points for the future compliance
|
||||
@@ -326,8 +357,8 @@ per-module extension points. Common examples:
|
||||
| Contract schema | `schemas/contract.schema.json` | JSON Schema for consumer contracts. |
|
||||
| Stack schema | `schemas/stack.schema.json` | JSON Schema for the resolved stack instance. |
|
||||
| Module catalog | [modules/](modules/) | All primitives and modules. |
|
||||
| Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.19`). |
|
||||
| Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.19`). |
|
||||
| Sample contract | `contracts/static-assets.yml` | The reference example contract (used with caller workflow `@v1.19`). |
|
||||
| Sample contract | `contracts/microservice.yml` | The microservice example contract (used with caller workflow `@v1.19`). |
|
||||
| Module examples | `modules/<name>/examples/` | Validated per-module example contracts (`simple.yaml` + `complex.yaml`). |
|
||||
| Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. |
|
||||
| Angine adapter | `adapters/terraform/adapter.py` | Compiles stack instances to infrastructure. |
|
||||
@@ -395,6 +426,11 @@ separately (or left running to monitor the decommissioned stack's
|
||||
endpoints going dark).
|
||||
## Per-environment deployment
|
||||
|
||||
> **This is Shape B** (the alternative to [Shape A's edit-and-destroy
|
||||
> path](#step-8--promote-to-qa--prod) in Step 8). Shape B avoids the
|
||||
> destroy step because each environment has its own state from the first
|
||||
> deploy — no prior environment to tear down.
|
||||
|
||||
Nova supports a **promotion-without-editing** model: you do not edit the
|
||||
`environment:` field in a contract to promote dev → qa → prod → dr.
|
||||
Instead, there is **one CI job per environment**, each pointing at its
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
"""REQ-106: consumer guide documents per-env caller workflows."""
|
||||
"""REQ-106 + REQ-290: consumer guide documents both promotion shapes.
|
||||
|
||||
Shape A (Step 8): edit the environment field → platform destroys the prior
|
||||
env before building the new env (no orphan path).
|
||||
Shape B (Per-environment deployment): per-env caller workflows, no field
|
||||
editing, promotion = running the matching job.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
@@ -43,6 +49,28 @@ def test_consumer_guide_has_interpolation_reference():
|
||||
assert "${contract.module}" not in text
|
||||
|
||||
|
||||
def test_consumer_guide_states_no_field_editing():
|
||||
def test_consumer_guide_documents_both_promotion_shapes():
|
||||
"""REQ-290: the guide documents both Shape A (edit + destroy) and
|
||||
Shape B (per-env caller workflows). Replaces the old
|
||||
test_consumer_guide_states_no_field_editing which asserted only
|
||||
Shape B."""
|
||||
text = GUIDE.read_text()
|
||||
assert "no" in text.lower() and "environment" in text.lower() and "editing" in text.lower()
|
||||
# Shape B: per-env caller workflows, no field editing
|
||||
assert "Per-environment deployment" in text
|
||||
assert "promotion-without-editing" in text.lower() or "promotion = running the matching job" in text.lower()
|
||||
# Shape A: edit environment field (Step 8 documents this as a valid path)
|
||||
assert "Shape A" in text or "Shape B" in text
|
||||
assert "edit the environment field" in text.lower() or "change `environment`" in text.lower() or "change \"environment\"" in text.lower()
|
||||
|
||||
|
||||
def test_consumer_guide_documents_destroy_on_env_change():
|
||||
"""REQ-290: the guide states the platform destroys the prior env's
|
||||
resources when the environment field is changed, and that there is no
|
||||
orphan path."""
|
||||
text = GUIDE.read_text()
|
||||
text_lower = text.lower()
|
||||
# The guide must state the platform destroys the prior environment
|
||||
assert "destroy" in text_lower and ("prior environment" in text_lower or "prior env" in text_lower)
|
||||
# The guide must state there is no orphan path / fail closed
|
||||
assert "no orphan path" in text_lower or "orphan" in text_lower
|
||||
assert "fail closed" in text_lower or "fails closed" in text_lower
|
||||
Reference in New Issue
Block a user