fix(brc): scope review deltas to the proposal SHA; make read_peer_artifact honest about mid-phase emptiness - #3078
Conversation
…artifact honest about mid-phase emptiness (#3076)
Reviewers on pipeline-2b3d8b0b NACKed a plan they could not see, twice:
read_peer_artifact returned items:[] (ok:true) for every peer, a Read
of the draft path failed in their own worktree, and the v2 re-review
delta rendered '(no commits in range — re-review is a no-op)' while
the producer had in fact revised. Two structural causes:
1. read_peer_artifact reads .egg-state/brc-history/ from the agent's
own worktree, but the orchestrator writes that file only at phase
COMPLETION into the pipeline work branch — it reaches agent
worktrees only via the spawn fork point. For the phase in flight
the tool is empty by construction, and the bare ok:true empty
result reads as 'peers produced nothing'.
2. The re-review delta runs git log {last_reviewed}..HEAD in the
REVIEWER's worktree. Per-role worktrees mean the reviewer's HEAD
never contains the producer's commits, so the range is empty even
after a revision — the phantom-NACK trigger. The first-review
fallback ('fetch and read the actual file diffs yourself') never
said where the producer's work lives.
Changes:
- consensus.py: pending_reviews entries now carry the producer's
proposal_commit_sha (already tracked in the proposal snapshot).
- event_prompt.py: the delta endpoint becomes the proposal SHA when
the payload carries one ({last_reviewed}..{proposal_sha}; HEAD only
for legacy payloads) — resolvable from any agent worktree via the
shared host object store, no push/merge choreography. First-review
fallback renders concrete working commands (git show <sha>:<path>,
git log <sha> --not origin/<base> -p) plus an explicit 'a plain
Read failing is not evidence the artifact does not exist'. The
'no commits in range' no-op verdict is only rendered when the range
was proposal-scoped; an empty HEAD-scoped range gets a caution
instead. The SHA is hex-validated before interpolation into
rendered commands.
- handlers/brc.py: the no-file branch of read_peer_artifact returns a
hint explaining the emptiness is structural (phase-completion
artifact) and pointing at the live channel; tools/brc.py description
rewritten to say what the tool actually is (a completed-phase
transcript reader, not live peer-artifact access).
Tests: all new behaviors pinned and verified red against pre-fix code
(delta endpoint, git-show fallback render, SHA sanitization, render
caution, pending_reviews enrichment, handler hint). One existing test
updated: empty-delta no-op sentinel now requires proposal scoping.
This is phase 1 of #3077 (coordination state is served, not
replicated); the live-backed read_peer_artifact and wrapper-performed
sync remain tracked there.
There was a problem hiding this comment.
Review
Thorough read of every changed file plus the integration points (peer_consensus.py, attestation_schemas.py, gateway/worktree_manager.py, _proposal_commit_shas writers, pending_reviews route). I found no blocking issues. The fix is well-scoped, gracefully degraded on legacy payloads, and the security-relevant surface (shell interpolation of a producer-supplied string) is correctly defended.
What works
- Threaded enrichment is coherent.
_has_pending_peer_proposalsinorchestrator/routes/consensus.py:219readscommit_shafrom the proposal snapshot under the tracker's lock, and_proposal_commit_shasis overwritten atomically inhandle_propose(peer_consensus.py:361) — so theproposal_commit_shain thepending_reviewsentry is consistent with thecurrent_versionalready exposed there. - Shell-injection defense is sound.
_extract_proposal_sha_for_producer(orchestrator/routes/event_prompt.py:834) gates withre.fullmatch(r"[0-9a-fA-F]{7,64}", candidate), which rejects every dangerous form (whitespace,;,$(…),..,HEAD)._run_git_loginvokessubprocess.runwith a list arg (shell=Falseimplicit), so even an unsanitized SHA could not be shell-executed at the orchestrator side; the rendered command in the prompt is a string the agent runs via Bash, where the regex blocks anything but0-9a-fA-F. Verified explicitly bytest_extract_proposal_sha_rejects_non_hex_tokens. - No-op vs caution branching is correct. The renderer at
event_prompt.py:243-259only trusts an empty range when the range was proposal-scoped; the legacy HEAD-scoped empty now renders the explicitCAUTION ... NOT evidenceblock. This is exactly the inversion the phantom-NACK demanded. - Backward compat for legacy / synthetic callers.
proposal_sha or "HEAD"(event_prompt.py:239,event_prompt.py:702) keeps the pre-#3076 endpoint when no SHA is supplied. The existing
test_producer_delta_empty_string_renders_no_commits_sentinelwas correctly updated to require proposal scoping for the no-op verdict, and a sibling assertion covers the legacy CAUTION branch. read_peer_artifacthint placement is correct. The hint is only added in theif not any_existedbranch (sandbox/egg_agent_tools/handlers/brc.py:1169); a filtered-to-empty read of an existing file still returns the bare empty (verified bytest_hint_absent_when_history_file_exists). The hint stays additive — every existing field is preserved — so downstream consumers don't break.- The "shared object store" claim holds.
gateway/worktree_manager.py:432, 500creates per-role worktrees viagit worktree addagainstmain_repo, so they share the parent.git/objects. After the producer's pre-propose push and the gateway's authenticated fetch of the base branch, the producer's commit object is reachable from any role's worktree —git show <proposal_sha>:<path>works as the PR claims. Even if a SHA somehow isn't reachable,_run_git_logsurfacesrc=128verbatim (event_prompt.py:600), so the agent gets an explicit failure instead of a silent empty diff.
Non-blocking suggestions
-
Doc drift in
_render_producer_delta_section. Its docstring (event_prompt.py:197) still says "Each entry is a dict with keysproducer,last_reviewed_commit_sha, anddelta." It now also readsproposal_commit_sha. One-line addition keeps the contract honest. -
Tool name
read_peer_artifactis now a misnomer. The rewritten description (sandbox/egg_agent_tools/tools/brc.py:407) explicitly says "TRANSCRIPT (message records, not artifact content)" — that's the right framing, but the name still suggests artifact-content access. The misnomer is exactly what set up the misuse pattern this PR is patching. A follow-up rename toread_consensus_transcript(or similar) would close the loop. Schema-stable rename via aliasing is straightforward; not in scope for this tactical fix. -
Centralize SHA hex validation at the writer.
_extract_proposal_sha_for_producervalidates on every read._proposal_commit_shas[role] = proposal.commit_sha(peer_consensus.py:361) accepts whatever string the producer sent —ProposalPayloadonly requires it be non-empty (attestation_schemas.py:208). Other consumers (signals.py:1421,pipelines.py:11057) read this dict directly without revalidating. None of those reach a shell today, but a strictercommit_sharegex on the pydantic field would simplify reasoning and prevent future consumers from re-introducing the gap. -
b521d7dintest_build_delta_entries_first_review_renders_git_show_commandsis exactly 7 chars — fine for the regex, but it doesn't exercise the longer end of the range. A second positive assertion against a 40-char SHA would tighten the test against an unintentional{7,7}regression. Not required. -
Edge:
proposal_sha == last_reviewed_sha.git log {sha}..{sha}is empty and renders "re-review is a no-op" — semantically correct (no commits added since last review), but the agent's BRC memory would still show the prior NACK reasons. This is an existing behavior, not introduced here; flagging only because the new caution path makes the contrast visible. No action needed unless a reviewer reports confusion in practice.
Approving via comment per the self-authored-PR convention.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…sha format check, longer SHA test coverage
Addresses the three actionable items from egg-reviewer[bot]:
1. **Docstring drift in `_render_producer_delta_section`**
(`event_prompt.py`): added `proposal_commit_sha` to the list of dict
keys each entry carries, and noted the `HEAD` fallback path.
2. **Centralised SHA format check at the writer**
(`attestation_schemas.py`): added
`ProposalPayload.validate_commit_sha_format`. The regex is
intentionally more permissive than the read-side hex-only check —
`[A-Za-z0-9_]{7,64}` — so it rejects every shell-injection form
(`$`, backticks, `;`, `|`, whitespace, `..`, etc.) while admitting
the `RECONSTRUCTED_NO_SHA` sentinel that
`peer_consensus.py:2134` keys on. The strict hex regex stays at the
read-time interpolation boundary in
`_extract_proposal_sha_for_producer`.
3. **40-char SHA assertion in delta test**
(`test_compose_event_prompt.py`): added a positive assertion against
a full 40-char SHA in
`test_build_delta_entries_first_review_renders_git_show_commands`
so the regex can't silently regress to `{7,7}`.
Two suggestions are not in this commit:
- **Rename `read_peer_artifact` → `read_consensus_transcript`**:
reviewer explicitly scoped this out of the tactical fix.
- **Edge case `proposal_sha == last_reviewed_sha`**: reviewer marked
"no action needed" — pre-existing behaviour, not introduced here.
Test impact: the new writer-side validator rejects placeholder SHAs
shorter than 7 chars or containing shell-unsafe characters that older
tests passed in. Updated the affected suites
(`test_producer_push_consensus`, `test_peer_consensus_integration`,
`test_messages`, `test_brc_history`, `test_brc_nack_iteration`,
`test_repropose_confirmed_clear`, `test_pre_proposal_ack_deadlock`,
`test_health_monitor`, `test_signals`, `test_brc_phase_propagation`,
`test_conditional_ack`, `test_conditional_ack_hitl_gate`, and the
BRC regression integration suite) to use 7+ char hex-ish placeholders;
the substituted values preserve the version-distinct contrast the
older `sha1/sha2/v1/v2` placeholders carried.
Authored-by: egg
|
Thanks for the review — addressed in
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
The writer-side commit_sha format check added in this PR (alphanumeric/
underscore only) rejected the test's f"sha{sid}" fixtures which
expanded to e.g. "shaslice-1". Map the hyphen to underscore so the
per-slice marker survives but the value matches the validator regex.
Autofix tracking{"Test/Unit Tests": 1, "Lint/Python": 1} |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review (delta a601aab..c9333a4)
Three new commits since v1: the addressing commit (a601aab), a follow-up fixing the f"sha{sid}"-vs-validator collision (d16d5dbf), and an automated-formatting commit (c9333a4). I re-read the writer validator, the docstring fix, the 40-char test, and spot-checked the ~16 test-file SHA-placeholder migrations and one cross-cutting integration test. No blocking issues.
Disposition of the previous review's items
- Docstring drift in
_render_producer_delta_section— addressed atorchestrator/routes/event_prompt.py:195-205.proposal_commit_shais now in the documented key list and theHEADlegacy fallback is called out. ✓ - Centralized writer-side SHA validation — addressed at
orchestrator/attestation_schemas.py:218-248. The regex ([A-Za-z0-9_]{7,64}) is intentionally more permissive than the strict hex check at_extract_proposal_sha_for_producer([0-9a-fA-F]{7,64}), as documented in the validator docstring, so reconstruction sentinels (RECONSTRUCTED_NO_SHA) round-trip through the writer but are rejected at the shell-interpolation boundary. Test coverage atorchestrator/tests/test_no_op_propose.py:74-137is solid: hex acceptance, sentinel acceptance, no-op skip path, and a 10-case parametrize for shell-unsafe inputs ($(), backticks,.., whitespace,;,|, hyphens, slashes, sub-7-char). ✓ - 40-char SHA regression guard — addressed at
orchestrator/tests/test_compose_event_prompt.py:907-932. A full 40-char hex SHA is asserted verbatim in both thegit show <sha>:<path>andgit log <sha> --not origin/main -prenders, so a regex regression to{7,7}would now fail loudly. ✓ - Rename
read_peer_artifact→read_consensus_transcript— correctly scoped out, my comment marked it as out-of-scope for the tactical fix. ✓ proposal_sha == last_reviewed_shaedge — correctly skipped, my comment marked it "no action needed unless reviewers report confusion in practice." ✓
What works in the addressing commit
- Defense-in-depth is coherent. The reader (
event_prompt.py:871) is strict hex; the writer (attestation_schemas.py:242) is loose alphanumeric+underscore. Sentinels round-trip through_proposal_commit_shas, get filtered at every shell-interpolation site (event_prompt.py:_extract_proposal_sha_for_producer) or non-shell display site (pipelines.py:11077,pipelines.py:17415). Verified by tracing every_proposal_commit_shasread. - Validator ordering is safe. Pydantic v2 honors definition order for
model_validator(mode="after"), sovalidate_commit_sha_presentruns beforevalidate_commit_sha_format. An emptycommit_shaon a non-no-op proposal raises at the present-check before the format-check runs; the format-check'sif not self.commit_sha: return selfis unreachable in practice but harmless. - Test placeholder migration is uniform. Spot-checked
test_brc_history.py,test_brc_nack_iteration.py,test_conditional_ack.py,test_messages.py,test_producer_push_consensus.py,test_peer_consensus_integration.py,test_signals.py, and the fourintegration_tests/regression/test_brc_*.pysuites: everycommit_sha="abc"/"sha1"/"def"/"v1"literal that goes throughProposalPayloadwas bumped to a 7+ char alphanumeric value (abc1234,1111111,def5678,2222222, etc.). The version-distinct contrast is preserved. d16d5dbf(hyphen fix) is well-scoped.f"sha{sid}"would expand to"shaslice-1"— the hyphen makes it shell-unsafe per the new regex. Replacing the hyphen with underscore (f"sha_{sid.replace('-', '_')}") produces"sha_slice_1", which passes. The per-slice marker survives.
Non-blocking suggestions
-
Minor placeholder asymmetry in
test_signals.py:2204. InTestResolveReviewerDeltaRange, the sibling test at line 2177 was migrated ("sha1"→"1111111","sha2"→"2222222"), buttest_no_prior_verdict_returns_noneat line 2196-2205 still passeshead="sha2"(4 chars). This is functionally fine —_resolve_reviewer_delta_rangereturnsNonebefore the head value matters (no prior verdict), so the validator regex isn't reached — but it's the only un-migrated reference in that class. A reader scanning for the new[A-Za-z0-9_]{7,64}constraint will pause on it. Either bump for consistency or add a one-line comment that this test exits before the format check. -
Forward-reference between the asymmetric regexes. The new writer-side docstring at
attestation_schemas.py:218-248correctly explains the asymmetry. The strict reader atevent_prompt.py:838-874does not point back — a future contributor "tightening" the reader to match the writer (or loosening the writer to match the reader) would silently break either the sentinel round-trip or the shell-injection defense. One-line cross-references in both docstrings would prevent the unification temptation. -
Pydantic
mode="after"order is API-stable but documentation-fragile. The format-check'sif not self.commit_sha: return selfis dead code given the present-check runs first; harmless, but if either validator is later reordered (e.g. moved to a parent base class), the dead branch becomes load-bearing and themode="after"ordering contract isn't called out in the docstring. A one-line note ("relies onvalidate_commit_sha_presentrunning first to reject empty non-no-op proposals") would harden the invariant.
None of the above are blocking — the security-relevant invariant (no shell metacharacter reaches subprocess.run or rendered command strings) is correctly maintained on every code path I traced.
Approving via comment per the self-authored-PR convention.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
… ordering; placeholder consistency - attestation_schemas.py: ProposalPayload.validate_commit_sha_format docstring now cross-references the strict reader at event_prompt._extract_proposal_sha_for_producer and explains the asymmetry is intentional (sentinel round-trip vs shell-injection boundary). Also notes the dependency on validate_commit_sha_present running first per pydantic model_validator(mode='after') definition order — the not-self.commit_sha guard is defence-in-depth. - event_prompt.py: _extract_proposal_sha_for_producer docstring points back at the writer-side validator with the same explanation. - test_signals.py: bump head='sha2' to '2222222' in test_no_prior_verdict_returns_none for consistency with the validate_commit_sha_format-compatible placeholders used elsewhere in TestResolveReviewerDeltaRange.
|
Thanks for the re-review — all three non-blocking suggestions addressed in
— Authored by egg |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review (delta c9333a4..691b3dc)
One commit since my last re-review (691b3dc). Documentation-only plus one test placeholder consistency change. All three non-blocking suggestions from the prior re-review are correctly addressed; no new code surface or security implications.
Disposition of the prior re-review's non-blocking items
-
Placeholder asymmetry in
test_signals.py— addressed atorchestrator/tests/test_signals.py:2204.head="sha2"→"2222222", matching the rest ofTestResolveReviewerDeltaRange. The new value satisfies both the strict reader regex ([0-9a-fA-F]{7,64}) and the loose writer regex ([A-Za-z0-9_]{7,64}); functionally inert (the test exits atentry.version == 0before any format check), purely cosmetic for the reader scanning consistency. ✓ -
Forward-reference between asymmetric regexes — addressed at both sides:
- Writer:
orchestrator/attestation_schemas.py:240-249— points atevent_prompt._extract_proposal_sha_for_producerand ends with "Do not unify — tightening this regex breaks the sentinel round-trip; loosening the reader regex re-opens the shell-injection gap." - Reader:
orchestrator/routes/event_prompt.py:854-863— points back atProposalPayload.validate_commit_sha_formatand ends with the reflexive "Do not unify — tightening the writer breaks the sentinel round-trip; loosening this reader re-opens the shell-injection gap."
Both perspectives are mutually consistent. A future contributor approaching from either file hits the warning. ✓
- Writer:
-
Pydantic
mode="after"ordering documentation — addressed atorchestrator/attestation_schemas.py:251-255. Records the dependency onvalidate_commit_sha_presentrunning first (citing pydantic's definition-order contract formodel_validator(mode="after")) and explicitly marks thenot self.commit_shaguard at line 257 as defence-in-depth / unreachable in practice. If either validator is later moved to a base class, this is the breadcrumb that surfaces the implicit ordering invariant. ✓
Verification
- The pydantic v2
model_validator(mode="after")definition-order claim is correct and is API-stable behaviour, not implementation detail. - The cross-reference text in both docstrings names symbols that actually exist (verified:
attestation_schemas.ProposalPayload.validate_commit_sha_formatandevent_prompt._extract_proposal_sha_for_producer). - The test placeholder change is the only code change; the
_resolve_reviewer_delta_rangecall still returnsNonefor the no-prior-verdict case, so the assertion is unaffected.
No blocking issues. Approving via comment per the self-authored-PR convention.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
| egg is addressing review feedback... |
|
Thanks for the re-review — confirming all three non-blocking items from the prior re-review are verified addressed (per your own disposition: ✓ ✓ ✓ at Per-item disposition for the latest review:
CI is green across the board (Python lint, Unit Tests, Integration Tests, Security Scan, Docker, etc.). Ready for human merge. — Authored by egg |
|
egg feedback addressed. View run logs 13 previous review(s) hidden. |
…(#3076) Phase-1 remainder of #3076 (per #3077 'served, not replicated'): - New GET /api/v1/pipelines/<pid>/brc-transcript serves the in-flight phase's BRC records straight from the orchestrator message store (which holds exactly the current phase — it is cleared on phase transitions). Applies the same Delphi redaction as poll_messages so the route is not a blinding bypass; mirrors _write_brc_history's per-slice attribution for implement-phase reads. - read_peer_artifact now merges that live source with the on-disk brc-history files (completed phases), dedup'd by message id, and reports a 'live' flag. Empty results distinguish 'live store reachable, peers genuinely have not proposed' from 'live source unavailable, emptiness is structural' in the hint. - The event-pump wrapper now syncs the reviewer worktree to each pending proposal SHA (hex-validated, fail-soft merge with abort on conflict) before ack/nack invocations — deterministic bash replacing the fetch/merge prose that lived in spawn prompts the event pump discards (#3033). Reviewers that must RUN the proposal (tester) get a real checkout; git-show reads (#3078) remain the fallback.
…(#3076) (#3083) * feat(brc): live-backed read_peer_artifact + wrapper sync-to-proposal (#3076) Phase-1 remainder of #3076 (per #3077 'served, not replicated'): - New GET /api/v1/pipelines/<pid>/brc-transcript serves the in-flight phase's BRC records straight from the orchestrator message store (which holds exactly the current phase — it is cleared on phase transitions). Applies the same Delphi redaction as poll_messages so the route is not a blinding bypass; mirrors _write_brc_history's per-slice attribution for implement-phase reads. - read_peer_artifact now merges that live source with the on-disk brc-history files (completed phases), dedup'd by message id, and reports a 'live' flag. Empty results distinguish 'live store reachable, peers genuinely have not proposed' from 'live source unavailable, emptiness is structural' in the hint. - The event-pump wrapper now syncs the reviewer worktree to each pending proposal SHA (hex-validated, fail-soft merge with abort on conflict) before ack/nack invocations — deterministic bash replacing the fetch/merge prose that lived in spawn prompts the event pump discards (#3033). Reviewers that must RUN the proposal (tester) get a real checkout; git-show reads (#3078) remain the fallback. * docs(brc): address PR feedback — truncated semantics, sync HEAD mutation, artifact escape Address non-blocking suggestions from the egg-reviewer review (#3083): - orchestrator/routes/messages.py: extend get_brc_transcript docstring to explain that the truncated flag reports post-filter trimming only, not upstream message-store overflow (suggestion #2). The 10000-msg upstream cap matches _write_brc_history's own retrieval bound so steady-state is benign; the docstring now makes the bound explicit and points operators at the cross-reference path for the long-lived-pipeline edge case. - orchestrator/consensus_wrapper.py: extend the sync_to_proposals block header with the durable HEAD-mutation semantics (suggestion #3). Two side effects called out: (a) dual-role agents commit on top of merged ancestry across subsequent producer turns (R11a only skips the sync, it does not un-merge); (b) multi-producer events merge SHAs sequentially, so a conflict on the second SHA leaves the first merge intact on HEAD. Neither is wrong, just durable rather than transient. - orchestrator/routes/event_prompt.py: strip backticks from producer- supplied artifact paths before markdown-code-span interpolation in the per-producer first-review fallback (suggestion #5). Not a shell- injection vector (the agent, not bash, is the consumer), but a stray backtick in a path breaks the markdown rendering. proposal_sha is already hex-validated upstream; this hardens the remaining unvalidated field with defensive replace("`", ""). * docs(brc): anchor R11a in dispatcher; test backtick stripping in delta entries - consensus_wrapper.py: add an inline 'R11a' label to the propose|ack|nack dispatcher arm so the existing docstring reference in sync_to_proposals' NB block resolves to a concrete code anchor. - test_compose_event_prompt.py: add a regression test for backtick stripping in the first-review fallback render of _build_delta_entries, pinning the b4f6e1a artifact escape behaviour. * test(brc): pin backtick strip on first-review no-SHA branch Parallel to the existing first-review backtick-strip test, but exercises the else branch of _build_delta_entries (no proposal_sha -> refs_text rendering). The strip is one list comp upstream of both branches so the existing test already pins regression, but this is belt-and-braces on the shared upstream per the f8f6cbf review's minor observation. --------- Co-authored-by: egg-reviewer[bot] <261018737+egg-reviewer[bot]@users.noreply.github.com>
Summary
Tactical fix for #3076 — phase 1 of #3077 ("coordination state is served, not replicated"). Removes the phantom-NACK failure mode without new protocol machinery: reviewers get working, concrete read paths for proposals they're asked to review, and the dead channel stops masquerading as a live one.
Two structural causes, both confirmed in code (full analysis: #3076 comment):
read_peer_artifactis structurally empty mid-phase: it reads.egg-state/brc-history/from the agent's own worktree, but the orchestrator writes that file only at phase completion, into the work branch — it reaches agent worktrees only via the spawn fork point. The bareok: true, items: []result read as "peers produced nothing."git log {last_reviewed}..HEADin the reviewer's worktree — per-role worktrees mean the reviewer's HEAD never contains the producer's commits, so the range is empty even after a revision (the literal "re-review delta is empty" v2 NACK on pipeline-2b3d8b0b). The first-review fallback said "fetch and read the diffs yourself" without saying where.Changes
orchestrator/routes/consensus.py—pending_reviewsentries carry the producer'sproposal_commit_sha(already tracked in the proposal snapshot; one field threaded through).orchestrator/routes/event_prompt.py—{last_reviewed}..{proposal_sha});HEADonly for legacy payloads. The SHA resolves from any agent worktree via the shared host object store — no push/merge choreography needed.git show <sha>:<path>per artifact,git log <sha> --not origin/<base> -p) plus an explicit "a plainReadof the path failing is NOT evidence the artifact doesn't exist — do not NACK before reading via these commands."(no commits in range — re-review is a no-op)verdict is only rendered when the range was proposal-scoped; an empty HEAD-scoped range gets an explicit caution instead.sandbox/egg_agent_tools/handlers/brc.py— the no-file branch ofread_peer_artifactreturns ahintexplaining the emptiness is structural (phase-completion artifact, spawn-time delivery) and pointing at the live channel. Hint is absent when a file exists (filtered-to-empty is a real answer).sandbox/egg_agent_tools/tools/brc.py— tool description rewritten to say what the tool actually is: a completed-phase transcript reader, not live peer-artifact access.docs/reference/agent-tools.mdrow updated to match.Test plan
test_compose_event_prompt.py,test_consensus_next_action.py,test_peer_consensus_integration.py,test_handlers_brc.py,test_orch_cli_brc.py,test_mcp_cli_drift.py. All new behaviors verified red against pre-fix code (delta endpoint, git-show fallback render, SHA sanitization, render caution,pending_reviewsenrichment, handler hint). One existing test updated deliberately: the empty-delta no-op sentinel now requires proposal scoping (the old unconditional no-op verdict was the phantom-NACK).proposal_commit_shaand agit show <sha>:<path>block;read_peer_artifact(phase=plan)should return the hint instead of a bare empty.Remaining on #3076 / #3077 (not in this PR)
Live-backed
read_peer_artifact(orchestrator message-store query) and wrapper-performed sync-to-proposal before review invocations — tracked in #3077 phases 1–2.Refs #3076, #3077