diff --git a/scripts/governance-drift-check.py b/scripts/governance-drift-check.py index 33c8e48..5447545 100755 --- a/scripts/governance-drift-check.py +++ b/scripts/governance-drift-check.py @@ -213,48 +213,137 @@ control("doctrine-id citing surface is reachable", # 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" -RE_HEAD = re.compile(r"^##\s+REVIEWED-(\d+)\s*[—-]\s*(.*)$", re.M) -RE_AMENDS = re.compile(r"\*\*Amends:\*\*\s*REVIEWED-(\d+)") +# WIDENED 2026-08-31 under REVIEWED-132 (PENDING-173 + ADDENDUM 1). The check was +# narrowed three times without declaring any of them: the header regex and the +# **Amends:** regex both hard-coded REVIEWED-, and the amendment test was +# startswith("AMENDMENT"). It saw 3 of the 81 amendment-shaped blocks in the +# registers, and — worse — an ADDENDUM was classified as an ORIGINAL, so it could +# have satisfied its own "an un-amended entry exists" test on behalf of a record +# that had been replaced. +# +# ⚠ WIDENING IS THE FLOOR, NOT THE FIX. 48 of the 81 blocks carry NO item number +# (`### AMENDMENT 1 — 2026-08-08`) and are attributable only by which header sits +# above them. No id-keyed reader can check those; they are reported NOT ESTABLISHED, +# never passed. The prospective-convention question is PENDING-146's, deliberately +# not decided here (REVIEWED-132 condition 5). +MARKERS = ("AMENDMENT", "ADDENDUM") +RE_MARKER_TOKEN = re.compile(r"\b(?:AMENDMENT|ADDENDUM)\b") +RE_HEAD_LINE = re.compile(r"^#{2,3}\s+(.*)$", re.M) +RE_ID = re.compile(r"^((?:PENDING|REVIEWED|COMPLETED)\S*)\s*[—–-]\s*(.*)$") +RE_INBODY = re.compile(r"^\*\*(?:AMENDMENT|ADDENDUM)\b.*$", re.M) +RE_AMENDS = re.compile(r"\*\*Amends:\*\*\s*((?:PENDING|REVIEWED|COMPLETED)-\S+?)[\s,.(]") + +# Every form the controls below actually exercise. A form found in the record and +# absent from this set is reported NOT ESTABLISHED rather than silently passed +# (REVIEWED-132 condition 2) — the check may not certify what it has never been shown. +FORMS_COVERED = {"id+marker", "id-only", "bare-marker", "in-body", "compound"} -def 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 +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 -reg_findings: list[str] = [] -if REVIEWED_MD.exists(): - reg_findings = register_findings(REVIEWED_MD.read_text(errors="replace"), - "REVIEWED.md") +def register_scan(texts: dict) -> tuple: + """-> (findings, unattributable, forms_seen). -# 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. + `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") -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")) +_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(register_findings(_BAD, "t")) == 2) + "[reproduces the 2026-08-07 loss]", len(_f_bad) == 2) +control("register check DETECTS a replacing ADDENDUM [the word the old test could " + "not see; it classified this as an ORIGINAL and passed]", len(_f_add) == 1) +control("register check passes a JOINED ### addendum [must-not-flag: ### depth is " + "the register's own placement form]", not _f_addok) +control("an unnumbered '### AMENDMENT 1' is NOT ESTABLISHED, never passed", + any(f == "bare-marker" for _l, f, _t in _u_bare) and not _f_bare) +_PROSE = "## PENDING-60 — Citation amendment (#2): specify the CTS-URN model\n" +_f_prose, _, _fm_prose = register_scan({"t": _PROSE}) +control("a lower-case 'amendment' in a TITLE stays an ORIGINAL [must-not-flag: the " + "mirror bug — it would strike six real originals off the list]", + "id-only" in _fm_prose and not _f_prose) +control("an UPPER-CASE marker mid-title is still excluded from originals [must-detect]", + "compound" in register_scan( + {"t": "## PENDING-164 — BUILD RECORD + AMENDMENT 1 result\n"})[2]) +control("PENDING-side registers are actually read", "PENDING.md" in _TEXTS) +control("every form present in the record is covered by a control", not reg_uncovered) control("register file is reachable", REVIEWED_MD.exists()) @@ -779,11 +868,32 @@ if 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)") +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: