--- phase: 10 name: v1-spike-l2-and-contract-e2e milestone: v1.1 milestone_type: feature status: planned requirements: [REQ-25, REQ-27, REQ-28] must_haves: - "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)" verification: typecheck: "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" test: "scripts/verify_phase10.sh" build: "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": "", "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 `. 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 `. 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.