fix(acpmux): relay a down peer's send through the outbox by session id
Takes over #27590 (its owner is gone) and fixes the review that blocked it. Its two commits are kept with their authorship.
When a peer's link is down, a send to one of its sessions now reaches it through the mesh relay. The relay is a transport under the durable outbox, not a way around it:
send_queuerouting andHub::mesh_may_routeare back to the base. Every send to a peer's session is queued in the outbox as before.- An attempt the peer's link refuses as not sent (
peer_unreachable) is a newNext::Unsent. While every attempt so far was unsent (Delivery::never_sent, which is false for records saved before the field), the outbox hands a text-only delivery to the team hub. - The hub gets the session id the peer's last list named, so the session id is kept end to end. A
PEER/<uuid>the peer never listed also goes by that id; any otherPEER/NAMEgoes as name plus host, like the existingHOST/NAMEpath. The id goes without a host because the hub (cmux-platformmesh/Core.tsresolve) matches every field it is given, and the host it knows is the peer's own machine name. - Each relay post records the whole message (
Delivery::relay_post) before it goes. From then on only the relay retries it, as the same message, even after the peer reconnects, so a post whose answer was lost never also runs on the peer link. - The hub (cmux-platform
mesh/Core.tssend) checks the idempotency key before anything else. So a definite refusal (4xx other than 408 or 429) means the hub holds no message: the record is cleared and the peer link takes over again. The exceptions aremesh.idempotency_conflictandmesh.not_found, which mean the hub holds an earlier message, so those stay relay-only. The client now keeps themesh.idempotency_conflictcode;KNOWN_CODESused to drop it. - Attachments,
--supersedeand a--classother than result stay queued for the peer link, because the relay cannot carry them. A hub that refuses the post also leaves the delivery queued, with the relay's reason in the error. send_statusanswers the hub's receipt under the outbox's delivery id, withrelayDeliveryId, and with thesteerandsessionIdthatacpmux sendreads.
Fixes these review findings:
- #27590's BLOCK at 89b10e04:
PEER/<session-id>became a session name, and the early mesh return bypassed the outbox. - Sol's BLOCK at cb26608f: a lost relay answer could lead to a double run, and the relayed
acceptedhad the wrong shape. - Sol's BLOCK at 0f0d4a81: after a lost answer, a refused retry never went back to the peer link.
Changelog
Fixed: acpmux send to a session on a machine whose direct link is down is delivered through the mesh relay by that session's id, and stays queued when the relay cannot carry it.
Testing
Commit the failing tests first, then the fix:
6ae03133(tests) on #27590's code: red. A down peer's send went straight to the hub asmesh:ā¦, by name.9e2e7d0f(tests) oncb26608f: red. After a lost relay answer, the reconnected peer link is tried.84b458ce(tests) on0f0d4a81: red. A refused retry of a lost post stays relay-only.1e371305: green expected.
Command: cmux-ci test -- bash -lc 'cd cmux-tui && cargo test -p acpmux --lib server::outbox::relay_tests'
Observed / Not run
- Observed:
prepush-gatereports no new violations. Sol reviewedcb26608fand0f0d4a81(both BLOCK; each finding is fixed in the next commit pair). Its re-review at1e371305is running. - Not run yet: the cargo tests above (they go to the fleet with this PR's CI; results will be added here), and a live relay between two hosts.
Agent session: agent session
š¤ Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Makes the mesh relay a transport under the durable outbox for sends to a peer whose direct link is down, so acpmux send to a session on that peer is delivered by that session's id through the hub, and stays queued when the relay cannot carry it. Fixes the review findings that blocked the earlier attempt.
Relay behavior
- Every send to a peer's session is queued as before; a
peer_unreachableattempt becomesNext::Unsent, and only then does the outbox hand a text-only delivery to the hub for the session id the peer's last list named. - The whole posted message is recorded before it goes: once the hub may hold it, only the relay retries it, as the same message, even after the peer reconnects. A definite refusal (4xx other than 408/429) clears the record and returns the delivery to the peer link;
mesh.idempotency_conflictandmesh.not_foundkeep it relay-only. Relay attempts never clearnever_sent. - Attachments,
--supersede, and a non-result--classstay queued for the peer link; a hub refusal also leaves it queued, with the relay's reason in the error. send_statusanswers the hub's receipt under the outbox's delivery id, with thesteerandsessionIdthatacpmux sendreads.
Review fixes
PEER/<session-id>goes as that session id, not a session name, and never bypasses the outbox.- A lost relay answer can no longer double-run the prompt on the peer link.
- A refused retry of a lost post goes back to the peer link with
never_sentintact, so the relay can carry it again; a delivery held while the relay is off here stays relay-only, with an error saying why. - A peer-link attempt is saved as maybe sent while it runs, so a crash and restart with the peer down cannot relay a prompt the peer may have already run.