Files
acdl/.ciagent/PLAN.md
T
Jon Chery b40aadd195 docs(P07): create Phase 07 plan (architecture-v1-finalization)
---ci---
project: acdl
phase: 7
milestone: v1.1
status: plan
plan:
  waves: 5
  tasks: 10
  requirements: [REQ-16, REQ-17, REQ-18, REQ-19, REQ-20, REQ-21, REQ-22]
---/ci---

Phase 07 plan authored by ci-planner. 5 waves (strictly ordered):
- Wave 1 (lead-developer): REQ-16 architecture-v1.0.md snapshot (umbrella)
- Wave 2 (platform + backend parallel): REQ-17 ir.schema.json || REQ-22 contract.schema.json
- Wave 3 (security-engineer, 5 files): REQ-18 PolicyCheckResult schema + checkov_adapter.py, REQ-20 audit_ledger_design.md, REQ-21 hitl_matrix_design.md + separation_of_duties.py
- Wave 4 (backend + security co-authored): REQ-19 confidence_signal.py (depends on Wave 3)
- Wave 5 (lead-developer): verify_phase07.sh + traceability

9 deliverable files + 1 verify script. Cross-persona co-authorship on
confidence_signal.py (backend weighted-sum + security penalty mapping)
mitigated by the PLAN content checklist fixing the critical-override
sentinel + short-circuit location.
2026-07-21 18:40:20 +00:00

76 KiB

phase, name, milestone, milestone_type, status, requirements, must_haves, verification
phase name milestone milestone_type status requirements must_haves verification
7 architecture-v1-finalization v1.1 feature planned
REQ-16
REQ-17
REQ-18
REQ-19
REQ-20
REQ-21
REQ-22
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)
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)
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
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
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
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
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
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
typecheck test build
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 scripts/verify_phase07.sh 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 shaperequired: [version, stack, resources], properties: version (semver string), stack (name/kind/depth), resources (array of resource, minItems 1), relationships (array of relationship, optional).
    3. stackname 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:<resourceId>.<outputName>); 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:"<uuid>",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 <contract-uuid>.
    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-<account-id>, 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 dataclassscore: 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:<name> 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 <inputs.json> 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:
      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/<file> — note this in the script.)
    3. The 3 .py files py_compile:
      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):
      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:
      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:
      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):
      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):
      # 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:
      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: <reason> 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: pendingcomplete (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 <checkov.json> <contract-id>.
  • 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:<name> on missing input.
  • compute() critical-override: any fail + criticalSignal(0.0, "block", ..., ["CRITICAL_OVERRIDE:<ruleId>"]).
  • 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.
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.