From 97ae59a0d3c25c30b41787164bade28215cbbc2f Mon Sep 17 00:00:00 2001 From: David F Glidden Date: Fri, 7 Aug 2026 17:32:51 +0200 Subject: [PATCH] [FIX] deferred decisions: check the trigger instead of remembering it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2026-05-16 jurist settlement 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 for another purpose. The steward's stated reason for settling it today was not the format question at all: "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 check 8 belongs here rather than in a new register. A deferred decision now declares a machine-checkable trigger in a comment block: `manual` never auto-fires and is listed rather than checked — an honest way to record a deferral whose condition cannot be mechanised, instead of inventing a proxy. Proxies are the failure being fixed: the old trigger stood in for "behavioural evidence on high-fidelity sources" and came true without producing any, because neither named test case was ever manifested. Scans */docs/**/*.md under ~/_Dev and ~/dotfiles; glob and path-exists resolve against the containing repo's root. First and only entry today is D-5 (tei-native), correctly reported as not due — no protocol spec exists yet. Controls, five, per the standing epistemic standard. The load-bearing one is the discriminating half: the evaluator must NOT fire on an unmet condition, because a checker that fires on everything reports nothing. Red-witnessed end-to-end by temporarily pointing D-5's trigger at a path that does exist: reported COME DUE with slug, owner, deferral date, trigger and file; restored after, and the spec's working tree verified clean. Also fixed in passing: this file's own report block was briefly duplicated and misplaced by a `str.replace` without a count, which substituted both `sys.exit(0)` occurrences including the early-exit branch. Caught by reading the output — the deferred-decisions line printed twice. Wake-up §2.c updated to describe all three of the script's reports, and to require that a COME DUE item be surfaced in the briefing under "What's unresolved". That is a change to the wake protocol, not only to a description: a mechanism nobody reads is not a mechanism. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NEWjLBP4quXbDPDL2byEzZ --- scripts/governance-drift-check.py | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/scripts/governance-drift-check.py b/scripts/governance-drift-check.py index 97cd3c1..3b5bbc0 100755 --- a/scripts/governance-drift-check.py +++ b/scripts/governance-drift-check.py @@ -15,6 +15,7 @@ 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 @@ -255,6 +256,102 @@ control("register check DETECTS an amendment that replaced its record " 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. +# +# +# +# `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"", re.S) + +SCAN_ROOTS = [HOME / "_Dev", HOME / "dotfiles"] +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 + return None + + +for root in SCAN_ROOTS: + if not root.is_dir(): + continue + for f in root.glob("*/docs/**/*.md"): + try: + if f.stat().st_size > 400_000: + 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 = ("") +_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("", CLAUDE_MD)) +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)) + + # ------------------------------------------------------------- report failed_controls = [lbl for lbl, ok in controls if not ok] if failed_controls: @@ -290,4 +387,18 @@ elif REVIEWED_MD.exists(): print(f"✓ register integrity: every amendment link resolves " f"({n_am} amendment(s) 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)") + sys.exit(0)