[HARDENING] STATE-CLAIM + the resolution state, built together (REVIEWED-127)

Both halves of the schema, shipped in one change because the ruling said half a schema
invites a third patch and a third patch is how a vocabulary accretes instead of being
designed.

157 — resolution state. `resolved:` on any block; a resolved block is no longer due but
is NOT dropped: it prints as a closed ledger, because a discharge that vanishes from the
report is its own decay. The pointer must RESOLVE — a real path or a real git object —
so an undocumented discharge is impossible to express rather than merely discouraged. A
dangling pointer reports in the register-integrity lane, the same lane as an amendment
that replaced the record it amends; both are a record closing over its own history.

The 25th's hand-rename is MIGRATED back to DEFERRED-DECISION with resolved: set. That
block was the per-instance workaround 157 was filed against, and it is now the
migration's own test case.

158 — STATE-CLAIM. 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. Two new trigger kinds earned directly from today's instances: text-present (the
trial-09 hold, falsified by REVIEWED-124's existence) and file-changed-since ("the filed
rule not edited", false one hour after writing).

16 new controls, each with its discriminating half — fires on met, silent on unmet,
manual listed-never-fired, resolved excluded from due-ness, the SAME block unresolved
still due, a real pointer resolves, "yes, done" does not.

Proven on the LIVE blocks, not only fixtures: pointing the state-claim at an older
commit made it report FALSIFIED by name; replacing the resolution with "yes done" made
register-integrity report it; both restored and both returned to quiet.

⚠ One control failed before shipping and the failure was the useful part. The negative
control for file-changed-since pointed at FOOL-SEED-RULE.md, which this same session then
edited — so "unchanged since HEAD" broke, correctly. A control whose subject is "did this
file change" must not point at a file the session is changing. Re-pointed at a frozen
2026-08-02 trial artifact, with the reason recorded at the fixture. Caught because the
controls run on every invocation rather than in a separate suite.

First two real state-claims filed, deliberately one of each kind: ~/CLAUDE.md untouched
under PENDING-150, mechanically watched and [ESCALATE]-grade the moment it goes false;
and §9's channel unbuilt, marked `manual` because it has no filename yet and inventing a
proxy falsifier is the error the schema's own comment warns against.

