From 57f83740a55bdc4b00777095016e02035350cded Mon Sep 17 00:00:00 2001 From: David F Glidden Date: Wed, 26 Aug 2026 17:26:22 +0200 Subject: [PATCH] [FIX] Resolve the STATE-CLAIM; [HARDENING] file PENDING-163; add the preservation instrument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01NvZAKSf9aqratbqHbU9LK5 --- PENDING.md | 50 +++++- claude/memory/MEMORY.md | 2 +- claude/memory/session-ledger-2026-08-26.md | 90 ++++++++++ scripts/preserve-transcripts.py | 183 +++++++++++++++++++++ 4 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 claude/memory/session-ledger-2026-08-26.md create mode 100755 scripts/preserve-transcripts.py diff --git a/PENDING.md b/PENDING.md index 9230993..1c1c5cc 100644 --- a/PENDING.md +++ b/PENDING.md @@ -5461,10 +5461,19 @@ on the coverage inversion this exposes.** + falsified-by: text-present REVIEWED.md ## REVIEWED-127 — PENDING-157 + PENDING-158 + resolved: 2026-08-26 — commit 7a92460 corrected the line in MEMORY-reference.md, struck rather than deleted so instance six keeps its evidence --> -**Awaiting:** nothing. Discharged by correcting the MEMORY.md line at the next wrap or wake, then -setting `resolved:` with a pointer to the commit that did it. +**Awaiting:** nothing — **DISCHARGED 2026-08-26**, as the agreed first act of the next session. +The line was corrected in `7a92460` and struck rather than deleted, because it is the evidence for +instance six and a silent deletion removes the error and the record of it together. + +⚠ **What this discharge is evidence FOR, stated before it gets quoted as more.** The marker worked: +it reported FALSIFIED at every wake until someone acted, and the correction happened in the first +session after it was filed. That is one instance, on a claim its own author marked, in the session +immediately following — the easiest possible case. **It bears on expressibility, not on adoption, +and adoption is the open question REVIEWED-127 C2 named.** The 57 unmarked claims are untouched by +this and nothing here says otherwise. --- @@ -5809,3 +5818,38 @@ central finding is that care is not a mechanism. **Files affected:** none. **Awaiting:** steward and jurist. ⚠ **The 09-08 read should not be run as though clean until this is ruled.** + +--- + +## PENDING-163 — The pre-commit size guard measures the working tree, so the remedy it prints cannot satisfy it +**Date:** 2026-08-26 +**Tag:** [HARDENING] +**Summary:** The global pre-commit hook refuses any staged file over 5 MB and prints *"Consider using Git LFS for large files."* The check reads `wc -c < "$file"` — the **working-tree** size — so an LFS-tracked file, which stages as a ~130-byte pointer, still measures 17 MB and is still refused. Following the guard's own instruction does not clear the guard. + +**Measured, not inferred** (2026-08-26, while preserving transcripts under PENDING-147): +| | | +|---|---| +| hook | `~/dotfiles/git/hooks/pre-commit`, global via `core.hooksPath` | +| the check | `for file in $(git diff --cached --name-only); … size=$(wc -c < "$file"); if [ $size -gt 5242880 ]` | +| the advice | `echo "Consider using Git LFS for large files"` | +| tried | `git lfs install --local` + `git lfs track "*.jsonl"` + `git add -A` → **same refusal, same file** | +| `git-lfs` present | 3.7.1, and already declared in `Brewfile:40` — so this is not an unavailable remedy | + +**Why this is a defect and not the rule working.** ⚠ Filed with the 2026-08-25 flag deliberately applied first — *filed a non-defect as a defect, twice in one direction in one day* — so the working-as-intended reading is stated before the defect reading: + +- **Working-as-intended reading:** the ceiling means *"no large files in the working tree of any repo, LFS or not"*, and the LFS line is merely a pointer to a different workflow, not a promise. +- **Against it:** the LFS line is printed **by the failing branch, as its remediation**, immediately after the error. A remedy printed at the point of refusal is a claim that it resolves the refusal. And LFS *does* serve the check's evident purpose — repository bloat — because the committed blob is a pointer; it is only the implementation, working-tree size, that LFS cannot change. + +**So the narrow claim, and it is the only one made here:** the check and its printed advice disagree. Either the advice is wrong and should be removed or reworded, or the check should measure the staged blob (`git cat-file -s :"$file"`) so LFS actually clears it. **Which of those is correct is a policy question, not a bug fix** — it decides whether large files may enter these repos at all. + +**⚠ Not fixed, and deliberately.** This hook is global to every repo and is governed by REVIEWED-100 and REVIEWED-105; its own doctrine is that *a disarmed hook must not look like an armed one*. Changing what it measures changes what it permits everywhere at once. It was also not bypassed: no `--no-verify`, no per-repo `core.hooksPath` override. + +**Options:** +- **(i) Reword the advice** to say the ceiling applies to the working tree and LFS does not exempt it. Smallest change; keeps current permissions exactly. +- **(ii) Measure the staged blob** (`git cat-file -s :"$file"`), so LFS-tracked files pass. Makes the printed advice true; **widens what may be committed everywhere**, which is the part needing a ruling. +- **(iii) Per-repo declaration** — let a repo opt out of the ceiling via the existing `.precommit-triggers` mechanism REVIEWED-100 already established, rather than a global change. + +**Recommendation: (i) now, (iii) if a large-file repo is actually wanted.** (i) costs nothing and stops the guard from giving advice that fails; (ii) is the one that changes policy and should not ride in on a wording fix. ⚠ **No option here is urgent** — nothing is currently blocked by this. The transcript preservation it surfaced during is complete on disk and needs no commit to be safe. + +**Files affected:** `~/dotfiles/git/hooks/pre-commit` (not modified). +**Awaiting:** Steward authorization. diff --git a/claude/memory/MEMORY.md b/claude/memory/MEMORY.md index 2e0026b..1335be9 100644 --- a/claude/memory/MEMORY.md +++ b/claude/memory/MEMORY.md @@ -79,7 +79,7 @@ permalink: claude-memory/memory > ⚠ **THE WRAP SEAM FAILED, WAS DIAGNOSED, FIXED, FIRED, AND WAS THEN REJECTED — all after the wrap.** The heartbeat proved the `Stop` hook was firing all along, so the silent-net prediction was **right that it would fail and wrong about why**. The detector sought the steward *typing* `/wrap-up`; the wrap arrived as prose + a Skill call. ⚠ **The earlier, correct fix is what blinded it.** PENDING-160's sixth and sharpest instance. > ⚠ **PENDING-162 `[ESCALATE]` — the executor breached REVIEWED-128 condition 3** within seven hours, reading the rejection log for content while diagnosing. **Conditions 1 and 2 were made structural; only 3 was left to care.** ⚠ It surfaced the clustering signal (2 seam rejections, 10 and 11 words vs a cap of 9) that the fortnight was meant to arbitrate — **not acted on, and must not be.** > 📌 **STEWARD OWES:** place the REVIEWED-129 amendment · rule PENDING-160 · **restart Claude Desktop** or `governance_pair` is absent · the §5 regrade ruling. -> ⚠ **DELIBERATELY NOT FIXED:** the `STEWARD OWES: place REVIEWED-127` line now sits in `MEMORY-reference.md` with this block's predecessor. It is **false**, it is **marked** by `STATE-CLAIM: memory-index-claims-reviewed-127-unplaced`, and correcting it is **next session's agreed first act** — carried forward rather than discharged as a side effect of this rotation. +> ✅ **DISCHARGED 2026-08-26 — the agreed first act, done.** The false `STEWARD OWES: place REVIEWED-127` line in `MEMORY-reference.md` is corrected (`7a92460`), **struck rather than deleted** so instance six keeps its evidence, and the `STATE-CLAIM` block carries `resolved:` with that pointer. ⚠ **This is one marked claim corrected in the very next session — evidence for expressibility, NOT for adoption**, which is the open question (REVIEWED-127 C2). The 57 unmarked claims are untouched. - [Session 2026-08-25 evening — Tarbuckle wired, and the gap controls cannot see](session-2026-08-25-tarbuckle-wired-and-the-contact-gap.md) — five surfaces built and every one failed on first real use; PENDING-151 step 1 + step 2 both run. **NEXT: the §5 regrade gate — the control is already degraded and decays with every exposure.** diff --git a/claude/memory/session-ledger-2026-08-26.md b/claude/memory/session-ledger-2026-08-26.md new file mode 100644 index 0000000..208810d --- /dev/null +++ b/claude/memory/session-ledger-2026-08-26.md @@ -0,0 +1,90 @@ +--- +name: Session Ledger 2026-08-26 +description: Practice-of-return ledger maintained by /symmetria — returns, open horizons, recalibrations, authorization moves, sub-agent dialogues, bypasses. +type: feedback +--- + +# Session Ledger — 2026-08-26 + +## Returns + +- 2026-08-26T~wake — `/wake-up` ran; Symmetria `init` at its close (the clasp's lineage hand). + N-now **re-measured, not relayed**: **43**, down from 51 on 08-25. The obligation found it, + not the vigilance — same shape as PENDING-147's own disclosure. + +- 2026-08-26T~afternoon — **Chose the wrong container and the guard caught it.** Put the + archive in `~/dotfiles` by habit, because the governance record lives there, without asking + whether 115 MB of raw transcripts is dotfiles material. The pre-commit hook refused it. The + return was reading the refusal as a **fit signal** rather than an obstacle — τὸ πρόσφορον — + and moving the archive rather than moving the guard. +- 2026-08-26T~afternoon — **Three chances to bypass a guard, none taken:** `--no-verify`, a + per-repo `core.hooksPath` override, and leaving a `.git` with no commits behind (which would + have made the archive *look* tracked). ⚠ The third is the one I nearly did by inertia; it is + the same shape as the hook's own doctrine that *a disarmed hook must not look like an armed one*. + +## What held + +- Ran `thread-query.py` on the actual thread and reported the **null honestly** (689 candidates, + nothing on-thread). A step that can only conclude "keep it" is not a trial. +- Did **not** run the literal question's grep at wake. The question is held open, per the wake's + own constraint; and `tarbuckle-rejects.jsonl` was not opened — REVIEWED-128 condition 3. +- Substrate-checked before calling anything outstanding: PENDING-147 (i) verified **unbuilt** + (no `claude/governance/transcripts*`, no git-tracked copy, no REVIEWED entry) rather than + read off its own recommendation line. + +- Verified the STATE-CLAIM resolution **end-to-end by re-running the instrument**, not by + trusting the write: `1 of 3 open are NOW FALSE` → `2 tracked, none falsified / plus 1 RESOLVED`, + with no dangling-pointer defect, so both the `resolved:` parse and its pointer were proved. +- Derived the `resolved:` form **from the parser** (`governance-drift-check.py:299,486,498`), + not from memory of the schema — and therefore made the correction commit FIRST, because + `pointer_resolves()` requires a commit that already exists. The say–do seam, avoided by ordering. +- Applied the *filed-a-non-defect-as-a-defect* flag **before** filing PENDING-163: stated the + working-as-intended reading first, then the argument against it, and kept the claim narrow + (the check and its printed advice disagree) rather than "the hook is broken". +- Preserved **all 43** transcripts rather than the 23 the item scoped, because the files expiring + soonest are the ones outside its scope — and said so in the README rather than letting the + wider action pass unremarked. + +## Open horizons + +1. **Agreed first act:** correct the false `STEWARD OWES: place REVIEWED-127` line now in + `MEMORY-reference.md`; set `resolved:` on the `STATE-CLAIM` block in `PENDING.md` with a + commit pointer. Carried deliberately across the rotation rather than discharged by side effect. +2. **PENDING-147 (i) — preserve the post-2026-08-07 transcripts.** Needs no ruling by its own + text. Earliest cohort deletes **2026-09-06**. ⚠ The only open item whose cost rises daily, + and N-now falling 51→43 in one day is that deletion happening in view. +2b. ⚠ **The archive has NO BACKUP.** Preserved from pruning; lost to a disk failure. The + steward's call: leave it, add a remote (a disclosure question — raw transcripts are more + verbose than the session memories that already reach `git.skemantix.com`), or Time Machine + it. **Cheapest fix for the actual gap is not git.** +2c. **PENDING-163** filed — the pre-commit hook's 5 MB check reads working-tree size, so the + Git LFS remedy it prints cannot satisfy it. Not urgent; nothing is blocked. +3. **The §5 regrade gate** — steward's and judge's to settle, not the executor's. The honest + outcome may be to record the control as degraded rather than run it and call it a control. +4. Steward-owed: place the REVIEWED-129 amendment · rule PENDING-160 · restart Claude Desktop. +5. Dated, 2026-09-08: two obligations in one sitting — mumble rate report + rejection log deletion. + +## Confidence to recalibrate + +- ⚠ Yesterday's finding is still the governing caution: **controls verify the code, not the + contact.** Anything built today is untested at the seam with a model, a shell, or a corpus + containing its own reader — and no control written today will see that either. +- ⚠ **Care failed inside a day** on REVIEWED-128 condition 3 (PENDING-162). Conditions made + structural held; the one left to care did not. Prefer a mechanism over a resolution today. +- ⚠ Bias observed twice on 08-25: **filing a non-defect as a defect.** Before calling something + broken, ask whether it is the rule working. + +## Authorization moves + +- **PENDING-147 option (i) executed WITHOUT a ruling**, on the item's own text — *"copying files + preserves evidence and changes no instrument, no doctrine and no ladder."* ⚠ **The option + bundles a second act the same sentence does not license:** *"re-express the trigger over the + preserved set"* changes `governance-drift-check.py`, which REVIEWED-95's falsifier leans on, + inside a trial under the REVIEWED-123 freeze. **Split; only the copy was done.** The archive is + currently inert with respect to every governance gate, and its README says so on its face. +- **PENDING-163 filed `[HARDENING]`, hook NOT modified.** It is global to every repo and governed + by REVIEWED-100 / REVIEWED-105; changing what it measures changes what it permits everywhere. + +## Sub-agent dialogues + +## Bypasses diff --git a/scripts/preserve-transcripts.py b/scripts/preserve-transcripts.py new file mode 100755 index 0000000..174df50 --- /dev/null +++ b/scripts/preserve-transcripts.py @@ -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())