#!/usr/bin/env bash # scripts/run_platform.sh - the ACDL platform pipeline. # # Modes: # --check-only (offline, no AWS/Checkov/DynamoDB — for CI) # load IR -> adapter -> validate output structure -> exit 0 # --plan-only (requires AWS creds, no Checkov/outbox) # load IR -> adapter -> terraform init/validate/plan -> exit 0 # (default) (requires AWS creds + Checkov + DynamoDB) # load IR -> adapter -> terraform plan -> Checkov -> confidence -> outbox # # NOTE: contract resolution (contract_resolver.py) was removed when the # thin-composition layer was taken out. The pipeline now starts from a # pre-existing IR instance (modules-ir/l1/l1-s3/spike_instance.json). A # new contract-resolution mechanism will be designed in a later phase. # # Uses the rotated spike key (D-039/D-047) from gitignored .env.secrets. # Plan-only (no apply); -lock=false per D-P09-1. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" CHECK_ONLY=0 PLAN_ONLY=0 for arg in "$@"; do case "$arg" in --check-only) CHECK_ONLY=1 ;; --plan-only) PLAN_ONLY=1 ;; *) echo "FAIL: unknown argument: $arg" >&2; exit 1 ;; esac done fail() { echo "FAIL: $*" >&2; exit 1; } CONTRACT_ID="11111111-1111-1111-1111-111111111111" # spike fixed UUID WORK="/tmp/spike_e2e" rm -rf "$WORK"; mkdir -p "$WORK" echo "=== Step 1+2: load pre-existing IR instance (contract resolution deferred) ===" IR_INSTANCE="modules-ir/l1/l1-s3/spike_instance.json" [ -f "$IR_INSTANCE" ] || fail "IR instance $IR_INSTANCE missing (contract resolution is deferred; load a pre-existing IR)" python3 -c "import json; d=json.load(open('$IR_INSTANCE')); print(f\"IR: {d['stack']['name']} {d['stack']['kind']} {len(d['resources'])} resource(s)\")" cp "$IR_INSTANCE" "$WORK/spike_ir.json" 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}" if [ "$CHECK_ONLY" = "1" ]; then echo "" echo "=== Step 3b: validate adapter output structure (offline) ===" python3 -c " import json, os d = json.load(open('$WORK/spike_ir.json')) assert d['stack']['name'] == 'l1-s3' assert len(d['resources']) == 1 tf_dir = 'terraform/spike' for f in ('main.tf', 'terraform.tf', 'providers.tf'): assert os.path.isfile(os.path.join(tf_dir, f)), f'{f} missing' main = open(os.path.join(tf_dir, 'main.tf')).read() assert 'aws_s3_bucket' in main assert 'acdl-spike-bucket' in main assert 'versioning' in main tf = open(os.path.join(tf_dir, 'terraform.tf')).read() assert 'backend' in tf assert 'required_version' in tf prov = open(os.path.join(tf_dir, 'providers.tf')).read() assert 'provider \"aws\"' in prov print('adapter output: OK') " echo "" echo "=== PLATFORM CHECK OK ===" echo "IR instance -> adapter -> structure validated (offline, no AWS)" exit 0 fi echo "=== Loading AWS credentials (not needed for --check-only) ===" 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" 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" if [ "$PLAN_ONLY" = "1" ]; then echo "" echo "=== PLATFORM PLAN OK ===" exit 0 fi 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 "=== PLATFORM E2E OK ===" echo "IR instance -> terraform plan -> Checkov -> confidence ($BAND) -> outbox" exit 0