open-standards/wai/wai-fleet/smoke.sh
Transaction Science d82b136176 wai-fleet: a fleet as a service, enrolments and records kept append-only, reports signed and kept with their bundles
The HTTP API of extensions/fleet.md §9 over a directory it owns (two
append-only logs, reports written whole, a lock, everything verified again on
start), and wai-fleet collect, which gives each line of a node's summary file,
a path or an http(s) address, to a running service. Its tests sign the
corpus's report byte for byte, recover the directory, cut off a line a crash
cut short, read every corpus summary back through the reference relay's own
types, and collect records the relay's own code signed from a file over HTTP;
smoke.sh checks the API over HTTP and the bundle with fleet_verify.py.

wai-rs: Held checks a record or an enrolment before it is kept, so the
service keeps one only once it is on disk.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 15:31:13 -04:00

99 lines
5.8 KiB
Shell
Executable file

#!/usr/bin/env bash
# smoke.sh BASE_URL: the fleet service's promises, checked over HTTP against a running instance
# started with the corpus's organisation key (CI starts one locally; see README.md).
#
# bash smoke.sh http://127.0.0.1:18252
#
# From the committed corpus (../live-conformance/fleet/vectors.json): it enrols the week's nodes,
# gives the service the week's records, asks for the week's report and requires the corpus's report
# byte for byte with the corpus's outcome; checks the report's bundle with the independent verifier,
# fleet_verify.py; requires a record held already, and a record of a node no enrolment names, to be
# refused; requires the lesson room's per-hop records back in the ledger's source form; and requires
# an altered bundle to be refused by POST /v1/fleet/verify. Each check prints one line and the script
# fails on the first that does not hold. Needs curl and python3.
set -euo pipefail
U=${1:?usage: smoke.sh BASE_URL}
U=${U%/}
HERE=$(cd "$(dirname "$0")" && pwd)
CORPUS="$HERE/../live-conformance/fleet/vectors.json"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
post() { curl -sS --max-time 300 -o "$3" -w '%{http_code}' -X POST "$U$1" -H 'content-type: application/json' --data-binary "@$2"; }
py() { python3 -I -c "$@"; }
curl -sS --max-time 30 "$U/v1/fleet" > "$tmp/describe.json"
py 'import json,sys
d=json.load(open(sys.argv[1])); c=json.load(open(sys.argv[2]))
assert d["service"]=="wai-fleet" and d["capability"]=="wai.delivery.fleet", d
assert d["organisation_key"]==c["keys"]["organisation"], "start the service with the corpus organisation key (README.md)"
print("describe: ok, organisation", d["organisation_key"][:16]+"…")' "$tmp/describe.json" "$CORPUS"
# The week's enrolments, then its records in one request.
py 'import json,sys
c=json.load(open(sys.argv[1])); v=next(x for x in c["vectors"] if x["name"]=="a_week")
for k,e in enumerate(v["bundle"]["enrolments"]):
json.dump({"enrolment": e}, open(sys.argv[2]+"/enrol-%02d.json" % k,"w"))
json.dump({"records": v["bundle"]["records"]}, open(sys.argv[2]+"/records.json","w"))
json.dump({"records": v["bundle"]["records"][:1]}, open(sys.argv[2]+"/again.json","w"))
s=next(x for x in c["vectors"] if x["name"]=="signer_not_enrolled")
json.dump({"records": [s["bundle"]["records"][8]]}, open(sys.argv[2]+"/outsider.json","w"))
json.dump({"start": v["outcome"]["start"], "end": v["outcome"]["end"]}, open(sys.argv[2]+"/period.json","w"))
print(v["outcome"]["rooms"][0]["room"])' "$CORPUS" "$tmp" > "$tmp/lesson"
for f in "$tmp"/enrol-*.json; do
code=$(post /v1/fleet/enrolments "$f" "$f.out")
[ "$code" = 201 ] || { echo "enrolment $(basename "$f"): HTTP $code: $(cat "$f.out")"; exit 1; }
done
echo "enrolments: ok, $(ls "$tmp"/enrol-*.json | wc -l | tr -d ' ') enrolled"
code=$(post /v1/fleet/records "$tmp/records.json" "$tmp/records.out")
[ "$code" = 200 ] || { echo "records: HTTP $code: $(cat "$tmp/records.out")"; exit 1; }
py 'import json,sys
a=json.load(open(sys.argv[1])); assert a["refused"]==0 and a["held"]>0, a
print("records: ok,", a["held"], "held")' "$tmp/records.out"
# A record held already, and one of a node no enrolment names, are refused.
post /v1/fleet/records "$tmp/again.json" "$tmp/again.out" > /dev/null
post /v1/fleet/records "$tmp/outsider.json" "$tmp/outsider.out" > /dev/null
py 'import json,sys
a=json.load(open(sys.argv[1])); b=json.load(open(sys.argv[2]))
assert a["results"]==[{"refused":"record_duplicate"}], a
assert b["results"]==[{"refused":"signer_not_enrolled"}], b
print("refusals: ok, record_duplicate and signer_not_enrolled")' "$tmp/again.out" "$tmp/outsider.out"
# The week's report: the corpus's, byte for byte, with its outcome.
code=$(post /v1/fleet/reports "$tmp/period.json" "$tmp/report.out")
[ "$code" = 201 ] || { echo "report: HTTP $code: $(cat "$tmp/report.out")"; exit 1; }
py 'import json,sys
c=json.load(open(sys.argv[1])); v=next(x for x in c["vectors"] if x["name"]=="a_week"); r=json.load(open(sys.argv[2]))
assert r["bundle"]["report"]==v["bundle"]["report"], "the report differs from the corpus"
assert r["outcome"]==v["outcome"], "the outcome differs from the corpus"
json.dump(r["bundle"], open(sys.argv[3]+"/week.wai-fleet.json","w"))
print("report: ok, the corpus report, digest", r["report"][:16]+"…")' "$CORPUS" "$tmp/report.out" "$tmp"
D=$(py 'import json,sys; print(json.load(open(sys.argv[1]))["report"])' "$tmp/report.out")
curl -sS --max-time 60 "$U/v1/fleet/reports/$D" > "$tmp/served.json"
py 'import json,sys
a=json.load(open(sys.argv[1])); b=json.load(open(sys.argv[2]))
assert a["bundle"]==b["bundle"] and a["outcome"]==b["outcome"]
print("report served: ok")' "$tmp/served.json" "$tmp/report.out"
# The bundle, checked by the second implementation, which shares no code with the service.
python3 -I "$HERE/../live-conformance/fleet_verify.py" --bundle "$tmp/week.wai-fleet.json" > "$tmp/independent.out" \
|| { echo "fleet_verify.py refused the bundle:"; cat "$tmp/independent.out"; exit 1; }
echo "independent verifier: ok, the bundle verifies"
# The lesson room's per-hop records, in the ledger's source form.
curl -sS --max-time 60 "$U/v1/fleet/rooms/$(cat "$tmp/lesson")/hops" > "$tmp/hops.json"
py 'import json,sys
h=json.load(open(sys.argv[1]))["hops"]
assert len(h)==3 and all(sorted(x)==["ledger","record"] for x in h), h
print("room hops: ok, 3 records with their ledgers")' "$tmp/hops.json"
# An altered bundle is refused.
py 'import json,sys
b=json.load(open(sys.argv[1])); r=bytearray(bytes.fromhex(b["report"])); r[-1]^=1; b["report"]=r.hex()
json.dump(b, open(sys.argv[2],"w"))' "$tmp/week.wai-fleet.json" "$tmp/altered.json"
post /v1/fleet/verify "$tmp/altered.json" "$tmp/altered.out" > /dev/null
py 'import json,sys
o=json.load(open(sys.argv[1])); assert o["outcome"]=="refuse" and o["reason"]=="bad_signature", o
print("verify: ok, an altered bundle is refused", o["reason"])' "$tmp/altered.out"
echo "smoke: all checks held"