From 58100c485e0c32588d0690651d3d414b5ce27094 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Thu, 23 Jul 2026 04:19:17 +0000 Subject: [PATCH] docs(P00): create 5 phase plans for v1.9 (phases 39-43) ---ci--- project: acdl phase: 0 milestone: v1.9 status: plan ---/ci--- 5 vertical-slice phases, wave-ordered: - Wave 1 (parallel): Phase 39 (design docs + P1-1, disjoint file sets) - Wave 2: Phase 40 (contract interpolation + env schema) - Wave 3: Phase 41 (per-env CI jobs + env workflow input) - Wave 4: Phase 42 (stubs: SoD, HITL gates, attestation matrix, Wiz, Kyverno) - Wave 5: Phase 43 (verify + review + audit + complete) Plans reference REQ-100..REQ-111. Decisions D-080..D-089 applied. Versioning: feature milestone, patches v1.8.1..v1.8.5, tag v1.9.0 at COMPLETE. --- .ciagent/PLAN.md | 370 +++++++++++++++++++++++------------------------ 1 file changed, 185 insertions(+), 185 deletions(-) diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md index e452b00..227d0e4 100644 --- a/.ciagent/PLAN.md +++ b/.ciagent/PLAN.md @@ -1,228 +1,228 @@ --- -phase: 28-38 -name: v1.8-p1-remediation-uptime-standards-encryption-decommission-docs -milestone: v1.8 -requirements: [REQ-76, REQ-77, REQ-78, REQ-79, REQ-80, REQ-81, REQ-82, REQ-83, REQ-84, REQ-85, REQ-86, REQ-87, REQ-88, REQ-89, REQ-90, REQ-91, REQ-92, REQ-93, REQ-94, REQ-95, REQ-96, REQ-97, REQ-98, REQ-99] -type: fix/feat/docs +phase: 39-43 +name: v1.9-design-doc-interpolation-per-env-ci-stubs-p1-1 +milestone: v1.9 +requirements: [REQ-100, REQ-101, REQ-102, REQ-103, REQ-104, REQ-105, REQ-106, REQ-107, REQ-108, REQ-109, REQ-110, REQ-111] +type: feat/docs/fix --- -# ACDL v1.8 — Phase Plans +# ACDL v1.9 — Phase Plans -> Milestone: v1.8. Planner: ci-planner. Status: active. -> 11 phases (28–38), 24 requirements (REQ-76..99). +> Milestone v1.9. Generated at PLAN stage. Autonomy: full. +> Requirements: REQ-100..REQ-111 (see REQUIREMENTS.md). +> Decisions: D-080..D-089 (see PROJECT.md + RESEARCH.md RA section). +> Versioning: feature milestone — progressive patch versions per phase +> (v1.8.1..v1.8.5), tag `v1.9.0` at milestone COMPLETE. -## Phase 28 — adapter-waf-and-resolver-outputs +## Wave ordering -**Requirements:** REQ-76 (WAF nested rules + default_action), REQ-77 (L2 outputs resolution) -**Personas:** platform-engineer (lead), backend-engineer -**Type:** fix - -### Tasks (Wave 1 — sequential): - -1. **platform-engineer:** Fix WAF `rules` emission in `adapters/terraform/adapter.py:346-348` — replace `rules = {_value_expr(...)}` with nested `rules { ... }` block emission per rule. Read `inputs.get("default_action")` (line 334) and emit `allow {}` / `block {}` based on input (default `allow` if absent). -2. **backend-engineer:** Implement L2 composition `outputs[]` processing in `core/contract_resolver.py` `resolve_l2()` — after building `resources` (line 232), parse `composition.get("outputs", [])`, resolve source via `child_outputs`, build `stack_instance["outputs"]` dict. -3. **platform-engineer:** Extend `adapter.py` `adapt()` to emit `output "" { value = }` blocks from `stack_instance.get("outputs", {})`. -4. **platform-engineer:** Add tests to `tests/test_adapter.py` (WAF custom rules, default_action block, output blocks) + `tests/test_contract_resolver.py` (L2 outputs for static-assets). - -### Must-haves: -- WAF with custom rules emits `rules {` blocks, not `rules = [` -- WAF `default_action: block` emits `block {}` -- L2 resolution yields `stack.outputs.*` -- Adapter emits `output` blocks -- `pytest` passes (275 → ~285) +- **Wave 1 (parallel, 2 tasks):** Phase 39 — design-doc refresh (security-engineer) + P1-1 adapter parameterization (platform-engineer). Disjoint file sets; no merge conflict. +- **Wave 2 (sequential):** Phase 40 — contract interpolation. Depends on Phase 39's design-doc context (lightweight). +- **Wave 3 (sequential):** Phase 41 — per-env CI jobs. Depends on Phase 40's interpolation + env schema. +- **Wave 4 (sequential):** Phase 42 — stub implementation. Depends on Phase 41's HITL job structure. +- **Wave 5 (sequential):** Phase 43 — verify + review + audit + complete. --- -## Phase 29 — ssm-kms-and-invoke-policy +## Phase 39 — design-doc-refresh-and-p1-1-parameterization -**Requirements:** REQ-78 (SSM fail-loud), REQ-79 (Terraform-rendered invoke policy) -**Personas:** backend-engineer (lead), lambda-engineer, security-engineer -**Type:** fix +**Requirements:** REQ-100, REQ-101, REQ-102 +**Personas:** security-engineer (lead: design docs), platform-engineer (lead: P1-1), backend-engineer (review) +**Branch:** `phase/39-design-doc-refresh-and-p1-1` -### Tasks (Wave 1): +### Task 39.1 — Refresh hitl_matrix_design.md (REQ-100, security-engineer) +- Rewrite the status block: "v1.2 wires the gates" → "v1.9 wires the gates (Phase 42)". +- Update "Spike scope note" → "v1.9 scope note": qa/prod/dr now exercised (Phase 41 wires the job structure; Phase 42 wires the attestation gates); dev remains autonomous. +- Update §10.4 matrix: mark the offline-testable concerns (contract NFRs, schema validity, policy pass) as **implemented in v1.9** (`core/attestation_matrix.py`); mark operator-supplied concerns as **accept signed evidence artifacts** (D-084). +- Add a "v1.9 wiring" section: cross-reference Phase 41's per-env jobs + Phase 42's `hitl_gates.py` + `attestation_matrix.py` + the outbox-based SoD check. +- Preserve D-042 (approver identity = `gitea.actor` / `github.actor`) — still accurate. +- Verify: `grep -i "dev-only spike" core/hitl_matrix_design.md` returns 0 hits; `grep -i "v1.2 wires" core/hitl_matrix_design.md` returns 0 hits. -1. **backend-engineer:** Change `core/output_publisher.py:54-55` `_kms_key_id()` — raise `RuntimeError` when `ACDL_KMS_KEY_ID` unset; add `ACDL_ALLOW_DEFAULT_KMS=1` escape hatch. -2. **lambda-engineer:** Convert `terraform/platform/consumer_invoke_policy.json` to Terraform-rendered template — add `terraform/platform/variables.tf` with `data "aws_caller_identity" "current" {}` + `templatestring` or `replace()` for account ID injection. -3. **backend-engineer:** Add `tests/test_output_publisher.py` cases: `test_kms_unset_raises`, `test_kms_unset_allow_default_kms`. Add `tests/test_invoke_policy.py` asserting rendered policy has no `000000000000`. +### Task 39.2 — Refresh audit_ledger_design.md (REQ-101, security-engineer) +- Mark the "Spike scope (D-041)" section as **shipped + production since v1.8** (hash chain + DynamoDB outbox + `acdl-evidence` mirror). +- Move the "v1.2 build-out" section (S3 Object Lock + JWS + async worker + DLQ + daily checkpoints) under a clearly-labeled "**Deferred to a future milestone (D-083)**" heading. Keep the content (it's the design for when it ships) but mark it not-v1.9. +- Update the RPO/RTO table: spike row → "v1.8+ (production): RPO=0 (sync outbox), RTO=workflow re-run"; v1.2 row → "Future milestone (D-083): RPO=0, RTO=DLQ replay". +- Update the outbox item shape: note `approver_qa`/`approver_prod`/`approver_dr` are populated by v1.9's `hitl_gates.attest` (Phase 42). +- Verify: `grep -i "Phases 08-10 implement" core/audit_ledger_design.md` returns 0 hits; the deferred section is clearly labeled. -### Must-haves: -- SSM raises RuntimeError without CMK; escape hatch works -- Rendered invoke policy has live account ID -- `pytest` passes (~290) +### Task 39.3 — P1-1 adapter parameterization (REQ-102, platform-engineer) +- `modules/l1/ecs-service/interface.json`: add inputs `desired_count` (integer, default 1), `launch_type` (string, default "FARGATE"), `family` (string, default "app"). +- `modules/l1/alb/interface.json`: add inputs `load_balancer_type` (string, default "application"), `target_type` (string, default "ip"). +- `modules/l1/vpc/interface.json`: add input `name` (string, default "app") for the VPC/IGW/RT `Name` tag prefix. +- `adapters/terraform/adapter.py`: change hardcoded defaults to `inputs.get("", "")` where the default matches the interface default (safety fallback; the resolver populates from the interface). Remove the hardcoded `Name = "acdl-microservice-rt"` (line 283) → use `inputs.get("name", "app")`-derived tag. +- Preserve the v1.1 S3 regression (S3 has none of these inputs → no change). +- Tests: `tests/test_p1_1_adapter_parameterization.py` — (a) `desired_count: 3` in contract inputs emits `desired_count = 3`; (b) absent `desired_count` emits `desired_count = 1` via interface default; (c) `target_type: "instance"` emits `target_type = "instance"`; (d) v1.1 S3 regression still passes (byte-identical `main.tf`). +- Verify: `pytest tests/test_p1_1_adapter_parameterization.py` passes; `run_platform.sh --check-only` exits 0; `pytest` total count increases; v1.1 S3 regression test passes. + +### Task 39.4 — Design doc test (REQ-100/101, backend-engineer) +- `tests/test_design_docs_current.py`: assert (a) no stale "dev-only spike" / "v1.2 wires the gates" / "Phases 08-10 implement" framing in either design doc; (b) `audit_ledger_design.md` has a "Deferred to a future milestone" section referencing D-083; (c) `hitl_matrix_design.md` references the v1.9 implementation (`attestation_matrix.py`, `hitl_gates.py`). +- Verify: `pytest tests/test_design_docs_current.py` passes. + +### Must-haves (Phase 39) +- [ ] `core/hitl_matrix_design.md` refreshed (no stale framing). +- [ ] `core/audit_ledger_design.md` refreshed (S3 Object Lock marked deferred D-083). +- [ ] Adapter has no hardcoded ECS/ALB/VPC defaults (read from inputs). +- [ ] `tests/test_p1_1_adapter_parameterization.py` + `tests/test_design_docs_current.py` pass. +- [ ] `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0; v1.1 S3 regression passes. --- -## Phase 30 — run-platform-isolation-and-api-portability +## Phase 40 — contract-interpolation -**Requirements:** REQ-80 (temp dir), REQ-81 (forge-agnostic URLs), REQ-82 (static-key override) -**Personas:** backend-engineer (lead), lambda-engineer -**Type:** fix +**Requirements:** REQ-103, REQ-104 +**Personas:** backend-engineer (lead), platform-engineer (review) +**Branch:** `phase/40-contract-interpolation` -### Tasks (Wave 1 — parallel): +### Task 40.1 — Environment JSON schema (REQ-104, backend-engineer) +- `schemas/environment.schema.json` (draft 2020-12): required `name` (string), `account_id` (string), `region` (string), `state_backend` (object: `bucket`, `lock_table`), `network` (object: `vpc_cidr`, `azs` array), `runner_role_arn` (string), `autonomy` (enum: full/attested), `confidence_threshold` (number). +- `core/environments/dev.json` validates against it. +- Add `core/environments/qa.json`, `prod.json`, `dr.json`: `account_id: "000000000000"`, `autonomy: "attested"`, `confidence_threshold` 0.75/0.90/0.95, regions us-east-1, state_backend buckets `acdl-qa-state`/`acdl-prod-state`/`acdl-dr-state`. +- `core/environment_check.py`: add `load(env_name, root=None)` returning the parsed env dict; `check()` stays. Add a stderr warning when `account_id == "000000000000"` and `env_name != "dev"` (prompts real binding). +- `tests/test_environment_schema.py`: all 4 env files validate; `load("dev")` returns the dict; warning emitted for qa/prod/dr placeholders. +- Verify: `pytest tests/test_environment_schema.py` passes. -1. **backend-engineer:** Change `scripts/run_platform.sh:122` adapter target from `terraform/spike` to `$WORK/tf`. Update all downstream references. Remove committed `terraform/spike/*.tf`. Update `tests/test_pipeline.py`. Update deploy.yml artifact upload path. -2. **lambda-engineer:** Add `_github_api_base()` + `_forge_type()` to `core/lambda/contract_ingestor.py`. Replace hardcoded URLs at lines 109, 149, 163. Add `tests/test_contract_ingestor.py` Gitea base URL test. -3. **backend-engineer:** Restructure `configure-aws-credentials` step in both deploy workflows (byte-identical) — single conditional step with `access-key`/`secret-key` inputs when static key present. Update `tests/test_pipeline_contract.py`. +### Task 40.2 — Interpolation expansion in the resolver (REQ-103, backend-engineer) +- `core/contract_resolver.py`: add `_expand_vars(value, context)` — recursively walks dicts/lists/strings; replaces `${env.}` and `${contract.}` tokens by looking up the dotted path in the context dict. Unknown token → `ValueError(f"unresolved interpolation token: {token}")`. +- `resolve()`: after schema validation, load the env via `environment_check.load(contract["environment"])`, build `context = {"env": env, "contract": contract}`, expand all string values in `contract["inputs"]` (recursively, per D-087), then proceed to IR resolution. +- The expansion is post-schema-validation (schema sees the raw tokens, which are valid strings) and pre-IR-resolution (the resolver sees concrete values). +- `tests/test_interpolation.py`: (a) `${env.region}` expands to `us-east-1`; (b) `${env.state_backend.bucket}` expands to `acdl-dev-state`; (c) `${contract.module}` expands to `static-assets`; (d) unknown token raises `ValueError`; (e) nested map value `env: { DB_URL: "acdl-${env.environment}-db" }` expands recursively; (f) `resolve("contracts/static-assets.yaml")` succeeds with expanded values. +- Verify: `pytest tests/test_interpolation.py` passes. -### Must-haves: -- `run_platform.sh --check-only` writes to temp dir -- `contract_ingestor.py` uses `GITHUB_API_BASE` -- Deploy workflow static-key override wired -- Both deploy workflows byte-identical -- `pytest` passes (~295) +### Task 40.3 — Sample contracts use naming patterns (REQ-103, backend-engineer) +- `contracts/static-assets.yaml`: `bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}` (the naming pattern the requirement calls out: region + account id + environment). +- `contracts/microservice.yaml`: same pattern for `bucket_name`. +- Keep `region: us-east-1` as a literal (or `${env.region}` — both valid; use `${env.region}` to demonstrate). +- `tests/test_sample_contracts_interpolate.py`: resolving the sample contracts produces concrete bucket names like `acdl-dev-static-assets-000000000000-us-east-1`. +- Verify: `pytest tests/test_sample_contracts_interpolate.py` passes; `run_platform.sh --check-only` exits 0 (resolver expands before adapter). + +### Must-haves (Phase 40) +- [ ] `schemas/environment.schema.json` exists; 4 env files validate. +- [ ] `_expand_vars` in resolver; unknown tokens raise. +- [ ] Sample contracts use `${env.*}` + `${contract.*}` naming patterns. +- [ ] `tests/test_environment_schema.py` + `tests/test_interpolation.py` + `tests/test_sample_contracts_interpolate.py` pass. +- [ ] `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0. --- -## Phase 31 — encryption-by-default-and-per-stack-cmk +## Phase 41 — per-environment-ci-jobs -**Requirements:** REQ-83 (kms-key primitive), REQ-84 (encryption NFRs on all primitives), REQ-85 (L2 CMK wiring) -**Personas:** platform-engineer (lead), security-engineer -**Type:** feat +**Requirements:** REQ-105, REQ-106 +**Personas:** backend-engineer (lead), security-engineer (HITL gate review) +**Branch:** `phase/41-per-environment-ci-jobs` -### Tasks (Wave 1 — kms-key primitive + adapter): -1. **platform-engineer:** Create `modules/l1/kms-key/` with `interface.json` (type `aws:kms:key`, inputs: description/region/deletion_window_days, outputs: kms_key_arn/kms_key_id, NFRs: enable_rotation default true, deletion_protection default true) + `instance.json` + `README.md` + `examples/`. -2. **platform-engineer:** Add `aws:kms:key → aws_kms_key` + `aws:kms:alias → aws_kms_alias` to adapter TYPE_MAP. Emit `enable_key_rotation = true` + alias. +### Task 41.1 — Per-env contract files (REQ-105, backend-engineer) +- `contracts/static-assets.dev.yaml`, `.qa.yaml`, `.prod.yaml`, `.dr.yaml` — each sets `environment:` to its own name; `inputs.bucket_name` uses `${env.environment}-${contract.module}-${env.account_id}-${env.region}` interpolation (so the file content is near-identical; only `environment:` differs). +- `contracts/microservice.{dev,qa,prod,dr}.yaml` — same pattern. +- Keep `contracts/static-assets.yaml` + `contracts/microservice.yaml` as the dev default (backwards compat). +- `tests/test_per_env_contracts.py`: all 8 per-env files validate against `schemas/contract.schema.json`; each resolves to a stack with the correct environment. +- Verify: `pytest tests/test_per_env_contracts.py` passes. -### Tasks (Wave 2 — encryption NFRs on all primitives, after Wave 1): -3. **platform-engineer:** Add `encryption_enabled` NFR (default true) + `kms_key_arn` input to every L1 `interface.json` (s3, rds, ecr, ecs-service, ecs-cluster, alb, cloudfront, waf, vpc, iam-role). Update adapter to emit encryption blocks referencing the CMK when `kms_key_arn` is provided; managed KMS fallback with stderr warning when not. -4. **platform-engineer:** Update both L2 `composition.json` files — add `kms-key` child + wires connecting `kms_key_arn` output to each child's `kms_key_arn` input. -5. **platform-engineer:** Add `tests/test_encryption.py` — assert every primitive has encryption NFRs; assert adapter emits encryption blocks; assert L2 wires CMK; assert `enable_key_rotation = true`. +### Task 41.2 — Deploy workflow `environment` input (REQ-106, backend-engineer) +- `.github/workflows/deploy.yml` + `.gitea/workflows/deploy.yml` (byte-identical): add `environment` input (`type: string`, default `""`, description "Target environment override (dev/qa/prod/dr); when empty, the contract's environment field is used"). +- `scripts/run_platform.sh`: add `--environment ` flag. When set, override the contract's `environment` field at load time (before schema validation per D-088, so interpolation context is consistent). Re-run the onboarding check against the supplied env. +- The workflow's "Run the platform pipeline" step passes `--environment ${{ inputs.environment }}` when non-empty. +- `tests/test_deploy_workflow_env_input.py`: both deploy workflows declare the `environment` input; byte-identical; `run_platform.sh --environment qa contracts/static-assets.yaml` produces a stack whose env is qa (tested via the resolver directly since run_platform.sh needs AWS for full mode — test the override logic in the resolver). +- `core/contract_resolver.py` `resolve()`: accept optional `environment_override` arg; when set, set `contract["environment"] = override` before schema validation + interpolation. +- Verify: `pytest tests/test_deploy_workflow_env_input.py` passes; both deploy workflows byte-identical. -### Must-haves: -- kms-key primitive exists + registered -- All primitives have `encryption_enabled` NFR + `kms_key_arn` input -- L2 modules wire per-stack CMK -- Adapter emits encryption blocks -- `pytest` passes (~310) +### Task 41.3 — Per-env caller workflow docs + HITL gate structure (REQ-106, security-engineer review) +- `docs/CONSUMER_GUIDE.md`: add a "Per-environment deployment" section with 4 caller-workflow examples (`.github/workflows/deploy-dev.yml`, `deploy-qa.yml`, `deploy-prod.yml`, `deploy-dr.yml`), each `uses: acdl/.github/workflows/deploy.yml@v1.9` with `environment: ` + `contract: .acdl/..yaml`. Document: "Promotion = running the matching job; no `environment:` field editing." +- HITL gate structure (wired in Phase 42, documented here): qa/prod/dr caller workflows use `workflow_dispatch` with approval inputs (`approve_qa`, `approve_prod`, `approve_dr`) per `hitl_matrix_design.md` D-042; `gitea.actor` / `github.actor` is the approver of record. dev is autonomous (no gate). +- `tests/test_consumer_guide_per_env_section.py`: the consumer guide has the per-env section with 4 caller examples. +- Verify: `pytest tests/test_consumer_guide_per_env_section.py` passes. + +### Must-haves (Phase 41) +- [ ] 8 per-env contract files exist + validate + resolve. +- [ ] Deploy workflow has `environment` input (byte-identical Gitea + GitHub). +- [ ] `run_platform.sh --environment ` overrides; resolver supports `environment_override`. +- [ ] Consumer guide documents per-env caller workflows + promotion-without-editing. +- [ ] `tests/test_per_env_contracts.py` + `tests/test_deploy_workflow_env_input.py` + `tests/test_consumer_guide_per_env_section.py` pass. +- [ ] `run_ci.sh` exits 0; both deploy workflows byte-identical. --- -## Phase 32 — deletion-protection-by-default-and-l2-feature-flag +## Phase 42 — stub-implementation -**Requirements:** REQ-86 (deletion_protection NFR on all primitives), REQ-87 (L2 feature flag) -**Personas:** platform-engineer (lead), backend-engineer -**Type:** feat +**Requirements:** REQ-107, REQ-108, REQ-109, REQ-110, REQ-111 +**Personas:** security-engineer (lead), backend-engineer (run_platform wiring), lambda-engineer (SNS topic Terraform) +**Branch:** `phase/42-stub-implementation` -### Tasks (Wave 1): -1. **platform-engineer:** Add `deletion_protection` NFR (boolean, default true) to every L1 `interface.json` (rds already has it). Update adapter to emit `lifecycle { prevent_destroy = true }` when NFR is true; omit when false. RDS gets BOTH `deletion_protection` arg + `prevent_destroy` lifecycle. -2. **backend-engineer:** Add `features` object support to `schemas/stack.schema.json` (optional `features.deletion_protection`). Update `core/contract_resolver.py` `resolve_l2()` to propagate `features.deletion_protection` to each child's `deletion_protection` NFR. Add `inputs.deletion_protection` to `schemas/contract.schema.json` (optional boolean). -3. **platform-engineer:** Add `tests/test_deletion_protection.py` — assert every primitive has the NFR; assert adapter emits `prevent_destroy`; assert L2 feature flag propagation. +### Task 42.1 — route_halt_artifact real (REQ-107, security-engineer + lambda-engineer) +- `core/separation_of_duties.py` `route_halt_artifact`: when `ACDL_SOD_HALT_TOPIC_ARN` set, publish to SNS via boto3 (`sns.publish(TopicArn=arn, Message=..., Subject="ACDL SoD halt")`); when unset, fall back to structured stderr emission + a `SEPARATION_OF_DUTIES_VIOLATION` event write via `outbox_writer.write_event` (so the halt is in the audit chain). No silent print-only stub. +- `terraform/platform/main.tf`: add `aws_sns_topic.acdl-sod-halt` + a basic access policy (allow the platform Lambda / runner role to publish). Output the topic ARN. +- `tests/test_route_halt_artifact.py`: (a) with `ACDL_SOD_HALT_TOPIC_ARN` set, moto-mocked SNS receives the publish; (b) without it, a `SEPARATION_OF_DUTIES_VIOLATION` event is written to the outbox (moto-mocked DynamoDB); (c) stderr emission occurs in both cases. +- Verify: `pytest tests/test_route_halt_artifact.py` passes. -### Must-haves: -- Every primitive has `deletion_protection` NFR (default true) -- Adapter emits `prevent_destroy = true` when true -- L2 feature flag propagates -- `pytest` passes (~320) +### Task 42.2 — HITL attestation gates (REQ-108, security-engineer + backend-engineer) +- `core/hitl_gates.py`: `attest(contract_id, env, approver, evidence, outbox_client=None)` → records `approver_qa`/`approver_prod`/`approver_dr` to the outbox item for `contract_id`; runs `separation_of_duties.check(outbox_client, contract_id, approver)` on prod; invokes the attestation matrix (Task 42.3) for the target env; returns `(ok, reason)`. Dev skips (returns `(True, "dev autonomous")`). +- `scripts/run_platform.sh`: before apply (for qa/prod/dr), call `hitl_gates.attest` with the approver from `GITHUB_ACTOR`/`GITEA_ACTOR` env. Block on `(ok=False)`. +- `tests/test_hitl_gates.py`: (a) dev skips; (b) qa records `approver_qa` (moto outbox); (c) prod records `approver_prod` + SoD blocks when `approver_qa == approver_prod`; (d) prod passes when approvers differ. +- Verify: `pytest tests/test_hitl_gates.py` passes. + +### Task 42.3 — 8-concern attestation matrix (REQ-109, security-engineer) +- `core/attestation_matrix.py`: `check(env, evidence_bundle)` → runs the 8 concerns. Offline-testable concerns (contract NFRs, schema validity, policy pass) run for real. Operator-supplied concerns accept an uploaded signed evidence artifact (JSON with `timestamp`, `type`, `payload`, optional `signature`); validate freshness (within the declared window from `hitl_matrix_design.md` §10.4) + schema (per-concern). Signature verification via KMS when `ACDL_ATTESTATION_SIGNING_KEY_ID` set; skipped + logged when unset (D-089). Fail loud if missing/expired for prod/dr. +- `hitl_gates.attest` calls `attestation_matrix.check(env, evidence)` and blocks on any failing concern. +- `tests/test_attestation_matrix.py`: (a) offline concerns pass for a valid contract; (b) operator-supplied concern missing → block for prod; (c) operator-supplied concern present + fresh → pass; (d) expired artifact → block; (e) signature skip when key unset (logged). +- Verify: `pytest tests/test_attestation_matrix.py` passes. + +### Task 42.4 — Wiz real API client (REQ-110, security-engineer) +- `adapters/wiz/wiz_adapter.py`: add `WizClient` class — `__init__` reads `WIZ_API_TOKEN` + `WIZ_API_URL`; `fetch_issues(filter_by)` queries the Wiz GraphQL API (`/graphql`, Bearer auth, `issues` query). Translate results → `PolicyCheckResult` records (`engine: "wiz"`, `ruleId: `, `severity: `, `status: FAIL`, `message: `, `resource: <entity.name>`). Graceful degrade: when `WIZ_API_TOKEN` or `WIZ_API_URL` unset → emit the existing single `SKIPPED` `WIZ_NOT_CONFIGURED` record (no network call). Pagination handled via `pageInfo.hasNextPage`. +- `tests/test_wiz_adapter_real_client.py`: (a) with a recorded GraphQL fixture, `WizClient` translates issues → `PolicyCheckResult` records; (b) graceful degrade when env unset; (c) pagination follows `endCursor`. +- Verify: `pytest tests/test_wiz_adapter_real_client.py` passes. + +### Task 42.5 — Kyverno translator fleshed out (REQ-111, security-engineer) +- `adapters/kyverno/kyverno_adapter.py`: full `PolicyReport` → `PolicyCheckResult` mapping — handle `pass`/`fail`/`skip`/`warn` results, severity mapping (critical/high/medium/low/info), resource extraction, skip-with-reason handling. Keep the inactive-for-Terraform guard (emits a single `SKIPPED` `KYVERNO_INACTIVE_TF_STACK` record when no K8s manifests). Add a `--kube-version` stub (parsed but not yet used — for future GitOps). +- `tests/test_kyverno_adapter.py`: expand — (a) `pass` result → `PolicyCheckResult` with `status: PASS`; (b) `fail` with severity → correct severity mapping; (c) `skip` with reason → `SKIPPED` record; (d) inactive-for-TF guard emits the `KYVERNO_INACTIVE_TF_STACK` record. +- Verify: `pytest tests/test_kyverno_adapter.py` passes. + +### Must-haves (Phase 42) +- [ ] `route_halt_artifact` real (SNS + outbox fallback); SNS topic in Terraform. +- [ ] `hitl_gates.py` attests qa/prod/dr; SoD blocks on identity equality. +- [ ] `attestation_matrix.py` implements 8 concerns (offline-testable + signed artifacts). +- [ ] Wiz adapter real client + graceful degrade. +- [ ] Kyverno translator fleshed out + inactive guard preserved. +- [ ] All 5 new test files pass; `run_ci.sh` exits 0. --- -## Phase 33 — uptime-kuma-primitive +## Phase 43 — verify-review-audit-complete -**Requirements:** REQ-88 (uptime primitive), REQ-89 (deployed by default after L2), REQ-90 (feature flag), REQ-91 (pipeline stage) -**Personas:** platform-engineer (lead), backend-engineer -**Type:** feat +**Requirements:** — (milestone gate) +**Personas:** lead-developer (lead), all personas (review participation) +**Branch:** `phase/43-verify-review-audit-complete` -### Tasks (Wave 1 — primitive + adapter): -1. **platform-engineer:** Create `modules/l1/uptime/` with `interface.json` (type `aws:ecs:uptime-service`, inputs: container_image/region/monitored_endpoints/static_checks/alert_channels/feature_flag_enabled/cpu/memory, outputs: uptime_url/service_arn/task_definition_arn, NFRs: deletion_protection/encryption_enabled) + `instance.json` + `README.md` + `examples/simple.yaml` + `examples/complex.yaml`. -2. **platform-engineer:** Add `aws:ecs:uptime-service` to adapter TYPE_MAP. Emit ECS Fargate task + service + ALB + listener + EFS volume + CloudWatch log group. When `feature_flag_enabled=false`, emit NO resources. Register in `registry.json`. +### Task 43.1 — 4-layer verify +- Structural: all new files present (environment.schema.json, 4 env files, 8 per-env contracts, hitl_gates.py, attestation_matrix.py, SNS topic in main.tf, 5+ new test files). +- Behavioral: `pytest` passes (count increases from v1.8's 350 by ~30+ new tests); `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0. +- Security: no hardcoded adapter defaults; HITL gates block on SoD violation; attestation matrix fails loud on missing evidence for prod/dr; Wiz degrades gracefully. +- Quality: each new feature has dedicated tests (interpolation, per-env jobs, SoD, HITL gates, attestation matrix, Wiz, Kyverno). -### Tasks (Wave 2 — pipeline + script, after Wave 1): -3. **backend-engineer:** Add `deploy-uptime` stage to `pipelines/deploy.yaml` (after `publish-outputs`). Update both deploy workflows (byte-identical) with the stage. Add `scripts/seed_uptime_monitors.py` for post-deploy monitor seeding via uptime-kuma API. -4. **backend-engineer:** Update `scripts/run_platform.sh` — add `deploy-uptime` step: read L2 stack outputs, construct synthetic uptime contract with `monitored_endpoints` from outputs, run second terraform apply with separate state (`$WORK/uptime-tf/`), publish uptime URL via PR comment. Skip when `inputs.uptime_enabled=false`. -5. **backend-engineer:** Add `tests/test_uptime_primitive.py` — validate interface; assert adapter emits ECS service when flag=true; assert no resources when flag=false; assert `deploy-uptime` stage in pipeline contract. +### Task 43.2 — Multi-persona review +- `ciagent-review` across the v1.9 diff (phases 39–42). Auto-apply P0; flag P1+ for post-hoc. +- Reconstruct `.ciagent/REVIEW.md` with v1.9 content (D-086). Note that v1.3–v1.8 reviews were not persisted (no git-history rewrite). -### Must-haves: -- Uptime primitive exists with feature flag + alert channels -- Deployed by default after L2 (separate state) -- Uptime URL published via PR comment -- Feature flag disables deployment -- `deploy-uptime` stage in deploy contract + byte-identical workflows -- `pytest` passes (~335) +### Task 43.3 — Audit +- Reconstruction: git log matches `.ciagent/` files. +- File discipline: all `.ciagent/` files valid. +- Branch hygiene: stale branches cleaned. +- Commit discipline: all commits have `---ci---` blocks. + +### Task 43.4 — Complete +- Update `.ciagent/REQUIREMENTS.md`: mark REQ-100..REQ-111 complete; add v1.9 traceability table. +- Update `.ciagent/ROADMAP.md`: add v1.9 milestone section (complete). +- Update `.ciagent/PROJECT.md`: v1.9 status → complete. +- Tag `v1.9.0`; update floating `v1.9` + `v1` tags. +- Bump `uses:`/`ref:` from `@v1.6` → `@v1.9` in `contracts/*.yaml`, `deploy.yml` checkout `ref:`, `docs/CONSUMER_GUIDE.md` (D-071 successor). +- Commit: `docs(milestone): complete v1.9`. + +### Must-haves (Phase 43) +- [ ] 4-layer verify PASS. +- [ ] Review: 0 new P0; P1+ flagged for post-hoc. +- [ ] Audit: clean. +- [ ] Tag `v1.9.0` created; floating tags updated. +- [ ] `uses:`/`ref:` bumped to `@v1.9`. +- [ ] REQUIREMENTS.md + ROADMAP.md + PROJECT.md updated. --- -## Phase 34 — decommission-alias-and-cmdb-validation - -**Requirements:** REQ-92 (decommission mode), REQ-93 (CMDB validation), REQ-94 (consumer docs) -**Personas:** backend-engineer (lead), lambda-engineer, security-engineer -**Type:** feat - -### Tasks (Wave 1 — CMDB + Lambda, parallel): -1. **lambda-engineer:** Add `acdl-change-requests` DynamoDB table to `terraform/platform/main.tf` (PK changeRequestId, SK submittedAt, SSE via CMK, PITR). Add `validate_change_request` action to `core/lambda/contract_ingestor.py` — query table, assert status=approved + consumerRepo match, return CR details or 403. -2. **backend-engineer:** Add `decommission_transform(stack_instance)` to `core/contract_resolver.py` — zero all counts (desired_count=0 for ECS, etc.). - -### Tasks (Wave 2 — pipeline + docs, after Wave 1): -3. **backend-engineer:** Add `mode: decommission` to deploy workflow inputs. Add decommission stages to `pipelines/deploy.yaml`: validate-change-request → disable-deletion-protection (HITL SRE gate via GitHub environment) → zero-counts (second HITL SRE gate) → confirm-decommission. Update both deploy workflows (byte-identical). -4. **backend-engineer:** Update `docs/CONSUMER_GUIDE.md` with "Decommissioning a stack" section (request CR, trigger decommission, HITL gates, what happens). -5. **backend-engineer:** Add `tests/test_decommission.py` — assert `decommission_transform` zeroes counts; assert `validate_change_request` rejects invalid CRs; assert decommission stages in pipeline contract. - -### Must-haves: -- Decommission mode on existing deploy pipeline -- 2-step with HITL SRE gates -- CR ID validated against DynamoDB CMDB -- Documented in consumer guide -- `pytest` passes (~345) - ---- - -## Phase 35 — module-engineering-standards - -**Requirements:** REQ-95 (STANDARDS.md), REQ-96 (catalog fix + template update) -**Personas:** lead-developer (lead), platform-engineer, backend-engineer -**Type:** docs + refactor - -### Tasks (Wave 1): -1. **lead-developer:** Author `modules/STANDARDS.md` — comprehensive L1+L2 authoring + review standards (scanned from current modules per RESEARCH TARGET 6): required files, interface schema, input/output/NFR conventions, encryption + deletion protection as mandatory NFRs, naming, multi-resource pattern, adapter extension pattern, code review checklist. -2. **lead-developer:** Fix `modules/README.md` catalog index — add rds + uptime + kms-key to Primitives table. Update `modules/README-TEMPLATE.md` — add `## NFRs` section. -3. **backend-engineer:** Add `tests/test_module_standards.py` — automated enforcement: every L1 has `deletion_protection` + `encryption_enabled` NFRs; every L2 has valid structure; every module registered; every module has README + examples. - -### Must-haves: -- `modules/STANDARDS.md` exists with L1+L2 standards -- Catalog index includes all primitives -- Template has NFRs section -- Automated standards test passes -- `pytest` passes (~355) - ---- - -## Phase 36 — schemas-adapters-pipelines-readmes - -**Requirements:** REQ-97 (schemas README), REQ-98 (pipelines README), REQ-99 (adapters README) -**Personas:** lead-developer (lead), backend-engineer, platform-engineer -**Type:** docs - -### Tasks (Wave 1 — parallel): -1. **lead-developer:** Author `schemas/README.md` — how to write schemas, wire into platform, test in CI, dependencies, existing catalog. -2. **lead-developer:** Author `pipelines/README.md` — how to write pipeline contracts, wire into workflows, test, dependencies, catalog. -3. **lead-developer:** Author `adapters/README.md` — how to write adapters, wire into platform, test, dependencies, catalog. -4. **backend-engineer:** Add `tests/test_docs_coverage.py` — assert all 3 READMEs exist + contain required sections. - -### Must-haves: -- All 3 READMEs exist with comprehensive documentation -- CI validates presence -- `pytest` passes (~358) - ---- - -## Phase 37 — verify - -**Personas:** lead-developer (lead), all personas -**Type:** verify - -### Tasks: -1. Structural: all new files present. -2. Behavioral: `pytest` passes (~358); `run_ci.sh` exits 0; `run_platform.sh --check-only` exits 0. -3. Security: no secrets; CMK enforced; no placeholder account IDs; deletion protection on by default. -4. Quality: each P1 fix + each new feature has a dedicated test. - ---- - -## Phase 38 — review-audit-complete - -**Personas:** lead-developer (lead), all personas -**Type:** review + audit + complete - -### Tasks: -1. Review: 0 new P0/P1; all P1-3..P1-9 + S1 resolved; 3 new requirements delivered. -2. Audit: reconstruction, file discipline, branch hygiene, commit discipline. -3. Complete: update REQUIREMENTS.md (REQ-76..99), ROADMAP.md, PROJECT.md. Tag `v1.8.0`. Update floating `v1.8` + `v1` tags. Bump `uses:` to `@v1.8`. \ No newline at end of file +*End of PLAN.md.* \ No newline at end of file