Files
acdl/.ciagent/PLAN.md
T
Jon Chery cc4c27c8ab docs(P10): create Phase 10 plan (v1-spike-l2-and-contract-e2e)
---ci---
project: acdl
phase: 10
milestone: v1.1
status: plan
plan:
  waves: 5
  tasks: 9
  requirements: [REQ-25, REQ-27, REQ-28]
---/ci---

Phase 10 plan (milestone capstone). 5 waves, 9 files:
- Wave 1 (platform): L2 composition + registry + adapter L2 extension (D-P10-1)
- Wave 2 (backend): contract spike.yaml + contract_resolver.py + outbox_writer.py
- Wave 3 (backend+security+platform): run_spike_e2e.sh (10-step pipeline)
- Wave 4 (lead): verify_phase10.sh (incl. REQ-28 substrate-agnostic grep)
- Wave 5 (EXECUTE-only): run e2e against real AWS + verify + ship

Key decisions: D-P10-1 (adapter consumes resolved IR instance; L2 root
module = L1 resource for depth-1), D-P10-2 (YAML contract + JSON Schema
validation), D-P10-3 (single CONFIDENCE_COMPUTED event; GENESIS chain).

REQ-28 binding claim: only adapters/ files contain aws_s3_bucket / aws_
Terraform-specific terms; modules-ir/ schemas/ contracts/ acdl_platform/
are substrate-agnostic.
2026-07-21 19:31:42 +00:00

9.6 KiB

