#73: the caller — a ratchet under UNNAMED refusals, because the missing leg was authority - #599
Merged
Merged
Conversation
|
Warning Review limit reachedNext included review available in 13 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (4)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
jobordu
added a commit
that referenced
this pull request
Sep 6, 2026
…time (#600) Measured on PR #599's own CI run: ⛔ 5 age at merge 9s since creation -> the advisory job went red A workflow triggered BY a PR's creation observes an age of seconds BY CONSTRUCTION, and it is not merging, so "was this old enough at merge?" is not a question it can ask. ⛔ THIS IS THE THIRD TIME, AND THAT IS THE FINDING. merge-guard has three legs that are facts about THE MERGE EVENT rather than about the PR: 0 holder == session a runner has no holder session fixed 1st 2 required gate it is INSIDE the run it asks about fixed 2nd 5 age at merge there is no merge yet fixed HERE ⚠ I fixed 0, then 2, and each time treated it as a one-off. I never re-read the remaining legs asking "which others have this property?" — so the same defect shipped three times in one tool, and each red cost a CI round trip. One instance is a bug; three is a class, and the class was visible after the first. ⇒ `--shape-only` now evaluates ONLY the shape legs — 1 base, 3 reviews, 4 three-dot diff — and NAMES each omission rather than passing it silently. ★ The control is a PAIR on identical input, because a skip with no known-negative is a bypass: seconds-old PR + --shape-only -> exit 0, "a merge that has not happened" seconds-old PR, no --shape-only -> exit 1, leg 5 BLOCKS #224 measured 25 of 100 PRs merged inside 60s of creation. That guard is the reason leg 5 exists, and the second test asserts the exemption did not weaken it. Suite 26/26 (was 24). Claude-Session: https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ Co-authored-by: Jonathan Borduas <jonathan.borduas@toolkit3d.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
disposition-scan.py built #73's predicate, planted it four ways, caught a use-versus-mention bug in itself — then stopped, and said exactly why: "committing other roles' files to a floor is not this tool's call. It reports; someone else decides." ⇒ Right, and not a technical gap. Deciding that 45 files owned by other roles may not get worse binds every one of those roles. It is a TEAMLEAD act. #73 sat with a working predicate and no caller for 16 days because the missing leg was AUTHORITY, and nobody who could supply it had been asked. ★ A RATCHET, NOT A GATE. A gating check reds 45 pre-existing files on its first run; a red naming 45 files nobody touched gets reverted or ignored, which is worse than absent because it teaches the gate is noise. This fails ONLY IF THE COUNT GROWS. Adoption lowers the floor; --record refuses to raise it. ⛔ IT DOES NOT WRITE WHAT IT READS — taken from #598, measured the same day. index-watch.py records the sha it just reported on, so the run that finds drift is the run that suppresses it. ⇒ On a DROP this says the floor CAN be lowered and refuses to. The suite asserts byte-identical output AND an untouched baseline across two consecutive runs. ⚠ THREE THINGS THIS FILE GOT WRONG FIRST, all caught by its own suite: 1 `def report(root, out=sys.stdout)` binds the default AT DEFINITION TIME, so redirect_stdout never reached it. Eight tests failed on empty output. 2 It FAILED ITS OWN RULE — classify() credits a disposition only on the same PHYSICAL SOURCE LINE as the refusal and the emit; every refusal here named ADDABLE across concatenated lines and scored UNNAMED. 3 The empty-population test deleted disposition-scan.py too, so it tripped the missing-predicate guard and PASSED FOR THE WRONG REASON. Two different VOIDs is #73's own subject. ⛔ THE BOUND, from #73 against itself: a check could pass while every refusal names a remedy nobody can act on. PRESENCE is not USEFULNESS. This counts the first only. ⚠ REBUILT ON A CLEAN BASE. The previous branch was cut before #597 was SQUASH merged, so a rebase replayed commits main holds only as one squashed commit and conflicted; `git merge-tree` exit 1 and GitHub mergeable=false agreed. ⇒ Rather than resolve a conflict against a base that no longer exists, this is cut from current origin/main and the README edits re-applied to ITS copy — carrying the old copy forward would have REVERTED #600's leg-5 fix silently. check-tools-index 0 · test_index_watch 0 · test_disposition_ratchet 13/13 · test_merge_guard 26/26. All four READ from the runs, not assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ
jobordu
force-pushed
the
teamlead/disposition-ratchet
branch
from
September 6, 2026 00:08
5ad0af9 to
d31c50d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #73 by supplying the leg it has been missing for 16 days — which was authority, not code.
The state #73 was actually in
tools/disposition-scan.pyalready built the predicate, planted it four ways, and caught ause-versus-mention bug in its own first version. Then it stopped, and said why:
⇒ That is correct, and it is not a technical gap. Deciding that 45 files owned by other roles may
not get worse binds every one of those roles. An instrument cannot make that commitment on
their behalf. TEAMLEAD can. The issue sat with a working predicate and no caller because nobody
who could supply that had been asked for it.
Measured at
6ee6b07A ratchet, not a gate
A gating check reds 45 pre-existing files on its first run. A red naming 45 files nobody touched
gets reverted or ignored — worse than no check at all, because it teaches that the gate is noise.
This fails only if the count GROWS. History is never red; adoption lowers the floor;
--recordrefuses to raise it.
⛔ It does not write what it reads
This is the design decision, and it is taken from a defect measured today — #598:
index-watch.pyrecords the sha it just reported on, so the run that finds drift is the run thatsuppresses it. Re-running to confirm a finding destroys the finding, and its second exit 0 means
"did not check" while reading as "clean".
⇒ On a DROP this says the floor can be lowered and refuses to lower it.
--recordis a separateact with its own diff and its own reviewer. The suite asserts byte-identical output and an
untouched baseline across two consecutive runs, so this tool fails if it ever grows that shape.
⚠ Three things this file got wrong first — all caught by its own suite
def report(root, out=sys.stdout)binds the default at definition time, so it capturedthe original stdout and
redirect_stdoutnever reached it. Eight tests failed against emptyoutput. Early binding, not a logic error.
classify()credits a disposition only when it shares a physicalsource line with both the refusal text and the
print(/stderremit. Every refusal here namedADDABLEacross implicitly concatenated lines, and scored UNNAMED. Conformed — and thelimitation is now recorded in the README, because a line-oriented predicate has a formatting
consequence that has nothing to do with whether a remedy is named clearly.
*.py— includingdisposition-scan.py— so it tripped the missing predicate guard and never reached the guardit is named for. It asserted VOID, got VOID, and went green. Two different VOIDs is exactly
A correctly-reported absence is indistinguishable from an unfixable one — the VOID that cost four hours was true on every run #73's subject, so a test that cannot tell them apart is the wrong test. Now drives
census()directly.
What it does not establish
⛔ Inherited from #73 against itself, and not quietly dropped: a check could pass while every
refusal names a remedy nobody can act on. PRESENCE of a disposition is not USEFULNESS of one.
This counts the first. Nothing here measures the second, and a falling count is not evidence
that the dispositions named are any good.
⚠
index-watchnow exits 1 — the indexed population moved, correctly, because this adds aninstrument. Its documented refresh is
verdict-census.py --ledger, which runs every instrument'smain path; for a forge-touching instrument that performs the action. Not run here, and not
claimed as clean.
Verification
tools/test_disposition_ratchet.py--self-test🤖 Generated with Claude Code
https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