---
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#<requestedEnvironment>#<timestamp>`, 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 `<env>.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).