Fix #2887: delta-scope adversarial re-review to each reviewer's own diff - #2889
Conversation
The re-review priming block (`_re_review_priming_block`, PR #2728) was hardcoded to the v1→v2 transition and took no version argument, yet was appended verbatim to every re-review (v3, v4, …). On N>2 cycles the stale "audit the v2 delta as a fresh reviewer, ignore your v1 NACK history" prose read as "re-audit the whole accumulated surface," widening review scope each cycle and blocking multi-round BRC convergence (observed on issue-2777-replan slice-1: 6+ propose→NACK rounds, reviewer scoping itself to the "broader v3/v4/v5/v6 surface"). Restore true delta-scoping — each reviewer audits only the diff since its own last review — without losing PR #2728's anti-named-blockers rigor: - peer_consensus: add a per-version commit-SHA history (`_proposal_commit_sha_history`) + `get_commit_sha_for_version()`, populated in `_handle_propose_inner` so manual propose, re-propose, and auto-push all pin each version's commit. Unlike the single-slot `_proposal_commit_shas`, this lets a re-review notice resolve a reviewer's last-verdicted version back to the commit they actually saw (`entry.version` → SHA), for ACKs and NACKs alike. - pipelines: parameterize `_re_review_priming_block(*, version, delta_range)`. Anchors to vN / v(N-1) instead of v1/v2; mandate 2 is now bounded to this reviewer's delta — a concrete `git log <last_sha>..HEAD` range on per-reviewer CONSENSUS_RE_REVIEW notices, or the reviewer-self-tracked REVIEWER-SYNC.md range (`{last_reviewed_commit}..HEAD`) on the broadcast CONSENSUS_PROPOSE body (to_role=all, where reviewers sit at different versions). Step-8 lifecycle text reworded version-agnostic. - signals: add `_resolve_reviewer_delta_range()` resolving each reviewer's `<last_sha>..HEAD`; thread version through all four call sites and the concrete range through the two per-reviewer ones. Tests: version anchoring (vN/v(N-1)), per-reviewer range embedding, broadcast REVIEWER-SYNC fallback, version-less graceful fallback, commit-SHA history accumulation + version resolution, and `_resolve_reviewer_delta_range`.
There was a problem hiding this comment.
Summary
Targeted, well-scoped fix for #2887: the per-reviewer CONSENSUS_RE_REVIEW priming now embeds a concrete {last_sha}..{head_sha} delta range, while the broadcast CONSENSUS_PROPOSE retains the version-agnostic REVIEWER-SYNC fallback. Data flow is sound: _handle_propose_inner pins version → commit_sha into the new _proposal_commit_sha_history; the signal handler reads it via get_commit_sha_for_version and resolves the range only when reviewer has a prior verdict and head differs. Defensive if proposal.commit_sha: guard, AttributeError guard for non-real-tracker test paths, and producer-excuse cleanup are all appropriate.
Verified:
invalidate_ack(orchestrator/approval_matrix.py:350) preservesentry.version, so post-invalidation stale reviewers still resolve to the correct previously-reviewed SHA._open_nacks_barrier_responseearly-return is handled before the propose body assembly that calls the priming helper.- Existing MagicMock-based propagation tests still pass: MagicMock returns truthy stubs but
_resolve_reviewer_delta_rangeshort-circuits cleanly when components are missing. - Broadcast-path placeholder text
{last_reviewed_commit}..HEAD --not origin/{base_branch} -pmatches the REVIEWER-SYNC.md convention at shared/prompts/REVIEWER-SYNC.md:110 verbatim.
No blocking issues. A few non-blocking observations below.
Non-blocking suggestions
1. No end-to-end test wiring delta_range into the emitted message body. The new unit tests in test_signals.py and test_pipeline_prompts.py cover _resolve_reviewer_delta_range and _re_review_priming_block independently, and test_producer_push_consensus.py covers the SHA-history accumulator. But there's no test that exercises handle_consensus_propose_signal / handle_consensus_producer_push_signal end-to-end and asserts the per-reviewer CONSENSUS_RE_REVIEW payload actually contains the resolved {last_sha}..{head_sha} string. The existing propagation tests use MagicMock for the tracker and would not catch a regression where the helper is called with wrong arguments or the result is dropped before being threaded into the message body. Consider an integration-style test using the real PeerConsensusTracker that walks: propose-v1 → ACK → propose-v2 → assert the v2 re-review message body contains {v1_sha}..{v2_sha}.
2. clear() does not reset _proposal_commit_sha_history. This matches the pre-existing omission for _proposal_commit_shas, so it's a consistency-with-precedent choice rather than a new regression. But the omission now propagates to a second dict, which slightly increases the cost when someone eventually cleans up clear(). Worth a one-line note in the new field's docstring pointing at the pre-existing pattern, or include both dicts in clear() in a small follow-up.
3. Placeholder convention drift in _re_review_priming_block. The per-reviewer branch uses <base> while the broadcast branch uses {base_branch} (intentionally matching REVIEWER-SYNC.md). Cosmetic, but a reader scanning the function may briefly wonder whether the per-reviewer text needs interpolation too. Consider unifying on <base> in both branches and updating the REVIEWER-SYNC reference, or adding a single comment near the broadcast clause explaining the brace style is a deliberate REVIEWER-SYNC match.
4. Maintenance hazard: literal braces in broadcast delta_clause. The broadcast-path string contains literal {last_reviewed_commit} and {base_branch} placeholders inside a normal Python string. If anyone ever refactors the function to use f-strings (which is tempting for the vN/vNm1 interpolation), those literals would silently raise KeyError at call time. A short inline comment along the lines of # NOTE: braces are literal — REVIEWER-SYNC placeholders, do not convert to f-string would prevent the foot-gun.
5. Inconsistent defensive coverage in _resolve_reviewer_delta_range. tracker.matrix.get_entry is wrapped in try/except AttributeError to tolerate non-real-tracker test stubs, but tracker.get_commit_sha_for_version (also new and equally likely to be missing on a stub) is not. Either expand the try to cover both calls, or drop the AttributeError catch entirely and rely on test setup providing a real tracker — the asymmetry is the surprising part.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- peer_consensus.py: clear() now resets both _proposal_commit_shas and the new _proposal_commit_sha_history dict (review suggestion #2). - pipelines.py: add NOTE comment in _re_review_priming_block explaining the broadcast-path braces are literal REVIEWER-SYNC placeholders that must not be converted to an f-string, and document the deliberate <base> vs {base_branch} convention split between per-reviewer and broadcast clauses (review suggestions #3 + #4). - signals.py: expand AttributeError guard in _resolve_reviewer_delta_range to cover get_commit_sha_for_version too — the asymmetry of catching one tracker read but not the other was flagged as a foot-gun (review suggestion #5). - test_signals.py: add end-to-end integration test exercising the real PeerConsensusTracker through propose-v1 → ACK → producer-push-v2 and asserting the per-reviewer CONSENSUS_RE_REVIEW body actually embeds the concrete <v1_sha>..<v2_sha> delta range — pins the seam that the isolated helper + propagation tests don't cover (review suggestion #1).
|
Thanks for the review. All five non-blocking suggestions addressed in commit 51c71a8:
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review Summary
All five non-blocking suggestions from my previous review were addressed in 51c71a8. Each fix is appropriate and the code remains well-scoped.
Verification of each fix
1. End-to-end test wiring delta_range into the message body — TestReReviewDeltaRangeReachesMessageBody::test_per_reviewer_re_review_body_contains_concrete_delta_range (orchestrator/tests/test_signals.py:2213) does what it claims:
- Uses a real
PeerConsensusTracker(viacreate_peer_consensus_tracker), not a MagicMock — exercises the production code path. - Walks propose-v1 (
v1sha1234abcd) → ACK → producer-push-v2 (v2sha5678efef) through the real handlerhandle_consensus_producer_push_signal. - Asserts the concrete
v1sha1234abcd..v2sha5678efefsubstring lands in theCONSENSUS_RE_REVIEWbody (not just that some re-prime text is appended). - Asserts the broadcast-path placeholder
{last_reviewed_commit}..HEADis not in the body (catches a regression wheredelta_rangeis computed but not threaded through). - Asserts dynamic version anchoring (
"Your v2 review","named v1 blockers") — would catch a regression whereversion=isn't propagated. - Correctly invalidates all ACKs by omitting
changed_files(the conservative path). - Cleans up via
remove_peer_consensus_trackerinfinally. - Patches
message_store.get_message_storecorrectly — the handler's lazyfrom message_store import get_message_storere-resolves through the patched module attribute at execution time.
This closes the seam between the isolated _resolve_reviewer_delta_range test and the MagicMock propagation test, exactly as requested.
2. clear() resets both SHA dicts — orchestrator/peer_consensus.py:1767-1768 now resets both _proposal_commit_shas (pre-existing omission) and _proposal_commit_sha_history (new dict). The pre-existing omission was a no-op in practice (clear() is only called from remove_peer_consensus_tracker, after which the tracker is discarded), but the contract is now honest. Note: _nudged_versions and _open_nack_notified_at remain omitted from clear() — also pre-existing, also no-op since the tracker is discarded after clear(). Not blocking; out of scope.
3 + 4. Placeholder convention drift + literal-braces f-string foot-gun — Single comment block at orchestrator/routes/pipelines.py:13823-13834 covers both concerns. The comment correctly explains:
- Why the broadcast branch uses
{...}(literal placeholders the reviewer agent substitutes from REVIEWER-SYNC.md at read-time). - Why the per-reviewer branch uses
<base>(only one placeholder remaining; angle-brackets make the "already-resolved vs. still-to-resolve" distinction visible). - The explicit
Do NOT convert this string to an f-stringwarning with the failure mode spelled out (NameErrorbecause there are no Python locals namedlast_reviewed_commit/base_branch).
Verified the REVIEWER-SYNC.md:110 reference matches the literal placeholder names.
5. Symmetric AttributeError coverage in _resolve_reviewer_delta_range — orchestrator/routes/signals.py:154-160 now wraps both tracker.matrix.get_entry and tracker.get_commit_sha_for_version in the same try/except AttributeError. The asymmetry is removed.
Minor observation (not blocking): the surrounding comment frames the catch as "for MagicMock trackers", but plain MagicMock() instances (which is what the propagation tests in test_brc_phase_propagation.py use) don't raise AttributeError on missing attributes — they auto-mock. The catch actually only helps for spec-restricted mocks (MagicMock(spec=...)) or stub objects that explicitly don't implement the method. Practical effect: the catch is harmless and the symmetric coverage is still the right call; only the framing is slightly off. Not worth a follow-up.
Behavioral analysis
Traced the v1 → ACK → v2 path against the production code:
record_proposalincrements version 1→2;_proposal_commit_sha_history["coder"]accumulates{1: v1sha, 2: v2sha}.invalidate_ackpreservesentry.version=1, so_resolve_reviewer_delta_rangeresolves tov1shaviaget_commit_sha_for_version("coder", 1).check_auto_reproposepasses withauto_repropose_debounce_seconds=0(theelapsed < 0check is always False) and the SHA-different guard.- The CONSENSUS_RE_REVIEW emission embeds the resolved range correctly.
No new issues introduced. No security/correctness concerns. Approving.
— Authored by egg
|
egg review completed. View run logs 3 previous review(s) hidden. |
Fixes #2887.
Problem
The adversarial re-review priming block (
_re_review_priming_block, added by PR #2728 from the #2724 post-mortem) was hardcoded to the v1→v2 transition and took no version argument, yet it was appended verbatim to every re-review (v3, v4, v5, …). On N>2 cycles the stale "audit the v2 delta as a fresh reviewer, ignore your v1 NACK history" prose read as "re-audit the whole accumulated surface" — widening review scope each cycle instead of shrinking to the latest delta, and blocking multi-round BRC convergence.Observed on
issue-2777-replanslice-1: 6+ propose→NACK rounds where the reviewer explicitly scoped itself to the "broader v3/v4/v5/v6 surface," surfacing one more (steadily lower-severity) finding each round so the NACK set never emptied.Fix
Restore true per-reviewer delta-scoping — each reviewer audits only the diff since its own last review — while keeping PR #2728's anti-named-blockers rigor. The anchor is orchestrator-authoritative (resolved from consensus state), not reliant on reviewer-side bookkeeping.
orchestrator/peer_consensus.py_proposal_commit_sha_history: dict[producer, {version: sha}]+get_commit_sha_for_version(), populated in_handle_propose_inner(covers manual propose, re-propose, and auto-push). Unlike the single-slot_proposal_commit_shas(overwritten each propose), this lets a re-review notice resolve a reviewer's last-verdicted version (entry.version) back to the commit they actually reviewed — for ACKs and NACKs.orchestrator/routes/pipelines.py_re_review_priming_block(*, version, delta_range): anchors tovN/v(N-1)instead ofv1/v2. Mandate 2 is bounded to this reviewer's delta:CONSENSUS_RE_REVIEW: a concretegit log <last_sha>..HEADrange;CONSENSUS_PROPOSE(to_role=all, reviewers at different versions): the reviewer-self-tracked REVIEWER-SYNC.md range{last_reviewed_commit}..HEAD.orchestrator/routes/signals.py_resolve_reviewer_delta_range()resolves each reviewer's<last_sha>..HEADfrom their last-verdicted version's commit; falls back toNone(→ the REVIEWER-SYNC generic range) when unresolvable.versionthrough all four call sites; thread the concretedelta_rangethrough the two per-reviewer ones.Tests
test_pipeline_prompts.py: dynamicvN/v(N-1)anchoring (no stale v1/v2), per-reviewer range embedding, broadcast REVIEWER-SYNC fallback, version-less graceful fallback.test_producer_push_consensus.py: commit-SHA history accumulates across versions; unknown-version → ""; reviewer last-verdict resolves to reviewed commit after the producer advances.test_signals.py:_resolve_reviewer_delta_rangespans last-verdict→HEAD, returnsNonewith no prior verdict / empty head.make test(changeset-aware): 17143 passed, 29 skipped.make lintclean.Relates to #2728, #2724, #2777.