[FIX] source_has: close the polarity source_lacks missed (PENDING-180)

PENDING-180's own order — (c) census, (b) counterpart, (a) instance.

The census swept 15 positive-form source assertions across 8 governed scripts
and found exactly one self-planted needle: the one the item filed.
tarbuckle-seam.py:165 searched its OWN source for a string that lives only in
tarbuckle-mumble.py:142, so it could never have found it and could only ever
pass on the copy it had planted in itself. The polarity argument predicted
siblings; there are none. The sweep bounds the problem from below — a needle
assembled from parts inside a defective control is invisible to it.

source_has() joins its needle from parts exactly as source_lacks() does, and
ships with the must-fail arm the negative form has had since it was written.
The seam control is re-aimed at the writer's file and split into two arms that
assert different things, plus S3nn.

Verified by mutation rather than by a green selftest: the repaired control
FAILS on both mutations, the old form PASSES the one that matters. 136 controls
green across the five surfaces.

The first mutation did not fail on its first run. The repair's own explanatory
comment named the truncation literally and planted a contiguous copy in the
file being searched — the bug re-created inside the sentence explaining it.
source_lacks/source_has cover the ASSERTION's needle, never the file; prose can
plant one. Warned at the site, and filed as a separate decidable question.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsN7nKHjKBsn5bfNRRCNmo
This commit is contained in:
David F Glidden
2026-09-09 21:55:51 +02:00
co-authored by Claude Opus 5
parent bf1bd802a2
commit 049ca57a35
2 changed files with 46 additions and 3 deletions
+27
View File
@@ -191,6 +191,26 @@ def source_lacks(path: str, *parts: str) -> bool:
return "".join(parts) not in open(path, encoding="utf-8").read() return "".join(parts) not in open(path, encoding="utf-8").read()
def source_has(path: str, *parts: str) -> bool:
"""True if the joined needle DOES appear in `path`. The positive counterpart.
⚠ THIS IS THE POLARITY `source_lacks()` DOES NOT COVER, AND IT IS THE ONE THAT
HIDES. Both bugs come from writing a needle as a literal, which plants it in the
file being searched. In the NEGATIVE form that makes the control always FAIL —
loudly, so it gets fixed. In the POSITIVE form it makes the control always PASS —
silently, forever. The louder failure got the mechanism first; this is the quieter
one. Assembling from parts is the whole protection, exactly as above.
⚠ WHAT IT STILL CANNOT SEE: whether the code it finds ever RUNS. A source control
is a claim about text, never about behaviour — `tarbuckle-seam.py:165` asserted the
rejection log keeps evidence and did not move when condition G made every rejection
write path inert (15/15 before, 15/15 after). Aim it at the file that holds the
code, and pair it with a behavioural control where activation is the question.
PENDING-180.
"""
return "".join(parts) in open(path, encoding="utf-8").read()
def soul_register() -> str | None: def soul_register() -> str | None:
"""The soul, verbatim, from the filed artifact. None if unreadable.""" """The soul, verbatim, from the filed artifact. None if unreadable."""
try: try:
@@ -466,6 +486,13 @@ def selftest() -> int:
ck("A4n the predicate can fail", ck("A4n the predicate can fail",
any(("PENDING" in q) for q in paths + ("/x/PENDING.md",))) any(("PENDING" in q) for q in paths + ("/x/PENDING.md",)))
# A10 — the positive-form source predicate, with the arm the negative form has had
# since it was built. A control that cannot be shown to fail is not evidence.
ck("A10 source_has finds a needle that is really there",
source_has(__file__, "def source_", "lacks("))
ck("A10n the predicate can fail",
not source_has(__file__, "def source_", "nowhere("))
for name in checks: for name in checks:
print(f" {'FAIL' if name in failed else 'ok '} {name}") print(f" {'FAIL' if name in failed else 'ok '} {name}")
print(f"{len(checks) - len(failed)}/{len(checks)} controls passed") print(f"{len(checks) - len(failed)}/{len(checks)} controls passed")
+19 -3
View File
@@ -41,7 +41,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# reimplementing is the "one canonical source" rule applied to a constraint: a second # reimplementing is the "one canonical source" rule applied to a constraint: a second
# copy of `acceptable()` is a second, quietly divergent standard. # copy of `acceptable()` is a second, quietly divergent standard.
from tarbuckle_mumble_shim import (acceptable, soul_register, session_material, # noqa: E402 from tarbuckle_mumble_shim import (acceptable, soul_register, session_material, # noqa: E402
source_lacks, muted, log_event, # noqa: E402 source_lacks, source_has, muted, log_event, # noqa: E402
echoes_soul, log_rejection, REJECTS) echoes_soul, log_rejection, REJECTS)
LAST_TICK = os.path.expanduser("~/.claude/state/tarbuckle-last-tick") LAST_TICK = os.path.expanduser("~/.claude/state/tarbuckle-last-tick")
@@ -161,8 +161,24 @@ def selftest() -> int:
ck("S3 generation is bounded", SEAM_TIMEOUT_S <= 20) ck("S3 generation is bounded", SEAM_TIMEOUT_S <= 20)
ck("S3 timeout path returns silence, not a line", ck("S3 timeout path returns silence, not a line",
"log_silence(f\"seam generation exceeded" in src and "TimeoutExpired" in src) "log_silence(f\"seam generation exceeded" in src and "TimeoutExpired" in src)
ck("S3 rejection log keeps the evidence, not just the verdict", # ⚠ AIMED AT THE WRITER'S FILE, NOT THIS ONE. The 200-character truncation lives
"line[:200]" in src and "log_silence(why, line)" in src) # in the canonical writer (tarbuckle-mumble.py); the old form searched THIS source
# for it, found only the copy it had planted in itself, and could only ever pass.
# ⚠⚠ AND THE NEEDLE CAN BE PLANTED BY PROSE. Naming that truncation literally in
# this comment re-created the bug in the sentence explaining it — caught by mutation
# test, not by the selftest. source_has/source_lacks assemble the ASSERTION's needle
# from parts; nothing stops a comment elsewhere in the file planting a contiguous
# copy. Do not write the literal here. PENDING-180.
# ⚠ AND IT IS A CLAIM ABOUT TEXT, NEVER ABOUT BEHAVIOUR: it did not move when
# condition G made every rejection write path inert. Activation is covered
# behaviourally by the generator's own G / A8 controls, not here.
import tarbuckle_mumble_shim as _shim
ck("S3 the canonical writer keeps the evidence, not just the verdict",
source_has(_shim.__file__, "line[:", "200]"))
ck("S3 this seam routes rejections through that writer",
source_has(__file__, "log_silence(", "why, line)"))
ck("S3nn the predicate can fail",
not source_has(__file__, "log_silence(", "why, absent)"))
ck("S3n no cached or fallback line exists", ck("S3n no cached or fallback line exists",
source_lacks(__file__, "FALLBACK", "_LINE")) source_lacks(__file__, "FALLBACK", "_LINE"))