Files
acdl/.ciagent/PLAN.md
T
Jon Chery ee5c372e65 docs(P00): complete pre-execution phase — v1.16 NFR scope
Phase 0: SPECIFY → CLARIFY → RESEARCH → IDEATE → PLAN → GRILL.
NFR milestone v1.16 (Nova Simplification), 20 execution phases + final.
Tags on v1.15.x line: v1.15.5 (this phase) → v1.15.6..v1.15.25 (P1-P20) →
v1.15.26 (P21 final = release).

Scope (D-113..D-119): Simplify without regressions, Security,
Maintainability, User/Developer Experience, No Humans Onboarding Flow
(request-path only; real AWS provisioning deferred). Regression gate
(D-118, G-111) gates P9 + P21 at 20/22 Verified + 2 Skipped.

Grill: PASS-with-binding (G-111..G-113, E-002 deferred to P21).

---ci---
project: acdl
phase: 0
milestone: v1.16
status: complete
phase_role: pre_execution
requirements:
  covered: [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]
  partial: []
---/ci---
2026-07-30 15:12:32 +00:00

420 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` (P1P20) →
`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 (P1P4): 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. P2P4 independent brand/dead-code/except work.
- **Wave 2 (P5P9): 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 (P10P14): 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 (P15P17): developer experience.** Independent; P17 last
(reflects the consolidated path after P15/P16 land).
- **Wave 5 (P18P20): 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 (P1P4)
### 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 (P5P9)
### 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 (P10P14)
### 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 (P15P17)
### 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 (P18P20)
### 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 (P18P20); real AWS provisioning
explicitly deferred (D-113, D-114).