--- phase: P0 name: pre-execution milestone: v1.16 requirements: [REQ-165, REQ-166, REQ-167, REQ-168, REQ-169, REQ-170, REQ-171, REQ-172, REQ-173, REQ-174, REQ-175, REQ-176, REQ-177, REQ-178, REQ-179, REQ-180, REQ-181, REQ-182, REQ-183, REQ-184] wave: 0 depends_on: [] --- # v1.16 — Nova Simplification Plan (20 execution phases + 1 final) **Milestone:** v1.16 (Nova Simplification — NFR) **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, No Humans Onboarding Flow. Clears the fresh debt the v1.15 rebrand left, delivers genuine simplification, and implements the first self-service onboarding request path (request-path only; real AWS provisioning deferred, D-113). ## Wave ordering - **Wave 1 (P1–P4): correctness + brand regression fixes.** P1 first — the state-bucket drift (`adapter.py:117` emits `acdl-tfstate-*` while the live bucket is `nova-tfstate-*`) and the Kyverno policy contradiction (enforces `acdl:*` labels that `nova_tagging.py` hard- fails) are the highest-severity findings, both correctness regressions left by the rebrand. P2–P4 independent brand/dead-code/except work. - **Wave 2 (P5–P9): simplify without regressions.** P5 before P6/P9 (regression-verify dedup is independent; P6/P9 both touch `run_platform.sh`). P8 changes the workflow byte-identity test → generator (D-115). P9 must run the regression gate (D-118) at the end of Wave 2 — 22/22 capabilities must stay Verified. - **Wave 3 (P10–P14): security + maintainability.** P10 before P11 (identity enforcement before payload validation). P12/P13 independent file splits. P14 mid-milestone checkpoint (offline) at end of Wave 3. - **Wave 4 (P15–P17): developer experience.** Independent; P17 last (reflects the consolidated path after P15/P16 land). - **Wave 5 (P18–P20): no-humans onboarding (request-path only).** P18 (schema + Lambda action) before P19 (env-file autogen consumes the schema) before P20 (cross-account role, offline-proven per D-114). - **Final (P21): review + audit + milestone ship.** ## Execution approach - **Per-phase ship:** each execution phase merges `phase/NN-*` → `milestone/v1.16-nova-simplification` and tags a patch on the v1.15.x line (`v1.15.6` = P1 ... `v1.15.26` = P21). - **Verification:** 4-layer verify (structural/behavioral/security/ quality) per phase; the regression gate (D-091, 22 capabilities) runs at P9 (end of Wave 2) and P21 (milestone complete) per D-118. - **No live AWS:** `NOVA_LIFECYCLE_MODE=plan` default; terraform changes validated via `terraform validate` + `--check-only`. P20 cross-account Terraform is offline-proven only (D-114). - **Test discipline:** each phase that changes runtime code adds/updates tests; `bash scripts/run_ci.sh` exits 0 at every phase boundary. ## Wave 1 — Correctness + Brand Regression Fixes (P1–P4) ### Phase P1 — state-bucket-and-kyverno-rebrand-fix (REQ-165) - **Lead:** backend-engineer; **Contributor:** data-engineer (kyverno) - **Must-haves:** - `adapters/terraform/adapter.py:117` `state_bucket = f"acdl-tfstate-{account_id}-us-east-1"` → `f"nova-tfstate-{account_id}-us-east-1"`. - `adapters/kyverno/policies/require-resource-labels.yml`: annotation title `Require ACDL Resource Labels` → `Require Nova Resource Labels`; rule names `require-acdl-owner-label`/`require-acdl-environment-label` → `require-nova-owner-label`/`require-nova-environment-label`; messages + patterns `acdl:owner`/`acdl:environment` → `nova:owner`/ `nova:environment`. - Update any test fixtures referencing the old bucket name / label keys. - **Verify:** `terraform validate` (adapter-emitted); pytest passes; `run_ci.sh` exits 0; regression gate 22/22 (run at P9, but P1 must not break any cap locally). ### Phase P2 — user-facing-acdl-to-nova-sweep (REQ-166) - **Lead:** lead-developer; **Contributor:** backend-engineer - **Must-haves:** - `core/environment_check.py:59,61` onboarding message header/body "ACDL" → "Nova". - `core/lambda/contract_ingestor.py:145` alert title `[ACDL-ALERT]` → `[NOVA-ALERT]`; `:191` issue body "ACDL platform Lambda" → "Nova platform Lambda". - `scripts/post_stage_comment.sh:39` PR comment header "ACDL Stage" → "Nova Stage"; `:46` footer "ACDL deploy pipeline" → "Nova deploy pipeline". - `scripts/run_ci.sh:39` CI banner "ACDL CI Pipeline" → "Nova CI Pipeline". - Module docstrings: `core/contract_resolver.py:1,474`, `core/confidence_signal.py:1`, `adapters/terraform/adapter.py:1`, `adapters/kyverno/kyverno_adapter.py:1`, `adapters/wiz/wiz_adapter.py:1`, `adapters/README.md:1`, `adapters/kyverno/README.md:4,18` → Nova. - Update tests that assert these strings. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P3 — dead-code-and-stale-prefix-cleanup (REQ-167) - **Lead:** lead-developer - **Must-haves:** - `scripts/run_platform.sh:153` remove the dead `export ACDL_ENVIRONMENT_OVERRIDE=...` line (comment says "removed in P5" but the line is present). - Stale dual-read comments: drop the "ACDL_* fallback until P5" / "dual-read NOVA_* first, ACDL_* fallback per G-106" comments in `core/local_emulators.py:15-16,503,505`, `core/regression_verify.py:318-319,333`, and the lifecycle scripts (the G-106 fallback is retired per `core/env.py:4-5`). - `acdl_*` temp-dir prefixes → `nova_*`: `core/local_emulators.py:71,252` (`acdl_outbox_`/`acdl_tfstate_`), `core/regression_verify.py:183,234` (`acdl_regr_`/`acdl_outbox_`), `scripts/run_pattern_plan.sh:29`, `scripts/run_primitive_plan.sh:29`, `scripts/run_lifecycle_test.sh:41`, `scripts/run_lifecycle_destroy.sh:36`. - `core/regression_verify.py:214` interpolation fixture `acdl-` → `nova-` (or make it a clearly-generic token). - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P4 — migrate-ssm-except-narrowing (REQ-168) - **Lead:** backend-engineer - **Must-haves:** - `scripts/migrate_ssm_paths.py:113` `except Exception: pass` → narrow to `ParameterNotFound` + structured log on the non- ParameterNotFound path. - Narrow `core/output_publisher.py:112,182` `except Exception` → specific `(ClientError, OSError)` + structured stderr log. - Test that a non-ParameterNotFound error is raised (not swallowed). - **Verify:** pytest passes; `run_ci.sh` exits 0. ## Wave 2 — Simplify Without Regressions (P5–P9) ### Phase P5 — regression-verify-dedup (REQ-169) - **Lead:** backend-engineer - **Must-haves:** - Extract `_check_live_terraform_plan(contract_path, label)` from the two ~95% identical methods `_check_live_terraform_plan_microservice` + `_check_live_terraform_plan_static_assets` (~35 lines saved). - Extract `_check_resolver(contract_path)` from `_check_resolver_static_assets` + `_check_resolver_microservice`. - Extract `_assert_contracts_resolve(module_dir)` from the duplicated lifecycle-contract-resolve block in `_check_lifecycle_module_terraform` + `_check_lifecycle_l2_module`. - Behavior preserved (the regression gate output is unchanged). - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P6 — run-platform-deadcode-and-hitl-fn (REQ-170) - **Lead:** lead-developer - **Must-haves:** - Extract the duplicated HITL attestation block (`:336-350` + `:452-466`) into a shell function `run_hitl_gate()` invoked at both sites (~14 lines saved). - `scripts/run_platform.sh:145` hardcoded `CONTRACT_ID` UUID → `NOVA_CONTRACT_ID` env with the existing UUID as default. - `scripts/run_platform.sh:146` `WORK="/tmp/acdl_platform_run_v18"` → `WORK="${NOVA_WORK_DIR:-/tmp/nova_platform_run}"` (drop the stale `v18` stamp + `acdl_` prefix). - Drop the stale brand comment `run_platform.sh:2` "the ACDL platform pipeline" → "the Nova platform pipeline". - **Verify:** pytest passes; `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0. ### Phase P7 — contract-resolver-envloader-and-kind (REQ-171) - **Lead:** backend-engineer - **Must-haves:** - `core/contract_resolver.py:50-68` `_load_env` → import `core/environment_check.py:load()` (dedup; both load + placeholder warning). - Add a `kind` field (`"l1"` / `"l2"`) to each `modules/registry.json` entry; the resolver reads `kind` directly instead of the fragile `is_l2 = "l2" in interface_path or "composition" in interface_path` heuristic (`contract_resolver.py:540`). - Collapse the redundant `kind` computation (`:584-589`) → `kind = "l2" if (multi_module or any_l2) else "l1"` (after the registry `kind` field is authoritative, simplify further). - **Verify:** pytest passes; `run_ci.sh` exits 0; resolver behavior unchanged (all contracts still resolve to the same stacks). ### Phase P8 — workflow-generator-dedup (REQ-172) - **Lead:** lead-developer; **Contributor:** backend-engineer (test) - **Must-haves:** - Author `scripts/sync_workflows.py` — reads one source workflow per pair (e.g. `workflows-src/ci.yml`, `workflows-src/deploy.yml`, `workflows-src/modules-lifecycle.yml`) and writes byte-identical copies to both `.gitea/workflows/` and `.github/workflows/`. Establish the `workflows-src/` dir as the single source. - Replace the byte-identity assertions in `tests/test_pipeline_contract.py` with a "generated outputs match committed files" test (run `sync_workflows.py --check` → exit 0 if the committed files match the generated output, non-zero + diff if drift). - Migrate the 3 existing pairs to the `workflows-src/` source; remove the hand-maintained duplicates (the generator owns them). - **Verify:** `python3 scripts/sync_workflows.py --check` exits 0; pytest passes; `run_ci.sh` exits 0; the 4 GitHub-only workflows are untouched (they have no pair). ### Phase P9 — run-platform-split (REQ-173) - **Lead:** lead-developer - **Must-haves:** - Extract the decommission block (`scripts/run_platform.sh:180-237`) into `scripts/run_decommission.sh` (sourced or invoked). - Extract the uptime block (`:520-606`) into `scripts/run_uptime.sh`. - `run_platform.sh` invokes the helpers; behavior unchanged. - **G-112 binding:** the helpers are **`source`d** (shared shell env), not invoked as subshells — the extracted blocks reference `run_platform.sh`-local vars (`NOVA_CONTRACT_ID`/`NOVA_WORK_DIR` from P6); a subshell would not inherit them. - **G-111 binding:** update `core/regression_verify.py` CAP-015/016 checks — when the live resource is absent (`ResourceNotFoundException`/`404`), mark `Skipped (post-teardown, D-096)` not `Decayed`, so a clean local run reports 20/20 Verified + 2 Skipped (not a strict-`all` failure on the known teardown state). - **Run the regression gate (D-118, end of Wave 2):** **20/22 Verified** is the passing bar (CAP-015/016 Skipped — post-v1.11-teardown steady state, D-096; re-provisioning is a future feature, not an NFR). Any non-Verified/non-Skipped capability halts Wave 3. - **Verify:** pytest passes; `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0; **regression gate 20/22 Verified + 2 Skipped**. ## Wave 3 — Security + Maintainability (P10–P14) ### Phase P10 — contract-ingestor-defense-in-depth (REQ-174) - **Lead:** backend-engineer; **Contributor:** lead-developer (review) - **Must-haves:** - `core/lambda/contract_ingestor.py:251-252` `if not caller_arn: pass` → fail closed: return a 401/403 with a clear message when IAM identity is absent (defense-in-depth; ABAC layer still the primary control). - `core/lambda/contract_ingestor.py:269` hardcoded `valid_envs = {"dev","qa","prod","dr"}` → derive from the `core/environments/` directory (list `*.json` filenames). - Document the ABAC reliance explicitly in the function docstring + ARCHITECTURE.md. - Test: a request without IAM identity is rejected; a request with an unknown environment is rejected. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P11 — contract-ingestor-payload-validation (REQ-175) - **Lead:** backend-engineer - **Must-haves:** - `submit_contract`: size-cap the `contract` blob (e.g. 256 KB) before the DynamoDB write; reject oversized payloads with 413. - Schema-validate the contract blob against `schemas/contract.schema.json` before the write; reject invalid with 400. - Consistent caps: `error` and `stackTrace` use the same cap (align the 10k vs 2k inconsistency). - Tests for size-limit + schema-rejection paths. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P12 — split-contract-resolver (REQ-176) - **Lead:** backend-engineer - **Must-haves:** - Split `core/contract_resolver.py` (638 lines) into: `core/contract_resolve.py` (the resolve + interpolation core), `core/decommission_transform.py` (the decommission zero-counts transform), `core/contract_resolver_cli.py` (the `__main__` CLI). - `core/contract_resolver.py` becomes a thin re-export shim for backwards compat (existing imports keep working). - **G-113 binding:** import direction is one-way — split modules import only each other + stdlib; the re-export shim imports the split modules; nothing imports the shim except external callers (prevents the latent cycle shim → split → split → shim). - Behavior unchanged; all tests pass without modification. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P13 — split-regression-verify (REQ-177) - **Lead:** backend-engineer - **Must-haves:** - Split `core/regression_verify.py` (670 lines) into: `core/regression_capabilities.py` (the CAP-001..022 checks), `core/regression_live_plan.py` (the shared live-plan helpers from P5), `core/regression_verify_cli.py` (the `__main__` CLI + `run_regression` orchestration). - `core/regression_verify.py` becomes a thin re-export shim. - Behavior unchanged; the regression gate output is identical. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P14 — schema-driven-outputs-and-cache (REQ-178) - **Lead:** backend-engineer; **Contributor:** data-engineer (interface.json) - **Must-haves:** - `core/output_publisher.py:38-55` `SAFE_OUTPUT_NAMES` hardcoded set → derived from `modules/l1/*/interface.json` `outputs[].sensitive` annotations (non-sensitive outputs are safe to publish). - `core/contract_resolver.py:498,617` (now in the split module) — cache loaded JSON schemas in a module-level dict (avoid re-reading from disk each resolve call). - **Mid-milestone checkpoint (offline):** regression gate spot-check (not the full P9/P21 gate); confirm Wave 3 introduced no regressions. - **Verify:** pytest passes; `run_ci.sh` exits 0. ## Wave 4 — Developer Experience (P15–P17) ### Phase P15 — run-platform-help-and-flags-doc (REQ-179) - **Lead:** lead-developer - **Must-haves:** - `scripts/run_platform.sh` add a real `--help` / `-h` flag that prints all flags + a one-line description each (`--check-only`, `--plan-only`, `--apply`, `--destroy`, `--quiet`, `--deploy-uptime`, `--decommission`, `--local`, `--environment`). The current `:82` reject-unknown-flags path must allow `--help` to print + exit 0. - Document `--deploy-uptime` in the header comment block (currently used at `:532` but absent from the header). - Surface `--local` (D-092 local emulating tier) in the README "How to run" section. - **Verify:** `run_platform.sh --help` exits 0 and lists all flags; pytest passes; `run_ci.sh` exits 0. ### Phase P16 — workflows-readme-catalog (REQ-180) - **Lead:** lead-developer - **Must-haves:** - Author `.github/workflows/README.md` cataloging all 7 workflows: `ci.yml`, `deploy.yml`, `platform-test.yml`, `primitives-plan.yml`, `patterns-plan.yml`, `release.yml`, `modules-lifecycle.yml`. For each: trigger (`on:`), inputs (reusable-workflow `workflow_call` inputs), required secrets, and one-line purpose. - Note which 3 are byte-identical Gitea mirrors (post-P8, generated by `sync_workflows.py`) and which 4 are GitHub-only (Gitea act_runner feature gaps). - Add a `tests/test_docs_coverage.py` assertion that the README exists + lists all 7 workflow filenames. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P17 — getting-started-consolidation (REQ-181) - **Lead:** lead-developer - **Must-haves:** - Consolidate the README "How to run" into a single getting-started section: **offline happy path first** (`bash scripts/run_ci.sh` + `bash scripts/run_platform.sh --check-only` / `--local` — no AWS needed), then the **AWS path** (bootstrap + `--apply`). - Remove the fragmented 3-step bootstrap as the lead; demote it to the AWS-path subsection. - Cross-link `docs/CONSUMER_GUIDE.md` for the consumer contract model. - **Verify:** pytest passes; `run_ci.sh` exits 0. ## Wave 5 — No Humans Onboarding Flow (P18–P20) ### Phase P18 — onboarding-schema-and-lambda-action (REQ-182) - **Lead:** backend-engineer; **Contributor:** lead-developer (schema) - **Must-haves:** - Author `schemas/onboarding.schema.json` (JSON Schema draft 2020-12): required fields `consumerRepo` (string, format), `requestedEnvironment` (string, enum from environments dir), `ownerId` (string), `billingTag` (string); optional `notes`. - `core/lambda/contract_ingestor.py` add an `onboard_consumer` action (D-119): validates the payload against the onboarding schema, writes a `pending` row to `nova-contracts` (PK `consumerRepo`, SK `onboarding##`, status `pending`). No AWS resources created (D-113). - Tests: valid onboarding request writes a pending row; invalid request rejected with 400; offline-testable via moto/local Lambda stub. - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P19 — onboarding-envfile-autogen (REQ-183) - **Lead:** backend-engineer; **Contributor:** lead-developer (docs) - **Must-haves:** - Author `core/onboarding.py` with `generate_env_file(request, template_env="dev")` — produces a `.json` from a consumer onboarding request (fills `account_id` placeholder, `ownerId`, `billingTag` into the env template). Emits the file + a git patch / PR-branch instruction. - Rebrand `core/environment_check.py:57-77` onboarding message to Nova; replace the "1. Contact the platform team" handoff with the self-service request path: "Run `nova onboard` (or POST to the Lambda `onboard_consumer` action) to request an environment; the platform generates a binding + opens a PR." - Update `core/environments/README.md:34-37` — self-service request path is now implemented (real provisioning still a future feature). - Tests: `generate_env_file` produces a valid env JSON; the rebranded message no longer says "contact the platform team". - **Verify:** pytest passes; `run_ci.sh` exits 0. ### Phase P20 — cross-account-role-automation-offline (REQ-184) - **Lead:** data-engineer; **Contributor:** backend-engineer (ABAC) - **Must-haves:** - Author `terraform/onboarding/` (new dir): `main.tf` defining the consumer deploy-role + `nova:owner` ABAC tag grant (cross-account IAM role + trust policy + tag-based permission boundary). Variables for `consumer_repo`, `owner_id`, `account_id`. - `terraform validate` passes; `terraform plan` (offline / no live apply per D-114) produces the expected role + policy. - Document the onboarding Terraform in `docs/ONBOARDING.md` — the request path (P18) → env-file autogen (P19) → role grant (P20, this phase, offline-proven; live apply deferred). - Tests: `terraform validate` for the onboarding module; a `test_onboarding_terraform.py` asserting the module validates. - **Verify:** `terraform validate` (onboarding module) passes; pytest passes; `run_ci.sh` exits 0. ## Final Phase — P21 — final-review-ship - **Lead:** lead-developer; **Contributors:** all active (review) - **Must-haves:** - Multi-persona code review across all v1.16 phases (ci-code-reviewer). Auto-apply P0 fixes; flag P1+ for post-hoc review. If P1+ found, fix in this phase (not loop back to EXECUTE). - Audit (ciagent-audit): reconstruction test (git log matches `.ciagent/` files), file discipline, branch hygiene, commit discipline. Fix critical issues in this phase. - **Run the regression gate (D-118, milestone complete):** **20/22 Verified** (CAP-015/016 Skipped — post-teardown steady state, D-096). - Update `.ciagent/REQUIREMENTS.md` — mark REQ-165..184 complete. - Update `.ciagent/ROADMAP.md` — mark v1.16 complete. - Update `.ciagent/PROJECT.md` — v1.16 complete summary. - Ship: merge `phase/21-final-review-ship` → `milestone/v1.16-nova-simplification`; merge milestone → `main`; tag `v1.15.26` (= milestone release); create Gitea release with full milestone summary. - Clear CHECKPOINT.json (milestone complete). ## Success Criteria (milestone gate) - All 20 requirements (REQ-165..184) satisfied; 0 partial. - Regression gate **20/22 Verified + 2 Skipped** at P9 + P21 (D-118, G-111; CAP-015/016 are the post-v1.11-teardown steady state, D-096). - `bash scripts/run_ci.sh` exits 0 at every phase boundary. - Review: 0 new P0; P1+ flagged or auto-fixed. - Audit: clean; reconstruction test passes. - Tag `v1.15.26` created; milestone merged to main. - Onboarding request path implemented (P18–P20); real AWS provisioning explicitly deferred (D-113, D-114).