#!/usr/bin/env python3 """verify-registry.py — check the append-only (profile_id, codec_digest) registry. usage: python3 verify-registry.py registry.jsonl [served-root] Rule: row N carries prev_sha256 = sha256 of row N-1's exact line bytes (no trailing newline); row 1 carries null. Rewriting or dropping any earlier row breaks every chain link after it. With a served-root (a directory holding the files the rows name, e.g. a local copy of /fixtures/l0/), every fixture_sha256 and reference_fn_sha256 is also recomputed. Exit 0 = every link and every named hash agrees; 1 otherwise. This checks the file's shape and its named bytes; it cannot check that a row was committed at the time it says. """ import sys, json, hashlib, os path = sys.argv[1]; root = sys.argv[2] if len(sys.argv) > 2 else None prev = None; bad = 0 for n, line in enumerate(open(path, 'rb').read().split(b"\n"), 1): if not line: continue r = json.loads(line) if r.get("prev_sha256") != prev: print(f"row {n} seq {r.get('seq')}: prev_sha256 {r.get('prev_sha256')} != {prev} BROKEN LINK"); bad += 1 if root: for key, fkey in (("fixture", "fixture_sha256"), ("reference_fn", "reference_fn_sha256"), ("codec", "codec_digest")): if r.get(key) and r.get(fkey): rel = r[key].split("/fixtures/l0/", 1)[1] if "/fixtures/l0/" in r[key] else r[key].lstrip("/") p = os.path.join(root, rel) if not os.path.exists(p): print(f"row {n}: {p} not present under root (not checked)"); continue h = hashlib.sha256(open(p, 'rb').read()).hexdigest() if h != r[fkey]: print(f"row {n} {key}: sha256 {h} != {r[fkey]} MISMATCH"); bad += 1 prev = hashlib.sha256(line).hexdigest() print(f"{'OK' if not bad else 'FAIL'}: chain head {prev} ({bad} problem(s))") sys.exit(1 if bad else 0)