diff --git a/.claude/doctrine-watermark b/.claude/doctrine-watermark new file mode 100644 index 0000000..f4becae --- /dev/null +++ b/.claude/doctrine-watermark @@ -0,0 +1 @@ +b2a3d4706516189d17533e30c9115d8c65b99d64 diff --git a/tools/runmarker.py b/tools/runmarker.py index c8a3b84..e031f09 100755 --- a/tools/runmarker.py +++ b/tools/runmarker.py @@ -49,16 +49,52 @@ ... result("ESTABLISHED-NOTHING") # on every controlled path """ +import subprocess import sys PREFIX_RUN = "NFORMA-RUN" PREFIX_RESULT = "NFORMA-RESULT" +def tree_distance(): + """How far is THIS WORKING TREE behind `origin/main`? "" when unmeasurable. + + ⛔ WHY THE MARKER CARRIES IT. #205: nine panes share one working tree, and that tree has + been pinned at `a163854` for two days — **365 commits behind**, with `CLAUDE.md` differing by + 25 lines and `goals/` by 2046. Every reading taken there is a reading of a two-day-old + repository, and nothing said so. + + ⇒ AND CI CANNOT DETECT IT, STRUCTURALLY. A workflow checks out fresh, so a gate NEVER sees a + pane's working tree. That is why #205 outlived every other missing-caller gap tonight: each + of those was closed by adding a caller in CI, and this one cannot be. The caller has to run + WHERE THE STALENESS LIVES — and 13 instruments already import this module, so they are it. + + ★ `tools/doctrine-uncommitted.py` fires on exactly this condition and has reported it to + nobody for two days: `git grep -l` finds four references — the tool, its test, its docs, its + ledger. A detector with no caller and a rule with no enforcement fail identically. + + ⚠ COSTS 14ms, measured. And it is REPORTED, NEVER ENFORCED: a stale tree is not an error and + this must not turn one into a failure. It is the label a reading always needed — the same + move as `ON ` in the gate summary, applied to the tree instead of the machine. + """ + try: + r = subprocess.run(["git", "rev-list", "--count", "HEAD..origin/main"], + capture_output=True, text=True, timeout=5) + except (OSError, subprocess.SubprocessError): + return "" + n = r.stdout.strip() + if r.returncode != 0 or not n.isdigit(): + # ⛔ UNMEASURABLE IS NOT ZERO. No repository, no `origin/main`, a failed call — each + # would read as "current" if this returned 0, which is the confident-wrong-answer this + # whole convention exists against. + return " tree=UNKNOWN" + return "" if n == "0" else f" tree={n}-behind-origin/main" + + def begin(tool): """Emit the start marker. ⛔ Call before argument parsing, or the marker cannot separate a rejected flag from a refused file.""" - print(f"{PREFIX_RUN} {tool}", file=sys.stderr, flush=True) + print(f"{PREFIX_RUN} {tool}{tree_distance()}", file=sys.stderr, flush=True) def result(state): diff --git a/tools/test_runmarker.py b/tools/test_runmarker.py index f9fa85b..5c9e49b 100755 --- a/tools/test_runmarker.py +++ b/tools/test_runmarker.py @@ -116,5 +116,46 @@ def test_markers_survive_the_construct_that_destroys_the_exit_code(self): self.assertIn("NFORMA-RESULT", err) + +def test_tree_distance_is_silent_when_current_and_never_guesses(): + """⛔ THREE STATES, and the third is the one that matters (#205). + + The shared tree has been pinned 365 commits behind `origin/main` for two days, with + CLAUDE.md differing by 25 lines and goals/ by 2046 — and nothing said so. ⇒ CI cannot + detect that: a workflow checks out fresh, so a gate NEVER sees a pane's working tree. + The marker can, because 13 instruments import this module and run IN the pane. + + ⚠ UNMEASURABLE MUST NOT READ AS CURRENT. Returning 0 for "no repository" or "no + origin/main" would report the confident wrong answer this convention exists against. + """ + import subprocess, tempfile, os + import runmarker + + # current tree -> SILENT. A label on every line is a label nobody reads. + here = runmarker.tree_distance() + assert here == "" or "behind-origin/main" in here or here == " tree=UNKNOWN", here + + # ⛔ no repository at all -> UNKNOWN, never 0 + with tempfile.TemporaryDirectory() as d: + cwd = os.getcwd() + try: + os.chdir(d) + assert runmarker.tree_distance() == " tree=UNKNOWN" + finally: + os.chdir(cwd) + + +def test_begin_carries_the_distance_into_the_marker(): + """The distance is useless if it does not reach the line a reader sees.""" + import io, contextlib, runmarker + buf = io.StringIO() + with contextlib.redirect_stderr(buf): + runmarker.begin("probe") + line = buf.getvalue().strip() + assert line.startswith("NFORMA-RUN probe"), line + # ⚠ asserts the SHAPE, not the number — the number is a property of the tree, and a + # control coupled to it would fail every time someone merged. + assert line == "NFORMA-RUN probe" or " tree=" in line, line + if __name__ == "__main__": unittest.main(verbosity=2)