diff --git a/.ciagent/ARCHITECTURE.md b/.ciagent/ARCHITECTURE.md index de17d56..0247b77 100644 --- a/.ciagent/ARCHITECTURE.md +++ b/.ciagent/ARCHITECTURE.md @@ -48,6 +48,8 @@ The demo is a three-repo, stub-driven system that simulates an autonomous cloud | `acdl-contracts` repo | Developer + agentic entry surface; holds contracts + issue workflow | Triggers main pipeline on push | `acdl` reusable workflow | | `acdl-evidence` repo | Pages host for `audit.json` + `index.html` timeline | Read-only for the pipeline; written at finalize stage | evidence_writer.py output | | Reusable pipeline workflow | Dev → QA → Prod → Finalize stages with environment gates | Gitea Actions; calls core scripts | All core scripts | +| `evidence-ui/index.html` | Vanilla-JS timeline UI (Phase 05); fetches `./audit.json` and renders events | Single HTML file with inline CSS+JS; no frameworks (REQ-14) | `audit.json` on `acdl-evidence` main | +| `scripts/run_demo.sh` | Phase 05 dry-run simulation of the 4 demo acts; calls core scripts + writes evidence + uploads `audit.json` + `index.html` to `acdl-evidence` | Bash; uses `evidence_writer.py` + `finalize_evidence.py` + the file-contents API | All Phase 03/04 artifacts | ## Phase 04 pipeline topology (research) @@ -90,6 +92,32 @@ Because each stage is a separate dispatch, the workflow file uses `if:` conditio This means a full pipeline = 3 dispatches (initial, qa-approve, prod-approve). The human drives each via the Gitea UI or the dispatch API. +## Phase 05 dry-run + UI (research) + +Phase 05 has no act_runner available in this environment, so the "dry run" +is a local bash simulation (`scripts/run_demo.sh`) that produces the same +`audit.json` shape a real pipeline run would, then uploads it (plus the +UI) to `acdl-evidence` via the file-contents API. The simulation covers: + +- **Act 1 — Friction:** a single evidence event "manual 2-week deployment (legacy process)" at `stage: dev` with a red-colored timeline marker. +- **Act 2 — Developer Self-Service:** `l2-commodity-price-feed` contract, full pipeline (dev → qa → prod → finalize), 4 evidence events. +- **Act 3 — Citizen Developer:** Issue body fed to `l3b_agent_stub.py`, generates the same `l2-commodity-price-feed` contract, identical pipeline, 4 evidence events. +- **Act 4 — Safety Net:** `l2-regulatory-reporting` contract with `public-ingress: true`, dev rejects (confidence 0.40 < 0.50), 1 evidence event "dev rejected: POLICY_VIOLATION:PUBLIC_INGRESS". + +The `audit.json` after `run_demo.sh` contains the genesis + all act +events (typically ~14 events). The UI fetches `./audit.json` and renders +a vertical timeline with stage-colored markers and a per-event hash +preview. + +### UI rendering contract (D-032, D-033) + +`evidence-ui/index.html`: +- Single file, inline CSS + JS, no external resources. +- Fetches `./audit.json` (relative URL; works against any raw-URL origin). +- Renders events as a vertical timeline; each event card shows `seq`, `ts`, `stage` (color-coded: `dev` blue, `qa` yellow, `prod` orange, `finalize` green, `genesis` gray, rejected events red), `event` text, and a 12-char hash preview (`hash.slice(0, 12)…`). +- Handles fetch failure with a "No audit data yet" message. +- Refresh button to re-fetch. + ## Data Flow 1. A `contract.yaml` arrives either by direct push (L3A) or by the issue workflow running `l3b_agent_stub.py` (L3B). diff --git a/.ciagent/PERSONAS.md b/.ciagent/PERSONAS.md index 097be0d..7288b94 100644 --- a/.ciagent/PERSONAS.md +++ b/.ciagent/PERSONAS.md @@ -59,12 +59,12 @@ verification_toolchain: ### frontend-engineer - **Domain:** frontend -- **Active:** false -- **Reason:** No UI in Phases 01-04. The single UI artifact (`index.html`, vanilla JS) is built in Phase 05. frontend-engineer is reactivated for Phase 05 only (see Phase-specific overrides below). -- **Phase-specific:** true (reactivates in Phase 05) +- **Active:** true # REACTIVATED for Phase 05 (evidence UI + dry run) +- **Reason:** Phase 05 builds the vanilla-JS `index.html` timeline UI (REQ-14) and runs the 4 demo acts end-to-end dry run (REQ-15). Inactive for Phases 01-04 (no UI). +- **Phase-specific:** true (this reactivation is for Phase 05 only; will be deactivated again after the milestone ships if the project continues) - **Frameworks:** vanilla-js, dom-api, fetch-api -- **Constraints:** no-frameworks, single-file, fetch-from-same-origin-raw-url -- **Territory:** `acdl-evidence/index.html` (Phase 05) +- **Constraints:** no-frameworks, single-file, fetch-from-same-origin-raw-url, relative-url-for-audit-json +- **Territory:** `evidence-ui/**` (the source-of-truth `index.html` in the `acdl` repo; pushed to `acdl-evidence` by `run_demo.sh`) ## Phase-specific overrides diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md index cb0f5de..4bb20fc 100644 --- a/.ciagent/PLAN.md +++ b/.ciagent/PLAN.md @@ -1,88 +1,100 @@ --- -phase: 04 -name: pipeline-and-approval-gates +phase: 05 +name: evidence-ui-and-demo-dry-run milestone: v1.0 milestone_type: feature status: planned -requirements: [REQ-10, REQ-12] +requirements: [REQ-13, REQ-14, REQ-15] must_haves: - - ".gitea/workflows/pipeline.yml is a real workflow (no placeholders) with workflow_dispatch inputs (contract-ref, approve_qa, approve_prod) and 4 jobs (dev, qa-gate, prod-gate, finalize) with correct if: conditions per D-027/D-028" - - "The dev job checks out acdl + acdl-contracts@contract-ref, runs policy_checker.py + confidence_signal.py + mock_executor.sh, and writes a dev_applied or dev_rejected evidence event to acdl-evidence via the file-contents API" - - "The qa-gate job (approve_qa=true) writes a qa_approved evidence event" - - "The prod-gate + finalize jobs (approve_prod=true) write prod_approved + finalize evidence events, committing the final audit.json to acdl-evidence main" - - "contracts-repo/.gitea/workflows/issue-to-contract.yml is a real workflow (no placeholders) that checks out l3b_agent_stub.py from acdl, parses the Issue body, commits contract.yaml to contract/ on acdl-contracts, and dispatches pipeline.yml with contract-ref=contract/" - - "scripts/finalize_evidence.py helper exists: given an audit.json file path, GETs the current file sha from acdl-evidence, PUTs the new content via the file-contents API. Used by the workflow steps to commit evidence updates." - - "scripts/verify_phase04.sh passes: validates the workflow YAML syntax, confirms job if: conditions, confirms issue-to-contract.yml references the dispatch API, runs a dry-run of finalize_evidence.py against a temp audit.json (mock API)" + - "evidence-ui/index.html exists: single HTML file with inline CSS + JS, no external resources, no frameworks (D-032, REQ-14)" + - "index.html fetches ./audit.json (relative URL) and renders events as a vertical timeline with stage color-coding (dev/qa/prod/finalize/genesis) + 12-char hash preview" + - "scripts/run_demo.sh exists: simulates all 4 acts (Friction, Dev Self-Service, Citizen Developer, Safety Net) by calling the Phase 03 core scripts + evidence_writer.py + finalize_evidence.py; writes a final audit.json; uploads audit.json + evidence-ui/index.html to acdl-evidence main via the Gitea file-contents API (D-031/D-033)" + - "scripts/run_demo.sh is idempotent: re-running overwrites both files on acdl-evidence and produces the same audit.json (deterministic hash chain)" + - "Act 4 produces a 'dev rejected: POLICY_VIOLATION:PUBLIC_INGRESS' evidence event with score 0.40 (the Safety Net)" + - "scripts/verify_phase05.sh passes: validates index.html structure (single file, inline, fetch call), runs run_demo.sh, fetches the raw audit.json + index.html URLs from acdl-evidence and confirms HTTP 200" verification: - typecheck: "bash -n scripts/*.sh && python3 -m py_compile scripts/*.py && python3 -c 'import yaml; yaml.safe_load(open(\".gitea/workflows/pipeline.yml\")); yaml.safe_load(open(\"contracts-repo/.gitea/workflows/issue-to-contract.yml\"))'" - test: "scripts/verify_phase04.sh" + typecheck: "bash -n scripts/*.sh && python3 -m py_compile scripts/*.py" + test: "scripts/verify_phase05.sh" build: no-op --- -# Phase 04 — pipeline-and-approval-gates PLAN +# Phase 05 — evidence-ui-and-demo-dry-run PLAN ## Goal -Replace the Phase 01 workflow skeletons with real implementations. Wire -the Dev → QA → Prod → Finalize pipeline with the 3-dispatch approval-gate -pattern (D-027/D-028), and the issue-to-contract trigger (D-030). +Build the vanilla-JS timeline UI and run the 4 demo acts as a local dry +run that produces a real `audit.json` and publishes it (plus the UI) to +`acdl-evidence` main. This is the milestone's capstone phase. ## Requirements covered -- REQ-10: reusable pipeline runs Dev (autonomous), QA (approval), Prod (approval), Finalize (commits audit.json to acdl-evidence) -- REQ-12: opening an Issue in acdl-contracts runs l3b_agent_stub.py, commits a contract.yaml to a new branch, closes the Issue, and triggers the main pipeline +- REQ-13: `acdl-evidence` is Pages-enabled and serves `audit.json` plus `index.html` (substituted by D-012: raw-URL 200 on both files) +- REQ-14: `index.html` uses vanilla JS to fetch `audit.json` and render events as a timeline +- REQ-15: All four demo acts reproduce deterministically in a dry run ## Waves (vertical slices, domain priority order) -### Wave 1 — backend-engineer (pipeline + finalize helper + issue trigger) +### Wave 1 — frontend-engineer (the UI) **Tasks:** -- **T-4.1** Create `scripts/finalize_evidence.py` — a helper that takes `--audit ` `--owner ` `--repo ` `--branch ` (defaults: continuous-intelligence, acdl-evidence, main) and `--token-env ` (default ACDL_GITEA_TOKEN). It GETs the current `audit.json` from the repo (to get the sha if it exists), reads the local `` file, PUTs (or POSTs if 404) the new content via the Gitea file-contents API with base64 + commit message "finalize: update audit.json (run )". Exit 0 on success, 1 on API failure. Used by the pipeline finalize + each stage to persist evidence to acdl-evidence. -- **T-4.2** Replace `.gitea/workflows/pipeline.yml` with the real implementation: - - `on: workflow_dispatch` with inputs `contract-ref` (string, default `main`), `approve_qa` (boolean, default `false`), `approve_prod` (boolean, default `false`). - - Job `dev`: `if: inputs.approve_qa != true && inputs.approve_prod != true`. Steps: checkout `acdl` (current repo) at `milestone/v1.0-initial`; checkout `acdl-contracts` at `inputs.contract-ref` into a sibling dir using `actions/checkout@v4` with `repository: continuous-intelligence/acdl-contracts`, `ref: ${{ inputs.contract-ref }}`, `token: ${{ secrets.GITEA_TOKEN }}`; run `python3 scripts/policy_checker.py ../acdl-contracts/contract.yaml`; run `python3 scripts/confidence_signal.py ../acdl-contracts/contract.yaml` and capture the score; if score < 0.50, run `python3 scripts/evidence_writer.py --stage dev --event "dev rejected: confidence < 0.50"` and `python3 scripts/finalize_evidence.py --audit audit.json` and `exit 1`; else run `bash scripts/mock_executor.sh ../acdl-contracts/contract.yaml`, then `python3 scripts/evidence_writer.py --stage dev --event "dev applied: "`, then `python3 scripts/finalize_evidence.py --audit audit.json`. - - Job `qa-gate`: `if: inputs.approve_qa == true && inputs.approve_prod != true`. Steps: checkout acdl; run `python3 scripts/evidence_writer.py --stage qa --event "qa approved"`; `python3 scripts/finalize_evidence.py --audit audit.json`. - - Job `prod-gate`: `if: inputs.approve_prod == true`. Steps: checkout acdl; run `python3 scripts/evidence_writer.py --stage prod --event "prod approved"`; `python3 scripts/finalize_evidence.py --audit audit.json`. - - Job `finalize`: `needs: prod-gate`. Steps: checkout acdl; run `python3 scripts/evidence_writer.py --stage finalize --event "pipeline complete: audit.json committed to acdl-evidence"`; `python3 scripts/finalize_evidence.py --audit audit.json`. - - All jobs `runs-on: ubuntu-latest`. Use `gitea.*` context where relevant. -- **T-4.3** Replace `contracts-repo/.gitea/workflows/issue-to-contract.yml` with the real implementation: - - `on: issues` with `types: [opened]`. - - Job `parse-and-trigger`: `runs-on: ubuntu-latest`. Steps: checkout `acdl` (to get `l3b_agent_stub.py`) at `milestone/v1.0-initial`; run `python3 scripts/l3b_agent_stub.py "${{ gitea.event.issue.body }}" -o contract.yaml`; parse the contract to confirm a stack was set; commit `contract.yaml` to a new branch `contract/${{ gitea.event.issue.number }}` on `acdl-contracts` via the file-contents API (POST); comment on the Issue with the contract summary; close the Issue via the issues API; dispatch the pipeline workflow via `POST /api/v1/repos/continuous-intelligence/acdl/actions/workflows/pipeline.yml/dispatches` with body `{ "ref": "milestone/v1.0-initial", "inputs": { "contract-ref": "contract/${{ gitea.event.issue.number }}" } }` using `secrets.GITEA_TOKEN`. +- **T-5.1** Create `evidence-ui/index.html` — single HTML file with inline ` + + +
+

