#!/usr/bin/env bash # scripts/run_demo.sh — Phase 05 dry-run simulation of the 4 demo acts (T-5.2). # # Simulates the full 4-act demo locally (no act_runner) by calling the core # scripts in sequence and writing hash-chained evidence events to audit.json, # then optionally uploads audit.json + evidence-ui/index.html to acdl-evidence # main via finalize_evidence.py (D-031, D-033). # # Usage: scripts/run_demo.sh [--no-upload] # --no-upload skip the Gitea API calls (useful for testing without a token) set -uo pipefail # ----------------------------------------------------------------------------- # Parse args # ----------------------------------------------------------------------------- UPLOAD=1 for arg in "$@"; do case "$arg" in --no-upload) UPLOAD=0 ;; *) echo "run_demo.sh: unknown argument: $arg" >&2 echo "usage: scripts/run_demo.sh [--no-upload]" >&2 exit 2 ;; esac done # ----------------------------------------------------------------------------- # Paths # ----------------------------------------------------------------------------- # Repo root = location of this script's parent dir. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" WORKDIR="/tmp/acdl_demo_run" AUDIT="$WORKDIR/audit.json" CONTRACTS="$WORKDIR/contracts" # Track failures so we can return non-zero at the end (we do NOT use set -e # because policy_checker intentionally exits 1 on Act 4). FAIL=0 # ----------------------------------------------------------------------------- # Helpers # ----------------------------------------------------------------------------- # Write one evidence event. Args: ev() { local stage="$1" local text="$2" if ! python3 "$SCRIPT_DIR/evidence_writer.py" --stage "$stage" --event "$text" --audit "$AUDIT"; then echo "run_demo.sh: evidence_writer failed for stage=$stage text=$text" >&2 FAIL=1 fi } # Run a contract through the Act 2/3 pipeline (policy -> confidence -> executor). # Assumes the contract already passed policy (caller verifies). Writes the # standard 4-event sequence. Args: run_passing_pipeline() { local dev_event="$1" ev dev "$dev_event" ev qa "qa approved" ev prod "prod approved" ev finalize "finalize: audit.json committed to acdl-evidence" } # ----------------------------------------------------------------------------- # Setup working directory # ----------------------------------------------------------------------------- mkdir -p "$CONTRACTS" rm -f "$AUDIT" # ----------------------------------------------------------------------------- # Initialize audit (genesis) # ----------------------------------------------------------------------------- echo "== run_demo.sh: initializing audit at $AUDIT ==" ev genesis "audit log initialized" # ----------------------------------------------------------------------------- # Act 1 — Friction # ----------------------------------------------------------------------------- echo "== Act 1 — Friction ==" ev dev "Act 1 Friction: manual 2-week deployment (legacy process)" # ----------------------------------------------------------------------------- # Act 2 — Developer Self-Service # ----------------------------------------------------------------------------- echo "== Act 2 — Developer Self-Service ==" cat > "$CONTRACTS/act2.yaml" <<'YAML' stack: l2-commodity-price-feed inputs: environment: dev owner: platform-team public-ingress: false YAML ACT2_POLICY="$(python3 "$SCRIPT_DIR/policy_checker.py" "$CONTRACTS/act2.yaml")" ACT2_POLICY_RC=$? echo " policy_checker: $ACT2_POLICY (rc=$ACT2_POLICY_RC)" if [ "$ACT2_POLICY" != "POLICY_PASS" ]; then echo "run_demo.sh: Act 2 expected POLICY_PASS, got '$ACT2_POLICY'" >&2 FAIL=1 fi ACT2_CONF="$(python3 "$SCRIPT_DIR/confidence_signal.py" "$CONTRACTS/act2.yaml")" echo " confidence_signal: $ACT2_CONF" # Expected: {"score": 0.90, "reason": "POLICY_PASS"} # mock_executor.sh resolves modules/l2//manifest.yaml relative to its # cwd, so it must run from the repo root. It writes state.json to its cwd; # clean it up from the repo root afterward so no stray file is left there. ( cd "$REPO_ROOT" && bash "$SCRIPT_DIR/mock_executor.sh" "$CONTRACTS/act2.yaml" ) MOCK_RC=$? rm -f "$REPO_ROOT/state.json" if [ "$MOCK_RC" -ne 0 ]; then echo "run_demo.sh: Act 2 mock_executor failed (rc=$MOCK_RC)" >&2 FAIL=1 fi run_passing_pipeline "dev applied: l2-commodity-price-feed" # ----------------------------------------------------------------------------- # Act 3 — Citizen Developer # ----------------------------------------------------------------------------- echo "== Act 3 — Citizen Developer ==" ISSUE_BODY="We need to ingest natural gas prices from Platts and report on compliance for the trading desk." if ! python3 "$SCRIPT_DIR/l3b_agent_stub.py" "$ISSUE_BODY" -o "$CONTRACTS/act3.yaml"; then echo "run_demo.sh: l3b_agent_stub failed for Act 3" >&2 FAIL=1 fi # Confirm the generated contract's stack (D-008: gas/price matches first). ACT3_STACK="$(python3 -c "import yaml,sys; print(yaml.safe_load(open('$CONTRACTS/act3.yaml'))['stack'])" 2>/dev/null || echo "")" echo " l3b generated stack: $ACT3_STACK" if [ "$ACT3_STACK" != "l2-commodity-price-feed" ]; then echo "run_demo.sh: WARNING Act 3 expected stack l2-commodity-price-feed, got '$ACT3_STACK'" >&2 # Continue anyway per the task spec. fi ACT3_POLICY="$(python3 "$SCRIPT_DIR/policy_checker.py" "$CONTRACTS/act3.yaml")" ACT3_POLICY_RC=$? echo " policy_checker: $ACT3_POLICY (rc=$ACT3_POLICY_RC)" if [ "$ACT3_POLICY" != "POLICY_PASS" ]; then echo "run_demo.sh: Act 3 expected POLICY_PASS, got '$ACT3_POLICY'" >&2 FAIL=1 fi ACT3_CONF="$(python3 "$SCRIPT_DIR/confidence_signal.py" "$CONTRACTS/act3.yaml")" echo " confidence_signal: $ACT3_CONF" ( cd "$REPO_ROOT" && bash "$SCRIPT_DIR/mock_executor.sh" "$CONTRACTS/act3.yaml" ) MOCK_RC=$? rm -f "$REPO_ROOT/state.json" if [ "$MOCK_RC" -ne 0 ]; then echo "run_demo.sh: Act 3 mock_executor failed (rc=$MOCK_RC)" >&2 FAIL=1 fi run_passing_pipeline "dev applied: l2-commodity-price-feed (Act 3 from issue)" # ----------------------------------------------------------------------------- # Act 4 — Safety Net # ----------------------------------------------------------------------------- echo "== Act 4 — Safety Net ==" cat > "$CONTRACTS/act4.yaml" <<'YAML' stack: l2-regulatory-reporting inputs: environment: dev owner: platform-team public-ingress: true YAML # policy_checker exits 1 on violation; capture without failing the script. ACT4_POLICY="$(python3 "$SCRIPT_DIR/policy_checker.py" "$CONTRACTS/act4.yaml" 2>&1 || true)" echo " policy_checker: $ACT4_POLICY" if [ "$ACT4_POLICY" != "POLICY_VIOLATION:PUBLIC_INGRESS" ]; then echo "run_demo.sh: Act 4 expected POLICY_VIOLATION:PUBLIC_INGRESS, got '$ACT4_POLICY'" >&2 FAIL=1 fi ACT4_CONF="$(python3 "$SCRIPT_DIR/confidence_signal.py" "$CONTRACTS/act4.yaml")" echo " confidence_signal: $ACT4_CONF" # Expected: {"score": 0.40, "reason": "POLICY_VIOLATION:PUBLIC_INGRESS"} # Score < 0.50 -> dev rejects. Do NOT run mock_executor, do NOT write qa/prod/finalize. ev dev "dev rejected: POLICY_VIOLATION:PUBLIC_INGRESS (confidence 0.40 < 0.50)" # ----------------------------------------------------------------------------- # Summary # ----------------------------------------------------------------------------- echo "== Summary ==" python3 - "$AUDIT" <<'PY' import json, sys audit = json.load(open(sys.argv[1])) for e in audit: print(f"{e['seq']} | {e['stage']} | {e['event']} | {e['hash'][:12]}") print(f"total events: {len(audit)}") PY EVENT_COUNT="$(python3 -c "import json; print(len(json.load(open('$AUDIT'))))")" echo "event count: $EVENT_COUNT" if [ "$EVENT_COUNT" -lt 11 ]; then echo "run_demo.sh: expected >= 11 events, got $EVENT_COUNT" >&2 FAIL=1 fi # ----------------------------------------------------------------------------- # Upload (optional) # ----------------------------------------------------------------------------- if [ "$UPLOAD" -eq 1 ]; then echo "== Upload ==" if [ -z "${ACDL_GITEA_TOKEN:-}" ]; then echo "run_demo.sh: ACDL_GITEA_TOKEN not set; skipping upload (use --no-upload to silence)" >&2 else # Upload audit.json to acdl-evidence main. if python3 "$SCRIPT_DIR/finalize_evidence.py" --audit "$AUDIT"; then echo " audit.json uploaded" else echo "run_demo.sh: finalize_evidence failed for audit.json" >&2 FAIL=1 fi # Upload index.html (the --audit flag accepts any local file path; --path # sets the remote destination). if python3 "$SCRIPT_DIR/finalize_evidence.py" \ --audit "$REPO_ROOT/evidence-ui/index.html" \ --path index.html \ --message "chore(ui): update index.html (demo dry run)"; then echo " index.html uploaded" else echo "run_demo.sh: finalize_evidence failed for index.html" >&2 FAIL=1 fi echo "Uploaded audit.json + index.html to acdl-evidence main" echo " raw URL: https://git.cloudinit.dev/continuous-intelligence/acdl-evidence/raw/branch/main/index.html" fi else echo "== Upload skipped (--no-upload) ==" fi # ----------------------------------------------------------------------------- # Exit # ----------------------------------------------------------------------------- if [ "$FAIL" -ne 0 ]; then echo "run_demo.sh: one or more steps failed (see warnings above)" >&2 exit 1 fi echo "run_demo.sh: OK ($EVENT_COUNT events)" exit 0