From 3ab7574b8d9fec5ae11ecd36c616187c96dfc5e7 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Tue, 21 Jul 2026 13:49:25 +0000 Subject: [PATCH] feat(P05): run_demo.sh 4-act dry-run + upload (T-5.2) ---ci--- phase: 5 milestone: v1.0 status: execute persona: backend-engineer task: T-5.2 requirements: covered: [REQ-13, REQ-15] ---/ci--- Wave 2, task T-5.2. scripts/run_demo.sh simulates the 4 demo acts locally (no act_runner): Act 1 Friction (1 event), Act 2 Developer Self-Service (valid contract, full pipeline, 4 events), Act 3 Citizen Developer (l3b_agent_stub generates the contract, same pipeline, 4 events), Act 4 Safety Net (public-ingress:true, dev rejects with score 0.40 < 0.50, 1 event). Uploads audit.json + evidence-ui/index.html to acdl-evidence main via finalize_evidence.py (skippable with --no-upload). Idempotent. --- scripts/run_demo.sh | 258 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100755 scripts/run_demo.sh diff --git a/scripts/run_demo.sh b/scripts/run_demo.sh new file mode 100755 index 0000000..4c19739 --- /dev/null +++ b/scripts/run_demo.sh @@ -0,0 +1,258 @@ +#!/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 \ No newline at end of file