Skip to content

--shape-only: skip leg 5 too — three merge-time legs, fixed one at a time - #600

Merged
jobordu merged 1 commit into
mainfrom
teamlead/shape-only-leg5
Sep 6, 2026
Merged

--shape-only: skip leg 5 too — three merge-time legs, fixed one at a time#600
jobordu merged 1 commit into
mainfrom
teamlead/shape-only-leg5

Conversation

@jobordu

@jobordu jobordu commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Third and last of the merge-time legs that --shape-only could never establish.

Found by #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.

⛔ The finding is that this is the third time

merge-guard has three legs that are facts about the merge event, not about the PR:

leg why a runner cannot establish it fixed
0 holder == session a runner has no holder session and cannot have one 1st
2 required gate it is inside the run it is asking about 2nd
5 age at merge there is no merge yet 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

A skip with no known-negative is a bypass, so both directions run on identical input:

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 is why leg 5 exists, and the
second test asserts this exemption did not weaken it.

Suite 26/26 (was 24).

🤖 Generated with Claude Code

https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ

…time

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).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTMf4EvaTEnDKXY47efRZZ
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 7bf69152-da66-4944-91d4-eaab93020dba

📥 Commits

Reviewing files that changed from the base of the PR and between 4ce44c4 and 47a3945.

📒 Files selected for processing (2)
  • tools/merge-guard.py
  • tools/test_merge_guard.py

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jobordu
jobordu merged commit 777b950 into main Sep 6, 2026
6 checks passed
jobordu pushed a commit that referenced this pull request Sep 6, 2026
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 added a commit that referenced this pull request Sep 6, 2026
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.


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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant