SPB Git

spb/wp29_uqo

Public
4.7 KB · 65 lines python
1"""Release validation: (1) build the release manifest (SHA-256 of every released file with provenance class), (2) replay the headline analysis in a2clean temporary copy of the repository (artefact replay mode: no provider access) and compare the regenerated estimates with the committed ones,3(3) scan released text for credentials, (4) check the PDF is non-empty and searchable. Usage: .venv/bin/python release/validate_release.py"""4import os, sys, json, hashlib, shutil, subprocess, tempfile, re, datetime5REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")); PY = os.path.join(REPO, ".venv", "bin", "python")6def sha(p):7    h = hashlib.sha256()8    with open(p, "rb") as fh:9        for c in iter(lambda: fh.read(1 << 20), b""): h.update(c)10    return h.hexdigest()11INCLUDE = ["protocol", "research", "configs", "tasks", "src", "evaluators", "tests", "analysis", "results", "reports", "paper/main.tex", "paper/references.bib", "paper/sections", "paper/appendices", "paper/figures", "paper/tables", "paper/main.pdf", "evidence/manifests", "evidence/public_screenshots", "runs/state.sqlite", "README.md", "Makefile", "pyproject.toml", "CLAUDE.md"]12EXCLUDE_RE = re.compile(r"(__pycache__|\.pyc$|raw_stream\.jsonl$|/sessions/|evidence/restricted|\.venv|\.aux$|\.log$|\.out$|\.fls$|\.fdb_latexmk$|\.bbl$|\.blg$|\.synctex)")13def manifest():14    files = {}15    for inc in INCLUDE:16        p = os.path.join(REPO, inc)17        if os.path.isfile(p): files[inc] = sha(p)18        elif os.path.isdir(p):19            for root, _, fs in os.walk(p):20                for f in fs:21                    q = os.path.join(root, f); rel = os.path.relpath(q, REPO)22                    if not EXCLUDE_RE.search(rel): files[rel] = sha(q)23    return files24def replay():25    tmp = tempfile.mkdtemp(prefix="wp29replay_")26    for d in ["analysis", "src", "results", "runs", "tasks", "protocol", "configs", "reports", "evaluators"]:27        src = os.path.join(REPO, d)28        if d == "runs":29            os.makedirs(os.path.join(tmp, "runs")); shutil.copy(os.path.join(src, "state.sqlite"), os.path.join(tmp, "runs", "state.sqlite"))30        else: shutil.copytree(src, os.path.join(tmp, d), ignore=shutil.ignore_patterns("__pycache__", "raw_stream.jsonl", "sessions"))31    os.symlink(os.path.join(REPO, ".venv"), os.path.join(tmp, ".venv"))32    before = {}33    for f in ["confirmatory_arm_table.csv", "confirmatory_contrasts.csv", "confirmatory_family_table.csv"]:34        p = os.path.join(REPO, "results", "estimates", f)35        if os.path.exists(p): before[f] = open(p).read()36    env = dict(os.environ, PYTHONPATH=os.path.join(tmp, "src"))37    for script in ["build_analysis_dataset.py", "primary_analysis.py", "robustness.py", "tables.py", "figures.py"]:38        r = subprocess.run([PY, os.path.join(tmp, "analysis", script)], cwd=tmp, capture_output=True, text=True, env=env, timeout=1800)39        if r.returncode != 0: return {"ok": False, "script": script, "stderr": r.stderr[-800:]}40    diffs = {}41    for f, b in before.items():42        a = open(os.path.join(tmp, "results", "estimates", f)).read()43        diffs[f] = "identical" if a == b else "DIFFERENT"44    shutil.rmtree(tmp, ignore_errors=True)45    return {"ok": all(v == "identical" for v in diffs.values()), "files": diffs}46def secret_scan():47    hits = []48    for rel in manifest():49        p = os.path.join(REPO, rel)50        if p.endswith((".pdf", ".png", ".sqlite")): continue51        try: t = open(p, errors="ignore").read()52        except Exception: continue53        if re.search(r"sk-ant-[A-Za-z0-9_-]{20,}", t): hits.append(rel)54    return hits55def main():56    man = manifest(); rep = replay(); hits = secret_scan()57    pdf = os.path.join(REPO, "paper", "main.pdf"); pdf_ok = os.path.exists(pdf) and os.path.getsize(pdf) > 10000058    txt = subprocess.run(["pdftotext", "-l", "3", pdf, "-"], capture_output=True, text=True).stdout if pdf_ok else ""59    out = {"validated_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds"), "n_files": len(man), "replay": rep, "secret_hits": hits, "pdf_nonempty": pdf_ok, "pdf_searchable": "Working Paper" in txt,60           "git_commit": subprocess.run(["git", "-C", REPO, "rev-parse", "HEAD"], capture_output=True, text=True).stdout.strip(), "exclusions": "raw_stream.jsonl, session transcripts and evidence/restricted are excluded from the public release (authorized raw traces kept locally); hidden oracles live outside the repository (~/wp29-oracles/) and are released separately as WP29_Oracles.zip to reviewers only"}61    json.dump({"validation": out, "files": man}, open(os.path.join(REPO, "release", "release_manifest.json"), "w"), indent=1)62    print(json.dumps(out, indent=1))63    return 0 if (rep.get("ok") and not hits and pdf_ok) else 164if __name__ == "__main__": sys.exit(main())65
Files · 152a55b8fa2830f50fa35e5aed99c0037f713345
Menu

Theme