Transport
How a frontend reaches a daemon that is not on its own box.
No loopback ports on the control plane
The control plane opens no loopback port. It is an ssh child's stdin/stdout, or a Unix socket or Windows pipe in a directory only the owning account can read, so there is nothing on it for a second account on the same box to reach.
The endpoint grammar
Every endpoint this version can dial has one of three schemes:
unix:<path>/pipe:<name>— a socket or named pipe reached directly. A generated hub relay socket is a route to a remote daemon: the window does not lease that remote daemon, and reuses the path for gated page and lane connections.ssh:<target>— that box's own daemon, reached by spawningssh <target> sotd stdio-bridgeand speaking the protocol over its stdin/stdout.<target>is a plain host name (hosts.toml's own grammar): never a shell command, never anything with a leading-.ssh:<target>/<host>— a daemon<target>(always the hub) can reach on<host>'s behalf, throughsotd stdio-bridge --host <host>. The--hostargument names which of the hub's own relay sockets to bridge to; the box that owns that endpoint derives its path itself — an endpoint's shape is never carried across the wire.
sotd stdio-bridge takes no caller-supplied label. With no argument it resolves this box's own daemon, at the label this box resolves for itself, never one a caller names.
--endpoint <unix:path|pipe:path> is for a script on this same box that already holds a local endpoint; the ssh forms above never carry one.
What sotd topology plan gives out
sotd topology plan --self <host> prints the facts the launchers use to pick each box's relay endpoint and the endpoint to dial for each daemon host. Dialing a host means spawning an ssh child, not opening a port ahead of time.
A box that has never declared a topology resolves its own daemon. A single-box install needs no hosts.toml at all: sotd topology relay-endpoint answers this box's own endpoint the moment there is no file, because with no file nothing has ever told the box of another box, so its own daemon is the only daemon it could mean. A file that exists but does not list this box is a different case and stays an error — that file names a hub, so other boxes provably exist, and answering "myself" there would be exactly the silent wrong-box failure this design deletes.
An endpoint you name that this version cannot dial is refused, never replaced by a local one. A stale SOT_FE_ENDPOINT or SOT_SPAWN_ENDPOINT naming a form this version does not speak (a leftover tcp: value, most often) is refused with a line naming it; clear the variable rather than expecting it to be silently reinterpreted.
The second and third connections
A remote host's control connection is not the only one this frontend ever opens to it: attaching a capsule pane and proxying one browser connection to a backend-served page each spawn their OWN ssh child, through the exact recipe the control connection already resolved for that host — never a second, independent guess at how to reach it. Each of these connections starts with a hello whose role is handoff and carries its lane.connect or proxy.connect in the same write, so the hello costs no extra round trip. An SSH pane needs a supervisor login and a voyage login for its initial cold attach; a proxied browser connection uses one login. Cold attach may start the voyage login alongside the supervisor login. Local endpoints park no ssh login. If the spare fails or expires, the first voyage uses its ordinary gated fresh dial. After the first voyage, every later dial uses one login.
A page served by a remote daemon pays once per browser connection, not once per file: the daemon's page servers keep a connection for the next request. A connection with no whole request head within 30 s of its wait's start is closed, as is one whose client takes more than 30 s to accept 64 KiB of a reply.
A port the daemon answers as not served (bad_port) is parked: its listener stays bound but closes each browser connection at once, with no login and no log line, until a page on that port is opened again from the frontend. A tab left open on a dead page therefore stops costing a login per retry after the daemon's first refusal. The daemon logs a refused port once, until that port is served again. While the host's link is down, a browser connection closes at once with no log line; the status line says the link is down.
The link gate. While a host's link is down — its control transport's last attempt got no hello reply, or its session ended — the frontend starts no ssh login to that host except the transport's own reconnect probe, and no start site repeats a failed login sooner than a doubling backoff allows. The gate is one flag per host. Only the transport writes it: up when any hello reply arrives (a refusal proves the link too), down as soon as its session ends for any reason but a refusal of the hello itself. Every other ssh start site asks the gate first, and the one ungated spawn is the transport's probe, so a new start site cannot skip it. The transport's probe backs off by doubling from 200 ms, to a cap of 5 s on a local socket and 30 s on an ssh dial. Once the wait reaches its cap, a down host costs the hub at most two logins a minute per frontend; the first minute, while the wait doubles, costs about nine. The wait starts over only after a session that lasted a minute (STABLE), or at F5, so a daemon that answers the hello and then drops costs no more than a down one; the daemon's hub link and each pane's attach worker follow the same rule. F5 retries at once. A browser connection to a proxied page is refused while the gate is down.
Pause and resume. A pane that is not on screen starts no new attach or voyage re-dial after a failure of any kind; it dials when you next view it. An attach whose lane dial finds the link down stops dialing, shows "host offline, waiting for the link", and checks every 100 ms whether the link is up and its pane is the one on screen. The transport's hello reply reopens the gate, so the viewed pane dials within a tick of it; parked panes keep their last screen and dial when next viewed. A resize read while paused is the size of the next attach. At the link's return the cost is the transport's one login plus two lane logins (supervisor and voyage) per viewed row, and no login of any kind while the link is down.
Residual. After a silent network drop the transport can take up to its keepalive window (about 45 s) to notice, so a lane that dies first may still dial once. Each such dial is bounded by the connect timeout and its backoff and reaches no sshd, because the host is unreachable.
A failed login or a dead sotd on the far end is the child exiting before speaking the protocol; its last stderr line is the diagnosis, surfaced in the pane's own status text or, for a proxied page, a log line — no per-cause exit codes to learn.
Browser-facing ports
A browser can only speak TCP, so the pages a daemon serves (video, documentation and its pool) and the frontend's page proxy keep loopback ports. Every one of them checks which OS account owns each incoming connection before reading a byte, and closes any other account's connection without answering: on Linux and macOS from the kernel's TCP table, which records the account that opened each connection, and on Windows from the account of the process that bound the connection's socket, refused if that process started after the bind. The first refusal per account and port is logged as a warning. A connection the daemon makes itself, the proxy reaching a page for a remote frontend, is the daemon's own account and is served.
A frontend showing documentation, a video or Pluto from another computer listens for it on a loopback port of its own, which the operating system assigns, and opens the page's address with that port in place of the daemon's: the daemon's port number names a port on the daemon's computer, which on the frontend's computer may belong to anything, its own daemon included. Such a tab does not survive a restart of the frontend; open the page again. It does survive the frontend's reconnects: a documentation page belongs to the window that opened it, not to one connection, so a dropped link or a computer that slept leaves it serving, until that window opens another page in its place, the daemon forgets the window (past 64 windows away, it forgets the one away longest), or, for a page on a port of its own (root-relative links), another window's open takes that port while this window is away. A restart of the daemon ends every page; open it again after one. A page your own code serves (wglshow, browserview) or sot-fe open-url names may name its own address, so it keeps the daemon's port number, and the frontend declines to open it when something on its computer already holds that number.
Pluto's server and the server wglshow starts in your REPL are Julia's own listeners on loopback ports, which any account on the box can reach, so each answers nothing without a secret drawn from the operating system's secure generator. Pluto serves everything, its own files included, under a path that is Pluto's session secret, and each notebook runs in a worker that checks a cluster cookie, read from its standard input, on every connection. A wglshow figure is served on a port the operating system assigns, at an address with a secret in it, and its websocket under an unguessable session id; the page carries its scripts and files inside it, so another account that finds the port gets nothing. One server per REPL keeps its port and its secret for the REPL's life, so the secret is never valid on a port it has let go. The page is sent with Referrer-Policy: no-referrer.
No page address goes on a command line, where another account could read it. The frontend hands the browser only the address of a one-use loopback listener of its own. That listener serves this account alone, answers the browser's first request with a redirect to the page, and then closes. Log files mask page secrets (sot_log::secret), and a BrowserView displays only a page's host and port. The logs and the screen are this account's own; the channels another account can read are command lines and ports, and neither carries a page secret.
An address your own code takes out of a BrowserView (bv.url) and returns or prints is your own output, shown like any other value on this account's own channels. An address you pass to sot-fe open-url is on that command's line: open a figure on one frontend with wglshow(fig; open = "<fe>"). A page your own code serves on a port it binds itself is a socket Ship of Tools never opened. Any account on that box can reach it, and nothing here can change that. Serve figures with wglshow.
Another account can still fill one of these listeners' queues and slow your own connections; each connection it opens is refused, or answered with nothing, quickly. The guarantee is isolation, not availability.
One listener is the exception to that isolation. On Linux the REPL and the Pluto server each have an X display of their own, an Xvfb on an abstract socket, which has no permissions: any account on the computer can connect to it, and its only lock is a 128-bit cookie kept in the child's own folder. ADR 0049 records it under User isolation. The child ends when its display ends, and gets a new display at its next start.
Who may connect
The window and the bridge speak only to an endpoint of their own OS account: on Unix a socket whose listener ran as that account when it called listen(), on Windows a pipe served by that account's process.
The daemon admits a connection only from its own OS account: at accept it checks the peer's account, and its first frame is a hello naming the OS account. A second account using the same hub account is refused with os_user_conflict.