# Nova — Requirements

## v1

### Category: Repos & Org
- **REQ-01:** All demo code lives under the `continuous-intelligence` Gitea org at `https://git.cloudinit.dev`.
- **REQ-09:** Three repos exist: `acdl` (platform + stubs + reusable workflows), `acdl-contracts` (developer surface), `acdl-evidence` (Pages audit timeline).

### Category: L1 Modules
- **REQ-02:** 8 L1 module folders exist under `acdl/modules/l1/`: `l1-eks-fargate`, `l1-iam-role`, `l1-lambda`, `l1-api-gateway`, `l1-eventbridge`, `l1-sqs`, `l1-s3`, `l1-cloudwatch`.
- **REQ-03:** Each L1 module has a `manifest.yaml` (declaring inputs) and a `mock_apply.sh` that echoes success, sleeps 1s, and exits 0.

### Category: L2 Modules
- **REQ-04:** 4 L2 modules exist under `acdl/modules/l2/`: `l2-invoice-service`, `l2-commodity-price-feed`, `l2-energy-analytics-api`, `l2-regulatory-reporting`, each composing the specified L1s.
- **REQ-05:** L2 modules compose L1 primitives into deployable shapes with a maximum depth of 5.

### Category: Core Scripts
- **REQ-06:** `mock_executor.sh` reads an L2 composition, invokes each L1 `mock_apply.sh`, and writes `state.json`.
- **REQ-07:** `policy_checker.py` reads `contract.yaml` and fails with `POLICY_VIOLATION:PUBLIC_INGRESS` on `public-ingress: true`; otherwise passes.
- **REQ-08:** `confidence_signal.py` returns a base score of 0.90 and drops to 0.40 (with reason code) when policy fails; gate threshold is ≥ 0.50.

### Category: Evidence Stream
- **REQ-11:** `evidence_writer.py` appends events to `audit.json` and links each event to the previous via a SHA-256 hash chain (`prev_hash` + own `hash`).
- **REQ-13:** `acdl-evidence` is Pages-enabled and serves `audit.json` plus `index.html`.

### Category: Pipeline
- **REQ-10:** The reusable pipeline runs Dev (autonomous), pauses at QA (manual approval), pauses at Prod (manual approval), then finalizes by committing `audit.json` to `acdl-evidence`.
- **REQ-12:** Opening an Issue in `acdl-contracts` runs `l3b_agent_stub.py`, commits a generated `contract.yaml` to a new branch, closes the Issue, and triggers the main pipeline.

### Category: Demo Acts
- **REQ-14:** `index.html` uses vanilla JS to fetch `audit.json` from the Pages URL and render events as a timeline.
- **REQ-15:** All four demo acts (Friction, Developer Self-Service, Citizen Developer, Safety Net) reproduce deterministically in a dry run.

## v2

(None — v1 covers the complete demo.)

## v1.1 (Prior milestone — architecture finalization + v1 spike, complete)

### Category: Architecture Finalization
- **REQ-16:** Architecture reaches v1.0 — all 11 open decisions in `docs/architecture.md` §13 are resolved and recorded in `PROJECT.md` (W1.A, W1.B, W2.A, W3.D, W3.E, BA.A–F, OpenTofu timing).
- **REQ-17:** Target Stack IR is defined as a JSON Schema under `schemas/ir.schema.json`; engine-agnostic (resources, relationships, composition max-depth-5, policy hooks).
- **REQ-18:** `PolicyCheckResult` normalized schema is defined under `schemas/policy_check_result.schema.json`; a Checkov adapter translates Checkov JSON to this schema.
- **REQ-19:** Six-input confidence signal is specified under `platform/confidence_signal.py` with per-env thresholds (dev 0.50 / qa 0.75 / prod 0.90 / dr 0.95) and severity→penalty mapping (critical=hard override, high=-0.2, medium=-0.05, low=-0.01, info=0.0).
- **REQ-20:** Tiered audit ledger design is authored: S3 Object Lock (compliance mode, 7-yr) + DynamoDB outbox (RPO=0, JWS detached signatures, `prev_event_hash` chain, daily checkpoints).
- **REQ-21:** Full 8-concern HITL matrix + separation-of-duties design is authored (CODEOWNERS routing + DynamoDB identity-distinctness check; pre-execution gate model; 1d warn / 2d freeze timeout).
- **REQ-22:** Contract schema (JSON Schema draft 2020-12) is defined under `schemas/contract.schema.json` with per-env mandatory/optional inputs (W3.E) and `profile: agentic` marker for L3B fields.

### Category: AWS OIDC Bootstrap
- **REQ-23:** AWS auth bootstrap + state backend for the spike: an S3 state bucket + DynamoDB lock/outbox table + an IAM user with a minimal scoped policy (S3 + DynamoDB + plan-only). The temporary long-lived key is used once (waiver D-034) then rotated via `scripts/rotate_spike_key.sh` after each spike run (D-039). **Real OIDC federation is deferred to v1.2** — Gitea Actions does not support `id-token: write` (RESEARCH TARGET 1, conf 0.95), blocked on go-gitea/gitea#36988.

### Category: v1 Spike — IR, L1, Adapter
- **REQ-24:** One real L1 module `l1-s3` exists under `modules-ir/l1/l1-s3/` with an IR-typed interface (typed inputs/outputs/NFRs) registered in the L1 registry.
- **REQ-25:** One real L2 thin-composition `l2-static-assets` exists under `modules-ir/l2/l2-static-assets/` referencing `l1-s3` only (depth 1, within max-depth-5).
- **REQ-26:** The Terraform adapter (`adapters/terraform/`) compiles the IR-typed L1 interface to Terraform `variable`/`output` blocks and the L2 thin-composition tree to a Terraform root module; it emits a real `terraform plan` against AWS via OIDC; state is stored in S3 + DynamoDB.

### Category: v1 Spike — End-to-End
- **REQ-27:** One end-to-end contract submission (`contracts/spike.yaml` for `l2-static-assets`) flows through: contract schema validation → contract→IR resolution → `terraform plan` (real AWS) → Checkov `PolicyCheckResult` → confidence signal → evidence event written to the DynamoDB outbox.
- **REQ-28:** Spike verification (`scripts/verify_phase10.sh`) proves the IR-shaped commitments hold: the adapter is the only engine-specific code; no polyglot mess; the L1 content, contract YML, and thin-composition tree are engine-agnostic.

## Out of Scope (v1.1)

| Feature | Reason |
|---------|--------|
| Full HITL matrix wiring (qa/prod/dr) | Spike is dev-only (`terraform plan`); HITL wiring is v1.2. |
| Kyverno + OPA policy engines | Spike uses Checkov only; Kyverno/OPA are v1.2. |
| MCP skill catalog + real L3B agent | L3B spike = a single stub contract submission; the 5-skill catalog is v1.2. |
| GitOps reconciler (ArgoCD/Flux) | v1.2. |
| Multi-region state / outbox | Single-region in v1 (§9, §12.3). |
| Prod/dr environments | v1.2. |
| Terraform `apply` (real provisioning) | Spike runs `plan` only; `apply` is gated by HITL in v1.2. |

## v1.2 (Prior milestone — platform hardening + first real consumer deployment, complete, tag `v1.3.0`)

### Category: Documentation & Simplification
- **REQ-29:** `README.md` is fully rewritten to reflect the v1.1-complete platform: the actual spike flow (contract → IR → `terraform plan` → Checkov → confidence signal → outbox), how to run it (`scripts/run_platform.sh`), the real repo layout (`acdl_platform/`, `schemas/`, `adapters/`, `terraform/`, `modules-ir/`, `contracts/`, `demo/`), and the v1.2 objective. No stale "v1.1 (active)" framing.
- **REQ-30:** NFR hardening of the v1.1 spike: (a) `terraform/bootstrap/spike_runner_policy.json` audited to least-privilege (S3 + DynamoDB + ECS + ECR + ELB + IAM plan-only, no wildcards beyond the documented exceptions); (b) `create_state_backend.py` and `create_iam_user.py` are idempotent (re-running exits 0 without duplicating resources); (c) `run_spike_plan.sh` + `run_spike_e2e.sh` consolidated into a single `scripts/run_platform.sh` with proper exit codes and error handling; (d) P1-1 carried forward from the v1.1 audit — the two AWS access key IDs in `.ciagent/VERIFY.md` Phase 09 narrative are redacted to placeholders; (e) any remaining stale `platform/` paths in `.ciagent/` are corrected to `acdl_platform/`.

### Category: L1 Catalog Expansion (ECS Fargate)
- **REQ-31:** Six new IR-typed L1 modules exist under `modules-ir/l1/` and are registered in `modules-ir/registry.json`: `l1-vpc` (VPC + subnets + route tables), `l1-ecs-cluster` (ECS Fargate cluster), `l1-ecs-service` (ECS service + task definition), `l1-iam-role` (task execution + task role), `l1-alb` (application load balancer + listener + target group), `l1-ecr` (ECR repository). Each has an `interface.json` valid against `schemas/ir.schema.json` and produces a valid `terraform plan` fragment via the Terraform adapter. The adapter `TYPE_MAP` is expanded to cover all six IR resource types.

### Category: L2 Composition & Contract Schema
- **REQ-32:** `l2-microservice` thin-composition exists under `modules-ir/l2/l2-microservice/` referencing the six ECS L1s (depth ≤ 5, within max-depth-5). `schemas/contract.schema.json` is extended with microservice inputs (`image: string`, `port: integer`, `env: map`, `healthcheck: object`) and validates a `contracts/microservice.yaml` submission. Contract→IR resolution (`acdl_platform/contract_resolver.py`) yields a complete target stack for `l2-microservice`.

### Category: Real Provisioning
- **REQ-33:** The platform runs `terraform apply` (not just `plan`) for the `dev` environment, autonomous per §10 (confidence ≥ 0.50, no HITL). The apply creates real AWS resources (VPC, ECS cluster, ECR repo, ALB, ECS service) and the result is captured in the evidence stream. `apply` for qa/prod/dr remains HITL-gated and out of scope for v1.2.

### Category: Consumer Repo
- **REQ-34:** A new Gitea repo `acdl-consumer-microservice` exists under the `continuous-intelligence` org, containing: a basic HTTP microservice (e.g., a tiny Python/Go server returning 200), a `Dockerfile`, an ECR push step, and a `contracts/microservice.yaml` submission for `l2-microservice` (dev environment).

### Category: End-to-End Verification
- **REQ-35:** One end-to-end flow: consumer commit to `acdl-consumer-microservice` → pipeline triggered → contract→IR resolution → `terraform plan` → `terraform apply` (dev) → a live ECS Fargate service serving HTTP 200 on its ALB → evidence event written to the DynamoDB outbox → the event renders on the `acdl-evidence` timeline. `scripts/verify_phase16.sh` proves the full flow green.

## v1.3 (Prior — module documentation + thin-composition removal, complete)

### Category: Thin-Composition Removal
- **REQ-36:** The L2 thin-composition layer is removed completely: `composition.json` files, `acdl_platform/contract_resolver.py`, `schemas/contract.schema.json`, `contracts/spike.yaml`, `contracts/microservice.yaml`, and L2 entries in `modules-ir/registry.json` are deleted. The L2 directories are kept as placeholders with READMEs. The downstream pipeline (adapter → checkov → confidence → outbox) is patched to load a pre-existing IR instance instead of resolving a contract.
- **REQ-37:** A `modules-ir/README-TEMPLATE.md` exists that works for both L1 and L2 modules, written in plain language (no jargon), with sections for Overview, Resources, Inputs, Outputs, Usage, Compliance extension points, and Versioning.
- **REQ-38:** Every module has a `README.md`: the 7 L1 modules have full READMEs with Resources/Inputs/Outputs/Usage/Compliance-extension-points/Versioning sections derived from their `interface.json`; the 2 L2 modules have placeholder READMEs noting the composition is under redesign. A `modules-ir/README.md` catalog index lists all modules with one-line descriptions and links.

### Category: Testing
- **REQ-39:** A pytest test suite exists under `tests/` covering the platform components offline (no AWS, no Checkov, no DynamoDB): the Terraform adapter (`adapters/terraform/adapter.py`), the confidence signal (`acdl_platform/confidence_signal.py`), the Checkov adapter (`adapters/terraform/policy/checkov_adapter.py`), and the outbox writer (`acdl_platform/outbox_writer.py`). The suite validates the IR schema, registry, spike_instance, and adapter output structure. `pyproject.toml` + `requirements-test.txt` pin test dependencies (pytest, jsonschema, pyyaml, boto3-stubs or moto for outbox mocking).

### Category: Shell Reproducibility
- **REQ-40:** `scripts/run_platform.sh` has a `--check-only` mode that runs offline: loads the pre-existing IR instance, runs the adapter to emit Terraform, validates the JSON structure — without AWS credentials, Checkov, or DynamoDB. The existing `--plan-only` and full modes continue to require AWS. The `--check-only` mode is what CI pipelines run.

### Category: CI/CD Pipelines
- **REQ-41:** Identical CI/CD pipelines exist for both Gitea Actions (`.gitea/workflows/ci.yml`, dev environment) and GitHub Actions (`.github/workflows/ci.yml`, production). Both run the same three stages: (1) lint — `py_compile` all Python files, (2) test — `pytest`, (3) check-only — `bash scripts/run_platform.sh --check-only`. Both trigger on push to main + pull request. Both use `ubuntu-latest`. Identical outcomes — the only difference is the runner environment.

- **REQ-42:** `pyproject.toml` exists at the repo root with pytest configuration (testpaths, markers) and the project metadata. `requirements-test.txt` pins test-only dependencies separate from runtime dependencies.

## v1.4 (Active — central pipeline contract + shell reproducibility + streaming)

### Category: Central Pipeline Contract
- **REQ-43:** A central pipeline contract exists as `schemas/pipeline.schema.json` (JSON Schema draft 2020-12) + `pipelines/ci.yaml` (YAML instance). The contract declares the pipeline name, triggers (push/PR branches), runner, Python version, and stages (name + command + required + install + description). Both `.gitea/workflows/ci.yml` (Gitea Actions, dev) and `.github/workflows/ci.yml` (GitHub Actions, production) implement the same stages, commands, triggers, and runner as declared in the contract. A test (`tests/test_pipeline_contract.py`) validates the contract against the schema and asserts both workflows conform (same jobs, same commands, same triggers, same runner, byte-identical).

### Category: Shell Reproducibility
- **REQ-44:** `scripts/run_ci.sh` reproduces the CI pipeline locally — runs the same 3 stages (lint, test, check-only) in sequence with proper exit codes, failing on first error. The script exits 0 with "CI PIPELINE OK" on success. A `--quiet` flag suppresses per-stage banners. The script mirrors the central pipeline contract (`pipelines/ci.yaml`) so the shell and CI environments produce identical outcomes.

### Category: Pipeline Streaming
- **REQ-45:** `scripts/run_platform.sh` streams output by default: terraform init/validate/plan output is piped to stdout via `tee` (visible to the user and logged), Checkov results are printed in human-readable form, and PolicyCheckResult records are displayed with severity, rule ID, and pass/fail status per record. The `--check-only` mode streams the emitted Terraform file content. A `--quiet` flag suppresses streaming (output to log files only) for backwards compatibility. Both gitea and github workflows are byte-identical (identical outcomes — the only difference is the forge runtime).

## v1.5 (Prior — consumer happy path + zero-trust docs + reusable deploy workflow, complete)

### Category: Consumer Happy Path Documentation
- **REQ-46:** `README.md` is rewritten so the consumer model is unambiguous: this repo is the platform source; a consumer never clones it. A consumer repo contains only app code + `contract.yaml` referencing the central pipeline + contract. The platform-flow diagram is a mermaid `flowchart TD` (replacing the ASCII art). "L3A"/"L3B" nomenclature is removed from README (single-surface model). "spike" nomenclature is removed from prose (code paths in bash blocks are kept verbatim).
- **REQ-47:** `docs/CONSUMER_GUIDE.md` (all-caps) replaces `docs/consumer-guide-static-assets.md`. It is generic across all L2 modules (`static-assets` as the worked example), uses mermaid diagrams (model + pipeline flow), documents versioned `uses:` references (floating MAJOR+MINOR tags — bare/`@main` discouraged), scopes prerequisites to consumer-repo bootstrap only (no Terraform/Checkov/boto3/runner-key — those are platform-repo concerns), and documents that the pipeline fetches the ACDL repo at run time via a reusable workflow (consumers never invoke `scripts/run_platform.sh` locally for the happy path).
- **REQ-48:** `README.md` Credentials section is rewritten to express the zero-trust target model: consumer repos use OIDC federation (no long-lived keys) with attribute-based authorization (ABAC) — IAM roles + session policies scoped by repository identity and resource-creation tags so a consumer can only view/update resources it created (blast-radius containment). A documented override allows a static key in GitHub Secrets (consumer repo) or `.env.secrets` (local testing), rotated by a platform-managed scheduled pipeline on a daily cadence; when `.env.secrets` is used locally, rotating out of band is the consumer's responsibility.

### Category: Reusable Deploy Workflow
- **REQ-49:** A reusable deploy workflow exists as byte-identical `.gitea/workflows/deploy.yml` (Gitea, dev) and `.github/workflows/deploy.yml` (GitHub, production), implementing the central deployment pipeline contract (`pipelines/deploy.yaml` validated against `schemas/deploy-pipeline.schema.json`). It is invoked by consumer repos via `uses: acdl/.gitea/workflows/deploy.yml@vMAJOR.MINOR` (versioned tag). The workflow checks out the consumer repo, checks out the ACDL platform repo into the runner workspace, installs runtime deps (Python, Terraform, Checkov), and invokes `scripts/run_platform.sh` against the consumer's contract path (passed as a workflow input). OIDC is the default auth (`permissions: id-token: write`); a static-key override reads from repository secrets.
- **REQ-50:** `contracts/static-assets.yaml` uses a versioned `uses:` reference (`@v1.4`, MAJOR+MINOR) — not bare `@v1` or `@main` — as the canonical example the consumer guide points at.
- **REQ-51:** `tests/test_pipeline_contract.py` is extended to validate the new deploy workflows: both files exist, are byte-identical, and conform to `schemas/deploy-pipeline.schema.json` (stages present, names match `pipelines/deploy.yaml` stage names). The existing CI-workflow conformance tests continue to pass unchanged.

## v1.6 (Active — consumer-facing docs restructure + terminology normalization + environments concept)

