Option (a) on conditions 1-4. The check read only REVIEWED.md, only `##`, and only headers starting with the literal word AMENDMENT. It now reads all three registers at `##` and `###`, recognises ADDENDUM, and computes `originals` across registers because a parent may be archived while its amendment stays open. Condition 1's default is inverted: a header is an ORIGINAL only if it carries an identifier and no marker. Compound headers are excluded from originals, not admitted to them, so an ADDENDUM can no longer satisfy "an un-amended entry exists" on behalf of a record that was replaced. Condition 3 did its job on my own code. Enumerating rather than counting returned 89 against the item's 82; the surplus was six prose titles — "Citation amendment (#2)", "Dream amendment" — matched by an upper-cased containment test and struck off `originals`, which is the mirror of the bug being fixed and would have raised false "the record was replaced" findings. Markers are now uppercase standalone tokens, with controls in both directions. The enumeration then agrees with the item's method at 82; the remaining +1 against the filed table is this session's own later filing, reported rather than reconciled away. ⚠ The widening is the floor, not the fix: 48 of the blocks carry no item number and are reported NOT ESTABLISHED, never passed. The prospective-convention question belongs to PENDING-146 and is deliberately not decided here (cond. 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wn9mxsFjHJkzYjuDdrtxwp
1002 lines
53 KiB
Python
Executable File
1002 lines
53 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
governance-drift-check — report state claims in ~/CLAUDE.md that the substrate contradicts.
|
|
|
|
Reports. Does not correct. Detection needs no authorization; correction does
|
|
(Constitutional Constraint #1). This exists so that a stale governance document
|
|
is *visible* rather than *misleading* — Constitutional Constraint #4, honest
|
|
degradation, applied to the governance document itself.
|
|
|
|
Every check carries a POSITIVE CONTROL in the same run: an absence is not
|
|
evidence until the instrument is shown capable of detecting presence.
|
|
(Ratified 2026-07-27 as an epistemic standard, jurist Q2.)
|
|
|
|
Built 2026-07-27 on steward authorization. Exit code is always 0 — this is a
|
|
report, not a gate.
|
|
"""
|
|
|
|
import datetime
|
|
import json
|
|
import os
|
|
import re
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
# Report cleanly when the reader closes early (`| head`), rather than tracebacking.
|
|
try:
|
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
except (AttributeError, ValueError): # not POSIX, or not on the main thread
|
|
pass
|
|
|
|
HOME = Path.home()
|
|
CLAUDE_MD = HOME / "dotfiles" / "CLAUDE.md"
|
|
MONTHS = ("january february march april may june july august september "
|
|
"october november december").split()
|
|
|
|
findings: list[str] = []
|
|
controls: list[tuple[str, bool]] = [] # (label, passed)
|
|
|
|
|
|
def control(label: str, passed: bool) -> bool:
|
|
controls.append((label, passed))
|
|
return passed
|
|
|
|
|
|
# ---------------------------------------------------------------- load
|
|
if not CLAUDE_MD.exists():
|
|
print(f"drift-check: CANNOT RUN — {CLAUDE_MD} not found")
|
|
sys.exit(0)
|
|
|
|
text = CLAUDE_MD.read_text()
|
|
lines = text.split("\n")
|
|
|
|
|
|
# ------------------------------------------------- 1. referenced paths
|
|
# Backticked paths that look like filesystem locations.
|
|
raw = set(re.findall(r"`(~[^`\s]+|/Users/[^`]+?)`", text))
|
|
paths = {p for p in raw if ("/" in p) and not p.endswith(("`",))}
|
|
|
|
control("path-check instrument reaches the filesystem", CLAUDE_MD.exists())
|
|
for p in sorted(paths):
|
|
expanded = Path(os.path.expanduser(p.strip().rstrip(".,;")))
|
|
if not expanded.exists():
|
|
ln = next((i + 1 for i, l in enumerate(lines) if p in l), None)
|
|
findings.append(f"L{ln}: path does not resolve — {p}")
|
|
|
|
|
|
# ------------------------------------- 2. named MCP tools / servers
|
|
# Tool names the document instructs the executor to call.
|
|
tool_names = set(re.findall(r"`(kg_\w+|diary_\w+|find_tunnels|traverse)`", text))
|
|
configured: set[str] = set()
|
|
for cfg in (HOME / ".claude/settings.json", HOME / ".claude/settings.local.json",
|
|
HOME / ".claude.json", HOME / ".mcp.json"):
|
|
if cfg.exists():
|
|
try:
|
|
configured |= set((json.loads(cfg.read_text()).get("mcpServers") or {}).keys())
|
|
except Exception:
|
|
pass
|
|
|
|
control("MCP config readable (>=1 server found somewhere)", bool(configured))
|
|
if tool_names and not any("mempal" in s.lower() for s in configured):
|
|
ln = next((i + 1 for i, l in enumerate(lines) if "kg_query" in l), None)
|
|
findings.append(
|
|
f"L{ln}: {len(tool_names)} named tools do not resolve — "
|
|
f"{', '.join(sorted(tool_names))} (no mempalace MCP server configured; "
|
|
f"servers present: {sorted(configured) or 'none'})"
|
|
)
|
|
|
|
|
|
# ------------------------------------------------- 3. hooks claimed to fire
|
|
hook_claims = re.findall(r"(Stop and PreCompact hooks fire|PreCompact hook)", text)
|
|
if hook_claims:
|
|
live: set[str] = set()
|
|
for cfg in (HOME / ".claude/settings.json", HOME / ".claude/settings.local.json"):
|
|
if cfg.exists():
|
|
try:
|
|
h = json.loads(cfg.read_text()).get("hooks") or {}
|
|
live |= {k for k, v in h.items() if v}
|
|
except Exception:
|
|
pass
|
|
control("hooks config readable (>=1 hook event configured)", bool(live))
|
|
missing = [e for e in ("Stop", "PreCompact") if e not in live]
|
|
if missing:
|
|
ln = next((i + 1 for i, l in enumerate(lines) if "PreCompact" in l), None)
|
|
findings.append(
|
|
f"L{ln}: document claims these hooks fire, but they are unconfigured — "
|
|
f"{', '.join(missing)} (configured: {sorted(live) or 'none'})"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------- 4. expired horizons
|
|
today = date.today()
|
|
horizon_re = re.compile(r"through\s+(?:the\s+)?end\s+of\s+(\w+)\s+(\d{4})", re.I)
|
|
control("horizon regex matches a known-present phrase",
|
|
bool(horizon_re.search(text)) or "through end of" not in text.lower())
|
|
for i, line in enumerate(lines, 1):
|
|
for m in horizon_re.finditer(line):
|
|
month, year = m.group(1).lower(), int(m.group(2))
|
|
if month in MONTHS:
|
|
mi = MONTHS.index(month) + 1
|
|
if (year, mi) < (today.year, today.month):
|
|
findings.append(
|
|
f"L{i}: horizon expired — \"{m.group(0)}\" "
|
|
f"(elapsed {(today.year - year) * 12 + today.month - mi} months)"
|
|
)
|
|
|
|
|
|
# ------------------------------------------------ 5. structural integrity
|
|
if not text.endswith("\n"):
|
|
findings.append(f"L{len(lines)}: no terminal newline "
|
|
f"(wc -l undercounts by 1 — breaks line-referenced patches)")
|
|
for i, line in enumerate(lines, 1):
|
|
# `||` inside a line carrying table pipes = two rows fused onto one line.
|
|
# Do NOT require the line to start with `|`: the real instance (L243) began
|
|
# mid-sentence, which is exactly how the fusion hides.
|
|
if "||" in line and line.count("|") >= 3:
|
|
findings.append(f"L{i}: table rows fused on one line (`||`) — row will not render")
|
|
if re.match(r"^\s+\|", line):
|
|
findings.append(f"L{i}: table row has stray leading whitespace — breaks the table")
|
|
if re.match(r"^#{2,4} .*\s+$", line) and i < len(lines) and lines[i].startswith("#"):
|
|
findings.append(f"L{i}: empty heading stub immediately followed by a heading "
|
|
f"— orphans the section beneath it")
|
|
|
|
|
|
# --------------------------------------------- 6. doctrine-ID integrity
|
|
# Doctrine units may carry a stable id in an HTML comment: `<!-- D:memory.check-first -->`.
|
|
# Invisible in prose, parseable by tools, and IN the canonical — so there is no second
|
|
# version to drift (L110). Skills cite an id instead of paraphrasing the rule; this
|
|
# section catches a citation whose doctrine has been reworded away, and duplicate ids.
|
|
# Dormant until the first id exists; the controls below run either way, so "nothing
|
|
# reported" means "checked and clean", not "never looked".
|
|
DOCTRINE_DEF = re.compile(r"<!--\s*(D:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*)\s*-->")
|
|
DOCTRINE_REF = re.compile(r"\b(D:[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*)\b")
|
|
|
|
defined: dict[str, int] = {}
|
|
for i, line in enumerate(lines, 1):
|
|
for did in DOCTRINE_DEF.findall(line):
|
|
if did in defined:
|
|
findings.append(f"L{i}: duplicate doctrine id {did} "
|
|
f"(also L{defined[did]}) — a citation cannot resolve it")
|
|
else:
|
|
defined[did] = i
|
|
|
|
# Citing surface: the skills, which are the intended consumers. Deliberately NOT
|
|
# PENDING.md — drafts there legitimately quote ids that do not exist yet.
|
|
skills_dir = HOME / ".claude" / "skills"
|
|
scanned = 0
|
|
if skills_dir.is_dir():
|
|
for f in sorted(skills_dir.rglob("*.md")):
|
|
try:
|
|
if f.stat().st_size > 200_000:
|
|
continue
|
|
body = f.read_text(errors="replace")
|
|
except OSError:
|
|
continue
|
|
scanned += 1
|
|
for ref in sorted(set(DOCTRINE_REF.findall(body))):
|
|
if ref not in defined:
|
|
findings.append(
|
|
f"{f.relative_to(HOME)}: cites doctrine id {ref}, which "
|
|
f"~/CLAUDE.md does not define — the rule was reworded or removed")
|
|
|
|
control("doctrine-id parser detects a definition",
|
|
bool(DOCTRINE_DEF.findall("x <!-- D:memory.check-first --> y")))
|
|
control("doctrine-id parser detects a citation",
|
|
DOCTRINE_REF.findall("see D:memory.check-first here") == ["D:memory.check-first"])
|
|
control("doctrine-id parser rejects a non-id",
|
|
not DOCTRINE_DEF.findall("<!-- D:nodot -->")
|
|
and not DOCTRINE_REF.findall("D:NoDot.Caps"))
|
|
control("doctrine-id citing surface is reachable",
|
|
(not skills_dir.is_dir()) or scanned > 0)
|
|
|
|
|
|
# ------------------------------------------ 7. register integrity (REVIEWED)
|
|
# EARNED 2026-08-07, from a real loss. An amendment block was placed OVER the
|
|
# record it amends: the original `## REVIEWED-87 — PENDING-99 — …` entry was
|
|
# replaced by `## REVIEWED-87 — AMENDMENT 2026-08-07`, leaving the amendment's
|
|
# own `**Amends:** REVIEWED-87` line pointing at a record no longer in the file.
|
|
# The content was recoverable from git and the underlying jurist ruling was
|
|
# filed separately, so nothing was lost — but NOTHING DETECTED IT. It surfaced
|
|
# because a diff was read by hand, and the tell was a deletion count on what
|
|
# should have been a pure append.
|
|
#
|
|
# The class: a register whose entries can silently replace one another cannot be
|
|
# trusted to answer "what was ruled under N", which is the register's whole job.
|
|
# This is `removing-a-claim-is-not-removing-the-reliance` at the governance
|
|
# layer — the amendment's dependency on the original survived the original's
|
|
# removal, and became invisible.
|
|
#
|
|
# Detection only, like every check here: REVIEWED.md is [ESCALATE], the
|
|
# steward's hand (Constitutional Constraint #1).
|
|
REVIEWED_MD = HOME / "dotfiles" / "REVIEWED.md"
|
|
PENDING_MD = HOME / "dotfiles" / "PENDING.md"
|
|
PENDING_ARCHIVE_MD = HOME / "dotfiles" / "PENDING-archive.md"
|
|
|
|
# WIDENED 2026-08-31 under REVIEWED-132 (PENDING-173 + ADDENDUM 1). The check was
|
|
# narrowed three times without declaring any of them: the header regex and the
|
|
# **Amends:** regex both hard-coded REVIEWED-, and the amendment test was
|
|
# startswith("AMENDMENT"). It saw 3 of the 81 amendment-shaped blocks in the
|
|
# registers, and — worse — an ADDENDUM was classified as an ORIGINAL, so it could
|
|
# have satisfied its own "an un-amended entry exists" test on behalf of a record
|
|
# that had been replaced.
|
|
#
|
|
# ⚠ WIDENING IS THE FLOOR, NOT THE FIX. 48 of the 81 blocks carry NO item number
|
|
# (`### AMENDMENT 1 — 2026-08-08`) and are attributable only by which header sits
|
|
# above them. No id-keyed reader can check those; they are reported NOT ESTABLISHED,
|
|
# never passed. The prospective-convention question is PENDING-146's, deliberately
|
|
# not decided here (REVIEWED-132 condition 5).
|
|
MARKERS = ("AMENDMENT", "ADDENDUM")
|
|
RE_MARKER_TOKEN = re.compile(r"\b(?:AMENDMENT|ADDENDUM)\b")
|
|
RE_HEAD_LINE = re.compile(r"^#{2,3}\s+(.*)$", re.M)
|
|
RE_ID = re.compile(r"^((?:PENDING|REVIEWED|COMPLETED)\S*)\s*[—–-]\s*(.*)$")
|
|
RE_INBODY = re.compile(r"^\*\*(?:AMENDMENT|ADDENDUM)\b.*$", re.M)
|
|
RE_AMENDS = re.compile(r"\*\*Amends:\*\*\s*((?:PENDING|REVIEWED|COMPLETED)-\S+?)[\s,.(]")
|
|
|
|
# Every form the controls below actually exercise. A form found in the record and
|
|
# absent from this set is reported NOT ESTABLISHED rather than silently passed
|
|
# (REVIEWED-132 condition 2) — the check may not certify what it has never been shown.
|
|
FORMS_COVERED = {"id+marker", "id-only", "bare-marker", "in-body", "compound"}
|
|
|
|
|
|
def _classify(head: str):
|
|
"""-> (form, ident|None, title). One place decides what a header is."""
|
|
m = RE_ID.match(head.strip())
|
|
if not m:
|
|
up = head.strip().upper()
|
|
return ("bare-marker" if any(up.startswith(k) for k in MARKERS) else "prose"), None, head
|
|
ident, rest = m.group(1), m.group(2)
|
|
s = rest.strip()
|
|
if any(s.startswith(k) for k in MARKERS):
|
|
return "id+marker", ident, rest
|
|
if RE_MARKER_TOKEN.search(s):
|
|
# A marker somewhere other than the start — "BUILD RECORD + AMENDMENT 1 result".
|
|
# Condition 1: NOT admitted to originals. Excluded, and made visible.
|
|
return "compound", ident, rest
|
|
# ⚠ CASE IS LOAD-BEARING, found 2026-08-31 by the enumeration REVIEWED-132 cond. 3
|
|
# requires. An earlier draft upper-cased the title first, so prose titles — "Citation
|
|
# amendment (#2)", "Dream amendment", "PENDING-46 addendum (2026-07-03)" — matched and
|
|
# were struck from `originals`. That is the mirror of the bug this widening repairs:
|
|
# it would raise FALSE "the record was replaced" findings about six real originals.
|
|
# Markers in this register are uppercase standalone tokens; a lower-case mention in a
|
|
# title is a title.
|
|
return "id-only", ident, rest
|
|
|
|
|
|
def register_scan(texts: dict) -> tuple:
|
|
"""-> (findings, unattributable, forms_seen).
|
|
|
|
`originals` is computed across ALL registers, because a parent may have been
|
|
archived while its amendment stays open; the old per-file rule would have
|
|
called that a replacement. A header is an original ONLY if it carries an
|
|
identifier and no marker anywhere — everything else is excluded (condition 1),
|
|
which is the opposite default from the version this replaces.
|
|
"""
|
|
parsed, forms = {}, {}
|
|
for label, text in texts.items():
|
|
rows = [_classify(h) for h in RE_HEAD_LINE.findall(text)]
|
|
rows += [("in-body", None, ln.strip()) for ln in RE_INBODY.findall(text)]
|
|
parsed[label] = rows
|
|
for form, _i, _r in rows:
|
|
if form != "prose":
|
|
forms.setdefault(form, set()).add(label)
|
|
originals = {i for rows in parsed.values() for f, i, _ in rows
|
|
if f == "id-only" and i}
|
|
findings, unattributable = [], []
|
|
for label, rows in parsed.items():
|
|
for form, ident, title in rows:
|
|
if form == "id+marker" and ident not in originals:
|
|
findings.append(f"{label}: '{ident} — {title[:48]}' exists with no "
|
|
f"un-amended {ident} entry — the amendment replaced "
|
|
f"the record it amends")
|
|
elif form in ("bare-marker", "in-body"):
|
|
unattributable.append((label, form, title[:72]))
|
|
for ident in sorted(set(RE_AMENDS.findall(text := texts[label]))):
|
|
if ident not in originals:
|
|
findings.append(f"{label}: a block declares '**Amends:** {ident}' but "
|
|
f"no un-amended {ident} entry exists in any register")
|
|
return findings, unattributable, forms
|
|
|
|
|
|
_TEXTS = {p.name: p.read_text(errors="replace")
|
|
for p in (REVIEWED_MD, PENDING_MD, PENDING_ARCHIVE_MD) if p.exists()}
|
|
reg_findings, reg_unattrib, reg_forms = ([], [], {}) if not _TEXTS else register_scan(_TEXTS)
|
|
reg_uncovered = sorted(set(reg_forms) - FORMS_COVERED - {"prose"})
|
|
|
|
# Controls. Drawn from the census, not from the author's memory of the convention
|
|
# (REVIEWED-132 condition 2): ADDENDUM headers, ### depth, in-body forms and
|
|
# PENDING-side instances are all exercised, because all four are in the record.
|
|
_GOOD = ("## REVIEWED-87 — PENDING-99 — original\n**Date:** 2026-08-05\n\n"
|
|
"## REVIEGH\n\n## REVIEWED-87 — AMENDMENT 2026-08-07\n"
|
|
"**Amends:** REVIEWED-87 (x).\n")
|
|
_BAD = ("## REVIEWED-87 — AMENDMENT 2026-08-07\n"
|
|
"**Amends:** REVIEWED-87 (x).\n")
|
|
_ADDENDUM_BAD = "## PENDING-142 — ADDENDUM 1: the selftest encodes the defect\n"
|
|
_ADDENDUM_OK = ("## PENDING-142 — the parent\n\n"
|
|
"### PENDING-142 — ADDENDUM 1: joined, at ### depth\n")
|
|
_BARE = "## PENDING-9 — parent\n\n### AMENDMENT 1 — 2026-08-08, no item number\n"
|
|
|
|
_f_good, _u_good, _ = register_scan({"t": _GOOD})
|
|
_f_bad, _, _ = register_scan({"t": _BAD})
|
|
_f_add, _, _ = register_scan({"t": _ADDENDUM_BAD})
|
|
_f_addok, _, _ = register_scan({"t": _ADDENDUM_OK})
|
|
_f_bare, _u_bare, _ = register_scan({"t": _BARE})
|
|
|
|
control("register check passes a correctly JOINED amendment", not _f_good)
|
|
control("register check DETECTS an amendment that replaced its record "
|
|
"[reproduces the 2026-08-07 loss]", len(_f_bad) == 2)
|
|
control("register check DETECTS a replacing ADDENDUM [the word the old test could "
|
|
"not see; it classified this as an ORIGINAL and passed]", len(_f_add) == 1)
|
|
control("register check passes a JOINED ### addendum [must-not-flag: ### depth is "
|
|
"the register's own placement form]", not _f_addok)
|
|
control("an unnumbered '### AMENDMENT 1' is NOT ESTABLISHED, never passed",
|
|
any(f == "bare-marker" for _l, f, _t in _u_bare) and not _f_bare)
|
|
_PROSE = "## PENDING-60 — Citation amendment (#2): specify the CTS-URN model\n"
|
|
_f_prose, _, _fm_prose = register_scan({"t": _PROSE})
|
|
control("a lower-case 'amendment' in a TITLE stays an ORIGINAL [must-not-flag: the "
|
|
"mirror bug — it would strike six real originals off the list]",
|
|
"id-only" in _fm_prose and not _f_prose)
|
|
control("an UPPER-CASE marker mid-title is still excluded from originals [must-detect]",
|
|
"compound" in register_scan(
|
|
{"t": "## PENDING-164 — BUILD RECORD + AMENDMENT 1 result\n"})[2])
|
|
control("PENDING-side registers are actually read", "PENDING.md" in _TEXTS)
|
|
control("every form present in the record is covered by a control", not reg_uncovered)
|
|
control("register file is reachable", REVIEWED_MD.exists())
|
|
|
|
|
|
# ------------------------------------------ 8. deferred decisions, and their triggers
|
|
# EARNED 2026-08-07. The jurist settlement of 2026-05-16 deferred TEI-native
|
|
# authoring "until Cluster A's MD-with-sidecar form is operational". Cluster A
|
|
# became operational, the condition was met, and NOBODY LOOKED — it surfaced
|
|
# months later, by accident, while reading an unrelated document. The steward's
|
|
# stated reason for settling it that day was not the format question but the
|
|
# forgetting: "I abhor deferring so many things and then forgetting them."
|
|
#
|
|
# A deferral is a claim: "not yet". When its trigger fires, the substrate
|
|
# contradicts that claim — which is exactly what this instrument detects. So a
|
|
# deferred decision declares a MACHINE-CHECKABLE trigger and this checks it,
|
|
# rather than relying on anyone to remember.
|
|
#
|
|
# <!-- DEFERRED-DECISION: <slug>
|
|
# since: YYYY-MM-DD
|
|
# owner: steward | jurist | executor
|
|
# trigger: glob <pattern> | path-exists <path> | date <YYYY-MM-DD> | manual
|
|
# discriminator: <where the deciding evidence is written down> -->
|
|
#
|
|
# `manual` never auto-fires and is listed rather than checked — an honest way to
|
|
# record a deferral whose condition genuinely cannot be mechanised, instead of
|
|
# inventing a proxy. Proxies are what failed here: the old trigger stood in for
|
|
# "behavioural evidence on high-fidelity sources" and came true without it.
|
|
DEFERRED_RE = re.compile(
|
|
r"<!--\s*DEFERRED-DECISION:\s*([a-z0-9][a-z0-9-]*)\s*\n(.*?)-->", re.S)
|
|
|
|
# REVIEWED-127 / PENDING-158. A negative STATE-claim is the same sentence as a deferral
|
|
# about a different object: a deferral says "not yet" about a DECISION, a state-claim says
|
|
# "not yet" about a STATE. Same words, same forgetting, same substrate standing ready to
|
|
# contradict them — and until now only one of the two had a trigger.
|
|
#
|
|
# So this reuses trigger_fired() verbatim and inverts only what firing MEANS: for a
|
|
# deferral, fired = the decision is due; for a state-claim, fired = THE CLAIM IS FALSE.
|
|
#
|
|
# <!-- STATE-CLAIM: <slug>
|
|
# since: YYYY-MM-DD
|
|
# claims: <the negative claim, in words>
|
|
# falsified-by: glob <p> | path-exists <p> | date <d> | text-present <f> <s>
|
|
# | file-changed-since <commit> <path> | manual
|
|
# resolved: YYYY-MM-DD — <pointer> (optional; see resolution state below) -->
|
|
#
|
|
# ⚠ Opt-in, and the limit is the item's, not a caveat added here: a marker is used by
|
|
# authors who remember to mark their claims, which is the same population that would have
|
|
# caught the claim anyway. ADOPTION is the open question, not expressibility.
|
|
STATE_CLAIM_RE = re.compile(
|
|
r"<!--\s*STATE-CLAIM:\s*([a-z0-9][a-z0-9-]*)\s*\n(.*?)-->", re.S)
|
|
|
|
# REVIEWED-127 C1 / PENDING-157. The resolution state, which BOTH kinds carry.
|
|
#
|
|
# Earned 2026-08-25: a trigger came due, was correctly discharged, and the schema had no
|
|
# way to say so. The only options were deleting the block (losing the record) or renaming
|
|
# its key by hand (losing machine-checkability, and relying on the reflex the mechanism
|
|
# exists to replace). Renaming keys one at a time is how you live with a defect.
|
|
#
|
|
# `resolved:` must SAY WHAT DISCHARGED IT. Non-empty is not enough — an undocumented
|
|
# discharge records THAT a gate closed and not WHY, which six months on is no record at
|
|
# all. A resolved block whose pointer resolves to nothing is reported as a defect, in the
|
|
# same lane as an amendment that replaced the record it amends: both close over history.
|
|
state_claims: list[dict] = []
|
|
|
|
|
|
def pointer_resolves(text: str) -> bool:
|
|
"""True if the resolution names something that exists — a path, or a git object."""
|
|
for tok in re.findall(r"[A-Za-z0-9_./~-]{4,}", text or ""):
|
|
tok = tok.strip(".,;:")
|
|
if re.fullmatch(r"[0-9a-f]{7,40}", tok):
|
|
if subprocess.run(["git", "-C", str(HOME / "dotfiles"), "cat-file", "-e",
|
|
tok + "^{commit}"], capture_output=True).returncode == 0:
|
|
return True
|
|
cand = (Path(os.path.expanduser(tok)) if tok.startswith(("~", "/"))
|
|
else HOME / "dotfiles" / tok)
|
|
if cand.exists():
|
|
return True
|
|
return False
|
|
|
|
SCAN_ROOTS = [HOME / "_Dev", HOME / "dotfiles"]
|
|
TRANSCRIPTS = HOME / ".claude/projects/-Users-davidglidden"
|
|
# Governance packages live outside the */docs/** convention the scan was written for,
|
|
# so a deferral filed there was invisible to this check. Found 2026-08-07 while wiring
|
|
# PENDING-112's falsifier: the mechanism existed, and the one place it most needed to
|
|
# reach was the one place it did not look.
|
|
EXTRA_SCAN_GLOBS = [(HOME / "dotfiles", "claude/governance/**/*.md")]
|
|
deferrals: list[dict] = []
|
|
|
|
|
|
def _repo_root(p: Path) -> Path:
|
|
for parent in [p] + list(p.parents):
|
|
if (parent / ".git").exists():
|
|
return parent
|
|
return p.parent
|
|
|
|
|
|
def parse_deferrals(text: str, src: Path) -> list[dict]:
|
|
out = []
|
|
for slug, body in DEFERRED_RE.findall(text):
|
|
fields = dict(re.findall(r"^\s*([a-z-]+):\s*(.+?)\s*$", body, re.M))
|
|
out.append({"slug": slug, "file": src, "root": _repo_root(src),
|
|
"trigger": fields.get("trigger", "manual"),
|
|
"owner": fields.get("owner", "?"),
|
|
"since": fields.get("since", "?"),
|
|
"resolved": fields.get("resolved", "").strip()})
|
|
return out
|
|
|
|
|
|
def parse_state_claims(text: str, src: Path) -> list[dict]:
|
|
"""Same shape as a deferral; `falsified-by` is mapped onto `trigger` so the one
|
|
evaluator serves both. Firing means the CLAIM IS FALSE, not that a decision is due."""
|
|
out = []
|
|
for slug, body in STATE_CLAIM_RE.findall(text):
|
|
fields = dict(re.findall(r"^\s*([a-z-]+):\s*(.+?)\s*$", body, re.M))
|
|
out.append({"slug": slug, "file": src, "root": _repo_root(src),
|
|
"trigger": fields.get("falsified-by", "manual"),
|
|
"claims": fields.get("claims", "?"),
|
|
"since": fields.get("since", "?"),
|
|
"resolved": fields.get("resolved", "").strip()})
|
|
return out
|
|
|
|
|
|
def trigger_fired(d: dict) -> bool | None:
|
|
"""True = condition met (decision is due). None = not mechanically checkable."""
|
|
kind, _, arg = d["trigger"].partition(" ")
|
|
arg = arg.strip()
|
|
if kind == "glob":
|
|
return any(d["root"].glob(arg))
|
|
if kind == "path-exists":
|
|
return Path(os.path.expanduser(arg)).exists() if arg.startswith(("~", "/")) \
|
|
else (d["root"] / arg).exists()
|
|
if kind == "date":
|
|
return datetime.date.today().isoformat() >= arg
|
|
if kind == "text-present":
|
|
# Falsified by a string appearing somewhere — e.g. a hold claim falsified by the
|
|
# ruling that lifted it. Earned by trial 09: "the run is held" stayed true-looking
|
|
# for five days after REVIEWED-124 voided it, because nothing compared the two.
|
|
fp, _, needle = arg.partition(" ")
|
|
needle = needle.strip()
|
|
if not needle:
|
|
return None
|
|
target = (Path(os.path.expanduser(fp)) if fp.startswith(("~", "/"))
|
|
else d["root"] / fp)
|
|
try:
|
|
return needle in target.read_text(errors="replace")
|
|
except OSError:
|
|
return None
|
|
if kind == "file-changed-since":
|
|
# Falsified by a file having been edited since a named commit — e.g. "the filed
|
|
# rule not edited", which went false one hour after it was written.
|
|
commit, _, path = arg.partition(" ")
|
|
path = path.strip()
|
|
if not commit or not path:
|
|
return None
|
|
try:
|
|
r = subprocess.run(["git", "-C", str(d["root"]), "diff", "--quiet",
|
|
commit, "--", path], capture_output=True)
|
|
except OSError:
|
|
return None
|
|
return True if r.returncode == 1 else (False if r.returncode == 0 else None)
|
|
if kind == "transcripts":
|
|
# Session-count trigger. Added 2026-08-07 for PENDING-112's pre-registered
|
|
# 20-session falsifier, which the jurist required be BINDING rather than a
|
|
# disclosed intention. A date would have been a proxy — sessions run at wildly
|
|
# variable rates — and this block's own comment records that proxies are what
|
|
# failed last time. Counting transcripts encodes the real condition.
|
|
try:
|
|
return len(list(TRANSCRIPTS.glob("*.jsonl"))) >= int(arg)
|
|
except (ValueError, OSError):
|
|
return None
|
|
return None
|
|
|
|
|
|
_scan_targets = [(r, "*/docs/**/*.md") for r in SCAN_ROOTS] + EXTRA_SCAN_GLOBS
|
|
|
|
# PENDING-118, and the item's own option (1) is REFUTED by building it.
|
|
# The item said "widen the scan to ~/PENDING-archive.md — the checker already parses
|
|
# that exact format." It does not. The structured marker is an HTML comment
|
|
# <!-- DEFERRED-DECISION: slug ... -->, and there are ZERO of those in PENDING.md or in
|
|
# the archive. Their deferrals are PROSE — measured 2026-08-08: 53 and 26 occurrences of
|
|
# "defer*". Widening alone would scan two more files, find nothing, and report clean:
|
|
# a silent net built to close a blind spot, which is the failure this item describes.
|
|
#
|
|
# So the widening ships WITH its own honest limit. Structured blocks anywhere are now
|
|
# found; prose deferrals are COUNTED and reported as un-machine-readable, never as
|
|
# absent. Counting is not classifying — how many of those conditions have fired is a
|
|
# READING task, and it is reported as owed rather than silently skipped.
|
|
_scan_targets += [(HOME / "dotfiles", "PENDING.md"), (HOME / "dotfiles", "PENDING-archive.md")]
|
|
PROSE_DEFERRAL_RE = re.compile(r"defer(?:red|ral)", re.I)
|
|
# The size guard bounds the unbounded `**/*.md` globs. It must NOT apply to the two
|
|
# governance files, which were added to the scan on purpose and are the whole reason
|
|
# the scan reaches outside docs/ at all.
|
|
#
|
|
# 2026-08-17 [FIX]: it did apply, and PENDING.md had grown to 546,944 bytes — so every
|
|
# structured DEFERRED-DECISION block in the governance register was skipped, silently,
|
|
# by the checker built to stop deferrals from being silently missed. Worse than silent:
|
|
# the prose-deferral loop below has no size guard, so PENDING.md's prose count appeared
|
|
# in the report and made the file look examined. Found by placing a block under
|
|
# REVIEWED-123 cond. 2 and noticing the tracked count did not move.
|
|
#
|
|
# Both halves are fixed here: the named governance files are never size-skipped, and a
|
|
# skip is now REPORTED rather than swallowed — the third outcome of REVIEWED-104's
|
|
# doctrine, on the instrument whose whole subject is conditions nobody is watching.
|
|
GOVERNANCE_FILES = {HOME / "dotfiles" / "PENDING.md",
|
|
HOME / "dotfiles" / "PENDING-archive.md"}
|
|
_seen_files: set = set()
|
|
skipped_too_large: list = []
|
|
for root, pattern in _scan_targets:
|
|
if not root.is_dir():
|
|
continue
|
|
for f in root.glob(pattern):
|
|
if f in _seen_files:
|
|
continue
|
|
_seen_files.add(f)
|
|
try:
|
|
if f.stat().st_size > 400_000 and f not in GOVERNANCE_FILES:
|
|
skipped_too_large.append(f)
|
|
continue
|
|
body = f.read_text(errors="replace")
|
|
except OSError:
|
|
continue
|
|
if "DEFERRED-DECISION:" in body:
|
|
deferrals.extend(parse_deferrals(body, f))
|
|
if "STATE-CLAIM:" in body:
|
|
state_claims.extend(parse_state_claims(body, f))
|
|
|
|
# REVIEWED-127 C1. A resolved block is no longer due — but it is NOT dropped. It stays
|
|
# counted as a closed ledger, because a discharge that vanishes from the report is its own
|
|
# species of decay: the register would show three tracked deferrals and no evidence at all
|
|
# that a fourth had ever been answered.
|
|
resolved_deferrals = [d for d in deferrals if d["resolved"]]
|
|
open_deferrals = [d for d in deferrals if not d["resolved"]]
|
|
fired = [d for d in open_deferrals if trigger_fired(d) is True]
|
|
manual = [d for d in open_deferrals if trigger_fired(d) is None]
|
|
|
|
resolved_claims = [c for c in state_claims if c["resolved"]]
|
|
open_claims = [c for c in state_claims if not c["resolved"]]
|
|
falsified = [c for c in open_claims if trigger_fired(c) is True]
|
|
claims_manual = [c for c in open_claims if trigger_fired(c) is None]
|
|
|
|
# Same lane as the amendment-that-replaced-its-own-record check: both are a record
|
|
# closing over its own history.
|
|
dangling = [d for d in (resolved_deferrals + resolved_claims)
|
|
if not pointer_resolves(d["resolved"])]
|
|
|
|
_T_OK = ("<!-- DEFERRED-DECISION: tei-native\n since: 2026-08-07\n"
|
|
" owner: steward\n trigger: date 2000-01-01\n-->")
|
|
_T_WAIT = _T_OK.replace("date 2000-01-01", "date 2999-01-01")
|
|
_p_ok = parse_deferrals(_T_OK, CLAUDE_MD)
|
|
_p_wait = parse_deferrals(_T_WAIT, CLAUDE_MD)
|
|
control("deferred-decision parser reads a well-formed block",
|
|
len(_p_ok) == 1 and _p_ok[0]["slug"] == "tei-native")
|
|
control("deferred-decision parser rejects a non-block",
|
|
not parse_deferrals("<!-- DEFERRED: nope -->", CLAUDE_MD))
|
|
# transcripts-trigger controls: it must fire on a threshold already passed and stay
|
|
# silent on one that has not. An absence is not evidence until the instrument is shown
|
|
# capable of detecting presence — and this trigger carries a standing obligation.
|
|
control("transcripts trigger fires on a passed threshold",
|
|
trigger_fired({"trigger": "transcripts 1", "root": HOME}) is True)
|
|
control("transcripts trigger silent on an unreached threshold",
|
|
trigger_fired({"trigger": "transcripts 999999", "root": HOME}) is False)
|
|
control("governance dir is inside the deferral scan",
|
|
any("claude/governance" in str(p) for p in _seen_files)
|
|
or not (HOME / "dotfiles/claude/governance").is_dir())
|
|
# 2026-08-17: PENDING.md passed 400 KB and every structured block in it went unscanned,
|
|
# silently, while its prose count still appeared in the report. These controls are derived
|
|
# from the PROPERTY (is the register's block actually parsed?), not from the guard's own
|
|
# vocabulary — a control asking "does the guard work" would have passed throughout.
|
|
control("the governance register is actually scanned for blocks, whatever its size",
|
|
(HOME / "dotfiles/PENDING.md") in _seen_files
|
|
and (HOME / "dotfiles/PENDING.md") not in skipped_too_large)
|
|
control("a real block IN the register is parsed [the live instance, not a fixture]",
|
|
any(d["file"].name == "PENDING.md" for d in deferrals)
|
|
or "DEFERRED-DECISION:" not in (HOME / "dotfiles/PENDING.md").read_text(errors="replace"))
|
|
control("the size guard still applies to non-governance files [negative control — "
|
|
"the exemption must not become 'scan everything']",
|
|
(HOME / "dotfiles/PENDING.md") in GOVERNANCE_FILES
|
|
and (HOME / "dotfiles/CLAUDE.md") not in GOVERNANCE_FILES)
|
|
control("trigger evaluator FIRES on a met condition",
|
|
_p_ok and trigger_fired(_p_ok[0]) is True)
|
|
control("trigger evaluator does NOT fire on an unmet condition "
|
|
"[the discriminating half]",
|
|
_p_wait and trigger_fired(_p_wait[0]) is False)
|
|
control("deferred-decision scan surface is reachable",
|
|
any(r.is_dir() for r in SCAN_ROOTS))
|
|
|
|
# ---- REVIEWED-127: state-claims, the two new trigger kinds, and the resolution state.
|
|
# Every one carries its discriminating half. An absence is not evidence until the
|
|
# instrument is shown capable of detecting presence — and this whole item exists because
|
|
# an instrument that reads nothing reports exactly like one that finds nothing.
|
|
_SC = ("<!-- STATE-CLAIM: fixture\n since: 2026-08-25\n"
|
|
" claims: the pulse has not been fetched\n"
|
|
" falsified-by: date 2000-01-01\n-->")
|
|
_sc = parse_state_claims(_SC, CLAUDE_MD)
|
|
control("state-claim parser reads a well-formed block",
|
|
len(_sc) == 1 and _sc[0]["slug"] == "fixture"
|
|
and _sc[0]["claims"] == "the pulse has not been fetched")
|
|
control("state-claim parser rejects a non-block [negative control]",
|
|
not parse_state_claims("<!-- STATE: nope -->", CLAUDE_MD))
|
|
control("state-claim FIRES when its falsifier is met [= the claim is now false]",
|
|
_sc and trigger_fired(_sc[0]) is True)
|
|
control("state-claim SILENT when its falsifier is unmet [the discriminating half]",
|
|
trigger_fired(parse_state_claims(
|
|
_SC.replace("date 2000-01-01", "date 2999-01-01"), CLAUDE_MD)[0]) is False)
|
|
control("state-claim `manual` is listed, never fired",
|
|
trigger_fired(parse_state_claims(
|
|
_SC.replace("date 2000-01-01", "manual"), CLAUDE_MD)[0]) is None)
|
|
|
|
_DF = HOME / "dotfiles"
|
|
control("text-present FIRES on a string that is present",
|
|
trigger_fired({"trigger": "text-present PENDING.md PENDING-158", "root": _DF}) is True)
|
|
control("text-present SILENT on a string that is absent [negative control]",
|
|
trigger_fired({"trigger": "text-present PENDING.md zzz-not-in-this-file-zzz",
|
|
"root": _DF}) is False)
|
|
control("text-present returns None on an unreadable target [honest degradation]",
|
|
trigger_fired({"trigger": "text-present no/such/file.md x", "root": _DF}) is None)
|
|
control("file-changed-since FIRES on a file edited since the named commit",
|
|
trigger_fired({"trigger": "file-changed-since 5694b92 "
|
|
"claude/governance/fool/seed/FOOL-BONES-2026-08-25.md",
|
|
"root": _DF}) is True)
|
|
# ⚠ The fixture below is deliberately a FROZEN artifact — a trial-03 run output from
|
|
# 2026-08-02 that is never edited. The first version of this control pointed at
|
|
# FOOL-SEED-RULE.md and failed the moment that file was edited in the working tree, which
|
|
# is correct behaviour from the trigger and a badly chosen fixture: a control whose
|
|
# subject is "did this file change" must not point at a file the session is changing.
|
|
# Caught by the controls running on every invocation rather than in a separate suite.
|
|
control("file-changed-since SILENT on a file unchanged since HEAD [discriminating half]",
|
|
trigger_fired({"trigger": "file-changed-since HEAD "
|
|
"claude/governance/fool/runs/trial-03-20260802T144136Z.raw.txt",
|
|
"root": _DF}) is False)
|
|
|
|
_RES_OK = _SC.replace("-->", " resolved: 2026-08-25 — commit 5694b925\n-->")
|
|
_RES_BAD = _SC.replace("-->", " resolved: yes, done\n-->")
|
|
control("resolved block parses its resolution",
|
|
parse_state_claims(_RES_OK, CLAUDE_MD)[0]["resolved"].startswith("2026-08-25"))
|
|
control("a resolved block is EXCLUDED from due-ness even with a met trigger "
|
|
"[C1: discharge without a hand-rename]",
|
|
not [c for c in parse_state_claims(_RES_OK, CLAUDE_MD)
|
|
if not c["resolved"] and trigger_fired(c) is True])
|
|
control("the SAME block unresolved IS due [negative control — the exclusion must be "
|
|
"the resolution, not the fixture]",
|
|
trigger_fired(parse_state_claims(_SC, CLAUDE_MD)[0]) is True)
|
|
control("pointer that names something real resolves",
|
|
pointer_resolves("2026-08-25 — commit 5694b925"))
|
|
control("pointer that names nothing is DANGLING [C1: an undocumented discharge must be "
|
|
"impossible to express, not merely discouraged]",
|
|
not pointer_resolves("yes, done"))
|
|
control("a path pointer resolves too, not only a commit",
|
|
pointer_resolves("see claude/governance/fool/seed/FOOL-BONES-2026-08-25.md"))
|
|
# Prose-deferral census: counted, never classified, and never read as absence.
|
|
prose_counts = {}
|
|
for _f in (HOME / "dotfiles" / "PENDING.md", HOME / "dotfiles" / "PENDING-archive.md"):
|
|
if _f.exists():
|
|
prose_counts[_f.name] = len(PROSE_DEFERRAL_RE.findall(_f.read_text(errors="replace")))
|
|
control("prose-deferral counter finds a known-present phrase",
|
|
PROSE_DEFERRAL_RE.search("this was deferred pending recurrence") is not None)
|
|
control("prose-deferral counter does not fire on unrelated text",
|
|
PROSE_DEFERRAL_RE.search("the fleet is green") is None)
|
|
control("register files are inside the widened deferral scan",
|
|
any(str(t[1]).endswith("PENDING-archive.md") for t in _scan_targets))
|
|
|
|
|
|
# ------------------------------------------ 9. built without a ruling in the register
|
|
# EARNED 2026-08-08. Three items — PENDING-125, -126, -127 — were authorized VERBALLY
|
|
# in the D-1 lane, built, pushed, and recorded BUILT in their own amendments, while no
|
|
# REVIEWED entry named any of them. Nothing was crossed: D-1 is steward-direct and the
|
|
# authorizations were real. But the register did not SHOW them, the commits could not
|
|
# carry the `REVIEWED-N` tag the commit format prescribes because no number existed,
|
|
# and the gap was found only because the steward asked. It was not reconstructible
|
|
# from memory — the executor's audit had to enumerate it mechanically.
|
|
#
|
|
# Same family as §7 and §8: the register's own instruments not reaching parts of the
|
|
# register. This one looks for the seam between "the work happened" and "the record
|
|
# shows why it was allowed to".
|
|
#
|
|
# ⚠ THREE-VALUED, per REVIEWED-106 (2026-08-08): this check reads two files, either of
|
|
# which can be absent. `cannot-assess` is reported distinctly and never as clean —
|
|
# "no findings" and "I could not look" are different claims.
|
|
|
|
# Real path, not the ~/PENDING.md symlink — the same convention REVIEWED_MD uses.
|
|
PENDING_MD = HOME / "dotfiles" / "PENDING.md"
|
|
|
|
RE_PEND_HEAD = re.compile(r"^##\s+PENDING-(\d+)\s*[—-]\s*(.*)$", re.M)
|
|
RE_REV_FOR = re.compile(r"^##\s+REVIEWED-\d+\s*[—-]\s*PENDING-(\d+)\b", re.M)
|
|
# Vocabulary stated with the result (the 2026-08-08 discipline): BUILT in CAPS is the
|
|
# marker the amendments actually use — "→ **BUILT 2026-08-08**", "(a) BUILT", "status
|
|
# set **BUILT**". Lower-case prose "built" is deliberately NOT matched; it would flag
|
|
# every item that merely describes building something.
|
|
RE_BUILT = re.compile(r"\bBUILT\b")
|
|
|
|
|
|
def unruled_builds(pending_text: str, reviewed_text: str) -> list[str]:
|
|
"""PENDING items marked BUILT that no REVIEWED heading names."""
|
|
ruled = set(RE_REV_FOR.findall(reviewed_text))
|
|
out: list[str] = []
|
|
parts = RE_PEND_HEAD.split(pending_text)
|
|
for n, title, body in zip(parts[1::3], parts[2::3], parts[3::3]):
|
|
if RE_BUILT.search(title + body) and n not in ruled:
|
|
out.append(f"PENDING-{n} is marked BUILT and no REVIEWED entry names it "
|
|
f"— {title.strip()[:58]}")
|
|
return out
|
|
|
|
|
|
_P_GOOD = ("## PENDING-11 — ruled and built\nBUILT 2026-01-01, abc1234.\n"
|
|
"## PENDING-12 — open\nAwaiting steward authorization.\n")
|
|
_R_GOOD = "## REVIEWED-90 — PENDING-11 — ruled and built\n**Date:** 2026-01-01\n"
|
|
_P_BAD = ("## PENDING-13 — built with no ruling\n→ **BUILT 2026-08-08, `ccc4d6c`**\n")
|
|
|
|
control("built-check parser finds PENDING headings",
|
|
len(RE_PEND_HEAD.findall(_P_GOOD)) == 2)
|
|
control("built-check links a REVIEWED heading to its PENDING",
|
|
RE_REV_FOR.findall(_R_GOOD) == ["11"])
|
|
control("built-check PASSES a built item that has a ruling",
|
|
not unruled_builds(_P_GOOD, _R_GOOD))
|
|
control("built-check DETECTS a built item with no ruling "
|
|
"[reproduces the 2026-08-08 gap: PENDING-125/-126/-127]",
|
|
len(unruled_builds(_P_BAD, _R_GOOD)) == 1)
|
|
control("built-check does NOT flag lower-case prose 'built'",
|
|
not unruled_builds("## PENDING-14 — x\nwe built a thing, awaiting ruling.\n",
|
|
_R_GOOD))
|
|
|
|
build_findings: list[str] = []
|
|
build_assessable = PENDING_MD.exists() and REVIEWED_MD.exists()
|
|
if build_assessable:
|
|
build_findings = unruled_builds(PENDING_MD.read_text(errors="replace"),
|
|
REVIEWED_MD.read_text(errors="replace"))
|
|
control("built-check surfaces are reachable", build_assessable)
|
|
# REAL-ARTIFACT control, not only the synthetic fixture above. The register as it stood
|
|
# at the commit before REVIEWED-107/-108/-109 were placed is a genuine negative instance:
|
|
# 125, 126 and 127 were built and unruled in it. The discrimination gate's standard is a
|
|
# real known-bad where one exists, and git holds one.
|
|
try:
|
|
import subprocess
|
|
_old = subprocess.run(["git", "-C", str(HOME / "dotfiles"), "show", "HEAD:REVIEWED.md"],
|
|
capture_output=True, text=True, timeout=10).stdout
|
|
if _old and PENDING_MD.exists():
|
|
_hits = unruled_builds(PENDING_MD.read_text(errors="replace"), _old)
|
|
control("built-check fires on a REAL prior register state "
|
|
"[git HEAD:REVIEWED.md — a genuine known-bad, not a fixture]",
|
|
len(_hits) > 0 or bool(RE_REV_FOR.findall(_old)))
|
|
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:
|
|
print("⚠ drift-check: INSTRUMENT NOT VERIFIED — treat results as unestablished")
|
|
for lbl in failed_controls:
|
|
print(f" failed control: {lbl}")
|
|
print()
|
|
|
|
if not findings:
|
|
print(f"✓ governance drift-check: CLAUDE.md clean "
|
|
f"({len(controls)}/{len(controls)} controls passed, {len(paths)} paths verified)")
|
|
else:
|
|
print(f"⚑ governance drift-check: {len(findings)} claim(s) in ~/CLAUDE.md "
|
|
f"contradicted by substrate")
|
|
for f in findings:
|
|
print(f" {f}")
|
|
print("\n Correction requires [ESCALATE] (Constitutional Constraint #1). "
|
|
"This report is detection only.")
|
|
|
|
# Register integrity is reported SEPARATELY. Folding it into the count above
|
|
# would make that line's own claim false — it says "claim(s) in ~/CLAUDE.md",
|
|
# and these are findings about a different file.
|
|
if reg_findings:
|
|
print(f"\n⚑ register integrity: {len(reg_findings)} broken amendment link(s) "
|
|
f"in ~/REVIEWED.md")
|
|
for f in reg_findings:
|
|
print(f" {f}")
|
|
print("\n An amendment must sit alongside the record it amends, never replace it.")
|
|
print(" Correction requires [ESCALATE] — REVIEWED.md is the steward's hand.")
|
|
elif _TEXTS:
|
|
# REVIEWED-132 condition 3: ENUMERATE, do not count. The old line reported a
|
|
# bare number and that number never moved when a matching block was appended.
|
|
_checked = {}
|
|
for _lab, _txt in _TEXTS.items():
|
|
_rows = [_classify(h) for h in RE_HEAD_LINE.findall(_txt)]
|
|
_rows += [("in-body", None, "") for _ in RE_INBODY.findall(_txt)]
|
|
for _f, _i, _r in _rows:
|
|
if _f != "prose" and _f != "id-only":
|
|
_checked.setdefault(_lab, {}).setdefault(_f, 0)
|
|
_checked[_lab][_f] += 1
|
|
_tot = sum(sum(d.values()) for d in _checked.values())
|
|
_att = sum(c for d in _checked.values() for f, c in d.items()
|
|
if f in ("id+marker", "compound"))
|
|
print(f"✓ register integrity: {_att} attributable amendment block(s) checked, "
|
|
f"all resolve — of {_tot} amendment-shaped block(s) across "
|
|
f"{len(_TEXTS)} register(s)")
|
|
for _lab in sorted(_checked):
|
|
print(" " + _lab + ": " + ", ".join(f"{f}={c}" for f, c in sorted(_checked[_lab].items())))
|
|
if reg_unattrib:
|
|
print(f" ⚠ {len(reg_unattrib)} block(s) NOT ESTABLISHED — they carry no item "
|
|
f"identifier, so no id-keyed reader can say what they amend. Counted, "
|
|
f"never passed. The convention question is PENDING-146's.")
|
|
if reg_uncovered:
|
|
print(f" ⚠ NOT ESTABLISHED — form(s) present in the record with no control: "
|
|
f"{', '.join(reg_uncovered)}")
|
|
|
|
# Built-without-a-ruling. THREE-VALUED (REVIEWED-106): clean / findings / cannot-assess.
|
|
if not build_assessable:
|
|
missing = [str(f.relative_to(HOME)) for f in (PENDING_MD, REVIEWED_MD) if not f.exists()]
|
|
print(f"\n— built-vs-ruled: CANNOT ASSESS — unreachable: {', '.join(missing)}")
|
|
print(" Not a pass and not a finding. Nothing was checked.")
|
|
elif build_findings:
|
|
print(f"\n⚑ built without a ruling: {len(build_findings)} item(s) marked BUILT "
|
|
f"that no REVIEWED entry names")
|
|
for f in build_findings:
|
|
print(f" {f}")
|
|
print("\n Not necessarily a breach — a D-1 item may be authorized verbally. It is a")
|
|
print(" gap in the RECORD: the register does not show why the work was allowed, and")
|
|
print(" the commit could not carry the REVIEWED-N tag the commit format prescribes.")
|
|
print(" Remedy: draft the entry for steward placement.")
|
|
else:
|
|
_nb = len([1 for n, t, b in zip(*[RE_PEND_HEAD.split(PENDING_MD.read_text(errors="replace"))[i::3]
|
|
for i in (1, 2, 3)]) if RE_BUILT.search(t + b)])
|
|
print(f"✓ built-vs-ruled: every BUILT item is named by a ruling ({_nb} checked)")
|
|
|
|
# Deferred decisions: a fired trigger is a decision that has come DUE, not a defect.
|
|
if deferrals:
|
|
if fired:
|
|
print(f"\n⏰ deferred decisions: {len(fired)} of {len(open_deferrals)} open have COME DUE")
|
|
for d in fired:
|
|
print(f" {d['slug']} — owner: {d['owner']}, deferred since {d['since']}")
|
|
print(f" trigger MET: {d['trigger']}")
|
|
print(f" {d['file'].relative_to(HOME)}")
|
|
print("\n A deferral is the claim 'not yet'. These triggers say otherwise.")
|
|
else:
|
|
waiting = len(open_deferrals) - len(manual)
|
|
print(f"✓ deferred decisions: {len(open_deferrals)} tracked, none due "
|
|
f"({waiting} checkable, {len(manual)} manual-only)")
|
|
if resolved_deferrals:
|
|
print(f" ✓ plus {len(resolved_deferrals)} RESOLVED — answered, and kept in the "
|
|
f"ledger rather than dropped:")
|
|
for _r in resolved_deferrals:
|
|
print(f" {_r['slug']} — {_r['resolved']}")
|
|
if prose_counts:
|
|
_tot = sum(prose_counts.values())
|
|
print(f" ⚠ plus {_tot} PROSE deferral mention(s) in the register "
|
|
f"({', '.join(f'{k} {v}' for k, v in prose_counts.items())}) — these carry no")
|
|
print(" DEFERRED-DECISION block, so NO trigger is machine-checkable for any of them.")
|
|
print(" Counted, not classified. Whether any condition has fired is unestablished.")
|
|
if skipped_too_large:
|
|
print(f" ⚠ and {len(skipped_too_large)} file(s) were NOT SCANNED for structured blocks "
|
|
f"(over the 400 KB guard):")
|
|
for _s in skipped_too_large:
|
|
print(f" {_s.relative_to(HOME) if HOME in _s.parents else _s} "
|
|
f"({_s.stat().st_size:,} bytes)")
|
|
print(" Any DEFERRED-DECISION block in these is INERT. This is 'could not assess',")
|
|
print(" not 'nothing there' — the distinction REVIEWED-104 rules may not be collapsed.")
|
|
|
|
# State claims: a fired falsifier is not a decision coming due — it is a claim that has
|
|
# STOPPED BEING TRUE while still being written down as true.
|
|
if state_claims:
|
|
if falsified:
|
|
print(f"\n⚠ state claims: {len(falsified)} of {len(open_claims)} open are NOW FALSE")
|
|
for c in falsified:
|
|
print(f" {c['slug']} — claims: {c['claims']}")
|
|
print(f" FALSIFIED BY: {c['trigger']}")
|
|
print(f" {c['file'].relative_to(HOME)}")
|
|
print("\n A state-claim is the claim 'not yet' about a state, not a decision.")
|
|
print(" The substrate says otherwise. Correct the document, or resolve the block.")
|
|
else:
|
|
_ok = len(open_claims) - len(claims_manual)
|
|
print(f"✓ state claims: {len(open_claims)} tracked, none falsified "
|
|
f"({_ok} checkable, {len(claims_manual)} manual-only)")
|
|
if resolved_claims:
|
|
print(f" ✓ plus {len(resolved_claims)} RESOLVED — kept in the ledger.")
|
|
else:
|
|
# Silence here is NOT evidence of clean state-claims. Nothing has opted in yet, and
|
|
# PENDING-158 records why that is the open question: an opt-in marker is used by the
|
|
# authors who would have caught the claim anyway. Say so rather than print a tick.
|
|
print("· state claims: 0 marked. ⚠ NOT 'none stale' — nothing has opted in yet;")
|
|
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")
|
|
for _d in 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)
|