Files
dotfiles/scripts/governance-drift-check.py
T
David F GliddenandClaude Opus 5 d04c875054 [FIX] The deferral checker silently skipped the governance register (REVIEWED-123)
Executing REVIEWED-123 conditions 2 and 3 surfaced a defect in the instrument
that was supposed to carry them.

PENDING.md had grown to 546,944 bytes, past the scanner's 400 KB guard, so EVERY
structured DEFERRED-DECISION block in the governance register was skipped —
silently — by the checker built to stop deferred conditions from being silently
missed. The 30-day review point placed under condition 2 was inert on arrival.

Worse than silent. The prose-deferral loop has no size guard, so PENDING.md's
prose count (70) still appeared in the report, making the file look examined
while its structured blocks were never read. Found only by placing a block and
noticing the tracked count did not move.

Both halves fixed:
- The two named governance files are exempt from the guard. The guard exists to
  bound the unbounded **/*.md globs; it was never meant for the files the scan
  reaches outside docs/ specifically to include.
- A size-skip is now REPORTED, not swallowed: "NOT SCANNED for structured blocks",
  named, with byte counts, and stated as "could not assess" rather than "nothing
  there" — REVIEWED-104's third outcome, applied to the instrument whose entire
  subject is conditions nobody is watching. Two legitimate skips now visible
  (Carruthers 1.6 MB, Yates 1.0 MB — scholarly texts, correctly out of scope).

Three controls added, derived from the PROPERTY rather than the guard's own
vocabulary: is the register actually scanned; is a real block in it parsed (the
live instance, not a fixture); and does the guard still apply to non-governance
files, so the exemption cannot quietly become "scan everything". A control asking
"does the guard work" would have passed throughout.

Result: 32/32 controls (was 29/29); deferred decisions 3 tracked (was 2), all
checkable. The ladder-freeze-30day-review trigger is live at date 2026-09-16.

Also under REVIEWED-123: N-now recorded (60 transcripts of 84, 24 remaining);
freeze scope stated as GENERAL per condition 1 and ladder file verified untouched;
owed-entries list ratified with each row naming its authorizing ruling per
condition 3 — OWED-1 under REVIEWED-122 cond. 9, OWED-2 explicitly NONE, queued
but not authorized and needing its own ruling before it joins the ladder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Y6t6qx7cpaCu5xGdD36u4
2026-08-17 20:40:53 +02:00

609 lines
30 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 sys
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"
RE_HEAD = re.compile(r"^##\s+REVIEWED-(\d+)\s*[—-]\s*(.*)$", re.M)
RE_AMENDS = re.compile(r"\*\*Amends:\*\*\s*REVIEWED-(\d+)")
def register_findings(text: str, label: str) -> list[str]:
"""Every amendment must sit ALONGSIDE the record it amends, never replace it."""
out: list[str] = []
heads = RE_HEAD.findall(text)
originals = {n for n, rest in heads
if not rest.strip().upper().startswith("AMENDMENT")}
for n, rest in heads:
if rest.strip().upper().startswith("AMENDMENT") and n not in originals:
out.append(f"{label}: '## REVIEWED-{n} — AMENDMENT' exists with no "
f"un-amended REVIEWED-{n} entry — the amendment replaced "
f"the record it amends")
for n in sorted(set(RE_AMENDS.findall(text))):
if n not in originals:
out.append(f"{label}: a block declares '**Amends:** REVIEWED-{n}' but "
f"no REVIEWED-{n} entry exists in the file")
return out
reg_findings: list[str] = []
if REVIEWED_MD.exists():
reg_findings = register_findings(REVIEWED_MD.read_text(errors="replace"),
"REVIEWED.md")
# Controls. The third is the one that matters and is the lesson of the day:
# a check that has never fired on a known-bad input is unestablished, so the
# instrument is run against a synthetic reproduction of the actual failure.
_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")
control("register parser finds REVIEWED headings", len(RE_HEAD.findall(_GOOD)) == 2)
control("register check passes a correctly JOINED amendment",
not register_findings(_GOOD, "t"))
control("register check DETECTS an amendment that replaced its record "
"[reproduces the 2026-08-07 loss]",
len(register_findings(_BAD, "t")) == 2)
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)
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", "?")})
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 == "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))
fired = [d for d in deferrals if trigger_fired(d) is True]
manual = [d for d in deferrals if trigger_fired(d) is None]
_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))
# 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
# ------------------------------------------------------------- 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 REVIEWED_MD.exists():
n_am = sum(1 for _, r in RE_HEAD.findall(REVIEWED_MD.read_text(errors="replace"))
if r.strip().upper().startswith("AMENDMENT"))
print(f"✓ register integrity: every amendment link resolves "
f"({n_am} amendment(s) checked)")
# 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(deferrals)} 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(deferrals) - len(manual)
print(f"✓ deferred decisions: {len(deferrals)} tracked, none due "
f"({waiting} checkable, {len(manual)} manual-only)")
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.")
sys.exit(0)