"""Freeze manifest: SHA-256 of every public bundle file, every oracle file, the arm texts, the harness files that affect outcomes, the preregistration and the judge prompt builder. Written before the pilot; any later change is an amendment. Usage: .venv/bin/python scripts/freeze.py --tasks ACC-01 ... --out protocol/preregistration/freeze_manifest.json""" from __future__ import annotations import argparse, hashlib, json, os, subprocess, sys, datetime as dt REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) ORACLES = os.path.expanduser("~/wp42-oracles") def sha(p): h = hashlib.sha256() with open(p, "rb") as f: for ch in iter(lambda: f.read(1 << 20), b""): h.update(ch) return h.hexdigest() def tree(root, skip=("__pycache__", ".DS_Store")): out = {} for dp, dns, fns in os.walk(root): dns[:] = [d for d in dns if d not in skip] for fn in sorted(fns): if fn in skip: continue p = os.path.join(dp, fn); out[os.path.relpath(p, root)] = sha(p) return out if __name__ == "__main__": ap = argparse.ArgumentParser(); ap.add_argument("--tasks", nargs="+", required=True); ap.add_argument("--out", default=os.path.join(REPO, "protocol", "preregistration", "freeze_manifest.json")) a = ap.parse_args() man = {"frozen_utc": dt.datetime.now(dt.timezone.utc).isoformat(), "git_head": subprocess.run(["git", "rev-parse", "HEAD"], cwd=REPO, capture_output=True, text=True).stdout.strip() or None, "scenarios": {}, "oracles": {}, "harness": {}, "protocol": {}} for t in a.tasks: man["scenarios"][t] = tree(os.path.join(REPO, "benchmark", "scenarios", t)) od = os.path.join(ORACLES, t); man["oracles"][t] = {k: v for k, v in tree(od).items() if not k.startswith("validation/")} for rel in ["src/wp42/arms.py", "src/wp42/launcher.py", "src/wp42/bundle.py", "src/wp42/scheduler.py", "src/wp42/judge.py", "src/wp42/evaluate.py", "src/wp42/metrics.py", "src/wp42/config.py", "src/wp42/portal.py", "src/wp42/office.py", "benchmark/tools/selfcheck.py", "benchmark/validators/common.py", "benchmark/BRIEF_FOOTER.md", "benchmark/COMPLETION_SCHEMA.json"]: man["harness"][rel] = sha(os.path.join(REPO, rel)) for rel in ["protocol/preregistration/PREREGISTRATION.md", "RESEARCH_PLAN.md", "analysis/econometrics/estimate.py"]: man["protocol"][rel] = sha(os.path.join(REPO, rel)) man["claude_code_controller"] = subprocess.run(["claude", "--version"], capture_output=True, text=True).stdout.strip() json.dump(man, open(a.out, "w"), indent=1); print("frozen", a.out, "scenarios:", len(man["scenarios"]))