acpmux: a peer's dispatch runs here and names none of this host's peers

Merged
TE
9d ago
+268-3

A peer daemon's connection (Origin::Peer, paired devices included) learns nothing about this host's other peers (#26823, #26860, #26881, #26887). _acpmux/dispatch still leaked them in three ways:

  • Over budget. When this host was over its CPU/RAM budget, the reply and the brief row carried suggestedHost: the connected peer with room.
  • host: auto. The placement engine could put the brief on one of this host's peers. The reply's brief then named that peer, and when no host had room the error repeated the engine's why.
  • A named host. The answer was no peer "X", peer "X" is not connected, dispatch to X failed: …, a role refusal from the fleet registry, or the forwarded reply. Each one told the caller whether this host has that peer and whether it is up.

For a peer connection, the brief now runs on this host, as a forwarded brief already does:

  • auto (the default) weighs no peer. The brief runs here.
  • local, or this host's own name, runs here.
  • Any other host gets one refusal: invalid_params, reason: dispatch.peer_host, "a peer's dispatch runs on this host only". It is the same answer whether that peer exists, is up, or is down. Nothing is looked up or forwarded.
  • Over budget, the reply keeps warning (host-budget: <this host> over its CPU/RAM budget: …), which covers this host only. suggestedHost is null, and the brief row is stored without one.
  • A replayed brief (same briefId or idempotencyKey) comes back only if this host ran it, meaning its session is one of this host's, open or closed (Hub::brief_ran_here). It comes back without suggestedHost. A row for a brief that one of this host's peers ran is kept here with that peer's name in host, target and to, so it is refused with one answer (reason: dispatch.brief_taken) whichever peer ran it.

Local, app and Web connections keep the full answer: placement, forwarding and suggestedHost. This follows the admission pattern from #26887 (Ask::peers).

Not changed here: the brief ledger's other methods, _acpmux/briefs, _acpmux/steer and _acpmux/report, still list and act on rows whose host is one of this host's peers when a peer connection calls them. That is a separate surface, left for its own PR.

Test

tests/peer_dispatch_origin.rs. The hub runs in process. Its budget is set through ACPMUX_HOST_BUDGET (any load and less than all RAM free counts as over), so this host is over budget. A fake peer reports a fixed health reading with room, so the peer is not. The test checks:

  • the unix socket gets suggestedHost: "elsewhere";
  • a peer connection's local and auto dispatches run here (brief.host: "local"), keep this host's warning, and never mention elsewhere;
  • a replay of the local brief over the peer connection never mentions elsewhere;
  • replaying a brief the unix socket dispatched to elsewhere is refused as taken, and the refusal never mentions elsewhere;
  • host: "elsewhere" and host: "nosuch-peer" get the same refusal.

Before the fix (this PR's final test file, with requests.rs and hub/dispatch.rs as on base; cmux-ci run https://github.com/manaflow-ai/cmux/actions/runs/40730753333), it fails:

panicked at crates/acpmux/tests/peer_dispatch_origin.rs:176:9:
a peer was told of this host's peer: {"brief":{... "host":"local", ... "suggestedHost":"elsewhere", ... "warning":"host-budget: ip-17... over its CPU/RAM budget: ..."}, ...

At the head (f9681211fbac; cmux-ci run https://github.com/manaflow-ai/cmux/actions/runs/40731429127), it passes, along with the neighbouring suites:

tests/inherit_start.rs          test result: ok. 13 passed; 0 failed
tests/peer_admission_origin.rs  test result: ok. 1 passed; 0 failed
tests/peer_dispatch_origin.rs   test result: ok. 1 passed; 0 failed
tests/peer_state_origin.rs      test result: ok. 2 passed; 0 failed
acpmux --lib dispatch           test result: ok. 3 passed; 0 failed

Risk: low. The change only affects peer-origin _acpmux/dispatch. Replaying a brief whose local session has since been deleted from the store is also refused as taken. A peer that used to send a non-forwarded dispatch naming one of this host's peers is now refused. Daemons always mark what they forward (forwarded: true), which still runs here as before. Nothing in the tree sends that request.

Agent session: agent session

šŸ¤– Generated with Claude Code