Two rules that look unrelated and are one failure, plus the change that makes
the second enforceable.
1. A FINDING REACHES `main` BEFORE THE CODE THAT CITES IT. A citation resolving
only on a peer branch is dead the moment it merges. Not hypothetical: 495
decoder and 366 port commits sit off `main`, and `port/scripts/boot.gd`
already cites two docs/re pages present on neither its own branch nor main.
2. A CHECK MAY ONLY SOFTEN AGAINST A CONDITION IT CAN TEST -- the Pi agent's
wording, and better than mine, because it is applicable while writing rather
than a call to be vigilant. The mechanical form:
Can this branch tell the difference between "not yet" and "no longer"?
`gitea-protect --verify` printed ⚪ "not a collaborator (yet)" and continued,
so the only instrument checking Write-not-Admin could not report that gate
being REMOVED. `check-citations` reported peer citations instead of failing
them, because under the old topology that was unfixable from the container.
Both were correct AND kind when written; neither recorded that the kindness
had a scope. Nobody edits these into being wrong -- the world moves and the
allowance stays, which is why they survive review. The smell is leniency with
an expiry date nobody set; the fix is the testable-condition rule.
check-citations gains `--for-merge`, which turns the peer class into a failure.
A flag rather than a new default because BOTH readings are still live: mid-work
on a topic branch the peer class really is unfixable noise. What the old code
could not express is where the code is GOING, and that is a condition the caller
can state. Measured on this tree: 19 citations resolve only on a peer branch --
which is the size of the #7-depends-on-#8 edge, not the 2 I had counted in
boot.gd.
The selftest gains that third class, because a flag whose classification is
unexercised is the shape this rule exists to catch. Controlled: emptying
PEER_REFS makes the peer case collapse into "nowhere" and the selftest reports
🔴 BROKEN, rc=2.
⚠️ Pre-existing and NOT from this change: the default run already exits 1 on 4
citations of `export/...` paths. Those are the generated tree, gitignored by
design, and main's copy of the tool fails identically. The CITE regex treats
`export/` as a repo prefix. Reported, not fixed -- it is the port's file and its
call whether the regex or the citations are wrong.
145 lines
6.6 KiB
Python
Executable File
145 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Do the repo paths cited in `docs/port/*.md` actually resolve?
|
|
|
|
tools/port/check-citations # assert
|
|
tools/port/check-citations --selftest # can it fail?
|
|
|
|
`audit-kinds` checks citations in `authored/`. Nothing checked the PROSE, and
|
|
prose is where this port explains itself. A first run found **37 of 91**
|
|
non-resolving, 41 %, in two very different classes:
|
|
|
|
* **19 on the Decoder's topic branch** — real files, not merged here. Not
|
|
errors. A reader in this checkout still cannot follow them, which is worth
|
|
reporting and not worth failing on; the fix is a merge, not an edit.
|
|
* **7 that resolve NOWHERE** — `docs/BLOCKED.md`, `docs/FORMAT.md`,
|
|
`port/manifest.json`, `port/screens/title/*.json`. Left behind by the
|
|
monorepo move and the `export/` rename. Those are simply wrong: a reader
|
|
following one gets nothing, and nothing had ever told anyone.
|
|
|
|
So the two classes are separated and only the second fails. A check that failed
|
|
on the first would be red for a state nobody in this container can fix, which is
|
|
the shape the display guard exists to prevent.
|
|
|
|
⚠️ THE PEER-BRANCH CLASS IS THE OTHER AGENT'S POINT, TURNED ON MYSELF. They
|
|
observed that everything they hand over links into `docs/re/` files that live
|
|
only on their branch, so every link they send is dangling from here. The same is
|
|
true in reverse and neither of us was counting.
|
|
"""
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import glob
|
|
|
|
# A repo path with a file extension, optionally in backticks or a markdown link.
|
|
CITE = re.compile(
|
|
r"`?((?:docs|crates|port|tools|authored|export)/[\w./-]+"
|
|
r"\.(?:md|rs|gd|json|txt|py|tsv|csv))`?"
|
|
)
|
|
PEER_REFS = ("origin/auto/frame-blend-draw-path", "origin/main")
|
|
|
|
|
|
def on_a_ref(path: str) -> str | None:
|
|
"""The first ref that carries `path`, or None."""
|
|
for ref in PEER_REFS:
|
|
if subprocess.run(["git", "cat-file", "-e", f"{ref}:{path}"],
|
|
capture_output=True).returncode == 0:
|
|
return ref
|
|
return None
|
|
|
|
|
|
def scan(files):
|
|
resolves, peer, nowhere = 0, {}, {}
|
|
for p in files:
|
|
try:
|
|
text = open(p, encoding="utf-8").read()
|
|
except OSError:
|
|
continue
|
|
for m in sorted(set(CITE.findall(text))):
|
|
if os.path.exists(m):
|
|
resolves += 1
|
|
elif (ref := on_a_ref(m)):
|
|
peer.setdefault(m, (p, ref))
|
|
else:
|
|
nowhere.setdefault(m, p)
|
|
return resolves, peer, nowhere
|
|
|
|
|
|
def main() -> int:
|
|
if "--selftest" in sys.argv:
|
|
# 🔴 A CHECK THAT CANNOT FAIL IS NOT A CHECK. This plants a citation of a
|
|
# path that exists on no ref and requires the scanner to catch it, and a
|
|
# citation of a real file and requires it NOT to. Both directions,
|
|
# because a scanner that flagged everything would also "pass" the first.
|
|
tmp = os.path.join(os.environ.get("TMPDIR", "/tmp"), "check-citations-selftest")
|
|
os.makedirs(tmp, exist_ok=True)
|
|
bad = os.path.join(tmp, "bad.md")
|
|
open(bad, "w").write("see `docs/port/this-file-does-not-exist-anywhere.md`\n")
|
|
good = os.path.join(tmp, "good.md")
|
|
open(good, "w").write("see `docs/port/PORT-MISSION.md`\n")
|
|
# The THIRD class, which `--for-merge` turns into a failure. It has to be
|
|
# told apart from both others: a peer citation is not dangling (the file
|
|
# exists) and does not resolve here (the reader still gets nothing), and
|
|
# a scanner that collapsed it into either would make the flag meaningless
|
|
# while still passing the two checks above.
|
|
peerfile = os.path.join(tmp, "peer.md")
|
|
open(peerfile, "w").write("see `docs/re/f5-a-press-snaps-the-plate.md`\n")
|
|
rp, pp, np_ = scan([peerfile])
|
|
|
|
_, _, nb = scan([bad])
|
|
r, _, ng = scan([good])
|
|
caught = len(nb) == 1
|
|
passed = len(ng) == 0 and r == 1
|
|
peer_ok = len(pp) == 1 and rp == 0 and len(np_) == 0
|
|
ok = caught and passed and peer_ok
|
|
print("selftest: planted dangling caught=%s, real citation passed=%s, "
|
|
"peer-branch classed separately=%s -> %s"
|
|
% (caught, passed, peer_ok, "ok" if ok else "🔴 BROKEN"))
|
|
if not peer_ok:
|
|
print(" 🔴 --for-merge cannot mean anything if the peer class is "
|
|
"not distinguished; got resolves=%d peer=%d nowhere=%d"
|
|
% (rp, len(pp), len(np_)))
|
|
return 0 if ok else 2
|
|
|
|
files = sorted(glob.glob("docs/port/*.md"))
|
|
resolves, peer, nowhere = scan(files)
|
|
total = resolves + len(peer) + len(nowhere)
|
|
print("citations of repo paths in docs/port/*.md: %d" % total)
|
|
print(" resolve here : %d" % resolves)
|
|
# 🔴 --for-merge TURNS THE PEER CLASS INTO A FAILURE.
|
|
#
|
|
# Reporting-not-failing was right when it was written: a peer-branch
|
|
# citation was "a state nobody in this container can fix", so failing on it
|
|
# would have been red for something unactionable. Under the pull-request
|
|
# workflow that stopped being true -- a PR into `main` is EXACTLY where it
|
|
# becomes fixable, by opening the finding's PR first and depending on it.
|
|
# The citation is dead the moment this merges, so the merge is the last
|
|
# place the leniency can still be withdrawn.
|
|
#
|
|
# Left as a flag rather than made unconditional, because both readings are
|
|
# still live: mid-work on a topic branch the peer class really is unfixable
|
|
# noise. The difference the old code could not express is WHERE the code is
|
|
# going, and that is a condition the caller can state.
|
|
merging = "--for-merge" in sys.argv
|
|
label = "🔴 FAILS (--for-merge)" if merging else "reported, not failed"
|
|
print(" on a peer branch, not merged: %d (%s)" % (len(peer), label))
|
|
for m, (src, ref) in sorted(peer.items()):
|
|
print(" %-52s %s <- %s" % (m, ref.split("/")[-1], os.path.basename(src)))
|
|
if peer and merging:
|
|
print("\n🔴 %d citation(s) resolve only on a peer branch." % len(peer))
|
|
print(" After this merges they resolve NOWHERE -- the reader gets a dead")
|
|
print(" path. Land the finding first and make it a dependency of this PR.")
|
|
return 1
|
|
if nowhere:
|
|
print(" 🔴 resolve NOWHERE : %d" % len(nowhere))
|
|
for m, src in sorted(nowhere.items()):
|
|
print(" %-52s <- %s" % (m, os.path.basename(src)))
|
|
print("\n🔴 a reader following those gets nothing. Fix the path or drop the citation.")
|
|
return 1
|
|
print(" 🔴 resolve nowhere : 0")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|