phase: 10, status: plan-as-execute, persona: lead-developer, task: T-10.9
---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).
This commit is contained in:
Executable
+140
@@ -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)"
|
||||
Reference in New Issue
Block a user