1"""Writes provenance/checksums.sha256 (sha256sum format: "<hex> <relative path>") over the2artifact directories plan/, sources/, data/, code/, results/, manuscript/, review/ and the3top-level CLAUDE.md. Excluded: results/reproduction/ (rewritten by every run of reproduce.sh),4compiled binaries, __pycache__, .claude/ directories and the LaTeX build by-products that5tectonic rewrites on each build (main.aux/.log/.out/.bbl/.blg). Large DRAT proofs are included6(their digests also appear in the result records).78usage: python3 code/tools/make_checksums.py [--verify]9"""10import hashlib11import os12import sys1314ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))15DIRS = ["plan", "sources", "data", "code", "results", "manuscript", "review"]16EXCLUDE_PREFIX = ["results/reproduction/", "results/revision/run_fortcover_checks.pid", "results/revision/verify_full.pid",17 "results/revision/finalize.log", "results/revision/finalize_summary.json"]18EXCLUDE_NAMES = {"brute_force", "ff_brute", "beta_brute", "drat-trim", "ref_ff", ".DS_Store"}19EXCLUDE_SUFFIX = (".pyc", ".aux", ".log", ".out", ".blg", ".bbl", ".fls", ".fdb_latexmk", ".pid")20OUT = os.path.join(ROOT, "provenance", "checksums.sha256")212223def sha256(path):24 h = hashlib.sha256()25 with open(path, "rb") as f:26 for chunk in iter(lambda: f.read(1 << 24), b""):27 h.update(chunk)28 return h.hexdigest()293031def listing():32 files = ["CLAUDE.md"]33 for d in DIRS:34 for dp, dns, fns in os.walk(os.path.join(ROOT, d)):35 dns[:] = sorted(x for x in dns if x not in ("__pycache__", ".claude"))36 for fn in sorted(fns):37 rel = os.path.relpath(os.path.join(dp, fn), ROOT)38 if any(rel.startswith(p) for p in EXCLUDE_PREFIX) or fn in EXCLUDE_NAMES or rel.endswith(EXCLUDE_SUFFIX):39 continue40 if os.path.islink(os.path.join(dp, fn)):41 continue42 files.append(rel)43 return files444546def main():47 if "--verify" in sys.argv:48 bad = 049 n = 050 for line in open(OUT):51 h, rel = line.rstrip("\n").split(" ", 1)52 n += 153 if not os.path.exists(os.path.join(ROOT, rel)) or sha256(os.path.join(ROOT, rel)) != h:54 bad += 155 print("MISMATCH", rel)56 print("checked %d files, %d mismatches" % (n, bad))57 sys.exit(1 if bad else 0)58 files = listing()59 with open(OUT, "w") as f:60 for rel in files:61 f.write("%s %s\n" % (sha256(os.path.join(ROOT, rel)), rel))62 print("wrote %s with %d entries" % (os.path.relpath(OUT, ROOT), len(files)))636465if __name__ == "__main__":66 main()67