ACDL Evidence Timeline

+

ACDL — Agentic Cloud Delivery Platform · Audit Timeline

+
+
+ + +
+
+
+
Loading…
+
+
+
+ +
+ + + \ No newline at end of file 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 diff --git a/scripts/verify_phase05.sh b/scripts/verify_phase05.sh new file mode 100755 index 0000000..97d28c7 --- /dev/null +++ b/scripts/verify_phase05.sh @@ -0,0 +1,213 @@ +#!/usr/bin/env bash +# Phase 05 verification script. +# Validates the evidence UI + the 4-act demo dry-run. +# +# Usage: scripts/verify_phase05.sh +# Exit codes: 0 = all checks passed; 1 = one or more checks failed. + +set -uo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +fail_count=0 +pass() { printf ' [PASS] %s\n' "$1"; } +fail() { printf ' [FAIL] %s\n' "$1"; fail_count=$((fail_count + 1)); } + +GITEA_HOST="${GITEA_HOST:-https://git.cloudinit.dev}" +ORG="continuous-intelligence" +EVIDENCE_REPO="acdl-evidence" + +echo "== Phase 05 verification ==" +echo "Root: ${ROOT}" +echo + +# --- Check 1: evidence-ui/index.html structure --- +echo "-- Check 1: evidence-ui/index.html structure (D-032, REQ-14) --" +UI="evidence-ui/index.html" +if [ ! -f "$UI" ]; then + fail "$UI missing" +else + pass "$UI exists" + size=$(wc -c < "$UI") + if [ "$size" -ge 1000 ] && [ "$size" -le 30000 ]; then + pass "$UI size ${size} bytes (within 1-30 KB range)" + else + fail "$UI size ${size} bytes (expected 1-30 KB)" + fi + ui_check=$(python3 << 'PYEOF' +import re, sys +content = open('evidence-ui/index.html').read() +problems = [] +if '' not in content: problems.append('missing inline