From e29319a7200ae51deea21fef4f7b3bced7b7b0f0 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 21 Jul 2026 19:38:49 +0000 Subject: [PATCH] phase: 10, status: plan-as-execute, persona: lead-developer, task: T-10.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- project: acdl phase: 10 milestone: v1.1 status: plan-as-execute persona: lead-developer task: T-10.9 requirements.covered: [REQ-28] ---/ci--- Wave 4: scripts/verify_phase10.sh - the capstone gate (8 checks). (a) composition.json shape (l2 depth=1 one child l1-s3@1.0.0) (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 (full pipeline end-to-end) (f) confidence band is pass for dev (g) outbox item present (query DynamoDB by PK) (h) REQ-28: the adapter is the only substrate-specific code. acdl_platform/ Python has no aws_s3_bucket; modules-ir/ JSON has no aws_s3_bucket as a resource-type value (docs/descriptions excluded — they reference the mapping, which is allowed); adapters/terraform/ has aws_s3_bucket (it should — it is the substrate-specific code). The IR commitments hold. VERIFIED - Phase 10: L2 + contract-e2e; IR commitments hold (REQ-28). --- scripts/verify_phase10.sh | 140 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100755 scripts/verify_phase10.sh diff --git a/scripts/verify_phase10.sh b/scripts/verify_phase10.sh new file mode 100755 index 0000000..44bd5f8 --- /dev/null +++ b/scripts/verify_phase10.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +# scripts/verify_phase10.sh - Phase 10 v1-spike-l2-and-contract-e2e gate (capstone). +set -u +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" +fail() { echo "FAIL: $*" >&2; exit 1; } +ok() { echo "ok: $*"; } + +ENV_FILE="$ROOT/.env.secrets" +[ -f "$ENV_FILE" ] || fail ".env.secrets missing (run scripts/rotate_spike_key.sh first)" +git check-ignore -q "$ENV_FILE" || fail ".env.secrets is not gitignored" +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" + +# --- Check (a): composition.json exists + shape --- +python3 <<'PY' || fail "composition.json shape wrong" +import json +c = json.load(open('modules-ir/l2/l2-static-asset/composition.json')) +assert c['kind'] == 'l2' and c['depth'] == 1 +assert len(c['children']) == 1 and c['children'][0]['module'] == 'l1-s3@1.0.0' +assert c['wires']['bucket_name']['target'] == 's3' +assert c['wires']['region']['target'] == 's3' +print('composition.json: kind=l2 depth=1 one child l1-s3@1.0.0 wires passthrough') +PY +ok "composition.json: l2-static-asset references l1-s3 only (depth 1)" + +# --- Check (b): spike.yaml validates against contract schema --- +python3 <<'PY' || fail "spike.yaml does not validate against contract schema" +import yaml, json, jsonschema +contract = yaml.safe_load(open('contracts/spike.yaml')) +schema = json.load(open('schemas/contract.schema.json')) +jsonschema.validate(contract, schema) +print('spike.yaml validates against contract.schema.json') +PY +ok "contracts/spike.yaml validates against the contract schema" + +# --- Check (c): resolver py_compiles + emits IR validating against ir.schema.json --- +python3 -m py_compile acdl_platform/contract_resolver.py || fail "contract_resolver.py py_compile failed" +TMP=$(mktemp -d) +python3 acdl_platform/contract_resolver.py contracts/spike.yaml "$TMP/spike_ir.json" 2>/dev/null +( cd /tmp && python3 -c " +import json, jsonschema +inst = json.load(open('$TMP/spike_ir.json')) +schema = json.load(open('$ROOT/schemas/ir.schema.json')) +jsonschema.validate(inst, schema) +print('IR validates against ir.schema.json') +" ) || fail "resolver IR does not validate against ir.schema.json" +ok "contract_resolver.py resolves spike.yaml to an IR-schema-valid instance" + +# --- Check (d): adapter py_compiles + emits main.tf with aws_s3_bucket --- +python3 -m py_compile adapters/terraform/adapter.py || fail "adapter.py py_compile failed" +python3 adapters/terraform/adapter.py "$TMP/spike_ir.json" "$TMP/tf" 2>/dev/null +grep -q 'resource "aws_s3_bucket"' "$TMP/tf/main.tf" || fail "adapter did not emit aws_s3_bucket" +ok "adapter.py compiles L2 IR to terraform with aws_s3_bucket" +rm -rf "$TMP" + +# --- Check (e): run_spike_e2e.sh exits 0 --- +bash scripts/run_spike_e2e.sh > /tmp/verify_phase10_e2e.log 2>&1 || { + cat /tmp/verify_phase10_e2e.log >&2 + fail "run_spike_e2e.sh failed" +} +grep -q "SPIKE E2E OK" /tmp/verify_phase10_e2e.log || fail "run_spike_e2e.sh did not print SPIKE E2E OK" +ok "run_spike_e2e.sh completes the full pipeline end-to-end" + +# --- Check (f): confidence band is pass for dev --- +grep -q "band=pass" /tmp/verify_phase10_e2e.log || fail "confidence band is not pass for dev" +ok "confidence band is pass for dev" + +# --- Check (g): outbox item exists --- +python3 <<'PY' || fail "outbox item not found in DynamoDB" +import boto3 +s = boto3.Session(region_name='us-east-1') +dyn = s.client('dynamodb') +r = dyn.query(TableName='acdl-outbox', + KeyConditionExpression='contractId = :cid', + ExpressionAttributeValues={':cid': {'S': '11111111-1111-1111-1111-111111111111'}}) +assert r.get('Count', 0) >= 1, f'no outbox item for the spike contractId (Count={r.get("Count", 0)})' +print(f'outbox item present (Count={r["Count"]})') +PY +ok "evidence event is written to the DynamoDB outbox" + +# --- Check (h): REQ-28 - the adapter is the only substrate-specific code --- +# The IR commitments hold: the adapter is the only place that knows Terraform +# resource types (aws_s3_bucket). The L1/L2 interfaces, the IR schema, the +# contract, the resolver, the confidence signal, and the outbox writer are +# substrate-agnostic. Documentation (.md) + schema $comment/description strings +# may mention aws_s3_bucket *to explain the mapping* — that's not a violation; +# the check scans actual executable code (.py) + data files (.json/.yaml) +# for resource-type declarations, excluding .md files + description/comment +# string values. +LEAK=$(grep -rn --include='*.py' -E 'aws_s3_bucket|aws_[a-z]+_[a-z]+' \ + acdl_platform/ 2>/dev/null) +if [ -n "$LEAK" ]; then + echo "$LEAK" >&2 + fail "REQ-28 violated: substrate-specific terms found in acdl_platform/ Python code (the platform must be substrate-agnostic)" +fi +# modules-ir/ data files: exclude .md (docs may reference the mapping); check +# only .json for actual resource-type field declarations (not description strings). +LEAK2=$(python3 <<'PY' 2>&1 || true +import json, os, sys +leaks = [] +for root, dirs, files in os.walk('modules-ir'): + for f in files: + if not f.endswith('.json'): + continue + path = os.path.join(root, f) + with open(path) as fh: + try: + data = json.load(fh) + except Exception: + continue + # Walk the JSON; flag 'aws_s3_bucket' (Terraform type) appearing as a + # VALUE (not a key), excluding description/comment strings. + def walk(obj, path_str=''): + if isinstance(obj, dict): + for k, v in obj.items(): + if k in ('description', '$comment') and isinstance(v, str): + continue # docs/comment strings are allowed to mention it + walk(v, path_str + '/' + k) + elif isinstance(obj, str): + if obj.startswith('aws_') and obj != 'aws:s3:bucket': + leaks.append(f'{path}: {path_str} = {obj!r}') + walk(data) +if leaks: + print('\n'.join(leaks)) +PY +) +if [ -n "$LEAK2" ]; then + echo "$LEAK2" >&2 + fail "REQ-28 violated: substrate-specific resource-type values found in modules-ir/ JSON" +fi +ADAPT_HAS=$(grep -rn --include='*.py' -E 'aws_s3_bucket' adapters/terraform/ 2>/dev/null) +[ -n "$ADAPT_HAS" ] || fail "REQ-28: adapter does not contain aws_s3_bucket (it should — it's the substrate-specific code)" +ok "REQ-28: adapter is the only substrate-specific code; modules-ir/ + acdl_platform/ are substrate-agnostic (docs/comments excluded)" + +echo "VERIFIED — Phase 10: L2 + contract-e2e; IR commitments hold (REQ-28)" \ No newline at end of file