From 0b9149b499fc4cbb24c8773bf8b2531ad9b5ec74 Mon Sep 17 00:00:00 2001 From: Jonathan Borduas Date: Sun, 6 Sep 2026 03:53:26 +0100 Subject: [PATCH 1/2] #603: stop naming an inert remedy, and give the branch its first controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cannot-invoke-bare branch told the reader: it is a limit of the invocation, and it needs a `# NO-SELF-TEST:` line or an invocation this gate can derive. ⛔ Adding that line changes nothing on this path. The branch returns at :272; DECLARES_NONE is first consulted at :340, sixty-eight lines later. Two-poled on tools/check-freshness.py — declaration present, then renamed — the gate emits IDENTICAL BYTES. ⇒ #73's own doctrine, failing inside the gate: an absence report must name a remedy that WORKS. Naming an inert one is worse than naming none, because the reader does the work and gets no signal that it did nothing. I spent a cycle adding declarations to two files and measuring no change, which is how this was found. ⛔ AND THE BRANCH HAD ZERO CONTROLS. 17 checks in this file's own --self-test and not one reached it. That is why it could go on naming an inert remedy: the message was never exercised, so nothing could disagree with it. ⇒ THIS COMMIT DOES TWO THINGS AND CHANGES NO VERDICT: 1 The message now states what is true — that a `# NO-SELF-TEST:` line does NOT change this verdict, WHY (this branch returns before that line is read), that it was measured two-poled, and what would change it. It also says the control may exist and be gated elsewhere while this gate cannot see it, because that is exactly the case for check-freshness and job-log. 2 Two controls, both poles: required args, no declaration -> UNESTABLISHED, "limit of the invocation" required args, WITH declaration -> UNESTABLISHED, "DOES NOT CHANGE THIS VERDICT" The second is the known-negative: it asserts the declaration does not silently rescue the subject AND that the run SAYS SO. Without it the fix is a comment nobody re-checks. ⚠ WHAT THIS DELIBERATELY DOES NOT DO. It does not make the declaration work here. The branch's own comment gives a real reason to be strict — "folding that into 'has no self-test' would be a claim about the world made from a limit of the invocation" — and whether a declaration should satisfy it is the gate owner's call, not mine. #603 carries that choice and the message now points at it. This is the half that needs no semantic decision. MEASURED, before and after, unchanged: ran 60 subject(s): 41 control(s) passed · 0 FAILED · 3 UNESTABLISHED · 7 UNVERIFIABLE · 0 TIMED OUT gate exit 2 --self-test 19 checks (was 17), exit 0 · exit-code-gate 0 · check-tools-index 0 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ --- scripts/gate-selftests.sh | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/scripts/gate-selftests.sh b/scripts/gate-selftests.sh index 238bc1c..66f6abe 100755 --- a/scripts/gate-selftests.sh +++ b/scripts/gate-selftests.sh @@ -272,8 +272,16 @@ classify_one() { echo "@@VERDICT unest $b" echo " ⚠ $b CANNOT BE INVOKED BARE — \`$FLAG\` alone is rejected for MISSING" echo " REQUIRED ARGUMENTS, so no control is reachable this way. ⛔ That is not" - echo " 'has no self-test'; it is a limit of the invocation, and it needs a" - echo " \`# NO-SELF-TEST:\` line or an invocation this gate can derive." + echo " 'has no self-test'; it is a limit of the invocation." + echo " ⛔ A \`# NO-SELF-TEST:\` LINE DOES NOT CHANGE THIS VERDICT. This branch" + echo " returns before that line is read — DECLARES_NONE is consulted further" + echo " down, on the flag-REJECTED path only. Measured 2026-09-06, two-poled:" + echo " identical bytes with the declaration present and with it renamed." + echo " ⇒ What WOULD change it: an invocation this gate can derive — the subject" + echo " accepting \`$FLAG\` with no other required argument. Until then the" + echo " control may exist and be gated elsewhere; this gate cannot SEE it." + echo " ⇒ nForma-AI/nForma-NEXT#603 carries the choice: honour the declaration" + echo " here, or keep this branch strict. Naming an inert remedy was the bug." return ;; esac @@ -622,6 +630,31 @@ selftest() { out=$(gate "$d" 'c_declared_broken.py' 2>&1); rc=$? check "a declaration does NOT rescue a control that FAILS" 1 "CONTROL FAILED" rm -f "$d/c_declared_broken.py" "$d/d_declared.py" + + # ⛔ THE CANNOT-INVOKE-BARE BRANCH HAD ZERO CONTROLS — 17 checks in this self-test and not + # one reached it, which is how it went on naming an inert remedy. #603. Both poles. + cat > "$d/req_args.py" <<'REQ' +#!/usr/bin/env python3 +"""A subject taking a REQUIRED argument, so `--self-test` alone cannot reach a control.""" +import argparse, sys +ap = argparse.ArgumentParser() +ap.add_argument("--repo", required=True) +ap.add_argument("--self-test", action="store_true") +ap.parse_args() +sys.exit(0) +REQ + out=$(gate "$d" 'req_args.py' 2>&1); rc=$? + check "a subject with REQUIRED ARGS is UNESTABLISHED, not 'has no self-test'" 2 \ + "CANNOT BE INVOKED BARE" "limit of the invocation" + + # ★ THE KNOWN-NEGATIVE THAT MAKES IT EVIDENCE: the declaration must not silently change the + # verdict, and the run must SAY SO rather than leaving a reader to discover it by measuring. + { echo '#!/usr/bin/env python3'; echo '# NO-SELF-TEST: planted; controlled elsewhere' + tail -n +2 "$d/req_args.py"; } > "$d/req_declared.py" + out=$(gate "$d" 'req_declared.py' 2>&1); rc=$? + check "a DECLARATION does not rescue a cannot-invoke-bare subject — and the run says so" 2 \ + "CANNOT BE INVOKED BARE" "DOES NOT CHANGE THIS VERDICT" + rm -f "$d/req_args.py" "$d/req_declared.py" printf '#!/usr/bin/env python3\n' > "$d/d_none.py" plant_py "$d/d_none.py" 'if a: void()' 'sys.exit(0)' From c5dbc2c72579878d975f587850dc6e241df6c233 Mon Sep 17 00:00:00 2001 From: Jonathan Borduas Date: Sun, 6 Sep 2026 04:02:32 +0100 Subject: [PATCH 2/2] =?UTF-8?q?review:=20DECLARES=5FNONE=20is=20consulted?= =?UTF-8?q?=20TWICE,=20not=20once=20=E2=80=94=20my=20parenthetical=20was?= =?UTF-8?q?=20wrong?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit on #604. The message I added said DECLARES_NONE is consulted "further down, on the flag-REJECTED path only" ⛔ False. It is consulted at :348 (flag-REJECTED) AND :368 (the exit-2 branch), and the cannot-invoke-bare branch preempts BOTH. Verified before adopting: grep -n DECLARES_NONE scripts/gate-selftests.sh 73:DECLARES_NONE="^# NO-SELF-TEST:" 348: if grep -q "$DECLARES_NONE" "$f"; then <- flag-REJECTED 368: 2) if grep -q "$DECLARES_NONE" "$f"; then <- exit-2 ⚠ The claim I got wrong was the PRECISE-SOUNDING half. "It returns before that line is read" was true and sufficient; I added "on the flag-REJECTED path only" as a helpful specific and never checked it. A parenthetical that narrows a true statement is a new claim, and it inherits none of the original's evidence. ⇒ Same family as everything else this branch is about: I have spent the day filing reports about labels read as states, and the defect I shipped into the fix was an unverified detail attached to a verified one. The corrected text names both paths and says this branch preempts both. Re-verified after the edit, all unchanged: --self-test 19 checks exit 0 · gate 60 subjects, 41/0/3/7/0, exit 2 exit-code-gate 0 · bash -n clean Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ --- scripts/gate-selftests.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/gate-selftests.sh b/scripts/gate-selftests.sh index 66f6abe..fbb7d9b 100755 --- a/scripts/gate-selftests.sh +++ b/scripts/gate-selftests.sh @@ -274,8 +274,9 @@ classify_one() { echo " REQUIRED ARGUMENTS, so no control is reachable this way. ⛔ That is not" echo " 'has no self-test'; it is a limit of the invocation." echo " ⛔ A \`# NO-SELF-TEST:\` LINE DOES NOT CHANGE THIS VERDICT. This branch" - echo " returns before that line is read — DECLARES_NONE is consulted further" - echo " down, on the flag-REJECTED path only. Measured 2026-09-06, two-poled:" + echo " returns before that line is read. DECLARES_NONE is consulted TWICE" + echo " further down — the flag-REJECTED path and the exit-2 path — and this" + echo " branch preempts BOTH. Measured 2026-09-06, two-poled:" echo " identical bytes with the declaration present and with it renamed." echo " ⇒ What WOULD change it: an invocation this gate can derive — the subject" echo " accepting \`$FLAG\` with no other required argument. Until then the"