### Category: Internal-surface scrub
- **REQ-52:** No consumer-facing documentation (README.md, docs/**, modules/**/README.md, contracts/**) references `.ciagent/` — it is local CIAgent metadata, never visible to platform engineers or consumers. The README repository-layout table has no `.ciagent/` row. No `.gitea/` references appear in consumer-facing docs (consumers use GitHub only); the README repository-layout table has no `.gitea/workflows/` row.
- **REQ-53:** `acdl_platform/` is renamed to `core/` across the directory, all imports in tests/scripts/pipelines/workflows, and all doc references. (`platform/` was the original target but shadows Python's stdlib `platform` module — `core/` was chosen to stay importable.) `grep -R "acdl_platform" .` (excluding `.ciagent/`, `demo/`, `.git/`) returns 0 hits. The test suite passes after the rename.

### Category: Docs site restructure
- **REQ-54:** `docs/` is restructured into a Jekyll-style GitHub Pages site: `docs/_config.yml`, `docs/index.md` (landing), `docs/modules/` (catalog + per-module Pages-friendly copies), `docs/contracts/index.md`, `docs/pipeline/index.md` + `docs/pipeline/versioning.md`, `docs/environments/index.md`, `docs/consumer-guide.md`, `docs/architecture.md` (consolidated from architecture.md + architecture-v1.0.md, current-architecture only), `docs/vision.md`. No `.ciagent/` links anywhere in `docs/`. Consumer-facing content (modules, contracts, pipeline, versioning) lives in Pages.

### Category: Terminology normalization
- **REQ-55:** Consumer-facing docs drop the "L2" nomenclature — L2 modules are referred to as "modules". "L1" label is dropped in consumer-facing docs — L1 primitives are referred to as "primitives". The "composition" terminology is changed to "pattern" for modules in prose (the on-disk `composition.json` files and code references are unchanged this phase). A roadmap entry records that "composition" will later describe the thin orchestration where consumers dynamically create a module directly from the contract file (future implementation, not implemented now).
- **REQ-56:** The term "forge" is replaced in consumer-facing docs with "platform runners" / "platform-managed" as appropriate. The term "forge" remains only in internal architecture docs.

### Category: README rewrite
- **REQ-57:** README.md repository-roles section is restated to match reality: a consumer repo contains (a) its application code, (b) one or more contracts (`.acdl/contract.yaml`), and (c) one or more CI definitions (a thin `.github/workflows/deploy.yml` that `uses:` the central reusable workflow, pointing at the appropriate environment + contract). The platform repo (this one) owns modules/adapters/schemas/pipelines/scripts/workflows. A consumer never clones the platform repo.
- **REQ-58:** README.md Status section is replaced with a Features list (referenceable by consumers and platform engineers) and a Roadmap subsection listing only planned future features (no internal CIAgent status, no version-by-version changelog).
- **REQ-59:** README.md "How the platform works" mermaid diagram is revised so all node text is visible (no overflow): labels are split with `<br/>`, boxes widened as needed. A security-checks stage is added before the policy-checks stage. Specific tools (Checkov, Terraform) are not named — they are "security checks (adapter)", "policy checks (adapter)", "infrastructure plan". An "infrastructure apply" stage is added at the appropriate level (dev only, after confidence).
- **REQ-60:** README.md Credentials & zero-trust section removes the "go-gitea/gitea#36988 blocked" mention and the "waivers D-039/D-047" language (not consumer/platform-engineer facing). It states: default OIDC + ABAC; alternative is a static AWS key (GitHub Secrets for platform-runner runs, or `.env.secrets` locally) with the expectation of daily rotation (platform-managed for runner runs) or out-of-band rotation (consumer-managed for local `.env.secrets`).

### Category: Environments concept + onboarding
- **REQ-61:** The concept of platform-managed environments is introduced: consumers are not required to provide an AWS account, VPC, subnet, S3 state bucket, or runner key. `docs/environments/index.md` documents that a named environment is a platform-owned AWS account + network + state backend + IAM role surfaced to the consumer via ABAC, selected by name in the contract. The old README environments table (dev/qa/prod/dr) is removed completely. A minimal onboarding scaffold exists: `platform/environments/` with a sample `dev.json` + README, `platform/environment_check.py`, a wire-in at the top of `scripts/run_platform.sh`, a friendly first-run onboarding message when no environment is defined for the repo, and `tests/test_environment_check.py` covering the missing-env and present-env cases.

## v1.7 (Active — production platform + contract ingestion + pipeline maturation)

### Category: Rename + production-ready stack
- **REQ-62:** `static-assets` is renamed to `static-assets` everywhere (D-048 — including `.ciagent/` historical narrative: verbatim phase descriptions, REQ-25/27/50 text, D-036, RESEARCH.md). `grep -R "static-assets[^s]" .` (excluding `.git/`) returns 0 hits. The module dir `modules/l2/static-assets/` → `modules/l2/static-assets/`; `contracts/static-assets.yaml` → `contracts/static-assets.yaml`; the registry key is renamed; all scripts, tests, docs, and `.ciagent/` files use `static-assets`. The reconstruction test is updated to expect `static-assets` throughout.
- **REQ-63:** Two new primitives exist: `cloudfront` (distribution + OAC, stack types `aws:cloudfront:distribution` + `aws:cloudfront:originaccesscontrol`) and `waf` (WAFv2 web ACL, stack type `aws:wafv2:webacl`), each with an `interface.json` valid against `schemas/stack.schema.json` and a full README (Resources/Inputs/Outputs/Usage/Compliance/Versioning). Both are registered in `modules/registry.json`. The Terraform adapter `TYPE_MAP`/`INPUT_MAP`/`OUTPUT_MAP` covers the new stack types.
- **REQ-64:** The `static-assets` module is augmented to a production-ready stack referencing s3 + cloudfront + waf (depth 1, D-049). `composition.json` wires the s3 bucket regional domain name to the CloudFront origin, and the WAF web ACL ARN to the CloudFront distribution. `schemas/contract.schema.json` is extended for the new module inputs (`price_class`, `viewer_protocol_policy`, `waf_enabled`, `default_ttl`, `max_ttl`). The `uses:`/`ref:` tag advances from `@v1.4` to `@v1.6` (D-056/D-057); floating git tags `v1.6` + `v1` are created pointing at `v1.6.0`.

### Category: Tagging standards + security adapters
- **REQ-65:** A required-tag set is defined in `schemas/tagging-standard.json` (`acdl:owner`, `acdl:contract`, `acdl:environment`, `acdl:cost-center`). A Checkov custom YAML rule at `adapters/terraform/policy/custom_rules/acdl_tagging.yaml` fails (severity `medium`) when required tags are missing on taggable resources. `checkov_adapter.py` removes the `_emit_tag_naming_skipped()` placeholder (D-043 closure) and maps `ACDL_TAG_NAMING` as a real rule. `scripts/run_platform.sh` Step 5 passes `--external-checks-dir` to load the custom rule.
- **REQ-66:** A Wiz adapter stub exists at `adapters/wiz/wiz_adapter.py` translating Wiz API issues → `PolicyCheckResult` records (`engine: "wiz"`, D-052). It degrades gracefully when unconfigured (emits a single `SKIPPED` `WIZ_NOT_CONFIGURED` record). `tests/test_wiz_adapter.py` passes offline with a fixture response. The pipeline invokes it optionally (Step 5b) when `WIZ_API_TOKEN` is set.
- **REQ-67:** A Kyverno K8s-native adapter exists at `adapters/kyverno/kyverno_adapter.py` translating Kyverno `PolicyReport` results → `PolicyCheckResult` records (`engine: "kyverno"`, D-053). Sample policies exist at `adapters/kyverno/policies/` (disallow-privileged, require-labels, require-image-digests). `tests/test_kyverno_adapter.py` passes offline. The adapter is inactive for Terraform-only stacks (the platform emits Terraform, not K8s manifests); it is ready for the GitOps reconciler roadmap item. `schemas/policy_check_result.schema.json` engine enum includes `checkov | kyverno | opa | wiz`.

### Category: Platform Lambda + contract ingestion
- **REQ-68:** A platform Lambda (`core/lambda/contract_ingestor.py`) is invoked via a Function URL (IAM auth) and accepts `{ consumerRepo, contractId, contract, environment, action }`. It writes contracts to a DynamoDB table `acdl-contracts` (PK `consumerRepo`, SK `contractId#submittedAt`, SSE via a customer-managed CMK, point-in-time recovery) (D-051). `terraform/platform/main.tf` defines the table, Lambda, Function URL, KMS key, Secrets Manager secret (`acdl/github-token`), and Lambda execution role. `terraform/platform/consumer_invoke_policy.json` grants the consumer's deploy role `lambda:InvokeFunctionUrl` on the Lambda ARN, scoped via ABAC (cross-account). Onboarding grants the Lambda-invoke permission; `docs/environments/index.md` documents this. `tests/test_contract_ingestor.py` passes offline (moto-mocked DynamoDB).

### Category: Deploy outputs + error reporting + stage comments
- **REQ-69:** `scripts/run_platform.sh` has a `publish-outputs` step (after apply) that writes deploy outputs to SSM Parameter Store as `SecureString` (KMS-encrypted, namespaced `/acdl/{env}/{contractId}/{output_name}`) for runtime-injectable values, and a `comment-outputs` step that posts a structured GitHub PR comment / job summary with human-readable connection strings (D-050). `core/output_publisher.py` implements the SSM write + GitHub comment formatting. `tests/test_output_publisher.py` passes offline (moto + mocked GitHub API). `pipelines/deploy.yaml` + both deploy workflow YAMLs declare the new stages (byte-identical).
- **REQ-70:** The Lambda `report_error` action (`core/lambda/contract_ingestor.py`) creates a GitHub issue on the platform repo (`acdl/acdl`) via the GitHub API using a token from Secrets Manager (D-055). Idempotent (comments on an existing open issue rather than duplicating). `.github/workflows/deploy.yml` + `.gitea/workflows/deploy.yml` (byte-identical) have an `if: failure()` error-report step invoking the Lambda via `aws lambda invoke-function-url` (SigV4-signed). Gitea is excluded (only the CIAgent uses it; platform engineers and consumers use GitHub).
- **REQ-71:** `.github/workflows/deploy.yml` + `.gitea/workflows/deploy.yml` (byte-identical) post a PR comment after every successful pipeline stage (validate-contract, resolve-stack, plan, checkov, confidence, apply, publish-outputs) via `scripts/post_stage_comment.sh` (uses `GITHUB_TOKEN` + `gh api`; no-op when not in a PR context). The comment includes the stage name, status (pass), and key metrics (plan counts, confidence score, outputs published).

### Category: Platform pipelines + release automation
- **REQ-72:** Three platform pipelines exist: (1) `.github/workflows/platform-test.yml` (PR, stages: lint, unit-test, integration-test — runs `run_platform.sh --check-only` for every sample contract, schema-validation — validates all `schemas/*.json` + `modules/**/interface.json` + `modules/**/composition.json` + `modules/<name>/examples/*.yaml` against their schemas); (2) `.github/workflows/primitives-plan.yml` (PR, plan-only for all L1 primitives via matrix, `scripts/run_primitive_plan.sh`); (3) `.github/workflows/patterns-plan.yml` (PR, plan-only for all L2 modules via matrix, `scripts/run_pattern_plan.sh`).
- **REQ-73:** `.github/workflows/release.yml` runs on merge to `main`, computes the next semver (PATCH per phase, MINOR on milestone COMPLETE), creates the MAJOR.MINOR.PATCH tag, force-moves the MAJOR.MINOR + MAJOR floating tags, and creates a GitHub release with an auto-generated body (D-057). `tests/test_release_logic.py` passes (unit test the semver computation + tag-update logic with a mocked `git describe`).

### Category: Remove legacy consumer-repos + module examples + RDS primitive
- **REQ-74:** The legacy consumer-repos directory is deleted entirely (a v1.2 artifact removed in v1.7; references in `.ciagent/` historical narrative are rewritten per D-048). A recursive grep for the legacy directory name (excluding `.git/`) returns 0 hits.
- **REQ-75:** A new RDS primitive (`modules/l1/rds/`) with an `engine` input (enum: postgres, mysql, etc.) demonstrates multi-engine variation (D-059). Every module (primitives + patterns) has a `modules/<name>/examples/` directory with `simple.yaml` + `complex.yaml` (+ variation files) validated against `schemas/contract.schema.json` in the platform-test pipeline schema-validation stage (D-058). Each module's `README.md` `## Examples` section references + excerpts the validated files. `docs/modules/index.md` + `docs/consumer-guide.md` + `docs/contracts/index.md` are updated with the new module names + examples.

## v1.8 (Complete — P1 remediation + uptime + engineering standards + encryption/deletion-protection by default + decommission + docs)

### Category: P1 Fixes
- **REQ-76:** WAF adapter emits custom `rules` as nested HCL blocks (not attribute syntax) and honors `default_action` input (allow/block) — P1-4, P1-5 closed.
- **REQ-77:** L2 composition `outputs[]` array is resolved by `contract_resolver.py` into `stack.outputs`; the adapter emits corresponding `output` blocks — P1-7 closed.
- **REQ-78:** SSM publisher fails loud when `ACDL_KMS_KEY_ID` is unset (no silent AWS-managed-key fallback); `ACDL_ALLOW_DEFAULT_KMS=1` escape hatch for local testing — P1-3 closed.
- **REQ-79:** `consumer_invoke_policy` is rendered via Terraform with the caller's live account ID (no `000000000000` placeholder) — P1-6 closed.
- **REQ-80:** `run_platform.sh` emits adapter output to a per-run temp dir, not committed `terraform/spike/*.tf`; the committed files are removed — P1-8 closed.
- **REQ-81:** `contract_ingestor.py` reads `GITHUB_API_BASE` env for forge-agnostic API URLs (GitHub + Gitea) — P1-9 closed.
- **REQ-82:** Deploy workflow static-key override is wired to `configure-aws-credentials` inputs (`access-key`/`secret-key`), not inert env vars — S1 closed.

### Category: Encryption by Default
- **REQ-83:** A per-stack CMK primitive (`kms-key`) exists with 90-day rotation enabled at creation; one key per L2 deployment; no shared keys across stacks.
- **REQ-84:** All primitives have encryption by default (`encryption_enabled` NFR, default true) + optional `kms_key_arn` input. CMK is prioritized; managed KMS is the fallback when no CMK is provided.
- **REQ-85:** L2 modules wire a per-stack CMK child + connect its `kms_key_arn` output to each child's `kms_key_arn` input.

### Category: Deletion Protection by Default
- **REQ-86:** `deletion_protection` NFR (boolean, default true) on every L1 primitive; the adapter emits `prevent_destroy` lifecycle meta-arg when true.
- **REQ-87:** L2 modules expose a `features.deletion_protection` flag (default true); consumers can disable via contract `inputs.deletion_protection: false`.

### Category: Uptime Monitoring
- **REQ-88:** An uptime-kuma L1 primitive exists (ECS Fargate) with: `feature_flag_enabled` (boolean, default true), `monitored_endpoints` (array of HTTP/DNS/TCP checks), `static_checks` (pre-defined health checks), `alert_channels` (Teams webhook, email, SMS, GitHub issues).
- **REQ-89:** Uptime is deployed by default after any L2 module deploy (separate terraform state, separate terraform run); L2 module outputs (endpoints) are passed to the uptime deployment as `monitored_endpoints`. The uptime URL is published to the consumer via PR comment.
- **REQ-90:** The `feature_flag_enabled` input (set from consumer contract `inputs.uptime_enabled`, default true) disables the uptime deployment entirely (no resources emitted).
- **REQ-91:** A `deploy-uptime` pipeline stage is declared in `pipelines/deploy.yaml` + both deploy workflow YAMLs (byte-identical).

### Category: Decommission + CMDB
- **REQ-92:** A decommission mode on the deploy pipeline (`mode: decommission`) implements a 2-step pipeline: (1) plan/apply to disable deletion protection with an HITL SRE gate, (2) plan/apply with all counts set to 0 with a second HITL SRE gate. Uses the existing deploy pipeline with different behavior.
- **REQ-93:** A DynamoDB `acdl-change-requests` table serves as the CMDB. The decommission alias accepts a `changeRequestId` input validated via a `validate_change_request` Lambda action (CR status must be `approved`).
- **REQ-94:** The decommission flow is documented in `docs/CONSUMER_GUIDE.md` (how to request a CR, trigger decommission, HITL gates, what happens).

### Category: Engineering Standards
- **REQ-95:** `modules/STANDARDS.md` exists with comprehensive L1 + L2 authoring + code review standards (scanned from current modules): required files, interface schema, input/output/NFR conventions, encryption + deletion protection as mandatory NFRs, naming, adapter extension pattern, code review checklist.
- **REQ-96:** `modules/README.md` catalog index includes all primitives (rds + uptime + kms-key added); `modules/README-TEMPLATE.md` updated with `## NFRs` section.

### Category: Path Documentation
- **REQ-97:** `schemas/README.md` documents how to write a schema, wire it into the platform, test it in CI, where to write tests, dependencies, and the existing schema catalog.
- **REQ-98:** `pipelines/README.md` documents how to write a pipeline contract, wire it into workflows, test it, dependencies, and the existing pipeline catalog.
- **REQ-99:** `adapters/README.md` documents how to write an adapter, wire it into the platform, test it, dependencies, and the existing adapter catalog.

## Out of Scope (v1.2)

| REQ | Original criterion | Clarified criterion (effective) | Decision |
|-----|--------------------|----------------------------------|----------|
| REQ-09 | Three repos exist | Three repos exist (`acdl`, `acdl-contracts`, `acdl-evidence`) under `continuous-intelligence`; new repos use `default_branch: "main"`, `auto_init: true` | D-015 |
| REQ-10 | "Pages returns 200 with placeholder `index.html`" on `acdl-evidence` | Gitea has no Pages; substitute: an HTTP GET against the raw file URL `https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html` returns 200 with the placeholder HTML body | D-012, D-016 |
| REQ-10 | "`qa` and `prod` environments exist on `acdl-contracts`" | Gitea has no environments API and ignores `environment:` blocks; substitute: the reusable workflow defines `qa-gate` and `prod-gate` jobs gated by `workflow_dispatch` approval inputs (D-004 fallback); a `qa` and `prod` branch may be created on `acdl-contracts` as a visible stand-in for environments | D-013 |

## Out of Scope (v1.0 demo — retained for history)

| Feature | Reason |
|---------|--------|
| Real cloud provisioning (AWS/GCP/Azure) | Demo explicitly stubs all infrastructure; no cloud access available. |
| Real LLM inference / external AI APIs | Spec forbids external AI; L3B is a keyword parser. |
| Production-grade infrastructure | Demo target is a 30-minute executive show, not a production system. |
| Adversarial tamper-proofing of evidence | Hash chain is demonstrative; not cryptographically secure against a determined attacker. |
| Multi-tenant isolation | Out of demo scope. |

## v1.9 (complete — design doc refresh + contract interpolation + per-env CI jobs + stub implementation + P1-1 remediation, tag `v1.9.0`)

### Category: Design Doc Refresh
- **REQ-100:** `core/hitl_matrix_design.md` is up to date: the "dev-only spike" framing is replaced with the v1.9 wired-gates reality (qa/prod/dr `workflow_dispatch` approval gates + CODEOWNERS routing + outbox-based SoD); the 8-concern attestation matrix is marked implemented (offline-testable subset) with operator-supplied concerns noted; the spike-scope note is updated. No stale "v1.2 wires the gates" language remains.
- **REQ-101:** `core/audit_ledger_design.md` is up to date: the hash-chain + DynamoDB-outbox path is marked shipped + production (since v1.8); the S3 Object Lock + JWS + async worker + DLQ + daily checkpoints build-out is clearly labeled "Deferred to a future milestone" (D-083); the RPO/RTO table reflects the v1.9 state.

### Category: P1-1 Remediation
- **REQ-102:** The adapter (`adapters/terraform/adapter.py`) contains no resource-type-specific hardcoded defaults for ECS/ALB/VPC resources — `desired_count`, `launch_type`, `target_type`, `load_balancer_type`, `family`, and `Name` tag values are read from L1 `interface.json` inputs (with defaults declared in the interface). The adapter is a thin translator. An L1 with an overridden `desired_count: 3` emits `desired_count = 3`; the default emits `desired_count = 1` via the interface default, not an adapter hardcode (P1-1 closed).

### Category: Contract Interpolation
- **REQ-103:** The contract resolver (`core/contract_resolver.py`) expands `${env.<field>}` and `${contract.<field>}` tokens in contract string values (including dotted paths like `${env.state_backend.bucket}`) after schema validation and before IR resolution. The `env` context is the loaded `core/environments/<contract.environment>.json`; the `contract` context is the contract dict. Unresolved tokens raise `ValueError` (fail loud). Sample contracts use naming patterns that include region, account id, and environment (e.g. `acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}`).
- **REQ-104:** An environment JSON schema `schemas/environment.schema.json` (draft 2020-12) defines the environment file shape (`name`, `account_id`, `region`, `state_backend`, `network`, `runner_role_arn`, `autonomy`, `confidence_threshold`). `core/environments/dev.json` validates against it. `qa.json`, `prod.json`, `dr.json` placeholder bindings exist (autonomy `attested`, thresholds 0.75/0.90/0.95).

### Category: Per-Environment CI Jobs
- **REQ-105:** Per-environment contract files exist for each sample module (`contracts/static-assets.{dev,qa,prod,dr}.yaml` and `contracts/microservice.{dev,qa,prod,dr}.yaml`), each setting `environment:` to its own name and using interpolation for env-specific values. The existing `contracts/static-assets.yaml` + `contracts/microservice.yaml` remain as the dev default for backwards compatibility.
- **REQ-106:** The reusable deploy workflow (`.github/workflows/deploy.yml` + `.gitea/workflows/deploy.yml`, byte-identical) declares an `environment` `workflow_call` input (enum dev/qa/prod/dr, default empty). When non-empty, `scripts/run_platform.sh --environment <name>` overrides the contract's `environment` field at load time (before interpolation). A consumer repo's caller workflow has one job per environment, each pointing at its respective contract (or the same contract + the env input). Promotion = running the matching job; no `environment:` field editing. `docs/CONSUMER_GUIDE.md` documents the per-env caller workflow pattern.

### Category: Stub Implementation
- **REQ-107:** `core/separation_of_duties.py` `route_halt_artifact` is a real implementation: publishes to an SNS topic `acdl-sod-halt` (ARN from `ACDL_SOD_HALT_TOPIC_ARN`); when unset, falls back to a structured stderr emission + a `SEPARATION_OF_DUTIES_VIOLATION` event write to the DynamoDB outbox via `outbox_writer.write_event`. No silent print-only stub. The SNS topic is defined in `terraform/platform/main.tf`.
- **REQ-108:** HITL qa/prod/dr pre-execution attestation gates are wired via `core/hitl_gates.py` (`attest(contract_id, env, approver, evidence)`). The gate records the approver (`gitea.actor` / `github.actor`) to the outbox (`approver_qa` / `approver_prod` / `approver_dr` attributes per `audit_ledger_design.md`), runs the separation-of-duties check on prod, and returns `(ok, reason)`. `scripts/run_platform.sh` calls `hitl_gates.attest` before apply for qa/prod/dr (dev skips). The workflow's `workflow_dispatch` approval input is the trigger.
- **REQ-109:** The full 8-concern attestation matrix from `hitl_matrix_design.md` §10.4 is implemented in `core/attestation_matrix.py`. Offline-testable concerns (contract NFRs, schema validity, policy pass) run for real; operator-supplied concerns (k6 load test, DR drill, FinOps forecast) accept an uploaded signed evidence artifact validated for freshness + schema, failing loud if missing/expired for prod/dr. `hitl_gates.attest` invokes the matrix for the target env and blocks on any failing concern.
- **REQ-110:** The Wiz adapter (`adapters/wiz/wiz_adapter.py`) is a real API client: a `WizClient` queries the Wiz GraphQL API (`WIZ_API_TOKEN` + `WIZ_API_URL`) and translates issues → `PolicyCheckResult` records. It degrades gracefully (existing `WIZ_NOT_CONFIGURED` SKIPPED record) when env unset. Offline tests use a recorded GraphQL fixture.
- **REQ-111:** The Kyverno adapter (`adapters/kyverno/kyverno_adapter.py`) translator is fleshed out: full `PolicyReport` → `PolicyCheckResult` mapping with severity + skip handling. It remains inactive for Terraform-only stacks (guard preserved); a `--kube-version` stub is added for future GitOps. Sample policies already exist.

## v1.10 (active — pipeline regression fix + capability re-verification + verified-reality rewrite, tag `v1.10.0`)

### Category: Pipeline Regression Fix
- **REQ-112:** The CIAgent VERIFY stage supports a `regression` mode that re-runs capability checks (not just diff checks), triggered at minimum on milestone completion. The regression run executes the local-emulator tier (REQ-113) for every capability marked Verified in prior milestones; any capability that fails the regression run blocks milestone completion. Regression results are recorded in `---ci---` blocks as `regression: { capability: <id>, status: Verified|Decayed|Broken }`. Existing diff-scoped VERIFY behavior is preserved for non-regression invocations. A regression run against the current codebase surfaces at least one Decayed/Broken capability (proving the gate catches decay, not just passes). `tests/test_verify_regression_mode.py` passes.

### Category: Local Emulating Adapters
- **REQ-113:** Local emulating adapters exist so the platform is fully locally testable without cloud credentials: (a) a flat-file DynamoDB outbox adapter that writes evidence events to flat files in a temp folder with a valid hash chain, same write/read interface as the live DynamoDB outbox adapter; (b) a local ECS Fargate emulator that records the service definition and returns a synthetic HTTP 200 from a local shell process, same interface as the live ECS adapter; (c) a local S3 state backend (flat-file tfstate in a temp folder); (d) a local Lambda stub that invokes the handler in-process with no AWS Lambda call. The headline E2E (contract submission → service live → evidence event) runs end-to-end against the local tier with no cloud credentials. `tests/test_local_emulating_adapters.py` passes. `run_platform.sh --local` (or equivalent) runs the full pipeline locally.

### Category: Capability Re-Verification Sweep
- **REQ-114:** Every capability advertised in v1.1→v1.8 PROJECT/ROADMAP is enumerated in `.ciagent/CAPABILITY_INVENTORY.md` with a unique ID per capability (v1.0 demo excluded as archived/superseded). Each capability is re-verified: the headline E2E (contract → ECS Fargate → evidence event) runs both live-AWS and local-emulator tiers, both must pass; all other capabilities run the local tier via emulating adapters (REQ-113). Each capability is tagged Verified / Decayed / Broken in `CAPABILITY_INVENTORY.md`. Every Decayed/Broken capability is fixed in-sweep (D-090: no cap) until Verified, with per-capability commits `verify(P54): <id> — <status>` and `fix(P54): <id> — <summary>`. All v1.1→v1.8 advertised capabilities end Verified. The regression run (REQ-112) is clean against the re-verified state.

### Category: Verified-Reality Rewrite
- **REQ-115:** PROJECT.md, ROADMAP.md, and both leadership decks are rewritten to match `CAPABILITY_INVENTORY.md` exactly. PROJECT.md gains a "Capability Status (Re-Verified 2026-07-27)" section listing every v1.1→v1.8 capability with its Verified tag and the tier(s) tested, plus a decay disclosure: capabilities marked complete in v1.1–v1.8 ran at the time of tagging; as of 2026-07-27 they were not reproducible and were re-verified in v1.10. ROADMAP.md v1.9.x entries note deck-freeze and superseded-by-reverification status. Both leadership decks reflect the re-verified status; any claim that cannot be demonstrated live is removed. HTML is re-rendered; PPTX is uploaded to the v1.10.0 release. Decks are unfrozen only after this lands. `ci-doc-verifier` confirms no stale capability claims remain. v1.10.0 is tagged; the Gitea release is published.

## Out of Scope (v1.9)

| Feature | Reason |
|---------|--------|
| S3 Object Lock + JWS + async worker + DLQ + daily checkpoints (audit ledger build-out) | Requires non-offline-testable AWS infra (Object Lock bucket, KMS signing key, SQS DLQ, Lambda worker). Deferred to a future milestone (D-083). The hash-chain + DynamoDB-outbox path remains the v1.9 production audit record. |
| Live k6/Gatling load test execution, live DR drill, live FinOps forecast | Operator-supplied evidence artifacts (signed blobs) are accepted + validated; the platform does not run these inline. |
| Self-service environment provisioning | Adding an environment remains a platform-team action (per `core/environments/README.md`). v1.9 adds the env files + schema, not self-service provisioning. |

## Traceability

### v1.0 (prior — demo)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-01 | 1 | complete (v1.0.1) |
| REQ-02 | 2 | complete (v1.0.2) |
| REQ-03 | 2 | complete (v1.0.2) |
| REQ-04 | 3 | complete (v1.0.3) |
| REQ-05 | 3 | complete (v1.0.3) |
| REQ-06 | 3 | complete (v1.0.3) |
| REQ-07 | 3 | complete (v1.0.3) |
| REQ-08 | 3 | complete (v1.0.3) |
| REQ-09 | 1 | complete (v1.0.1) |
| REQ-10 | 4 | complete (v1.0.4) |
| REQ-11 | 3 | complete (v1.0.3) |
| REQ-12 | 4 | complete (v1.0.4) |
| REQ-13 | 5 | complete (v1.0.5) |
| REQ-14 | 5 | complete (v1.0.5) |
| REQ-15 | 5 | complete (v1.0.5) |

### v1.1 (prior — architecture finalization + v1 spike, complete)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-16 | 07 | complete (v1.1.2) |
| REQ-17 | 07 | complete (v1.1.2) |
| REQ-18 | 07 | complete (v1.1.2) |
| REQ-19 | 07 | complete (v1.1.2) |
| REQ-20 | 07 | complete (v1.1.2) |
| REQ-21 | 07 | complete (v1.1.2) |
| REQ-22 | 07 | complete (v1.1.2) |
| REQ-23 | 08 | complete (v1.1.3) |
| REQ-24 | 09 | complete (v1.1.4) |
| REQ-25 | 10 | complete (v1.1.5) |
| REQ-26 | 09 | complete (v1.1.4) |
| REQ-27 | 10 | complete (v1.1.5) |
| REQ-28 | 10 | complete (v1.1.5) |

### v1.2 (prior — platform hardening + first real consumer deployment, complete)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-29 | 11 | complete (v1.2.1) |
| REQ-30 | 12 | complete (v1.2.2) |
| REQ-31 | 13 | complete (v1.2.3) |
| REQ-32 | 14 | complete (v1.2.4) |
| REQ-33 | 15 | partial (v1.2.5, IAM-blocked) |
| REQ-34 | 15 | complete (v1.2.5) |
| REQ-35 | 16 | partial (v1.2.6, IAM-blocked) |

### v1.3 (prior — module documentation + thin-composition removal, complete)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-36 | 17 | complete (v1.3.1) |
| REQ-37 | 17 | complete (v1.3.1) |
| REQ-38 | 17 | complete (v1.3.1) |
| REQ-39 | 18 | complete (v1.3.2) |
| REQ-40 | 18 | complete (v1.3.2) |
| REQ-41 | 18 | complete (v1.3.2) |
| REQ-42 | 18 | complete (v1.3.2) |

### v1.4 (prior — central pipeline contract + shell reproducibility + streaming)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-43 | 19 | complete (v1.4.1) |
| REQ-44 | 19 | complete (v1.4.1) |
| REQ-45 | 19 | complete (v1.4.1) |

### v1.5 (prior — consumer happy path + zero-trust docs + reusable deploy workflow, complete)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-46 | 20 | complete (v1.5.0) |
| REQ-47 | 20 | complete (v1.5.0) |
| REQ-48 | 20 | complete (v1.5.0) |
| REQ-49 | 20 | complete (v1.5.0) |
| REQ-50 | 20 | complete (v1.5.0) |
| REQ-51 | 20 | complete (v1.5.0) |

### v1.6 (complete — consumer-facing docs restructure + terminology normalization + environments concept, tag `v1.6.0`)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-52 | 21 | complete (v1.6.0) |
| REQ-53 | 21 | complete (v1.6.0) |
| REQ-54 | 21 | complete (v1.6.0) |
| REQ-55 | 21 | complete (v1.6.0) |
| REQ-56 | 21 | complete (v1.6.0) |
| REQ-57 | 21 | complete (v1.6.0) |
| REQ-58 | 21 | complete (v1.6.0) |
| REQ-59 | 21 | complete (v1.6.0) |
| REQ-60 | 21 | complete (v1.6.0) |
| REQ-61 | 21 | complete (v1.6.0) |

### v1.7 (complete — production platform + contract ingestion + pipeline maturation, tag `v1.7.0`)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-62 | 22 | complete (v1.7.0) |
| REQ-63 | 22 | complete (v1.7.0) |
| REQ-64 | 22 | complete (v1.7.0) |
| REQ-65 | 23 | complete (v1.7.0) |
| REQ-66 | 23 | complete (v1.7.0) |
| REQ-67 | 23 | complete (v1.7.0) |
| REQ-68 | 24 | complete (v1.7.0) |
| REQ-69 | 25 | complete (v1.7.0) |
| REQ-70 | 25 | complete (v1.7.0) |
| REQ-71 | 25 | complete (v1.7.0) |
| REQ-72 | 26 | complete (v1.7.0) |
| REQ-73 | 26 | complete (v1.7.0) |
| REQ-74 | 27 | complete (v1.7.0) |
| REQ-75 | 27 | complete (v1.7.0) |

### v1.8 (complete — P1 remediation + uptime + standards + encryption/deletion-protection by default + decommission + docs, tag `v1.8.0`)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-76 | 28 | complete (v1.8.0) |
| REQ-77 | 28 | complete (v1.8.0) |
| REQ-78 | 29 | complete (v1.8.0) |
| REQ-79 | 29 | complete (v1.8.0) |
| REQ-80 | 30 | complete (v1.8.0) |
| REQ-81 | 30 | complete (v1.8.0) |
| REQ-82 | 30 | complete (v1.8.0) |
| REQ-83 | 31 | complete (v1.8.0) |
| REQ-84 | 31 | complete (v1.8.0) |
| REQ-85 | 31 | complete (v1.8.0) |
| REQ-86 | 32 | complete (v1.8.0) |
| REQ-87 | 32 | complete (v1.8.0) |
| REQ-88 | 33 | complete (v1.8.0) |
| REQ-89 | 33 | complete (v1.8.0) |
| REQ-90 | 33 | complete (v1.8.0) |
| REQ-91 | 33 | complete (v1.8.0) |
| REQ-92 | 34 | complete (v1.8.0) |
| REQ-93 | 34 | complete (v1.8.0) |
| REQ-94 | 34 | complete (v1.8.0) |
| REQ-95 | 35 | complete (v1.8.0) |
| REQ-96 | 35 | complete (v1.8.0) |
| REQ-97 | 36 | complete (v1.8.0) |
| REQ-98 | 36 | complete (v1.8.0) |
| REQ-99 | 36 | complete (v1.8.0) |
### v1.9 (complete — design doc refresh + contract interpolation + per-env CI jobs + stub implementation + P1-1 remediation, tag `v1.9.0`)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-100 | 39 | complete (v1.9.0) |
| REQ-101 | 39 | complete (v1.9.0) |
| REQ-102 | 39 | complete (v1.9.0) |
| REQ-103 | 40 | complete (v1.9.0) |
| REQ-104 | 40 | complete (v1.9.0) |
| REQ-105 | 41 | complete (v1.9.0) |
| REQ-106 | 41 | complete (v1.9.0) |
| REQ-107 | 42 | complete (v1.9.0) |
| REQ-108 | 42 | complete (v1.9.0) |
| REQ-109 | 42 | complete (v1.9.0) |
| REQ-110 | 42 | complete (v1.9.0) |
| REQ-111 | 42 | complete (v1.9.0) |
### v1.10 (active — pipeline regression fix + capability re-verification + verified-reality rewrite, tag `v1.10.0`)

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-112 | 52 | complete (v1.9.9) |
| REQ-113 | 53 | complete (v1.9.10) |
| REQ-114 | 54 | complete (v1.9.11) |
| REQ-115 | 55 | complete (v1.9.12) |

## v1.11 (active — RESTART: stateless adapter + pipeline-driven module lifecycle testing, tag `v1.11.0`)

The v1.11 milestone closes G-005 (CAP-017..022 deploy-unverified) and G-008
(no cost docs) via a corrected architecture. The first v1.11 attempt is
abandoned (branches `phase/56-iam-re-bootstrap` + `phase/57-live-deploy-microservice`);
the restart branches off `v1.10.2`.

### Category: Stateless Adapter
- **REQ-123** — The terraform adapter (`adapters/terraform/adapter.py`) is rewritten from a 918-line monolith (3 constant tables `TYPE_MAP`/`INPUT_MAP`/`OUTPUT_MAP` + 39 type-specific branches) to a ~80-line stateless assembler. Each L1 module ships a real `terraform/` module dir owning its resource shape, nested blocks, and defaults. The adapter reads the registry and emits `module "x" { source = ... }` blocks. No type-specific logic in the adapter. (Phase P56a)

### Category: Per-Module Terraform
- **REQ-124** — All 12 L1 modules have a `terraform/` subdir (`versions.tf`/`variables.tf`/`locals.tf`/`main.tf`/`outputs.tf`) with defaults centralized in `locals.tf` (heavy interpolation of vars against sensible defaults). `interface.json` stays engine-agnostic. The registry has a `terraform_dir` field per entry. (Phase P56b)

### Category: Shell Lifecycle Modes
- **REQ-125** — `scripts/run_platform.sh` gains `--apply` and `--destroy` modes; the shell owns all terraform lifecycle. Python never runs terraform. `scripts/verify_deploy_microservice.py` is deleted. (Phase P57)

### Category: Single Platform VPC + Deterministic State
- **REQ-126** — `terraform/platform/main.tf` owns ONE VPC; the microservice composition references it via `data` source (no inline VPC). State keys are deterministic and env-aware (`spike/{id}/{env}/terraform.tfstate`), stable across apply/modify/destroy. (Phase P58)

### Category: L1 Lifecycle Pipeline
- **REQ-127** — A `modules-lifecycle` pipeline (Gitea + GitHub, byte-identical) matrix-runs each L1 module's `examples/{simple,complex}.yml` contracts through apply→modify→destroy against live AWS. No per-module Python. The "test" = the pipeline cell going green. (Phases P59–P60)

### Category: L2 Lifecycle Pipeline
- **REQ-128** — The lifecycle pipeline extends to L2 modules (static-assets, microservice). L2 = composition only (no L2 terraform files); the composition is deterministic (same contract → same stack → same state key). (Phases P61–P62)

### Category: Operating Model + G-005/G-008 Closure
- **REQ-116** — CAP-017..022 marked Verified in CAPABILITY_INVENTORY + PROJECT + decks with "Verified live-aws via lifecycle pipeline; torn down to zero-cost" note. (Phase P65)
- **REQ-118** — Both leadership decks rewritten to reflect verified-then-torn-down status; no stale "deploy-unverified" claims. (Phase P65)
- **REQ-119** — `.ciagent/COST.md` documents the v1.0→v1.10 AWS spend window (Cost Explorer query). (Phase P63)
- **REQ-120** — `.ciagent/PRE_MORTEM.md` documents the v1.10 decay root cause + forward pre-mortem. (Phase P64)
- **REQ-121** — CAP-017..022 added to the regression registry (evidence = lifecycle pipeline green). (Phase P63)
- **REQ-122** — All deployed stacks torn down via `--decommission` (D-070 two-step, CR CHG0680001); zero live ACDL resources remain. (Phase P64)

### v1.11 Traceability

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-123 | P56a | complete |
| REQ-124 | P56b | complete |
| REQ-125 | P57 | complete |
| REQ-126 | P58 | complete |
| REQ-127 | P59, P60 | complete |
| REQ-128 | P61, P62 | complete |
| REQ-116 | P65 | complete |
| REQ-118 | P65 | complete |
| REQ-119 | P63 | complete |
| REQ-120 | P64 | complete |
| REQ-121 | P63 | complete |
| REQ-122 | P64 | complete |

### Out of Scope (v1.11)
- OIDC act_runner adoption (pending go-gitea/gitea#36988).
- Per-phase regression (G-007: milestone-level regression gate is correct).
- Audit ledger build-out (D-083).
- Operator-supplied evidence.
- Pilot onboarding (G-001).
- Boto3 post-deploy verification probes (CAP-017..022 live-verify via boto3) — deferred to a future QA milestone. The lifecycle pipeline apply→destroy IS the verification for v1.11.

---

## Milestone v1.12 — Presentation Refinement (REQ-129..REQ-133)

**Objective:** Refine the leadership presentation decks to reflect the
verified reality after v1.11 — the stateless adapter, pipeline-driven
lifecycle testing, the cost operating model, the pre-mortem, and the
teardown to zero-cost. The v1.11 P65 deck-rewrite task did not fully land
on the deck artifacts: the rendered HTML still claims 6 cloud
capabilities are "deploy-unverified (IAM drift)", the road-to-north-star
diagram still shows v1.10 as "NEXT", and the v1.11 architecture stories
are absent. The v1.10 decay lesson (PRE_MORTEM.md FM-3) requires decks
to match verified reality exactly, not outrun it. The v1.12 regression
gate run (Phase 66) surfaced 3 Broken capabilities — one real adapter
defect (CAP-013) and two regression-probe bugs (CAP-017, CAP-018) — that
must be fixed before the decks can honestly claim 22/22 Verified.

**Surface:** leadership decks only (`docs/presentations/`) — both decks
across all four layers (source markdown, Marp deck, rendered HTML,
talking points) + diagrams + README. Plus the one real adapter fix and
two probe fixes required to make the deck claims true.

### Requirements

- **REQ-129** — The adapter's module-call dedup logic
  (`adapters/terraform/adapter.py`) is fixed so multi-resource L1s with
  stack outputs (e.g. `ecs-service`, `alb`) produce valid Terraform:
  `terraform validate` succeeds for the microservice stack (CAP-013
  Verified live-aws). The regression gate re-runs and confirms 22/22
  Verified. (Phase 67)
- **REQ-130** — The two regression-probe bugs are fixed: CAP-017's
  probe no longer requires `locals.tf` for modules that legitimately
  omit it (`core/regression_verify.py`); CAP-018's probe instantiates
  `LocalLambdaStub` with the required `outbox` arg. The regression gate
  re-runs clean (19 Verified + 3 fixed → 22/22 Verified). (Phase 67)
- **REQ-131** — Both leadership decks' capability claims match
  `CAPABILITY_INVENTORY.md` exactly: 22/22 Verified, no
  "deploy-unverified" / "IAM drift" / "design-verified" framing. The
  decks reflect "Verified live-aws via lifecycle pipeline; torn down to
  zero-cost." A grep-based doc verification (successor to the planned
  `ci-doc-verifier`) confirms zero stale claims across
  `docs/presentations/`. (Phase 68, Phase 70)
- **REQ-132** — Both decks reflect v1.11's architecture as
  leadership-relevant stories: (a) the stateless adapter
  (918→~80 lines, defaults centralized in per-module `terraform/`
  dirs, the adapter is an assembler); (b) pipeline-driven lifecycle
  testing (a `modules-lifecycle` pipeline matrix-runs each module
  apply→modify→destroy against live AWS — the green cell IS the
  verification). The `road-to-north-star` diagram + both decks' roadmap
  appendix slides reflect v1.11 complete (v1.10 no longer "NEXT").
  Version refs in deck examples bump from `@v1.10` → `@v1.11` (and
  `@v1.12` at Phase 70 complete after the tag exists). (Phase 68)
- **REQ-133** — Both decks' "Operating Model & Cost" appendix slide
  carries the real `COST.md` figures ($0.001883 / 8 days, ~$0.007/mo,
  S3-dominated, zero BAU compute) + the zero-cost-steady-state /
  D-096 teardown claim, and references the pre-mortem
  (`PRE_MORTEM.md`: v1.10 decay root cause + four forward failure modes
  + structural mitigations). Both rendered HTML decks re-rendered and
  committed; both talking-points files re-distilled to match the updated
  Marp structure (including the A6 Operating Model & Cost section that
  was missing from the talking points). PPTX exported to the v1.12.0
  release. (Phase 69, Phase 70)
- **REQ-134** — The `modules-lifecycle` pipeline defaults to **plan-only**
  (fast, no AWS mutation) so it runs on every PR without cost or AWS
  credentials. A CI variable `ACDL_LIFECYCLE_MODE` (workflow input
  `lifecycle_mode`, default `plan`) overrides to `full` for the real
  apply→modify→destroy against live AWS. The four lifecycle scripts
  (`run_lifecycle_test.sh`, `run_lifecycle_destroy.sh`,
  `run_l2_lifecycle_test.sh`, `run_l2_lifecycle_destroy.sh`) read the
  flag and dispatch to `--plan-only` (plan mode) or `--apply`/`--destroy`
  (full mode). Both forge workflows (`.github` + `.gitea`, byte-identical)
  expose `lifecycle_mode` as a `workflow_dispatch` input and pass it via
  `env:` to every lifecycle step; the CI VPC apply/destroy jobs are
  skipped in plan mode. `pipelines/modules-lifecycle.yml` + the schema
  document the `default_mode: plan` field. Tests assert the plan-only
  default, the override path, the byte-identity of both workflows, and
  the CI VPC skip in plan mode. (Phase 67b)

### v1.12 Traceability

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-129 | P67 | complete |
| REQ-130 | P67 | complete |
| REQ-134 | P67b | complete |
| REQ-131 | P68, P70 | complete |
| REQ-132 | P68 | complete |
| REQ-133 | P69, P70 | complete |

### Out of Scope (v1.12)
- docs/ site, README.md, consumer-guide, module READMEs (decks only).
- Structural deck rework (re-ordering, adding/removing main slides) —
  v1.12 keeps the 10 main + 6 appendix structure to avoid the
  backwards-sequencing failure mode (PRE_MORTEM.md FM-3).
- New capability claims beyond what v1.11 verified.
- Per-phase regression hardening (G-007, unchanged).

---

## Milestone v1.14 — NFR Refinement (REQ-135..REQ-154)

**Objective:** Bug fixes, security posture improvements, stub/missing-
functionality identification + implementation, and documentation + NFR
refinement across the entire codebase. **No new features.** NFR milestone
— the final phase's patch IS the deliverable.

The v1.11 multi-persona review left 5 P1 + 4 P2 findings open; the
codebase has 6+ swallowed-error sites, 15+ hardcoded account-ID
references, 7 untested scripts, an offline-proxy regression gate,
ARCHITECTURE.md with no v1.11–v1.13 addendum, and consumer-facing docs
referencing stale `@v1.6`–`@v1.9` workflow tags. v1.14 clears all of it
in a 20-phase sweep.

### Requirements

- **REQ-135** — The adapter dedup loop raises `ValueError` for
  unregistered-module resources instead of silently dropping them (P1-1).
  (Phase P1)
- **REQ-136** — The static-assets L2 composition wires `default_ttl`/
  `max_ttl`/`price_class`/`viewer_protocol_policy` and makes WAF
  conditional via `waf_enabled`, so `complex.yml` is a real modify (P1-2).
  (Phase P2)
- **REQ-137** — The L2 lifecycle scripts' usage strings no longer
  advertise the vestigial `[ci-vpc-outputs.json]` arg, or document the
  remote-state design (P1-3). (Phase P3)
- **REQ-138** — The regression gate's CAP-017..022 checks run
  `terraform validate` (not just file-existence + resolver); the
  offline-proxy caveat is documented honestly (P1-5). (Phase P4)
- **REQ-139** — Unit tests for adapter dedup merge behavior +
  `ACDL_REMOTE_STATE_KEY` override exist and pass (P2-2). (Phase P5)
- **REQ-140** — The ALB target group `name_prefix` derives from `var.name`
  (P2-1). (Phase P6)
- **REQ-141** — 6 over-broad `except ...: pass` sites narrowed to specific
  exceptions; errors logged with context. (Phase P7)
- **REQ-142** — The hardcoded account ID `581513795199` is externalized to
  `ACDL_AWS_ACCOUNT_ID` env / `data.aws_caller_identity` across 15+ sites.
  (Phase P8)
- **REQ-143** — 6 `Resource: "*"` IAM statements scoped to `acdl-*` ARNs;
  regression test asserts the scoping. (Phase P9)
- **REQ-144** — The contract ingestor validates `contractId`/`environment`/
  `error`; ABAC reliance documented; spoofing-resistance test passes.
  (Phase P10)
- **REQ-145** — `contract.schema.json` + `environment.schema.json` reject
  undocumented fields (`additionalProperties: false`); format validation
  for bucket/ARN/CIDR. (Phase P11)
- **REQ-146** — `.gitignore` has a credential-pattern catch-all;
  `test_no_secrets_tracked.py` passes. (Phase P12)
- **REQ-147** — The Kyverno `--kube-version` flag is either implemented or
  removed with a documented deferral rationale. (Phase P13)
- **REQ-148** — Orphan bytecode + dead config cleaned (orphan `.pyc`,
  stale coverage source, stale version, dead JS allowlist). (Phase P14)
- **REQ-149** — 7 untested scripts have unit test coverage (≥1 test each).
  (Phase P15)
- **REQ-150** — Gitea workflow parity resolved; `rotate_spike_key.sh` +
  `sync_to_gl.sh` have `set -euo pipefail`. (Phase P16)
- **REQ-151** — `config.json` persona block + branching strategy +
  ollama-cloud backend aligned with PERSONAS.md + actual runtime.
  (Phase P17)
- **REQ-152** — `modules/STANDARDS.md` internally consistent; no stale
  `TYPE_MAP` reference. (Phase P18)
- **REQ-153** — ARCHITECTURE.md has v1.11–v1.14 addenda; stale `@v1.6–1.9`
  → `@v1.13`; GRILL G-005/G-008 resolved; COST.md window covers v1.11–v1.14;
  D-083 deferral recorded. (Phase P19)
- **REQ-154** — Platform VPC CIDR is a variable; subnet count is
  data-driven; `0.0.0.0/0` ingress documented. (Phase P20)

### v1.14 Traceability

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-135 | P1 | complete |
| REQ-136 | P2 | complete |
| REQ-137 | P3 | complete |
| REQ-138 | P4 | complete |
| REQ-139 | P5 | complete |
| REQ-140 | P6 | complete |
| REQ-141 | P7 | complete |
| REQ-142 | P8 | complete |
| REQ-143 | P9 | complete |
| REQ-144 | P10 | complete |
| REQ-145 | P11 | complete |
| REQ-146 | P12 | complete |
| REQ-147 | P13 | complete |
| REQ-148 | P14 | complete |
| REQ-149 | P15 | complete |
| REQ-150 | P16 | complete |
| REQ-151 | P17 | complete |
| REQ-152 | P18 | complete |
| REQ-153 | P19 | complete |
| REQ-154 | P20 | complete |

### Out of Scope (v1.14)
- New features (feat phases). v1.14 is NFR-only.
- D-083 audit ledger build-out (S3 Object Lock + JWS + SQS DLQ + async
  worker) — remains deferred; documented explicitly in ARCHITECTURE.md.
- Real OIDC federation (blocked on go-gitea/gitea#36988).
- Per-phase regression hardening (G-007, unchanged).
- Boto3 post-deploy verification probes (deferred to a future QA
  milestone).

---

## v1.15 — Nova (Rebrand)

**Milestone type:** Major (breaking — consumer-facing path, env var
prefixes, SSM path, AWS tag keys, and AWS resource names all change).
Per the branch-strategy precedent (breaking/feature milestones tag on
their OWN minor line), v1.15 tags run on the **v1.15.x minor line**:
`v1.15.0` (P0) → `v1.15.1..v1.15.4` (P1–P4) → `v1.15.4` (P5 final =
milestone release). (G-104 binding: the v1.14.x patch line is the NFR
convention; a Major milestone ships on its own minor.)

A full rebrand from **ACDL** / "Agentic Cloud Delivery Platform" →
**Nova** / "The New Dawn of DevSecOps — security as a seamless enabler
of fast deployments." The new tagline is added alongside the existing
"North Star" / "consumers declare intent" framing; the S&P Global Energy
visual theme (`sp-theme.json`) is a client brand and is **not** touched.
The rebrand applies across docs, decks, code, configs, CI, env vars,
consumer conventions, SSM paths, AWS tag keys, and AWS resource names —
with a staged infrastructure migration to avoid breakage.

Ideation source: `--ideate` flag (user-directed scope; the survey found
1,465 occurrences of `ACDL`/`acdl` across 205 files and zero existing
`nova` references — no collision risk). Accepted ideas become
IDEATE-01..IDEATE-10, mapped to REQ-155..REQ-164.

### Requirements

- **REQ-155** — (IDEATE-01) All prose, titles, headers, and comments
  across `README.md`, `docs/**`, `.ciagent/*.md`, deck markdown sources,
  `pyproject.toml` name/description, and `release.yml` release-title
  prefix are rebranded `ACDL`/`Agentic Cloud Delivery Platform` → `Nova`.
  Illustrative URLs in docs (`github.com/acdl/...`,
  `git.cloudinit.dev/continuous-intelligence/acdl*`) are updated to
  `nova` for prose consistency. Gitea release titles going forward read
  `Nova vX.Y.Z` (past releases keep their names). (Phase P1)
- **REQ-156** — (IDEATE-02) All Marp deck markdown sources
  (`docs/presentations/*-marp.md`, `*.md`, `*-talking-points.md`) and
  mermaid source `.mmd` files are rebranded `ACDL` → `Nova`; the deck
  title-slide subtitle becomes `Nova — The New Dawn of DevSecOps`. The
  `.mmd` sources are edited and the rendered PNG diagrams are
  re-exported so the committed PNGs match the new labels. The S&P visual
  theme (`sp-theme.json`) is unchanged. HTML decks are re-rendered.
  (Phase P1)
- **REQ-157** — (IDEATE-03) The Nova tagline ("The New Dawn of DevSecOps
  — security as a seamless enabler of fast deployments") is added to the
  README header, both deck title slides, and `docs/vision.md` —
  alongside (not replacing) the existing "North Star" / "consumers
  declare intent" framing. (Phase P1)
- **REQ-158** — (IDEATE-04) `adapters/terraform/policy/custom_rules/acdl_tagging.py`
  is renamed `nova_tagging.py` with its Checkov custom-rule registration
  updated (`schemas/tagging-standard.json` line 5 + adapter config). The
  Checkov rule enforces `nova:*` tag keys. (Phase P2)
- **REQ-159** — (IDEATE-05) All 21 `ACDL_*` env var prefixes are renamed
  to `NOVA_*` across `scripts/`, `core/`, `adapters/`, `tests/`,
  workflows (`.gitea/`, `.github/`), `.env`, `.env.secrets` (key names
  only — values/secret material stay), and consumer docs. A **dual-read
  fallback** (`NOVA_X` preferred, fall back to `ACDL_X`) is implemented
  in the config/env loader so deployments do not break during the
  transition window; the fallback is removed in the final phase once all
  consumers are migrated. Gitea repo secrets are rotated via API.
  (Phase P2)
- **REQ-160** — (IDEATE-06) The consumer on-disk contract path
  `.acdl/contract.yml` (and `.acdl/static-assets.*.yml`,
  `.acdl/contract.yaml`) becomes `.nova/contract.yml` across the
  contract resolver, deploy workflow checkout path, consumer docs, and
  the contract schema description. A consumer migration guide is shipped
  with P1 docs. (Phase P2)
- **REQ-161** — (IDEATE-07) The SSM parameter path prefix
  `/acdl/{env}/{contractId}/{output}` becomes
  `/nova/{env}/{contractId}/{output}` across `core/output_publisher`,
  the contract resolver, and consumer docs. A migration script copies
  existing `/acdl/...` parameters → `/nova/...`, readers are updated,
  then old parameters are deleted. (Phase P3)
- **REQ-162** — (IDEATE-08) AWS tag keys `acdl:owner`,
  `acdl:environment`, `acdl:contract`, `acdl:cost-center`, `acdl:ref`
  become `nova:owner`, `nova:environment`, `nova:contract`,
  `nova:cost-center`, `nova:ref` across terraform tagging, the Checkov
  custom rule (`nova_tagging.py`), and ABAC session policies. A
  **parallel-tag period** adds `nova:*` tags to all resources first,
  updates the ABAC session policies to match `nova:*`, then removes the
  `acdl:*` tags once consumers are verified. (Phase P3)
- **REQ-163** — (IDEATE-09) All `acdl-*` AWS resource names are renamed
  to `nova-*` via terraform: KMS alias `alias/acdl-platform` →
  `alias/nova-platform`, SNS `acdl-sod-halt` → `nova-sod-halt`, SG
  `acdl-ecs-sg` → `nova-ecs-sg`, Lambda `acdl-contract-ingestor` →
  `nova-contract-ingestor`, DynamoDB `acdl-contracts`/`acdl-change-requests`
  → `nova-contracts`/`nova-change-requests` (scan+copy data migration,
  verify row counts, keep old tables until verified), ECR
  `acdl-microservice` → `nova-microservice` (re-push images), IAM
  user/policy `acdl-spike-runner` → `nova-spike-runner` (re-bootstrap
  with new key), state bucket `acdl-tfstate-...` → `nova-tfstate-...`
  (`terraform init -migrate-state` to new backend, state JSON backed up
  first), ALB name prefix `acdl-alb` → `nova-alb` (recreate, brief
  downtime). A maintenance window + rollback runbook is published with
  the migration. (Phase P4)
- **REQ-164** — (IDEATE-10) The dual-read env var fallback
  (`ACDL_*`→`NOVA_*`) and any `ACDL_*`-only references are removed once
  all consumers are migrated; the consumer migration guide is finalized;
  `nova_tagging.py` no longer accepts `acdl:*` tag keys. (Phase P5)

### v1.15 Traceability

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-155 | P1 | complete |
| REQ-156 | P1 | complete |
| REQ-157 | P1 | complete |
| REQ-158 | P2 | complete |
| REQ-159 | P2 | complete |
| REQ-160 | P2 | complete |
| REQ-161 | P3 | complete |
| REQ-162 | P3 | complete |
| REQ-163 | P4 | complete |
| REQ-164 | P5 | complete |

### Out of Scope (v1.15)
- Renaming the real Gitea org/repo (`continuous-intelligence/acdl`) or
  GitHub org `acdl` — config.json `release.gitea.repo` stays `acdl`;
  URLs in docs are illustrative and updated to `nova` for prose only.
- Renaming the S&P Global Energy visual theme (`sp-theme.json`,
  deck CSS) — that is client branding, not the Nova product brand.
- Past Gitea release titles — existing releases keep their `ACDL vX.Y.Z`
  names; only future releases use `Nova vX.Y.Z`.
- Git branch/tag naming — branches use `milestone/v*` / `phase/*` and
  tags use `v*` semver; no brand name present, no change needed.

---

## v1.16 — Nova Simplification (NFR)

**Milestone type:** NFR (all phases fix/chore/docs/refactor/test). The
final phase's patch IS the deliverable — no separate milestone tag. Tags
run on the v1.15.x line: `v1.15.5` (P0) → `v1.15.6..v1.15.25` (P1–P20) →
`v1.15.26` (P21 final = milestone release).

**Objective:** A 20-phase NFR sweep (no new features) themed around five
user-directed axes: Simplify without regressions, Security,
Maintainability, User/Developer Experience, and No Humans Onboarding
Flow. The v1.15 rebrand left a fresh debt layer (stale brand strings, a
state-bucket drift, a Kyverno policy contradicting the Nova tagging
standard, dead code) that this milestone clears, alongside genuine
simplification and the first self-service onboarding request path.

### Requirements

- **REQ-165** — The adapter-emitted terraform backend references
  `nova-tfstate-*` (not `acdl-tfstate-*`); the Kyverno
  `require-resource-labels.yml` policy enforces `nova:*` labels (not
  `acdl:*`). Correctness regression fix from the v1.15 rebrand. (Phase P1)
- **REQ-166** — All user-facing "ACDL" strings rebranded to Nova:
  onboarding message, Lambda alert title/body, PR-stage comments, CI
  banner, module docstrings (contract_resolver/confidence_signal/adapter/
  kyverno/wiz + adapters README). (Phase P2)
- **REQ-167** — Dead `ACDL_ENVIRONMENT_OVERRIDE` export removed; stale
  dual-read comments dropped; `acdl_*` temp-dir prefixes → `nova_*`. (Phase P3)
- **REQ-168** — `migrate_ssm_paths.py` `except Exception: pass` narrowed
  to `ParameterNotFound` + structured log. (Phase P4)
- **REQ-169** — `regression_verify.py` duplicated live-plan/resolver/
  lifecycle-resolve blocks extracted into shared helpers (~70 lines
  saved). (Phase P5)
- **REQ-170** — `run_platform.sh` dead export removed; HITL attestation
  block extracted to a shell function; hardcoded UUID/`v18` work-dir
  stamp replaced with config. (Phase P6)
- **REQ-171** — `contract_resolver.py` imports the env loader from
  `environment_check` (dedup); registry entries carry a `kind` field;
  fragile `is_l2` path-string heuristic replaced. (Phase P7)
- **REQ-172** — `scripts/sync_workflows.py` generates the 3
  byte-identical workflow pairs from one source; the byte-identity test
  is replaced with a generator-output test. (Phase P8)
- **REQ-173** — `run_platform.sh` decommission + uptime blocks extracted
  into `scripts/run_decommission.sh` + `scripts/run_uptime.sh`. (Phase P9)
- **REQ-174** — `contract_ingestor.py` fails closed (not silent `pass`)
  when IAM identity is absent; the env enum is derived from
  `core/environments/` (not hardcoded). (Phase P10)
- **REQ-175** — The contract blob payload is size-capped + schema-
  validated before the DynamoDB write; error/stackTrace caps are
  consistent. (Phase P11)
- **REQ-176** — `contract_resolver.py` (638 lines) split into resolve /
  decommission-transform / cli modules. (Phase P12)
- **REQ-177** — `regression_verify.py` (670 lines) split into capability
  checks / live-plan helpers / cli modules. (Phase P13)
- **REQ-178** — `SAFE_OUTPUT_NAMES` is schema-driven (from
  interface.json `sensitive` annotations); loaded schemas are cached in
  the resolver. (Phase P14)
- **REQ-179** — `run_platform.sh` has a real `--help`; `--deploy-uptime`
  is documented; `--local` is surfaced in the README. (Phase P15)
- **REQ-180** — `.github/workflows/README.md` catalogs all 7 workflows'
  triggers, inputs, required secrets, and reusable-workflow contracts. (Phase P16)
- **REQ-181** — A single getting-started section in the README:
  offline happy path (`run_ci.sh` + `run_platform.sh --check-only`/
  `--local`) first, AWS path second. (Phase P17)
- **REQ-182** — `schemas/onboarding.schema.json` defines the onboarding
  request; `contract_ingestor.py` gains an `onboard_consumer` action that
  writes a `pending` CMDB row. (Phase P18)
- **REQ-183** — `core/onboarding.py` generates a `<env>.json` from a
  consumer request + emits a PR; the onboarding message is rebranded to
  Nova and no longer routes to "contact the platform team" for the
  request step. (Phase P19)
- **REQ-184** — Terraform for the consumer deploy-role + `nova:owner`
  ABAC tag grant, offline-proven (`terraform validate` + `--check-only`
  only; no live apply). (Phase P20)

### v1.16 Traceability

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-165 | P1 | complete |
| REQ-166 | P2 | complete |
| REQ-167 | P3 | complete |
| REQ-168 | P4 | complete |
| REQ-169 | P5 | complete |
| REQ-170 | P6 | complete |
| REQ-171 | P7 | complete |
| REQ-172 | P8 | complete |
| REQ-173 | P9 | complete |
| REQ-174 | P10 | complete |
| REQ-175 | P11 | complete |
| REQ-176 | P12 | complete |
| REQ-177 | P13 | complete |
| REQ-178 | P14 | complete |
| REQ-179 | P15 | complete |
| REQ-180 | P16 | complete |
| REQ-181 | P17 | complete |
| REQ-182 | P18 | complete |
| REQ-183 | P19 | complete |
| REQ-184 | P20 | complete |

### Out of Scope (v1.16)
- New features (feat phases). v1.16 is NFR-only.
- Real AWS account/network/state provisioning (self-service) — the
  onboarding request path is implemented (D-113); actual cloud resource
  creation stays a future feature milestone.
- Live apply of the cross-account role Terraform (D-114) — offline-proven
  only; live apply deferred.
- D-083 audit ledger build-out (carries forward; unchanged).
- Real OIDC federation (carries forward; blocked on go-gitea/gitea#36988).
- Re-proposing v1.14 NFR categories already closed (D-117): over-broad
  excepts (REQ-141), hardcoded account-ID (REQ-142), IAM `Resource:"*"`
  scoping (REQ-143), contractId/env validation (REQ-144), `.gitignore`
  catch-all (REQ-146), `--kube-version` removal (REQ-147), orphan
  cleanup (REQ-148), `set -euo pipefail` parity (REQ-150).

## v1.17 — Strategic Direction, Leadership Metrics & Unified Story

**Milestone type:** Feature (P1–P3 feat; P4 docs; P5 docs+test; P6 test;
P7 review+audit+ship). Progressive patches; the final phase's patch IS
the milestone release. Tags run on the v1.16.x line: `v1.16.0` (P0) →
`v1.16.1..v1.16.7` (P1–P7) → `v1.16.8` (P8 final = milestone release).

**Objective:** Three pillars. (A) Encode the PO's strategic direction in
a durable `NORTH_STAR.md` read by CIAgent in every future `/ci-run`.
(B) Instrument Nova to collect, aggregate, and surface leadership-grade
metrics that prove the "no-humans" autonomous-infrastructure value
proposition — grounded in signals Nova actually emits, derived via
documented formulas, or explicitly deferred with a decision ID — flowing
into PowerBI-ready views. (C) Merge the two existing decks into one
unified narrative deck with the "tell them x3" arc at deck + slide level,
per-slide benefit callouts, and fluid transitions.

**Hard constraint:** DO NOT make anything up. Every metric carries a
`grounded` / `derived` / `deferred` status with a source file or
decision ID. Deferred metrics ship as empty PowerBI placeholder views
with documented schemas.

### Requirements

**Pillar A — Strategic Direction**

- **REQ-185** — `.ciagent/NORTH_STAR.md` is PO-authored with Vision,
  Strategic Objectives (4), Anti-Goals (5), Non-Goals (v1.17 scope),
  12–18mo Targets (with grounding column), and Success Criteria. The
  attestation clarification is reflected: human attestation required at
  stage gates (QA for production, SRE for operational readiness);
  autonomy in operations, not in accountability. (Phase P0)
- **REQ-186** — CIAgent reads `NORTH_STAR.md` in context-loading for all
  future milestones; the file is referenced from PROJECT.md and
  ARCHITECTURE.md so the strategic direction survives across milestones.
  (Phase P4)

**Pillar B — Leadership Metrics + PowerBI**

- **REQ-187** — Event emitters: a CloudEvents 1.0 envelope is adopted;
  a per-run manifest writer emits structured events (run_id, contractId,
  env, stages×durations, exit, confidence, HITL block count) to
  `metrics/runs/`; existing ephemeral `$WORK/*.json` (pcr, signal,
  event, outbox, stack) are persisted as durable artifacts; pytest
  `addopts` gains `--junitxml`+`--json-report`; Infracost runs as a
  plan post-processor emitting `cost.estimated{delta_usd}` (offline).
  (Phase P1)
- **REQ-188** — Decision Ledger: `outbox_writer.py` is extended to emit
  to a SQLite append-only table with hash chain; `ai.decision.made`
  events are modeled from Nova's real decision points (decision_id=run_id,
  chosen_action=band outcome, confidence=score, alternatives=perInput
  breakdown, human_override=HITL block) with outcome backfill from
  apply.completed; `attestation.recorded` events capture qa/prod/dr
  sign-offs (approver, env, concerns, result). Honors D-083 (no S3 Object
  Lock/JWS). (Phase P1)
- **REQ-189** — Metrics collector: `core/metrics/collector.py` +
  `schemas/metrics_*.schema.json` read all grounded signals
  (REGRESSION_REPORT.json, per-run manifests, junit XML, pcr.json,
  signal.json, COST.md, decision ledger) → normalized SQLite cold store
  at `metrics/nova_metrics.db`; idempotent re-runs. (Phase P2)
- **REQ-190** — PowerBI export: `core/metrics/powerbi_export.py` emits
  CSV/JSON views to `metrics/powerbi/` (fact_run, fact_capability,
  fact_policy_check, fact_confidence, fact_test, fact_decision,
  fact_cost_estimate, dim_capability, dim_milestone + 8 empty
  placeholder views for deferred metrics with documented schemas) +
  `docs/METRICS_VIEWS.md` schema doc. (Phase P3)
- **REQ-191** — Zero-touch efficiency metrics: Autonomous Resolution
  Rate (runs without operational HITL block ÷ total; attestation gates
  excluded), Human Escalation Frequency (operational HITL blocks only),
  AI Decision Accuracy (decisions not followed by apply.failed/incident
  within 5min), MTTD/MTTR (platform-run: apply.failed → successful
  retry). (Attestation Coverage is owned by REQ-194, not here.)
  (Phase P4)
- **REQ-192** — Velocity metrics: Provisioning Lead Time
  (apply.completed.time − intent.received.time), Deployment Frequency
  (count(apply.completed) per day). Self-Healing Velocity deferred (no
  auto-remediator). (Phase P4)
- **REQ-193** — Financial & cost-ROI metrics: FTE Hours Saved (derived:
  run count × manual baseline), Cost Savings via Infracost estimates
  (grounded), Cost Efficiency Ratio (derived), Platform ROI (derived
  formula). Live CUR reconciliation deferred (D-096). (Phase P4)
- **REQ-194** — Reliability, security & compliance metrics: Zero-Trust
  Policy Compliance Rate (from pcr.json), Attestation Coverage (prod/dr
  promotions attested by a human ÷ total prod/dr promotions; grounded in
  hitl_gates.py + outbox approver_* attributes; canonical owner of this
  metric). Uptime, Patch Remediation, SLA/downtime deferred (D-096).
  (Phase P4)
- **REQ-195** — Metrics catalog doc: `docs/METRICS.md` catalogs every
  executive KPI with `grounded`/`derived`/`deferred` status, source
  file or decision ID, and a per-KPI definition-of-success doc in
  `docs/metrics/<kpi>.md`. (Phase P4)

**Pillar C — Unified Narrative Deck**

- **REQ-196** — The two existing decks (`how-the-platform-works` +
  `the-developer-experience`) are merged into one unified narrative deck
  "Nova — The No-Humans Infrastructure Platform" with a single arc:
  Problem → Vision/Direction (NORTH_STAR) → How it works → Proof
  (metrics) → Roadmap/Ask. The x3 structure ("tell them what you're
  going to tell them → tell them → tell them what you told them") applies
  at deck level (opening = arc; body = tell them; closing = recap + ask).
  Both old decks are retired (all derived artifacts deleted). (Phase P5)
- **REQ-197** — Each slide has the x3 structure (opens with what it
  covers, delivers, closes with an explicit "benefit of this stage"
  callout) + fluid transitions between slides (no disjointed jumps).
  The 4-step deck process (source `.md` → Marp → HTML → talking-points)
  is re-run for the unified deck. (Phase P5)

**Cross-cutting**

- **REQ-198** — Regression capability: CAP-023 (metrics collector runs,
  emits expected schema) + CAP-024 (deck structure: slide count, x3
  present, per-slide benefit present) added to `core/regression_verify.py`.
  (Phase P6)

**Ideation enhancements (REQ-199..213 — additive, within D-120..D-132)**

- **REQ-199** — Metrics schema validation in CI: `run_ci.sh` validates
  `metrics/powerbi/*.json` + a sample `metrics/events.jsonl` against
  their schemas; exits 0. (Phase P3)
- **REQ-200** — Idempotent collector re-run test: `test_metrics_collector_idempotent`
  passes (two runs → identical row counts + chain verified). (Phase P2)
- **REQ-201** — Metrics store backup/restore doc: `metrics/README.md`
  documents regenerable vs append-only artifacts + restore procedure.
  (Phase P2)
- **REQ-202** — Metrics glossary appendix slide: the unified deck has a
  "Metrics Glossary" appendix slide with one-line KPI definitions +
  grounding badges. (Phase P5)
- **REQ-203** — "What's Deferred — and Why" slide: the unified deck has
  a slide pairing each of 8 deferred metrics with its blocking decision
  ID. (Phase P5)
- **REQ-204** — NORTH_STAR diff-check in CI: `run_ci.sh` includes
  `check_north_star_diff` that fails when Vision/Objectives/Anti-Goals/
  Targets sections change without a `NORTH_STAR-CHANGE:` commit trailer.
  (Phase P4)
- **REQ-205** — Per-module lifecycle success-rate report: each lifecycle
  run writes `metrics/lifecycle/<module>-<env>.json`; collector projects
  into `fact_lifecycle`; PowerBI "Module Lifecycle Health" view. (Phase
  P1 emitter + P2 collector + P3 view)
- **REQ-206** — Code coverage trend emission: `pyproject.toml` addopts
  gains `--cov=core --cov=adapters --cov-report=json:metrics/coverage.json`;
  collector ingests; `fact_test` carries a coverage column. (Phase P1 +
  P2)
- **REQ-207** — Decision Ledger CLI: `core/metrics/decision_ledger_cli.py`
  supports `query`, `verify-chain`, `stats`, `export`, `replay`;
  `verify-chain` detects broken hashes; `replay` prints ordered events;
  tests pass offline. (Phase P2)
- **REQ-208** — PowerBI starter dashboard README: `metrics/powerbi/NOVA_DASHBOARD_README.md`
  documents folder-connector import + starter visual model + reference
  screenshot. (Phase P3)
- **REQ-209** — PowerBI column-level data dictionary: `docs/METRICS_VIEWS.md`
  has a per-column data-dictionary table (column, type, source/formula,
  unit, grounded/derived/deferred status). (Phase P3/P4)
- **REQ-210** — Deferred-metrics activation roadmap: `docs/METRICS_DEFERRED_ROADMAP.md`
  lists 8 deferred metrics + onboarding-grant half with {blocking
  decision, unblock requirement, candidate milestone} + a "Hot-Path
  Activation (post-D-096)" section (Nova-native only, D-120) +
  "Re-evaluation Triggers" section. (Phase P4)
- **REQ-211** — Trust-snapshot report: `core/metrics/trust_snapshot.py`
  emits `metrics/TRUST_SNAPSHOT.md` with 5 trust metrics (Decision Ledger
  Coverage, Attestation Coverage, Capability Health, AI Decision
  Accuracy, Confidence-Gate Halt Rate) + chain-integrity verdict +
  snapshot hash; runs offline. (Phase P4)
- **REQ-212** — Confidence-Gate Halt Rate metric: `docs/METRICS.md` +
  trust snapshot include "Confidence-Gate Halt Rate" (signal.json
  band=halt ÷ total runs); PowerBI view includes it. (Phase P4)
- **REQ-213** — "No-humans" thesis defensibility brief: `docs/NO_HUMANS_THESIS.md`
  defines the thesis, grounded proof metrics, deferred proof metrics,
  and explicit anti-claims (incl. D-122 honesty); the unified deck's
  Vision act cites it. (Phase P4/P5)

### v1.17 Traceability

| Requirement | Phase | Status |
|-------------|-------|--------|
| REQ-185 | P0 | complete |
| REQ-186 | P4 | complete |
| REQ-187 | P1 | complete |
| REQ-188 | P1 | complete |
| REQ-189 | P2 | complete |
| REQ-190 | P3 | complete |
| REQ-191 | P4 | complete |
| REQ-192 | P4 | complete |
| REQ-193 | P4 | complete |
| REQ-194 | P4 | complete |
| REQ-195 | P4 | complete |
| REQ-196 | P5 | complete |
| REQ-197 | P5 | complete |
| REQ-198 | P6 | complete |
| REQ-199 | P3 | complete |
| REQ-200 | P2 | complete |
| REQ-201 | P2 | complete |
| REQ-202 | P5 | complete |
| REQ-203 | P5 | complete |
| REQ-204 | P4 | complete |
| REQ-205 | P1+P2+P3 | complete |
| REQ-206 | P1+P2 | complete |
| REQ-207 | P2 | complete |
| REQ-208 | P3 | complete |
| REQ-209 | P3/P4 | complete |
| REQ-210 | P4 | complete |
| REQ-211 | P4 | complete |
| REQ-212 | P4 | complete |
| REQ-213 | P4/P5 | complete |

### Out of Scope (v1.17)

- Live AWS re-provisioning (D-096) — metrics requiring live
  infrastructure ship as placeholder views.
- Onboarding auto-grant (D-113/D-114/D-119) — only the request-path
  metric is grounded.
- ML anomaly-forecasting / predictive remediation — no emitter today;
  Predictive-vs-Reactive metric ships as a placeholder.
- Drift detection scheduled job (D-096 + no scheduler) — drift metrics
  ship as placeholders.
- Live cost CUR reconciliation (D-096) — Infracost pre-apply estimates
  are grounded; actuals are not.
- S3 Object Lock / JWS tamper-evident ledger (D-083) — Decision Ledger
  uses a local SQLite hash-chain this milestone.
- Multi-cloud support (Azure/GCP/K8s) — Nova is AWS-only this milestone.
- A third deck — the two existing decks merge into one; no new
  standalone metrics deck.
- A Nova web UI — dashboards are PowerBI, not a Nova-built frontend.

---

## v1.18 — Citizen Developer & Production-Grade Guidance

> **Milestone type:** Feature. Tags run on the v1.17.x patch line (previous
> minor per branch-strategy). `v1.17.0` (P0) → `v1.17.1..v1.17.6` (P1–P6) →
> `v1.17.7` (P7 final = milestone release).
> **Active milestone:** v1.18. **Branch:**
> `milestone/v1.18-citizen-developer-guidance`.

### Requirements

- **REQ-214** — S&P Global Energy Marp theme restored in the unified deck
  (`docs/presentations/nova-no-humans-platform-marp.md`). The `style:` block
  from commit `ae0cb58` (v1.9.2 / P45) is ported: H1/H2 `#D6002A`
  (S&P red-core), title-slide bg `#1B1B1B` (grey-90) with 8px `#D6002A` top
  accent bar, body text `#1B1B1B`, blockquote border `#D6002A`,
  table headers `#F0F0F0`, font `'Akkurat Pro'` with web-safe fallbacks. The
  current Nova header/footer text is preserved (rebrand is not touched —
  only the visual theme is restored). HTML re-rendered with the S&P theme.

- **REQ-215** — RACI matrix authored in `PROJECT.md` (new `## RACI Matrix`
  section) and `docs/raci.md` (citizen-developer-facing copy). Three roles:
  **Citizen Developer** (Responsible for all Functional Requirements + User
  Acceptance Testing — via their AI coding agent / upstream agentic SDLC /
  upstream development platform; the source does not matter as all are
  subject to the same compliance standards), **Platform** (Responsible for
  all NFRs + Infrastructure + QA + Production deployments to cloud),
  **Release Management** (co-owned: QA + SRE attestations required by the
  actual release, performed agentically but overseen & triggered by the
  Citizen Developer). Rendered as a table: rows = work categories (FRs, UAT,
  NFRs, Infra, QA, Prod deploy, Release attestation), columns = R/A/C/I per
  role. Includes the compliance-standard-equivalence note.

- **REQ-216** — PDLC-upstream scope statement made explicit in `PROJECT.md`
  (new `## Scope: Nova is Downstream of PDLC` subsection under Domain
  Boundaries) and `docs/scope.md`. States that the PDLC (Product Development
  Lifecycle — product backlog, code authorship, IDE) is upstream of Nova;
  Nova governs infra + delivery only; integration is through the validated
  contract boundary. Promotes Core Tenet #2 + Anti-Goal #1 from buried
  tenets to a dedicated, unmissable scope statement.

- **REQ-217** — `schemas/submission-readiness.schema.json` (JSON Schema
  draft 2020-12) defines what is acceptable to start — a superset gate
  *above* `contract.schema.json` validity. Required fields: `contractId`
  (non-empty), `environment` (dev/qa/prod/dr) with the W3.E per-env mandatory
  table enforced (dev: stack+environment; qa: +validation.e2eSuite
  +validation.loadTest; prod: +runbook+dashboard+oncall; dr: +drDrillRef),
  `tags` (the 5 required Nova tags per D-054: `nova:owner`, `nova:contract`,
  `nova:environment`, `nova:cost-center`, `nova:ref`), `policyPreconditions`
  (declared policy expectations the platform will enforce, e.g.,
  `public-ingress: false`), `profile` (`developer` or `agentic`; if
  `agentic`, requires `naturalLanguageIntent`, `confidenceAtSubmission`,
  `agentTrace` per REQ-22 / W3.E), `appSource` (repo + ref pointer for
  runtime fetch).

- **REQ-218** — `core/submission_readiness.py` validator, invoked as
  `contract_ingestor.py --check-readiness` subcommand (decision D-133). Returns
  a structured `ReadinessResult` (pass/fail per check, with reason codes).
  On fail → the ingestor rejects with a citizen-developer-facing error
  (not a stack trace). On pass → proceeds to existing contract ingestion.
  Calls `contract.schema.json` validation first, then the readiness checks.
  Reason codes: `MISSING_TAGS`, `ENV_MISSING_MANDATORY:<env>:<field>`,
  `AGENTIC_MISSING_INTENT`, `MISSING_APP_SOURCE`, `POLICY_PRECONDITION_MISSING`.

- **REQ-219** — `docs/submission-readiness.md` citizen-developer-facing doc
  explaining what is acceptable to start, with good + rejected examples and
  the reason-code catalog. References `schemas/submission-readiness.schema.json`
  as the source of truth.

- **REQ-220** — `tests/test_submission_readiness.py` covers: good contract
  passes; missing tags fail with `MISSING_TAGS`; missing env mandatory fails
  with `ENV_MISSING_MANDATORY:<env>:<field>`; agentic profile missing intent
  fails with `AGENTIC_MISSING_INTENT`; missing appSource fails with
  `MISSING_APP_SOURCE`.

- **REQ-221** — `skills/` directory with 9 Atelier-derived skill files mapped
  to the BA.A citizen-developer catalog: `skills/api.md` (domains/api/),
  `skills/security.md` (domains/security/), `skills/data.md` (domains/data/),
  `skills/testing.md` (domains/testing/), `skills/observability.md`
  (domains/observability/), `skills/errors.md` (domains/errors/),
  `skills/devops.md` (domains/devops/), `skills/infrastructure-as-code.md`
  (domains/infrastructure-as-code/), `skills/compliance.md`
  (domains/compliance/). Each names the Atelier source path, distills the
  first-principles to the citizen-developer-relevant subset, links to
  agent-checklist triggers, and maps to the BA.A 5-skill catalog (web API,
  worker, scheduled job, static asset, basic observability bootstrap).

- **REQ-222** — `docs/skills.md` index page listing the skill catalog, the
  Atelier provenance, and how the citizen developer's AI agent consumes them
  (read before completing a task; run `review/agent-checklist.md` before
  finishing). `PROJECT.md` BA.A decision extended with the Atelier-derived
  skill catalog reference.

- **REQ-223** — `mcp/atelier/server.py` MCP server (stdio transport,
  decision D-135) with a **plugin-registry architecture** (decision D-140):
  `plugins/<name>.py` modules each expose `register(mcp: MCPServer) -> None`
  and call `@mcp.tool()` for their tools; `server.py` scans `plugins/` and
  calls `register` on each. Initial plugins: `principles.py`
  (`atelier.lookup_principle`, `atelier.list_domains`, `atelier.matrix_lookup`)
  and `validation.py` (`atelier.validate_against_principles` — agentic
  validation against the Atelier agent-checklist, beyond Wiz/Checkmarx/Mend).
  Uses the MCP Python SDK v2 (`modelcontextprotocol/python-sdk`).

- **REQ-224** — `mcp/atelier/vendor/` vendored Atelier snapshot (pinned tag,
  decision D-136) for audit reproducibility. `mcp/atelier/vendor/VERSION.md`
  records the pinned tag + a `scripts/update_atelier_vendor.sh` helper for
  intentional upgrades. `mcp/atelier/README.md` documents the server: how to
  run, transport, tool catalog, plugin-authoring guide, vendoring policy.

- **REQ-225** — `tests/test_atelier_mcp.py` covers: tool registration (all 4
  tools discoverable via `tools/list`), `atelier.lookup_principle` returns
  the principle text + core C-rule, `atelier.validate_against_principles`
  catches a planted C1 (correctness) + C7 (observability) violation in a
  known-bad snippet and passes a known-good snippet, `atelier.matrix_lookup`
  returns the domain→core mapping, plugin discovery loads all plugins in
  `plugins/`.

- **REQ-226** — 3 new deck slides added to the unified deck
  (`docs/presentations/nova-no-humans-platform-marp.md`) → 21 slides total:
  Slide 19 "Scope: Downstream of PDLC", Slide 20 "RACI: Who Owns What",
  Slide 21 "Production-Grade Guidance via Atelier". Arc Preview slide
  updated to reflect 21-slide count. Talking points
  (`nova-no-humans-platform-talking-points.md`) synced for the 3 new slides.
  S&P theme preserved (regression check vs P1). CAP-024 deck structure
  regression passes.

- **REQ-227** — `docs/presentations/README.md` slide count + deck table
  updated to reflect 21 slides + the 3 new slide titles.

- **REQ-228** — `scripts/render_deck.sh` (renders HTML + PPTX from a Marp
  deck, commits both to git) and `scripts/attach_release_asset.py` (uploads
  a file to a Gitea release via the API). Any phase modifying
  `docs/presentations/*-marp.md` or `docs/presentations/assets/` MUST
  re-render HTML + PPTX, commit the PPTX binary to `docs/presentations/`,
  and attach it to the phase's Gitea release. PPTX is stored as a committed
  binary (no LFS, decision D-141).

### Out of Scope (v1.18)

- **Streamable HTTP transport for the MCP server** — stdio ships now; HTTP
  is a future milestone (the SDK supports it on the same server object, so
  adding it later is a transport-only change, not a rewrite).
- **A Nova-built frontend / dashboard** — observability stays PowerBI /
  external; no Nova web UI.
- **Replacing the existing BA.A 5-skill catalog** — the Atelier-derived
  skills extend it, not replace it.
- **Live AWS re-provisioning** (D-096, still deferred) — submission-readiness
  validates the contract shape, not a live AWS deployment.
- **A second forge adapter** (GitLab) — BA.F cross-platform evolution is
  future work.
- **Atelier live-fetch mode** — vendoring is the only mode this milestone;
  live-fetch (with its reproducibility trade-offs) is not implemented.

### v1.18 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-214 | P1 | complete |
| REQ-215 | P2 | complete |
| REQ-216 | P2 | complete |
| REQ-217 | P3 | complete |
| REQ-218 | P3 | complete |
| REQ-219 | P3 | complete |
| REQ-220 | P3 | complete |
| REQ-221 | P4 | complete |
| REQ-222 | P4 | complete |
| REQ-223 | P5 | complete |
| REQ-224 | P5 | complete |
| REQ-225 | P5 | complete |
| REQ-226 | P6 | complete |
| REQ-227 | P6 | complete |
| REQ-228 | P1/P2/P6 | complete |

## v1.19 — Nova 2nd-Release Sync (GitLab consumer mirror)

> **NFR-only chore milestone.** A single execution phase shipping a patch on
> the v1.18.x line (tag `v1.18.0`). Establishes the manual-only "2nd release"
> pipeline from `~/acdl` (CIAgent-managed source of truth) into `~/nova`
> (GitLab `jonathanchery/nova` — a separate repo, separate history, consumer /
> platform-team audience). `~/acdl` retains the full CIAgent audit trail;
> `~/nova` receives only the consumer subset, committed with real
> conventional commits per domain (no kitchen-sink "sync from source mirror").

- **REQ-229** — `scripts/sync_to_nova.sh` replaces `scripts/sync_to_gl.sh`.
  The script: (1) refuses to run without `--release` / `RELEASE_CONFIRMED=1`
  (manual-only — never triggerable by CI); (2) rsyncs the consumer subset of
  `~/acdl` into `~/nova`, excluding `.ciagent/`, `.gitea/`, `.env*`, `terraform/`,
  `demo/`, runtime metrics artifacts, and internal-only scripts (full list in
  `EXCLUDE_SCRIPTS`), while protecting `~/nova/.git` history via rsync
  `--filter=P .git`; (3) commits changes domain-by-domain in a fixed order
  (config → core → adapters → modules → contracts → schemas → pipelines →
  mcp → skills → scripts → tests → docs → workflows) using one
  conventional-commit message per changed domain passed via repeated `-m`
  flags (positional mapping over changed domains only — no kitchen-sink
  commit); (4) validates conventional-commit format (`feat|fix|docs|chore|…`)
  unless `--no-verify-format`; (5) pushes to the branch upstream unless
  `--no-push`. `--list-domains`, `--dry-run`, `-v` supported. The old
  `sync_to_gl.sh` is removed. (Phase P1)

### Out of Scope (v1.19)

- **coreci / Atelier review gate on the synced tree** — deferred; the sync
  ships consumer contents as-is. A future milestone may run a vendored-Atelier
  review pass before commit and block on P0 findings.
- **Tagging releases on the `~/nova` side** — could add `--tag <semver>` later.
- **Deleting `~/gl`** — the old GitLab `acdl` mirror is left on disk; only the
  sync script targeting it is removed.

### v1.19 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-229 | P1 | complete |

## v1.20 — Consumer Cleanup + Transparent Terraform + Slide Pipeline

> **Multi-concern milestone.** Four user-directed inputs spanning consumer
> cleanup, infrastructure transparency, and presentation automation. Tags
> run on the v1.19.x line (milestone v1.20 → tags v1.19.0, v1.19.1, …).
>
> **Input 1 — Gitea/GitLab removal:** Remove all mentions of `gitea` / `gitlab`
> (case-insensitive) from every file synced to `~/nova`. The platform team
> (consumer of `~/nova`) must never know about the dev forge or the GitLab
> mirror. Genericize forge-detection code to `forge` / `generic_forge`.
>
> **Input 2 — Documentation simplification:** Radically simplify all synced
> documentation. Anything the CIAgent needs to reference for itself lives in
> `.ciagent/`. Everything else is tailored to the Platform Team audience.
> Strip ciagent-internal provenance (REQ-/D-/P-/CAP- IDs, milestone headers,
> `.ciagent/PROJECT.md` citations) from synced docs. Delete completed
> migration guides. Move internal artifacts to `.ciagent/`.
>
> **Input 3 — Transparent terraform:** Move terraform `init` / `validate` /
> `plan` / `apply` / `output` into native workflow steps (transparent, visible
> in CI logs). Split `run_platform.sh` into `run_codegen.sh` (pre-TF) +
> `run_postapply.sh` (post-TF). Add `var.enabled` feature flags to every L1
> module + L2 composition toggles. Wire forge repo variables as per-client
> feature flags — different clients test different functionality without
> version upgrades.
>
> **Input 4 — Slide pipeline + product roadmap:** The slides have not adopted
> the S&P Global theme fully. Create a dedicated render pipeline that builds
> the slides (mermaid PNGs + Marp HTML/PPTX) with the S&P theme applied to all
> slide chrome. Add a 12-month product roadmap (high-level, product-oriented
> vs the technical roadmap in `.ciagent/ROADMAP.md`) to the deck.

- **REQ-230** — No `gitea` / `gitlab` string literal (case-insensitive) appears
  in any file synced to `~/nova`. Verified by
  `tests/test_no_forge_mentions.py` which scans the synced subset (same
  path rules as `sync_to_nova.sh`'s `DOMAINS` / `EXCLUDES`). Forge-detection
  code (`contract_ingestor.py`, `hitl_gates.py`, `run_platform.sh`) is
  genericized: `gitea` → `forge` / `generic_forge`, `GITEA_ACTOR` →
  `FORGE_ACTOR` (with `GITHUB_ACTOR` primary). (Phase P1)

- **REQ-231** — Synced documentation is tailored to the Platform Team
  audience. Ciagent-internal provenance (`v1.XX — Strategic Direction` headers,
  `REQ-NNN` / `D-NNN` / `P-NNN` / `CAP-NNN` IDs, `.ciagent/PROJECT.md`
  "source of truth" citations) is stripped from synced docs. (Phase P1)

- **REQ-232** — Completed/historical migration docs
  (`docs/NOVA_MIGRATION.md`, `docs/NOVA_AWS_MIGRATION.md`) removed from the
  synced tree. `docs/NO_HUMANS_THESIS.md` moved to `.ciagent/` (internal
  thesis-defense artifact). (Phase P1)

- **REQ-233** — Terraform `init` / `validate` / `plan` / `apply` / `output`
  run as native workflow steps in `deploy.yml` (transparent, named steps
  visible in CI logs), not buried inside `run_platform.sh`. (Phase P4)

- **REQ-234** — `run_platform.sh` is split: `run_codegen.sh` (pre-TF: env
  check, validate, resolve, adapt) + `run_postapply.sh` (post-TF: Checkov,
  confidence, HITL, outbox, SSM, comment, uptime). A thin `run_platform.sh`
  shim preserves backward compat for local-dev usage. (Phase P4)

- **REQ-235** — Every L1 module has `variable "enabled" { type = bool,
  default = true }` + `count = var.enabled ? 1 : 0` on its primary
  resource(s); declared in `interface.json`. The `uptime` module's
  `feature_flag_enabled` is renamed to `enabled` (with backward-compat alias).
  (Phase P4)

- **REQ-236** — L2 `composition.json` supports per-child `enabled` toggles
  driven by contract `inputs.enable_<child>`. The resolver skips children
  with `enabled: false`. (Phase P4)

- **REQ-237** — `deploy.yml` reads feature flags from forge repository
  variables (`vars.ENABLE_*`) and passes them as `-var` flags to terraform,
  enabling per-client feature toggles without version upgrades. (Phase P4)

- **REQ-238** — Stale artifact path `/tmp/acdl_platform_run_v18` in
  `deploy.yml` fixed to use `NOVA_WORK_DIR`. (Phase P4)

- **REQ-239** — A dedicated S&P Global theme CSS file
  (`docs/presentations/assets/nova-sp-theme.css`) is the Marp theme for all
  Nova presentation decks. The theme applies the S&P Red/Black/White palette
  (`#D6002A`, `#1B1B1B`, `#FFFFFF`) to all slide chrome (background,
  header/footer, pagination, tables, blockquotes), not just headings. (Phase P2)

- **REQ-240** — A dedicated render pipeline (`scripts/render_slides.sh`)
  builds the presentation deck end-to-end: (1) renders all
  `assets/mmd/*.mmd` → `assets/png/*.png` via `mermaid-cli --configFile
  sp-theme.json`; (2) renders the Marp deck → HTML + PPTX via `marp-cli`;
  (3) stages all rendered artifacts to git. Supersedes `render_deck.sh`.
  (Phase P2)

- **REQ-241** — A CI workflow (`workflows-src/slides.yml` +
  `.github/workflows/slides.yml`) runs `render_slides.sh` on any change to
  `docs/presentations/**` and commits the rendered HTML/PPTX/PNGs back. No
  manual re-render step; no artifact drift. (Phase P2)

- **REQ-242** — `tests/test_slides_pipeline.py` validates: (1) the Marp
  deck frontmatter references `nova-sp-theme.css`; (2) the CSS contains the
  S&P colors; (3) every `.mmd` has a corresponding `.png`; (4) the HTML
  exists and is newer than the Marp `.md`. (Phase P2)

- **REQ-243** — `docs/presentations/README.md` directory layout is updated
  to remove retired decks (`how-the-platform-works-*`,
  `the-developer-experience-*`) and document the render pipeline + theme CSS.
  (Phase P2)

- **REQ-244** — A 12-month product roadmap (4 quarters, product-outcome
  oriented, grounded in NORTH_STAR strategic objectives + deferred-metric
  candidate milestones) is added to the presentation deck as Slide 20 +
  Slide 21. The roadmap is distinct from Slide 15's deferred-metric unblock
  paths. A matching talking-points section is added. (Phase P3)

### Out of Scope (v1.20)

- **Multi-cloud (Azure/GCP) implementation** — deferred; only the product
  roadmap references it as a Q4 aspiration.
- **ML anomaly-forecasting service** — deferred; only the product roadmap
  references it as a Q4 aspiration.
- **Actual pilot estate activation** — deferred (requires live AWS
  re-provisioning, D-096 lift); the product roadmap references it as Q1.
- **Token rotation for `NOVA_GITEA_TOKEN`** — out of scope; the `.env` files
  are correctly excluded from sync. Flagged for awareness only.

### v1.20 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-230 | P1 | complete |
| REQ-231 | P1 | complete |
| REQ-232 | P1 | complete |
| REQ-233 | P4 | complete |
| REQ-234 | P4 | complete |
| REQ-235 | P4 | complete |
| REQ-236 | P4 | complete |
| REQ-237 | P4 | complete |
| REQ-238 | P4 | complete |
| REQ-239 | P2 | complete |
| REQ-240 | P2 | complete |
| REQ-241 | P2 | complete |
| REQ-242 | P2 | complete |
| REQ-243 | P2 | complete |
| REQ-244 | P3 | complete |

## v1.21 — Nova Deck Refinement & Pipeline Hardening

> Leadership-deck refinement based on 33 review notes on the v1.20 deck
> (v1.20 shipped as `nova-no-humans-platform*`). This milestone renames the
> deck to the professional "Autonomous Cloud Delivery Platform" framing,
> restructures the narrative (Problem → Solution → Proof → Roadmap + Ask),
> removes internal provenance from audience-facing slides, hardens the
> policy pipeline (Checkov before plan, Wiz-or-Checkov on plan), and moves
> the strategic integration objective into the North Star.
>
> Tags run on the v1.20.x line (milestone v1.21 → tags v1.20.0, v1.20.1, …).

### REQ-245 — Deck rename + restructure

The deck files are renamed from `nova-no-humans-platform*` to
`nova-autonomous-cloud-delivery*` across all five artifacts
(source `.md`, `-marp.md`, `.html`, `.pptx`, `-talking-points.md`).
The in-deck title becomes "Nova — The Autonomous Cloud Delivery Platform"
(professional, conveys autonomy without the provocative "no-humans"
wording). The narrative restructures to 18 main + 1 appendix slides:

1. The Problem (merged old 1+2; broader problem framing; no "arc"; no
   "18 capabilities verified"; not "humans are the problem"; add tribal
   knowledge / rockstar-operator framing)
2. Nova's Vision
3. Strategic Objectives + Anti-Goals
4. Scope: Downstream of PDLC (moved up)
5. RACI: Who Owns What (moved up)
6. The Platform Pipeline
7. The Decision Ledger
8. The Attestation Matrix
9. Telemetry & Live Ops
10. Decision Ledger + Attestation Coverage
11. Cost & ROI
12. What's Deferred — and Why
13. Roadmap to the North Star
14. 12-Month Product Roadmap
15. Quarter-by-Quarter Outcomes
16. Production-Grade Guidance via Atelier (1/2)
17. Production-Grade Guidance via Atelier (2/2)
18. Recap + Ask
A1. Metrics Glossary

Removed: old Slide 10 (Capability Health), old Slide 12 (Zero-Touch
Efficiency), old Appendix A2 (Operating Model & Cost). Slide 5's first
table removed.

### REQ-246 — Thesis rename + reframe

`.ciagent/NO_HUMANS_THESIS.md` is renamed (git mv) to
`.ciagent/AUTONOMY_THESIS.md`. Content reframes from "removing humans" to
"autonomy in operations, human at stage gates" — professional, not
provocative. The operator-bottleneck framing is softened; the attestation
model + provable trust are emphasized. Anti-claims are retained and
reworded for a tech-leadership audience. All references across the repo
are updated to the new filename + framing.

### REQ-247 — Strategic-docs sync (NORTH_STAR + PROJECT)

`NORTH_STAR.md` is updated:
- Vision polished for a technical audience concerned about security,
  security remediation velocity, and reliability; "infrastructure
  operations become visible" is preserved as a recurring theme.
- Strategic Objective #2 (provable trust) is reworded: trust is
  established by deterministic scripts that calculate a score, not by
  AI. The platform functions without AI. "AI decisions" are really
  automated decisions.
- Strategic Objective #3 (ROI) is reworded with four CTO-grade metrics:
  Lead Time (PR → Production), Infrastructure Vulnerability Count
  (downward trend), MTTR, Cloud Spend Reduction. All flow into PowerBI
  views and are captured by the telemetry pipeline.
- Strategic Objective #4 is replaced: integrate with externally owned
  PDLC, SDLC, Agentic, and Citizen Developer platforms regardless of
  source; Nova provides skills + MCP endpoints to make applications
  production-grade; all intents to deploy to production go through the
  same rigorous controls and quality gates.
- Anti-goals #1 (hyperscaler competitor), #4 (legacy untagged), and #5
  (sold to operators) are removed. Two new anti-goals added: not an
  upstream development platform; not a replacement for the Product
  Lifecycle (PDLC).
- Anti-goal #3 reworded to remove the "removes humans" framing.

`PROJECT.md` mission statement + scope are synchronized with the
integration objective and the reworded strategic objectives.

### REQ-248 — RACI restructure (Quality Engineering + SRE)

The RACI matrix (slide + `docs/raci.md`) is restructured:
- A **Quality Engineering** column is added.
- The Platform column no longer holds the **A** for release attestation;
  accountability is reassigned to QA or SRE as appropriate.
- "Release Management" is renamed to **SRE**.
- "Release attestation" is split into two rows: the SRE part is
  **Production Readiness** (operational readiness sign-off).
- The slide is sized to fit (text shrunk / low-impact rows dropped).

### REQ-249 — Atelier split (2 slides)

Slide 19 (Production-Grade Guidance via Atelier) is split into two slides:
- **16 (1/2):** Skills + MCP server overview (the 9 skills, the 4 MCP
  tools, the plugin-registry + stdio surface).
- **17 (2/2):** Agentic validation beyond deterministic scanners +
  vendored Atelier for audit reproducibility.
The benefit wording is improved; the same spirit is retained.

### REQ-250 — Pipeline hardening (Checkov before plan; Wiz-or-Checkov on plan)

`scripts/run_platform.sh` (and `scripts/run_postapply.sh` where
relevant) implement the two-stage policy scan:
1. **Checkov runs on static code** (the generated `main.tf` / TF
   directory) **before** `terraform plan` — fail-fast, quick developer
   feedback on policy violations in the authored code.
2. **After `terraform plan`:** if `WIZ_API_TOKEN` + `WIZ_API_URL` are
   set, run **Wiz against the plan**; otherwise run **Checkov against
   the plan** as a drop-in replacement. **Wiz and Checkov are never
   both run on the plan.**
`adapters/wiz/wiz_adapter.py` is updated if needed for plan-mode
input. Slide 6 + `docs/scope.md` reflect the new flow. Tests
(`tests/test_pipeline.py`, `tests/test_pipeline_contract.py`, and
any checkov/wiz tests) are updated and pass.

### REQ-251 — Theme CSS fix (Appendix A1) + footer cleanup

`docs/presentations/assets/nova-sp-theme.css` is fixed so the Appendix
A1 Metrics Glossary table is readable (the table background color is
corrected). The Marp footer no longer shows the version (`v1.20`) or
the `Act %{page}/5` artifact. The title-slide subtitle no longer shows
`v1.18 — Citizen Developer & Production-Grade Guidance`; it becomes
"Product Development & Citizen Developer Overview" (or similar) to
convey the audience for the platform.

### REQ-252 — Global citation + badge + version removal

Across all audience-facing slides (the Marp deck, the source-of-truth
markdown, and the talking points):
- All internal citations are removed: `D-###` decision IDs,
  `REQ-###` requirement IDs, and internal file paths
  (e.g. `outbox_writer.py`, `confidence_signal.py`).
- All `<span class="badge planned">Planned</span>` badges are removed.
- The version is removed from the footer and the title slide.
Every benefit callout is rewritten for a tech-leadership audience
(security, remediation velocity, reliability, lead time). A "less is
more / no fluff" final prose pass is applied; the story stays clear.

### REQ-253 — Render + verify + ship

Changed/new mermaid diagrams are re-rendered (slide 1 new diagram, slide
9 expand, Atelier split). HTML + PPTX are re-rendered via
`scripts/render_slides.sh`. `tests/test_slides_pipeline.py` passes:
asserts 18 main + 1 appendix slides, no badge spans, no version in the
footer, no `D-###`/`REQ-###`/`.py` paths in audience-facing slides, and
filename refs updated in render scripts + CI workflow + README.
`tests/test_no_forge_mentions.py` passes. Full `pytest` passes
(pipeline-hardening tests green). `run_platform.sh --check-only` passes.
Milestone ship: tag the final phase on the v1.20.x line; create a
release; attach the PPTX.

### Out of Scope (v1.21)

- **Live pilot estate activation** — still deferred (D-096).
- **ML anomaly-forecasting service** — still deferred.
- **Multi-cloud (Azure/GCP) implementation** — still deferred.
- **Tamper-evident ledger (S3 Object Lock + JWS)** — still deferred
  (D-083); the deck describes it as a roadmap item without citing the
  decision ID in the audience-facing slides.

### v1.21 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-245 | P2 | complete |
| REQ-246 | P1 | complete |
| REQ-247 | P1 | complete |
| REQ-248 | P2 | complete |
| REQ-249 | P2 | complete |
| REQ-250 | P4 | complete |
| REQ-251 | P3 | complete |
| REQ-252 | P2 | complete |
| REQ-253 | P5 | complete |

## v1.22 — Nova Deck Layout Fix

> Fixes the systemic layout/formatting problems in the Nova presentation
> deck that made every slide look "out of whack" after the v1.21 P5
> re-render. Root cause (per investigation): `nova-sp-theme.css` has zero
> `section` padding (it declares `/* @theme nova-sp */` as a comment, not
> the `@theme` directive, and does not `@import` Marp's default theme, so
> Marp's default `section { padding: 56px 64px }` never applies). Combined
> with `overflow:hidden` (silent clip), a blunt `img { max-height: 320px }`
> rule, header+footer chrome on every slide, and two new P5 diagrams with
> extreme aspect ratios (13.52× and 0.63×), 8 of 19 slides overflow and
> the rest look jammed against the edges. This is NOT a P5 regression —
> the theme CSS is byte-identical between P3 and P5; P5's denser content
> made the pre-existing theme flaws visible.
>
> Comprehensive fix across four layers: theme CSS, render scripts, mermaid
> diagrams, deck content. Adds the layout/aspect-ratio/theme-structural
> tests that were missing (the gap that let this through).
>
> Tags run on the v1.21.x line (milestone v1.22 → tags v1.21.0, v1.21.1, …).

### REQ-254 — Theme CSS: section padding + overflow handling

`docs/presentations/assets/nova-sp-theme.css` adds a `section` padding
rule so content is not jammed against the slide edges. The padding
reserves space for the header (top) and footer (bottom) chrome: e.g.
`section { padding: 48px 56px 40px; }`. The theme also adds explicit
overflow handling on `section` so dense content is not silently clipped
by the marpit base `overflow:hidden` — either `overflow: auto` as an
authoring-time signal, or a documented shrink-to-fit rule. The fix does
NOT re-introduce Marp's default theme via `@import` (the theme remains
standalone); it explicitly sets the padding the default would have
provided.

### REQ-255 — Theme CSS: aspect-ratio-aware image rules

The blunt `img { max-height: 320px }` rule is replaced with an
aspect-ratio-aware rule that does not break the Marp `w:`/`h:` directives:
`img { max-width: 100%; max-height: 380px; object-fit: contain; }`. A
`.wide` / `.tall` class convention is added for diagrams (wide diagrams:
`max-height: 280px`; tall diagrams: `max-height: 480px`) so authors can
opt into the right bound per diagram instead of fighting a single blunt
rule. The `w:900` directive on a tall image (slide 9) no longer gets
silently overridden by `max-height`.

### REQ-256 — Theme CSS: title-slide chrome + spacing tightening

- `section.title header, section.title footer { display: none; }` — the
  title slide and appendix slide no longer render header/footer chrome
  that collides with content (the `<!-- _class: title -->` +
  `<!-- _paginate: false -->` directives only suppress the page number,
  not the chrome).
- `section h2 + p { margin-top: 0.2em; }` — tightens the spacing between
  the `## Slide N — Title` heading and the bold lead paragraph that
  follows it on every content slide (reclaims ~22px per slide).
- `section p { margin: 0.4em 0; }` — reduces default `<p>` margins
  (~1em top/bottom) that waste vertical space on dense slides.
- `ol` styling added (matches `ul`/`li`).
- Table cell padding reduced to `4px 8px` for tables with ≥8 rows (via
  a `table.dense` class or a `:nth-child` heuristic) so 10-13 row tables
  (slides 8, 12, A1) fit.
- `@media print` overrides added for PPTX export fidelity.

### REQ-257 — Render scripts: delete render_deck.sh + pin CLI versions

`scripts/render_deck.sh` is **deleted** (it omits `--theme`, relying on
the frontmatter `theme: nova-sp` which Marp cannot resolve as a custom
theme without `--theme-set` — it falls back to the default theme,
producing unthemed output). The README already documents
`render_slides.sh` as the canonical script. Both `render_slides.sh` and
the deleted `render_deck.sh` references are removed from any docs/tests.
`render_slides.sh` pins marp-cli and mermaid-cli to specific versions
(replace `@latest` with pinned versions) to prevent uncontrolled
boilerplate-CSS drift like the P3→P5 HTML diff.

### REQ-258 — render_slides.sh: 2x scale + transparent bg for mermaid

The mermaid-cli invocation in `scripts/render_slides.sh` (lines 51-55)
adds `-s 2 -b transparent` to match the README spec (line 193). This
produces crisp 2x PNGs with transparent backgrounds instead of the
current 1x renders (e.g. `platform-pipeline.png` is only 1568px wide
instead of the 3136px a 2x render would produce).

### REQ-259 — Re-layout telemetry-live-ops.mmd to LR

`docs/presentations/assets/mmd/telemetry-live-ops.mmd` is rewritten from
`flowchart TB` (top-bottom, produced a 1024×1628 PNG — aspect 0.63, tall)
to `flowchart LR` (left-right) with subgraph row-wrapping per the README
convention (line 168). The re-rendered PNG (at 2x transparent, per
REQ-258) has an aspect ratio in [1.2, 2.5] suitable for a 16:9 slide.
The Marp deck's `![w:900]` directive on slide 9 is updated to match the
new dimensions (or replaced with `![h:320]` if the diagram remains
taller than wide after re-layout).

### REQ-260 — Re-layout platform-pipeline.mmd to 2-row wrap

`docs/presentations/assets/mmd/platform-pipeline.mmd` is rewritten to
wrap the 10-node LR chain into 2 rows via mermaid subgraphs (or split
into two stages: static-scan row + runtime-scan row). The current
1568×116 PNG (aspect 13.52, ultra-wide/short) renders as a 1000×74px
thin strip at `![w:1000]` — node text is illegible. The re-rendered
PNG (at 2x transparent) has an aspect ratio in [1.2, 2.5] suitable for
a 16:9 slide.

### REQ-261 — Trim/split 8 overflowing slides + remove redundant header

The 8 slides identified as overflowing 720px are trimmed or split:
- **Slide 3** (Objectives + Anti-Goals): split into Slide 3a (4
  objectives) + Slide 3b (4 anti-goals). Main slide count 18 → 19.
- **Slide 5** (RACI): apply `table.dense` class (from REQ-256) to
  reduce cell padding; keep 8 rows.
- **Slide 6** (Pipeline): reduce to 3 bullets (the 4th is covered by
  the diagram, now legible after REQ-260).
- **Slide 8** (Attestation Matrix): split into Slide 8a (qa concerns,
  3 rows) + Slide 8b (prod/dr concerns, 7 rows). Main slide count
  19 → 20.
- **Slide 9** (Telemetry): reduce to 3 bullets; image now legible
  after REQ-259.
- **Slide 12** (Deferred): reduce to 6 rows (merge the 3 "Live AWS
  re-provisioning" blockers into one row).
- **Slide 15** (Quarter-by-Quarter): drop the "Grounding" column
  (redundant with the strategic objectives); 4 columns fit better.
- **Appendix A1** (Glossary): apply `table.dense` class (16px font);
  keep 13 rows.

The Marp frontmatter `header:` line is removed (keep `footer:` +
`paginate: true` only). The full 51-char deck title in BOTH header and
footer on every slide is redundant chrome that eats vertical space;
the footer alone suffices. The title slide and appendix already use
`<!-- _class: title -->` which (after REQ-256) suppresses chrome.

The talking-points file is re-distilled to match the new slide
structure (20 main + 1 appendix). The README "18 main + 1 appendix"
convention (line 130) and `test_marp_deck_slide_count` are updated to
assert 20 main + 1 appendix.

### REQ-262 — Re-render HTML + PPTX + add layout/aspect-ratio tests

- Run `bash scripts/render_slides.sh nova-autonomous-cloud-delivery` →
  re-render all mermaid PNGs (2x transparent) + HTML + PPTX. Verify
  slide count (20 main + 1 appendix = 21) and media embedding.
- Add tests to `tests/test_slides_pipeline.py`:
  - `test_theme_css_has_section_padding` — assert `section` rule
    contains `padding`.
  - `test_theme_css_suppresses_title_chrome` — assert
    `section.title header` / `section.title footer` `display: none`.
  - `test_png_aspect_ratios_sane` — for every PNG in `assets/png/`,
    assert aspect ratio ∈ [1.2, 2.5] (catches the 13.52× and 0.63×
    outliers).
  - `test_render_slides_has_2x_scale` — assert `render_slides.sh`
    contains `-s 2` and `-b transparent`.
  - `test_render_deck_removed` — assert `render_deck.sh` does not
    exist.
  - `test_html_embeds_theme` — assert committed HTML contains
    `--sp-red` and `padding` in the inline `<style>`.
  - `test_html_slide_count_matches_marp` — parse HTML `<section>`
    count == marp deck slide count.
- Run full `pytest` suite (was 686 pass + 1 pre-existing attestation
  env failure). `run_platform.sh --check-only` exits 0.
- Milestone ship: tag the final phase on the v1.21.x line; create a
  release; attach the PPTX.

### Out of Scope (v1.22)

- **Deck narrative changes** — the 4-beat arc (Problem → Solution →
  Proof → Roadmap + Ask) and slide content are unchanged except for
  the trim/split needed to relieve overflow.
- **Re-introduction of badges, version strings, or internal citations**
  — v1.21 removed these; v1.22 does not re-add them.
- **Live pilot estate activation** — still deferred.
- **ML anomaly-forecasting service** — still deferred.
- **Multi-cloud (Azure/GCP) implementation** — still deferred.
- **Tamper-evident ledger (S3 Object Lock + JWS)** — still deferred.

### v1.22 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-254 | P1 | complete |
| REQ-255 | P1 | complete |
| REQ-256 | P1 | complete |
| REQ-257 | P2 | complete |
| REQ-258 | P2 | complete |
| REQ-259 | P3 | complete |
| REQ-260 | P3 | complete |
| REQ-261 | P4 | complete |
| REQ-262 | P5 | complete |

## v1.23 — Nova Deck Cleanup & Python PPTX

> **NFR milestone** (docs/render/test only; no features). Tags run on the
> **v1.22.x** line (milestone v1.23 → tags v1.22.0..v1.22.6). Final patch
> `v1.22.6` = milestone release.
>
> Consolidates the deck to a single source-of-truth markdown document,
> restores the clean S&P visual style (Marp `default` theme + inline
> `style:` block, matching the old `the-developer-experience.html`),
> embeds images as base64 in the HTML for redistribution, builds a
> parallel structured python-pptx PPTX generator, and trims verbose
> slides. `nova-sp-theme.css` is retained as a styling reference but
> retired from the render path.

### Category: Consolidate Docs
- **REQ-263:** `nova-autonomous-cloud-delivery-marp.md` becomes the sole
  source of truth. Speaker notes + talking points from the plain `.md`
  are folded into the deck as Marp HTML comments (`<!-- Speaker notes:
  ... -->`, `<!-- Talking points: ... -->`). The plain
  `nova-autonomous-cloud-delivery.md` is deleted.
- **REQ-264:** `nova-autonomous-cloud-delivery-talking-points.md` is kept
  as a standalone presenter aid, synced from the deck's `<!-- Talking
  points: -->` comments. Header note documents the mirror relationship.

### Category: Restore Clean Style
- **REQ-265:** Revert deck frontmatter `theme: nova-sp` → `theme:
  default` and add an inline `style:` block porting the S&P visual
  language (palette #D6002A/#1B1B1B, Akkurat Pro font, black title slide
  with red top border, tables, blockquotes, code, aspect-ratio-aware
  images). Keep current structure (H2 + bold-lead, no header, no badges).
- **REQ-266:** `nova-sp-theme.css` is retained as a styling reference
  (header comment documents its retired status). `render_slides.sh`
  drops the `--theme "$THEME_CSS"` argument; the inline `style:` block in
  frontmatter is the sole styling source at render time.
- **REQ-267:** Benefit callouts on every slide are restyled: the
  `**Benefit:**` prefix is removed; the callout becomes a styled
  element (red top-rule + black italic text; white on title slides)
  using a `.benefit` class in the inline style block.

### Category: Inline Images
- **REQ-268:** New `scripts/inline_images.py` (stdlib only: `base64`,
  `re`, `mimetypes`) post-processes the rendered HTML: finds all
  `<img src="assets/...">` relative paths, replaces each `src` with a
  `data:image/<mime>;base64,...` URI. HTML becomes self-contained
  (redistributable without the `assets/` folder). `render_slides.sh`
  invokes it after the MARP HTML render, before staging.

### Category: Python PPTX Generator
- **REQ-269:** New `scripts/render_pptx.py` parses the consolidated
  `*-marp.md` and produces a structured, editable, S&P-themed PPTX
  (`nova-autonomous-cloud-delivery-python.pptx`) using `python-pptx`.
  16:9 slides; title slide (black bg, red top bar, white H1); content
  slides (red H2 title, bold lead, bullets, blockquote, embedded PNGs,
  native PPTX tables, benefit callouts). HTML-comment speaker
  notes/talking points are skipped. `python-pptx` added to
  `pyproject.toml`. `render_slides.sh` invokes it as a new step.
- **REQ-270:** Both PPTX outputs (MARP image-of-slide + python
  structured) are produced by `render_slides.sh` and staged. CI
  workflows install `python-pptx` and commit both. `attach_release_asset.py`
  attaches both to the release.

### Category: Trim Word Count
- **REQ-271:** Targeted word-count trim on ~8 verbose slides (1, 5, 7,
  8, 13, 14, 20, plus the appendix) — ~20-30% reduction on trimmed
  slides. Tables and short slides are untouched. The spirit of each
  slide is preserved.
- **REQ-272:** The term "penetrate" (and derivatives) is removed from
  all presentation files. Slide 5's "Nova never penetrates it" phrase is
  removed with no replacement (slide 4 Anti-Goals already excludes the
  PDLC from scope). `grep -ri penetrat docs/presentations/` returns
  nothing.

### Category: CI, Tests, README
- **REQ-273:** CI workflows (`workflows-src/slides.yml` + synced
  `.github`/`.gitea` copies) install `python-pptx`, run `render_slides.sh`
  (which produces HTML + both PPTX + inlined images), and commit all
  rendered artifacts. README documents the new pipeline.
- **REQ-274:** `tests/test_slides_pipeline.py` is updated for the
  consolidated doc (no plain `.md`), default theme + inline style
  assertions (S&P visual properties, not theme filename),
  `nova-sp-theme.css` retained-as-reference assertion, image-inlining
  assertions (zero `src="assets/` references, ≥1 base64 per image),
  python-pptx output existence, benefit callout class, "penetrate"
  absence. New `tests/test_pptx_generator.py` asserts slide count, title
  colors, table rendering, image embedding.
- **REQ-275:** `docs/presentations/README.md` is rewritten to document
  the single-document process (author `*-marp.md` → render HTML + both
  PPTX → talking points mirrored), inline `style:` approach,
  `nova-sp-theme.css` reference status, image inlining, and dual PPTX
  output.

### Out of Scope (v1.23)

- **Deck narrative / 4-beat arc changes** — the Problem → Solution →
  Proof → Roadmap + Ask structure is unchanged; only word count is
  trimmed.
- **Re-introduction of badges, version strings, or internal citations**
  — v1.21 removed these; v1.23 does not re-add them.
- **Removal of MARP** — MARP stays for HTML + PPTX; python-pptx runs in
  parallel for comparison.
- **Removal of orphaned `developer-experience-*` assets** — deferred to
  a future cleanup phase (optional in v1.23 Phase 6 only if time
  permits).
- **Mermaid render scoping** — the mermaid render step continues to
  render all `.mmd` files; scoping to referenced-only is deferred.

### v1.23 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-263 | P1 | complete |
| REQ-264 | P1 | complete |
| REQ-265 | P2 | complete |
| REQ-266 | P2 | complete |
| REQ-267 | P2 | complete |
| REQ-268 | P3a | complete |
| REQ-269 | P3b | complete |
| REQ-270 | P3b | complete |
| REQ-271 | P4 | complete |
| REQ-272 | P4 | complete |
| 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 | complete |
| REQ-277 | P1 | complete |
| REQ-278 | P1 | complete |
| REQ-279 | P1 | complete |
| REQ-280 | P1 | complete |
| REQ-281 | P1 | complete |
| REQ-282 | P2 | complete |
| REQ-283 | P2 | complete |
| REQ-284 | P2 | complete |
| REQ-285 | P2 | complete |
| REQ-286 | P2 | complete |
| REQ-287 | P2 | complete |
| REQ-288 | P3 | complete |
| REQ-289 | P3 | complete |
| REQ-290 | P1 | complete |

## v1.25 — kyverno-json Unified Policy Engine

> **Feature milestone.** `kyverno-json` becomes the primary compliance /
> policy tool, implemented behind a swappable `PolicyEngine` adapter so
> OPA (or any other engine) can replace it one day. Tags run on the
> **v1.24.x** line (milestone v1.25 → tags v1.24.0..v1.24.N). Final patch
> = milestone release.
>
> One problem, one architectural correction:
> 1. **Fragmented policy posture.** Nova's compliance rules are split
>    across Checkov (imperative YAML + a Python custom rule for tagging),
>    Wiz (API findings), the K8s-only Kyverno adapter (inactive for
>    Terraform stacks — D-053), and imperative Python in
>    `core/env_transition.py` + `core/regression_verify.py`. There is no
>    single declarative place where "what Nova considers compliant" lives.
>    The K8s Kyverno adapter can't help because it only speaks to K8s
>    manifests, and the platform emits Terraform.
>
> The correction: `kyverno-json` (a Kyverno-ecosystem runtime that applies
> Kyverno policies to **any** JSON/YAML payload) becomes the **unified
> orchestrator** of compliance checks. Checkov and Wiz remain as
> raw-finding adapters feeding *into* kyverno-json meta-policies. The
> engine is behind a `PolicyEngine` protocol so it is replaceable. The
> confidence signal is untouched — it already consumes
> `list[PolicyCheckResult]` engine-agnostically.

### Decisions (locked in CLARIFY, full autonomy)

- **D-115 (C-1):** `kyverno-json` is a runtime dependency installed via
  `go install github.com/kyverno/kyverno-json/cmd/kj@latest` (pinned in a
  `scripts/install-kyverno-json.sh` helper; the CI image installs it).
  Not a Python package — kyverno-json is a Go binary. The
  `KyvernoJsonEngine.is_configured()` checks `which kj` and skips
  gracefully when absent (emits `SKIPPED` PCR, mirroring the Wiz adapter).
- **D-116 (C-2):** kyverno-json PCR records carry `engine: "kyverno"`
  (no new enum value). The existing `engine` enum in
  `schemas/policy_check_result.schema.json` already includes `"kyverno"`;
  adding `"kyverno-json"` would force a schema change + checkov_adapter
  test regression for no semantic gain. The `ruleId` prefix `KJ_`
  distinguishes kyverno-json rules from the K8s Kyverno adapter's
  `KYVERNO_` prefix where they overlap.
- **D-117 (C-3):** Checkov and Wiz adapters keep their current
  `adapt() -> list[PolicyCheckResult]` signatures. They emit PCRs as
  today. The meta-policies in `adapters/kyverno-json/policies/meta/`
  consume the **merged** PCR list (checkov + wiz + kyverno-json) as their
  input payload, applying Nova-specific posture rules on top. No adapter
  signature changes.
- **D-118 (C-4):** `NOVA_TAG_NAMING` (the Checkov custom rule in
  `adapters/terraform/policy/custom_rules/nova_tagging.py`) is **kept**.
  A kyverno-json mirror policy `require-tagging-standard.json` is added
  in `adapters/kyverno-json/policies/stack-ir/`. The P3 meta-policy
  `tagging-rules-agree.json` asserts the two engines agree on every
  resource; divergence emits an `error` PCR (defense-in-depth against
  rule drift). The Checkov rule stays the source of truth for
  Terraform-static scanning; the kyverno-json policy covers Stack IR.

### Category: Policy Engine Core (feat)
- **REQ-291:** `core/policy_engine.py` defines a `PolicyEngine` Python
  `Protocol` (PEP 544) with three members: `name -> str`,
  `is_configured() -> bool`, and
  `evaluate(payload: dict | str, policy_dir: Path, contract_id: str) ->
  list[dict]` (where each dict conforms to
  `schemas/policy_check_result.schema.json`). A `PolicyEngineRegistry`
  singleton selects the active engine from `config.json`'s new
  `policy.engine` key (default `"kyverno-json"`); raises
  `KeyError` on an unknown engine name. The registry exposes
  `get_engine()` and `register(name, factory)`. Pure stdlib, no engine
  imports at the protocol layer.
- **REQ-292:** `.ciagent/config.json` gains a new top-level `policy`
  object: `{"engine": "kyverno-json", "policy_root":
  "adapters/kyverno-json/policies"}`. The registry reads `policy.engine`
  to select the active engine and `policy.policy_root` as the default
  policy directory. Backward-compatible: if the `policy` key is absent,
  the registry returns a `NullEngine` that emits only `SKIPPED` records
  (so existing tests that don't set the key still pass).

### Category: kyverno-json Engine Adapter (feat)
- **REQ-293:** `adapters/kyverno-json/kyverno_json_engine.py` implements
  `KyvernoJsonEngine` satisfying the `PolicyEngine` protocol.
  `is_configured()` returns `True` when `which kj` succeeds. `evaluate()`
  writes the payload to a temp JSON file, invokes
  `kj scan --policy <policy_dir> --payload <payload.json> -o json`,
  parses the native result list, and translates each entry to a PCR dict
  (`engine: "kyverno"`, `ruleId` prefixed `KJ_<policy_name>`, severity
  mapped, `result` mapped pass/fail/skip → pass/fail/skipped). When
  `is_configured()` is false, `evaluate()` returns a single `SKIPPED`
  PCR with `ruleId: "KJ_ENGINE_NOT_CONFIGURED"` (mirrors the Wiz
  adapter's `is_configured()` guard). Native output parsing is
  defensive: any kyverno-json output that doesn't match the expected
  shape produces an `error` PCR, never an exception.
- **REQ-294:** `adapters/kyverno-json/__init__.py` exports
  `KyvernoJsonEngine`. `adapters/kyverno-json/policies/_smoke.json`
  is a single trivial policy (`require-contract-id`) used to validate
  the engine round-trip end-to-end in tests. `scripts/install-kyverno-json.sh`
  runs `go install github.com/kyverno/kyverno-json/cmd/kj@latest` and
  prints `kj version`; documented in `adapters/kyverno-json/README.md`.
  The CI image (`.github/workflows/ci.yml` + `.gitea/workflows/ci.yml`)
  installs Go + kj when `policy.engine == "kyverno-json"`; the install
  is cached.

### Category: Contract Policies (feat)
- **REQ-295:** `adapters/kyverno-json/policies/contract/` holds
  kyverno-json policies over consumer contract JSON. Four policies
  mirroring `schemas/contract.schema.json` constraints:
  `require-id-pattern.json` (`id` matches `^[a-z][a-z0-9-]{2,5}$`),
  `require-env-in-enum.json` (`environment` in dev/qa/prod/dr),
  `require-infrastructure-min-1.json` (`infrastructure` has ≥1 entry),
  `forbid-unknown-fields.json` (only `id`/`name`/`environment`/
  `infrastructure` allowed). Each policy is a single Kyverno `Policy`
  resource with one `validate.assert` rule using JMESPath against the
  payload root. Policies are the declarative equivalent of the
  jsonschema `required`/`pattern`/`enum` constraints — they let Nova
  apply its own compliance posture on top of schema validity.
- **REQ-296:** `core/contract_resolver.py` invokes the
  `PolicyEngineRegistry.get_engine().evaluate()` with the contract dict
  and `policies/contract/` **before** resolving (early-fail on contract
  violations) and emits a `nova.policy.evaluated` metrics event (engine
  name in the event payload). Failures feed the confidence signal's
  `policy` input as `fail` PCRs; the resolver does not exit — the
  confidence signal decides the gate (consistent with the existing
  `--soft-fail` Checkov pattern).

### Category: Stack-IR Policies (feat)
- **REQ-297:** `adapters/kyverno-json/policies/stack-ir/` holds policies
  over the resolved Target Stack IR dict. `require-tagging-standard.json`
  — every resource carries `nova:owner` + `nova:environment` tags
  (ports `adapters/terraform/policy/custom_rules/nova_tagging.py` logic
  into a declarative Kyverno policy over the IR's `resources[]` array;
  mirrors the v1.8 D-tagging-standard). `forbid-public-ingress.json` —
  no resource has `public_ingress: true` (the v1.0 demo rule, now
  declarative). `require-encryption-by-default.json` — every S3 bucket
  + EBS volume + KMS-aliased resource carries encryption config (ports
  the v1.8 D-encryption-default rule).
- **REQ-298:** `core/contract_resolver.py` invokes the engine with the
  resolved Stack IR and `policies/stack-ir/` **after** resolving. The
  resulting PCRs are appended to the contract-policy PCRs and fed to the
  confidence signal. The resolver's existing `tests/test_contract_resolver.py`
  continues to pass (the policy call is additive — it does not change
  resolver return values or exceptions).
- **REQ-299:** `tests/test_stack_ir_policies.py` + fixture
  `tests/fixtures/stack_ir/` — a passing IR (all tags + encryption) and
  a failing IR (missing tags, public ingress, plaintext bucket). Each
  policy is tested in isolation + the full `policies/stack-ir/` dir as a
  bundle. Tests run the `KyvernoJsonEngine` against real `kj` when
  `which kj` succeeds, and skip with a `pytest.skip("kj not installed")`
  when absent (so CI without the binary doesn't fail).

### Category: Plan-JSON Policies + Pipeline Wiring (feat)
- **REQ-300:** `adapters/kyverno-json/policies/plan-json/` holds policies
  over `terraform show -json` output. `forbid-plaintext-secrets.json`
  (ports `CKV_AWS_41/45/46` — no `aws_db_instance.password` /
  `aws_iam_user.*` plaintext). `forbid-iam-wildcard.json` (ports
  `CKV_AWS_1/40` — no `Action: "*"` or `Resource: "*"` in IAM policies).
  `require-kms-reference.json` (ports `CKV_AWS_7/33` — KMS keys referenced
  by alias, not inline). Each policy uses JMESPath over the plan's
  `planned_values.root_module.resources[]` array. The Checkov `RULE_MAP`
  in `checkov_adapter.py` is unchanged — these are declarative mirrors,
  not replacements.
- **REQ-301:** `run_platform.sh` Step 5 ("runtime policy scan") gains a
  parallel kyverno-json pass: after Checkov/Wiz produce raw PCRs, the
  script runs `kj scan --policy adapters/kyverno-json/policies/plan-json/
  --payload <tfshow.json> -o json` and pipes through
  `adapters/kyverno-json/kyverno_json_engine.py` to produce a second PCR
  list. Both lists are concatenated and fed to the confidence signal's
  `policy` input. The script emits a `nova.policy.evaluated` event with
  both engine names. When `which kj` is false, the script logs
  "kyverno-json not installed; skipping plan-json policies" and proceeds
  with the Checkov/Wiz list only (no hard failure — the platform
  functions without kj).
- **REQ-302:** `tests/test_plan_json_policies.py` + fixture
  `tests/fixtures/plan_json/` — a passing plan JSON (no secrets, no
  wildcard, KMS alias) and a failing plan JSON (plaintext password,
  `Action: "*"`, inline KMS key). Tests the three policies in isolation
  + as a bundle. `tests/test_run_platform_plan_json_policies.py`
  asserts `run_platform.sh` has the kyverno-json Step 5 block and that
  it concatenates PCR lists (pattern from `tests/test_pipeline.py:79-95`
  — read script text + assert substrings).

### Category: Meta-Policies (feat)
- **REQ-303:** `adapters/kyverno-json/policies/meta/` holds policies
  whose **payload** is the merged `list[PolicyCheckResult]` itself.
  `block-on-any-critical.json` — asserts no PCR in the list has
  `severity: "critical"` + `result: "fail"`; if any does, the meta-policy
  emits a `fail` PCR with `ruleId: "KJ_META_BLOCK_CRITICAL"` and
  severity `critical`. This is the **declarative** source of truth for
  "critical = block"; the `confidence_signal.py` `PENALTY["critical"]:
  None` hard-override stays as defense-in-depth (D-118-adjacent
  decision). `tagging-rules-agree.json` — for every resource in the
  Stack IR, asserts the Checkov `NOVA_TAG_NAMING` result and the
  kyverno-json `KJ_REQUIRE_TAGGING_STANDARD` result agree; divergence
  emits an `error` PCR. `tests/test_meta_policies.py` covers both.

### Category: Regression-Gate Policies (feat, quality improvement from IDEATE)
- **REQ-304:** `adapters/kyverno-json/policies/regression/` holds
  policies over the capability-inventory JSON frontmatter
  (`CAPABILITY_INVENTORY.md` parsed as structured data). Three policies
  port the imperative checks in `core/regression_verify.py`:
  `cap-013-adapter-dedup.json` (no duplicate adapter registrations),
  `cap-023-metrics-collector.json` (every metric in `docs/METRICS.md`
  has a grounded/derived/deferred status), `cap-024-deck-structure.json`
  (deck slide structure matches the documented arc). The policies read
  the parsed capability inventory as payload and emit `pass`/`fail` PCRs
  per capability. The existing `core/regression_verify.py` is **kept**
  (it drives the CI gate); the policies are the **declarative mirror**
  that makes capability regression auditable as a policy artifact, not
  imperative Python. Future milestones may switch the gate to the
  policy version.
- **REQ-305:** `tests/test_regression_policies.py` + fixture
  `tests/fixtures/capability_inventory.json` — a clean inventory (all
  caps pass) and a drifted inventory (duplicate adapter, missing metric
  status, broken deck arc). The regression gate (`pytest` suite)
  continues to pass 287/287 (or new count); the new policy tests are
  additive.

### Category: Documentation (docs)
- **REQ-306:** `adapters/README.md` gains a new row for the
  `kyverno-json` adapter + a new section "Policy Engine Protocol"
  documenting the `PolicyEngine` Protocol, the registry, and the
  swap boundary (how to add an `OpaEngine`). `adapters/kyverno-json/README.md`
  documents the engine, the install path, the policy directory layout,
  and the four policy categories (contract/stack-ir/plan-json/meta).
- **REQ-307:** `.ciagent/ARCHITECTURE.md` gains §12.7 "Policy Engine
  Registry" with the registry diagram (engine ↔ protocol ↔ registry ↔
  config.json ↔ confidence signal). `schemas/README.md` notes the
  `engine: "kyverno"` value is shared by the K8s Kyverno adapter and the
  kyverno-json engine (distinguished by `ruleId` prefix). `modules/STANDARDS.md`
  gains a "Policy authoring standard" section for module owners who want
  to ship per-module kyverno-json policies. `docs/METRICS.md` notes the
  policy engine is now swappable (Strategic Objective #2 — provable
  trust via a replaceable substrate, not a vendor lock-in).

### Category: Tests (test)
- **REQ-308:** `tests/test_policy_engine.py` — protocol conformance
  (the registry returns an engine implementing all three methods),
  unknown-engine `KeyError`, `NullEngine` fallback when the `policy`
  key is absent, `KyvernoJsonEngine.is_configured()` returns false when
  `which kj` fails (mocked). `tests/test_kyverno_json_engine.py` —
  `evaluate()` returns valid PCR dicts against
  `schemas/policy_check_result.schema.json` (validated with
  `jsonschema`); native-output parsing is defensive (malformed kyverno-json
  output → `error` PCR, not exception); `is_configured()==false` →
  `SKIPPED` PCR with `KJ_ENGINE_NOT_CONFIGURED`.
- **REQ-309:** All new tests use `pytest.skip("kj not installed")` when
  `which kj` is absent, so the suite passes in environments without the
  binary (CI matrix: with-kj and without-kj). The full suite
  (`pytest tests/`) continues to pass at 287/287 baseline + new tests
  (the new tests skip without kj, so the count grows only when kj is
  installed). `pyproject.toml` + `requirements-test.txt` unchanged
  (kyverno-json is a Go binary, not a Python dep).

### Out of Scope (v1.25)
- **Removing Checkov or Wiz.** Both stay as raw-finding adapters. The
  unified-orchestrator model layers kyverno-json on top, not in place of.
- **`OpaEngine` implementation.** The protocol is the swap boundary;
  the OPA implementation is a future milestone. RESEARCH documents the
  OPA-equivalent surface so the swap is a known quantity.
- **Per-module policies.** `modules/<name>/policies/` is documented as
  the future pattern in `modules/STANDARDS.md` but not populated this
  milestone (policies live under `adapters/kyverno-json/policies/`
  for v1.25).
- **kyverno-json as a long-running service.** v1.25 uses the CLI
  (`kj scan`); the `kj serve` web-app mode is a future consideration
  for lower-latency evaluation (RESEARCH notes it).
- **Replacing the K8s Kyverno adapter.** The K8s adapter
  (`adapters/kyverno/`) remains documentation-only (D-053 — platform
  emits Terraform). The kyverno-json engine and the K8s adapter are
  siblings, not replacements.

### v1.25 Traceability

| REQ | Phase | Status |
|-----|-------|--------|
| REQ-291 | P1 | complete |
| REQ-292 | P1 | complete |
| REQ-293 | P1 | complete |
| REQ-294 | P1 | complete |
| REQ-295 | P2 | complete |
| REQ-296 | P2 | complete |
| REQ-297 | P2 | complete |
| REQ-298 | P2 | complete |
| REQ-299 | P2 | complete |
| REQ-300 | P3 | complete |
| REQ-301 | P3 | complete |
| REQ-302 | P3 | complete |
| REQ-303 | P3 | complete |
| REQ-304 | P4 | complete |
| REQ-305 | P4 | complete |
| REQ-306 | P4 | complete |
| REQ-307 | P4 | complete |
| REQ-308 | P1 | complete |
| REQ-309 | P1 | complete |
