--- phase: 7 name: architecture-v1-finalization milestone: v1.1 milestone_type: feature status: planned requirements: [REQ-16, REQ-17, REQ-18, REQ-19, REQ-20, REQ-21, REQ-22] must_haves: # 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: "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 07 — architecture-v1-finalization PLAN ## Goal 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. 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 - **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). 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 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. 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. 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 (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 ``` **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. **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. --- ### Wave 1 — lead-developer: REQ-16 architecture-v1.0.md snapshot 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. #### T-7.1 — Author `docs/architecture-v1.0.md` - **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. --- ### Wave 2 — platform-engineer + backend-engineer (parallel): REQ-17 IR + REQ-22 contract schema Two independent JSON Schemas, different owners, parallelizable. Both are JSON Schema draft 2020-12, both live under `schemas/`, both must `python3 -m jsonschema` validate. #### T-7.2 — Author `schemas/ir.schema.json` (Target Stack IR) - **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). #### 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`.*