Repository navigation
feat(premerge): route blocked_on_file returns into the next attempt's wave plan - #766
Merged
Merged
Conversation
added 8 commits
August 23, 2026 12:17
… patterns hoisted
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 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 |
Collaborator
Author
|
Superseded for review by #768; the trust trail will be emitted there. |
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 #755
/premerge's fixer waves are one agent per file, disjoint per wave. A fix whosehonest scope spans two files therefore has no route: the agent can only
REFUSE,and the finding survives to the next attempt — spending a whole repair round to
learn something the agent already knew. Worse is case 3 in the issue, where a
correct fix leaves a consequence in a file nobody in that wave owns, and nobody
reports it in a machine-readable way at all.
This PR makes
blocked_on_filepart of the fixer-wave return contract, androutes the paths it returns into the next attempt's wave plan.
What the issue asked, and where this departs from it
The issue's item 3 says:
This PR deliberately does not do that. Two independent reasons:
Security. The commit fence derives the allowed set solely from the wave
plan (
jq -r '.waves[][].file' fix-waves-<NN>.json), and that derivation isexactly what keeps committable and owned by exactly one agent the same
set. Widening the allowed set from agent-returned text makes a path
committable while nobody owns it — and since
_fix_wavesnever groupedit, the disjointness guard says nothing about the two agents in the next wave
who may now both edit that one file, concurrently, against the single
un-isolated worktree those waves share.
It would not have worked. Fixer waves are dispatched from the wave plan.
A path present only in the allowed list has no agent assigned to it, so it
is edited by nobody — the cross-file consequence survives the round untouched,
which is the exact failure the issue was filed about. The issue's own
instruction does not deliver its own acceptance criterion.
Instead, the returned paths become synthesised blocker findings routed
through the unchanged
_fix_waves. Each becomes a first-class wave memberwith exactly one owner, and the commit fence keeps deriving its allowed set from
fix-waves-<NN>.jsonexactly as before. The scope guard needed no edit atall —
P21blocks that it is still derived from the wave plan and that theassigned list still has exactly one producer.
Bounded by construction
Not "optional, repeatable" with no ceiling — an allow-list that grows every round
constrains nothing by the last round:
plan --carry-blockedopensblocked-on-<N-1>.jsonby name, never a range. The sibling in the samefile (
_carry_prior_suggestions) walksrange(1, attempt)and copying thatshape reads as consistency — but it is correct there because a suggestion is
never fixed and must not be lost, and a blocked-on path is the opposite: it
widens what a fixer may edit.
MAX_BLOCKED_ON = 8, equal to the max fix-wave size, so one attempt canadd at most one extra wave's worth of files — a bound a reader can check rather
than a number someone picked.
one malformed row an agent emitted. Dropping is also the fail-closed direction:
a dropped path is a path no fixer may touch.
already exist on disk (which also kills the newline-separator injection into
the
jq -rderived list).blocked_on_admitted/blocked_on_droppedinclassified.json, andBLOCKED=<n>appended to thePREMERGE_TRIAGEline.A validator that claimed a guarantee it did not have
_repo_path's docstring claimed it was the "same predicate ascode_fixer_contract.py's scope validator". It is not: that one pairs itslexical half with
beneath(), a realpath +commonpathcontainment check, and_repo_pathimplemented only the lexical half. That was fine while the value wasjust a parse-time JSON field; it stops being fine the moment the same path
becomes a filesystem authorization decision. The docstring is corrected to
say what it actually does, and the missing half is added as
_beneath_repo_root— so a tracked symlink that is lexically repo-relative but resolves outside the
repo now dies (
B32f).Agent text is kept out of the fingerprint
summaryis controller-authored and deterministic, because it feeds_fingerprint, which is the cross-attempt identity_blocker_fingerprintsdepends on. Letting an agent author it would make that identity agent-authored
too. The agent's own words go in
failure_scenario, where nothing keys on them.<external-untrusted-input>envelope with no encoder between them, so a row naming the envelope token
could close the envelope early and read as the controller's words. Such rows are
dropped.
reason,originandtarget.targetis theagent-chosen string that lands in the finding's
filekey — the mostagent-controlled of the three, and the one the plan left unchecked.
lineis deliberatelyNone: the consequence was observed at coordinates otherwave members have already moved.
Two latent siblings fixed, not scoped out
Both ran
commwithoutLC_ALL=C—sortorders by the ambient collation whilecomm's merge walk assumes its own, and when they disagree the walkdesynchronises:
goal-state.sh's PR file-collision detector. This one fails open: acomm -12intersection that misses a shared path reports no collision betweentwo PRs that do collide, and the rebase the function exists to trigger never
fires — the same disjointness property this issue is about.
review-consolidate.sh's staged-set scope guard. This one fails closed butwrongly: it names a path present in both lists as extra and refuses a merge
whose staged set was correct.
Both were previously correct only by accident (the sorts happen to share a shell).
Pinning makes it a property of the code rather than of whatever
LC_ALL/LC_COLLATE/LANGthe operator inherited — and this guard runs on everyplatform UberDev does.
Verification
B32–B32h) driving the realplanverb, withthe fence's allowed set derived by executing the fence's own
jqlineextracted live from
SKILL.md, not a transcription of it (B32hlocks theextractor, including that it returns a sentinel rather than an empty set when
extraction fails).
stopping synthesised findings from reaching the wave plan. That is the
acceptance criterion itself under test, not a proxy for it. Re-executed while
preparing this PR: deleting
blockers = blockers + synthesised(the onlyproducer feeding
_fix_waves) takes the fixture from 190 pass / 0 fail to181 pass / 9 fail, and the nine are exactly the allowed-set and cap rows.
P21/P21b/P21c), each scoped to anextracted
§2ablock rather than the wholeSKILL.md, with extraction-sizeguards so a moved anchor reds loudly instead of passing vacuously.
command on the line pinned, the other not. That is the exact half-fix a reviewer
waves through, and the originally-planned line-level
grep -v 'LC_ALL=C'filterscored it as clean (measured: zero hits). The shipped predicate neutralises
pinned pairs per token, not per line, and
G56.mutation-half/RCX15.mutation-halfprove it.Acceptance
P21)blocked_on_file, admitting exactly those and no othersB32,B32a,B32b) — against the wave plan, per the departure aboveItem 3 needs a live
/premergerun over real PRs and real CI, which cannot beproduced inside a PR. The executed
B32fixture is the in-PR proxy: it drives thereal verb end to end and asserts the widened path is assigned to exactly one owner.
Reported, not fixed
Both outside this PR's scope:
plugins/uberdev/hooks/session-end:21runsfind -H /tmp -maxdepth 1 \( -name 'uberdev-*' -type d \) -exec rm -rf {} +,which deletes every
/tmp/uberdev-*directory on any session endanywhere on the machine — not just the ending session's own. It corrupted three
separate agents' scratch state during this work. Compounding it: the hook is
extension-less (
hooks/session-end, notsession-end.sh), so--include='*.sh'greps cannot see it.tests/skill-size.test.sh'smerge-pipelinewaiver row claims~27% fenced; the file measures 45.7% (812 fenced body lines of 1778). Thepercentages in that table are prose annotations, not assertions, so nothing
reds — which is why it drifted.
One consequence of the re-pin
The
premerge-pipelinesize ceiling is re-pinned to the measured 2391, so theceiling now equals the measurement exactly and the next line added to that
SKILL.mdredsS2. That is the ratchet working as designed — it was equallytight at 2323 — but any follow-up touching that file must re-pin in the same
commit.
Version
No version bump: fleet PR, so the bump belongs to the commit that lands the stack.
The size ceiling re-pin is not a version surface.
Full suite
The complete
shape-checksrun list from.github/workflows/test.yml— all132 fixtures, argv taken verbatim from the
run_onelines (so the five*-zsh.test.shrows keep theirzshdriver), each invocation's exit codecaptured on its own, never through a pager, with per-fixture logs pre-created and
probed so a failed redirect could not masquerade as an ordinary red.
132 result rows, 132 logs, 132
=== END rc=markers — every fixture demonstrablyran. 131 passed, 1 failed.
The one failure — pre-existing, not caused by this PR
Attributed and verified independently for this PR:
b7dbe74a…is reachable only from the unmergedfix/746-agent-description-budgetbranch (
git branch -a --containsnames nomain), andgit merge-base --is-ancestor b7dbe74a… origin/mainreturns false.origin/main'splugins/uberdev/vendor.json, so the row is red on cleanorigin/maintoo.plugins/uberdev/vendor.json(git diff --name-only aa3e54cd..HEADmatches nothing under
vendor).It clears when
fix/746lands. Nothing else in the suite is red.tests/code-fixer-contract-windows.test.pyis not part ofshape-checks— it iswired only into the Windows job — so it is not in this run by design.
Rows this PR adds, all green
tests/premerge-findings.test.shB32–B32h)planverbtests/premerge.test.shP21/P21b/P21c)tests/goal.test.shG56)tests/review-pr-consolidate.test.shRCX15)