Transport abstraction
Transport abstraction
TL;DR. The wire codec sits behind an async Transport trait on both
ends, so the same framing rides any byte stream. Five implementations exist
today: a Unix-domain-socket transport for local server/client links, a
WebSocket transport that carries the identical codec to browser consumers,
a QUIC transport for remote clients, a WebTransport (HTTP/3 over QUIC)
transport that gives browsers QUIC-class transport, and an SSH-stdio
transport that splices the codec through ssh HOST phux stdio-bridge (the
federation hub’s ssh:// dial path). Outbound establishment for the TLS
transports is shared in phux-dial (attach loop and hub dialer alike). No
domain module names a concrete transport type; all I/O goes through the
trait.
The wire codec sits behind an async Transport trait on both server and
client. No domain module in phux-server or phux-client names a concrete
transport type; all I/O goes through the trait, which keeps new transports
additive rather than invasive.
Implementations that exist today
UnixSocketTransport— the local server/client link. This is the default path for a server and the clients attached to it on the same host.- WebSocket transport — carries the same wire codec to browser
consumers.
phux-web(the web consumer, per ADR-0025) speaks the exact framing over WebSocket and projects engine state locally; the bytes on the wire are identical to the UDS path, only the byte stream underneath differs. Native attach can also use this path withphux attach --ws, making it the TCP fallback when UDP/QUIC is blocked. - QUIC transport (via
quinn, ADR-0007) — for remote clients. Each connection opens one bidirectional QUIC stream and frames the identical codec over it. TLS 1.3 is intrinsic; a routable listener authenticates each attachment with a bearer-token preamble (ADR-0031 parity with thewss://path), reusing the same persisted self-signed cert and token store. Opt-in viaphux server --quic <HOST:PORT>; the connection migration and 0-RTT resumption that motivate QUIC (the Mosh-class roaming UX) are inherent to the stack, with a roaming-aware client the follow-up. - WebTransport transport (via
wtransport) — QUIC-class transport for browsers, which cannot open raw QUIC connections. An HTTP/3CONNECTsession whose single bidirectional stream carries the identical length-prefixed frames the UDS and QUIC paths use; the HTTP/3 layer is a transport detail below the frame seam, so nothing on the wire changes. Always TLS 1.3 (QUIC mandates it); a routable listener requires the samephux pairbearer token as thewss://path (ADR-0031), carried in theCONNECTrequest —Authorization: Bearer <hex>from native consumers, or?token=<hex>on the session URL from browsers (the JSWebTransportAPI cannot set request headers) — and refused with HTTP 403 before the session exists. Shares the persisted certificate and token store with the WebSocket and QUIC listeners; binds its own UDP socket because browsers offer only theh3ALPN while the raw-QUIC endpoint advertises the phux-private one. Opt-in viaphux server --webtransport <HOST:PORT>orPHUX_WT_ADDR;phux-webdials it first and falls back to WebSocket. Feature-gated inphux-serveraswebtransport(on by default). - SSH-stdio transport (ADR-0007, phux-v45.9) — frames the wire codec
over a child SSH process’s stdin/stdout. The dialing side spawns the
system
sshbinary ($PHUX_SSHoverrides the program — an OpenSSH-compatible wrapper, or a stub in tests) running the remotephux stdio-bridgeverb, which splices its stdin/stdout byte-transparently to the server’s Unix socket on that host. SSH supplies authentication and encryption; the bridge holds an ordinary local UDS connection under the socket’s owner-only permissions, so no bearer token or certificate pin is involved (ADR-0038 addendum). First consumer: the federation hub dialingssh://satellite endpoints. The hub spawns ssh withBatchMode=yes(never an interactive prompt), a---guarded, charset-validated argv (endpoint parts that could read as ssh options are rejected at hub-table validation), and treats the child exiting as a dropped link, feeding the same capped-backoff redial loop as the QUIC/WS paths. Keepalive / idle: liveness for ssh links lives at the SSH layer, not in-band — the hub dials withServerAliveInterval/ServerAliveCountMaxderived from the same interval/timeout constants the WS path uses, so a silent partition makes the ssh child exit and the exit is the ordinary disconnect signal. The bridged phux stream stays byte-transparent (no link ping rides it), mirroring how QUIC delegates the same contract to quinn.
All five run the same codec. A consumer that can frame the codec over a stream is a peer regardless of which stream it uses.
Outbound dialing is shared
The client-side establishment of the two TLS remote transports — TLS 1.3
with a fingerprint-pinned (or loopback skip-verify) certificate verifier,
plus the ADR-0031 bearer token — lives in the phux-dial crate, consumed
by both the phux-client attach loop and the federation hub’s outbound
link supervisors (phux server --hub dials each enabled satellite as an
ordinary remote consumer per ADR-0038, with reconnect and capped
exponential backoff; see phux-server::hub::link). phux-dial stops at
the established byte stream; framing stays behind the transport trait on
each end. The SSH-stdio path does not go through phux-dial — its
“establishment” is a child-process spawn, and its trust stack is SSH’s,
not rustls — but it feeds the same link supervisors, backoff, and
per-satellite status reporting on the hub.
The hub relays frames over its links
While a satellite link is up, the hub routes frames over it
(phux-server::hub::relay, ADR-0007 §4): a frame targeting
TerminalId::Satellite { host, id } is rewritten to the satellite’s
Local { id } space and forwarded verbatim — the hub never re-encodes VT
bytes — and return-leg responses and subscribed streams are re-tagged
Local -> Satellite { host, id } before reaching the consumer. Each link
owns a bounded relay mailbox (producers try_send and fail fast), its own
link-side COMMAND.request_id remap, and a proxy-subscription registry;
return-leg fan-out try_sends into each consumer’s bounded outbound
mailbox so one slow consumer never stalls the link. While the link is down
(dialing, backoff, ADR-0038 fail-closed refusal) the supervisor drains the
mailbox and fails every request with the typed SatelliteUnreachable
error; a satellite disconnect fails in-flight commands the same way and
pushes one typed error to every proxy-subscribed consumer before the
registry clears. A satellite that dies silently is bounded too: each
relayed command carries a hub-side deadline resolving to the same typed
error, and every link enforces a keepalive / idle contract — QUIC via the
transport (phux-dial sets keep_alive_interval / max_idle_timeout),
WebSocket via hub-originated pings plus an inbound-idle limit in
phux-server::hub::link, SSH-stdio via the SSH layer’s
ServerAliveInterval / ServerAliveCountMax on the dial argv (a peer
that stops answering probes exits the ssh child — the drop signal) — so
a partition without FIN/RST is torn down like an ordinary disconnect
instead of pinning consumers on a link that still looks connected.
Normative routing semantics: docs/spec/L1.md §9.1.
With SSH-stdio built, every transport ADR-0007 designed exists. See ADR-0007 for the forward-compat constraints that still govern them (URI-shaped session IDs, hub-and-spoke satellite topology, per-pane encoder isolation).