From 857ea37b962fe22988cd3ff7c4cbc38f92f14162 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Sat, 25 Jul 2026 00:00:08 -0500 Subject: [PATCH] admission: bind one receipt to one image digest The July experiment deployed image e1926f3, which was not the artifact its receipt covered, and nothing in the pipeline refused it. This adds the missing mechanism. scripts/admit run requires a clean worktree, runs every suite, builds the exact linux/amd64 image, and writes a receipt naming the Stream, Zig, upstream-Jetstream and dependency revisions plus the dashboard checksum. publish pushes that image and pins the receipt to the registry digest, but refuses if the local image no longer matches what the receipt covered. verify is the deployment-side gate. Rules that follow from how the previous receipt failed: - the receipt records the artifact's digest, never a tag, because tags move; - a suite that does not run is written as "skipped" rather than omitted, so a partial run cannot be read as a full one, and a skipped suite blocks admission exactly like a failed one; - any suite failure aborts before a receipt exists; - the tree is re-checked after the suites, since a long run leaves room for the source to change under it. tests/admission_contract.py pins the refusals rather than the happy path: unknown digest, unpublished receipt, another artifact's digest, a skipped suite and a failed suite must all be rejected. Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 + justfile | 11 ++ scripts/admit | 240 ++++++++++++++++++++++++++++++++++++ tests/admission_contract.py | 88 +++++++++++++ 4 files changed, 342 insertions(+) create mode 100755 scripts/admit create mode 100644 tests/admission_contract.py diff --git a/.gitignore b/.gitignore index bae927b..9e66b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ EXPERIMENTS.md # claude code harness state (session locks, local config) .claude/ data-*/ + +# admission receipt logs (the receipts themselves are tracked) +receipts/logs-*/ diff --git a/justfile b/justfile index 799f2c0..ff75b8d 100644 --- a/justfile +++ b/justfile @@ -425,6 +425,17 @@ package: zig build -Doptimize=ReleaseSafe -Dtarget=x86_64-linux-gnu @ls -lh zig-out/bin/stream +# Offline: prove the deployment gate refuses every artifact it has not seen +# pass -- unknown digest, unpublished receipt, skipped suite, failed suite. +admission-contract: + python3 tests/admission_contract.py + +# Exact-artifact admission. `run` requires a clean tree, runs every suite, +# builds the linux/amd64 image and writes one receipt; `publish` pins that +# receipt to the pushed registry digest; `verify` is the deployment gate. +admit *ARGS: + ./scripts/admit {{ARGS}} + # build + push the deploy image from a FRESH CLONE — the image tag always # corresponds 1:1 to a pushed commit (provenance by construction; zlay's # dirty-tree lesson) diff --git a/scripts/admit b/scripts/admit new file mode 100755 index 0000000..001f5a3 --- /dev/null +++ b/scripts/admit @@ -0,0 +1,240 @@ +#!/usr/bin/env bash +# Exact-artifact admission for Stream. +# +# The previous whole-network experiment deployed image e1926f3, which was not +# the artifact its receipt covered. Nothing detected that. This binds one +# receipt to one image digest and gives deployment a way to refuse anything +# else. +# +# scripts/admit build build the exact linux/amd64 image, no publish +# scripts/admit run clean tree -> suites -> image -> receipt +# scripts/admit publish push the admitted image and pin the receipt +# to the registry digest +# scripts/admit verify exit non-zero unless is admitted +# +# Design rules, learned from the failure this replaces: +# - the receipt records the artifact's own digest, never a tag; tags move +# - a skipped suite is written into the receipt as "skipped", never omitted, +# so a partial run cannot read as a full one +# - any suite failure aborts before a receipt exists +# - the tree must be clean, so the receipt's revisions describe the source +# that actually produced the binary +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/.." +ROOT="$PWD" +RECEIPTS="${STREAM_RECEIPTS_DIR:-$ROOT/receipts}" +IMAGE_REPO="${STREAM_IMAGE_REPO:-atcr.io/zat.dev/stream}" + +die() { printf 'admit: %s\n' "$*" >&2; exit 1; } +note() { printf '\033[1m==> %s\033[0m\n' "$*"; } + +require_clean_tree() { + [ -z "$(git status --porcelain)" ] || + die "worktree is dirty; the receipt must describe the source that built the artifact +$(git status --porcelain)" +} + +# Every pinned input that can change behavior. A receipt that does not name +# these cannot be checked later. +revisions_json() { + python3 - "$ROOT" <<'PY' +import json, re, subprocess, sys, pathlib +root = pathlib.Path(sys.argv[1]) +zon = (root / "build.zig.zon").read_text() +deps = dict(re.findall(r'\.(\w[\w-]*) = \.\{\s*\.url = "([^"]+)"', zon)) +def git(*a): + return subprocess.check_output(["git", *a], cwd=root, text=True).strip() +upstream = pathlib.Path.home() / "github.com/bluesky-social/jetstream" +try: + upstream_rev = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=upstream, text=True).strip() + upstream_dirty = bool(subprocess.check_output( + ["git", "status", "--porcelain", "--untracked-files=no"], cwd=upstream, text=True).strip()) +except Exception: + upstream_rev, upstream_dirty = None, None +print(json.dumps({ + "stream": git("rev-parse", "HEAD"), + "stream_describe": git("describe", "--always", "--dirty"), + "zig": subprocess.check_output(["zig", "version"], text=True).strip(), + "dependencies": deps, + "upstream_jetstream": upstream_rev, + "upstream_jetstream_dirty": upstream_dirty, + "dashboard_sha256": __import__("hashlib").sha256( + (root / "deploy/grafana/jetstream-upstream.json").read_bytes()).hexdigest(), +}, indent=2, sort_keys=True)) +PY +} + +# name|command. Negative suites come first: they are the ones that decide +# whether a green positive run means anything. +SUITES=( + "unit-debug|zig build test" + "unit-releasesafe|zig build test -Doptimize=ReleaseSafe" + "differential-oracle|just differential-oracle" + "listener-contract|just listener-contract" + "shutdown-contract|just shutdown-contract" + "logging-contract|just logging-contract" + "environment-contract|just environment-contract" + "dashboard-test|just dashboard-test" + "process-metrics-contract|just process-metrics-contract" + "http-metrics-contract|just http-metrics-contract" + "archive-contract|just archive-contract" + "plan-config-contract|just plan-config-contract" + "cursor-lookback-contract|just cursor-lookback-contract" + "compaction-config-contract|just compaction-config-contract" + "retry-config-contract|just retry-config-contract" + "subscribe-config-contract|just subscribe-config-contract" + "subscribe-read-batch-contract|just subscribe-read-batch-contract" + "status-contract|just status-contract" + "powerloss-oracle|just powerloss-oracle" +) + +run_suites() { + local out="$1" log_dir="$2" first=1 + mkdir -p "$log_dir" + printf '{\n' >"$out" + for entry in "${SUITES[@]}"; do + local name="${entry%%|*}" cmd="${entry#*|}" status started elapsed + # STREAM_ADMIT_SKIP is an escape hatch for an environment that genuinely + # cannot run a suite. It is recorded, not hidden. + if [[ ",${STREAM_ADMIT_SKIP:-}," == *",$name,"* ]]; then + status="skipped" + note "SKIP $name (STREAM_ADMIT_SKIP)" + else + note "$name" + started=$SECONDS + if (cd "$ROOT" && eval "$cmd") >"$log_dir/$name.log" 2>&1; then + status="pass" + else + status="fail" + fi + elapsed=$((SECONDS - started)) + if [ "$status" = fail ]; then + tail -30 "$log_dir/$name.log" >&2 + die "$name failed after ${elapsed}s; no receipt written" + fi + printf ' %s (%ss)\n' "$status" "$elapsed" + fi + [ $first -eq 1 ] || printf ',\n' >>"$out" + first=0 + printf ' "%s": "%s"' "$name" "$status" >>"$out" + done + printf '\n}\n' >>"$out" +} + +build_image() { + local tag="$1" + note "building linux/amd64 image $tag" + docker build --platform linux/amd64 -t "$tag" "$ROOT" >&2 + # The local image ID is the artifact's identity before it is pushed. The + # registry digest replaces it at publish time. + docker image inspect --format '{{.Id}}' "$tag" +} + +cmd_build() { + require_clean_tree + local sha; sha=$(git rev-parse --short HEAD) + build_image "$IMAGE_REPO:$sha" +} + +cmd_run() { + require_clean_tree + local sha stamp receipt log_dir suites_json revisions image_id tag + sha=$(git rev-parse --short HEAD) + stamp=$(date -u +%Y%m%dT%H%M%SZ) + log_dir="$RECEIPTS/logs-$sha-$stamp" + suites_json=$(mktemp) + trap 'rm -f "$suites_json"' EXIT + + run_suites "$suites_json" "$log_dir" + + tag="$IMAGE_REPO:$sha" + image_id=$(build_image "$tag") + revisions=$(revisions_json) + + # Re-check: a suite run can take long enough for someone to touch the tree. + require_clean_tree + + mkdir -p "$RECEIPTS" + receipt="$RECEIPTS/$sha.json" + python3 - "$receipt" "$image_id" "$tag" "$stamp" "$suites_json" <&2 + digest=$(docker image inspect --format '{{index .RepoDigests 0}}' "$tag" | cut -d@ -f2) + [ -n "$digest" ] || die "could not read the pushed digest" + + python3 - "$receipt" "$digest" <<'PY' +import json, sys +receipt, digest = sys.argv[1], sys.argv[2] +data = json.load(open(receipt)) +if data["image"]["registry_digest"] not in (None, digest): + raise SystemExit(f"admit: receipt already pinned to {data['image']['registry_digest']}") +data["image"]["registry_digest"] = digest +json.dump(data, open(receipt, "w"), indent=2, sort_keys=True) +open(receipt, "a").write("\n") +PY + note "admitted $digest" +} + +# The deployment-side gate. Anything not carrying an admitted digest is +# refused, which is exactly what was missing when e1926f3 shipped. +cmd_verify() { + local digest="${1:-}" + [ -n "$digest" ] || die "usage: scripts/admit verify " + python3 - "$RECEIPTS" "$digest" <<'PY' +import json, pathlib, sys +receipts, digest = pathlib.Path(sys.argv[1]), sys.argv[2] +for path in sorted(receipts.glob("*.json")): + data = json.load(open(path)) + if data.get("image", {}).get("registry_digest") != digest: + continue + suites = data.get("suites", {}) + bad = {k: v for k, v in suites.items() if v != "pass"} + if bad: + print(f"REJECT {digest}: {path.name} has non-passing suites: {bad}") + raise SystemExit(1) + print(f"ADMITTED {digest} ({path.name}, stream {data['revisions']['stream'][:12]})") + raise SystemExit(0) +print(f"REJECT {digest}: no receipt admits this digest") +raise SystemExit(1) +PY +} + +case "${1:-}" in + build) shift; cmd_build "$@" ;; + run) shift; cmd_run "$@" ;; + publish) shift; cmd_publish "$@" ;; + verify) shift; cmd_verify "$@" ;; + *) die "usage: scripts/admit {build|run|publish|verify }" ;; +esac diff --git a/tests/admission_contract.py b/tests/admission_contract.py new file mode 100644 index 0000000..70b6f78 --- /dev/null +++ b/tests/admission_contract.py @@ -0,0 +1,88 @@ +"""Offline contract for the deployment-side admission gate. + +The previous experiment deployed an image no receipt covered, and nothing +refused it. These cases pin the refusals, not the happy path: the gate is only +worth anything if it says no to everything it has not seen pass. + + python3 tests/admission_contract.py +""" +import json +import pathlib +import subprocess +import sys +import tempfile + +ADMIT = pathlib.Path(__file__).resolve().parent.parent / "scripts" / "admit" +DIGEST = "sha256:" + "a" * 64 +OTHER = "sha256:" + "b" * 64 + + +def verify(receipts_dir, digest): + return subprocess.run( + [str(ADMIT), "verify", digest], + capture_output=True, + text=True, + env={"PATH": "/usr/bin:/bin:/usr/local/bin", "STREAM_RECEIPTS_DIR": str(receipts_dir)}, + ) + + +def write_receipt(receipts_dir, name, *, digest, suites): + (receipts_dir / name).write_text( + json.dumps( + { + "schema": "stream-admission/1", + "image": {"tag": "atcr.io/zat.dev/stream:test", "registry_digest": digest}, + "revisions": {"stream": "0" * 40}, + "suites": suites, + } + ) + ) + + +ALL_PASS = {"unit-debug": "pass", "archive-contract": "pass"} + + +def case(label, condition): + if not condition: + print(f"FAIL: {label}") + sys.exit(1) + print(f" ok: {label}") + + +with tempfile.TemporaryDirectory() as tmp: + receipts = pathlib.Path(tmp) + + # Nothing admitted yet: the gate must refuse, not default open. + r = verify(receipts, DIGEST) + case("empty receipts dir rejects", r.returncode != 0 and "REJECT" in r.stdout) + + write_receipt(receipts, "abc1234.json", digest=DIGEST, suites=ALL_PASS) + r = verify(receipts, DIGEST) + case("fully passing receipt admits its own digest", r.returncode == 0 and "ADMITTED" in r.stdout) + + # The core of the e1926f3 failure: a *different* artifact must not inherit + # another artifact's receipt. + r = verify(receipts, OTHER) + case("a different digest is rejected", r.returncode != 0 and "REJECT" in r.stdout) + + # A receipt that never reached publish has no digest to stand behind. + write_receipt(receipts, "unpublished.json", digest=None, suites=ALL_PASS) + r = verify(receipts, "null") + case("unpublished receipt admits nothing", r.returncode != 0) + + # A skipped suite is not a passed suite. Silence must not read as success. + skipped = receipts / "skipped" + skipped.mkdir() + write_receipt(skipped, "def5678.json", digest=DIGEST, + suites={**ALL_PASS, "powerloss-oracle": "skipped"}) + r = verify(skipped, DIGEST) + case("a skipped suite blocks admission", r.returncode != 0 and "non-passing" in r.stdout) + + failed = receipts / "failed" + failed.mkdir() + write_receipt(failed, "aaa1111.json", digest=DIGEST, + suites={**ALL_PASS, "differential-oracle": "fail"}) + r = verify(failed, DIGEST) + case("a failed suite blocks admission", r.returncode != 0 and "non-passing" in r.stdout) + +print("ADMISSION GATE CONTRACT PASS") -- 2.54.0