ADR-0096: rio speaks the protocol; it does not dial
- Status: Accepted
- Date: 2026-09-11
- Deciders: jka
- Decision: D96
Context
ADR-0030 left a pending phase: an --ssh host [path] wrapper that would open a tunnel
and attach in one step. The maintainer rejected it: people reach a core in many ways
(ssh -L, Tailscale, WireGuard, a VPN, a bastion host), rio should not predict which,
and it should be network-transparent in the way X11 is.
X11 does not know what SSH is. ssh -X is SSH's feature: SSH opens a listener and sets
DISPLAY. X's contract is only that a display string names an endpoint and X speaks its
protocol over whatever byte stream it is given.
Decision
rio speaks its protocol over a byte stream; how that stream reaches the core is the
operator's business. The --ssh wrapper is refused as a category, not deferred, and the
channel-transport plan is closed at its third phase.
--connect host:port already fulfils the requirement: every tunnel, overlay network and
VPN ends in a host and a port. Future proposals shaped like "rio helps you reach a
core" (tunnel helpers, connection managers, discovery) are out of scope by decision.
Alternatives considered
--core-cmd <command>, where the operator names a command whose standard streams
become the channel (ssh host … --stdio, kubectl exec -i, socat), in the style of
git's GIT_SSH_COMMAND and LSP client commands. It was built, tested and committed, then
reverted at the maintainer's request: rio already had one way to reach a remote core, and
a second mechanism for the same job contradicts the same simplicity argument that refused
--ssh.
Consequences
- There is nothing to maintain for any particular tunnel technology.
- rio cannot be used remotely without a listening core. A private remote core over a
pipe, with process ownership as access control, is a real gap. The
--core-cmddesign above is how to close it if someone needs it. - While a socket is the only remote transport, the GUI's
::core_remoteflag stands for two facts: the transport is a socket, and the core's filesystem is not the GUI's. Anything that makes a pipe remote must first split it, as the reverted change did with::core_transport(pipeorsocket). Otherwise the stale-link watchdog (ADR-0037), which is meant for sockets only, would arm against a pipe.