diff --git a/.gitignore b/.gitignore index 52cd9fb..d1e02de 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,22 @@ __pycache__/ # macOS Finder droppings .DS_Store + +# git-lfs shims in the GLOBAL hook directory (REVIEWED-131 / PENDING-165 option (b)) +# git-lfs installs these into whatever core.hooksPath names; here that is the hook +# directory shared by every repo on this machine. On 2026-03-20 a routine `git add` +# captured them (066a47a) and they sat TRACKED for four weeks, executing everywhere and +# indistinguishable from hooks the steward wrote. Ignoring them stops that capture. +# +# ⚠ DELIBERATELY NARROW — these four names only, not `git/hooks/*` with an allowlist. +# A blanket ignore would silently prevent committing a NEW legitimate hook: it would work +# locally, never reach the repo, and the allowlist check cannot see that (it reads the +# filesystem, not the index). A narrow ignore trades away nothing. +# +# Visibility is NOT lost by this: governance-drift-check.py declares the directory's +# contents and reports anything unexpected, tracked-ness not consulted. That check is +# what makes ignoring safe — do not ignore more than this without extending it. +git/hooks/pre-push +git/hooks/post-checkout +git/hooks/post-commit +git/hooks/post-merge diff --git a/claude/memory/session-ledger-2026-08-26.md b/claude/memory/session-ledger-2026-08-26.md index 436c450..3f7c00f 100644 --- a/claude/memory/session-ledger-2026-08-26.md +++ b/claude/memory/session-ledger-2026-08-26.md @@ -40,6 +40,16 @@ type: feedback ignoring them would hide the pollution instead of surfacing it. ⚠ Care did not prevent the second occurrence; I had cleaned the first one two hours earlier. +- 2026-08-26T~night — ⚠ **FIFTH self-referential instrument event, and it was in the fix + for the class.** The new hook-allowlist controls were appended AFTER `failed_controls` is + computed (line 702 vs 846), so all five ran, none was counted — the tally still read + `48/48` — and **a failure among them would have printed nothing.** I built the check + against *blind checks* with a check blind to itself, in the same hour, immediately after + the jurist caught the previous blindness in the same item. Caught only because I compared + the printed tally against the number of controls I knew I had added. Fixed by moving the + section above the report block (`53/53`), then **verified by deliberately breaking one new + control and confirming it now prints INSTRUMENT NOT VERIFIED and names itself.** + ## What held - Ran `thread-query.py` on the actual thread and reported the **null honestly** (689 candidates, diff --git a/scripts/governance-drift-check.py b/scripts/governance-drift-check.py index b290f14..33c8e48 100755 --- a/scripts/governance-drift-check.py +++ b/scripts/governance-drift-check.py @@ -22,6 +22,7 @@ import re import signal import subprocess import sys +import tempfile from datetime import date from pathlib import Path @@ -697,6 +698,58 @@ except Exception: pass # git absent is not a check failure; the fixtures still ran +# --------------------------- N. the global hook directory (REVIEWED-131 / PENDING-165) +# git-lfs installs four shims — pre-push, post-checkout, post-commit, post-merge — into +# whatever core.hooksPath names. Here that is the GLOBAL hook directory for 37 repos, and +# no human is in the invocation path. +# +# ⚠ THIS CHECK DELIBERATELY DOES NOT TEST TRACKED-NESS, and that is the whole correction. +# The first filed form of it reported files that were "neither tracked nor pre-commit". +# The one occurrence that did damage — 066a47a, 2026-03-20 — was TRACKED: a routine +# `git add` captured the shims and they sat in the tracked hook path for four weeks, +# executing on every push/checkout/commit/merge in every repo. `not tracked` was false +# for that entire period, so the check would have been silent throughout the only +# occurrence that mattered. It saw DEPOSIT and not CAPTURE, and capture is the laundering. +# Caught by the jurist before the check was built (PENDING-165 AMENDMENT 1). +# +# So: declare the contents instead. Anything unexpected is a finding, tracked or not, and +# anything DECLARED-BUT-MISSING is also a finding — REVIEWED-105 §2 measured that an +# absent hook file produces zero output rather than an ambiguous silence, so absence must +# be asserted rather than inferred from quiet. +HOOKS_DIR = HOME / "dotfiles/git/hooks" +HOOKS_ALLOWED = {"README.md", "pre-commit"} + + +def scan_hooks(d): + """(unexpected, missing) for a hook directory. Consults the FILESYSTEM only — + never git, never the index. See the note above for why tracked-ness is excluded.""" + present = {f.name for f in d.iterdir() if f.is_file()} + return sorted(present - HOOKS_ALLOWED), sorted(HOOKS_ALLOWED - present) + + +with tempfile.TemporaryDirectory() as _td: + _t = Path(_td) + (_t / "README.md").touch() + (_t / "pre-commit").touch() + control("hook allowlist: an exactly-conforming directory yields no finding", + scan_hooks(_t) == ([], [])) + (_t / "post-commit").touch() + control("hook allowlist: an unexpected file IS detected [the deposit case]", + scan_hooks(_t) == (["post-commit"], [])) + (_t / "pre-commit").unlink() + control("hook allowlist: a DECLARED-BUT-MISSING file IS detected [negative control — " + "REVIEWED-105 §2: an absent hook is silent, not obviously broken]", + scan_hooks(_t) == (["post-commit"], ["pre-commit"])) +# Structural, not behavioural: the correction is that tracked-ness is never consulted, and +# a prose comment saying so is not a check. This asserts it of the code itself. +control("hook allowlist: the scan never consults git [the tracked/untracked test is the " + "defect this replaced — 066a47a was TRACKED]", + "git" not in scan_hooks.__code__.co_names + and "subprocess" not in scan_hooks.__code__.co_names) +control("hook allowlist: the real hook directory is reachable", HOOKS_DIR.is_dir()) + +hook_unexpected, hook_missing = (scan_hooks(HOOKS_DIR) if HOOKS_DIR.is_dir() else (None, None)) + # ------------------------------------------------------------- report failed_controls = [lbl for lbl, ok in controls if not ok] if failed_controls: @@ -809,6 +862,7 @@ else: print(" ~57 candidate negative-state claims are unmarked and unread (a grep, not a") print(" census). Adoption is the open question, not expressibility (PENDING-158).") + if dangling: print(f"\n⚠ register integrity: {len(dangling)} resolved block(s) name no pointer " f"that resolves") @@ -816,4 +870,22 @@ if dangling: print(f" {_d['slug']} — resolved: {_d['resolved'] or '(empty)'}") print(" A discharge recording THAT a gate closed but not WHAT closed it is no record.") +if hook_unexpected is None: + print(f"\n— hook directory: CANNOT ASSESS — {HOOKS_DIR} unreachable.") + print(" Not a pass and not a finding. Nothing was checked.") +elif hook_unexpected or hook_missing: + print(f"\n⚠ hook directory: {len(hook_unexpected)} unexpected, " + f"{len(hook_missing)} declared-but-missing") + for _f in hook_unexpected: + print(f" UNEXPECTED {_f}") + for _f in hook_missing: + print(f" MISSING {_f}") + print(" ~/dotfiles/git/hooks is global to every repo. An unexpected file here executes") + print(" everywhere; git-lfs deposits four shims and a routine `git add` can capture them") + print(" as though governed (066a47a sat tracked for four weeks). Tracked-ness is not") + print(" consulted here on purpose — see PENDING-165.") +else: + print(f"✓ hook directory: exactly the declared contents " + f"({', '.join(sorted(HOOKS_ALLOWED))})") + sys.exit(0)