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]>
99 lines
5.8 KiB
Shell
Executable file
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"
|