--- phase: P0 name: pre-execution milestone: v1.14 requirements: [REQ-135, REQ-136, REQ-137, REQ-138, REQ-139, REQ-140, REQ-141, REQ-142, REQ-143, REQ-144, REQ-145, REQ-146, REQ-147, REQ-148, REQ-149, REQ-150, REQ-151, REQ-152, REQ-153, REQ-154] wave: 0 depends_on: [] --- # v1.14 — NFR Refinement Plan (20 execution phases + 1 final) **Milestone:** v1.14 (NFR — bug fixes, security, stubs, tests, docs) **Type:** NFR (all phases fix/test/docs/chore/refactor). Final patch IS the release. Tags: `v1.13.3` (P0) → `v1.13.4..v1.13.23` (P1–P20) → `v1.13.24` (P21 = milestone release). **Branch:** `milestone/v1.14-refinement` → `phase/NN-` ## Wave ordering (D-098) - **Wave 1 (P1–P6):** bug fixes. P1→P2 sequential (composition depends on dedup correctness); P3–P6 independent. **G-105: full regression gate run after P4** (validates the hardened gate before W2). - **Wave 2 (P7–P12):** security. P8→P9 sequential (IAM ARNs reference externalized account ID); rest independent. **G-106: mid-milestone regression-gate checkpoint after P12** (offline gate run; non-Verified halts W3 until fixed). - **Wave 3 (P13–P17):** stub/test/CI/hygiene. P15 depends on P7 (hardened errors before script tests); P17 depends on P14 (both touch config.json); P13 independent. - **Wave 4 (P18–P20):** standards/docs/VPC. P19 depends on P1–P18 (reflects all prior phases); P18 + P20 independent. ## Execution approach Each phase: EXECUTE (persona-assigned task groups) → VERIFY (4 layers + regression gate at milestone complete) → SHIP (patch tag). Phase boundary checkpoint resets context. The execute workflow reads this PLAN.md + ROADMAP.md §v1.14 + PERSONAS.md for task decomposition. --- ## Wave 1 — Bug Fixes (P1–P6) ### P1 — adapter-dedup-diagnostic (REQ-135) **Persona:** backend-engineer **Territory:** `adapters/terraform/adapter.py` **Tasks:** 1. In the dedup loop (`adapter.py:159-170`), when `tf_dir` is `None`, raise `ValueError(f"no terraform_dir in registry for module {module}")` instead of silently skipping. 2. Verify registered-module dedup behavior preserved (multi-resource L1s still merge into one `module "x" { ... }` block). 3. Run `pytest tests/test_adapter.py` + `run_ci.sh`. ### P2 — static-assets-wiring-fix (REQ-136) **Persona:** data-engineer **Territory:** `modules/l2/static-assets/` **Tasks:** 1. Wire `default_ttl`/`max_ttl`/`price_class`/`viewer_protocol_policy` in `composition.json` to the cloudfront child's inputs. 2. Add a `waf_enabled` feature flag (default true) to the static-assets composition; make the WAF child conditional on it. 3. Update `examples/complex.yml` to set `waf_enabled: true` + non-default TTLs so it resolves to a different resource set than `simple.yml`. 4. Run `pytest` + `run_ci.sh`. ### P3 — lifecycle-script-arg-cleanup (REQ-137) **Persona:** backend-engineer **Territory:** `scripts/run_l2_lifecycle_*.sh` **Tasks:** 1. Remove the `[ci-vpc-outputs.json]` token from the usage strings of `run_l2_lifecycle_test.sh` + `run_l2_lifecycle_destroy.sh`, OR add a comment documenting the L2-uses-remote-state design + parity reason. 2. Run `pytest` + `run_ci.sh`. ### P4 — regression-gate-evidence-hardening (REQ-138) **Persona:** backend-engineer **Territory:** `core/regression_verify.py`, `.ciagent/CAPABILITY_INVENTORY.md` **Binding decisions:** G-105 (gate must pass clean post-P4 before W2) **Tasks:** 1. Add a `terraform validate` step to `_check_lifecycle_module_terraform` (or document why it's too slow + fall back to a `terraform fmt -check` syntax probe). 2. Tighten CAPABILITY_INVENTORY + docstrings to "offline proxy; live apply/modify/destroy verified by the modules-lifecycle workflow run, not by this gate." 3. **Run the full regression gate immediately after P4 lands** (G-105). Gate must pass clean before W2 begins. 4. Run `pytest` + `run_ci.sh`. ### P5 — adapter-behavior-tests (REQ-139) **Persona:** backend-engineer **Territory:** `tests/test_adapter.py` **Tasks:** 1. Add `test_adapter_dedup_merges_same_module` — two resources with the same `module` collapse to one `module "" { ... }` block with merged inputs. 2. Add `test_adapter_remote_state_key_override` — `ACDL_REMOTE_STATE_KEY` overrides the default `platform/terraform.tfstate` key in the emitted `data terraform_remote_state` block. 3. Run `pytest` + `run_ci.sh`. ### P6 — alb-name-prefix-fix (REQ-140) **Persona:** data-engineer **Territory:** `modules/l1/alb/terraform/main.tf` **Tasks:** 1. Change `name_prefix = "tg-ci-"` to `name_prefix = "${var.name}-"` so the consumer's name prefixes the target group. 2. Run `terraform validate` in the alb module dir standalone. 3. Run `pytest` + `run_ci.sh`. --- ## Wave 2 — Security (P7–P12) ### P7 — swallowed-error-hardening (REQ-141) **Persona:** backend-engineer **Territory:** `core/local_emulators.py`, `core/lambda/contract_ingestor.py`, `terraform/bootstrap/create_state_backend.py`, `core/output_publisher.py`, `terraform/bootstrap/apply_iam_baseline.py` **Tasks:** 1. `local_emulators.py:374` — narrow `except Exception: pass` to catch `AttributeError`/`TypeError` (monkeypatch setup); log + re-raise if patching fails (prevents network egress). 2. `contract_ingestor.py:157` — catch `urllib.error.URLError`/ `HTTPError` specifically; log the search failure; keep `existing = []` only on `404`/network, re-raise on auth errors. 3. `create_state_backend.py:51` — catch `ClientError` with `NoSuchBucket`/`404` error code; re-raise on permissions/network. 4. `output_publisher.py:100,168` — catch `ClientError`/`HTTPError` specifically; log with context. 5. `apply_iam_baseline.py:78` — catch `NoSuchEntityException` on old-version delete; re-raise on other errors. 6. Run `pytest` + `run_ci.sh`. ### P8 — account-id-externalization (REQ-142) **Persona:** backend-engineer + data-engineer **Territory:** `adapters/terraform/adapter.py`, `terraform/bootstrap/`, `scripts/push_consumer_image.py`, terraform resource ARNs **Binding decisions:** G-101 (grep excludes backend blocks), G-102 (fallback bound to live account ID + workflow env wiring) **Tasks:** 1. `adapter.py:125,140` — read `ACDL_AWS_ACCOUNT_ID` env; build the state-bucket name dynamically. **Fallback constant = `581513795199`** (the live account ID, NOT a placeholder — G-102). Documented for offline tests. 2. `apply_iam_baseline.py:33`, `create_state_backend.py:33,35` — read from env (same fallback). 3. `push_consumer_image.py:32` — read from env. 4. Terraform: use `data.aws_caller_identity.current.account_id` for **resource ARNs** in `spike_runner_policy.json` + resource names. **Exclude terraform `backend "s3"` blocks** (`terraform/*/terraform.tf`, `terraform/ci-vpc/main.tf`, `terraform/platform/main.tf`, `terraform/microservice/terraform.tf`) — backend `bucket` args are static-config-only, evaluated pre-init (G-101). Leave backend blocks literal or move to `terraform init -backend-config` (separate change, not in P8 scope). 5. **Lifecycle workflow env wiring (G-102):** the `modules-lifecycle.yml` full-mode jobs must set `ACDL_AWS_ACCOUNT_ID` from `aws sts get-caller-identity --query Account --output text` before any `run_platform.sh`/lifecycle invocation. No full-mode run proceeds with the env unset. 6. Run `pytest` + `run_ci.sh`; verify `grep -rn "581513795199" adapters/ scripts/ terraform/bootstrap/ core/` returns 0 hits (excluding tests + docs + terraform backend blocks). ### P9 — iam-policy-least-privilege (REQ-143) **Persona:** data-engineer **Territory:** `terraform/bootstrap/spike_runner_policy.json`, `tests/test_iam_policy_baseline.py`, `modules/l1/*/terraform/main.tf`, `modules/l2/*/composition.json` **Binding decisions:** G-104 (verify acdl-* naming before merge) **Tasks:** 1. Scope `iam:CreateRole` etc. (line 236) to `arn:aws:iam::*:role/acdl-*`. 2. Scope KMS (line 218) to `arn:aws:kms::*:key/acdl-*` (or `alias/acdl-*`). 3. CloudFront (line 117) + WAFv2 (line 129) remain `Resource: "*"` with a documented global-ARN constraint (CloudFront ARNs are global; cannot be account-scoped — G-104). 4. **Verify acdl-* naming (G-104):** grep/audit `modules/l1/*/terraform/main.tf` + `modules/l2/*/composition.json` for every IAM role + KMS key name created by the lifecycle pipeline. If any non-`acdl-*` name is found, rename the resource or widen that one statement (documented). 5. Add a regression test in `test_iam_policy_baseline.py` asserting no new `Resource: "*"` on non-global actions. 6. Run `pytest` + `run_ci.sh`. ### P10 — contract-ingestor-identity-validation (REQ-144) **Persona:** backend-engineer **Territory:** `core/lambda/contract_ingestor.py`, `tests/test_contract_ingestor.py` **Tasks:** 1. Add `contractId` format validation (regex, ≤64 chars). 2. Add `environment` enum validation (dev/qa/prod/dr). 3. Add `error` length cap (truncate `stackTrace` at a reasonable limit). 4. Document the ABAC reliance in the `_validate_caller_identity` docstring + add a note to ARCHITECTURE.md (P19 will land it). 5. Add a spoofing-resistance test (caller submits a `consumerRepo` they don't own → rejected if ABAC misconfigured; documented best-effort). 6. Run `pytest` + `run_ci.sh`. ### P11 — schema-input-validation-hardening (REQ-145) **Persona:** backend-engineer **Territory:** `schemas/contract.schema.json`, `schemas/environment.schema.json`, `tests/test_environment_schema.py`, `tests/test_contract_schema.py` **Tasks:** 1. Add `"additionalProperties": false` to both schemas' top-level objects. 2. Add `maxItems`/`maxProperties` bounds to `infrastructure` map + `monitored_endpoints` array. 3. Add `pattern` validation for `state_backend.bucket` (S3 naming rules: lowercase, 3-63 chars, no underscores). 4. Add `pattern` validation for `runner_role_arn` (ARN format). 5. Add `pattern` validation for `vpc_cidr` (CIDR format). 6. Add tests asserting rejection of undocumented fields + malformed values. 7. Run `pytest` + `run_ci.sh`. ### P12 — gitignore-credential-hygiene (REQ-146) **Persona:** lead-developer **Territory:** `.gitignore`, `tests/test_no_secrets_tracked.py` **Tasks:** 1. Add credential-pattern catch-all to `.gitignore`: `*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.cer`, `*.crt`, `*.jks`. 2. Create `tests/test_no_secrets_tracked.py` — runs `git ls-files | grep -E '\.(pem|key|p12|pfx|cer|crt|jks)$'` and asserts 0 hits. 3. Run `pytest` + `run_ci.sh`. --- ## Wave 3 — Stub / Test / CI / Hygiene (P13–P17) ### P13 — kyverno-kube-version-resolution (REQ-147) **Persona:** backend-engineer **Territory:** `adapters/kyverno/kyverno_adapter.py`, `tests/test_kyverno_adapter.py` **Binding decisions:** G-103 (removal+documentation path, NOT implementation) **Tasks:** 1. **Remove the `--kube-version` flag** from `kyverno_adapter.py:11,115-116` (G-103 — implementing version-aware policy selection would be a new feature, violating D-095). 2. Add a docstring documenting the deferral to the GitOps reconciler roadmap (D-053): the Kyverno adapter is inactive for Terraform-only stacks; `--kube-version` will be relevant when the GitOps reconciler emits K8s manifests. 3. Update `test_kyverno_adapter.py` to remove the `--kube-version` test cases + assert the flag is absent. 4. Run `pytest` + `run_ci.sh`. ### P14 — orphan-artifact-and-dead-config-cleanup (REQ-148) **Persona:** lead-developer **Territory:** `scripts/__pycache__/`, `pyproject.toml`, `.ciagent/config.json` **Tasks:** 1. Delete the orphan `scripts/__pycache__/verify_deploy_microservice.cpython-312.pyc`. 2. Fix `pyproject.toml` coverage source: `acdl_platform` → `core`. 3. Bump `pyproject.toml` version `1.3.0` → current (v1.14). 4. Remove dead JS allowlist entries from `config.json` `bash_allowlist.allowed_commands` (npm/node/npx/pnpm/yarn/jest/eslint/ tsc/prettier — no package.json). 5. Run `pytest` + `run_ci.sh`. ### P15 — untested-scripts-coverage (REQ-149) **Persona:** backend-engineer **Territory:** `tests/` (new test files for 7 scripts) **Tasks:** 1. `tests/test_seed_uptime_monitors.py` — mock the uptime-kuma API; assert monitor creation from a JSON file. 2. `tests/test_push_consumer_image.py` — mock `subprocess.run` (docker login/build/push) + boto3 ECR; assert the flow. 3. `tests/test_sync_to_gl.sh` (shell test) — dry-run mode; assert the copy + push commands are constructed correctly. 4. `tests/test_post_stage_comment.sh` (shell test) — no-op when not in a PR context; assert the `gh api` call structure when in PR. 5. `tests/test_rotate_spike_key.sh` (shell test) — mock `aws iam`; assert deactivate/create/update-secret flow. 6. `tests/test_create_state_backend.py` — mock boto3 S3/DynamoDB; assert idempotent creation. 7. `tests/test_create_iam_user.py` — mock boto3 IAM; assert idempotent user/policy/key creation. 8. Run `pytest` + `run_ci.sh`. ### P16 — workflow-parity-and-script-flags (REQ-150) **Persona:** backend-engineer **Territory:** `.gitea/workflows/`, `scripts/rotate_spike_key.sh`, `scripts/sync_to_gl.sh` **Tasks:** 1. Either mirror the 4 GitHub-only workflows (patterns-plan, platform-test, primitives-plan, release) to `.gitea/workflows/`, or add a README documenting the Gitea limitation (Gitea runners don't use release/primitives-plan/patterns-plan; release is GitHub-only by design). 2. Add `set -euo pipefail` to `rotate_spike_key.sh` (currently only `set -u`). 3. Add `set -euo pipefail` to `sync_to_gl.sh` (currently no `set` flags). 4. Run `pytest` + `run_ci.sh`. ### P17 — config-and-persona-hygiene (REQ-151) **Persona:** lead-developer **Territory:** `.ciagent/config.json`, `.ciagent/PERSONAS.md` **Tasks:** 1. Mark `frontend-engineer` persona `active: false` in `config.json` `personas.personas[]` (PERSONAS.md:80 already says inactive). 2. Fix `branching_strategy: "phase"` — either change to `"flat"` or document that the field is advisory + the project uses flat workflow (committed directly to main per established convention). 3. Configure `ollama-cloud` backend: set `base_url` to the actual endpoint OR add a comment documenting why it's intentionally unset (the runtime uses the `glm-5.2` model via the opencode backend, not the `llm_backends` config). 4. Run `pytest` + `run_ci.sh`. --- ## Wave 4 — Standards / Docs / VPC (P18–P20) ### P18 — module-standards-consistency (REQ-152) **Persona:** data-engineer **Territory:** `modules/STANDARDS.md`, `modules/l1/{ecr,ecs-cluster,rds}/terraform/` **Tasks:** 1. Either add `locals.tf` to `ecr`, `ecs-cluster`, `rds` (extract inlined locals from `main.tf`), OR reconcile STANDARDS §9.4 to explicitly allow inlining for trivial single-resource modules. 2. Remove the stale `TYPE_MAP` reference in STANDARDS §8 (deleted in the v1.11 stateless rewrite). 3. Run `pytest` + `run_ci.sh`. ### P19 — documentation-sync-v1.14 (REQ-153) **Persona:** lead-developer **Territory:** `.ciagent/ARCHITECTURE.md`, `docs/`, `README.md`, `.ciagent/COST.md`, `.ciagent/GRILL.md`, `.ciagent/IAM_POLICY.md`, `docs/presentations/` **Tasks:** 1. ARCHITECTURE.md: add v1.11 addendum (stateless adapter, platform VPC, ACDL_LIFECYCLE_MODE), v1.12 addendum (CAP-013 fix, plan-only default), v1.13 addendum (config.json schema migration, badge cleanup, platform-architecture diagram), v1.14 addendum (all 20 phases). Record D-083 deferral explicitly. 2. Bump stale `@v1.6`–`@v1.9` → `@v1.13` across `README.md:225`, `docs/consumer-guide.md` (12 sites), `docs/architecture.md:233`, `docs/pipeline/versioning.md:29`, `docs/pipeline/index.md:42`. 3. Sync decks to v1.13.2 reality (version refs, capability claims). 4. Update COST.md window to v1.11–v1.14 (lifecycle pipeline live-runs + teardown). 5. Resolve G-005/G-008 in GRILL.md (CAP-017..022 now Verified via lifecycle pipeline; COST.md now exists + covers v1.11+). 6. Update IAM_POLICY.md for v1.12/v1.13/v1.14 (plan-only default, config.json schema, v1.14 IAM scoping from P9). 7. Run `pytest` + `run_ci.sh`; verify `grep -rn "@v1\.[6-9]" docs/ README.md` returns 0 hits. ### P20 — platform-vpc-parameterization (REQ-154) **Persona:** data-engineer **Territory:** `terraform/platform/main.tf` **Tasks:** 1. Add a `vpc_cidr` variable (default `10.0.0.0/16`); replace the hardcoded `cidr_block`. 2. Replace `count = 2` subnets with `count = length(data.aws_availability_zones.available.names)`. 3. Add a `data "aws_availability_zones" "available" {}` block. 4. Document the `0.0.0.0/0` ingress on port 80 (ALB-fronted, acceptable for a public-facing service; add a comment). 5. Run `terraform validate` + `pytest` + `run_ci.sh`. --- ## Final Phase — P21 (review + audit + ship) **Persona:** lead-developer (review coordination) + ci-code-reviewer + ci-debugger (audit) **Tasks:** 1. Multi-persona code review across all v1.14 phases (P1–P20). Auto-apply P0 fixes; flag P1+ for post-hoc review. If P1+ found, fix in-phase. 2. Audit: reconstruction test (git log vs `.ciagent/` files), file discipline, branch hygiene, commit discipline. Fix critical issues in-phase. 3. Complete: update REQUIREMENTS.md (REQ-135..154 → complete), ROADMAP.md (v1.14 complete), PROJECT.md. 4. Tag `v1.13.24` (IS the milestone release). Merge `milestone/v1.14-refinement` → `main`. Create Gitea release with full milestone summary. ## Success Criteria (milestone gate) 1. All 20 REQ-135..REQ-154 marked complete in REQUIREMENTS.md. 2. Review: 0 new P0; all P1-1..P1-5 + P2-1..P2-4 resolved. 3. Audit: clean; reconstruction test passes. 4. Regression gate (D-091) clean against the v1.14 state. 5. `pytest` passes; `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0. 6. Tag `v1.13.24` created; milestone merged to main.