acpmux: a peer's lane-cap warning and down-peer refusal name none of this host's peers

Merged
TE
9d ago
+224-13

Follow-up to #26860 and #26881, which stopped this host's peer topology from reaching a connection whose origin is Origin::Peer (a peer daemon, or a paired client device). Their Codex review named two older paths that could still name another peer to such a connection. This PR closes both.

What a peer connection now gets

  1. Lane-cap warning (session/new, session/fork, composer creates). Before, a create started past this host's lanes.max_lanes returned _meta.acpmux.admission.suggestedHost and a warning that named one of this host's connected peers ("Next time try acpmux new --host auto (placed on far: ...)"), or said "No connected host has room either." Now a peer's connection gets no suggestedHost, and the warning covers this host only: "... started anyway, as a cap only warns. Next time start it on another host." That is the line a Web client already gets. (Ask::peers, hub/admission.rs; set in server/requests.rs ask.)
  2. peer_unreachable refusals for a PEER/... session id. Before, a request for a session on a peer that is down was refused as peer_unreachable: gone (offline, never reached: IO error: Connection refused (os error 111)); ..., plus data.peer, peerState, lastSeen and error. That happened both when gone's saved rows list the session (the request is forwarded and the link refuses it) and when they don't (Hub::peer_down_for). Now:
    • for a name that gone's saved rows don't list, a peer's connection gets the same answer as a name that matches no peer: no session matches "gone/lane". This matches #26881, where _acpmux/resolve gives a peer the read-only device's generic not-found (server/peer_forward.rs);
    • for a session the saved rows do list, it gets peer_unreachable: the request was not sent; retry later, with only reason, retryable and detail in data. The same applies to any peer_unreachable the forward path returns to a peer's connection, such as a stalled link's queue timeout (redact::hide_unreachable_peer). A peer connection's requests are still forwarded, because a paired client device may still open a peer's session through this host.

The unix socket, the local app and Web origins see exactly what they saw before. Sub-session admission keeps its old inputs (peers: req.local), and it never returns the warning anyway.

Risk: low. Both changes only drop detail for Origin::Peer. A paired client device that sends gone/x while gone is down now gets "no session matches" (an unlisted name) or the generic "peer_unreachable ... retry later" (a listed one), with no peer name or reason. Nothing on a peer reads this host's peer topology (#26860). On the peer, recovery.rs still finds reason: peer_unreachable, and its peer link writes its own name for this host into data.peer (name_peer).

Tests (fail before, pass after)

  • tests/peer_admission_origin.rs (new; its own process, because it sets FLEET_REGISTRY): A has a fake peer far whose _acpmux/host_health reading is fixed, so the result doesn't depend on runner load. The registry caps A at 0 lanes and gives far room. The unix socket's create is told suggestedHost: "far". The peer's create is warned about A's cap only: no suggestedHost, no "far", no "connected host".
  • tests/peer_state_origin.rs a_peer_connection_hears_nothing_of_a_down_peer_in_a_refusal: gone is down, and its saved rows list gone/m1. For both gone/m1 and gone/lane, the unix socket gets peer_unreachable: gone (... Connection refused ...). A peer gets the generic refusal for gone/m1, and for gone/lane the same answer it gets for nosuch/lane with the name swapped.
  • hub/admission.rs unit test: the peer variant of the warning text says nothing about connected hosts.

Before (the final tests run against base source; cmux-ci run 40713115685):

---- a_peer_connections_lane_cap_warning_names_none_of_this_hosts_peers ----
a peer was told of this host's peer: {"live":1,"maxLanes":0,"suggestedHost":"far","warning":"<host> has 1 live sessions, at its cap of 0 (...); started anyway, as a cap only warns. Next time try `acpmux new --host auto` (placed on far: 1.0/64 cores busy, 537 GB disk free, 0/1000 lanes, room for 630)."}
---- a_peer_connection_hears_nothing_of_a_down_peer_in_a_refusal ----
assertion `left == right` failed: a peer's refusal told it of this host's peer
  left: "peer_unreachable: gone (reconnecting, last seen 20754d ago: IO error: Connection refused (os error 111)); the request was not sent; run `acpmux host ls` and retry after the peer reconnects"
 right: "peer_unreachable: the request was not sent; retry later"

At the first tests-only commit (8a21777b, run 40708575431), the unlisted-name case failed the same way: left: "peer_unreachable: gone (offline, never reached: IO error: Connection refused (os error 111)); ...", right: "no session matches \"gone/lane\"".

After (head 4fc44c1c; cmux-ci run 40713176737): cargo fmt --all --check OK and cargo clippy -p acpmux --all-targets -- -D warnings OK. All three peer-origin tests pass:

test a_peer_connections_lane_cap_warning_names_none_of_this_hosts_peers ... ok
test a_peer_connection_hears_nothing_of_a_down_peer_in_a_refusal ... ok
test a_peer_connection_hears_and_lists_none_of_this_hosts_peers ... ok

The rest of cargo test -p acpmux has reds that also fail on base 05149d1d (base run 40711205959): daemon_chat_open, ephemeral_run, hermetic_daemon_guard, remote_guard_modes, session_pool. document_prompt (3 timeouts) and attach::upload::one_sender_at_a_time... failed once at this head and passed when rerun alone at the same head (runs 40716793397, 40715736931), as did hub_integration stop::a_cancel... (a 200 ms timing budget, rerun green at 2db480c4).

Review: Codex gpt-6-astra (high, fast). It returned BLOCK at 2db480c4 (a down peer's saved rows bypassed the guard; the admission test depended on runner load), both fixed in 10d8b585 and 4fc44c1c. It returned SHIP at 4fc44c1c.

Not covered here

_acpmux/dispatch also returns a suggestedHost when this host is over its CPU/RAM budget. This PR leaves it alone, because an in-process test can't put one hub over budget while its peer on the same machine is not. A follow-up can cover it with a spawned peer daemon.

Agent session: agent session

šŸ¤– Generated with Claude Code