orrery what it is the phases the wire what running it found what was refused still open

Orrery · the build, end to end

A console for a room of machines, written without a single dependency

Eleven phases turned a read-only web wall into the thing an operator stands in front of: a room of eight machines you select from, a row of verbs that act on the selection, and five protocols underneath them that were all written by hand — because the one machine this has to run on cannot reach the internet to fetch a crate.

25,700 lines of Rust 0 dependencies 319 tests · 340 on Linux 817 KB binary 11 phases, 1 refused

What it is now

Three faces on one read model

Every screen renders copal fleet state and nothing else. A node that is not in that document cannot be dialled, and there is no second way of learning anything — a console that discovered nodes for itself would be a second fleet.

and what each one is for
facecommandwhat it is
the wallorrery --listen a page the gallery screen and a phone can open
the seatorrery --seat ID one node, full screen, in the terminal you are already in
the museum interfaceorrery --gui a native window on a Mac or a node: the lab, the verbs, the panes

The verb bar under the lab shows only what can apply to the current selection, and the footer counts what cannot. A verb with no transport is absent rather than greyed — the count went 6 → 5 → 3 → 2 → 1 as the phases landed, and the one that remains is a decision rather than a gap.

The schedule, as it actually went

Eleven phases, and what proved each one

The order was chosen so the riskiest thing was found out early and the useful thing was usable long before the end. Stopping after phase 3 would have left a working console.

#phaseproved against
0–3the seams, input, the Mac backend, the museum interface a real compositor (weston, headless) and a rendered frame
4crypto.rs — the primitives published vectors: RFC 7748, 8032, 8439, FIPS 180-4 and 197, NIST GCM
5ssh.rs and Terminal a real sshd, plus a hand-written server for the lies a real one will not tell
6sftp.rs, Exchange and Send OpenSSH’s own sftp-server — no fake at all
7tls.rs and x509.rs openssl s_server, including mutual TLS
8rdp.rs and Control xrdp — 1024×768, 137 rectangles, every one RLE compressed
9the media pane and the card writer copal-prep.sh on a real terminal, typed ERASE and all
10the node’s half, in the other repository make lint, which checks every embedded script under dash
11the truth pass this page, and a README that no longer promises what it cannot do

The rule that shaped every phase

No fake where a real server exists. A hand-written server agrees with the client’s own reading of the specification, including wherever that reading is wrong. Four of the five protocol phases ended up proved against somebody else’s implementation, and each of those found something a fake never would have.

The wire

Five protocols, one whitelist

profile.rs names one key exchange, one host-key algorithm, one signature algorithm and one cipher to offer. A test fails the build if sha1, cbc, rc4, ssh-rsa or ecdsa ever appears on a list — and orrery --profile-sshd prints the same list as sshd_config lines, which the node writes into its own configuration and whose drift make lint refuses.

the gate

A certificate, never known_hosts

The host key must be an ssh-ed25519-cert-v01 from the fleet CA, of the host kind, naming that node, valid now — and then the node has to prove it holds the certified key. No trust-on-first-use, no prompt, no bare-key branch. The signature is checked before the principals and the dates, because until then they are text an attacker chose.

the lockdown

Mutual TLS is what stops a standard client

The node demands a client certificate from the fleet CA. mstsc and FreeRDP have none and cannot be given one, so their handshake ends before RDP begins. Proved against s_server -Verify 1: in with the certificate, out without it.

the narrowing

One suite offered, two accepted

In SSH the client’s preference decides, so offering ChaCha first and offering it alone are the same session. In TLS the server picks — so the ClientHello names exactly one suite, because the alternative runs at half a megabyte a second.

What running it found

Six things no amount of reading would have caught

1 · GSSAPIAuthentication no is an error on Alpine

OpenSSH there is built without GSSAPI, so the line the profile rendered made every connection log a complaint about the node’s own configuration. Found on the first run of ssh-check.sh against a real sshd; the line is gone and a test keeps it gone.

2 · The bitsliced AES costs 370×, not “a few”

Measured rather than assumed: 186.1 MB/s for ChaCha20-Poly1305 against 0.5 MB/s for AES-256-GCM. Since a TLS server picks the cipher and rustls prefers AES, offering both would have handed a Raspberry Pi the slow one. That is why tls.rs offers one suite — and why a test now runs an AES-only server and requires the console to refuse it.

3 · The transcript bug that only appears with a client certificate

TLS 1.3’s application keys come from the transcript up to the server’s Finished and nothing after it. Working that point out by arithmetic at the end of the handshake was right while the client sent only a Finished, and silently wrong the moment it also sent a certificate: a handshake that completes and then cannot decrypt a byte.

4 · FreeRDP will not serve an Ed25519 certificate

It derives its channel bindings by hashing the certificate with the digest named in the signature algorithm, and Ed25519 names none. unable to retrieve bindings, and the connection drops before RDP starts. That is a finding about the fleet’s one-algorithm-everywhere decision rather than about this code — and it is why the RDP suite runs against xrdp instead.

5 · fcntl is variadic, and declaring it otherwise is silent

C declares it int fcntl(int, int, ...). Declared with a fixed third argument it compiles, links and runs — and on aarch64 Darwin the variadic convention passes that argument on the stack while a fixed one goes in a register, so O_NONBLOCK never arrived. Every read meant to return immediately blocked until the far end spoke. It hid for two phases because every read happened after a question had been asked, and appeared the instant something read a pipe before asking one. Two tests now ask for nothing and require an answer anyway.

6 · Three drawing bugs that a green test run cannot see

Rect::cut_bottom returns the piece it took first, the opposite of cut_top — the whole file browser drew inside a forty-pixel strip. The gutter between its columns was never painted, so it showed the canvas’s own zeroes as a black hole. And a button labelled ← copy drew as copy, because the font baked U+2192 and not U+2190 and a missing glyph is a blank cell rather than a panic. All three were found by rendering a frame to a file and looking at it.

What was refused

The absences are the design

The arithmetic

What it costs to run

Binary, release, stripped
817 KB
Resident, serving the wall
1.1 MB
The Python prototype it replaced
22 MB
ChaCha20-Poly1305
186.1 MB/s
AES-256-GCM, constant-time circuit
0.5 MB/s
SHA-256
129.8 MB/s
Ed25519 sign · verify
0.97 ms · 0.42 ms
X25519 exchange
0.15 ms
Tests, macOS · Linux
319 · 340

Still open

What this does not prove

The truth pass exists to keep this list accurate rather than to shorten it.

And the next five days of work, priced

One SSH connection per node instead of one per verb (a second saved per session, eight per fan-out); row-wise blitting instead of per-pixel (the reason Control feels heavy); one byte writer instead of three (150 lines, and three places to get a length wrong becomes one); one pane lifecycle; one fixture script for the container suites. Each is written up with what it costs today and what the fix costs, because a refactoring list with no prices on it is a wish list.