The same parenthetical header shape that broke ruled_pendings in wake-digest.py on
2026-09-05, in a second instrument, found the next day. Taken as one act with that
widening, per the jurist.
RE_ID was ^((?:PENDING|REVIEWED|COMPLETED)\S*)\s*[—–-]\s*(.*)$. On
`## REVIEWED-131 (PENDING-172) — …` the greedy \S* backtracked until the hyphen INSIDE
`REVIEWED-131` served as the separator, yielding ident `REVIEWED`. No un-amended
`REVIEWED-131` original was then found, so the register check reported that
REVIEWED-131's amendment had replaced the record it amends. It had not — the record is
intact and the reader could not see it.
[FIX] against existing specification: the check's stated subject is detecting an
amendment that replaced its record, and reporting a replacement that did not occur
fails that specification. Taken now rather than queued because a false alarm standing
in the register is the disarmed-tripwire hazard PENDING-139 measured — red-on-absent
trains the reader to discount red.
ENUMERATED BEFORE LANDING, per REVIEWED-132 condition 3 — enumerate, do not count.
All 549 headers across REVIEWED.md, PENDING.md and PENDING-archive.md classified under
both patterns: exactly THREE change, all parenthetical rulings recovering their true
ident (REVIEWED-131, -132, -133). Nothing else in the record moves.
Controls are paired, and the mangled-ident case is stated as its own control because
"ident is wrong" and "header is unseen" fail identically downstream. 59 -> 65 controls,
all passing; the false finding is gone and no new finding replaced it.
PENDING-139 RE-MEASURED, not repaired, and it needs re-reading before it is ruled:
leg (A) — REPAIRED. RE_HEAD_LINE is ^#{2,3}, so a ###-level amendment heading is seen
and classifies id+marker, identically to ##. The item still reads as live on this leg.
leg (B) — STILL LIVE. RE_BUILT is r"\bBUILT\b" and fires on "NOT BUILT", "NOT YET
BUILT" and "the mechanism is NOT BUILT". Untouched here; it is not this fix's subject.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3L79vgAnt1x2kxvf23Qt7
1042 lines
55 KiB
Python
Executable File
1042 lines
55 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)
|
|
# ⚠ WIDENED 2026-09-06. The old pattern was
|
|
# ^((?:PENDING|REVIEWED|COMPLETED)\S*)\s*[—–-]\s*(.*)$
|
|
# and on `## REVIEWED-131 (PENDING-172) — …` the greedy \S* BACKTRACKED until the hyphen
|
|
# INSIDE `REVIEWED-131` served as the separator, yielding ident `REVIEWED` — not
|
|
# `REVIEWED-131`. No un-amended `REVIEWED-131` original was then found, so the register
|
|
# check reported that REVIEWED-131's amendment had REPLACED the record it amends. It had
|
|
# not. A false alarm standing in the register is the disarmed-tripwire hazard PENDING-139
|
|
# measured: red-on-absent trains the reader to discount red.
|
|
#
|
|
# THE SAME PARENTHETICAL HEADER SHAPE THAT BROKE `ruled_pendings` IN wake-digest.py ON
|
|
# 2026-09-05, in a second instrument, found the next day. Both failures ran toward hiding
|
|
# a record that exists. Fixed as one act with that widening, per the jurist.
|
|
#
|
|
# The id is now an explicit token and the trailing parenthetical is consumed rather than
|
|
# collided with. Enumerated over all 549 headers in the three registers before landing
|
|
# (REVIEWED-132 condition 3 — enumerate, do not count): exactly THREE classifications
|
|
# change, all of them parenthetical rulings recovering their true ident —
|
|
# REVIEWED-131, -132, -133. Nothing else in the record moves.
|
|
RE_ID = re.compile(
|
|
r"^((?:PENDING|REVIEWED|COMPLETED)(?:-\S+)?)(?:\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])
|
|
# --- RE_ID parenthetical widening, 2026-09-06. Paired: the form is read, AND the forms
|
|
# that must not move are held. The mangled-ident case is stated as its own control
|
|
# because "ident is wrong" and "header is unseen" fail identically downstream.
|
|
control("a PARENTHETICAL ruling header yields its FULL ident [must-detect: the "
|
|
"REVIEWED-131 false positive]",
|
|
RE_ID.match("REVIEWED-131 (PENDING-172) — A version upgrade").group(1)
|
|
== "REVIEWED-131")
|
|
control("that header does NOT yield the bare family name [negative control — the exact "
|
|
"old failure, which looked like a clean parse]",
|
|
RE_ID.match("REVIEWED-131 (PENDING-172) — x").group(1) != "REVIEWED")
|
|
control("the ordinary em-dash form is unchanged [regression]",
|
|
RE_ID.match("REVIEWED-121 — PENDING-134 — The whose-proposition test").group(1)
|
|
== "REVIEWED-121")
|
|
control("a NON-NUMERIC family header still parses [regression: `## PENDING — ICP-19`]",
|
|
RE_ID.match("PENDING — ICP-19 Remit Expansion").group(1) == "PENDING")
|
|
control("an id+marker header still classifies as id+marker [regression]",
|
|
_classify("REVIEWED-135 — AMENDMENT 1 — §8's dependency")[:2]
|
|
== ("id+marker", "REVIEWED-135"))
|
|
control("a parenthetical header carrying a MARKER is still caught [must-detect: the "
|
|
"widening must not smuggle an amendment into originals]",
|
|
_classify("REVIEWED-131 (PENDING-172) — AMENDMENT 1: x")[0] == "id+marker")
|
|
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)
|