256c96a0
docs: the Mac gate drives a pristine macOS guest, not the host
a73x 2026-09-03 18:46
Commit message
docs/superpowers/plans/2026-09-03-macos-port-step3-darwin-arm.md
| Old | New | ||
|---|---|---|---|
| @@ -637,29 +637,54 @@ ssh -o BatchMode=yes squirtle 'cd ~/code/rad/mux && export PATH=/opt/homebrew/bi | |||
| 637 | 637 | ||
| 638 | --- | 638 | --- |
| 639 | 639 | ||
| 640 | ### Task 7: `test/mac.sh` — the `make mac` journey | 640 | ### Task 7: `test/mac.sh` — the `make mac` journey, against a macOS GUEST on the Mac |
| 641 | |||
| 642 | **Ruling (user, 2026-09-03):** the gate drives a macOS virtual machine on | ||
| 643 | squirtle, not the host, so every run starts from a pristine image the way | ||
| 644 | `make vm` starts from a scorched-earth reset. The host builds (it holds the | ||
| 645 | toolchain and the shadow SDK); the guest only RUNS the built binaries, which | ||
| 646 | is the stronger claim — a clean macOS with no developer tooling. | ||
| 647 | |||
| 648 | **The VM is a human/host action, never harness code** (the `vm.sh` rule). | ||
| 649 | Tooling: Cirrus Labs `tart` (installed 2026-09-03 on squirtle, `brew trust | ||
| 650 | cirruslabs/cli && brew install cirruslabs/cli/tart`), base image | ||
| 651 | `ghcr.io/cirruslabs/macos-tahoe-base:latest` (macOS 26; pulled once, ~28 GB). | ||
| 652 | The pristine VM is made ONCE by hand and documented in `test/mac.sh`'s header: | ||
| 653 | |||
| 654 | ```sh | ||
| 655 | tart clone ghcr.io/cirruslabs/macos-tahoe-base:latest mux-mac-base | ||
| 656 | tart run --no-graphics mux-mac-base & # user admin, password admin | ||
| 657 | tart exec mux-mac-base sh -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo "PUBKEY" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' | ||
| 658 | tart stop mux-mac-base | ||
| 659 | ``` | ||
| 660 | |||
| 661 | (`PUBKEY` is the key squirtle's own `~/.ssh/id_*.pub` holds, so the host can | ||
| 662 | ssh into the guest without a password; `tart exec` needs the guest agent the | ||
| 663 | cirruslabs images ship.) `mux-mac-base` is never run again by the harness: | ||
| 664 | each run clones it. | ||
| 641 | 665 | ||
| 642 | **Files:** | 666 | **Files:** |
| 643 | - Create: `test/mac.sh` | 667 | - Create: `test/mac.sh` |
| 644 | - Modify: `Makefile` (`mac` target from Task 1 already points here) | 668 | - Modify: `Makefile` (`mac` target from Task 1 already points here), `README.md` (one paragraph under the macOS section: the base-VM recipe above) |
| 645 | 669 | ||
| 646 | **Interfaces:** | 670 | **Interfaces:** |
| 647 | - Consumes: `MUX_MAC` (default `squirtle`), the repo at `~/code/rad/mux` on the box, `deps/mac-sdk/bin` on PATH there. | 671 | - Consumes: `MUX_MAC` (default `squirtle`), the repo at `~/code/rad/mux` on the host, `deps/mac-sdk/bin` on the host PATH, `tart` on the host, the pristine `mux-mac-base` VM, `test/os_oracle.sh` (its Darwin arm runs INSIDE the guest). |
| 648 | 672 | ||
| 649 | - [ ] **Step 1: The script**, shaped like `test/vm.sh` (same header contract: real box, every claim asserted by a separate ssh through the Darwin oracle, `set -eu`, BatchMode). Legs, each a named step with its own failure line: | 673 | - [ ] **Step 1: The script**, shaped like `test/vm.sh` (same header contract; `set -eu`; BatchMode; every claim asserted by a separate ssh reading the guest through the oracle). All ssh to the guest goes THROUGH the host: `ssh squirtle "ssh -o BatchMode=yes -o StrictHostKeyChecking=no admin@$GIP '…'"`, one helper `gssh` that quotes once. Legs, each a named step with its own failure line: |
| 650 | 674 | ||
| 651 | 1. `push` — `git push "$MAC:code/rad/mux" HEAD:refs/heads/mac-journey` then remote `git checkout -q mac-journey`. | 675 | 1. `push` — `git push "$MAC:code/rad/mux" HEAD:refs/heads/mac-journey`, remote `git checkout -q mac-journey && git reset -q --hard mac-journey`. |
| 652 | 2. `build` — remote `make build` and `make check`, rc captured. | 676 | 2. `build` — on the host: `make build` and `make check` (rc captured); `deps/zig/zig build test` (rc captured) — the unit suite needs the toolchain and so runs on the host. |
| 653 | 3. `unit` — remote `deps/zig/zig build test`, rc captured. | 677 | 3. `vm` — on the host: `tart stop mux-mac 2>/dev/null; tart delete mux-mac 2>/dev/null; tart clone mux-mac-base mux-mac; nohup tart run --no-graphics mux-mac >/tmp/mux-mac.log 2>&1 &`; poll `tart ip mux-mac` up to 120 s; poll `gssh true` up to 120 s. Refuse with a clear line if `mux-mac-base` does not exist (`tart list`), pointing at the header recipe. |
| 654 | 4. `daemon` — remote, under `XDG_RUNTIME_DIR=$(mktemp -d /tmp/muxj.XXXXXX)` exported for every later leg: `zig-out/bin/mux d start -d`; assert via `pid_holds_unix_sock DPID $XDG_RUNTIME_DIR/muxd.sock` from the oracle, and `pid_exe DPID` equals `real_path zig-out/bin/mux`. | 678 | 4. `install` — `scp` (through the host) `zig-out/bin/mux`, `zig-out/bin/ptyclient` and `test/os_oracle.sh` into the guest's `~/mux-e2e/`; `gssh '~/mux-e2e/mux --version'` prints the version; assert `file_mode` of the binary is `755` via the oracle sourced in the guest. |
| 655 | 5. `session` — `zig-out/bin/mux a run --sock … 'echo mac-$$'`-style (use whichever `mux a` verb the README documents for a one-shot command) and read the pid back off the grid; assert the pid is a child of the daemon via `pid_children`. | 679 | 5. `daemon` — in the guest, with `XDG_RUNTIME_DIR` UNSET (this is the one place the Darwin fallback `/tmp/mux-<uid>` is exercised end to end; the Task 2 ⚠️ item): `~/mux-e2e/mux d start -d`; then assert via the oracle IN the guest: `pid_holds_unix_sock DPID /tmp/mux-501/muxd.sock` (uid 501 is `admin`; read it with `id -u`), `pid_exe DPID` equals `real_path ~/mux-e2e/mux`, and `file_mode /tmp/mux-$(id -u)` is `700`. |
| 656 | 6. `attach` — `zig-out/bin/ptyclient` drives `mux --sock …` on a real pty, types `printf JOURNEY-%s\\n ok`, waits for `JOURNEY-ok` in the snapshot (the e2e lib has the wait; mirror its shape), detaches with the chord. | 680 | 6. `session` — `~/mux-e2e/mux a …` one-shot (the `mux a` verb README documents) runs `echo mac-$$`; read the pid off the reply; assert it is a child of the daemon via `pid_children`. |
| 657 | 7. `stop` — `mux d stop`; assert `pid_alive DPID` false within 3 s, socket path gone. | 681 | 7. `attach` — `~/mux-e2e/ptyclient` drives `~/mux-e2e/mux` on a real pty in the guest, types `printf JOURNEY-%s\\n ok`, waits for `JOURNEY-ok` in the snapshot (mirror the e2e lib's wait shape), detaches with the chord. |
| 658 | 8. Cleanup trap: `mux d stop` on the rig socket if still alive; `rm -rf` the rig dir. | 682 | 8. `stop` — `mux d stop`; `pid_alive DPID` false within 3 s; the socket path gone. |
| 683 | 9. Cleanup trap: `tart stop mux-mac` on the host, always; the clone is left for inspection and replaced by the next run's clone. | ||
| 659 | 684 | ||
| 660 | - [ ] **Step 2: Run it** — `make mac` from this box; every leg prints its name and `ok`; exit 0. | 685 | - [ ] **Step 2: Run it** — `make mac` from this box; every leg prints its name and `ok`; exit 0. Record the wall-clock of the `vm` leg (clone + boot) in the report. |
| 661 | 686 | ||
| 662 | - [ ] **Step 3: Commit** — `test: make mac drives the Mac over ssh the way make vm drives the VM` | 687 | - [ ] **Step 3: Commit** — `test: make mac drives a pristine macOS guest on the Mac the way make vm drives the VM` |
| 663 | 688 | ||
| 664 | --- | 689 | --- |
| 665 | 690 | ||