From d9ba2c73b172f9ef87633341e1399663133b64a7 Mon Sep 17 00:00:00 2001 From: Fabian Hamm Date: Mon, 14 Sep 2026 21:18:55 +0200 Subject: [PATCH] fix(re): the capture checker counted its own selftest fixture as a citation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check-capture-citations` exits 1 on a clean checkout of this branch, and would have kept doing so for ever: 🔴 cited but NOT committed: 1 docs/re/captures/no-such-file-anywhere.png `SEARCH` includes `tools`, so the scan reads this file -- and `selftest()`'s planted fixture is a literal capture path. The checker cited its own test data, to a file that will never exist by construction. A gate that is red before anyone changes anything teaches people to ignore it, which is the opposite of what Phase 4 built it for. Excluding only this one file (`SELF`) keeps every other tool in scope. A real citation belongs in a page, or in a tool that opens the capture -- never in the checker itself. Added a selftest case for it, because this is exactly the class of bug the rest of this file already documents surviving: the ERE that matched nothing while five ticks stayed green. Verified the guard can fail, rather than assuming it: exclusion neutralised -> "own fixtures not counted 🔴 FAILED", selftest rc=2, real check rc=1 reproducing the bug exclusion restored -> selftest ok (7 cases), check rc=0 After the fix, on this branch: captures committed : 212 cited by a page or a tool : 212 cited by nothing : 0 🔴 cited but NOT committed: 0 rc=0 which is also the first independent confirmation of Phase 4's "212 captures, all cited" -- the checker now agrees with the page. Co-Authored-By: Claude Opus 5 --- tools/re/check-capture-citations | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/re/check-capture-citations b/tools/re/check-capture-citations index 616f6018..e691e7c8 100755 --- a/tools/re/check-capture-citations +++ b/tools/re/check-capture-citations @@ -41,6 +41,14 @@ CITE = re.compile(r"(?:docs/re/)?captures/[A-Za-z0-9._/-]+") CITE_ERE = r"(docs/re/)?captures/[A-Za-z0-9._/-]+" # Where a citation may live. Prose AND code: tests and tools open these files. SEARCH = ["docs", "crates", "tools", "port", "authored"] +# 🔴 AND NOT THIS FILE. `SEARCH` includes `tools`, so the scan read *itself* -- +# and `selftest()`'s planted fixture below is a literal capture path. The check +# therefore reported a dangling citation to a file that will never exist, and +# exited 1 on a clean checkout, for ever. A gate that is red before anyone +# changes anything teaches people to ignore it. +# Excluding only this one file keeps every other tool in scope; a real citation +# belongs in a page or a tool that opens the capture, never in the checker. +SELF = ":!tools/re/check-capture-citations" def git(*args: str) -> str: @@ -67,7 +75,7 @@ def committed() -> tuple[set[str], set[str]]: def cited() -> set[str]: - raw = git("grep", "-rhoE", CITE_ERE, "--", *SEARCH).splitlines() + raw = git("grep", "-rhoE", CITE_ERE, "--", *SEARCH, SELF).splitlines() out = set() for line in raw: c = line.split(":")[-1] @@ -98,7 +106,7 @@ def named_bare() -> set[str]: out = set() for line in git("grep", "-rhoE", r"[A-Za-z0-9._-]+\.(png|jpg|csv|log|txt|json|jsonl|bin|tsv)", - "--", *SEARCH).splitlines(): + "--", *SEARCH, SELF).splitlines(): out.add(line.split(":")[-1].strip()) return out @@ -151,7 +159,16 @@ def selftest() -> int: print(" %-34s %s (%d citations found)" % ("citation gathering works", "ok" if gathering_ok else "🔴 FAILED", len(refs))) - ok = gathering_ok + # 🔴 REGRESSION GUARD FOR `SELF`. The planted fixture above is a literal + # capture path living in a file `SEARCH` covers. Before the exclusion + # existed the scan counted it as a real citation, so the check reported a + # dangling capture and exited 1 on a clean tree — permanently. If anyone + # drops `SELF`, this goes red here instead of in everybody else's run. + fixture_counted = cases[0][1] in refs + print(" %-34s %s" + % ("own fixtures not counted", "🔴 FAILED" if fixture_counted else "ok")) + + ok = gathering_ok and not fixture_counted for name, path, want in cases: got = resolves(path.rstrip(".,;:)`"), files, dirs) mark = "ok" if got == want else "🔴 FAILED"