a73x

39bf2478

docs: the macOS gates name their boxes, and the guest is provisioned

a73x   2026-09-04 12:50

Commit message
docs: the macOS gates name their boxes, and the guest is provisioned

CLAUDE.md's toolchain block and the README's macOS section say what the
three names are and what a run looks like; the README's VM recipe is now
the base image only, since the clone is provision-mac.sh's. The dated
decisions entry records why the gates take names, why bridged rather
than NAT, the forget-and-relearn of a per-clone host key, scrub at both
ends, and that the user's real Mac is no longer a gate target.

decisions.md re-measured at 8.8k lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SakwJEwD9dXBoRP5kWbemW

CLAUDE.md
Old New
@@ -17,7 +17,15 @@ make check # fmt + unit tests + shell syntax + comment-claim
17 make ci # check + e2e + agent + throughput — the delivery gate 17 make ci # check + e2e + agent + throughput — the delivery gate
18 make agent soak bench throughput 18 make agent soak bench throughput
19 make vm # real user journeys against the mux-e2e VM (test/vm.sh) 19 make vm # real user journeys against the mux-e2e VM (test/vm.sh)
20 make mac xos # macOS journeys (test/mac.sh); the cross-OS gate (test/xos.sh) 20 make mac xos # macOS journeys (test/mac.sh); the cross-OS gate (test/xos.sh).
21 # Both take their boxes BY NAME and have no default
22 # for one: MAC_BOX (macOS, no toolchain, SCRUBBED),
23 # MAC_BUILDER (a Mac with zig + deps/mac-sdk, builds
24 # only, never scrubbed, defaults to $MAC_BOX), and
25 # for xos also LINUX_BOX (x86_64 Linux VM, scrubbed).
26 make provision-mac # clone, boot and key a macOS guest on a tart host and
27 # print one line; `eval "$(test/provision-mac.sh)"`
28 # is how MAC_BOX gets set. --down takes it away.
21 make install # the one user binary -> ~/.local/bin, ReleaseSafe 29 make install # the one user binary -> ~/.local/bin, ReleaseSafe
22 # MUX_TARGET follows the HOST: a Mac installs a Mac 30 # MUX_TARGET follows the HOST: a Mac installs a Mac
23 # binary, because that is the only one it can link 31 # binary, because that is the only one it can link
@@ -36,7 +44,7 @@ costs ~800k tokens; every token stays in context and is re-billed each turn.
36 siblings in `src/server/`, none over 1.8k; the agent relay and the 44 siblings in `src/server/`, none over 1.8k; the agent relay and the
37 session table are `src/server/server_agent.zig` and `src/server/server_sessions.zig`), 45 session table are `src/server/server_agent.zig` and `src/server/server_sessions.zig`),
38 `src/tui/interact.zig` (4.8k), 46 `src/tui/interact.zig` (4.8k),
39 `docs/decisions.md` (8.7k). `test/e2e.sh` is a 188-line runner now; the 47 `docs/decisions.md` (8.8k). `test/e2e.sh` is a 188-line runner now; the
40 bodies are `test/e2e_NN_*.sh` (`E2E_ONLY=<group>` runs one). 48 bodies are `test/e2e_NN_*.sh` (`E2E_ONLY=<group>` runs one).
41 Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. 49 Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window.
42 `src/client/client.zig` is 3.3k now — cheap enough to read, and so is 50 `src/client/client.zig` is 3.3k now — cheap enough to read, and so is
@@ -485,7 +493,7 @@ own. Test fixtures in `test/`:
485 ## Where the answers live 493 ## Where the answers live
486 494
487 `docs/roadmap.md` (~660 ln) the ranked queue, but stale past 2026-08-16 — 495 `docs/roadmap.md` (~660 ln) the ranked queue, but stale past 2026-08-16 —
488 `git-collab issue list` is the live order · `docs/decisions.md` (8.7k ln, grep 496 `git-collab issue list` is the live order · `docs/decisions.md` (8.8k ln, grep
489 only) every decision + measurement · `README.md` user-facing usage. Design 497 only) every decision + measurement · `README.md` user-facing usage. Design
490 history (specs, plans, the original handoff) lives in git: `git log -- docs/superpowers`. 498 history (specs, plans, the original handoff) lives in git: `git log -- docs/superpowers`.
491 499
README.md
Old New
@@ -755,13 +755,32 @@ so a `make e2e` on a Mac stops at the first of the two. The other gates —
755 `make agent`, `make soak`, `make throughput`, `make xversion` — have not 755 `make agent`, `make soak`, `make throughput`, `make xversion` — have not
756 been run on a Mac at all. 756 been run on a Mac at all.
757 757
758 The gate for macOS is `make mac`, 758 The gate for macOS is `make mac`, the macOS half of what `make vm` is for
759 which is the macOS half of what `make vm` is for Linux: it pushes this 759 Linux. Both macOS gates take the machines they use BY NAME, as ssh targets
760 commit to the Mac named by `MUX_MAC` (default `squirtle`), builds it there, 760 in the environment, and neither has a default for one:
761 and then runs the user journeys against a *pristine* macOS virtual machine 761
762 with no developer tooling on it, so what is graded is the product on a bare 762 | Name | What it is |
763 Mac rather than on the machine that compiled it. The VM is Cirrus Labs 763 |---|---|
764 `tart`, and the base image is made once by hand, on the Mac: 764 | `MAC_BOX` | the macOS box under test, with no developer tooling on it. Scrubbed to a mux-less state at the start of a run and again at the end, so make it a VM. |
765 | `MAC_BUILDER` | a Mac holding this repo with zig and the shadow SDK. It builds, and nothing else: no mux is installed there and nothing is reset there. Defaults to `MAC_BOX`. |
766 | `LINUX_BOX` | `make xos` only: an x86_64 Linux VM that both this box and `MAC_BOX` can reach. Scrubbed the same way. |
767
768 `make mac` pushes this commit to the builder, builds and runs the unit suite
769 there, streams the binary onto `MAC_BOX`, and then runs the user journeys —
770 so what is graded is the product on a bare Mac rather than on the machine
771 that compiled it. A run looks like this:
772
773 ```sh
774 eval "$(test/provision-mac.sh)" # prints: export MAC_BOX=admin@192.168.0.x
775 MAC_BUILDER=squirtle make mac
776 test/provision-mac.sh --down # when you are done with the guest
777 ```
778
779 `test/provision-mac.sh` is where the virtual machine's life is spelled: it
780 clones a pristine base image on a Mac that has Cirrus Labs `tart`, boots the
781 clone bridged onto the LAN so this box can ssh to it directly, and installs
782 this box's public key on it. The base image itself is made once, by hand, on
783 that Mac:
765 784
766 ```sh 785 ```sh
767 brew trust cirruslabs/cli && brew install cirruslabs/cli/tart 786 brew trust cirruslabs/cli && brew install cirruslabs/cli/tart
@@ -771,21 +790,17 @@ tart exec mux-mac-base sh -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo "PUBKE
771 tart stop mux-mac-base 790 tart stop mux-mac-base
772 ``` 791 ```
773 792
774 `PUBKEY` is that Mac's own `~/.ssh/id_ed25519.pub`. Every `make mac` clones 793 `PUBKEY` is that Mac's own `~/.ssh/id_ed25519.pub`. Nothing stops you
775 `mux-mac-base` to `mux-mac`, boots the clone, and stops it on the way out, 794 pointing `MAC_BOX` at some other spare Mac instead, as long as you mean the
776 so no run can see the run before it. 795 word scrubbed.
777 796
778 `make mac` and `make vm` each run one operating system on both ends of every 797 `make mac` and `make vm` each run one operating system on both ends of every
779 connection. `make xos` is the gate that does not: it attaches a macOS client 798 connection. `make xos` is the gate that does not: it attaches a macOS client
780 to a Linux daemon and a Linux client to a macOS daemon, over the ssh handoff 799 to a Linux daemon and a Linux client to a macOS daemon, over the ssh handoff
781 and over QUIC, and checks that a cross-architecture `mux d upgrade` is 800 and over QUIC, and checks that a cross-architecture `mux d upgrade` is
782 refused in both directions before any of the image is streamed. It needs two 801 refused in both directions before any of the image is streamed. It uses all
783 other machines — the Mac named by `MUX_MAC`, which builds and installs its 802 three names above, and pairs the two boxes' ssh keys itself, because each
784 own arm64 binary, and a Linux box named by `MUX_LAN` (default 803 one dials the other from the inside.
785 `ubuntu@192.168.0.37`) that the Mac can reach by plain ssh. The Linux box is
786 a throwaway and is reset to a mux-less state at the start of every run; the
787 Mac is somebody's machine, so the gate starts nothing there that it does not
788 stop and refuses to run at all if a mux is already going.
789 804
790 ## How it works 805 ## How it works
791 806
docs/decisions.md
Old New
@@ -8740,3 +8740,96 @@ e2e suite did before this branch and defines its own `now_ms` through
8740 be expected to find the same class of thing the e2e suite just did. 8740 be expected to find the same class of thing the e2e suite just did.
8741 8741
8742 Spec: `docs/superpowers/specs/2026-09-03-macos-port-design.md`. 8742 Spec: `docs/superpowers/specs/2026-09-03-macos-port-design.md`.
8743
8744 ## 2026-09-04 — the box gates take their boxes by name
8745
8746 `make mac` and `make xos` were written against one developer's machines and
8747 said so in their own source: a Mac called `squirtle`, a Linux VM at
8748 `ubuntu@192.168.0.37`, and a `MUX_MAC_IP` literal for the QUIC legs. Nobody
8749 else could run either gate. Worse, the two disagreed about what a gate may
8750 do to a machine it finds: `xos` scrubbed the Linux box to bare metal and
8751 tiptoed around the Mac — refusing to run at all if a mux was already going
8752 there, and carrying a `MAC_CFG_MADE` flag so it could put back a
8753 `~/.config/mux` the product had created — while `mac` cloned a fresh tart
8754 guest per run and reached it only through the host's NAT, over an
8755 ssh-through-ssh string in every command.
8756
8757 Both gates now take three ssh targets, and neither has a default for one:
8758 `MAC_BOX` (macOS, no toolchain, the box under test), `LINUX_BOX` (x86_64
8759 Linux, `xos` only) and `MAC_BUILDER` (a Mac with zig and the shadow SDK,
8760 which BUILDS and is neither installed onto nor scrubbed; it defaults to
8761 `MAC_BOX`). A missing name is a preflight refusal with rc 2 that names the
8762 variable and the line to run, before anything is written anywhere. There is
8763 no default because a gate that guesses a box eventually guesses somebody's
8764 laptop — which is the same reason the "it is a person's real machine"
8765 branches are gone rather than kept: MAC_BOX and LINUX_BOX are VMs the gate
8766 MAY scrub, stated once, and `box_scrub` is the whole of what that means.
8767
8768 The shared pieces live in `test/box_lib.sh`, sourced by both gates after
8769 `test/os_oracle.sh`: `box_preflight` (one `ssh -n` per box, `arm64`
8770 normalised to `aarch64` exactly as `main.archMatches` does for `mux d
8771 upgrade`), `box_pair` (idempotent key install, then a real dial from A to B
8772 so the pairing is proved and B's host key recorded where the plain ssh mux
8773 spawns will read it), `box_scrub`, and `box_ssh`/`box_scp`/`box_stream`,
8774 which replaced the three copies of the script-on-stdin runner the two files
8775 had between them.
8776
8777 The scrub runs at the START of a run and again at the END, so a failed run
8778 leaves nothing behind and the next one starts from the same place whatever
8779 happened. It writes NO shell rc file, which reverses what the old reset did.
8780 The old one inserted a `~/.local/bin` PATH line into the Linux box's
8781 `.bashrc`, and the first draft of this work added the Darwin mirror of it, a
8782 `~/.zshenv` line, on the reasoning that the entry dial's `~/.local/bin`
8783 lookup is part of what these gates test. That reasoning was wrong. Every
8784 remote spelling the product sends already carries the prefix itself —
8785 `handoff.local_bin_append`, `PATH="$PATH:$HOME/.local/bin"`, on the entry
8786 dial's word, on the upgrade preflight and on the upgrade's push — so a line
8787 in a box's shell rc tests nothing mux needs, and it is the fixture
8788 configuring the machine. A bare box that finds mux only because the gate
8789 edited its shell rc is exactly the configured developer machine the pristine
8790 guest exists to rule out. The gate's own remote scripts spell
8791 `$HOME/.local/bin/mux` in full instead, checked leg by leg, and `xos`'s
8792 install leg asserts the product's actual question: a plain
8793 `ssh BOX 'PATH="$PATH:$HOME/.local/bin"; command -v mux'` — that prefix
8794 character for character — answers `~/.local/bin/mux` on both boxes.
8795
8796 The one arrangement the scrub still makes is `loginctl enable-linger` on the
8797 Linux arm, because without it logind takes `/run/user/<uid>` down with the
8798 ssh session that started the daemon and reaps the detached daemon with it.
8799 It exits with its own code so a box without passwordless sudo is told that,
8800 rather than being handed "the reset was refused".
8801
8802 The Mac guest's lifecycle moved out of `test/mac.sh` and into
8803 `test/provision-mac.sh`, run from the Linux box: it clones the hand-made
8804 `mux-mac-base` on a tart host, boots the clone with `--net-bridged`, polls
8805 `tart ip --resolver=arp` (6 s when measured), installs this box's public key
8806 through the host's own, verifies a DIRECT ssh from here, and prints one line
8807 — `export MAC_BOX=admin@IP` — with everything else on stderr, so
8808 `eval "$(test/provision-mac.sh)"` works. Bridged and not NAT is what removes
8809 the host hop: the guest took `192.168.0.170` on the LAN, this box reaches it
8810 directly, and the Linux VM can see its port 22, which is what lets the two
8811 boxes dial each other for the cross-OS legs.
8812
8813 One consequence has to be handled rather than configured away. The guest's
8814 host key is new on every clone, so the harness's own ssh to `MAC_BOX` keeps
8815 none (`StrictHostKeyChecking=no`, `UserKnownHostsFile=/dev/null`) — but
8816 mux's OWN entry dial spawns a plain `ssh HOST` that reads the real
8817 `~/.ssh/known_hosts` and cannot be handed those options. `xos`'s preflight
8818 therefore does `ssh-keygen -R IP` and then one accept-new dial, so leg 7
8819 finds the key where the product looks for it; `box_pair` does the same
8820 forget-and-relearn on the far box, because a re-provisioned guest at the
8821 same address would otherwise be refused by a stale entry for the rest of
8822 that VM's life.
8823
8824 The binary reaches `MAC_BOX` as a stream — `ssh BUILDER cat` into
8825 `ssh MAC_BOX cat` — because neither Mac needs a key of the other's for that
8826 and only the orchestrating box can reach both. `cat` carries no mode, so
8827 `box_stream` chmods 755 and both gates compare the builder's sha256 against
8828 the far side's: the assertion that the bytes arrived is the hash, not the
8829 copy tool.
8830
8831 Leg counts: `xos` is still ten legs. `mac` is seven, not eight — the
8832 clone-and-boot leg left with the VM lifecycle, and what replaced it is the
8833 preflight line, which is not an `ok`. The user's real Mac is no longer a
8834 gate target at all; it can still be `MAC_BUILDER`, which is the one role
8835 that touches nothing.