#!/usr/bin/env python3 """Preserve Claude Code session transcripts out of the harness's pruned directory. WHY THIS EXISTS ~/.claude/projects/-Users-davidglidden is pruned on a ~30-day retention policy (cleanupPeriodDays default 30). PENDING-147 established that the ladder trial's evidence therefore sits on a deletion clock nobody set: the pre-registered 20-session falsifier in REVIEWED-95 Q6 cannot be graded if its earliest cohort has been deleted before the 20 sessions have run. This copies transcripts to a git-tracked location so the population stops shrinking. It is PENDING-147 option (i), and ONLY option (i): copying files "changes no instrument, no doctrine and no ladder" and needs no ruling. WHAT THIS DELIBERATELY DOES NOT DO It does NOT re-express governance-drift-check.py's `transcripts N` trigger over the preserved set. That would change an instrument a placed ruling leans on, inside a trial under the REVIEWED-123 freeze, and is not covered by the no-ruling justification above. The preserved set is inert with respect to the trigger until someone with the authority rules on it. IDEMPOTENT Safe to re-run. New transcripts are added; transcripts whose source has grown (they are append-only) are refreshed; already-preserved files are re-verified. Preserved files are never deleted here, even when the source is pruned — that is the entire point. INTEGRITY Preservation is proved by READING BACK, not by copy success. Every preserved file is re-hashed from disk after the copy and compared to the manifest. A write that "succeeded" into an unreadable file is the failure mode this guards (steward rule 2026-05-04, learned on MemPalace). """ import hashlib import json import os import shutil import sys import tempfile from datetime import datetime, timezone from pathlib import Path HOME = Path.home() SRC = HOME / ".claude/projects/-Users-davidglidden" DST = HOME / "_Dev/claude-transcript-archive" MANIFEST = DST / "manifest.json" def sha256(path: Path) -> str: h = hashlib.sha256() with open(path, "rb") as f: for block in iter(lambda: f.read(1 << 20), b""): h.update(block) return h.hexdigest() def iso(ts: float) -> str: return datetime.fromtimestamp(ts, timezone.utc).isoformat(timespec="seconds") def now_iso() -> str: return datetime.now(timezone.utc).isoformat(timespec="seconds") # ---------------------------------------------------------------- controls def run_controls() -> bool: """Same-run positive AND negative controls. An absence is not evidence until the instrument is shown capable of detecting presence (epistemic standard, jurist Q2 ruling). These run on EVERY invocation rather than in a separate suite, so a failure cannot be re-run until green. """ ok = True with tempfile.TemporaryDirectory() as td: td = Path(td) # positive: a known byte string hashes to its known digest p = td / "pos.bin" p.write_bytes(b"capablemind") expected = hashlib.sha256(b"capablemind").hexdigest() if sha256(p) != expected: print("CONTROL FAIL: hashing does not reproduce a known digest", file=sys.stderr) ok = False # negative: a corrupted copy MUST be detected as differing a, b = td / "a.bin", td / "b.bin" a.write_bytes(b"x" * 4096) shutil.copy2(a, b) if sha256(a) != sha256(b): print("CONTROL FAIL: identical copy reported as differing", file=sys.stderr) ok = False with open(b, "r+b") as f: # flip one byte f.seek(2048) f.write(b"y") if sha256(a) == sha256(b): print("CONTROL FAIL: one-byte corruption NOT detected", file=sys.stderr) ok = False # negative: mtime preservation must actually preserve old = 1_600_000_000 os.utime(a, (old, old)) c = td / "c.bin" shutil.copy2(a, c) if abs(c.stat().st_mtime - old) > 1: print("CONTROL FAIL: copy2 did not preserve mtime", file=sys.stderr) ok = False return ok def main() -> int: if not run_controls(): print("INSTRUMENT NOT VERIFIED — controls failed; result is unestablished.", file=sys.stderr) return 2 if not SRC.is_dir(): print(f"source directory absent: {SRC}", file=sys.stderr) return 2 DST.mkdir(parents=True, exist_ok=True) manifest = json.loads(MANIFEST.read_text()) if MANIFEST.exists() else {"files": {}} files = manifest.setdefault("files", {}) added, refreshed, unchanged = [], [], [] for src in sorted(SRC.glob("*.jsonl")): digest = sha256(src) rec = files.get(src.name) dst = DST / src.name if rec and rec["sha256"] == digest and dst.exists(): unchanged.append(src.name) continue shutil.copy2(src, dst) # -p: mtime is load-bearing here entry = { "sha256": digest, "bytes": src.stat().st_size, "source_mtime": iso(src.stat().st_mtime), "first_preserved": (rec or {}).get("first_preserved") or now_iso(), "last_refreshed": now_iso(), } (refreshed if rec else added).append(src.name) files[src.name] = entry # ---- READ BACK. Preservation is proved from disk, never from copy success. failures, orphaned = [], [] for name, rec in files.items(): p = DST / name if not p.exists(): failures.append(f"{name}: MISSING from preserved set") continue if sha256(p) != rec["sha256"]: failures.append(f"{name}: HASH MISMATCH on read-back") if not (SRC / name).exists(): orphaned.append(name) # pruned at source — preserved here. The point. manifest["last_run"] = now_iso() manifest["readback"] = "PASS" if not failures else "FAIL" manifest["counts"] = { "preserved_total": len(files), "present_at_source": len(files) - len(orphaned), "pruned_at_source_but_preserved": len(orphaned), } MANIFEST.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") total = sum(r["bytes"] for r in files.values()) print(f"preserved total : {len(files)} transcripts, {total/1048576:.1f} MB") print(f" newly added : {len(added)}") print(f" refreshed : {len(refreshed)} (append-only growth at source)") print(f" unchanged : {len(unchanged)}") print(f" pruned at source, surviving only here : {len(orphaned)}") print(f"N-now at source (the trial's own counter): " f"{len(list(SRC.glob('*.jsonl')))}") if failures: print("\nREAD-BACK FAILURES:", file=sys.stderr) for f in failures: print(" " + f, file=sys.stderr) return 1 print("read-back : PASS (every preserved file re-hashed from disk)") return 0 if __name__ == "__main__": sys.exit(main())