#!/usr/bin/env python3 """Verify the Zerebro Mind corpus against its manifest. Standard library only. Usage: python3 verify.py corpus/manifest.json Manifest format: {"generated_at": "...", "root_sha256": "", "items": [{"id": "...", "source": "...", "url": "...", "date": "...", "sha256": "", "path": ""}, ...]} root_sha256 = sha256 of the UTF-8 text made of one line per item, " \n", sorted by id. """ import hashlib import json import sys from pathlib import Path def file_sha256(path): h = hashlib.sha256() with open(path, "rb") as f: for chunk in iter(lambda: f.read(1 << 16), b""): h.update(chunk) return h.hexdigest() def root_hash(items): lines = "".join(f"{it['sha256']} {it['id']}\n" for it in sorted(items, key=lambda i: i["id"])) return hashlib.sha256(lines.encode("utf-8")).hexdigest() def main(): manifest_path = Path(sys.argv[1] if len(sys.argv) > 1 else "corpus/manifest.json") manifest = json.loads(manifest_path.read_text(encoding="utf-8")) base = manifest_path.parent items = manifest.get("items", []) bad = 0 for it in items: p = base / it["path"] if not p.is_file(): print(f"MISSING {it['id']} {p}") bad += 1 continue got = file_sha256(p) if got != it["sha256"]: print(f"MISMATCH {it['id']} expected {it['sha256']} got {got}") bad += 1 root = root_hash(items) root_ok = root == manifest.get("root_sha256") print(f"items checked: {len(items)} failures: {bad}") print(f"root sha256: {root} ({'matches' if root_ok else 'DOES NOT MATCH'} manifest)") print(f"manifest file: {file_sha256(manifest_path)}") sys.exit(0 if bad == 0 and root_ok else 1) if __name__ == "__main__": main()