⚠ The zero-state prints a WARNING, not a tick: "0 marked, NOT none-stale" with the ~57
unmarked candidates named as a grep. An instrument that reads nothing reports exactly
like one that finds nothing, and that is the failure this item exists to end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J6hZXNYSxEfZseBGTni4sf
This commit is contained in:
David F Glidden
2026-08-25 15:29:59 +02:00
co-authored by Claude Opus 5
parent 0dcf304486
commit 063eccfd80
4 changed files with 247 additions and 17 deletions
+10 -10
View File
@@ -4252,20 +4252,20 @@ Jurist: *"Confirm that lowercasing is applied at exactly one point in the code a
| PENDING-89 source (iv) | **AUTHORIZED to read** — 55 files; answers generation diversity only, never correlation of misses |
| §13.6 block | ⚠ **LIFTED** — its stated condition (§5 ratification) is met |
<!-- DISCHARGED-DECISION: fool-beacon-derivation-run-once
<!-- DEFERRED-DECISION: fool-beacon-derivation-run-once
since: 2026-08-22
trigger: date 2026-08-25 — FIRED. Announced BY NAME at the 2026-08-25 wake, which is
the first time this mechanism carried a real one rather than a rehearsed one.
trigger: date 2026-08-25
owner: executor
discharged: 2026-08-25 — the derivation ran ONCE. Record:
claude/governance/fool/seed/FOOL-BONES-2026-08-25.md, commit 5694b925.
RENAMED, not deleted: governance-drift-check.py's parser has no `resolved:` field, so a
taken decision would be reported COME DUE forever and the instrument would degrade into
crying wolf. Renaming the key stops the report while leaving the record that the trigger
existed, fired, and was answered. If a `resolved:` field is ever added, this reverts to
DEFERRED-DECISION with that field set.
resolved: 2026-08-25 — ran once; claude/governance/fool/seed/FOOL-BONES-2026-08-25.md, commit 5694b925
-->
⚠ **MIGRATED 2026-08-25 under REVIEWED-127.** This block was closed on the 25th by
hand-renaming its key to `DISCHARGED-DECISION`, because the schema had no way to say
*answered*. That rename was the per-instance workaround PENDING-157 was filed against, and
it is now reverted: the key is `DEFERRED-DECISION` again and the closure is carried by a
`resolved:` field the checker reads. **This block is the migration's own test case** — the
first resolved deferral, and the one whose pointer must resolve.
⚠ **TRIGGER BLOCK ADDED 2026-08-24.** Until today this run-once, irreversible, dated act had **no
trigger of any kind** — no cron, no launchd, no scheduled agent, and not a tracked
DEFERRED-DECISION. It was one of the 105 prose deferrals the drift-check reports as carrying no
+10 -1
View File
@@ -299,7 +299,16 @@ which is the entire point of having filed it early. Current state lives in
- ~~No bones have been derived. No soul has been generated.~~
→ **Bones derived 2026-08-25**, once, commit `5694b925`. **No soul generated** — that
remains true, and §7 of the doctrine is unrun.
- `~/CLAUDE.md` has **not** been touched (PENDING-150, unbundled).
- `~/CLAUDE.md` has **not** been touched (PENDING-150, unbundled). — **still true, and now watched.**
<!-- STATE-CLAIM: claude-md-untouched-pending-150
since: 2026-08-22
claims: ~/CLAUDE.md has not been touched under PENDING-149; PENDING-150 stays unbundled
falsified-by: file-changed-since d6377af CLAUDE.md
-->
*(The first negative state-claim in this system to carry a falsifier. It is the one bullet
in §6 still true, it is `[ESCALATE]`-grade the moment it stops being true, and until today
nothing in this system would have noticed. REVIEWED-127.)*
- Nothing has been implemented of §8, §8a or §9 — the status line is confirmed free but
not built.
@@ -207,6 +207,16 @@ both went false within hours of being written and neither was caught by any mech
- §8a body — unbuilt; condition 2 (variation from time or nothing, never content) unmet.
- §9 channel — unbuilt. **Tarbuckle has a name, bones and a voice, and nowhere yet to be
silent.**
<!-- STATE-CLAIM: fool-channel-unbuilt
since: 2026-08-25
claims: §9's channel is unbuilt; Tarbuckle has nowhere to speak or be silent
falsified-by: manual
-->
*(Deliberately `manual`. §9's channel has no filename yet, so there is no path to test and
no string to match — inventing a proxy falsifier would be exactly the error this schema's
own comment warns against. `manual` is listed and never fired: the claim is tracked AND
known to be unwatched, rather than merely looking watched. REVIEWED-127.)*
- No second generation. No hand edit.
- ~~No regeneration ruling.~~ → **Ruled 2026-08-25** (§4a): the glosses were never a §7
input, so there is no implementation error to demonstrate and §4 REGENERATION is not
+217 -6
View File
@@ -20,6 +20,7 @@ import json
import os
import re
import signal
import subprocess
import sys
from datetime import date
from pathlib import Path
@@ -282,6 +283,55 @@ control("register file is reachable", REVIEWED_MD.exists())
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,
@@ -306,7 +356,22 @@ def parse_deferrals(text: str, src: Path) -> list[dict]:
out.append({"slug": slug, "file": src, "root": _repo_root(src),
"trigger": fields.get("trigger", "manual"),
"owner": fields.get("owner", "?"),
"since": fields.get("since", "?")})
"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
@@ -321,6 +386,33 @@ def trigger_fired(d: dict) -> bool | None:
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
@@ -384,9 +476,27 @@ for root, pattern in _scan_targets:
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))
fired = [d for d in deferrals if trigger_fired(d) is True]
manual = [d for d in deferrals if trigger_fired(d) is None]
# 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-->")
@@ -428,6 +538,70 @@ control("trigger evaluator does NOT fire on an unmet condition "
_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"):
@@ -580,16 +754,21 @@ else:
# 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")
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(deferrals) - len(manual)
print(f"✓ deferred decisions: {len(deferrals)} tracked, none due "
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 "
@@ -605,4 +784,36 @@ if deferrals:
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.")
sys.exit(0)