#!/usr/bin/env bash # FablePool end-to-end demo, driven purely through the public CLI. # Mirrors demo/run_demo.py: two user nodes sync, a correction cascades, # a coach node receives a claims-only slice, and revocation is honoured. # # Usage: ./demo/run_demo.sh [state-dir] set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" ROOT="$(dirname "$HERE")" STATE="${1:-$(mktemp -d -t fablepool-demo-XXXXXX)}" PY="${PYTHON:-python3}" FABLE="$PY -m fablepool.cli" export PYTHONPATH="$ROOT${PYTHONPATH:+:$PYTHONPATH}" step() { printf '\n========================================================\n%s\n========================================================\n' "$1"; } step "STEP 1: create phone, laptop, and coach nodes ($STATE)" $FABLE init --path "$STATE/phone" --name "Phone" $FABLE init --path "$STATE/laptop" --name "Laptop" $FABLE init --path "$STATE/coach" --name "Coach (delegate)" COACH_ID="$($FABLE id --path "$STATE/coach")" step "STEP 2: ingest evidence and derive claims on each user node" $FABLE ingest --path "$STATE/phone" --kind calendar "$ROOT/datasets/phone/calendar.ics" $FABLE ingest --path "$STATE/phone" --kind photos "$ROOT/datasets/phone/photos.json" $FABLE ingest --path "$STATE/laptop" --kind notes "$ROOT/datasets/laptop/notes" $FABLE derive --path "$STATE/phone" $FABLE derive --path "$STATE/laptop" step "STEP 3: two-way log synchronisation via signed bundles" $FABLE export --path "$STATE/phone" -o "$STATE/phone.bundle.json" $FABLE import --path "$STATE/laptop" "$STATE/phone.bundle.json" $FABLE export --path "$STATE/laptop" -o "$STATE/laptop.bundle.json" $FABLE import --path "$STATE/phone" "$STATE/laptop.bundle.json" step "STEP 4: what do you know about me and why? (laptop)" $FABLE answer --path "$STATE/laptop" step "STEP 5: refute a claim on the laptop; sync the correction to the phone" CLAIM_ID="$($FABLE claims --path "$STATE/laptop" --json | $PY -c \ 'import json,sys; cs=json.load(sys.stdin); print(cs[0]["claim_id"])')" $FABLE refute --path "$STATE/laptop" "$CLAIM_ID" --reason "User correction: not true." $FABLE export --path "$STATE/laptop" -o "$STATE/laptop.bundle2.json" $FABLE import --path "$STATE/phone" "$STATE/laptop.bundle2.json" echo "phone view after correction:" $FABLE claims --path "$STATE/phone" --all step "STEP 6: grant the coach a claims-only slice (narrow predicates)" PREDS="$($FABLE claims --path "$STATE/phone" --json | $PY -c \ 'import json,sys; cs=json.load(sys.stdin); print(",".join(sorted({c["predicate"] for c in cs})[:2]))')" echo "granting predicates: $PREDS" GRANT_ID="$($FABLE grant --path "$STATE/phone" --to "$COACH_ID" \ --predicates "$PREDS" --note "coach slice" --json | $PY -c \ 'import json,sys; print(json.load(sys.stdin)["grant_id"])')" $FABLE export --path "$STATE/phone" --grant "$GRANT_ID" -o "$STATE/coach.slice.json" # Prove the slice carries no raw evidence before handing it over. $PY -c ' import json,sys ops = json.load(open(sys.argv[1]))["ops"] types = {o.get("op_type", o.get("type")) for o in ops} assert "evidence" not in types, "grant slice leaked evidence!" print(f"slice OK: {len(ops)} ops, types={sorted(types)} (no evidence)")' "$STATE/coach.slice.json" $FABLE import --path "$STATE/coach" "$STATE/coach.slice.json" echo "coach view (claims only, granted predicates only):" $FABLE claims --path "$STATE/coach" step "STEP 7: revoke the grant; the coach verifiably honours it" $FABLE revoke --path "$STATE/phone" "$GRANT_ID" --reason "user revoked access" $FABLE export --path "$STATE/phone" --grant "$GRANT_ID" -o "$STATE/coach.revoke.json" $FABLE import --path "$STATE/coach" "$STATE/coach.revoke.json" echo "coach view after revocation (expected: no active claims):" $FABLE claims --path "$STATE/coach" $PY -m fablepool.cli claims --path "$STATE/coach" --json | $PY -c \ 'import json,sys; cs=json.load(sys.stdin); assert not cs, "revocation NOT honoured"; print("revocation honoured: delegate holds 0 active claims")' step "STEP 8: verify every signature in every log" $FABLE verify --path "$STATE/phone" $FABLE verify --path "$STATE/laptop" $FABLE verify --path "$STATE/coach" echo echo "DEMO COMPLETE. Node state: $STATE"