diff --git a/scripts/run_spike_e2e.sh b/scripts/run_spike_e2e.sh new file mode 100755 index 0000000..5e07253 --- /dev/null +++ b/scripts/run_spike_e2e.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# scripts/run_spike_e2e.sh - the v1.1 spike end-to-end pipeline (Phase 10 capstone). +# +# Orchestrates: contract validation -> IR resolution -> terraform plan +# (real AWS) -> Checkov -> PolicyCheckResult -> confidence signal -> +# evidence event to DynamoDB outbox. +# +# Uses the rotated spike key (D-039) from gitignored .env.secrets. +# Plan-only (no apply); -lock=false per D-P09-1. +set -u +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +fail() { echo "FAIL: $*" >&2; exit 1; } + +ENV_FILE="$ROOT/.env.secrets" +[ -f "$ENV_FILE" ] || fail ".env.secrets missing (run scripts/rotate_spike_key.sh)" +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" + +CONTRACT="contracts/spike.yaml" +CONTRACT_ID="11111111-1111-1111-1111-111111111111" # spike fixed UUID +WORK="/tmp/spike_e2e" +rm -rf "$WORK"; mkdir -p "$WORK" + +echo "=== Step 1+2: resolve contract -> IR (validates contract schema + IR schema) ===" +python3 acdl_platform/contract_resolver.py "$CONTRACT" "$WORK/spike_ir.json" || fail "contract resolution failed" +python3 -c "import json; d=json.load(open('$WORK/spike_ir.json')); print(f\"IR: {d['stack']['name']} {d['stack']['kind']} {len(d['resources'])} resource(s)\")" + +echo "=== Step 3: adapter compiles IR -> terraform/spike/*.tf (regenerate) ===" +python3 adapters/terraform/adapter.py "$WORK/spike_ir.json" terraform/spike || fail "adapter failed" +echo "adapter: emitted terraform/spike/{main.tf,terraform.tf,providers.tf}" + +echo "=== Step 4: terraform init + validate + plan -lock=false (real AWS) ===" +cd terraform/spike +terraform init -reconfigure -lock=false -input=false >> "$WORK/tf.log" 2>&1 || fail "terraform init failed" +terraform validate >> "$WORK/tf.log" 2>&1 || fail "terraform validate failed" +terraform plan -lock=false -input=false -out=tfplan >> "$WORK/tf.log" 2>&1 || fail "terraform plan failed" +echo "terraform plan OK (1 to add, 0 to change, 0 to destroy expected)" +cd "$ROOT" + +echo "=== Step 5: run Checkov on terraform/spike/main.tf ===" +checkov -f terraform/spike/main.tf --framework terraform -o json --soft-fail > "$WORK/checkov.json" 2> "$WORK/checkov.err" +[ -s "$WORK/checkov.json" ] || fail "checkov produced no output" +echo "checkov: $(python3 -c "import json; d=json.load(open('$WORK/checkov.json')); print(len(d.get('results',{}).get('failed_checks',[])), 'failed,', len(d.get('results',{}).get('passed_checks',[])), 'passed')")" + +echo "=== Step 6: Checkov adapter -> PolicyCheckResult list ===" +python3 adapters/terraform/policy/checkov_adapter.py "$WORK/checkov.json" "$CONTRACT_ID" > "$WORK/pcr.json" || fail "checkov adapter failed" +PCR_COUNT=$(python3 -c "import json; print(len(json.load(open('$WORK/pcr.json'))))") +echo "PolicyCheckResult: $PCR_COUNT record(s)" + +echo "=== Step 7: confidence signal compute ===" +python3 < "$WORK/signal.json" || fail "confidence signal failed" +import json +import acdl_platform.confidence_signal as c +pcr = json.load(open("$WORK/pcr.json")) +inputs = { + "policy": pcr, + "validation": {"schema": True, "ir_resolved": True, "tf_validated": True, "tf_planned": True}, + "freshness": {"age_days": 0, "max_age_days": 7}, + "source": {"submitter": "spike", "commit_sha": "spike-sha", "signed": False}, + "history": {"prior_rollbacks": 0, "prior_policy_fails": 0}, + "nfrs": {"conformance": None}, +} +sig = c.compute("$CONTRACT_ID", "dev", inputs) +print(json.dumps({"score": sig.score, "band": sig.band, "perInput": sig.perInput, "reasonCodes": sig.reasonCodes}, indent=2)) +PY +BAND=$(python3 -c "import json; print(json.load(open('$WORK/signal.json'))['band'])") +SCORE=$(python3 -c "import json; print(round(json.load(open('$WORK/signal.json'))['score'],3))") +echo "confidence: score=$SCORE band=$BAND" +[ "$BAND" = "pass" ] || fail "confidence band is $BAND, expected pass for dev" + +echo "=== Step 8: write evidence event to DynamoDB outbox ===" +python3 < "$WORK/event.json" || fail "event build failed" +import json, datetime +sig = json.load(open("$WORK/signal.json")) +event = { + "contractId": "$CONTRACT_ID", + "eventType": "CONFIDENCE_COMPUTED", + "ts": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "environment": "dev", + "stack": "l2-static-asset", + "score": sig["score"], + "band": sig["band"], + "prev_event_hash": "GENESIS", +} +print(json.dumps(event, indent=2)) +PY +python3 acdl_platform/outbox_writer.py "$WORK/event.json" > "$WORK/outbox_item.json" || fail "outbox write failed" +echo "outbox: $(python3 -c "import json; d=json.load(open('$WORK/outbox_item.json')); print('contractId=', d['contractId'], 'hash=', d['hash'][:16]+'...')")" + +echo "" +echo "=== SPIKE E2E OK ===" +echo "contract=$CONTRACT -> IR -> terraform plan -> Checkov -> confidence ($BAND) -> outbox" +exit 0 \ No newline at end of file