From e054a95fd537060cf1ec48cfb1401a599c88158e Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 21 Jul 2026 19:13:31 +0000 Subject: [PATCH 1/4] phase: 9, status: plan-as-execute, persona: platform-engineer, task: T-9.1..T-9.4 ---ci--- project: acdl phase: 9 milestone: v1.1 status: plan-as-execute persona: platform-engineer task: [T-9.1, T-9.2, T-9.3, T-9.4] requirements.covered: [REQ-24] ---/ci--- Wave 1: the IR-typed L1 module l1-s3. - T-9.1: modules-ir/l1/l1-s3/interface.json - the L1 interface contract (typed inputs bucket_name/region, outputs bucket_arn/bucket_name, NFR versioning; type aws:s3:bucket). Per D-P09-2 this is a contract (typed declarations), NOT an IR-schema instance. - T-9.2: modules-ir/l1/l1-s3/spike_instance.json - a concrete stack instance with values (bucket_name=acdl-spike-bucket, region=us-east-1) that validates against schemas/ir.schema.json (verified). - T-9.3: modules-ir/registry.json - the L1 registry, one entry l1-s3@1.0.0 -> interface.json (D-P09-3, co-located with modules-ir/). - T-9.4: modules-ir/l1/l1-s3/README.md - the L1 doc + the IR->Terraform mapping table the adapter performs. Spike instance validates against ir.schema.json; interface + registry valid JSON. --- modules-ir/l1/l1-s3/README.md | 40 +++++++++++++++++++++++++ modules-ir/l1/l1-s3/interface.json | 36 ++++++++++++++++++++++ modules-ir/l1/l1-s3/spike_instance.json | 23 ++++++++++++++ modules-ir/registry.json | 9 ++++++ 4 files changed, 108 insertions(+) create mode 100644 modules-ir/l1/l1-s3/README.md create mode 100644 modules-ir/l1/l1-s3/interface.json create mode 100644 modules-ir/l1/l1-s3/spike_instance.json create mode 100644 modules-ir/registry.json diff --git a/modules-ir/l1/l1-s3/README.md b/modules-ir/l1/l1-s3/README.md new file mode 100644 index 0000000..23b3cb8 --- /dev/null +++ b/modules-ir/l1/l1-s3/README.md @@ -0,0 +1,40 @@ +# l1-s3 — S3 bucket primitive + +The first real L1 module for the v1.1 spike. Single-purpose, +substrate-agnostic (the IR type is `aws:s3:bucket`, not a Terraform +resource type). + +## Interface (the IR-typed contract) + +See `interface.json`: inputs `bucket_name` + `region` (strings), outputs +`bucket_arn` (arn) + `bucket_name` (string), NFR `versioning` (bool, +default true). + +## IR → Terraform mapping (performed by the adapter) + +The Terraform adapter (`adapters/terraform/adapter.py`) translates this +L1's IR shape to Terraform: + +| IR | Terraform | +|----|-----------| +| `resource.type = aws:s3:bucket` | `resource "aws_s3_bucket" "" { ... }` | +| `resource.inputs.bucket_name` | `bucket = ` arg | +| `resource.inputs.region` | `provider "aws" { region = }` | +| `resource.outputs.bucket_arn` | `output "bucket_arn" { value = aws_s3_bucket..arn }` | +| `resource.outputs.bucket_name` | `output "bucket_name" { value = aws_s3_bucket..id }` | + +The adapter is a thin layer (ARCHITECTURE.md §12.2); it does not own L1 +content — it only translates. + +## Spike instance + +`spike_instance.json` is a concrete stack instance (with values +`bucket_name=acdl-spike-bucket`, `region=us-east-1`) that validates +against `schemas/ir.schema.json`. The adapter consumes this instance +(not the interface contract) to emit Terraform. + +## Versioning (W3.D) + +`1.0.0` — interface MAJOR, behavior MINOR, lifecycle PATCH. MAJOR bumps +require a new registry entry (immutable publication); old entries enter +a 12-month deprecation window. \ No newline at end of file diff --git a/modules-ir/l1/l1-s3/interface.json b/modules-ir/l1/l1-s3/interface.json new file mode 100644 index 0000000..f215495 --- /dev/null +++ b/modules-ir/l1/l1-s3/interface.json @@ -0,0 +1,36 @@ +{ + "name": "l1-s3", + "version": "1.0.0", + "kind": "l1", + "type": "aws:s3:bucket", + "description": "S3 bucket primitive (substrate-agnostic IR type aws:s3:bucket; the Terraform adapter translates to aws_s3_bucket).", + "inputs": { + "bucket_name": { + "type": "string", + "description": "Globally-unique S3 bucket name.", + "required": true + }, + "region": { + "type": "string", + "description": "AWS region the bucket is created in.", + "required": true + } + }, + "outputs": { + "bucket_arn": { + "type": "arn", + "description": "The S3 bucket ARN." + }, + "bucket_name": { + "type": "string", + "description": "The bucket name (echoes the input)." + } + }, + "nfrs": { + "versioning": { + "type": "boolean", + "description": "Enable S3 versioning (default true).", + "default": true + } + } +} \ No newline at end of file diff --git a/modules-ir/l1/l1-s3/spike_instance.json b/modules-ir/l1/l1-s3/spike_instance.json new file mode 100644 index 0000000..6046920 --- /dev/null +++ b/modules-ir/l1/l1-s3/spike_instance.json @@ -0,0 +1,23 @@ +{ + "version": "1.0.0", + "stack": { + "name": "l1-s3", + "kind": "l1", + "depth": 1 + }, + "resources": [ + { + "id": "s3", + "type": "aws:s3:bucket", + "module": "l1-s3@1.0.0", + "inputs": { + "bucket_name": "acdl-spike-bucket", + "region": "us-east-1" + }, + "outputs": { + "bucket_arn": {"type": "arn", "description": "The S3 bucket ARN."}, + "bucket_name": {"type": "string", "description": "The bucket name."} + } + } + ] +} \ No newline at end of file diff --git a/modules-ir/registry.json b/modules-ir/registry.json new file mode 100644 index 0000000..9c480bd --- /dev/null +++ b/modules-ir/registry.json @@ -0,0 +1,9 @@ +{ + "l1-s3": { + "1.0.0": { + "interface": "modules-ir/l1/l1-s3/interface.json", + "published_at": "2026-07-21T19:00:00Z", + "deprecated": false + } + } +} \ No newline at end of file From 3070a68e1d0fa6085bd65c981f696eeeccd02f73 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 21 Jul 2026 19:15:10 +0000 Subject: [PATCH 2/4] phase: 9, status: plan-as-execute, persona: platform-engineer, task: T-9.5..T-9.7+T-9.9 ---ci--- project: acdl phase: 9 milestone: v1.1 status: plan-as-execute persona: platform-engineer task: [T-9.5, T-9.6, T-9.7, T-9.9] requirements.covered: [REQ-26] ---/ci--- Waves 2+3: Terraform adapter + generated spike TF + run script. - T-9.5: adapters/terraform/adapter.py - compiles an IR instance to a Terraform root module. TYPE_MAP {aws:s3:bucket -> aws_s3_bucket}. Thin layer; does not own L1 content. Emits main.tf (resource + outputs) + terraform.tf (required_version/providers + S3 backend, NO dynamodb_table per D-P09-1) + providers.tf (aws provider region from the IR). CLI: adapter.py . - T-9.6: terraform/spike/{main.tf,terraform.tf,providers.tf} - generated by running the adapter against modules-ir/l1/l1-s3/spike_instance.json. Committed so verify_phase09.sh can validate/plan without regenerating (D-P09-4); the verify script will regenerate + diff to prove reproducibility. - T-9.7: scripts/run_spike_plan.sh - loads rotated spike key from gitignored .env.secrets, exports AWS env vars, cd terraform/spike, terraform init -lock=false, terraform validate, terraform plan -lock=false -out=tfplan. Plan-only; no apply. - T-9.9: .gitignore - add terraform/spike/.terraform/ + tfplan + *.tfstate*. EXECUTE: ran scripts/run_spike_plan.sh against real AWS via the rotated spike key (D-039). terraform plan succeeded: 1 to add (the S3 bucket), outputs computed. One non-blocking deprecation warning (aws_s3_bucket versioning block -> use aws_s3_bucket_versioning in v1.2). No long-lived credential in the workflow (key loaded from .env.secrets at runtime). --- .gitignore | 5 +- adapters/terraform/adapter.py | 131 ++++++++++++++++++++++++++++++++++ scripts/run_spike_plan.sh | 29 ++++++++ terraform/spike/main.tf | 14 ++++ terraform/spike/providers.tf | 3 + terraform/spike/terraform.tf | 14 ++++ 6 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 adapters/terraform/adapter.py create mode 100755 scripts/run_spike_plan.sh create mode 100644 terraform/spike/main.tf create mode 100644 terraform/spike/providers.tf create mode 100644 terraform/spike/terraform.tf diff --git a/.gitignore b/.gitignore index 23b879b..f053016 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,7 @@ audit.json .DS_Store runner-data/ .env.secrets -terraform/bootstrap/.bootstrap_state.json \ No newline at end of file +terraform/bootstrap/.bootstrap_state.json +terraform/spike/.terraform/ +terraform/spike/tfplan +terraform/spike/*.tfstate* \ No newline at end of file diff --git a/adapters/terraform/adapter.py b/adapters/terraform/adapter.py new file mode 100644 index 0000000..bf0a591 --- /dev/null +++ b/adapters/terraform/adapter.py @@ -0,0 +1,131 @@ +"""ACDL Terraform adapter — compile a Target Stack IR instance to Terraform. + +ARCHITECTURE.md §12.2: the adapter translates the IR-typed L1 interface +to a Terraform variable/output block, the L2 thin-composition tree to a +root module that calls the L1 modules, the IR-typed relationships to +Terraform module references, and emits a Terraform plan from the IR. + +The adapter is a THIN LAYER; it does not own L1/L2 content — it only +translates. Substrate-agnostic in, Terraform out. + +Spike scope (Phase 09): handles one L1 (l1-s3, IR type aws:s3:bucket). +L2 thin-composition + relationships land in Phase 10. + +CLI: adapter.py +""" + +import json +import os +import sys + + +# IR type -> Terraform resource type. The only substrate-specific table. +# As more L1s land, this grows; the L1 content + IR do not change. +TYPE_MAP = { + "aws:s3:bucket": "aws_s3_bucket", +} + + +def _tf_block(block_type, name, body_lines, indent=2): + head = f'{block_type} "{name}" {{' + body = "\n".join(f" {l}" for l in body_lines) + return f"{head}\n{body}\n}}\n" + + +def _emit_resource(resource): + rtype = resource["type"] + rid = resource["id"] + tf_type = TYPE_MAP.get(rtype) + if not tf_type: + raise ValueError(f"unknown IR type {rtype!r} (adapter spike handles aws:s3:bucket only)") + body = [] + inputs = resource.get("inputs", {}) + # S3 bucket: bucket_name -> bucket arg; region -> provider (handled separately) + if "bucket_name" in inputs: + body.append(f'bucket = "{inputs["bucket_name"]}"') + # NFR: versioning (default true) + nfrs = resource.get("nfrs", {}) + versioning = nfrs.get("versioning", True) if isinstance(nfrs, dict) else True + body.append("versioning {") + body.append(f' enabled = {"true" if versioning else "false"}') + body.append("}") + return _tf_block("resource", f'aws_s3_bucket.{rid}', body) if False else _resource_block(rid, tf_type, body) + + +def _resource_block(rid, tf_type, body): + """Emit a top-level resource block.""" + head = f'resource "{tf_type}" "{rid}" {{' + body_str = "\n".join(f" {l}" for l in body) + return f"{head}\n{body_str}\n}}\n" + + +def _emit_output(output_name, value_expr): + return f'output "{output_name}" {{\n value = {value_expr}\n}}\n' + + +def adapt(ir_instance, out_dir): + """Emit main.tf + terraform.tf + providers.tf to out_dir for the IR instance.""" + os.makedirs(out_dir, exist_ok=True) + stack = ir_instance["stack"] + resources = ir_instance["resources"] + + # --- providers.tf: aws provider, region from the first resource's inputs.region --- + region = "us-east-1" + for r in resources: + if "region" in r.get("inputs", {}): + region = r["inputs"]["region"] + break + providers_tf = ( + f'provider "aws" {{\n' + f' region = "{region}"\n' + f'}}\n' + ) + + # --- terraform.tf: required_version + required_providers + S3 backend (no DynamoDB lock per D-P09-1) --- + terraform_tf = ( + 'terraform {\n' + ' required_version = ">= 1.9, < 1.10"\n' + ' required_providers {\n' + ' aws = {\n' + ' source = "hashicorp/aws"\n' + ' version = "~> 5.0"\n' + ' }\n' + ' }\n' + ' backend "s3" {\n' + ' bucket = "acdl-tfstate-581513795199-us-east-1"\n' + ' key = "spike/l1-s3/terraform.tfstate"\n' + ' region = "us-east-1"\n' + ' }\n' + '}\n' + ) + + # --- main.tf: resources + outputs --- + main_tf_parts = [] + for r in resources: + main_tf_parts.append(_emit_resource(r)) + rid = r["id"] + outputs = r.get("outputs", {}) + for out_name in outputs: + if out_name == "bucket_arn": + main_tf_parts.append(_emit_output("bucket_arn", f"aws_s3_bucket.{rid}.arn")) + elif out_name == "bucket_name": + main_tf_parts.append(_emit_output("bucket_name", f"aws_s3_bucket.{rid}.id")) + main_tf = "\n".join(main_tf_parts) + + with open(os.path.join(out_dir, "main.tf"), "w") as fh: + fh.write(main_tf) + with open(os.path.join(out_dir, "terraform.tf"), "w") as fh: + fh.write(terraform_tf) + with open(os.path.join(out_dir, "providers.tf"), "w") as fh: + fh.write(providers_tf) + return out_dir + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("usage: adapter.py ", file=sys.stderr) + sys.exit(2) + with open(sys.argv[1], "r") as fh: + ir = json.load(fh) + adapt(ir, sys.argv[2]) + print(f"adapter: emitted terraform to {sys.argv[2]}", file=sys.stderr) \ No newline at end of file diff --git a/scripts/run_spike_plan.sh b/scripts/run_spike_plan.sh new file mode 100755 index 0000000..7fc38a5 --- /dev/null +++ b/scripts/run_spike_plan.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# scripts/run_spike_plan.sh - run the v1.1 spike's real terraform plan against AWS. +# +# Uses the rotated spike key (D-039) from gitignored .env.secrets. +# Plan-only (no apply); -lock=false per D-P09-1 (the spike's DynamoDB +# outbox table PK is contractId, not Terraform's expected LockID; plan +# does not write state so locking is unnecessary; v1.2 creates a proper +# LockID-keyed acdl-tflock table). +set -u +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +ENV_FILE="$ROOT/.env.secrets" +[ -f "$ENV_FILE" ] || { echo "FAIL: .env.secrets missing (run scripts/rotate_spike_key.sh)" >&2; exit 1; } +set -a +. "$ENV_FILE" +set +a +export AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID" +export AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY" +export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION" + +cd terraform/spike +echo "=== terraform init -lock=false -input=false ===" +terraform init -lock=false -input=false +echo "=== terraform validate ===" +terraform validate +echo "=== terraform plan -lock=false -input=false -out=tfplan ===" +terraform plan -lock=false -input=false -out=tfplan +echo "spike plan OK" \ No newline at end of file diff --git a/terraform/spike/main.tf b/terraform/spike/main.tf new file mode 100644 index 0000000..d750400 --- /dev/null +++ b/terraform/spike/main.tf @@ -0,0 +1,14 @@ +resource "aws_s3_bucket" "s3" { + bucket = "acdl-spike-bucket" + versioning { + enabled = true + } +} + +output "bucket_arn" { + value = aws_s3_bucket.s3.arn +} + +output "bucket_name" { + value = aws_s3_bucket.s3.id +} diff --git a/terraform/spike/providers.tf b/terraform/spike/providers.tf new file mode 100644 index 0000000..c125940 --- /dev/null +++ b/terraform/spike/providers.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = "us-east-1" +} diff --git a/terraform/spike/terraform.tf b/terraform/spike/terraform.tf new file mode 100644 index 0000000..c1d14ab --- /dev/null +++ b/terraform/spike/terraform.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.9, < 1.10" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + backend "s3" { + bucket = "acdl-tfstate-581513795199-us-east-1" + key = "spike/l1-s3/terraform.tfstate" + region = "us-east-1" + } +} From 3936bf460a64072b58fad294d8053ee7253759d4 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 21 Jul 2026 19:16:33 +0000 Subject: [PATCH 3/4] phase: 9, status: plan-as-execute, persona: lead-developer, task: T-9.8 ---ci--- project: acdl phase: 9 milestone: v1.1 status: plan-as-execute persona: lead-developer task: T-9.8 ---/ci--- Wave 4: scripts/verify_phase09.sh. 8 checks: all 9 deliverable files exist; spike_instance.json validates against ir.schema.json; registry has l1-s3@1.0.0; adapter py_compiles + emits aws_s3_bucket + bucket_arn output; terraform/spike/*.tf match a fresh adapter run (D-P09-4 reproducibility, diff); no AKIA in committed files (excluding .terraform/ provider binaries which contain AKIA bytes - gitignored anyway); secrets + TF working artifacts gitignored; real terraform plan against AWS succeeds (rotated spike key, plan-only, -lock=false per D-P09-1). VERIFIED - Phase 09: IR + l1-s3 + Terraform adapter; real terraform plan succeeds. --- scripts/verify_phase09.sh | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100755 scripts/verify_phase09.sh diff --git a/scripts/verify_phase09.sh b/scripts/verify_phase09.sh new file mode 100755 index 0000000..27b61bb --- /dev/null +++ b/scripts/verify_phase09.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# scripts/verify_phase09.sh - Phase 09 v1-spike-ir-and-l1-and-adapter gate. +set -u +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +fail() { echo "FAIL: $*" >&2; exit 1; } +ok() { echo "ok: $*"; } + +# --- Check 1: L1 module files exist --- +for f in modules-ir/l1/l1-s3/interface.json \ + modules-ir/l1/l1-s3/spike_instance.json \ + modules-ir/l1/l1-s3/README.md \ + modules-ir/registry.json \ + adapters/terraform/adapter.py \ + terraform/spike/main.tf \ + terraform/spike/terraform.tf \ + terraform/spike/providers.tf \ + scripts/run_spike_plan.sh; do + [ -f "$f" ] || fail "missing $f" +done +ok "all 9 deliverable files exist" + +# --- Check 2: spike_instance.json validates against ir.schema.json --- +( cd /tmp && python3 -c " +import json, jsonschema +inst = json.load(open('$ROOT/modules-ir/l1/l1-s3/spike_instance.json')) +schema = json.load(open('$ROOT/schemas/ir.schema.json')) +jsonschema.validate(inst, schema) +" ) || fail "spike_instance.json does not validate against ir.schema.json" +ok "spike_instance.json validates against ir.schema.json" + +# --- Check 3: registry has the l1-s3@1.0.0 entry --- +python3 -c " +import json +r = json.load(open('modules-ir/registry.json')) +assert 'l1-s3' in r and '1.0.0' in r['l1-s3'], 'l1-s3@1.0.0 missing' +print('l1-s3@1.0.0 present') +" || fail "registry missing l1-s3@1.0.0" +ok "registry has l1-s3@1.0.0" + +# --- Check 4: adapter py_compiles + generates terraform containing aws_s3_bucket --- +python3 -m py_compile adapters/terraform/adapter.py || fail "adapter.py py_compile failed" +TMP=$(mktemp -d) +python3 adapters/terraform/adapter.py modules-ir/l1/l1-s3/spike_instance.json "$TMP" 2>/dev/null +grep -q 'resource "aws_s3_bucket"' "$TMP/main.tf" || fail "adapter did not emit aws_s3_bucket resource" +grep -q 'output "bucket_arn"' "$TMP/main.tf" || fail "adapter did not emit bucket_arn output" +ok "adapter.py py_compiles + emits aws_s3_bucket + bucket_arn output" + +# --- Check 5: generated terraform/spike/*.tf match a fresh adapter run (D-P09-4 reproducibility) --- +diff "$TMP/main.tf" terraform/spike/main.tf || fail "terraform/spike/main.tf is stale (differs from a fresh adapter run)" +diff "$TMP/terraform.tf" terraform/spike/terraform.tf || fail "terraform/spike/terraform.tf is stale" +diff "$TMP/providers.tf" terraform/spike/providers.tf || fail "terraform/spike/providers.tf is stale" +ok "terraform/spike/*.tf match a fresh adapter run (reproducible)" +rm -rf "$TMP" + +# --- Check 6: no long-lived credential (AKIA) in committed files --- +# Skip .terraform/ (provider binaries contain AKIA bytes; gitignored anyway). +if grep -rn --exclude-dir=.terraform "AKIA" terraform/spike/ adapters/ modules-ir/ 2>/dev/null; then + fail "AKIA key id found in committed files (terraform/spike/ adapters/ modules-ir/)" +fi +ok "no AKIA in committed files (excluding .terraform/ provider binaries)" + +# --- Check 7: .env.secrets + terraform working artifacts are gitignored --- +git check-ignore -q .env.secrets || fail ".env.secrets not gitignored" +git check-ignore -q terraform/spike/.terraform/ || fail "terraform/spike/.terraform/ not gitignored" +git check-ignore -q terraform/spike/tfplan || fail "terraform/spike/tfplan not gitignored" +ok "secrets + TF working artifacts gitignored" + +# --- Check 8: real terraform plan against AWS succeeds (uses rotated spike key) --- +bash scripts/run_spike_plan.sh > /tmp/verify_phase09_plan.log 2>&1 || { + cat /tmp/verify_phase09_plan.log >&2 + fail "scripts/run_spike_plan.sh failed (see /tmp/verify_phase09_plan.log)" +} +grep -q "spike plan OK" /tmp/verify_phase09_plan.log || fail "run_spike_plan.sh did not print 'spike plan OK'" +ok "real terraform plan against AWS succeeded (rotated spike key, plan-only, -lock=false)" + +echo "VERIFIED — Phase 09: IR + l1-s3 + Terraform adapter; real terraform plan succeeds" \ No newline at end of file From 4c9314710b55bf78bd032b7c20e9eee14a8d4602 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 21 Jul 2026 19:17:30 +0000 Subject: [PATCH 4/4] docs(P09): post-ship traceability + roadmap update (v1.1.4) ---ci--- project: acdl phase: 9 milestone: v1.1 status: shipped requirements: complete: [REQ-24, REQ-26] release: tag: v1.1.4 ---/ci--- ROADMAP Phase 09 -> complete (v1.1.4). REQUIREMENTS REQ-24/26 -> complete. The IR-typed L1 module l1-s3 + the Terraform adapter compile to a real terraform plan against AWS (plan-only, -lock=false per D-P09-1, rotated spike key per D-039). --- .ciagent/REQUIREMENTS.md | 4 ++-- .ciagent/ROADMAP.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ciagent/REQUIREMENTS.md b/.ciagent/REQUIREMENTS.md index 6c9d804..7db655f 100644 --- a/.ciagent/REQUIREMENTS.md +++ b/.ciagent/REQUIREMENTS.md @@ -122,8 +122,8 @@ | REQ-21 | 07 | complete (v1.1.2) | | REQ-22 | 07 | complete (v1.1.2) | | REQ-23 | 08 | complete (v1.1.3) | -| REQ-24 | 09 | pending | +| REQ-24 | 09 | complete (v1.1.4) | | REQ-25 | 10 | pending | -| REQ-26 | 09 | pending | +| REQ-26 | 09 | complete (v1.1.4) | | REQ-27 | 10 | pending | | REQ-28 | 10 | pending | \ No newline at end of file diff --git a/.ciagent/ROADMAP.md b/.ciagent/ROADMAP.md index c19b596..370ee72 100644 --- a/.ciagent/ROADMAP.md +++ b/.ciagent/ROADMAP.md @@ -112,7 +112,7 @@ milestone COMPLETE: `v1.2.0` (feature milestone, next minor per ship.md). ### Phase 09 — v1-spike-ir-and-l1-and-adapter - **Description:** Implement the Target Stack IR, one real L1 `l1-s3` (IR-typed interface, registered), and the Terraform adapter that compiles the IR → Terraform `variable`/`output` + root module and emits a real `terraform plan` against AWS (via the rotated-key secret per D-039; OIDC is v1.2). State in S3 + DynamoDB. -- **Status:** pending +- **Status:** complete (v1.1.4) - **Depends on:** [08] - **Requirements:** REQ-24, REQ-26 - **Success Criteria:**