[FIX] Resolve the STATE-CLAIM; [HARDENING] file PENDING-163; add the preservation instrument

STATE-CLAIM: memory-index-claims-reviewed-127-unplaced -> resolved, pointing at
7a92460. Verified end to end by re-running governance-drift-check.py rather than
trusting the write: "1 of 3 open are NOW FALSE" -> "2 tracked, none falsified /
plus 1 RESOLVED", no dangling-pointer defect, so the resolution parses AND its
pointer resolves. The `resolved:` form was derived from the parser, not from
memory of the schema, which is also why the correction commit had to come first.

What that discharge is evidence for is written into the item so it cannot be
quoted as more: one marked claim, marked by its own author, corrected in the
immediately following session. Expressibility, not adoption. The 57 unmarked
claims are untouched.

PENDING-163 [HARDENING]: the global pre-commit hook refuses files over 5MB and
prints "Consider using Git LFS", but measures `wc -c < "$file"` — working-tree
size — so an LFS-tracked file stages as a ~130-byte pointer and is still refused.
Tried it; same refusal, same file. The hook is NOT modified: it is global and
governed by REVIEWED-100/105.

preserve-transcripts.py: PENDING-147 option (i). The archive itself is NOT in this
repo — 115MB of transcripts is not dotfiles material, which is what the hook was
right about even though its reasoning measures the wrong thing. It lives at
~/_Dev/claude-transcript-archive, outside the harness's pruned path, which is what
actually stops the clock. 43 transcripts, read-back PASS.

No guard was bypassed: no --no-verify, no per-repo core.hooksPath override, and no
empty .git left behind that would make the archive look tracked when it is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvZAKSf9aqratbqHbU9LK5
This commit is contained in:
David F Glidden
2026-08-26 17:26:22 +02:00
co-authored by Claude Opus 5
parent 7a92460a60
commit 57f83740a5
4 changed files with 321 additions and 4 deletions
+183
View File
@@ -0,0 +1,183 @@
#!/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())