phase, name, milestone, milestone_type, status, requirements, must_haves, verification
phase name milestone milestone_type status requirements must_haves verification
10 v1-spike-l2-and-contract-e2e v1.1 feature planned
REQ-25
REQ-27
REQ-28
modules-ir/l2/l2-static-asset/composition.json exists with kind=l2, depth=1, one child l1-s3@1.0.0, wires passthrough
modules-ir/registry.json extended with l2-static-asset@1.0.0 entry
modules-ir/l2/l2-static-asset/README.md documents the thin-composition
contracts/spike.yaml exists (stack: l2-static-asset, environment: dev, inputs: bucket_name + region)
acdl_platform/contract_resolver.py exists, py_compiles, loads YAML contract -> validates against contract.schema.json -> resolves L2 composition -> emits IR instance validating against ir.schema.json
adapters/terraform/adapter.py extended to handle kind=l2 IR instances (D-P10-1: shallow L2 root module = the L1 resource)
acdl_platform/outbox_writer.py exists, py_compiles, writes a DynamoDB outbox item (PK contractId, SK eventType#eventTs, prev_event_hash=GENESIS, hash=SHA-256 canonical JSON, expire_at TTL)
scripts/run_spike_e2e.sh exists, bash -n passes, orchestrates the 10-step end-to-end pipeline
scripts/verify_phase10.sh exists, bash -n passes, asserts all success criteria + REQ-28 (grep: only adapters/ files contain aws_s3_bucket; modules-ir/ schemas/ contracts/ acdl_platform/ are substrate-agnostic)
Evidence event is written to the DynamoDB outbox (verified by querying the table)
Confidence band is 'pass' for dev (score >= 0.50)
typecheck test build
python3 -m py_compile acdl_platform/contract_resolver.py acdl_platform/outbox_writer.py adapters/terraform/adapter.py && bash -n scripts/run_spike_e2e.sh scripts/verify_phase10.sh scripts/verify_phase10.sh terraform -chdir=terraform/spike init -lock=false

Phase 10 — v1-spike-l2-and-contract-e2e PLAN

Goal

The milestone capstone. Implement l2-static-asset (thin-composition referencing l1-s3 only, depth 1), the contract→IR resolution, and one end-to-end contract submission flowing through: contract schema validation → IR resolution → terraform plan (real AWS) → Checkov PolicyCheckResult → confidence signal → evidence event to the DynamoDB outbox. Verify the IR commitments hold (REQ-28: the adapter is the only substrate-specific code; no polyglot mess).

After Phase 10 ships + verifies: the COMPLETE gate (review → ship v1.2.0 → audit).

Requirements covered

  • REQ-25 → T-10.1 (composition.json), T-10.2 (registry extend), T-10.3 (README)
  • REQ-27 → T-10.4 (spike.yaml), T-10.5 (contract_resolver.py), T-10.6 (adapter L2 extension), T-10.7 (outbox_writer.py), T-10.8 (run_spike_e2e.sh)
  • REQ-28 → T-10.9 (verify_phase10.sh with the substrate-agnostic grep check)

Waves

Wave 1 (platform-engineer)   — T-10.1 composition.json, T-10.2 registry, T-10.3 README, T-10.6 adapter L2 extension
Wave 2 (backend-engineer)    — T-10.4 spike.yaml, T-10.5 contract_resolver.py, T-10.7 outbox_writer.py
Wave 3 (backend+security)    — T-10.8 run_spike_e2e.sh
Wave 4 (lead-developer)      — T-10.9 verify_phase10.sh
Wave 5 (lead, EXECUTE-only)  — run e2e + verify + traceability + ship

Dependencies: Wave 2 → Wave 1 (resolver reads composition.json); Wave 3 → Wave 1+2; Wave 4 → all prior; Wave 5 EXECUTE-only.


Wave 1 — platform-engineer: L2 composition + registry + adapter extension

T-10.1 — Author modules-ir/l2/l2-static-asset/composition.json

  • Owner: platform-engineer
  • Content: {name: l2-static-asset, version: 1.0.0, kind: l2, depth: 1, children: [{id: s3, module: l1-s3@1.0.0}], wires: {bucket_name: {target: s3, input: bucket_name}, region: {target: s3, input: region}}}

T-10.2 — Extend modules-ir/registry.json with l2-static-asset@1.0.0

  • Owner: platform-engineer
  • Content: add "l2-static-asset": {"1.0.0": {"composition": "modules-ir/l2/l2-static-asset/composition.json", "published_at": "<iso8601>", "deprecated": false}}

T-10.3 — Author modules-ir/l2/l2-static-asset/README.md

  • Owner: platform-engineer
  • Content: thin-composition doc (references l1-s3 only, depth 1, wires passthrough, the adapter's L2→root-module translation, D-P10-1)

T-10.6 — Extend adapters/terraform/adapter.py for kind=l2

  • Owner: platform-engineer
  • Content: D-P10-1: the adapter consumes the resolved IR instance (which has kind=l2 + the L1 resources as its resources array). For a depth-1 thin-composition, the L2 root module IS the L1's resource — no separate module block. The existing adapt() + TYPE_MAP + resource emission handle both l1 and l2 instances (the resources array is the same shape). The only L2-specific bit: the relationships array is ignored at TF level for the spike. Update the backend key to spike/l2-static-asset/terraform.tfstate for the L2 spike.

Wave 2 — backend-engineer: contract + resolver + outbox writer

T-10.4 — Author contracts/spike.yaml

  • Owner: backend-engineer
  • Content: stack: l2-static-asset\nenvironment: dev\ninputs:\n bucket_name: acdl-spike-bucket\n region: us-east-1\n

T-10.5 — Author acdl_platform/contract_resolver.py

  • Owner: backend-engineer
  • Content: resolve(contract_path) -> ir_instance dict. Steps: (1) load YAML; (2) validate against contract.schema.json; (3) look up the L2 in registry.json; (4) load composition.json; (5) map contract inputs through wires to child L1 inputs; (6) emit IR instance; (7) validate IR against ir.schema.json. CLI: contract_resolver.py <contract.yaml> <out_ir.json>. stdlib + jsonschema + yaml only.

T-10.7 — Author acdl_platform/outbox_writer.py

  • Owner: backend-engineer
  • Content: write_event(event, outbox_table="acdl-outbox") — compute SHA-256 over canonical JSON, set prev_event_hash="GENESIS" for the first event, build DynamoDB item, boto3 put_item. CLI: outbox_writer.py <event.json>. stdlib + boto3.

Wave 3 — backend+security+platform: e2e runner

T-10.8 — Author scripts/run_spike_e2e.sh

  • Owner: backend-engineer (orchestration) + platform-engineer (TF) + security-engineer (Checkov)
  • Content: the 10-step orchestrator: (1) load .env.secrets; (2) resolve contract→IR (contract_resolver.py validates as step 1); (3) adapter compiles IR→terraform/spike/*.tf; (4) terraform init -lock=false + validate + plan -lock=false -out=tfplan; (5) run Checkov on terraform/spike/main.tf → /tmp/checkov.json; (6) checkov_adapter.py → /tmp/pcr.json (PolicyCheckResult list); (7) build confidence inputs (policy = PCR list, validation all true, freshness/source/history/nfrs cold-start) + confidence_signal.py → /tmp/signal.json; (8) assert band=="pass" for dev; (9) write evidence event to outbox (outbox_writer.py); (10) print summary + Signal + outbox item. Exit 0 only if all steps succeed + band==pass.

Wave 4 — lead-developer: verify script

T-10.9 — Author scripts/verify_phase10.sh

  • Owner: lead-developer
  • Content: 8 checks: (a) composition.json exists + shape; (b) spike.yaml validates against contract schema; (c) resolver py_compiles + emits IR validating against ir.schema.json; (d) adapter py_compiles + emits main.tf with aws_s3_bucket; (e) run_spike_e2e.sh exits 0; (f) confidence band is "pass" for dev; (g) outbox item exists (query DynamoDB); (h) REQ-28: grep for aws_s3_bucket + aws_ — only adapters/ match; modules-ir/ schemas/ contracts/ acdl_platform/ do NOT match (substrate-agnostic).

Wave 5 — EXECUTE-only

Run scripts/run_spike_e2e.sh against real AWS + scripts/verify_phase10.sh + traceability + ship.

Decisions made during planning

ID Decision Rationale
D-P10-1 The adapter handles kind=l2 by consuming the resolved IR instance (L1 resources as the resources array); for depth-1, the L2 root module IS the L1's resource — no separate module block. Relationships ignored at TF level for the spike. The adapter is a thin layer; the composition is shallow. v1.2 may emit module "l1_s3" { source = "..." } when L1s become real TF modules.
D-P10-2 The contract is authored as YAML + the resolver parses YAML→dict→validates against the JSON contract schema. YAML is the consumer surface; JSON Schema validates the parsed dict. PyYAML required.
D-P10-3 The evidence event is a single CONFIDENCE_COMPUTED event (spike writes ONE event; chain = GENESIS→this event). The spike proves the outbox write path; v1.2 writes the full event stream.

Spike scope vs v1.2 boundary

Concern Spike (Phase 10) v1.2
L2 composition depth-1, one child, wires passthrough multi-child, complex wires, module blocks
Evidence events ONE CONFIDENCE_COMPUTED event full stream (submission, validation, plan, policy, confidence, promotion)
Audit chain GENESIS → one event full chain + JWS + Object Lock
HITL dev-only (not exercised) qa/prod/dr gates wired
Checkov terraform framework + ACDL_TAG_NAMING SKIPPED custom Checkov YAML rule + Kyverno + OPA

REQ-28 verification (the binding spike claim)

verify_phase10.sh's Check (h) greps the repo for aws_s3_bucket + aws_ (Terraform-specific terms). ONLY adapters/terraform/adapter.py

  • adapters/terraform/policy/checkov_adapter.py should match. modules-ir/, schemas/, contracts/, acdl_platform/confidence_signal.py, acdl_platform/contract_resolver.py, acdl_platform/outbox_writer.py should NOT match (substrate-agnostic). This proves the IR commitments hold: the adapter is the only substrate-specific code; the L1 content, contract YML, resolver, confidence signal, and outbox writer are all substrate-neutral.