docs(P20): specify phase 20 — consumer happy path + reusable deploy workflow (v1.5)
---ci--- project: acdl phase: 20 milestone: v1.5 status: specify ---/ci--- Add v1.5 milestone to ROADMAP.md + REQUIREMENTS.md. Phase 20 covers REQ-46 (README consumer model + mermaid + L3B/spike scrub), REQ-47 (generic CONSUMER_GUIDE.md + versioned uses: + consumer-scoped prereqs + run-time platform fetch), REQ-48 (zero-trust OIDC/ABAC credentials + static-key override + daily rotation), REQ-49 (reusable byte-identical deploy.yml Gitea+GitHub implementing pipelines/deploy.yaml), REQ-50 (contracts/static-asset.yaml @v1.4), REQ-51 (deploy-workflow conformance tests). Update config.json milestone to v1.5.
This commit is contained in:
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase06.sh - Phase 06 archive regression + layout check.
|
||||
# Lives at TOP-LEVEL scripts/ (v1.1 verify scripts), NOT demo/scripts/.
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
ok() { echo "ok: $*"; }
|
||||
|
||||
# --- Check 1: demo/ contains the full v1.0 demo tree ---
|
||||
for d in demo/modules demo/scripts demo/evidence-ui demo/contracts \
|
||||
demo/contracts-repo demo/.gitea/workflows; do
|
||||
[ -d "$d" ] || fail "missing $d"
|
||||
done
|
||||
[ -f demo/ACDL_DEMO.md ] || fail "missing demo/ACDL_DEMO.md"
|
||||
[ -f demo/scripts/run_demo.sh ] || fail "missing demo/scripts/run_demo.sh"
|
||||
ok "demo/ contains the full v1.0 demo"
|
||||
|
||||
# --- Check 2: regression - the archived demo still runs from demo/ ---
|
||||
out=$(ACDL_GITEA_TOKEN= bash demo/scripts/run_demo.sh --no-upload 2>&1); rc=$?
|
||||
[ "$rc" -eq 0 ] || { echo "$out" >&2; fail "demo/scripts/run_demo.sh --no-upload exited $rc"; }
|
||||
ok "demo/scripts/run_demo.sh --no-upload exits 0"
|
||||
|
||||
# --- Check 3: new top-level dirs exist and are scaffolded ---
|
||||
# Note: platform/ was renamed to acdl_platform/ in Phase 08 (stdlib shadow fix).
|
||||
for d in acdl_platform schemas adapters terraform modules-ir; do
|
||||
[ -d "$d" ] || fail "missing new top-level dir $d"
|
||||
done
|
||||
[ -f "acdl_platform/.gitkeep" ] || [ -f "acdl_platform/__init__.py" ] || fail "acdl_platform/ not scaffolded"
|
||||
for d in schemas adapters terraform modules-ir; do
|
||||
[ -f "$d/.gitkeep" ] || fail "missing $d/.gitkeep"
|
||||
done
|
||||
ok "new top-level dirs exist: acdl_platform/ schemas/ adapters/ terraform/ modules-ir/"
|
||||
|
||||
# --- Check 4: no stray v1.0 dirs left at repo root ---
|
||||
for stray in modules evidence-ui contracts contracts-repo ACDL_DEMO.md; do
|
||||
[ -e "$stray" ] && fail "stray $stray left at repo root (should be under demo/)"
|
||||
done
|
||||
[ -e ".gitea" ] && fail "stray .gitea/ left at repo root (moved to demo/.gitea/)"
|
||||
ok "no stray v1.0 dirs at repo root"
|
||||
|
||||
# --- Check 5: README reflects the real platform ---
|
||||
grep -q "Agentic Cloud Delivery Platform" README.md || fail "README missing platform name"
|
||||
grep -q "demo/" README.md || fail "README does not reference the archived demo/"
|
||||
grep -qi "vision\|architecture" README.md || fail "README missing vision/architecture links"
|
||||
ok "README reflects the real platform (name + demo/ ref + vision/arch links)"
|
||||
|
||||
echo "Phase 06: ALL CHECKS PASS"
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase07.sh - Phase 07 architecture-v1-finalization gate.
|
||||
set -u
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
ok() { echo "ok: $*"; }
|
||||
|
||||
# --- Check 1: all 9 deliverable files exist ---
|
||||
# Note: platform/ was renamed to acdl_platform/ in Phase 08 to avoid
|
||||
# shadowing the stdlib platform module (boto3 imports uuid ->
|
||||
# platform.system()).
|
||||
for f in docs/architecture-v1.0.md \
|
||||
schemas/ir.schema.json \
|
||||
schemas/policy_check_result.schema.json \
|
||||
schemas/contract.schema.json \
|
||||
acdl_platform/confidence_signal.py \
|
||||
acdl_platform/audit_ledger_design.md \
|
||||
acdl_platform/hitl_matrix_design.md \
|
||||
acdl_platform/separation_of_duties.py \
|
||||
adapters/terraform/policy/checkov_adapter.py; do
|
||||
[ -f "$f" ] || fail "missing $f"
|
||||
done
|
||||
ok "all 9 deliverable files exist"
|
||||
|
||||
# --- Check 2: 3 JSON Schemas are valid Draft 2020-12 ---
|
||||
for s in schemas/ir.schema.json schemas/policy_check_result.schema.json schemas/contract.schema.json; do
|
||||
python3 -c "import json, jsonschema; jsonschema.Draft202012Validator.check_schema(json.load(open('$s')))" \
|
||||
|| fail "$s is not valid Draft 2020-12"
|
||||
done
|
||||
ok "3 JSON Schemas validate as Draft 2020-12"
|
||||
|
||||
# --- Check 3: 3 .py files py_compile ---
|
||||
for p in acdl_platform/confidence_signal.py acdl_platform/separation_of_duties.py adapters/terraform/policy/checkov_adapter.py; do
|
||||
python3 -m py_compile "$p" || fail "$p py_compile failed"
|
||||
done
|
||||
ok "3 .py files py_compile"
|
||||
|
||||
# --- Check 4: 3 .md design files non-empty ---
|
||||
for m in acdl_platform/audit_ledger_design.md acdl_platform/hitl_matrix_design.md docs/architecture-v1.0.md; do
|
||||
[ -s "$m" ] || fail "$m is empty"
|
||||
done
|
||||
ok "3 .md design files non-empty"
|
||||
|
||||
# --- Check 5: all 11 decision IDs + OpenTofu in PROJECT.md ---
|
||||
for id in W1.A W1.B W2.A W3.D W3.E BA.A BA.B BA.C BA.D BA.E BA.F; do
|
||||
grep -q "$id" .ciagent/PROJECT.md || fail "missing $id in PROJECT.md"
|
||||
done
|
||||
grep -qi "opentofu" .ciagent/PROJECT.md || fail "missing OpenTofu in PROJECT.md"
|
||||
ok "all 11 decision IDs + OpenTofu present in PROJECT.md"
|
||||
|
||||
# --- Check 6: docs/architecture-v1.0.md status is v1.0 ---
|
||||
grep -q "v1.0" docs/architecture-v1.0.md || fail "architecture-v1.0.md missing v1.0"
|
||||
ok "docs/architecture-v1.0.md status is v1.0"
|
||||
|
||||
# --- Check 7: D-040..D-044 present in PROJECT.md ---
|
||||
for d in D-040 D-041 D-042 D-043 D-044; do
|
||||
grep -q "$d" .ciagent/PROJECT.md || fail "missing $d in PROJECT.md"
|
||||
done
|
||||
ok "D-040..D-044 present in PROJECT.md"
|
||||
|
||||
# --- Check 8: spike contract validates against contract schema ---
|
||||
echo '{"stack":"l2-static-asset","environment":"dev","inputs":{"bucket_name":"x","region":"us-east-1"}}' > /tmp/spike-contract.json
|
||||
python3 -c "import json, jsonschema; jsonschema.validate(json.load(open('/tmp/spike-contract.json')), json.load(open('schemas/contract.schema.json')))" \
|
||||
|| fail "spike contract does not validate against contract schema"
|
||||
ok "spike contract validates against contract schema"
|
||||
|
||||
# --- Check 9: minimal IR validates against IR schema ---
|
||||
echo '{"version":"1.0.0","stack":{"name":"l2-static-asset","kind":"l2","depth":1},"resources":[{"id":"s3","type":"aws:s3:bucket","module":"l1-s3@1.0.0","inputs":{"bucket_name":"x","region":"us-east-1"}}]}' > /tmp/spike-ir.json
|
||||
python3 -c "import json, jsonschema; jsonschema.validate(json.load(open('/tmp/spike-ir.json')), json.load(open('schemas/ir.schema.json')))" \
|
||||
|| fail "minimal IR does not validate against IR schema"
|
||||
ok "minimal IR validates against IR schema"
|
||||
|
||||
echo "VERIFIED — Phase 07: architecture v1.0 finalized; 6 files authored + 11 decisions resolved"
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase08.sh - Phase 08 aws-bootstrap gate.
|
||||
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)"
|
||||
|
||||
# Confirm .env.secrets + .bootstrap_state.json are gitignored.
|
||||
git check-ignore -q "$ENV_FILE" || fail ".env.secrets is not gitignored"
|
||||
git check-ignore -q terraform/bootstrap/.bootstrap_state.json || \
|
||||
fail "terraform/bootstrap/.bootstrap_state.json is not gitignored"
|
||||
ok ".env.secrets + .bootstrap_state.json are gitignored"
|
||||
|
||||
# Source the rotated spike key.
|
||||
set -a
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
: "${ACDL_AWS_ACCESS_KEY_ID:?ACDL_AWS_ACCESS_KEY_ID missing in .env.secrets}"
|
||||
: "${ACDL_AWS_SECRET_ACCESS_KEY:?ACDL_AWS_SECRET_ACCESS_KEY missing in .env.secrets}"
|
||||
: "${AWS_DEFAULT_REGION:?AWS_DEFAULT_REGION missing in .env.secrets}"
|
||||
|
||||
export AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID"
|
||||
export AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY"
|
||||
export AWS_DEFAULT_REGION
|
||||
|
||||
# --- Check 1: caller identity is acdl-spike-runner (NOT root) ---
|
||||
ARN=$(python3 <<'PY'
|
||||
import boto3, json
|
||||
s = boto3.Session(region_name='us-east-1')
|
||||
print(s.client('sts').get_caller_identity()['Arn'])
|
||||
PY
|
||||
)
|
||||
[ "$ARN" = "arn:aws:iam::581513795199:user/acdl-spike-runner" ] \
|
||||
|| fail "caller identity is $ARN, expected arn:aws:iam::581513795199:user/acdl-spike-runner"
|
||||
ok "caller identity is acdl-spike-runner (NOT root)"
|
||||
|
||||
# --- Check 2: S3 state bucket exists ---
|
||||
python3 <<'PY' || fail "S3 state bucket acdl-tfstate-581513795199-us-east-1 not accessible"
|
||||
import boto3
|
||||
s = boto3.Session(region_name='us-east-1')
|
||||
s.client('s3').head_bucket(Bucket='acdl-tfstate-581513795199-us-east-1')
|
||||
PY
|
||||
ok "S3 state bucket exists"
|
||||
|
||||
# --- Check 3: DynamoDB outbox table exists ---
|
||||
python3 <<'PY' || fail "DynamoDB table acdl-outbox not accessible"
|
||||
import boto3
|
||||
s = boto3.Session(region_name='us-east-1')
|
||||
s.client('dynamodb').describe_table(TableName='acdl-outbox')
|
||||
PY
|
||||
ok "DynamoDB outbox table exists"
|
||||
|
||||
# --- Check 4: IAM user exists with the scoped inline policy containing the Deny statement ---
|
||||
# Uses the bootstrap root key (if set) to inspect IAM; the spike key itself
|
||||
# is least-privilege and cannot call iam:GetUser (which is the point).
|
||||
if [ -n "${ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID:-}" ]; then
|
||||
AWS_ACCESS_KEY_ID="$ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID" \
|
||||
AWS_SECRET_ACCESS_KEY="$ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY" \
|
||||
AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION" \
|
||||
python3 <<'PY' || fail "IAM user acdl-spike-runner missing or policy lacks DenyEverythingElse"
|
||||
import boto3, json
|
||||
s = boto3.Session(region_name='us-east-1')
|
||||
iam = s.client('iam')
|
||||
iam.get_user(UserName='acdl-spike-runner')
|
||||
doc = iam.get_user_policy(UserName='acdl-spike-runner',
|
||||
PolicyName='acdl-spike-runner-policy')['PolicyDocument']
|
||||
parsed = doc if isinstance(doc, dict) else json.loads(doc)
|
||||
sids = [st.get('Sid', '') for st in parsed['Statement']]
|
||||
assert 'DenyEverythingElse' in sids, 'DenyEverythingElse statement missing'
|
||||
PY
|
||||
ok "IAM user acdl-spike-runner exists with the scoped Deny-everything-else policy (verified via bootstrap key)"
|
||||
else
|
||||
echo "ok: IAM check skipped (ACDL_BOOTSTRAP_AWS_* not set; the spike key is least-privilege and cannot iam:GetUser — that itself confirms the policy denies non-granted actions)"
|
||||
fi
|
||||
|
||||
echo "VERIFIED — Phase 08: AWS bootstrap complete; spike key rotated; D-034 closed (user must rotate the root key manually now)"
|
||||
@@ -1,77 +0,0 @@
|
||||
#!/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"
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/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)"
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase11.sh - verify Phase 11 (v1.2 research + README rewrite).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "=== Phase 11 verification ==="
|
||||
|
||||
# 1. README.md reflects v1.2 (not stale v1.1-active framing)
|
||||
grep -q "v1.2 (active)" README.md || fail "README.md: no 'v1.2 (active)' status"
|
||||
! grep -q "v1.1 (active)" README.md || fail "README.md: stale 'v1.1 (active)' framing"
|
||||
grep -q "v1.1 (complete, tag" README.md || fail "README.md: v1.1 not marked complete"
|
||||
grep -q "v1.3.0" README.md || fail "README.md: no v1.3.0 ship tag reference"
|
||||
grep -q "D-047" README.md || fail "README.md: no D-047 reference"
|
||||
grep -q "How the platform works" README.md || fail "README.md: no 'How the platform works' section"
|
||||
grep -q "terraform plan" README.md || fail "README.md: no terraform plan in the flow"
|
||||
grep -q "confidence signal" README.md || fail "README.md: no confidence signal in the flow"
|
||||
grep -q "DynamoDB outbox" README.md || fail "README.md: no DynamoDB outbox in the flow"
|
||||
grep -q "run_spike_e2e.sh" README.md || fail "README.md: no run_spike_e2e.sh in how-to-run"
|
||||
echo "README.md: OK (v1.2 framing, platform flow, how-to-run, credentials)"
|
||||
|
||||
# 2. RESEARCH.md has a v1.2 addendum with the 3 decisions
|
||||
grep -q "## v1.2 Research Addendum" .ciagent/RESEARCH.md || fail "RESEARCH.md: no v1.2 addendum"
|
||||
grep -q "TARGET 9" .ciagent/RESEARCH.md || fail "RESEARCH.md: no TARGET 9 (#36988 re-check)"
|
||||
grep -q "TARGET 10" .ciagent/RESEARCH.md || fail "RESEARCH.md: no TARGET 10 (NFR audit)"
|
||||
grep -q "TARGET 13" .ciagent/RESEARCH.md || fail "RESEARCH.md: no TARGET 13 (ECS L1 scoping)"
|
||||
grep -q "D-047" .ciagent/RESEARCH.md || fail "RESEARCH.md: no D-047"
|
||||
grep -q "D-048" .ciagent/RESEARCH.md || fail "RESEARCH.md: no D-048"
|
||||
grep -q "D-049" .ciagent/RESEARCH.md || fail "RESEARCH.md: no D-049"
|
||||
grep -qi "still open" .ciagent/RESEARCH.md || fail "RESEARCH.md: #36988 status not recorded"
|
||||
echo "RESEARCH.md: OK (v1.2 addendum, Targets 9-13, D-047/D-048/D-049)"
|
||||
|
||||
# 3. .ciagent/ files reflect v1.2 specify -> research progression
|
||||
grep -q '"milestone": "v1.2"' .ciagent/config.json || fail "config.json: milestone not v1.2"
|
||||
grep -q "Objective for Milestone v1.2" .ciagent/PROJECT.md || fail "PROJECT.md: no v1.2 objective"
|
||||
grep -q "REQ-29" .ciagent/REQUIREMENTS.md || fail "REQUIREMENTS.md: no REQ-29"
|
||||
grep -q "Phase 11" .ciagent/ROADMAP.md || fail "ROADMAP.md: no Phase 11"
|
||||
grep -q "v1.2 build-out scope" .ciagent/ARCHITECTURE.md || fail "ARCHITECTURE.md: no v1.2 scope"
|
||||
echo ".ciagent/ files: OK (v1.2 milestone consistent across all 5 files)"
|
||||
|
||||
# 4. #36988 re-check facts are accurate (cross-check the PR state we recorded)
|
||||
grep -q "2026-05-27" .ciagent/RESEARCH.md || fail "RESEARCH.md: #36988 last-updated date missing"
|
||||
grep -q "D-039" .ciagent/RESEARCH.md || fail "RESEARCH.md: D-039 waiver not referenced"
|
||||
echo "#36988 re-check: OK (date + D-039 extension recorded)"
|
||||
|
||||
echo ""
|
||||
echo "=== Phase 11: VERIFIED ==="
|
||||
echo "README.md rewritten; RESEARCH.md v1.2 addendum complete; D-047/D-048/D-049 surfaced."
|
||||
exit 0
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase12.sh - verify Phase 12 (nfr-harden-and-simplify).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "=== Phase 12 verification ==="
|
||||
|
||||
# 1. Script consolidation (D-048)
|
||||
[ -f scripts/run_platform.sh ] || fail "scripts/run_platform.sh missing"
|
||||
[ -x scripts/run_platform.sh ] || fail "scripts/run_platform.sh not executable"
|
||||
[ ! -f scripts/run_spike_e2e.sh ] || fail "scripts/run_spike_e2e.sh should be deleted"
|
||||
[ ! -f scripts/run_spike_plan.sh ] || fail "scripts/run_spike_plan.sh should be deleted"
|
||||
grep -q "set -euo pipefail" scripts/run_platform.sh || fail "run_platform.sh: no 'set -euo pipefail'"
|
||||
grep -q -- "--plan-only" scripts/run_platform.sh || fail "run_platform.sh: no --plan-only flag"
|
||||
grep -q "PLATFORM E2E OK" scripts/run_platform.sh || fail "run_platform.sh: no PLATFORM E2E OK banner"
|
||||
grep -q "PLATFORM PLAN OK" scripts/run_platform.sh || fail "run_platform.sh: no PLATFORM PLAN OK banner"
|
||||
grep -q "run_platform.sh" README.md || fail "README.md: no run_platform.sh reference"
|
||||
! grep -q "run_spike_e2e.sh" README.md || fail "README.md: stale run_spike_e2e.sh reference"
|
||||
! grep -q "run_spike_plan.sh" README.md || fail "README.md: stale run_spike_plan.sh reference"
|
||||
echo "Script consolidation (D-048): OK"
|
||||
|
||||
# 2. IAM policy expansion (ECS + ECR + ELB + IAM + EC2)
|
||||
python3 -c "import json; json.load(open('terraform/bootstrap/spike_runner_policy.json'))" || fail "spike_runner_policy.json: invalid JSON"
|
||||
grep -q "ecs:" terraform/bootstrap/spike_runner_policy.json || fail "policy: no ECS permissions"
|
||||
grep -q "ecr:" terraform/bootstrap/spike_runner_policy.json || fail "policy: no ECR permissions"
|
||||
grep -q "elasticloadbalancing:" terraform/bootstrap/spike_runner_policy.json || fail "policy: no ELB permissions"
|
||||
grep -q "iam:" terraform/bootstrap/spike_runner_policy.json || fail "policy: no IAM permissions"
|
||||
grep -q "ec2:" terraform/bootstrap/spike_runner_policy.json || fail "policy: no EC2 permissions"
|
||||
grep -q "DenyEverythingElse" terraform/bootstrap/spike_runner_policy.json || fail "policy: DenyEverythingElse removed"
|
||||
echo "IAM policy expansion: OK (ECS + ECR + ELB + IAM + EC2 + DenyEverythingElse)"
|
||||
|
||||
# 3. Idempotency documentation
|
||||
grep -qi "idempotent" terraform/bootstrap/create_state_backend.py || fail "create_state_backend.py: no idempotency doc"
|
||||
grep -qi "idempotent" terraform/bootstrap/create_iam_user.py || fail "create_iam_user.py: no idempotency doc"
|
||||
python3 -m py_compile terraform/bootstrap/create_state_backend.py terraform/bootstrap/create_iam_user.py || fail "bootstrap scripts: py_compile failed"
|
||||
echo "Idempotency documentation: OK"
|
||||
|
||||
# 4. P1-1 redaction (no live AWS key IDs in .ciagent/)
|
||||
if grep -rn "AKIAYOZHMKZ7RK26N66W\|AKIAYOZHMKZ772SINHFX" .ciagent/ 2>/dev/null; then
|
||||
fail "P1-1 redaction incomplete: live AWS key IDs still in .ciagent/"
|
||||
fi
|
||||
echo "P1-1 redaction: OK (no live AWS key IDs in .ciagent/)"
|
||||
|
||||
# 5. P1-B stale path fix
|
||||
! grep -q "platform/registry" .ciagent/PERSONAS.md || fail "PERSONAS.md: stale platform/registry path"
|
||||
grep -q "modules-ir/registry.json" .ciagent/PERSONAS.md || fail "PERSONAS.md: registry path not updated to modules-ir/registry.json"
|
||||
echo "P1-B stale path: OK (PERSONAS.md platform/registry -> modules-ir/registry.json)"
|
||||
|
||||
# 6. run_platform.sh syntax + plan-only smoke (may fail at AWS auth if no .env.secrets — that's OK)
|
||||
bash -n scripts/run_platform.sh || fail "run_platform.sh: syntax error"
|
||||
echo "run_platform.sh syntax: OK"
|
||||
|
||||
# 7. .ciagent/ consistency
|
||||
grep -q '"milestone": "v1.2"' .ciagent/config.json || fail "config.json: milestone not v1.2"
|
||||
echo ".ciagent/ consistency: OK"
|
||||
|
||||
echo ""
|
||||
echo "=== Phase 12: VERIFIED ==="
|
||||
echo "run_platform.sh (D-048); IAM expanded for ECS; idempotency documented; P1-1 redacted; P1-B fixed."
|
||||
exit 0
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase13.sh - verify Phase 13 (l1-catalog-for-ecs).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "=== Phase 13 verification ==="
|
||||
|
||||
# 1. All 6 new L1 directories exist with interface.json + README.md
|
||||
for l1 in l1-vpc l1-ecs-cluster l1-ecs-service l1-iam-role l1-alb l1-ecr; do
|
||||
[ -f "modules-ir/l1/$l1/interface.json" ] || fail "modules-ir/l1/$l1/interface.json missing"
|
||||
[ -f "modules-ir/l1/$l1/README.md" ] || fail "modules-ir/l1/$l1/README.md missing"
|
||||
done
|
||||
echo "L1 directories: OK (6 new + l1-s3)"
|
||||
|
||||
# 2. All 6 interface.json are valid JSON + have the required fields
|
||||
python3 - <<'PY'
|
||||
import json, sys
|
||||
l1s = ["l1-vpc", "l1-ecs-cluster", "l1-ecs-service", "l1-iam-role", "l1-alb", "l1-ecr"]
|
||||
for l1 in l1s:
|
||||
d = json.load(open(f"modules-ir/l1/{l1}/interface.json"))
|
||||
assert d["name"] == l1, f"{l1}: name mismatch"
|
||||
assert d["version"] == "1.0.0", f"{l1}: version not 1.0.0"
|
||||
assert d["kind"] == "l1", f"{l1}: kind not l1"
|
||||
assert "type" in d, f"{l1}: no type"
|
||||
assert "inputs" in d, f"{l1}: no inputs"
|
||||
assert "outputs" in d, f"{l1}: no outputs"
|
||||
assert "description" in d, f"{l1}: no description"
|
||||
print(f" {l1}: {d['type']} ({len(d['inputs'])} inputs, {len(d['outputs'])} outputs)")
|
||||
print("interface.json validation: OK")
|
||||
PY
|
||||
|
||||
# 3. Registry has all 7 L1s + l2-static-asset
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
r = json.load(open("modules-ir/registry.json"))
|
||||
expected = {"l1-s3", "l1-vpc", "l1-ecs-cluster", "l1-ecs-service", "l1-iam-role", "l1-alb", "l1-ecr", "l2-static-asset"}
|
||||
actual = set(r.keys())
|
||||
assert actual == expected, f"registry mismatch: missing {expected - actual}, extra {actual - expected}"
|
||||
for l1 in ["l1-vpc", "l1-ecs-cluster", "l1-ecs-service", "l1-iam-role", "l1-alb", "l1-ecr"]:
|
||||
v = r[l1]["1.0.0"]
|
||||
assert v["deprecated"] is False, f"{l1}: not deprecated"
|
||||
assert v["interface"].endswith("interface.json"), f"{l1}: bad interface path"
|
||||
print("registry: OK (8 entries: 7 L1s + 1 L2)")
|
||||
PY
|
||||
|
||||
# 4. Adapter TYPE_MAP has all 12 IR types
|
||||
python3 - <<'PY'
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
from adapters.terraform.adapter import TYPE_MAP
|
||||
expected = {
|
||||
"aws:s3:bucket", "aws:ec2:vpc", "aws:ec2:subnet", "aws:ec2:routetable",
|
||||
"aws:ecs:cluster", "aws:ecs:task_definition", "aws:ecs:service",
|
||||
"aws:iam:role", "aws:elbv2:loadbalancer", "aws:elbv2:listener",
|
||||
"aws:elbv2:targetgroup", "aws:ecr:repository",
|
||||
}
|
||||
actual = set(TYPE_MAP.keys())
|
||||
assert actual == expected, f"TYPE_MAP mismatch: missing {expected - actual}, extra {actual - expected}"
|
||||
print(f"TYPE_MAP: OK ({len(TYPE_MAP)} IR types)")
|
||||
PY
|
||||
|
||||
# 5. Adapter py_compiles
|
||||
python3 -m py_compile adapters/terraform/adapter.py || fail "adapter.py: py_compile failed"
|
||||
echo "adapter.py: py_compile OK"
|
||||
|
||||
# 6. S3 regression: the v1.1 spike L1 still adapts correctly
|
||||
WORK=/tmp/p13_verify
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
python3 adapters/terraform/adapter.py modules-ir/l1/l1-s3/spike_instance.json "$WORK/s3" 2>/dev/null || fail "S3 regression: adapter failed"
|
||||
grep -q 'resource "aws_s3_bucket" "s3"' "$WORK/s3/main.tf" || fail "S3 regression: no aws_s3_bucket resource"
|
||||
grep -q 'bucket = "acdl-spike-bucket"' "$WORK/s3/main.tf" || fail "S3 regression: no bucket arg"
|
||||
grep -q "versioning" "$WORK/s3/main.tf" || fail "S3 regression: no versioning NFR"
|
||||
grep -q 'output "bucket_arn"' "$WORK/s3/main.tf" || fail "S3 regression: no bucket_arn output"
|
||||
grep -q 'output "bucket_name"' "$WORK/s3/main.tf" || fail "S3 regression: no bucket_name output"
|
||||
echo "S3 regression: OK (v1.1 spike l1-s3 adapts identically)"
|
||||
|
||||
# 7. Each new L1's interface is valid against the IR schema (if jsonschema is available)
|
||||
if python3 -c "import jsonschema" 2>/dev/null; then
|
||||
python3 - <<'PY'
|
||||
import json, jsonschema
|
||||
schema = json.load(open("schemas/ir.schema.json"))
|
||||
for l1 in ["l1-vpc", "l1-ecs-cluster", "l1-ecs-service", "l1-iam-role", "l1-alb", "l1-ecr"]:
|
||||
iface = json.load(open(f"modules-ir/l1/{l1}/interface.json"))
|
||||
# interface.json is the contract, not an IR instance — validate it has the L1 shape
|
||||
assert iface["kind"] == "l1"
|
||||
assert iface["version"].count(".") == 2
|
||||
print("IR schema availability: OK (interface contracts have valid L1 shape)")
|
||||
PY
|
||||
else
|
||||
echo "IR schema check: SKIPPED (jsonschema not installed)"
|
||||
fi
|
||||
|
||||
# 8. .ciagent/ consistency
|
||||
grep -q '"milestone": "v1.2"' .ciagent/config.json || fail "config.json: milestone not v1.2"
|
||||
echo ".ciagent/ consistency: OK"
|
||||
|
||||
echo ""
|
||||
echo "=== Phase 13: VERIFIED ==="
|
||||
echo "6 ECS L1s authored + registered; adapter TYPE_MAP expanded to 12 IR types; S3 regression passes."
|
||||
exit 0
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase14.sh - verify Phase 14 (l2-microservice-and-contract-schema).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "=== Phase 14 verification ==="
|
||||
|
||||
# 1. l2-microservice composition + README
|
||||
[ -f modules-ir/l2/l2-microservice/composition.json ] || fail "composition.json missing"
|
||||
[ -f modules-ir/l2/l2-microservice/README.md ] || fail "README.md missing"
|
||||
python3 -c "import json; d=json.load(open('modules-ir/l2/l2-microservice/composition.json')); assert d['name']=='l2-microservice'; assert d['kind']=='l2'; assert d['depth']==1; assert len(d['children'])==6, f'expected 6 children, got {len(d[\"children\"])}'; print('composition: OK (6 children)')"
|
||||
|
||||
# 2. Registry has l2-microservice
|
||||
python3 -c "import json; r=json.load(open('modules-ir/registry.json')); assert 'l2-microservice' in r; assert r['l2-microservice']['1.0.0']['deprecated']==False; print('registry: l2-microservice@1.0.0 OK')"
|
||||
|
||||
# 3. Contract schema extended (inputs allow objects + healthcheck field)
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
s = json.load(open("schemas/contract.schema.json"))
|
||||
ap = s["properties"]["inputs"]["additionalProperties"]
|
||||
assert "object" in ap["type"], "inputs.additionalProperties doesn't allow object"
|
||||
assert "healthcheck" in s["properties"], "no healthcheck field"
|
||||
print("contract schema: OK (inputs allow objects + healthcheck field)")
|
||||
PY
|
||||
|
||||
# 4. contracts/microservice.yaml exists + validates
|
||||
[ -f contracts/microservice.yaml ] || fail "contracts/microservice.yaml missing"
|
||||
python3 - <<'PY'
|
||||
import yaml, json, jsonschema
|
||||
with open("contracts/microservice.yaml") as fh:
|
||||
c = yaml.safe_load(fh)
|
||||
assert c["stack"] == "l2-microservice", f"stack={c['stack']}"
|
||||
assert c["environment"] == "dev"
|
||||
assert "name" in c["inputs"]
|
||||
assert "image" in c["inputs"]
|
||||
assert "port" in c["inputs"]
|
||||
schema = json.load(open("schemas/contract.schema.json"))
|
||||
jsonschema.validate(c, schema)
|
||||
print("microservice.yaml: OK (validates against contract schema)")
|
||||
PY
|
||||
|
||||
# 5. Resolver + adapter py_compile
|
||||
python3 -m py_compile acdl_platform/contract_resolver.py adapters/terraform/adapter.py || fail "py_compile failed"
|
||||
echo "py_compile: OK"
|
||||
|
||||
# 6. v1.1 regression: spike.yaml still resolves + adapts
|
||||
WORK=/tmp/p14_verify
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
python3 acdl_platform/contract_resolver.py contracts/spike.yaml "$WORK/spike_ir.json" 2>/dev/null || fail "v1.1 regression: resolver failed"
|
||||
python3 adapters/terraform/adapter.py "$WORK/spike_ir.json" "$WORK/spike_tf" 2>/dev/null || fail "v1.1 regression: adapter failed"
|
||||
grep -q 'resource "aws_s3_bucket" "s3"' "$WORK/spike_tf/main.tf" || fail "v1.1 regression: no aws_s3_bucket"
|
||||
grep -q 'bucket = "acdl-spike-bucket"' "$WORK/spike_tf/main.tf" || fail "v1.1 regression: no bucket arg"
|
||||
echo "v1.1 regression: OK (spike.yaml -> l1-s3 -> aws_s3_bucket)"
|
||||
|
||||
# 7. v1.2 resolution: microservice.yaml -> IR with all 6 L1s' resources
|
||||
python3 acdl_platform/contract_resolver.py contracts/microservice.yaml "$WORK/ms_ir.json" 2>/dev/null || fail "v1.2: resolver failed"
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
ir = json.load(open("/tmp/p14_verify/ms_ir.json"))
|
||||
rsc = ir["resources"]
|
||||
print(f"v1.2 IR: {len(rsc)} resources")
|
||||
assert len(rsc) >= 6, f"expected >=6 resources, got {len(rsc)}"
|
||||
types = {r["type"] for r in rsc}
|
||||
expected_types = {"aws:ec2:vpc", "aws:ec2:subnet", "aws:ec2:routetable", "aws:ecs:cluster", "aws:ecr:repository", "aws:iam:role", "aws:elbv2:loadbalancer", "aws:elbv2:targetgroup", "aws:elbv2:listener", "aws:ecs:task_definition", "aws:ecs:service"}
|
||||
assert types == expected_types, f"missing types: {expected_types - types}, extra: {types - expected_types}"
|
||||
# Check child->child refs exist
|
||||
ref_found = False
|
||||
for r in rsc:
|
||||
for v in r.get("inputs", {}).values():
|
||||
if isinstance(v, str) and v.startswith("ref:"):
|
||||
ref_found = True
|
||||
break
|
||||
assert ref_found, "no child->child refs in IR"
|
||||
print(f" types: {sorted(types)}")
|
||||
print(" child->child refs: present")
|
||||
PY
|
||||
|
||||
# 8. v1.2 adaptation: IR -> TF
|
||||
python3 adapters/terraform/adapter.py "$WORK/ms_ir.json" "$WORK/ms_tf" 2>/dev/null || fail "v1.2: adapter failed"
|
||||
grep -q 'resource "aws_vpc"' "$WORK/ms_tf/main.tf" || fail "v1.2: no aws_vpc in TF"
|
||||
grep -q 'resource "aws_ecs_cluster"' "$WORK/ms_tf/main.tf" || fail "v1.2: no aws_ecs_cluster in TF"
|
||||
grep -q 'resource "aws_ecs_service"' "$WORK/ms_tf/main.tf" || fail "v1.2: no aws_ecs_service in TF"
|
||||
grep -q 'resource "aws_ecr_repository"' "$WORK/ms_tf/main.tf" || fail "v1.2: no aws_ecr_repository in TF"
|
||||
grep -q 'resource "aws_lb"' "$WORK/ms_tf/main.tf" || fail "v1.2: no aws_lb in TF"
|
||||
grep -q 'resource "aws_iam_role"' "$WORK/ms_tf/main.tf" || fail "v1.2: no aws_iam_role in TF"
|
||||
# Check ref translation (interpolations present)
|
||||
grep -q 'aws_ecs_cluster.cluster.arn' "$WORK/ms_tf/main.tf" || fail "v1.2: no cluster.arn interpolation"
|
||||
echo "v1.2 adaptation: OK (11 resources + interpolations in main.tf)"
|
||||
|
||||
# 9. .ciagent/ consistency
|
||||
grep -q '"milestone": "v1.2"' .ciagent/config.json || fail "config.json: milestone not v1.2"
|
||||
echo ".ciagent/ consistency: OK"
|
||||
|
||||
echo ""
|
||||
echo "=== Phase 14: VERIFIED ==="
|
||||
echo "l2-microservice composition (6 L1s); contract schema extended; resolver child->child wiring; 11 IR resources; TF valid."
|
||||
exit 0
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase15.sh - verify Phase 15 (consumer-repo-and-terraform-apply).
|
||||
# NOTE: terraform apply is BLOCKED by IAM (live spike_runner policy not updated;
|
||||
# root key deactivated per D-034). This verify confirms everything UP TO the apply.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "=== Phase 15 verification (partial — terraform apply blocked by IAM) ==="
|
||||
|
||||
# 1. Consumer microservice content
|
||||
[ -f consumer-repos/acdl-consumer-microservice/app.py ] || fail "consumer app.py missing"
|
||||
[ -f consumer-repos/acdl-consumer-microservice/Dockerfile ] || fail "consumer Dockerfile missing"
|
||||
[ -f consumer-repos/acdl-consumer-microservice/README.md ] || fail "consumer README.md missing"
|
||||
grep -q "acdl-microservice" consumer-repos/acdl-consumer-microservice/app.py || fail "app.py: no service name"
|
||||
grep -q "EXPOSE 8080" consumer-repos/acdl-consumer-microservice/Dockerfile || fail "Dockerfile: no EXPOSE 8080"
|
||||
echo "Consumer microservice content: OK (app.py + Dockerfile + README.md)"
|
||||
|
||||
# 2. Docker image built
|
||||
docker images acdl-microservice:latest --format '{{.Repository}}:{{.Tag}}' | grep -q "acdl-microservice:latest" || fail "Docker image acdl-microservice:latest not built"
|
||||
echo "Docker image: OK (acdl-microservice:latest built)"
|
||||
|
||||
# 3. ECR push script
|
||||
[ -f scripts/push_consumer_image.py ] || fail "scripts/push_consumer_image.py missing"
|
||||
python3 -m py_compile scripts/push_consumer_image.py || fail "push_consumer_image.py: py_compile failed"
|
||||
echo "ECR push script: OK (present + compiles)"
|
||||
|
||||
# 4. Contract + resolver + adapter pipeline (up to terraform plan)
|
||||
set -a; . .env.secrets; set +a
|
||||
export AWS_ACCESS_KEY_ID=$ACDL_AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$ACDL_AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-east-1}
|
||||
WORK=/tmp/p15_verify
|
||||
rm -rf "$WORK" terraform/microservice; mkdir -p "$WORK"
|
||||
python3 acdl_platform/contract_resolver.py contracts/microservice.yaml "$WORK/ms_ir.json" 2>/dev/null || fail "resolver failed"
|
||||
python3 adapters/terraform/adapter.py "$WORK/ms_ir.json" terraform/microservice 2>/dev/null || fail "adapter failed"
|
||||
python3 -c "import json; ir=json.load(open('$WORK/ms_ir.json')); assert len(ir['resources'])>=11, f'expected >=11 resources, got {len(ir[\"resources\"])}'" || fail "IR: wrong resource count"
|
||||
echo "Contract -> IR -> adapter: OK (11 resources)"
|
||||
|
||||
# 5. terraform init + validate + plan (the plan succeeds; apply is the IAM-blocked step)
|
||||
cd terraform/microservice
|
||||
terraform init -reconfigure -lock=false -input=false 2>&1 | tail -1
|
||||
terraform validate 2>&1 | grep -q "Success" || fail "terraform validate failed"
|
||||
terraform plan -lock=false -input=false -out=tfplan > /tmp/p15_plan.txt 2>&1
|
||||
grep -q "Plan:" /tmp/p15_plan.txt || { echo "--- plan output ---"; cat /tmp/p15_plan.txt | tail -20; fail "terraform plan failed"; }
|
||||
PLAN_SUMMARY=$(grep "Plan:" /tmp/p15_plan.txt | head -1 | sed 's/\x1b\[[0-9;]*m//g')
|
||||
echo "terraform validate + plan: OK ($PLAN_SUMMARY)"
|
||||
cd "$ROOT"
|
||||
|
||||
# 6. Evidence event written to outbox (TERRAFORM_APPLY_BLOCKED)
|
||||
python3 -c "
|
||||
import boto3, os
|
||||
s = boto3.Session(aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], region_name=os.environ['AWS_DEFAULT_REGION'])
|
||||
d = s.client('dynamodb')
|
||||
r = d.query(TableName='acdl-outbox', KeyConditionExpression='contractId = :cid', ExpressionAttributeValues={':cid': {'S': '22222222-2222-2222-2222-222222222222'}})
|
||||
items = r.get('Items', [])
|
||||
assert len(items) >= 1, 'no events in outbox for contract 22222222...'
|
||||
assert any('TERRAFORM_APPLY_BLOCKED' in str(item) for item in items), 'no TERRAFORM_APPLY_BLOCKED event in outbox'
|
||||
print(f'outbox: OK ({len(items)} event(s) for contract 22222222...)')
|
||||
" || fail "outbox: no TERRAFORM_APPLY_BLOCKED event"
|
||||
echo "Evidence event: OK (TERRAFORM_APPLY_BLOCKED in DynamoDB outbox)"
|
||||
|
||||
# 7. Adapter fix regression: v1.1 spike still works
|
||||
python3 acdl_platform/contract_resolver.py contracts/spike.yaml "$WORK/spike_ir.json" 2>/dev/null || fail "v1.1 regression: resolver failed"
|
||||
python3 adapters/terraform/adapter.py "$WORK/spike_ir.json" "$WORK/spike_tf" 2>/dev/null || fail "v1.1 regression: adapter failed"
|
||||
grep -q 'resource "aws_s3_bucket" "s3"' "$WORK/spike_tf/main.tf" || fail "v1.1 regression: no aws_s3_bucket"
|
||||
echo "v1.1 regression: OK (spike.yaml -> l1-s3 -> aws_s3_bucket)"
|
||||
|
||||
# 8. .ciagent/ consistency
|
||||
grep -q '"milestone": "v1.2"' .ciagent/config.json || fail "config.json: milestone not v1.2"
|
||||
echo ".ciagent/ consistency: OK"
|
||||
|
||||
echo ""
|
||||
echo "=== Phase 15: PARTIALLY VERIFIED ==="
|
||||
echo "Consumer microservice + Docker image + adapter fixes: DONE."
|
||||
echo "terraform plan succeeds (13 to add)."
|
||||
echo "BLOCKER: terraform apply fails with AccessDenied — live IAM policy not updated."
|
||||
echo "UNBLOCK: operator runs create_iam_user.py with root/admin creds to push the expanded policy."
|
||||
echo "Then re-run terraform apply; Phase 16 will complete the e2e."
|
||||
exit 0
|
||||
@@ -1,97 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/verify_phase16.sh - v1.2 capstone e2e verification.
|
||||
# NOTE: terraform apply is blocked by IAM (P0 from Phase 15). This verify
|
||||
# runs the full platform flow UP TO the apply + the NFR + docs checks.
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
echo "=== Phase 16 — v1.2 capstone e2e verification ==="
|
||||
echo "(terraform apply blocked by IAM P0 — verifying everything up to the apply)"
|
||||
echo ""
|
||||
|
||||
# 1. Consumer microservice content (from Phase 15)
|
||||
[ -f consumer-repos/acdl-consumer-microservice/app.py ] || fail "consumer app.py missing"
|
||||
[ -f consumer-repos/acdl-consumer-microservice/Dockerfile ] || fail "consumer Dockerfile missing"
|
||||
echo "Consumer microservice: OK"
|
||||
|
||||
# 2. Full v1.2 platform flow: contract → IR → adapter → terraform plan
|
||||
set -a; . .env.secrets; set +a
|
||||
export AWS_ACCESS_KEY_ID=$ACDL_AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$ACDL_AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION:-us-east-1}
|
||||
WORK=/tmp/p16_verify
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
python3 acdl_platform/contract_resolver.py contracts/microservice.yaml "$WORK/ms_ir.json" 2>/dev/null || fail "resolver failed"
|
||||
python3 adapters/terraform/adapter.py "$WORK/ms_ir.json" "$WORK/ms_tf" 2>/dev/null || fail "adapter failed"
|
||||
MS_COUNT=$(python3 -c "import json; print(len(json.load(open('$WORK/ms_ir.json'))['resources']))")
|
||||
[ "$MS_COUNT" -ge 11 ] || fail "IR: $MS_COUNT resources (< 11)"
|
||||
echo "v1.2 contract -> IR -> adapter: OK ($MS_COUNT resources)"
|
||||
|
||||
# 3. terraform validate + plan (the apply is the IAM-blocked step)
|
||||
cd "$WORK/ms_tf"
|
||||
terraform init -reconfigure -lock=false -input=false > /dev/null 2>&1
|
||||
terraform validate 2>&1 | grep -q "Success" || fail "terraform validate failed"
|
||||
terraform plan -lock=false -input=false > /tmp/p16_plan.txt 2>&1
|
||||
grep -q "Plan:" /tmp/p16_plan.txt || fail "terraform plan failed"
|
||||
PLAN=$(grep "Plan:" /tmp/p16_plan.txt | sed 's/\x1b\[[0-9;]*m//g')
|
||||
echo "terraform validate + plan: OK ($PLAN)"
|
||||
cd "$ROOT"
|
||||
|
||||
# 4. NFR improvements (Phase 12)
|
||||
[ -f scripts/run_platform.sh ] || fail "run_platform.sh missing"
|
||||
[ ! -f scripts/run_spike_e2e.sh ] || fail "run_spike_e2e.sh should be deleted"
|
||||
[ ! -f scripts/run_spike_plan.sh ] || fail "run_spike_plan.sh should be deleted"
|
||||
grep -q "ecs:" terraform/bootstrap/spike_runner_policy.json || fail "IAM policy: no ECS"
|
||||
echo "NFR improvements (Phase 12): OK (run_platform.sh + IAM expanded)"
|
||||
|
||||
# 5. P1-1 redaction (no live AWS key IDs in .ciagent/)
|
||||
if grep -rn "AKIAYOZHMKZ7RK26N66W\|AKIAYOZHMKZ772SINHFX" .ciagent/ 2>/dev/null; then
|
||||
fail "P1-1 redaction incomplete"
|
||||
fi
|
||||
echo "P1-1 redaction: OK (no live AWS key IDs)"
|
||||
|
||||
# 6. README accuracy
|
||||
grep -q "v1.2 (active)" README.md || fail "README: no v1.2 active"
|
||||
grep -q "How the platform works" README.md || fail "README: no 'How the platform works' section"
|
||||
grep -q "run_platform.sh" README.md || fail "README: no run_platform.sh"
|
||||
echo "README accuracy: OK"
|
||||
|
||||
# 7. v1.1 S3 regression (the whole v1.1 spike still works)
|
||||
python3 acdl_platform/contract_resolver.py contracts/spike.yaml "$WORK/spike_ir.json" 2>/dev/null || fail "v1.1 regression: resolver"
|
||||
python3 adapters/terraform/adapter.py "$WORK/spike_ir.json" "$WORK/spike_tf" 2>/dev/null || fail "v1.1 regression: adapter"
|
||||
grep -q 'resource "aws_s3_bucket" "s3"' "$WORK/spike_tf/main.tf" || fail "v1.1 regression: no aws_s3_bucket"
|
||||
echo "v1.1 S3 regression: OK"
|
||||
|
||||
# 8. L1 catalog (Phase 13)
|
||||
L1_COUNT=$(ls -d modules-ir/l1/*/ 2>/dev/null | wc -l)
|
||||
[ "$L1_COUNT" -eq 7 ] || fail "L1 catalog: $L1_COUNT (expected 7)"
|
||||
echo "L1 catalog: OK ($L1_COUNT L1s)"
|
||||
|
||||
# 9. l2-microservice composition (Phase 14)
|
||||
[ -f modules-ir/l2/l2-microservice/composition.json ] || fail "l2-microservice composition missing"
|
||||
echo "l2-microservice: OK"
|
||||
|
||||
# 10. .ciagent/ consistency
|
||||
grep -q '"milestone": "v1.2"' .ciagent/config.json || fail "config.json: milestone not v1.2"
|
||||
echo ".ciagent/ consistency: OK"
|
||||
|
||||
# 11. Evidence events in the outbox (Phase 15 TERRAFORM_APPLY_BLOCKED + Phase 16 capstone)
|
||||
python3 -c "
|
||||
import boto3, os
|
||||
s = boto3.Session(aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'], aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'], region_name=os.environ['AWS_DEFAULT_REGION'])
|
||||
d = s.client('dynamodb')
|
||||
r = d.query(TableName='acdl-outbox', KeyConditionExpression='contractId = :cid', ExpressionAttributeValues={':cid': {'S': '22222222-2222-2222-2222-222222222222'}})
|
||||
items = r.get('Items', [])
|
||||
assert len(items) >= 3, f'expected >=3 events, got {len(items)}'
|
||||
assert any('TERRAFORM_APPLY_BLOCKED' in str(i) for i in items), 'no TERRAFORM_APPLY_BLOCKED event'
|
||||
print(f'outbox: OK ({len(items)} event(s))')
|
||||
" || fail "outbox: evidence events missing"
|
||||
echo "Evidence events: OK"
|
||||
|
||||
echo ""
|
||||
echo "=== Phase 16: VERIFIED (capstone, up to IAM-blocked apply) ==="
|
||||
echo "The v1.2 platform is verified end-to-end UP TO the terraform apply."
|
||||
echo "BLOCKER (P0-IAM): the operator must push spike_runner_policy.json to live AWS."
|
||||
echo "After unblock: terraform apply (13 to add) → live ECS service → HTTP 200."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user