diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md index d8be636..32fb80a 100644 --- a/.ciagent/PLAN.md +++ b/.ciagent/PLAN.md @@ -1,453 +1,1316 @@ --- -phase: 6 -name: archive-demo-and-reorient +phase: 7 +name: architecture-v1-finalization milestone: v1.1 milestone_type: feature status: planned -requirements: [] # Phase 06 introduces NO new REQ — it is repo hygiene (ROADMAP §Phase 06). +requirements: [REQ-16, REQ-17, REQ-18, REQ-19, REQ-20, REQ-21, REQ-22] must_haves: - - "demo/ contains the full v1.0 demo (modules/, scripts/, evidence-ui/, contracts/, contracts-repo/, .gitea/workflows/, ACDL_DEMO.md)" - - "demo/scripts/run_demo.sh --no-upload exits 0 (regression: the archived demo still runs from demo/)" - - "New top-level dirs exist and are empty-but-scaffolded with a .gitkeep: platform/, schemas/, adapters/, terraform/, modules-ir/" - - "A new top-level scripts/ dir exists with scripts/verify_phase06.sh (v1.1 verify scripts live at top-level scripts/, NOT demo/scripts/)" - - "README.md reflects the real platform (vision + architecture links, new layout); the v1.0 demo README content moves to demo/ACDL_DEMO.md (already there)" - - ".gitignore updated to ignore runner-data/ (untracked demo runner artifact) if not already" - - "No stray empty dirs left at repo root from the moves (contracts-repo/ etc.)" + # REQ-16 — architecture snapshot + status bump + - "docs/architecture-v1.0.md exists as a snapshot of docs/architecture.md with all 11 open decisions applied; status note bumped from v0.2 to v1.0; a 'v1.1 spike = Phases 08-10' + 'v1.2 build-out' note is present" + - "All 11 open-decision IDs are recorded in PROJECT.md (W1.A, W1.B, W2.A, W3.D, W3.E, BA.A, BA.B, BA.C, BA.D, BA.E, BA.F) + the OpenTofu timing sub-decision (Q1.3)" + # REQ-17 — Target Stack IR schema + - "schemas/ir.schema.json exists, validates as JSON Schema draft 2020-12, and encodes: resources (typed inputs/outputs/NFRs), relationships (single parent per child), composition (tree, max-depth-5), policy hooks; substrate-agnostic (no Terraform-specific terms in the schema body)" + # REQ-18 — PolicyCheckResult schema + Checkov adapter + - "schemas/policy_check_result.schema.json exists, validates as JSON Schema draft 2020-12, and matches ARCHITECTURE.md §12.6 canonical shape" + - "adapters/terraform/policy/checkov_adapter.py exists, py_compiles, and translates Checkov JSON → PolicyCheckResult records; maps CKV_AWS_* to the 4 L2 checks + tag/naming; emits a single ACDL_TAG_NAMING SKIPPED PolicyCheckResult (severity: info) per D-043" + # REQ-19 — confidence signal + - "platform/confidence_signal.py exists, py_compiles, and implements the 6-input weighted sum (policy 0.30, validation 0.25, freshness 0.10, source 0.15, history 0.10, nfrs 0.10 per D-040); per-env thresholds (dev 0.50, qa 0.75, prod 0.90, dr 0.95); severity→penalty (critical hard-override, high -0.2, medium -0.05, low -0.01, info 0.0); output {score, band, perInput, reasonCodes}; halts with reason on missing input" + # REQ-20 — audit ledger design + - "platform/audit_ledger_design.md exists and specifies: S3 Object Lock compliance mode 7-yr + DynamoDB outbox RPO=0 + JWS detached signature (platform KMS key, quarterly rotation) + prev_event_hash SHA-256 chain over canonical JSON + daily checkpoints; spike scope (D-041) = v1.0 hash chain + outbox write; v1.2 build-out = Object Lock + JWS + async worker + DLQ clearly marked" + # REQ-21 — HITL matrix + SoD + - "platform/hitl_matrix_design.md exists and contains the full 8-concern attestation matrix from docs/architecture.md §10.4; pre-execution gate model; timeout 1d warn / 2d freeze; rejection → HELD + supersedes; reviewer routing via CODEOWNERS + Gitea workflow_dispatch gitea.actor (D-042, no Environments API)" + - "platform/separation_of_duties.py exists, py_compiles, and reads approver_qa from the DynamoDB outbox, compares to the prod-dispatch gitea.actor, blocks + emits SEPARATION_OF_DUTIES_VIOLATION on equality, routes halt artifact to SRE on-call" + # REQ-22 — contract schema + - "schemas/contract.schema.json exists, validates as JSON Schema draft 2020-12, and encodes per-env mandatory inputs (dev: stack+environment; qa adds validation.e2eSuite+loadTest; prod adds runbook+dashboard+oncall; dr adds drDrillRef); inputs always optional; profile: agentic marker with naturalLanguageIntent/confidenceAtSubmission/agentTrace optional everywhere" + # verification + - "scripts/verify_phase07.sh exists and asserts: all 6 schema/design files exist; the 3 JSON Schemas validate (python3 -m jsonschema); the 2 .py files py_compile; the 2 .md design files are non-empty; all 11 decision IDs + OpenTofu present in PROJECT.md; docs/architecture-v1.0.md exists + status is v1.0" + - "Traceability updated: REQUIREMENTS.md REQ-16..22 → covered/complete; ROADMAP.md Phase 07 → complete" verification: - typecheck: "bash -n demo/scripts/*.sh && python3 -m py_compile demo/scripts/*.py" - test: "scripts/verify_phase06.sh # lives at TOP-LEVEL scripts/, not demo/scripts/" - build: "no-op # Phase 06 is repo hygiene; terraform init is Phase 09+" + typecheck: "python3 -m py_compile platform/confidence_signal.py platform/separation_of_duties.py adapters/terraform/policy/checkov_adapter.py && python3 -m jsonschema schemas/ir.schema.json schemas/policy_check_result.schema.json schemas/contract.schema.json" + test: "scripts/verify_phase07.sh" + build: "no-op # Phase 07 is design-authoring; terraform init is Phase 09+" --- -# Phase 06 — archive-demo-and-reorient PLAN +# Phase 07 — architecture-v1-finalization PLAN ## Goal -Archive the complete v1.0 demo under `demo/` (preserve it as the intent -reference per D-037), establish the new v1.1 repo layout -(`platform/`, `schemas/`, `adapters/`, `terraform/`, `modules-ir/`), and -rewrite `README.md` to reflect the real Agentic Cloud Delivery Platform. -Verify the archived demo still runs end-to-end from `demo/` via -`demo/scripts/run_demo.sh --no-upload` (regression gate). +Finalize the ACDL architecture to v1.0 by authoring the 6 locked +schema/design files + 1 architecture snapshot that resolve all 11 open +decisions recorded in `PROJECT.md` (D-038). This phase is +**design-authoring, not runtime code**: the files are the contracts the +v1.1 spike (Phases 08-10) implements against, not the spike itself. -This is **repo hygiene only** — no new code, no new REQ. It reorients the -repo from "the v1.0 demo is the repo" to "the v1.0 demo is an archived -artifact under `demo/`; the repo root is now the real platform's home." +The deliverables, mapped to requirements: + +| REQ | File(s) | Owner persona (PERSONAS.md territory) | +|-----|---------|---------------------------------------| +| REQ-16 | `docs/architecture-v1.0.md` (snapshot of `docs/architecture.md` with 11 resolutions applied + status bumped to v1.0) | lead-developer (owns `docs/**` meta) | +| REQ-17 | `schemas/ir.schema.json` (Target Stack IR) | platform-engineer (owns the IR; `schemas/` co-owned with backend-engineer) | +| REQ-18 | `schemas/policy_check_result.schema.json` + `adapters/terraform/policy/checkov_adapter.py` | security-engineer (owns `adapters/terraform/policy/**`); schema co-authored | +| REQ-19 | `platform/confidence_signal.py` (6-input weighted signal) | backend-engineer (6-input weighted sum + thresholds) + security-engineer (severity→penalty + critical-override) — co-owned per PERSONAS.md conflict resolutions | +| REQ-20 | `platform/audit_ledger_design.md` (tiered ledger) | security-engineer | +| REQ-21 | `platform/hitl_matrix_design.md` + `platform/separation_of_duties.py` | security-engineer | +| REQ-22 | `schemas/contract.schema.json` (contract schema) | backend-engineer (owns `schemas/**` contract + IR + PolicyCheckResult) | +| — | `scripts/verify_phase07.sh` + traceability update | lead-developer | + +The spike scope (D-041, D-043) means the designs author the **full +target** but clearly mark what is spike-scope vs v1.2 build-out. The spike +(Phases 08-10) only runs `terraform plan` + Checkov + confidence + outbox +write; Object Lock / JWS / HITL-wiring / L3B-skill-catalog are design-only +in Phase 07. ## Requirements covered -**None new.** Phase 06 carries no REQ-NN entry (ROADMAP §Phase 06: -"Requirements: (no new REQ; repo hygiene)"). It is a structural -prerequisite for Phase 07 (architecture-v1-finalization), which owns -REQ-16..REQ-22. +- **REQ-16** → T-7.1 (`docs/architecture-v1.0.md` snapshot + status bump). +- **REQ-17** → T-7.2 (`schemas/ir.schema.json`). +- **REQ-22** → T-7.3 (`schemas/contract.schema.json`). +- **REQ-18** → T-7.4 (`schemas/policy_check_result.schema.json`) + + T-7.5 (`adapters/terraform/policy/checkov_adapter.py`). +- **REQ-20** → T-7.6 (`platform/audit_ledger_design.md`). +- **REQ-21** → T-7.7 (`platform/hitl_matrix_design.md`) + + T-7.8 (`platform/separation_of_duties.py`). +- **REQ-19** → T-7.9 (`platform/confidence_signal.py`, co-authored). +- **All** → T-7.10 (`scripts/verify_phase07.sh` + traceability update). -**Dependency:** the v1.0 demo must be complete and tagged. It is — tag -`v1.1.0` exists (verified: `git tag` lists `v1.0.1`..`v1.0.5`, `v1.1.0`). -All v1.0 phases shipped and audited PASS (ROADMAP §v1.0). The archive -preserves the demo immutably; v1.0 tags are not rewritten (PROJECT.md -operational parameter: "v1.0 tags preserved"). - -**No upstream phase dependency within v1.1** — Phase 06 is the first -v1.1 phase. RESEARCH TARGET 10 (conf 0.90) provides the authoritative -move list and confirms via static analysis that no path fixups are -needed: all demo scripts resolve paths via `SCRIPT_DIR`/`REPO_ROOT` -relative-to-script, which auto-adjust when the tree moves to `demo/`. +All 7 requirements (REQ-16..REQ-22) are Phase 07 per REQUIREMENTS.md +traceability table. Phase 07 depends on Phase 06 (complete, v1.1.1): +the new repo layout (`schemas/`, `platform/`, `adapters/`) is scaffolded +empty with `.gitkeep` and ready to receive these files. ## Waves Domain priority order from `PERSONAS.md`: `coordination → security → platform → backend → frontend`. -Phase 06 active personas (PERSONAS.md "Phase-specific overrides"): -**lead-developer** (coordination — owns the moves, the new layout, the -README, and the verify script) + **frontend-engineer** (demo UI move -confirmation only). Backend/platform/security are idle this phase. +Phase 07 active personas (PERSONAS.md "Phase-specific overrides"): +**lead-developer** (coordination — the snapshot + verify script), +**backend-engineer** (schemas: contract + co-IR + co-PolicyCheckResult + +co-confidence), **security-engineer** (HITL/ledger/SoD/Checkov-adapter/ +co-confidence), **platform-engineer** (IR). frontend-engineer idle. -Three waves, strictly ordered (each wave's tasks depend on the prior -wave completing): +The files are mostly independent (no runtime dependencies between +schemas), but there are logical dependencies: +- REQ-16 (architecture snapshot) is the umbrella — it references all + the others. +- The 3 JSON Schemas (REQ-17, REQ-18-partial, REQ-22) are independent. +- REQ-19 (confidence_signal.py) consumes PolicyCheckResult records + (REQ-18) — depends on T-7.4. +- REQ-21 (separation_of_duties.py) reads the outbox schema (REQ-20) — + depends on T-7.6. +- REQ-20 + REQ-21 are both security-engineer; one wave, sequential + within. ---- - -### Wave 1 — lead-developer: file moves + new layout + README + verify script - -The structural reorientation. All moves use `git mv` to preserve history. -The v1.0 verify scripts move *with* `scripts/` into `demo/scripts/` (they -are demo artifacts); the *new* v1.1 verify script lives at a *new* -top-level `scripts/` dir (created fresh in this wave). - -#### T-6.1 — Move the v1.0 demo tree into `demo/` - -- **Owner:** lead-developer (territory: `.ciagent/**`, `README.md`, - `.gitignore`, top-level layout). -- **Files owned:** `demo/` (new), everything moved into it. -- **Commit message:** - `phase: 6, status: plan-as-execute, persona: lead-developer, task: T-6.1` -- **Commands to run verbatim** (cross-checked against RESEARCH.md - TARGET 10's move list; verified the dirs exist and are tracked - via `git ls-files`): - - ```bash - # From the repo root (/root/acdl). All moves via `git mv` to preserve history. - # 1. Create the demo/ container. - mkdir -p demo - - # 2. Move the five demo dirs + the demo deck + the demo contracts-repo. - git mv modules demo/modules - git mv scripts demo/scripts # carries verify_phase01..05.sh (v1.0 demo verify scripts) - git mv evidence-ui demo/evidence-ui - git mv contracts demo/contracts # only holds examples/ (v1.0 sample contracts) - git mv contracts-repo demo/contracts-repo # tracked: .gitea/workflows/{.gitkeep,issue-to-contract.yml} - git mv .gitea demo/.gitea # carries workflows/{.gitkeep,pipeline.yml} - git mv ACDL_DEMO.md demo/ACDL_DEMO.md # the v1.0 presentation deck (was untracked — `git add` it first) - - # 3. Handle the untracked demo runner artifact: do NOT git mv (it's untracked). - # Move it out of the repo root so it doesn't linger as a stray dir. - mv runner-data demo/runner-data # plain mv — not tracked; runner-data/.runner - # (runner-data/ is added to .gitignore in T-6.4 so it stays a local artifact.) - - # 4. Sanity: no empty stray dirs remain at repo root. - # (git mv removes the source dir when it becomes empty; verify with `ls`.) - ``` - - **Cross-check vs RESEARCH.md TARGET 10 move list:** - - `modules/ → demo/modules/` ✓ - - `scripts/ → demo/scripts/` ✓ (carries v1.0 verify_phase01..05.sh — those - are *demo* verify scripts; the v1.1 verify script is created in T-6.3 - at a *new* top-level `scripts/` dir) - - `evidence-ui/ → demo/evidence-ui/` ✓ - - `contracts/ → demo/contracts/` ✓ - - `.gitea/workflows/{.gitkeep,pipeline.yml} → demo/.gitea/workflows/` ✓ - (achieved by moving the whole `.gitea/` dir) - - `ACDL_DEMO.md → demo/ACDL_DEMO.md` ✓ - - `contracts-repo/ → demo/contracts-repo/` ✓ (tracked; confirmed via - `git ls-files contracts-repo/` → `.gitea/workflows/.gitkeep` + - `.gitea/workflows/issue-to-contract.yml`) - - `runner-data/ → demo/runner-data/` ✓ (untracked; plain `mv`) - - **Subtlety (per task instructions):** the v1.1 verify scripts live at a - *new* top-level `scripts/` dir (created in T-6.3), NOT inside `demo/scripts/` - (which holds the v1.0 demo verify scripts `verify_phase01..05.sh`). This - avoids colliding the v1.1 verification toolchain with the archived demo's. - - **Risk note (R-5 from RESEARCH.md):** static analysis found only - `/tmp/...` temp-file writes in the demo scripts (e.g. - `scripts/gitea_setup.sh`, `scripts/verify_phase01.sh`) — no source-root - absolute paths. All demo scripts use `SCRIPT_DIR`/`REPO_ROOT` - relative-to-script, which auto-resolve to `demo/` after the move. The - regression check in T-6.3 / Wave 3 confirms this empirically. - -#### T-6.2 — Scaffold the new v1.1 top-level dirs (empty, .gitkeep'd) - -- **Owner:** lead-developer (territory: top-level layout). -- **Files owned:** `platform/.gitkeep`, `schemas/.gitkeep`, - `adapters/.gitkeep`, `terraform/.gitkeep`, `modules-ir/.gitkeep`. -- **Commit message:** - `phase: 6, status: plan-as-execute, persona: lead-developer, task: T-6.2` -- **Commands:** - - ```bash - # From the repo root. Five new empty-but-scaffolded dirs per ROADMAP §Phase 06. - # These are populated in Phases 07–10; here they only need to exist + be tracked. - for d in platform schemas adapters terraform modules-ir; do - mkdir -p "$d" - : > "$d/.gitkeep" # empty placeholder so git tracks the dir - done - git add platform/.gitkeep schemas/.gitkeep adapters/.gitkeep terraform/.gitkeep modules-ir/.gitkeep - ``` - - **Territory note (PERSONAS.md):** these dirs are owned in later phases - by backend-engineer (`platform/confidence_signal.py`, `schemas/**`, - `platform/outbox/**`), platform-engineer (`adapters/terraform/**`, - `modules-ir/**`, `terraform/**`, `platform/registry/**`), and - security-engineer (`platform/hitl_matrix_design.md`, - `platform/audit_ledger_design.md`, `adapters/terraform/policy/**`). In - Phase 06 they are *empty* — no territory conflict. - -#### T-6.3 — Create the new top-level `scripts/` + write `scripts/verify_phase06.sh` - -- **Owner:** lead-developer (territory: `scripts/verify_phase*.sh`). -- **Files owned:** `scripts/.gitkeep` (or the verify script itself acts as - the dir anchor), `scripts/verify_phase06.sh`. -- **Commit message:** - `phase: 6, status: plan-as-execute, persona: lead-developer, task: T-6.3` -- **What `scripts/verify_phase06.sh` must assert** (the Phase 06 success - criteria from ROADMAP, made executable): - - ```bash - #!/usr/bin/env bash - # scripts/verify_phase06.sh — Phase 06 archive regression + layout check. - # Lives at TOP-LEVEL scripts/ (v1.1 verify scripts), NOT demo/scripts/. - set -u - ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" - cd "$ROOT" - fail() { echo "FAIL: $*" >&2; exit 1; } - ok() { echo "ok: $*"; } - - # --- Check 1: demo/ contains the full v1.0 demo tree --- - for d in demo/modules demo/scripts demo/evidence-ui demo/contracts \ - demo/contracts-repo demo/.gitea/workflows; do - [ -d "$d" ] || fail "missing $d" - done - [ -f demo/ACDL_DEMO.md ] || fail "missing demo/ACDL_DEMO.md" - [ -f demo/scripts/run_demo.sh ] || fail "missing demo/scripts/run_demo.sh" - ok "demo/ contains the full v1.0 demo" - - # --- Check 2: regression — the archived demo still runs from demo/ --- - # run_demo.sh uses WORKDIR=/tmp/acdl_demo_run (absolute temp), so it - # does not pollute the repo. --no-upload skips Gitea API calls. - out=$(ACDL_GITEA_TOKEN= bash demo/scripts/run_demo.sh --no-upload 2>&1); rc=$? - [ "$rc" -eq 0 ] || { echo "$out" >&2; fail "demo/scripts/run_demo.sh --no-upload exited $rc"; } - ok "demo/scripts/run_demo.sh --no-upload exits 0" - - # --- Check 3: new top-level dirs exist and are scaffolded --- - for d in platform schemas adapters terraform modules-ir; do - [ -d "$d" ] || fail "missing new top-level dir $d" - [ -f "$d/.gitkeep" ] || fail "missing $d/.gitkeep" - done - ok "new top-level dirs exist: platform/ schemas/ adapters/ terraform/ modules-ir/" - - # --- Check 4: no stray v1.0 dirs left at repo root --- - for stray in modules evidence-ui contracts contracts-repo ACDL_DEMO.md; do - [ -e "$stray" ] && fail "stray $stray left at repo root (should be under demo/)" - done - # .gitea/ at repo root is OK *only if* it's a new v1.1 workflow dir; in Phase 06 - # we moved the demo's .gitea/ to demo/.gitea/, so repo-root .gitea/ should NOT - # exist yet (Phase 07+ may re-create it for the real pipeline). - [ -e ".gitea" ] && fail "stray .gitea/ left at repo root (moved to demo/.gitea/)" - ok "no stray v1.0 dirs at repo root" - - # --- Check 5: README reflects the real platform --- - grep -q "Agentic Cloud Delivery Platform" README.md || fail "README missing platform name" - grep -q "demo/" README.md || fail "README does not reference the archived demo/" - grep -qi "vision\|architecture" README.md || fail "README missing vision/architecture links" - ok "README reflects the real platform (name + demo/ ref + vision/arch links)" - - echo "Phase 06: ALL CHECKS PASS" - ``` - - **Note on `bash -n` / `py_compile` (typecheck gate):** the verify script - itself is covered by `bash -n scripts/verify_phase06.sh`; the moved demo - scripts are covered by `bash -n demo/scripts/*.sh` + `python3 -m py_compile - demo/scripts/*.py` (the typecheck gate in the frontmatter). These run in - Wave 3 before the full verify. - -#### T-6.4 — Rewrite `README.md` + update `.gitignore` - -- **Owner:** lead-developer (territory: `README.md`, `.gitignore`). -- **Files owned:** `README.md`, `.gitignore`. -- **Commit message:** - `phase: 6, status: plan-as-execute, persona: lead-developer, task: T-6.4` -- **`README.md` must contain:** - - Project name: "ACDL — Agentic Cloud Delivery Platform". - - One-line vision lift from `PROJECT.md` (consumers declare intent; the - platform delivers safe production deployment through an agentic stack). - - Links: `docs/vision.md` (the why), `docs/architecture.md` (the how), - `.ciagent/PROJECT.md` (decisions), `.ciagent/ARCHITECTURE.md` (target - architecture), `.ciagent/ROADMAP.md` (phase plan). - - **New layout section** listing the top-level dirs and what each is for: - - `platform/` — platform code (confidence signal, contract resolver, - outbox, HITL/ledger designs) [populated Phase 07+]. - - `schemas/` — JSON Schemas (IR, PolicyCheckResult, contract) [Phase 07]. - - `adapters/` — substrate adapters (Terraform adapter in v1) [Phase 09]. - - `terraform/` — state backend + provider config [Phase 08+]. - - `modules-ir/` — IR-typed L1/L2 modules (`l1-s3`, `l2-static-asset`) - [Phase 09–10]. - - `scripts/` — v1.1 verify scripts (`verify_phaseNN.sh`). - - `demo/` — the archived v1.0 executive demo (tag `v1.1.0`); runs locally - via `demo/scripts/run_demo.sh --no-upload`. Intent reference; not the - platform. - - `.ciagent/` — CIAgent metadata (plans, decisions, personas, roadmap). - - `docs/` — upstream vision + architecture sources. - - A "Status" line: v1.1 active — architecture finalization + v1 spike. - v1.0 demo complete (tag `v1.1.0`), archived under `demo/`. - - **No** v1.0 demo run instructions in the root README — point at - `demo/ACDL_DEMO.md` for the demo deck and `demo/scripts/run_demo.sh`. - -- **`.gitignore` update:** - - Add `runner-data/` (the untracked demo runner artifact; moved to - `demo/runner-data/` in T-6.1, but any future local runner registration - should be ignored at the repo root too). - - Keep existing entries (`__pycache__/`, `*.pyc`, `*.pyo`, `.env*`, - `state.json`, `audit.json`, `*.tmp`, `.DS_Store`). - - The existing `.gitignore` (read) is: - ``` - __pycache__/ - *.pyc - *.pyo - .env - .env.* - state.json - audit.json - *.tmp - .DS_Store - ``` - Append `runner-data/` (and optionally `demo/runner-data/` for - completeness). - ---- - -### Wave 2 — frontend-engineer: confirm `demo/evidence-ui/` moved cleanly - -The UI is frozen as a demo artifact (D-033: relative fetch URL). No code -changes unless the move broke the fetch path — which static analysis says -it cannot (the UI is a single static `index.html` with a relative URL to -`audit.json` on the `acdl-evidence` raw URL). This wave is a confirmation, -not a build. - -#### T-6.5 — Confirm `demo/evidence-ui/index.html` integrity + fetch path - -- **Owner:** frontend-engineer (territory: `evidence-ui/**`, now - `demo/evidence-ui/**`). -- **Files owned:** `demo/evidence-ui/index.html` (read-only confirmation; - edit only if the fetch URL is broken, which it is not per D-033). -- **Commit message (if no edit needed — confirmation only, may be a - no-op commit or folded into Wave 3's verify):** - `phase: 6, status: plan-as-execute, persona: frontend-engineer, task: T-6.5` -- **Checks the frontend-engineer performs:** - 1. `demo/evidence-ui/index.html` exists and is byte-identical to the - pre-move file (git history preserved via `git mv`). - 2. The fetch URL inside `index.html` is relative (per D-033) — e.g. - `fetch('audit.json')` or a raw-URL reference to `acdl-evidence`. - It must NOT reference a repo-root-relative path that broke when - moved under `demo/`. - 3. If (and only if) `scripts/verify_phase06.sh` (run in Wave 3) reports - the UI fetch path broken, the frontend-engineer patches - `demo/evidence-ui/index.html`'s fetch URL to be relative. Per D-033 - and RESEARCH TARGET 10 finding #5, **no patch is expected** — this - is a confirm-only task. -- **No code changes expected.** If a patch is needed, it is a one-line - fetch-URL fix and the commit message above applies. - ---- - -### Wave 3 — lead-developer: run the full Phase 06 verification - -The regression gate. Runs only after Wave 1 (moves done) and Wave 2 -(frontend confirmation done). - -#### T-6.6 — Run `scripts/verify_phase06.sh` + typecheck gate - -- **Owner:** lead-developer (territory: `scripts/verify_phase*.sh`). -- **Files owned:** none (execution-only). -- **Commit message (if any fixup is needed; otherwise this is a verify - run, not a commit):** - `phase: 6, status: plan-as-execute, persona: lead-developer, task: T-6.6` -- **Commands:** - - ```bash - # Typecheck gate (from frontmatter): - bash -n demo/scripts/*.sh && echo "demo shell scripts: syntax ok" - python3 -m py_compile demo/scripts/*.py && echo "demo python scripts: compile ok" - bash -n scripts/verify_phase06.sh && echo "verify_phase06.sh: syntax ok" - - # Test gate (the Phase 06 regression): - bash scripts/verify_phase06.sh - # Expected output: "Phase 06: ALL CHECKS PASS" - # This runs demo/scripts/run_demo.sh --no-upload internally (Check 2). - ``` - - **On failure:** if `demo/scripts/run_demo.sh --no-upload` fails, the - cause is an undetected absolute path in a demo script (R-5 residual - 0.10). Fix the offending path in `demo/scripts/*.sh` (make it - `SCRIPT_DIR`/`REPO_ROOT`-relative) and re-run. If the UI fetch path is - broken, Wave 2's frontend-engineer patches `demo/evidence-ui/index.html`. - - **On pass:** Phase 06 is complete. The phase-completion commit + - `v1.1.0`-style phase tag (or the v1.1 phase-tagging convention) is - created by the execute workflow, not this plan. - ---- - -## Wave ordering (dependency graph) +Five waves, strictly ordered (each wave depends on the prior wave's +commits being present so the verify script and snapshot can reference +the authored files): ``` -Wave 1 (T-6.1, T-6.2, T-6.3, T-6.4) lead-developer - │ moves + scaffold + verify script + README - ▼ -Wave 2 (T-6.5) frontend-engineer - │ confirm demo/evidence-ui/ moved cleanly (no code change expected) - ▼ -Wave 3 (T-6.6) lead-developer - run scripts/verify_phase06.sh (regression + layout + README checks) +Wave 1 (lead-developer) — T-7.1 architecture-v1.0.md snapshot (umbrella) +Wave 2 (platform + backend) — T-7.2 ir.schema.json + T-7.3 contract.schema.json (parallel) +Wave 3 (security-engineer) — T-7.4 policy_check_result.schema.json + T-7.5 checkov_adapter.py + T-7.6 audit_ledger_design.md + T-7.7 hitl_matrix_design.md + T-7.8 separation_of_duties.py (sequential) +Wave 4 (backend + security) — T-7.9 confidence_signal.py (co-authored; depends on T-7.4) +Wave 5 (lead-developer) — T-7.10 verify_phase07.sh + traceability ``` -- **Wave 1 → Wave 2:** the UI must be moved before it can be confirmed. -- **Wave 2 → Wave 3:** the frontend confirmation must precede the full - verify (so any UI fetch-path patch is in place before - `verify_phase06.sh` runs the demo end-to-end). -- **Within Wave 1:** T-6.1 (moves) must precede T-6.2 (new dirs — they - would collide if `scripts/` still held the v1.0 verify scripts when - T-6.3 creates the new top-level `scripts/`). T-6.3 (verify script) can - be authored in parallel with T-6.2 but cannot *run* until T-6.1 + T-6.2 - are committed. T-6.4 (README) is independent of T-6.1..T-6.3 in content - but should land in the same wave so the repo is coherent after Wave 1. +**Dependency graph (explicit):** +- Wave 4 → Wave 3 (confidence_signal consumes PolicyCheckResult, T-7.4). +- Wave 4 → Wave 3 (separation_of_duties reads the outbox schema documented + in audit_ledger_design.md, T-7.6 — the .py reads the outbox shape + defined in the .md). +- Wave 5 → all prior waves (verify script asserts all 6 files exist + + validate). +- Wave 1 (snapshot) is umbrella — it references the *decisions* (already + in PROJECT.md), not the *authored files*, so it can land first. It + notes that the 6 files are authored in Phases 07 Waves 2-4. - **Strict sub-ordering within Wave 1:** T-6.1 → T-6.2 → T-6.3 (T-6.3 - creates the new top-level `scripts/` which must not exist during T-6.1's - `git mv scripts demo/scripts`). T-6.4 (README + .gitignore) may be done - in parallel with T-6.2/T-6.3. +**Parallelizable:** Wave 2's two tasks (T-7.2 IR, T-7.3 contract) are +independent JSON Schemas owned by different personas — run in parallel. +All other waves are sequential within the wave. -## Dependencies +--- -- **v1.0 demo complete + tagged** — satisfied (tag `v1.1.0` present; - ROADMAP §v1.0 "complete"). -- **No upstream v1.1 phase dep** — Phase 06 is the first v1.1 phase. -- **RESEARCH.md TARGET 10** — the authoritative move list + the static - analysis confirming no path fixups are needed (conf 0.90; R-5 residual - is the only open risk, caught by the Wave 3 regression check). -- **PERSONAS.md** — Phase 06 active roster: lead-developer + frontend- - engineer (demo UI move only). backend/platform/security idle. +### Wave 1 — lead-developer: REQ-16 architecture-v1.0.md snapshot -## Tasks summary +The umbrella doc. References the 11 resolutions already recorded in +PROJECT.md (does not duplicate them — points at PROJECT.md's +"Open-decision resolutions" table + decisions D-034..D-046). Bumps the +status note from v0.2 → v1.0. Notes the v1.1 spike (Phases 08-10) + +v1.2 build-out boundary. -| Task | Wave | Owner | Territory | Commit message tag | -|------|------|-------|-----------|--------------------| -| T-6.1 | 1 | lead-developer | `demo/` (new), moved dirs | `task: T-6.1` | -| T-6.2 | 1 | lead-developer | `platform/`, `schemas/`, `adapters/`, `terraform/`, `modules-ir/` (new empty) | `task: T-6.2` | -| T-6.3 | 1 | lead-developer | top-level `scripts/verify_phase06.sh` (new) | `task: T-6.3` | -| T-6.4 | 1 | lead-developer | `README.md`, `.gitignore` | `task: T-6.4` | -| T-6.5 | 2 | frontend-engineer | `demo/evidence-ui/index.html` (confirm-only) | `task: T-6.5` | -| T-6.6 | 3 | lead-developer | (execution: `scripts/verify_phase06.sh`) | `task: T-6.6` | +#### T-7.1 — Author `docs/architecture-v1.0.md` -All commit messages follow the format: -`phase: 6, status: plan-as-execute, persona: , task: T-6.N` +- **Owner:** lead-developer (territory: `docs/**` meta only — not + architecture authoring; this is a *snapshot* of the upstream + `docs/architecture.md`, not a re-authoring). +- **Files owned:** `docs/architecture-v1.0.md` (new). +- **What the file must contain** (cite sources): + 1. **Header** — copy the upstream `docs/architecture.md` header verbatim, + then change the status line from + `Status: Draft v0.2. Section 13 lists 11 open decisions. ...` + to + `Status: v1.0 (snapshot taken in ACDL Phase 07, milestone v1.1). All 11 open decisions in §13 are resolved — see PROJECT.md "Open-decision resolutions" table + decisions D-034..D-046.` + 2. **Resolution session log table** — replace the upstream §0 + resolution-session log with a v1.0 resolution log: for each of the + 11 open IDs (W1.A, W1.B, W2.A, W3.D, W3.E, BA.A, BA.B, BA.C, BA.D, + BA.E, BA.F, Q1.3-OpenTofu), a row with `ID | Resolution (one-line) | + See PROJECT.md`. Do **not** duplicate the full resolution text — + point at PROJECT.md. + 3. **§13 Consolidated Open Design Decisions** — replace the upstream + "🟡 OPEN" markers with "✅ RESOLVED (see PROJECT.md)" for each of the + 11 items, preserving the upstream section structure. + 4. **§14 Document Status** — rewrite to: + `Status: v1.0. All 11 open items in §13 are resolved. The architecture is internally consistent; the v1.1 implementation spike (ACDL Phases 08-10) validates the locked substrate abstraction + contract→IR→adapter path. The v1.2 build-out (Object Lock, JWS, HITL wiring, L3B skill catalog, Kyverno/OPA, OIDC federation) is design-authored in Phase 07 and implemented post-spike.` + 5. **New §15 — Phase 07 authored artifacts** — a table mapping each + of the 6 schema/design files to its REQ + owner persona: + `REQ-17 → schemas/ir.schema.json (platform-engineer)`, + `REQ-18 → schemas/policy_check_result.schema.json + adapters/terraform/policy/checkov_adapter.py (security-engineer)`, + `REQ-19 → platform/confidence_signal.py (backend + security co-authored)`, + `REQ-20 → platform/audit_ledger_design.md (security-engineer)`, + `REQ-21 → platform/hitl_matrix_design.md + platform/separation_of_duties.py (security-engineer)`, + `REQ-22 → schemas/contract.schema.json (backend-engineer)`. + Note that these files land in Waves 2-4 of Phase 07. + 6. **gitea-runner rename (D-046)** — update any `act_runner` reference + in the snapshot to `gitea-runner` (formerly `act_runner`, renamed + 2026-04 in gitea/runner#850) with a one-line note. + 7. **Body** — the rest of the upstream `docs/architecture.md` body + (§§1-12) is copied verbatim; the §12.6 PolicyCheckResult shape, §10.4 + HITL matrix, §8 confidence signal, §9 audit stream, §7 contract + schema are all *locked* — the snapshot preserves them as the v1.0 + record. The authored schema files (Waves 2-4) formalize these + sections; the snapshot is the prose, the schemas are the contract. +- **Source citations:** + - `docs/architecture.md` (upstream v0.2 — the source of truth for how). + - `PROJECT.md` "Open-decision resolutions" table (the 11 resolutions). + - `PROJECT.md` decisions D-034..D-046. + - `ARCHITECTURE.md` §status (the ACDL-repo operating copy, already + notes "finalizes it to v1.0 in Phase 07"). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.1` + (followed by a docs commit: + `docs(P07): snapshot architecture to v1.0 (REQ-16); 11 decisions resolved`) +- **Self-verify:** + - [ ] `docs/architecture-v1.0.md` exists. + - [ ] First status line says `v1.0` (not `v0.2`). + - [ ] All 11 open-decision IDs appear with `✅ RESOLVED` (grep each: + `W1.A`, `W1.B`, `W2.A`, `W3.D`, `W3.E`, `BA.A`, `BA.B`, `BA.C`, + `BA.D`, `BA.E`, `BA.F`, `Q1.3`). + - [ ] `gitea-runner` appears (D-046 applied); `act_runner` only in a + "formerly" note. + - [ ] §15 table lists all 6 authored files. -## Risks (specific to Phase 06) +--- -1. **R-5 (RESEARCH, residual 0.10): undetected absolute path in a demo - script.** Mitigation: Wave 3 runs `demo/scripts/run_demo.sh --no-upload` - and asserts exit 0. A failure is caught there and fixable in-phase by - making the offending path `SCRIPT_DIR`/`REPO_ROOT`-relative. The - typecheck gate (`bash -n demo/scripts/*.sh`) catches syntax issues - but not path issues — only the runtime regression catches those. +### Wave 2 — platform-engineer + backend-engineer (parallel): REQ-17 IR + REQ-22 contract schema -2. **`contracts-repo/` is tracked and moves with `git mv`.** Verified: - `git ls-files contracts-repo/` returns - `.gitea/workflows/.gitkeep` + `.gitea/workflows/issue-to-contract.yml`. - It is a demo artifact (the v1.0 `acdl-contracts` mirror used by - `verify_phase04.sh`). Moving it to `demo/contracts-repo/` is correct; - `demo/scripts/verify_phase04.sh` references it via the - `ROOT`-relative path `contracts-repo/.gitea/workflows/...` which - auto-resolves to `demo/contracts-repo/...` after the move (ROOT = - parent of `demo/scripts/` = `demo/`). +Two independent JSON Schemas, different owners, parallelizable. Both +are JSON Schema draft 2020-12, both live under `schemas/`, both must +`python3 -m jsonschema` validate. -3. **`runner-data/` is untracked.** It is moved with plain `mv` (not - `git mv`) and gitignored. If a future runner registration recreates it - at the repo root, `.gitignore` (updated in T-6.4) keeps it untracked. +#### T-7.2 — Author `schemas/ir.schema.json` (Target Stack IR) -4. **`ACDL_DEMO.md` was untracked** (verified: `git status` shows - `?? ACDL_DEMO.md`). `git mv` on an untracked file will fail; the - executor must `git add` it first (or use `mv` + `git add demo/ACDL_DEMO.md`). - The command list in T-6.1 uses `git mv ACDL_DEMO.md demo/ACDL_DEMO.md` - — if git rejects it as untracked, fall back to: - `mv ACDL_DEMO.md demo/ACDL_DEMO.md && git add demo/ACDL_DEMO.md`. +- **Owner:** platform-engineer (territory: `modules-ir/**`, + `adapters/terraform/**`, the IR; `schemas/` co-owned with + backend-engineer — platform-engineer is the IR author per PERSONAS.md + conflict resolution). +- **Files owned:** `schemas/ir.schema.json` (new). +- **What the file must contain** (cite sources): + 1. **JSON Schema draft 2020-12** — `$schema: + "https://json-schema.org/draft/2020-12/schema"`, `$id: + "https://acdl.cloudinit.dev/schemas/ir.schema.json"`, `title: + "ACDL Target Stack IR"`. + 2. **Top-level shape** — `required: [version, stack, resources]`, + `properties`: `version` (semver string), `stack` (name/kind/depth), + `resources` (array of resource, minItems 1), `relationships` (array + of relationship, optional). + 3. **`stack`** — `name` pattern `^l[12]-[a-z][a-z0-9-]*$`, `kind` enum + `[l1, l2]`, `depth` integer min 1 max 5 (ARCHITECTURE.md §3 "max + depth 5"). + 4. **`resource` ($defs/resource)** — required `[id, type, module, + inputs]`; `id` pattern `^[a-z][a-z0-9-]*$`; `type` string + (substrate-agnostic, e.g. `aws:s3:bucket` — the *IR* type, not a + Terraform address); `module` pattern + `^l1-[a-z][a-z0-9-]*@\d+\.\d+\.\d+$` (L1 registry reference + `name@semver` per W3.D); `parent` (string, absent for root — single + parent per child per ARCHITECTURE.md §12.1); `inputs` (object, + additionalProperties free-form — typed by the L1 module's declared + inputs, validated at contract→IR resolution); `outputs` (object of + outputSpec — typed output contract; the adapter translates to + Terraform outputs); `nfrs` (object, opaque to the adapter, consumed + by the confidence signal's NFR input). + 5. **`outputSpec` ($defs/outputSpec)** — required `[type]`; `type` + string (IR type, e.g. `string`, `arn`, + `ref:.`); `description` optional. + 6. **`relationship` ($defs/relationship)** — required `[from, to, kind]`; + `from`/`to` resource ids; `kind` enum `[parent, depends_on, + uses_output]` with a description noting v1 uses `parent` + + `uses_output` only (ARCHITECTURE.md §12.1); `shared_keyword` + (string, reserved for v2 multi-relationship, unused in v1 — + A-3.2). + 7. **Substrate-agnostic invariant** — the schema body must contain + **no Terraform-specific terms** (`variable`, `output`, `module`, + `resource` as Terraform blocks, `aws_` provider prefixes in the + schema keywords). The `type` field values are IR types + (`aws:s3:bucket`), not Terraform resource types (`aws_s3_bucket`). + The adapter (Phase 09) does the translation; the IR is neutral. + 8. **Why this round-trips to Terraform (v1)** — include a + `$comment` at the top: "v1 ships one adapter (Terraform). The IR is + nearly isomorphic to Terraform in v1 (ARCHITECTURE.md §12.1); the + adapter compiles resource.module → module block, resource.inputs → + variable + arg, resource.outputs → output, relationship.kind= + uses_output → interpolation, relationship.kind=parent → composition + ordering hint. As more adapters appear (v2+), the IR gains + expressiveness; the L1 content + contract YML + thin-composition + tree do not change." +- **Source citations:** + - `ARCHITECTURE.md` §12.1 (substrate abstraction — locked commitments: + resources typed inputs/outputs/NFRs, relationships single parent per + child, composition tree max-depth-5, policy hooks). + - `RESEARCH.md` TARGET 3 (v1 IR shape sketch — the executor uses this + as the basis; the sketch is a *recommendation*, the authored schema + must match ARCHITECTURE.md's locked commitments and be + self-contained). + - `PROJECT.md` W3.D (L1/L2 standard versioning — `name@semver` pin + model, MAJOR bump = new registry entry, 12-month deprecation + window). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: platform-engineer, task: T-7.2` + (then `feat(P07): author schemas/ir.schema.json (REQ-17)`) +- **Self-verify:** + - [ ] `python3 -m jsonschema schemas/ir.schema.json` exits 0 (schema is + itself valid JSON Schema). + - [ ] A minimal valid IR instance validates: `{version:"1.0.0", + stack:{name:"l2-static-asset",kind:"l2",depth:1}, + resources:[{id:"s3",type:"aws:s3:bucket", + module:"l1-s3@1.0.0",inputs:{bucket_name:"x",region:"us-east-1"}}]}` + — write this to a temp file and validate against the schema. + - [ ] No `aws_s3_bucket` / Terraform-block keywords in the schema body. + - [ ] `depth` max is 5; `module` pattern enforces `name@semver`; + `relationship.kind` enum includes `parent` + `uses_output`. + - [ ] `shared_keyword` is reserved (present, unused in v1). -5. **`verify_phase05.sh` has an anti-stray check** (lines 159-160: - `if [ -d "$ROOT/contracts" ]; then fail "contracts/ directory left in - repo root"`). After the move, `ROOT` for `demo/scripts/verify_phase05.sh` - is `demo/`, and `demo/contracts/` *does* exist — so this check would - fail if the v1.0 verify script is re-run from `demo/`. This is - expected: the v1.0 verify scripts are *archived demo artifacts*, not - re-run after Phase 06. The v1.1 verify script - (`scripts/verify_phase06.sh`) supersedes them. No action needed beyond - documenting that v1.0 verify scripts are not re-run post-archive. \ No newline at end of file +#### T-7.3 — Author `schemas/contract.schema.json` + +- **Owner:** backend-engineer (territory: `schemas/**` contract + IR + + PolicyCheckResult per PERSONAS.md; owns the contract schema + the + contract→IR resolution). +- **Files owned:** `schemas/contract.schema.json` (new). +- **What the file must contain** (cite sources): + 1. **JSON Schema draft 2020-12** — `$schema: + "https://json-schema.org/draft/2020-12/schema"`, `$id: + "https://acdl.cloudinit.dev/schemas/contract.schema.json"`, + `title: "ACDL Contract"`. + 2. **Top-level required** — `[stack, environment]` (dev's minimum per + W3.E). `stack` pattern `^l2-[a-z][a-z0-9-]*$` (L2 reference); + `environment` enum `[dev, qa, prod, dr]` (no `staging` — Path A + locked, ARCHITECTURE.md §5). + 3. **`inputs`** — object, `additionalProperties` type + `[string, number, boolean]`, always optional (W3.E: "`inputs` + always optional"). Description: "L2-level parameter map; free-form + in v1, typed per-L1 in v1.2" (A-9.2). + 4. **`validation`** — object with `e2eSuite` (string, ref to the e2e + suite) + `loadTest` (string, ref to the load test). Mandatory in qa + via the `allOf` conditional below. + 5. **`runbook`, `dashboard`, `oncall`** — strings, mandatory in prod. + 6. **`drDrillRef`** — string, mandatory in dr. + 7. **`profile`** — enum `[developer, agentic]`, default `developer`. + The L3B marker (ARCHITECTURE.md §5 "profile: agentic unlocks L3B + fields"). + 8. **`naturalLanguageIntent`, `confidenceAtSubmission`, `agentTrace`** + — the L3B fields, optional everywhere (W3.E). When `profile: + agentic`, `naturalLanguageIntent` becomes required (via `allOf`). + `confidenceAtSubmission` is a number in `[0, 1]`. + 9. **`supersedes`** — string, `format: uuid`, optional. Description: + "prior contractId this re-submission replaces (after rejection — + ARCHITECTURE.md §10.6)". + 10. **`allOf` conditionals** (W3.E per-env mandatory table): + - `if environment == qa → then required: [validation]` (and + within `validation`, require `e2eSuite` + `loadTest`). + - `if environment == prod → then required: [runbook, dashboard, oncall]`. + - `if environment == dr → then required: [drDrillRef]`. + - `if profile == agentic → then required: [naturalLanguageIntent]`. + 11. **`$comment`** — "Per-env mandatory inputs per W3.E (PROJECT.md). + dev requires stack+environment; qa adds validation.e2eSuite + + validation.loadTest; prod adds runbook+dashboard+oncall; dr adds + drDrillRef. inputs always optional. profile: agentic fields + optional everywhere (naturalLanguageIntent required when profile + is agentic)." +- **Source citations:** + - `ARCHITECTURE.md` §7 (contract schema — W3.E per-env mandatory + table). + - `RESEARCH.md` TARGET 9 (contract schema sketch — the executor uses + this as the basis; must be self-contained + match ARCHITECTURE.md). + - `PROJECT.md` W3.E (the locked per-env mandatory/optional table). + - `PROJECT.md` W2.A (tag for dev/qa, SHA for prod — not a schema field + but informs the `stack` reference resolution in the CLI, noted in + the `$comment`). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: backend-engineer, task: T-7.3` + (then `feat(P07): author schemas/contract.schema.json (REQ-22)`) +- **Self-verify:** + - [ ] `python3 -m jsonschema schemas/contract.schema.json` exits 0. + - [ ] The spike contract + `{stack:"l2-static-asset",environment:"dev",inputs:{bucket_name:"acdl-spike-bucket",region:"us-east-1"}}` + validates (dev requires only stack+environment). + - [ ] A qa contract missing `validation` *fails* validation. + - [ ] A prod contract missing `runbook` *fails*. + - [ ] A dr contract missing `drDrillRef` *fails*. + - [ ] An agentic contract missing `naturalLanguageIntent` *fails*; + an agentic contract with it *passes*. + - [ ] `environment` enum does NOT include `staging`. + +--- + +### Wave 3 — security-engineer: REQ-18 + REQ-20 + REQ-21 + +One persona, five files, one wave (sequential within the wave). The +Checkov adapter (T-7.5) depends on the PolicyCheckResult schema (T-7.4); +the SoD module (T-7.8) depends on the outbox shape in the ledger design +(T-7.6). Order within the wave: T-7.4 → T-7.5 → T-7.6 → T-7.7 → T-7.8. + +#### T-7.4 — Author `schemas/policy_check_result.schema.json` + +- **Owner:** security-engineer (owns `adapters/terraform/policy/**`; the + schema is co-authored with backend-engineer who owns `schemas/**` + contract + IR + PolicyCheckResult — security-engineer owns the + *adapter*, the schema is the contract both share. security-engineer + authors it here because the adapter's shape is the security concern + + the severity enum is the security-engineer's territory per the + conflict-resolution note on confidence_signal co-ownership). +- **Files owned:** `schemas/policy_check_result.schema.json` (new). +- **What the file must contain** (cite sources): + 1. **JSON Schema draft 2020-12** — `$schema`, `$id: + "https://acdl.cloudinit.dev/schemas/policy_check_result.schema.json"`, + `title: "ACDL PolicyCheckResult"`. + 2. **Canonical shape** — exactly the JSON shape from + `ARCHITECTURE.md` §12.6: + - `contractId` (string, format uuid). + - `evaluatedAt` (string, format date-time — ISO-8601). + - `engine` (enum `[checkov, kyverno, opa]`). + - `ruleId` (string — e.g. `CKV_AWS_24`, `KYVERNO_NO_PRIVILEGED`, + `ACDL_TAG_NAMING`). + - `severity` (enum `[critical, high, medium, low, info]`). + - `result` (enum `[pass, fail, skipped, error]`). + - `message` (string, human-readable). + - `evidence` (object, additionalProperties true — engine-specific + payload, opaque to the confidence signal per §12.6). + - `resourceRef` (string — IR-typed resource identifier). + 3. **Required** — all of: `contractId, evaluatedAt, engine, ruleId, + severity, result, message, resourceRef` (evidence is optional — + some engines may omit; the confidence signal does not read it). + 4. **`$comment`** — "Canonical PolicyCheckResult (ARCHITECTURE.md + §12.6). The confidence signal (platform/confidence_signal.py) + consumes a list of these as its `policy` input; it is + engine-agnostic. The severity enum drives the severity→penalty + mapping (critical hard-override, high -0.2, medium -0.05, low + -0.01, info 0.0). Adapters (checkov_adapter.py, future + kyverno_adapter) translate native engine output to this shape." +- **Source citations:** + - `ARCHITECTURE.md` §12.6 (the canonical shape — copy verbatim into + the schema). + - `RESEARCH.md` TARGET 4 (PolicyCheckResult + Checkov adapter). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.4` + (then `feat(P07): author schemas/policy_check_result.schema.json (REQ-18 schema half)`) +- **Self-verify:** + - [ ] `python3 -m jsonschema schemas/policy_check_result.schema.json` + exits 0. + - [ ] A valid instance validates: + `{contractId:"",evaluatedAt:"2026-07-21T12:00:00Z",engine:"checkov",ruleId:"CKV_AWS_24",severity:"high",result:"fail",message:"SG 0.0.0.0/0 on 22",evidence:{resource:"aws_security_group.r1"},resourceRef:"r1"}`. + - [ ] `severity` enum has exactly `[critical, high, medium, low, info]`. + - [ ] `result` enum has exactly `[pass, fail, skipped, error]`. + - [ ] `engine` enum has exactly `[checkov, kyverno, opa]`. + +#### T-7.5 — Author `adapters/terraform/policy/checkov_adapter.py` + +- **Owner:** security-engineer (territory: + `adapters/terraform/policy/**`). +- **Files owned:** `adapters/terraform/policy/checkov_adapter.py` (new); + create `adapters/terraform/policy/__init__.py` if needed for import. +- **What the file must contain** (cite sources): + 1. **Module docstring** — "Translate Checkov JSON output to ACDL + PolicyCheckResult records (schemas/policy_check_result.schema.json). + Reads Checkov's JSON output (one framework key, e.g. + `terraform_plan`), emits a list of PolicyCheckResult dicts. + Run Checkov with `--soft-fail` so Checkov never exits non-zero; the + confidence signal decides the gate, not Checkov's exit code." + 2. **`RULE_MAP`** — dict mapping Checkov rule IDs to + `(ACDL L2 check name, default severity when Checkov omits one)`, + per RESEARCH.md TARGET 4 table: + - `CKV_AWS_41, CKV_AWS_45, CKV_AWS_46` → `("secrets-in-plaintext", "high")`. + - `CKV_AWS_20, CKV_AWS_57` → `("public-ingress", "high")` (S3 public ACL). + - `CKV_AWS_24, CKV_AWS_25` → `("public-ingress", "medium")` (SG 0.0.0.0/0). + - `CKV_AWS_1` → `("iam-wildcard", "high")`. + - `CKV_AWS_40` → `("iam-wildcard", "medium")`. + - `CKV_AWS_7, CKV_AWS_33` → `("kms-key-reference", "medium")`. + Unknown Checkov rule IDs → `(rule_id, "info")` fallback. + 3. **`_iso8601_now()`** — UTC now in ISO-8601 `Z` suffix. + 4. **`_to_pcr(checkov_record, contract_id, result_str)`** — map a + Checkov check record to a PolicyCheckResult dict: + - `contractId` = the contract_id arg. + - `evaluatedAt` = `_iso8601_now()`. + - `engine` = `"checkov"`. + - `ruleId` = `checkov_record["check_id"]`. + - `severity` = `checkov_record.get("severity", default_sev).lower()` + (prefer Checkov's severity if present; fall back to RULE_MAP + default — A-4.1: spike runs without Prisma Cloud API key). + - `result` = `{"PASSED":"pass","FAILED":"fail", + "SKIPPED":"skipped"}.get(result_str, "error")`. + - `message` = `checkov_record.get("check_name", "")`. + - `evidence` = `{"file_path":..., "resource":..., + "resource_address":..., "code_block":...}` (engine-specific, + opaque to the signal). + - `resourceRef` = `checkov_record.get("resource_address") or + checkov_record.get("resource", "")` (defensive — A-4.1 0.15 + residual for field-name drift). + 5. **`adapt(checkov_json_path, contract_id)`** — read the Checkov JSON, + iterate frameworks, for each framework's `results` body iterate + `passed_checks` (→ "PASSED"), `failed_checks` (→ "FAILED"), + `skipped_checks` (→ "SKIPPED"), build the list. Tolerate both + `{framework: {results: {...}}}` and `{framework: {...}}` shapes + (Checkov minor-version drift). + 6. **`_emit_tag_naming_skipped(contract_id)`** — per D-043, emit a + single `PolicyCheckResult` with `ruleId: "ACDL_TAG_NAMING"`, + `result: "skipped"`, `severity: "info"`, `engine: "checkov"`, + `message: "tag/naming check deferred to v1.2 (D-043)"`. This + guarantees the confidence signal's `policy` input is non-empty even + if Checkov finds nothing (the "all six inputs present" dev gate). + 7. **`adapt()` appends the tag/naming skipped record** to the output + list after the Checkov pass. + 8. **`__main__`** — `checkov_path, contract_id = sys.argv[1], + sys.argv[2]`; `print(json.dumps(adapt(...), indent=2))`. CLI: + `python3 adapters/terraform/policy/checkov_adapter.py checkov.json + `. + 9. **No external deps** — stdlib only (`json`, `sys`, `datetime`). + Checkov is invoked by the workflow, not by this module; this module + reads Checkov's JSON output file. + 10. **Spike-scope note** — a module-level comment: "Spike scope (D-043): + tag/naming is a single SKIPPED record. A custom Checkov YAML rule + for tag presence lands in v1.2." +- **Source citations:** + - `ARCHITECTURE.md` §12.4 (policy toolchain — Checkov for the L2 + checks + tag/naming) + §12.6 (PolicyCheckResult shape). + - `RESEARCH.md` TARGET 4 (Checkov adapter sketch + rule→severity map + — the executor uses this as the basis; must be self-contained). + - `PROJECT.md` D-043 (tag/naming deferred — emit SKIPPED). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.5` + (then `feat(P07): author checkov_adapter.py (REQ-18 adapter half; D-043 tag/naming skipped)`) +- **Self-verify:** + - [ ] `python3 -m py_compile + adapters/terraform/policy/checkov_adapter.py` exits 0. + - [ ] A synthetic Checkov JSON fixture (a single failed CKV_AWS_24) + produces a PolicyCheckResult with `severity:"medium"`, + `result:"fail"`, `ruleId:"CKV_AWS_24"` + the appended + `ACDL_TAG_NAMING` skipped record. + - [ ] Output validates against `schemas/policy_check_result.schema.json` + (each record). + - [ ] `RULE_MAP` has all 11 Checkov rule IDs from RESEARCH.md TARGET 4. + - [ ] No non-stdlib imports. + +#### T-7.6 — Author `platform/audit_ledger_design.md` + +- **Owner:** security-engineer (territory: + `platform/audit_ledger_design.md`). +- **Files owned:** `platform/audit_ledger_design.md` (new). +- **What the file must contain** (cite sources): + 1. **Header** — "ACDL Tiered Audit Ledger Design (REQ-20). Status: + design authored in Phase 07; spike implements the v1.0 hash chain + + outbox write (D-041); v1.2 builds out Object Lock + JWS + async + worker + DLQ." + 2. **Tiered ledger overview** — the three tiers: + - **Cold tier (source of truth):** S3 with Object Lock in + *compliance mode*, 7-year retention (ARCHITECTURE.md §9, RESEARCH + TARGET 7). No one — including root — can delete/overwrite until + retention expires. + - **Hot tier (query index):** `acdl-evidence` audit repo (the v1.0 + demo's path; unchanged). Not part of the chain; a queryable + mirror. The evidence UI (`evidence-ui/index.html`) reads it. + - **Outbox (write path):** DynamoDB, RPO=0 (synchronous write before + submission ack). Single-region in v1 (us-east-1). + 3. **Spike scope (D-041) — what the spike implements:** + - DynamoDB outbox: table `acdl-outbox`, `PAY_PER_REQUEST` (D-044), + PK `contractId`, SK `eventType#eventTs`, TTL `expire_at` = now + + 365d (1-year storage per §8). + - `prev_event_hash` chain: SHA-256 over canonical JSON + (`json.dumps(event, sort_keys=True, separators=(",",":"))`), + lifted from the v1.0 demo's `evidence_writer.py`. Auto-genesis: + first event has `prev_hash="GENESIS"`. + - Synchronous write via boto3 `put_item` (strong-consistent by + default). No separate async worker / DLQ in the spike (RTO = + workflow re-run; A-5.2). + - Mirror to `acdl-evidence` (unchanged from v1.0 — the finalize + step commits `audit.json`). + - Spike evidence event shape (RESEARCH TARGET 7): + `{seq, ts, stage, event, prev_hash, hash, contractId, + environment, stack, score, band}`. + 4. **v1.2 build-out — what Phase 07 designs but the spike defers:** + - **S3 Object Lock:** bucket `acdl-evidence-lock-`, + Object Lock enabled at creation, compliance mode, 7-yr retention + (`RetainUntilDate` = now + 7y). The outbox→S3 path is an async + worker that reads from the outbox and writes to Object Lock. + - **JWS detached signature (RFC 7515):** the event payload is + canonical-JSON-serialized, SHA-256 hashed, signed with a private + key; the signature is stored *detached* alongside the payload. + Signing key = **platform-level KMS key** (not per-contract — + RESEARCH A-7.2), rotated **quarterly**. The `jws` field is added + to the event shape in v1.2. + - **Async worker + DLQ:** a Lambda (or Gitea Actions scheduled + workflow) reads the outbox, writes to S3 Object Lock, signs with + KMS. DLQ = an SQS dead-letter queue for failed writes. RTO = DLQ + replay. + - **Daily checkpoints (§9):** a daily job reads the last event hash + and writes a "checkpoint" event to the ledger (+ optionally to a + public notarization service). The spike runs in minutes, not + days — no checkpoint in spike. + 5. **JWS vs chain — orthogonality note:** the `prev_event_hash` chain + gives ordering/tamper-evidence *within* the log; JWS gives + authenticity *per event*. The chain is spike-scope; JWS is v1.2. + 6. **Outbox item shape (full, spike+v1.2)** — the DynamoDB item: + - PK `contractId` (UUID). + - SK `eventType#eventTs` (e.g. `POLICY_CHECKED#2026-07-21T12:00:00Z`). + - `payload` (the event body — hash-chained in spike, JWS-signed in + v1.2). + - `prev_event_hash` (chain link). + - `hash` (this event's SHA-256). + - `approver_qa` (Gitea username of the QA approver; empty in + dev-only spike; populated on qa-promotion — D-042). + - `approver_prod` (SRE username; empty in spike). + - `environment`, `stack`, `score`, `band`. + - `expire_at` (TTL = now + 365d). + - v1.2 only: `jws` (detached signature), `checkpoint_ref`. + 7. **RPO/RTO table:** + - Spike: RPO=0 (sync outbox write), RTO=workflow re-run. + - v1.2: RPO=0 (sync outbox), RTO=async worker DLQ replay. + 8. **Decision trail** — cite D-041 (spike scope), D-044 (outbox mode + + TTL), D-042 (approver identities in outbox). +- **Source citations:** + - `ARCHITECTURE.md` §9 (audit + evidence stream — locked: S3 Object + Lock compliance 7-yr + DynamoDB outbox RPO=0 + JWS + chain + daily + checkpoints). + - `RESEARCH.md` TARGET 7 (tiered audit ledger — S3 Object Lock + + JWS + chain; spike minimum). + - `RESEARCH.md` TARGET 5 (DynamoDB outbox pattern — RPO=0, on-demand, + PK/SK, TTL). + - `PROJECT.md` D-041 (spike scope = hash chain + outbox), + D-044 (outbox mode + TTL), D-042 (approver identities in outbox). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.6` + (then `docs(P07): author platform/audit_ledger_design.md (REQ-20; D-041 spike scope)`) +- **Self-verify:** + - [ ] File exists, non-empty markdown. + - [ ] "compliance mode" + "7-year" appear (S3 Object Lock locked). + - [ ] "RPO = 0" / "RPO=0" appears. + - [ ] "JWS" + "detached" + "KMS" + "quarterly" appear (v1.2 design). + - [ ] "prev_event_hash" + "canonical JSON" + "SHA-256" appear (chain). + - [ ] "daily checkpoint" appears. + - [ ] "D-041", "D-044", "D-042" appear (decision trail). + - [ ] A clear "Spike scope" vs "v1.2 build-out" section boundary. + +#### T-7.7 — Author `platform/hitl_matrix_design.md` + +- **Owner:** security-engineer (territory: + `platform/hitl_matrix_design.md`). +- **Files owned:** `platform/hitl_matrix_design.md` (new). +- **What the file must contain** (cite sources): + 1. **Header** — "ACDL Human-in-the-Loop Matrix + Separation-of-Duties + Design (REQ-21). Status: design authored in Phase 07; v1.2 wires + the gates. Spike (Phases 08-10) is dev-only; HITL is not exercised." + 2. **Gate model (§10.1)** — pre-execution gates. The contract is held + in "validated but not applied" until the human attests. qa, prod, dr + are attestation gates. No partial deployment to roll back on + rejection (qa, prod); dr is a separate deployment against a separate + cluster/region. + 3. **Gitea-specific gate mechanics (D-042)** — Gitea has **no + Environments API** and ignores `environment:` blocks (v1.0 D-013; + re-confirmed RESEARCH TARGET 8). The pre-execution gate is modeled + as a `workflow_dispatch` with approval inputs: + - qa gate: `workflow_dispatch` with `approve_qa: true`; the + dispatch run's `gitea.actor` is the QA approver. + - prod gate: `workflow_dispatch` with `approve_prod: true`; + `gitea.actor` is the SRE approver. + - dr gate: `workflow_dispatch` with `approve_dr: true`; same. + The approver identity of record = `gitea.actor` of the dispatch + run (D-042). There is no other approval-identity signal in Gitea. + 4. **Reviewer routing (§10.2)** — Gitea CODEOWNERS routes the right + reviewer to the right gate (qa → QA team; prod → SRE team; dr → + SRE team). CODEOWNERS routes; it does **not** enforce identity + distinctness (that's the outbox check in T-7.8). + 5. **Full 8-concern attestation matrix (§10.4)** — the complete table + from `docs/architecture.md` §10.4, lifted verbatim: + | Env | Concern | Evidence artifact | Freshness | Source | Attester | + | qa | Functional correctness | Last successful run of contract-declared validation.e2eSuite with pass rate ≥ 99% | Last 24h | Test runner declared in contract | QA | + | qa | Performance baseline | Load test report (k6/Gatling/Locust) showing p99 latency < declared NFR and throughput > declared minimum | Last 7d | Load test runner declared in contract | QA | + | qa | Security posture | Vulnerability scan (Trivy/Snyk/contract-declared) with no criticals/highs, signed by Security on-call | Last 24h | Security scanner + Security team signature | QA | + | qa | Contract NFRs | Platform-generated report: schema valid, NFR assertions within declared bounds | At submission | Platform contract validator | QA | + | prod | Operational readiness | Runbook published, dashboard exists, on-call rotation assigned, alerts configured | At submission, validated against last 30d history | Platform + SRE | SRE | + | prod | Incident response | Sev-1 runbook tabletop or live drill completed | Last 90d | SRE drill record | SRE | + | prod | Capacity / cost | FinOps forecast for next 30d within budget envelope, cost anomaly baseline stored, budget alert configured | Forecast valid for next 30d | FinOps + SRE | SRE | + | prod | Resilience | DR drill, chaos engineering report, backup verified | DR: 180d; chaos: 90d; backup: 30d | SRE + Platform | SRE | + | dr | dr-region deploy with the most recent prod-bound dr drill as canary evidence | dr drill report | Last 180d | SRE | SRE | + 6. **Timeout behavior (§10.5)** — the table: + | Time | State | Action | + | Submission | PENDING_ATTESTATION | Notify responsible team | + | 1 business day | PENDING_ATTESTATION_WARNING | Notify team + platform on-call (elevated path); emit PENDING_ATTESTATION_TIMEOUT_WARNING event | + | 2 business days | PENDING_ATTESTATION_AUTO_FREEZE | Auto-freeze; require re-submission; emit PENDING_ATTESTATION_AUTO_FREEZE event; new submission linked via `supersedes` | + Implementation: a Gitea `on: schedule` workflow (runs hourly) that + scans the outbox for `PENDING_ATTESTATION` events with `ts` older + than 1/2 business days and emits the warn/freeze events. Not + implemented in the spike (A-8.3). + 7. **Rejection and rollback (§10.6)** — rejection returns the contract + to `HELD` state with the rejection reason captured as a + `PROMOTION_REJECTED` event. The consumer fixes the cause and + re-submits; the new submission is linked to the rejected one via + `supersedes` (a contract-schema field — T-7.3). The audit chain is + extended, not torn up (the "Not a mutable audit log" anti-goal). + No partial deployment to roll back at any v1 gate. + 8. **Separation of duties (§10.3) — pointer to the .py** — a section + that describes the check sequence (dev→qa writes approver_qa to + outbox; qa→prod reads approver_qa + new gitea.actor; if equal → + block + emit SEPARATION_OF_DUTIES_VIOLATION + route halt artifact + to SRE on-call) and points at `platform/separation_of_duties.py` + (T-7.8) for the implementation. + 9. **Spike scope note** — the spike is dev-only (REQ-27 contract has + `environment: dev`), so HITL is not exercised. Phase 07 authors + the design; Phase 10's `verify_phase10.sh` does not assert HITL + behavior. v1.2 wires the gates. + 10. **Decision trail** — cite D-042 (approver identity = gitea.actor), + D-013 (v1.0 workflow_dispatch fallback, re-used). +- **Source citations:** + - `docs/architecture.md` §10 (HITL mechanics — §10.1 gate model, + §10.2 routing, §10.3 SoD, §10.4 full matrix, §10.5 timeout, §10.6 + rejection). + - `ARCHITECTURE.md` §10 (ACDL-repo operating copy — same content, + refined). + - `RESEARCH.md` TARGET 8 (HITL matrix + SoD — Gitea adaptation, + design doc sketch). + - `PROJECT.md` D-042 (approver identity = gitea.actor). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.7` + (then `docs(P07): author platform/hitl_matrix_design.md (REQ-21; D-042 gitea.actor)`) +- **Self-verify:** + - [ ] File exists, non-empty markdown. + - [ ] All 8 concerns from §10.4 appear (functional, performance, + security, NFRs, op-readiness, incident response, capacity/cost, + resilience). + - [ ] "gitea.actor" appears (D-042). + - [ ] "workflow_dispatch" + "approve_qa" + "approve_prod" + "approve_dr" + appear. + - [ ] "1 business day" + "2 business days" + "PENDING_ATTESTATION" + appear. + - [ ] "supersedes" + "HELD" + "PROMOTION_REJECTED" appear. + - [ ] "CODEOWNERS" appears. + - [ ] "no Environments API" / "no Environments API" appears. + +#### T-7.8 — Author `platform/separation_of_duties.py` + +- **Owner:** security-engineer (territory: + `platform/separation_of_duties.py`). +- **Files owned:** `platform/separation_of_duties.py` (new). +- **What the file must contain** (cite sources): + 1. **Module docstring** — "Check that qaApprover != prodApprover for a + contract (ARCHITECTURE.md §10.3, D-042). Reads `approver_qa` from + the DynamoDB outbox for the contractId, compares to the + prod-dispatch `gitea.actor`. Blocks on equality, emits + `SEPARATION_OF_DUTIES_VIOLATION`, routes a halt artifact to SRE + on-call. Spike: dev-only — the check is a no-op stub that returns + (True, 'dev-only') because the spike does not exercise HITL + (A-8.1)." + 2. **`check(outbox_client, contract_id, current_prod_approver)`** + function: + - `item = outbox_client.get(contract_id)` — reads the outbox item + for the contractId (the outbox shape is defined in + `platform/audit_ledger_design.md` T-7.6: PK `contractId`, SK + `eventType#eventTs`, attributes include `approver_qa`). + - If `item is None`: return `(True, "no prior approver (first + promotion)")` — dev→qa has no SoD check (the first promotion has + no prior approver to compare against). + - `qa_approver = item.get("approver_qa")`. If `not qa_approver`: + return `(True, "no QA approver recorded (dev-only spike)")` — + the spike's dev-only path. + - If `qa_approver == current_prod_approver`: return + `(False, f"SEPARATION_OF_DUTIES_VIOLATION: qaApprover==prodApprover=={qa_approver}")` + — the platform blocks prod promotion, writes a + `SEPARATION_OF_DUTIES_VIOLATION` event to the evidence stream + (the pipeline step does the write; this function returns the + verdict), and routes a halt artifact to SRE on-call. + - Else: return `(True, "distinct")`. + 3. **`route_halt_artifact(contract_id, violation_reason, oncall_client)`** + — a helper that routes the halt artifact to SRE on-call (spike: + stub that logs the artifact; v1.2 wires a real pager). Not invoked + in the dev-only spike. + 4. **Spike-scope note** — a module-level comment: "Spike scope + (A-8.1): the spike is dev-only (REQ-27 contract has + environment: dev); HITL is not exercised. This module is authored + to its full v1.2 shape but the spike calls it with + current_prod_approver=None and a None outbox_client — the check + returns (True, 'dev-only')." + 5. **No external deps** — stdlib only (`typing` for the signature). + `outbox_client` is a duck-typed boto3-like client (has `.get(pk)`); + the spike passes a stub. No `import boto3` (the pipeline step owns + the boto3 client; this function receives it). +- **Source citations:** + - `ARCHITECTURE.md` §10.3 (separation of duties — identity + distinctness sequence). + - `RESEARCH.md` TARGET 8 (SoD sketch — the executor uses this as the + basis). + - `PROJECT.md` D-042 (approver identity = gitea.actor; the check reads + approver_qa from the outbox, compares to the prod-dispatch + gitea.actor). + - `platform/audit_ledger_design.md` T-7.6 (the outbox item shape — + PK, SK, `approver_qa` attribute). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: security-engineer, task: T-7.8` + (then `feat(P07): author platform/separation_of_duties.py (REQ-21; D-042; reads outbox approver_qa)`) +- **Self-verify:** + - [ ] `python3 -m py_compile platform/separation_of_duties.py` exits 0. + - [ ] `check(None, "any", None)` returns `(True, "no prior approver...")`. + - [ ] `check(stub_with_approver_qa("alice"), "cid", "alice")` returns + `(False, "...SEPARATION_OF_DUTIES_VIOLATION...")`. + - [ ] `check(stub_with_approver_qa("alice"), "cid", "bob")` returns + `(True, "distinct")`. + - [ ] No `import boto3` (duck-typed client). + - [ ] "SEPARATION_OF_DUTIES_VIOLATION" string is present. + +--- + +### Wave 4 — backend-engineer + security-engineer co-authored: REQ-19 confidence_signal.py + +One file, one commit, co-authored (PERSONAS.md conflict resolution: +backend owns the 6-input weighted sum + per-env thresholds; security +owns the severity→penalty + critical-override). Depends on T-7.4 +(PolicyCheckResult schema — the confidence signal consumes a list of +PolicyCheckResult records as its `policy` input). + +#### T-7.9 — Author `platform/confidence_signal.py` + +- **Owners:** backend-engineer (6-input weighted sum + per-env + thresholds + `compute()` orchestration + `Signal` dataclass) + + security-engineer (severity→penalty mapping + critical-override + semantics + the policy-input penalty loop). Co-authored in one commit. + lead-developer arbitrates if the two disagree (PERSONAS.md conflict + resolution). +- **Files owned:** `platform/confidence_signal.py` (new). +- **What the file must contain** (cite sources): + 1. **Module docstring** — the 6 inputs + weights + severity→penalty, + verbatim from D-040 + ARCHITECTURE.md §8: + ``` + Inputs (weights sum to 1.0, D-040): + 1. policy_results (0.30) — list[PolicyCheckResult] (schemas/policy_check_result.schema.json) + 2. validation (0.25) — {schema: bool, ir_resolved: bool, tf_validated: bool, tf_planned: bool} + 3. freshness (0.10) — {age_days: float, max_age_days: float} + 4. source (0.15) — {submitter: str, commit_sha: str, signed: bool} + 5. history (0.10) — {prior_rollbacks: int, prior_policy_fails: int} + 6. nfrs (0.10) — {declared: list[str], conformance: float|None} + Severity → penalty (locked, §8): + critical → hard override (score = 0, block) + high → -0.20 + medium → -0.05 + low → -0.01 + info → 0.00 + Per-env thresholds (locked, §8): dev 0.50, qa 0.75, prod 0.90, dr 0.95. + Output: {score, band, perInput, reasonCodes}. + Halt with explicit reason on missing input (§8). + ``` + 2. **`WEIGHTS`** dict — `{"policy":0.30, "validation":0.25, + "freshness":0.10, "source":0.15, "history":0.10, "nfrs":0.10}` + (D-040). Sum = 1.0. + 3. **`PENALTY`** dict — `{"critical":None, "high":0.20, "medium":0.05, + "low":0.01, "info":0.0}` (`None` = hard override — security-engineer + territory). + 4. **`THRESHOLDS`** dict — `{"dev":0.50, "qa":0.75, "prod":0.90, + "dr":0.95}` (ARCHITECTURE.md §8). + 5. **`Signal` dataclass** — `score: float`, `band: Literal["pass", + "warn", "block"]`, `perInput: dict`, `reasonCodes: list[str]`. + 6. **`_per_input_score(name, raw)`** — normalize a raw input to + `[0,1]`. Spike cold-start: unknown/missing inputs → `0.5` (A-6.2) + with a `INPUT_MISSING:` reason code. The per-input scoring + rules (documented inline): + - `policy`: weighted by severity — pass=1.0, skipped=1.0 (treat as + pass — the ACDL_TAG_NAMING skipped record is neutral), fail=0.0 + per finding, averaged. (The severity penalty is applied + separately at the score level — see `compute()`.) + - `validation`: `1.0` if all four bools true, else proportional. + - `freshness`: `1.0 - (age_days / max_age_days)` clamped to `[0,1]`. + - `source`: `1.0` if `submitter` + `commit_sha` present (dev gate is + *presence*, not *identity* — RESEARCH A-6.1). + - `history`: `1.0 - (prior_rollbacks * 0.2 + prior_policy_fails * + 0.1)` clamped to `[0,1]`. Spike cold-start (first submission) = + `0.5` (no history — A-6.2). + - `nfrs`: `1.0` if `conformance` present, else `0.5` (no NFRs + declared — spike cold-start A-6.2). + 7. **`compute(contract_id, environment, inputs)`** — the orchestrator + (backend-engineer): + - If `set(inputs.keys()) != set(WEIGHTS.keys())`: return + `Signal(0.0, "block", {}, [f"INPUT_MISSING:{m}" for m in + missing])` — halt with explicit reason on missing input (§8). + - Build `per_input`, `reasons`, `base` (weighted sum) via + `_per_input_score` for each input. + - **Severity penalty loop (security-engineer):** for each `pcr` in + `inputs.get("policy_results", [])` where `pcr["result"] == "fail"`: + - If `PENALTY[pcr["severity"]] is None` (critical): return + `Signal(0.0, "block", per_input, reasons + + [f"CRITICAL_OVERRIDE:{pcr['ruleId']}"])` — **hard override**, + one critical finding blocks regardless of all other inputs + (§8). + - Else: `penalty += PENALTY[pcr["severity"]]`. + - `score = max(0.0, min(1.0, base - penalty))` (clamp to `[0,1]`). + - `threshold = THRESHOLDS[environment]`. + - `band = "pass" if score >= threshold else ("block" if score < + threshold - 0.10 else "warn")` (the warn band is 0.10 wide — + A-6.3). + - **Dev override:** `if environment == "dev" and band == "warn": + band = "block"` — dev is autonomous, no reviewer to handle a warn + (A-6.3). + - Return `Signal(score, band, per_input, reasons)`. + 8. **`__main__`** — a tiny CLI for spike invocation: read a JSON file + of inputs + environment, print the Signal as JSON. Spike workflow + calls `python3 platform/confidence_signal.py dev`. + 9. **No external deps** — stdlib only (`dataclasses`, `typing`, + `json`, `sys`). The PolicyCheckResult records arrive as plain dicts + (the Checkov adapter emits dicts, not a typed class). + 10. **Spike cold-start note** — a module-level comment: "Spike cold- + start (A-6.2): inputs 3 (freshness), 5 (history), 6 (nfrs) are + 'present + neutral 0.5' because the spike is the first submission + with no history and no declared NFRs. The gate is *presence*, not + *conformance* — the 'all six inputs present' dev gate (§5) is + satisfied by non-null per-input scores." +- **Source citations:** + - `ARCHITECTURE.md` §8 (confidence signal — 6 inputs, weighted sum, + per-env thresholds, severity→penalty, output shape, halt-on-missing). + - `RESEARCH.md` TARGET 6 (6-input confidence signal sketch — the + executor uses this as the basis; must be self-contained + match + ARCHITECTURE.md). + - `PROJECT.md` D-040 (the 6 inputs + weights frozen for v1). + - `schemas/policy_check_result.schema.json` T-7.4 (the `policy_results` + input is a list of these records). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: backend-engineer+security-engineer, task: T-7.9` + (then `feat(P07): author platform/confidence_signal.py (REQ-19; D-040 6 inputs; co-authored backend+security)`) +- **Self-verify:** + - [ ] `python3 -m py_compile platform/confidence_signal.py` exits 0. + - [ ] `WEIGHTS` sums to 1.0; values match D-040 exactly. + - [ ] `PENALTY["critical"] is None` (hard override sentinel). + - [ ] `THRESHOLDS` matches dev 0.50 / qa 0.75 / prod 0.90 / dr 0.95. + - [ ] A missing-input call returns `Signal(0.0, "block", ..., + ["INPUT_MISSING:..."])`. + - [ ] A critical-fail PolicyCheckResult hard-overrides to `0.0, block` + regardless of other inputs. + - [ ] A dev `warn` band is flipped to `block` (autonomous, no + reviewer). + - [ ] A cold-start spike input set (all 6 present, policy_results = + [the ACDL_TAG_NAMING skipped record], validation all true, + freshness/source/history/nfrs neutral 0.5) produces a `pass` + for dev (score ≥ 0.50). + - [ ] No non-stdlib imports. + +--- + +### Wave 5 — lead-developer: verify_phase07.sh + traceability + +The phase gate. Depends on all prior waves (the verify script asserts +all 6 files exist + validate). Updates the traceability tables in +REQUIREMENTS.md + ROADMAP.md. + +#### T-7.10 — Author `scripts/verify_phase07.sh` + traceability update + +- **Owner:** lead-developer (territory: `scripts/verify_phase*.sh`, + `.ciagent/**` traceability). +- **Files owned:** `scripts/verify_phase07.sh` (new) + + `.ciagent/REQUIREMENTS.md` (traceability table edit) + + `.ciagent/ROADMAP.md` (Phase 07 status edit). +- **What the verify script must assert** (cite the must_haves in this + PLAN.md frontmatter): + 1. **All 6 schema/design files exist:** + - `schemas/ir.schema.json` + - `schemas/policy_check_result.schema.json` + - `schemas/contract.schema.json` + - `platform/confidence_signal.py` + - `platform/audit_ledger_design.md` + - `platform/hitl_matrix_design.md` + - `adapters/terraform/policy/checkov_adapter.py` + - `platform/separation_of_duties.py` + - `docs/architecture-v1.0.md` + (9 files — the 6 REQ-mapped files + checkov_adapter + SoD + the + snapshot; count carefully: REQ-17→1, REQ-18→2, REQ-19→1, REQ-20→1, + REQ-21→2, REQ-22→1, REQ-16→1 = 9 files total.) + 2. **The 3 JSON Schemas validate** as JSON Schema draft 2020-12: + ```bash + python3 -m jsonschema schemas/ir.schema.json && \ + python3 -m jsonschema schemas/policy_check_result.schema.json && \ + python3 -m jsonschema schemas/contract.schema.json + ``` + (If `python3 -m jsonschema` is unavailable, fall back to `ajv + compile -s schemas/` — note this in the script.) + 3. **The 3 .py files py_compile:** + ```bash + python3 -m py_compile platform/confidence_signal.py \ + platform/separation_of_duties.py \ + adapters/terraform/policy/checkov_adapter.py + ``` + 4. **The 3 .md design files are non-empty** (markdown presence): + ```bash + test -s platform/audit_ledger_design.md && \ + test -s platform/hitl_matrix_design.md && \ + test -s docs/architecture-v1.0.md + ``` + 5. **All 11 decision IDs + OpenTofu are present in PROJECT.md:** + ```bash + for id in W1.A W1.B W2.A W3.D W3.E BA.A BA.B BA.C BA.D BA.E BA.F; do + grep -q "$id" .ciagent/PROJECT.md || FAIL "missing $id" + done + grep -qi "opentofu" .ciagent/PROJECT.md || FAIL "missing OpenTofu" + ``` + 6. **`docs/architecture-v1.0.md` exists + status is v1.0:** + ```bash + test -f docs/architecture-v1.0.md + grep -q "v1.0" docs/architecture-v1.0.md | head -1 # status line + ``` + 7. **D-040, D-041, D-042, D-043, D-044 present in PROJECT.md** (the + Phase 07-relevant decisions): + ```bash + for d in D-040 D-041 D-042 D-043 D-044; do + grep -q "$d" .ciagent/PROJECT.md || FAIL "missing $d" + done + ``` + 8. **The spike contract validates against the contract schema** (a + cross-file consistency check — the spike contract is the one + REQ-27 will use): + ```bash + # Write a minimal dev contract to a temp file, validate against + # schemas/contract.schema.json. (The real contracts/spike.yaml + # lands in Phase 10.) + echo '{"stack":"l2-static-asset","environment":"dev","inputs":{"bucket_name":"x","region":"us-east-1"}}' \ + > /tmp/spike-contract.json + python3 -c "import jsonschema, json; \ + jsonschema.validate(json.load(open('/tmp/spike-contract.json')), \ + json.load(open('schemas/contract.schema.json')))" + ``` + 9. **A minimal IR instance validates against the IR schema:** + ```bash + echo '{"version":"1.0.0","stack":{"name":"l2-static-asset","kind":"l2","depth":1},"resources":[{"id":"s3","type":"aws:s3:bucket","module":"l1-s3@1.0.0","inputs":{"bucket_name":"x","region":"us-east-1"}}]}' \ + > /tmp/spike-ir.json + python3 -c "import jsonschema, json; \ + jsonschema.validate(json.load(open('/tmp/spike-ir.json')), \ + json.load(open('schemas/ir.schema.json')))" + ``` + 10. **Exit 0 on success, non-zero on any failure** (with a clear + `FAIL: ` message per assertion). Print `VERIFIED — Phase + 07: architecture v1.0 finalized; 6 files authored + 11 decisions + resolved` on success. +- **Traceability update** (in the same commit or a follow-up): + - `.ciagent/REQUIREMENTS.md` traceability table: REQ-16..22 → + `complete (v1.1.2)` (the Phase 07 ship tag — Phase 06 was v1.1.1). + - `.ciagent/ROADMAP.md` Phase 07 status: `pending` → `complete + (v1.1.2)`. +- **Source citations:** + - This PLAN.md's must_haves (the assertions enumerate them). + - `PERSONAS.md` verification_toolchain (typecheck/test/build + substitutes — `python3 -m jsonschema`, `py_compile`, + `scripts/verify_phaseNN.sh`). +- **Commit message:** + `phase: 7, status: plan-as-execute, persona: lead-developer, task: T-7.10` + (then `verify(P07): VERIFIED — 6 files authored, 11 decisions resolved, architecture v1.0`) + (then `docs(P07): post-ship traceability + roadmap update (v1.1.2)`) +- **Self-verify:** + - [ ] `scripts/verify_phase07.sh` exits 0 when run from repo root. + - [ ] The script fails (non-zero) if any of the 9 files is missing. + - [ ] The script fails if any of the 3 JSON Schemas is invalid JSON + Schema. + - [ ] The script fails if any of the 3 .py files fails py_compile. + - [ ] The script fails if any of the 11 decision IDs is missing from + PROJECT.md. + - [ ] The script fails if `docs/architecture-v1.0.md` does not say + `v1.0` in its status line. + - [ ] REQUIREMENTS.md traceability shows REQ-16..22 complete. + - [ ] ROADMAP.md Phase 07 status is complete. + +--- + +## Wave ordering + dependencies (summary) + +``` +Wave 1 (lead-developer) T-7.1 architecture-v1.0.md (umbrella; references decisions) + ↓ +Wave 2 (platform + backend) T-7.2 ir.schema.json (platform-engineer) + T-7.3 contract.schema.json (backend-engineer) [parallel with T-7.2] + ↓ +Wave 3 (security-engineer) T-7.4 policy_check_result.schema.json + T-7.5 checkov_adapter.py (depends T-7.4) + T-7.6 audit_ledger_design.md + T-7.7 hitl_matrix_design.md + T-7.8 separation_of_duties.py (depends T-7.6 outbox shape) + ↓ +Wave 4 (backend + security) T-7.9 confidence_signal.py (depends T-7.4 PolicyCheckResult; co-authored) + ↓ +Wave 5 (lead-developer) T-7.10 verify_phase07.sh + traceability (depends all prior) +``` + +**Parallelizable within a wave:** +- Wave 2: T-7.2 (IR, platform-engineer) || T-7.3 (contract, + backend-engineer) — independent schemas, different owners, no shared + files. + +**Sequential within a wave (same persona or file dependency):** +- Wave 3: T-7.4 → T-7.5 (adapter consumes the schema it emits) → T-7.6 + → T-7.7 → T-7.8 (SoD reads the outbox shape T-7.6 documents). All + security-engineer; one persona, sequential to avoid territory + friction. + +**Cross-wave dependencies:** +- Wave 4 → Wave 3 T-7.4 (confidence_signal consumes PolicyCheckResult). +- Wave 4 → Wave 3 T-7.6 (confidence_signal's policy input shape is + informed by the outbox/ledger design — not a hard dependency, but + consistency). +- Wave 5 → all prior waves (verify script asserts all files). + +--- + +## Per-file content checklist (executor self-verify) + +### `schemas/ir.schema.json` (REQ-17, T-7.2) +- [ ] JSON Schema draft 2020-12 (`$schema` + `$id` + `title`). +- [ ] Top-level `required: [version, stack, resources]`. +- [ ] `stack.depth` integer min 1 max 5. +- [ ] `stack.name` pattern `^l[12]-[a-z][a-z0-9-]*$`. +- [ ] `stack.kind` enum `[l1, l2]`. +- [ ] `resource` required `[id, type, module, inputs]`. +- [ ] `resource.module` pattern `^l1-[a-z][a-z0-9-]*@\d+\.\d+\.\d+$` + (name@semver per W3.D). +- [ ] `resource.parent` (single parent per child, ARCHITECTURE.md §12.1). +- [ ] `resource.outputs` (typed outputSpec). +- [ ] `resource.nfrs` (opaque to adapter, consumed by confidence NFR + input). +- [ ] `relationship.kind` enum `[parent, depends_on, uses_output]`. +- [ ] `relationship.shared_keyword` reserved for v2 (present, unused). +- [ ] **Substrate-agnostic invariant:** no Terraform-block keywords + (`variable`, `output` as block, `aws_` provider prefixes) in the + schema body; `type` values are IR types (`aws:s3:bucket`), not + Terraform types (`aws_s3_bucket`). +- [ ] `$comment` explains the v1 round-trip to Terraform. +- [ ] `python3 -m jsonschema` validates the schema itself. +- [ ] A minimal valid IR instance validates against it. + +### `schemas/policy_check_result.schema.json` (REQ-18, T-7.4) +- [ ] JSON Schema draft 2020-12. +- [ ] Canonical shape from ARCHITECTURE.md §12.6: `contractId`, + `evaluatedAt`, `engine`, `ruleId`, `severity`, `result`, + `message`, `evidence`, `resourceRef`. +- [ ] `engine` enum `[checkov, kyverno, opa]`. +- [ ] `severity` enum `[critical, high, medium, low, info]`. +- [ ] `result` enum `[pass, fail, skipped, error]`. +- [ ] `evidence` object, additionalProperties true (opaque to signal). +- [ ] `python3 -m jsonschema` validates; a valid instance passes. + +### `adapters/terraform/policy/checkov_adapter.py` (REQ-18, T-7.5) +- [ ] Module docstring (Checkov JSON → PolicyCheckResult). +- [ ] `RULE_MAP` with all 11 Checkov rule IDs (CKV_AWS_41/45/46/20/57/ + 24/25/1/40/7/33). +- [ ] `_to_pcr()` maps severity (prefer Checkov's, fall back to + RULE_MAP default). +- [ ] `_emit_tag_naming_skipped()` emits the ACDL_TAG_NAMING SKIPPED + record (D-043). +- [ ] `adapt()` tolerates both Checkov JSON shapes + (`{framework:{results:{...}}}` and `{framework:{...}}`). +- [ ] `__main__` CLI: `checkov_adapter.py `. +- [ ] stdlib only (no `import boto3` / no third-party). +- [ ] `python3 -m py_compile` exits 0. +- [ ] Output records validate against policy_check_result.schema.json. + +### `schemas/contract.schema.json` (REQ-22, T-7.3) +- [ ] JSON Schema draft 2020-12. +- [ ] Top-level `required: [stack, environment]` (dev minimum). +- [ ] `stack` pattern `^l2-[a-z][a-z0-9-]*$`. +- [ ] `environment` enum `[dev, qa, prod, dr]` (no `staging`). +- [ ] `inputs` object, additionalProperties `[string, number, boolean]`, + always optional. +- [ ] `validation` (e2eSuite + loadTest), `runbook`, `dashboard`, + `oncall`, `drDrillRef` all present. +- [ ] `profile` enum `[developer, agentic]`, default `developer`. +- [ ] L3B fields `naturalLanguageIntent`, `confidenceAtSubmission`, + `agentTrace` present, optional everywhere. +- [ ] `supersedes` (uuid, optional). +- [ ] `allOf` conditionals: qa→validation required, prod→runbook+ + dashboard+oncall required, dr→drDrillRef required, + agentic→naturalLanguageIntent required. +- [ ] Spike contract validates; qa/prod/dr/agentic negative cases fail. + +### `platform/confidence_signal.py` (REQ-19, T-7.9) +- [ ] Module docstring with the 6 inputs + weights + severity→penalty. +- [ ] `WEIGHTS` = `{policy:0.30, validation:0.25, freshness:0.10, + source:0.15, history:0.10, nfrs:0.10}` (D-040; sums to 1.0). +- [ ] `PENALTY` = `{critical:None, high:0.20, medium:0.05, low:0.01, + info:0.0}` (security-engineer; None = hard override). +- [ ] `THRESHOLDS` = `{dev:0.50, qa:0.75, prod:0.90, dr:0.95}` (§8). +- [ ] `Signal` dataclass: `score, band, perInput, reasonCodes`. +- [ ] `_per_input_score()` per-input rules (policy severity-weighted, + validation all-true, freshness age ratio, source presence, history + rollback/fail, nfrs conformance or neutral 0.5). +- [ ] `compute()` halts with `INPUT_MISSING:` on missing input. +- [ ] `compute()` critical-override: any `fail` + `critical` → + `Signal(0.0, "block", ..., ["CRITICAL_OVERRIDE:"])`. +- [ ] `compute()` dev `warn` flipped to `block` (autonomous, no + reviewer). +- [ ] Spike cold-start (all 6 present, ACDL_TAG_NAMING skipped, + validation true, others neutral 0.5) → dev `pass`. +- [ ] stdlib only; `python3 -m py_compile` exits 0. + +### `platform/audit_ledger_design.md` (REQ-20, T-7.6) +- [ ] Header notes Phase 07 design + spike scope (D-041) + v1.2 + build-out. +- [ ] Three tiers: S3 Object Lock (cold/source of truth), acdl-evidence + (hot/query index), DynamoDB outbox (write path). +- [ ] S3 Object Lock: **compliance mode**, **7-year retention**. +- [ ] DynamoDB outbox: RPO=0, `PAY_PER_REQUEST` (D-044), PK/SK, + TTL `expire_at` = now + 365d. +- [ ] `prev_event_hash` chain: SHA-256 over canonical JSON, genesis + `prev_hash="GENESIS"`. +- [ ] JWS: detached signature, platform KMS key, quarterly rotation + (v1.2). +- [ ] Daily checkpoints (v1.2; spike runs in minutes). +- [ ] Async worker + DLQ (v1.2; spike RTO = workflow re-run). +- [ ] Outbox item shape (PK, SK, payload, prev_event_hash, hash, + approver_qa, approver_prod, environment, stack, score, band, + expire_at; v1.2: jws, checkpoint_ref). +- [ ] RPO/RTO table (spike vs v1.2). +- [ ] Decision trail: D-041, D-044, D-042 cited. +- [ ] Clear "Spike scope" vs "v1.2 build-out" section boundary. +- [ ] File non-empty. + +### `platform/hitl_matrix_design.md` (REQ-21, T-7.7) +- [ ] Header notes Phase 07 design + v1.2 wiring + spike dev-only. +- [ ] Pre-execution gate model (§10.1). +- [ ] Gitea-specific gate mechanics: `workflow_dispatch` + + `approve_qa`/`approve_prod`/`approve_dr`, `gitea.actor` as + approver identity (D-042). +- [ ] "no Environments API" note (Gitea constraint). +- [ ] CODEOWNERS routing (§10.2). +- [ ] Full 8-concern matrix from §10.4 (verbatim table). +- [ ] Timeout 1d warn / 2d freeze (§10.5) + `PENDING_ATTESTATION*` + states. +- [ ] Rejection → HELD + `PROMOTION_REJECTED` + `supersedes` (§10.6). +- [ ] SoD section pointing at `platform/separation_of_duties.py`. +- [ ] Spike-scope note (dev-only, HITL not exercised). +- [ ] D-042 cited. +- [ ] File non-empty. + +### `platform/separation_of_duties.py` (REQ-21, T-7.8) +- [ ] Module docstring (qaApprover != prodApprover, D-042, reads outbox). +- [ ] `check(outbox_client, contract_id, current_prod_approver)` → + `(ok: bool, reason: str)`. +- [ ] `None` outbox item → `(True, "no prior approver...")`. +- [ ] Empty `approver_qa` → `(True, "no QA approver... (dev-only)")`. +- [ ] `qa_approver == current_prod_approver` → `(False, + "SEPARATION_OF_DUTIES_VIOLATION: ...")`. +- [ ] Distinct → `(True, "distinct")`. +- [ ] `route_halt_artifact()` helper (spike stub). +- [ ] stdlib only (duck-typed outbox_client; no `import boto3`). +- [ ] `python3 -m py_compile` exits 0. + +### `docs/architecture-v1.0.md` (REQ-16, T-7.1) +- [ ] Status line says `v1.0` (not `v0.2`). +- [ ] Resolution log table with all 11 IDs (W1.A, W1.B, W2.A, W3.D, + W3.E, BA.A, BA.B, BA.C, BA.D, BA.E, BA.F, Q1.3). +- [ ] §13 markers changed from `🟡 OPEN` to `✅ RESOLVED (see + PROJECT.md)`. +- [ ] §14 status rewritten to v1.0 + spike/v1.2 note. +- [ ] §15 new table mapping the 6 files → REQ + owner persona. +- [ ] `gitea-runner` rename applied (D-046); `act_runner` only in a + "formerly" note. +- [ ] Body §§1-12 copied from upstream `docs/architecture.md`. + +### `scripts/verify_phase07.sh` (T-7.10) +- [ ] Asserts all 9 files exist (6 REQ-mapped + checkov_adapter + SoD + + snapshot). +- [ ] Validates the 3 JSON Schemas (`python3 -m jsonschema` or `ajv`). +- [ ] py_compiles the 3 .py files. +- [ ] Markdown presence for the 3 .md files. +- [ ] Greps PROJECT.md for all 11 decision IDs + OpenTofu. +- [ ] Confirms `docs/architecture-v1.0.md` exists + status is v1.0. +- [ ] Confirms D-040..D-044 present in PROJECT.md. +- [ ] Cross-check: spike contract validates against contract schema; + minimal IR validates against IR schema. +- [ ] Exits 0 on success, non-zero with `FAIL:` on any failure. +- [ ] Prints `VERIFIED — Phase 07: ...` on success. +- [ ] REQUIREMENTS.md REQ-16..22 → complete. +- [ ] ROADMAP.md Phase 07 → complete. + +--- + +## Spike scope vs v1.2 build-out (design-authoring boundary) + +The Phase 07 designs author the **full target** but clearly mark the +spike scope (D-041, D-043) vs the v1.2 build-out. This boundary is +binding for the executor: + +| Concern | Spike scope (Phases 08-10) | v1.2 build-out (design only in Phase 07) | +|---------|------------------------------|------------------------------------------| +| Audit ledger | hash chain + DynamoDB outbox write + acdl-evidence mirror | S3 Object Lock + JWS + KMS key + async worker + DLQ + daily checkpoints | +| HITL | dev-only (not exercised) | full 8-concern matrix wiring (qa/prod/dr gates, timeout, rejection) | +| SoD | dev-only stub (returns "distinct") | full outbox read + gitea.actor compare + halt artifact routing | +| Policy | Checkov + ACDL_TAG_NAMING SKIPPED (D-043) | custom Checkov YAML rule for tag/naming + Kyverno + OPA | +| L3B | single stub contract submission | 5-skill catalog + real agent | +| AWS auth | per-run-rotated long-lived key (D-039 waiver) | real OIDC federation (blocked on go-gitea/gitea#36988) | +| Terraform | `plan` only | `apply` (HITL-gated) | +| Regions | single-region (us-east-1) | multi-region | + +The designs must not collapse this boundary — the spike scope is the +*minimum* that proves the IR commitments hold (REQ-28); the v1.2 +build-out is the production platform. Each design doc has a clear +"Spike scope" section + a "v1.2 build-out" section. + +--- + +## Decisions made during planning + +| ID | Decision | Rationale | Confidence | Alternatives | +|----|----------|-----------|------------|--------------| +| D-P07-1 | 5 waves, not 4 | Wave 1 (snapshot) is umbrella — it references the *decisions* (already in PROJECT.md), not the *authored files*, so it can land first and does not need to wait for Waves 2-4. Splitting the security-engineer's 5 files into one wave (Wave 3) keeps one persona's territory coherent. Wave 4 (co-authored confidence_signal) is a distinct co-authorship event, separate from Wave 3. Wave 5 (verify) is the phase gate. | 0.85 | 4 waves (merge Wave 1 into Wave 5; but then the snapshot is authored last, after the files it references — worse); 6 waves (split Wave 3 into two — unnecessary, same persona). | +| D-P07-2 | Wave 2 parallel (IR || contract) | The two JSON Schemas are independent (no shared `$ref`, no shared file), owned by different personas (platform-engineer + backend-engineer), and both live under `schemas/`. Parallelizing them uses the `parallelization.enabled: true` config + `max_concurrent_agents: 5`. | 0.90 | Sequential (slower, no benefit — no file dependency). | +| D-P07-3 | Wave 3 sequential (one persona, 5 files) | All 5 files are security-engineer territory. Running them sequentially within the wave avoids territory friction (PERSONAS.md territory enforcement mode = `warn`) and respects the intra-wave file dependencies (T-7.5 → T-7.4; T-7.8 → T-7.6). | 0.85 | Parallel (territory overlap risk; the co-owned `schemas/policy_check_result.schema.json` is authored by security-engineer here but backend-engineer co-owns `schemas/**` — sequential avoids a race). | +| D-P07-4 | Wave 4 co-authored in one commit | `platform/confidence_signal.py` is co-owned (backend: 6-input weighted sum + thresholds; security: severity→penalty + critical-override). One file, one commit, co-authored. lead-developer arbitrates if the two disagree (PERSONAS.md conflict resolution). | 0.80 | Two commits (backend first, security second) — but that splits a single file across two commits, which is worse for review. | +| D-P07-5 | verify_phase07.sh cross-checks spike contract + minimal IR against schemas | The verify script does not just assert file presence — it cross-checks that the spike contract (REQ-27's `contracts/spike.yaml` shape) validates against the contract schema, and a minimal IR validates against the IR schema. This catches schema/instance drift early (Phase 10 would otherwise discover it). | 0.85 | Presence-only assertions (weaker; Phase 10 discovers drift later). | + +--- + +## Cross-persona co-authorship risk + +Two files are co-owned per PERSONAS.md conflict resolutions: + +1. **`platform/confidence_signal.py`** (Wave 4, T-7.9) — backend-engineer + (6-input weighted sum + per-env thresholds + `compute()` orchestration) + + security-engineer (severity→penalty + critical-override). **Risk:** + the two authors must agree on the `PENALTY` dict's sentinel for + critical (`None` vs a special float vs a separate `hard_override` + flag) + the `compute()` control flow (where the critical-override + short-circuit lives). The PLAN.md spec fixes this: `PENALTY["critical"] + is None` is the sentinel; the critical-override is a short-circuit + `return` in `compute()` before the score clamp. lead-developer + arbitrates if the authors disagree (PERSONAS.md). **Mitigation:** one + commit, co-authored; the PLAN.md content checklist is the contract + between the two authors. + +2. **`schemas/policy_check_result.schema.json`** (Wave 3, T-7.4) — + security-engineer authors it (owns the adapter); backend-engineer + co-owns `schemas/**` (contract + IR + PolicyCheckResult). **Risk:** + low — the shape is locked verbatim from ARCHITECTURE.md §12.6; there + is no design freedom to conflict over. security-engineer authors; + backend-engineer reviews. **Mitigation:** the §12.6 canonical shape + is the contract; no co-authoring needed, just review. + +No other co-authorship risk in Phase 07. The IR schema +(`schemas/ir.schema.json`) is platform-engineer's (PERSONAS.md conflict +resolution: platform-engineer owns the IR; backend-engineer owns the +contract schema + contract→IR resolution — *different* files). The +contract schema is backend-engineer's alone. The security-engineer's +five files (Wave 3) are all security-engineer territory with no overlap. + +--- + +*End of Phase 07 PLAN. Path: `/root/acdl/.ciagent/PLAN.md`.* \ No newline at end of file