a73x

2e5ce7ef

test: make mac takes MAC_BOX and MAC_BUILDER by name

a73x   2026-09-04 12:50

Commit message
test: make mac takes MAC_BOX and MAC_BUILDER by name

The gate no longer knows a machine's name. MAC_BUILDER builds and is
touched no other way; MAC_BOX is the bare macOS the binary is streamed
onto and the journeys run against, reached directly from here now that
provision-mac.sh bridges the guest onto the LAN. Neither name has a
default and the preflight refuses on a missing one.

Hermeticity used to come from a per-run clone; it comes from box_scrub
now, at the start of the run and again on the way out, and the daemon
leg still asserts it rather than assuming it. The binary arrives as a
stream between two Macs that hold no key of each other's, so `cat`
carries no mode and the leg compares the builder's sha256 against the
far side's: what proves the bytes is the hash, not the copy tool.

Seven legs, not eight — the clone-and-boot leg left with the lifecycle,
and the preflight line that replaced it is not an `ok`.

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

test/mac.sh
Old New
@@ -5,67 +5,57 @@
5 # suite and the e2e suite grade the code; this grades the PRODUCT on the 5 # suite and the e2e suite grade the code; this grades the PRODUCT on the
6 # operating system it was ported to, spelled exactly as a user spells it. 6 # operating system it was ported to, spelled exactly as a user spells it.
7 # 7 #
8 # Two machines, and the split is the point. The HOST (a Mac, $MUX_MAC) 8 # Two boxes, taken by name, and the split is the point (test/box_lib.sh):
9 # holds the toolchain and the shadow SDK, so it builds. The GUEST is a
10 # macOS virtual machine cloned fresh for every run, with no developer
11 # tooling on it at all, and it only RUNS what the host built — which is the
12 # stronger claim: a bare Mac, not the machine that compiled the binary.
13 # 9 #
14 # The pristine guest is a human action, never harness code (test/vm.sh's 10 # $MAC_BUILDER a Mac with the toolchain and the shadow SDK on it. It
15 # rule). Tooling is Cirrus Labs `tart`; the base image is 11 # BUILDS, and the unit suite runs there for the same reason:
16 # ghcr.io/cirruslabs/macos-tahoe-base (macOS 26). The base VM is made ONCE, 12 # both need zig, the SDK and the source tree.
17 # by hand, on the host: 13 # $MAC_BOX the macOS box under test, with no developer tooling on it
14 # at all. It only RUNS what the builder built, which is the
15 # stronger claim: a bare Mac and not the machine that
16 # compiled the binary. Normally a pristine tart guest from
17 # test/provision-mac.sh, which this gate does not create,
18 # boot or destroy — a gate that owned a VM's lifecycle could
19 # not be pointed at a Mac somebody already had.
18 # 20 #
19 # brew trust cirruslabs/cli && brew install cirruslabs/cli/tart 21 # MAC_BOX is reached DIRECTLY from here: provision-mac.sh bridges the guest
20 # tart clone ghcr.io/cirruslabs/macos-tahoe-base:latest mux-mac-base 22 # onto the LAN, so there is no ssh-through-ssh string in this file any more.
21 # tart run --no-graphics mux-mac-base & # user admin, password admin 23 # It is a box the gate MAY scrub, and box_scrub is the whole of what that
22 # tart exec mux-mac-base sh -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo "PUBKEY" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' 24 # means; the scrub runs at the start and again on the way out, so a failed
23 # tart stop mux-mac-base 25 # run leaves nothing behind and the next one starts from the same place
26 # whatever happened. Hermeticity used to come from a per-run clone; it comes
27 # from the scrub now, and the daemon leg still asserts it rather than
28 # assuming it.
24 # 29 #
25 # PUBKEY is the host's own ~/.ssh/id_ed25519.pub, so the host can ssh into 30 # Every claim about MAC_BOX is asserted by a separate ssh reading it through
26 # the guest without a password; `tart exec` needs the guest agent the 31 # test/os_oracle.sh, never by trusting the daemon's own report over the
27 # cirruslabs images ship. `mux-mac-base` is never RUN by this script: every 32 # connection under test ("ask the OS about the OS").
28 # run clones it to `mux-mac`, boots the clone, and stops the clone on the
29 # way out. Hermeticity comes from the clone, which is why the daemon leg
30 # can assert the guest holds no mux and no runtime directory before it
31 # starts one — a second run must not be able to see the first run's.
32 #
33 # Every claim about the guest is asserted by a separate ssh reading the
34 # guest through test/os_oracle.sh, never by trusting the daemon's own
35 # report over the connection under test ("ask the OS about the OS").
36 set -eu 33 set -eu
37 34
38 MAC="${MUX_MAC:-squirtle}" 35 BOX_GATE=mac
39 # Relative to the HOST's $HOME, because that is how ssh's scp-like remote
40 # spelling (`squirtle:code/rad/mux`) resolves a path.
41 REPO="${MUX_MAC_REPO:-code/rad/mux}"
42 BRANCH=mac-journey
43 VM_BASE=mux-mac-base
44 VM_RUN=mux-mac
45 GUSER="admin"
46 # A LITERAL $HOME: this string is pasted into a command line the HOST's
47 # shell parses, so the expansion has to happen there and not here.
48 GKEY='$HOME/.ssh/id_ed25519'
49 36
50 # BatchMode so a broken auth fails in seconds instead of prompting a 37 MAC_BOX=${MAC_BOX:-}
51 # harness nobody sits in front of. The guest key is accept-anything because 38 # The one Mac that is both is the common case, so the builder follows the
52 # a freshly cloned VM has a host key nobody has ever seen and takes a new 39 # box under test unless it is named. It is never scrubbed and never has a
53 # one on the next run; the guest is reachable only on the host's own 40 # mux installed on it: it builds, and that is all.
54 # virtual network, and the host hop above it is BatchMode-authenticated. 41 MAC_BUILDER=${MAC_BUILDER:-$MAC_BOX}
55 HOST_SSH="-o BatchMode=yes -o ConnectTimeout=10" 42 # Relative to the BUILDER's $HOME, because that is how ssh's scp-like remote
56 # LogLevel=ERROR because a throwaway known-hosts file makes ssh announce the 43 # spelling (`HOST:code/rad/mux`) resolves a path.
57 # new key on stderr every single time, and that line would be most of what a 44 REPO="${MAC_BUILDER_REPO:-code/rad/mux}"
58 # green run prints. 45 BRANCH=mac-journey
59 GUEST_SSH="-o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=10 -i $GKEY"
60 46
61 # now_ms, and the file_mode/pid_* names the guest legs source for 47 # now_ms, and the file_mode/pid_* names the MAC_BOX legs source for
62 # themselves. The harness spells time and paths through the oracle so this 48 # themselves. The harness spells time and paths through the oracle so this
63 # script would still answer if it were ever run FROM a Mac. 49 # script would still answer if it were ever run FROM a Mac.
64 . "$(dirname "$0")/os_oracle.sh" 50 . "$(dirname "$0")/os_oracle.sh"
51 . "$(dirname "$0")/box_lib.sh"
52
53 # A provisioned guest has a new host key every clone, so the harness's own
54 # ssh to it records none. This gate never dials MAC_BOX with mux, so nothing
55 # here needs that key in the real known_hosts.
56 BOX_EPHEMERAL=$MAC_BOX
65 57
66 GIP="" # the guest's address, set by the vm leg
67 OK_COUNT=0 58 OK_COUNT=0
68 PUSHED=no
69 59
70 ok() { 60 ok() {
71 OK_COUNT=$((OK_COUNT + 1)) 61 OK_COUNT=$((OK_COUNT + 1))
@@ -77,67 +67,46 @@ fail() {
77 exit 1 67 exit 1
78 } 68 }
79 69
80 # hssh SECONDS — run the script on stdin on the HOST, under /bin/sh. 70 # What every command on MAC_BOX runs under. XDG_STATE_HOME is isolated
81 # 71 # because every mux that attaches records its daemon in it (CLAUDE.md), and
82 # /bin/sh and not the host's login shell (zsh): `$VAR` holding several 72 # XDG_RUNTIME_DIR is UNSET on purpose: a fresh ssh login to a Mac has no such
83 # words does not word-split in zsh, so an options string pasted into a 73 # variable, and the Darwin fallback /tmp/mux-<uid> is exactly what the daemon
84 # command line there arrives as ONE argument and ssh answers "keyword 74 # leg is here to exercise end to end.
85 # batchmode extra arguments at end of line".
86 # 75 #
87 # The rule for a script handed to this: any ssh INSIDE it takes -n. The 76 # The builder gets the same prelude, where its only effect is a scratch
88 # host's sh is reading its own script off stdin, and an ssh that does not 77 # directory the cleanup removes: the build legs cd into the repo themselves,
89 # close its stdin swallows the rest of that script — silently, with a 0 78 # and no mux ever runs there.
90 # exit, so the lines after it simply never run. 79 PRELUDE='
91 hssh() {
92 _ht=$1
93 # shellcheck disable=SC2086 # HOST_SSH is words on purpose
94 timeout "$_ht" ssh $HOST_SSH "$MAC" /bin/sh -s
95 }
96
97 # What every guest command runs under. XDG_STATE_HOME is isolated because
98 # every mux that attaches records its daemon in it (CLAUDE.md), and
99 # XDG_RUNTIME_DIR is UNSET on purpose: a fresh ssh login to a Mac has no
100 # such variable, and the Darwin fallback /tmp/mux-<uid> is exactly what the
101 # daemon leg is here to exercise end to end.
102 GUEST_PRELUDE='
103 export XDG_STATE_HOME=$HOME/mux-e2e/state 80 export XDG_STATE_HOME=$HOME/mux-e2e/state
104 unset XDG_RUNTIME_DIR 81 unset XDG_RUNTIME_DIR
105 mkdir -p $HOME/mux-e2e 82 mkdir -p $HOME/mux-e2e
106 cd $HOME/mux-e2e 83 cd $HOME/mux-e2e
107 ' 84 '
108 85
109 # gssh SECONDS — run the script on stdin in the GUEST, through the host. 86 # ---- preflight: every box answers, before anything is written ----------
110 # 87 box_preflight MAC_BOX "$MAC_BOX" aarch64 \
111 # The quoting rule is that there is NO quoting. The script rides this 88 'Provision one with: eval "$(test/provision-mac.sh)"'
112 # shell's stdin into the host's ssh, which forwards it to the guest's 89 box_preflight MAC_BUILDER "$MAC_BUILDER" aarch64 \
113 # `sh -s`; no shell between here and there parses it as a command line, so 90 'A Mac holding this repo with zig and deps/mac-sdk. Defaults to $MAC_BOX.'
114 # a leg writes ordinary sh with whatever quotes it likes and nothing is
115 # escaped anywhere. The one thing a caller owes: always give this a
116 # heredoc. Called with no redirect it reads the enclosing script's stdin.
117 gssh() {
118 _gt=$1
119 [ -n "$GIP" ] || fail vm "gssh before the guest had an address"
120 # shellcheck disable=SC2086 # HOST_SSH is words on purpose
121 { printf '%s\n' "$GUEST_PRELUDE"; cat; } |
122 timeout "$_gt" ssh $HOST_SSH "$MAC" "ssh $GUEST_SSH $GUSER@$GIP sh -s"
123 }
124 91
125 # The clone is stopped on every exit, including a failing one. It is left 92 # The start scrub, and the one on the way out. The builder is not scrubbed —
126 # in place rather than deleted so a failure can be logged into, and the 93 # it is not the gate's machine to reset — so its scratch directory is all
127 # next run replaces it. 94 # there is to take back.
95 box_scrub "$MAC_BOX"
128 cleanup() { 96 cleanup() {
129 _rc=$? 97 _rc=$?
130 hssh 180 >/dev/null 2>&1 <<H || true 98 box_scrub "$MAC_BOX" >/dev/null 2>&1 || true
131 export PATH=/opt/homebrew/bin:\$PATH 99 box_ssh 60 "$MAC_BUILDER" >/dev/null 2>&1 <<'B' || true
132 tart stop $VM_RUN 2>/dev/null || true 100 rm -rf "$HOME/mux-e2e"
133 H 101 B
134 exit "$_rc" 102 exit "$_rc"
135 } 103 }
136 trap cleanup EXIT 104 trap cleanup EXIT
137 105
138 # ---- leg 1: push — the host builds THIS commit ------------------------ 106 # ---- leg 1: push — the builder builds THIS commit ----------------------
139 HEAD_SHA=$(git rev-parse HEAD) 107 HEAD_SHA=$(git rev-parse HEAD)
140 HAVE=$(hssh 60 <<H || true 108 PUSHED=no
109 HAVE=$(box_ssh 60 "$MAC_BUILDER" <<H || true
141 cd \$HOME/$REPO 2>/dev/null || exit 0 110 cd \$HOME/$REPO 2>/dev/null || exit 0
142 git rev-parse -q --verify refs/heads/$BRANCH 2>/dev/null || true 111 git rev-parse -q --verify refs/heads/$BRANCH 2>/dev/null || true
143 H 112 H
@@ -146,7 +115,7 @@ if [ "$HAVE" != "$HEAD_SHA" ]; then
146 # A push cannot update the branch the remote has checked out, and the 115 # A push cannot update the branch the remote has checked out, and the
147 # run before this one left it checked out. Detaching first is what 116 # run before this one left it checked out. Detaching first is what
148 # makes a re-run work; it changes no commit and no file. 117 # makes a re-run work; it changes no commit and no file.
149 hssh 60 >/dev/null <<H || fail push "cannot detach $MAC:$REPO before the push" 118 box_ssh 60 "$MAC_BUILDER" >/dev/null <<H || fail push "cannot detach $MAC_BUILDER:$REPO before the push"
150 set -e 119 set -e
151 cd \$HOME/$REPO 120 cd \$HOME/$REPO
152 if [ "\$(git rev-parse --abbrev-ref HEAD)" = "$BRANCH" ]; then 121 if [ "\$(git rev-parse --abbrev-ref HEAD)" = "$BRANCH" ]; then
@@ -154,34 +123,34 @@ if [ "\$(git rev-parse --abbrev-ref HEAD)" = "$BRANCH" ]; then
154 fi 123 fi
155 H 124 H
156 # --force because this branch is a scratch mirror of whatever this tree 125 # --force because this branch is a scratch mirror of whatever this tree
157 # is and never shared history: a local amend or rebase leaves the host's 126 # is and never shared history: a local amend or rebase leaves the
158 # ref on a commit the new HEAD does not descend from, and a plain push 127 # builder's ref on a commit the new HEAD does not descend from, and a
159 # would be rejected as a non-fast-forward for the rest of the branch's 128 # plain push would be rejected as a non-fast-forward for the rest of the
160 # life. 129 # branch's life.
161 git push --force "$MAC:$REPO" "HEAD:refs/heads/$BRANCH" >/dev/null 2>&1 || 130 git push --force "$MAC_BUILDER:$REPO" "HEAD:refs/heads/$BRANCH" >/dev/null 2>&1 ||
162 fail push "git push $MAC:$REPO HEAD:refs/heads/$BRANCH was refused" 131 fail push "git push $MAC_BUILDER:$REPO HEAD:refs/heads/$BRANCH was refused"
163 PUSHED=yes 132 PUSHED=yes
164 fi 133 fi
165 hssh 60 >/dev/null <<H || fail push "the host could not check out $BRANCH" 134 box_ssh 60 "$MAC_BUILDER" >/dev/null <<H || fail push "the builder could not check out $BRANCH"
166 set -e 135 set -e
167 cd \$HOME/$REPO 136 cd \$HOME/$REPO
168 git checkout -q $BRANCH 137 git checkout -q $BRANCH
169 git reset -q --hard $BRANCH 138 git reset -q --hard $BRANCH
170 H 139 H
171 HOST_SHA=$(hssh 60 <<H 140 BUILDER_SHA=$(box_ssh 60 "$MAC_BUILDER" <<H
172 cd \$HOME/$REPO && git rev-parse HEAD 141 cd \$HOME/$REPO && git rev-parse HEAD
173 H 142 H
174 ) 143 )
175 [ "$HOST_SHA" = "$HEAD_SHA" ] || 144 [ "$BUILDER_SHA" = "$HEAD_SHA" ] ||
176 fail push "the host is at $HOST_SHA, this tree is at $HEAD_SHA" 145 fail push "the builder is at $BUILDER_SHA, this tree is at $HEAD_SHA"
177 ok push "$MAC:$REPO on $BRANCH at $(git rev-parse --short HEAD), pushed=$PUSHED" 146 ok push "$MAC_BUILDER:$REPO on $BRANCH at $(git rev-parse --short HEAD), pushed=$PUSHED"
178 147
179 # ---- leg 2: build — the toolchain lives on the host -------------------- 148 # ---- leg 2: build — the toolchain lives on the builder -----------------
180 # The unit suite runs HERE and not in the guest for the same reason: it 149 # The unit suite runs HERE and not on MAC_BOX for the same reason the build
181 # needs zig, the shadow SDK and the source tree, and the guest has none of 150 # does: it needs zig, the shadow SDK and the source tree, and the box under
182 # them. `make test` rather than a bare `deps/zig/zig build test` so the 151 # test has none of them. `make test` rather than a bare `deps/zig/zig build
183 # mac-sdk shim is on PATH the way every other target gets it. 152 # test` so the mac-sdk shim is on PATH the way every other target gets it.
184 BOUT=$(hssh 2400 <<H 153 BOUT=$(box_ssh 2400 "$MAC_BUILDER" <<H
185 cd \$HOME/$REPO || { echo "RC_BUILD=90"; exit 0; } 154 cd \$HOME/$REPO || { echo "RC_BUILD=90"; exit 0; }
186 export PATH=\$HOME/$REPO/deps/mac-sdk/bin:/opt/homebrew/bin:\$PATH 155 export PATH=\$HOME/$REPO/deps/mac-sdk/bin:/opt/homebrew/bin:\$PATH
187 make build >/tmp/mac-build.log 2>&1; echo "RC_BUILD=\$?" 156 make build >/tmp/mac-build.log 2>&1; echo "RC_BUILD=\$?"
@@ -196,89 +165,62 @@ for _step in BUILD CHECK TEST; do
196 # The verdict first and the reading under it, test/vm.sh's shape: a 165 # The verdict first and the reading under it, test/vm.sh's shape: a
197 # reader scrolling back finds the sentence that says what broke, not 166 # reader scrolling back finds the sentence that says what broke, not
198 # the tail of somebody else's stack trace. 167 # the tail of somebody else's stack trace.
199 echo "mac FAIL: build: make $_log on $MAC exited ${_rc:-<no answer>}" 168 echo "mac FAIL: build: make $_log on $MAC_BUILDER exited ${_rc:-<no answer>}"
200 hssh 60 <<H | sed 's/^/ /' || true 169 box_ssh 60 "$MAC_BUILDER" <<H | sed 's/^/ /' || true
201 tail -30 /tmp/mac-$_log.log 2>/dev/null || true 170 tail -30 /tmp/mac-$_log.log 2>/dev/null || true
202 H 171 H
203 exit 1 172 exit 1
204 done 173 done
205 ok build "make build, check and test on $MAC" 174 ok build "make build, check and test on $MAC_BUILDER"
206 175
207 # ---- leg 3: vm — a pristine guest, cloned and booted ------------------- 176 # ---- leg 3: install — the builder's build, onto a bare Mac -------------
208 VM_T0=$(now_ms) 177 # Streamed through this box rather than copied between the two, because
209 hssh 600 >/dev/null <<H || fail vm "could not clone and boot $VM_RUN on $MAC" 178 # neither Mac needs a key of the other's for that and only this one can
210 export PATH=/opt/homebrew/bin:\$PATH 179 # reach both. `cat` carries no mode, so box_stream chmods and the two hashes
211 tart list 2>/dev/null | awk '{print \$2}' | grep -qx $VM_BASE || { 180 # are compared below: the bytes that run on MAC_BOX are the bytes the
212 echo "mac FAIL: vm: $MAC has no VM called $VM_BASE." >&2 181 # builder produced, asserted rather than assumed.
213 echo " Make it once by hand; the recipe is this script's header." >&2 182 box_ssh 60 "$MAC_BOX" >/dev/null <<'G' || fail install "could not make the scratch directory on $MAC_BOX"
214 exit 1; } 183 set -e
215 tart stop $VM_RUN >/dev/null 2>&1 || true 184 mkdir -p "$HOME/mux-e2e"
216 tart delete $VM_RUN >/dev/null 2>&1 || true
217 tart clone $VM_BASE $VM_RUN || exit 1
218 nohup tart run --no-graphics $VM_RUN >/tmp/$VM_RUN.log 2>&1 &
219 sleep 1
220 exit 0
221 H
222 # Polled on the HOST rather than one ssh per tick: a tick costs a whole
223 # connection setup from here and the address usually arrives in seconds.
224 GIP=$(hssh 240 <<H
225 export PATH=/opt/homebrew/bin:\$PATH
226 _i=0
227 while [ \$_i -lt 60 ]; do
228 _ip=\$(tart ip $VM_RUN 2>/dev/null) && [ -n "\$_ip" ] && { echo "\$_ip"; exit 0; }
229 sleep 2; _i=\$((_i + 1))
230 done
231 exit 1
232 H
233 ) || fail vm "$VM_RUN took no address within 120 s (host log: /tmp/$VM_RUN.log)"
234
235 _i=0
236 while :; do
237 # An `if` and not `gssh ... && break`: under `set -e` an AND-OR list
238 # that ends in a failing test is a failing command, so the first tick
239 # against a guest still booting would end the run.
240 if gssh 25 >/dev/null 2>&1 <<'G'
241 true
242 G 185 G
243 then break; fi 186 for _f in mux ptyclient; do
244 _i=$((_i + 1)) 187 box_stream "$MAC_BUILDER" "\$HOME/$REPO/zig-out/bin/$_f" "$MAC_BOX" "\$HOME/mux-e2e/$_f" ||
245 [ "$_i" -lt 40 ] || fail vm "$VM_RUN at $GIP never answered ssh within 120 s" 188 fail install "could not stream $_f from $MAC_BUILDER to $MAC_BOX"
246 sleep 3
247 done 189 done
248 VM_MS=$(( $(now_ms) - VM_T0 )) 190 box_scp "$MAC_BOX" "$(dirname "$0")/os_oracle.sh" mux-e2e/os_oracle.sh ||
249 ok vm "$VM_RUN cloned, booted and answering at $GIP in $((VM_MS / 1000))s" 191 fail install "could not copy the oracle to $MAC_BOX"
250 192 WANT=$(box_ssh 60 "$MAC_BUILDER" <<H
251 # ---- leg 4: install — the host's build, onto a bare Mac ----------------
252 hssh 300 >/dev/null <<H || fail install "could not copy the build into $VM_RUN"
253 set -e
254 cd \$HOME/$REPO 193 cd \$HOME/$REPO
255 ssh -n $GUEST_SSH $GUSER@$GIP "mkdir -p ~/mux-e2e" 194 . ./test/os_oracle.sh
256 scp -q $GUEST_SSH zig-out/bin/mux zig-out/bin/ptyclient test/os_oracle.sh $GUSER@$GIP:mux-e2e/ 195 echo "ver=\$(./zig-out/bin/mux --version)"
257 H 196 echo "sha=\$(sha256_of ./zig-out/bin/mux)"
258 WANT_VER=$(hssh 60 <<H
259 cd \$HOME/$REPO && ./zig-out/bin/mux --version
260 H 197 H
261 ) 198 )
262 GOT_VER=$(gssh 60 <<'G' 199 WANT_VER=$(printf '%s\n' "$WANT" | sed -n 's/^ver=//p')
263 ./mux --version 200 WANT_SHA=$(printf '%s\n' "$WANT" | sed -n 's/^sha=//p')
264 G 201 GOT=$(box_ssh 60 "$MAC_BOX" <<'G'
265 )
266 [ -n "$WANT_VER" ] && [ "$WANT_VER" = "$GOT_VER" ] ||
267 fail install "the guest answers '$GOT_VER', the host built '$WANT_VER'"
268 BMODE=$(gssh 60 <<'G'
269 . ./os_oracle.sh 202 . ./os_oracle.sh
270 file_mode ./mux 203 echo "ver=$(./mux --version)"
204 echo "sha=$(sha256_of ./mux)"
205 echo "mode=$(file_mode ./mux)"
271 G 206 G
272 ) 207 )
273 [ "$BMODE" = "755" ] || 208 GOT_VER=$(printf '%s\n' "$GOT" | sed -n 's/^ver=//p')
274 fail install "the copied binary is mode $BMODE, want 755" 209 GOT_SHA=$(printf '%s\n' "$GOT" | sed -n 's/^sha=//p')
275 ok install "a bare macOS runs the host's build and answers '$GOT_VER'" 210 GOT_MODE=$(printf '%s\n' "$GOT" | sed -n 's/^mode=//p')
211 [ -n "$WANT_VER" ] && [ "$WANT_VER" = "$GOT_VER" ] ||
212 fail install "$MAC_BOX answers '$GOT_VER', $MAC_BUILDER built '$WANT_VER'"
213 [ -n "$WANT_SHA" ] && [ "$WANT_SHA" = "$GOT_SHA" ] ||
214 fail install "$MAC_BOX holds a mux hashing $GOT_SHA, the builder's is $WANT_SHA"
215 [ "$GOT_MODE" = "755" ] ||
216 fail install "the streamed binary is mode $GOT_MODE, want 755"
217 ok install "a bare macOS runs the builder's own bytes and answers '$GOT_VER'"
276 218
277 # ---- leg 5: daemon — no XDG_RUNTIME_DIR, so /tmp/mux-<uid> ------------ 219 # ---- leg 4: daemon — no XDG_RUNTIME_DIR, so /tmp/mux-<uid> ------------
278 # The pristine claim, asserted rather than assumed: this is what the 220 # The scrubbed claim, asserted rather than assumed: this is what the scrub
279 # per-run clone buys, and a clone that silently carried a previous run's 221 # buys, and a box that silently carried a previous run's daemon would make
280 # daemon would make every leg below it a check on somebody else's state. 222 # every leg below it a check on somebody else's state.
281 PRE=$(gssh 60 <<'G' 223 PRE=$(box_ssh 60 "$MAC_BOX" <<'G'
282 echo "uid=$(id -u)" 224 echo "uid=$(id -u)"
283 echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')" 225 echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')"
284 [ -e /tmp/mux-$(id -u) ] && echo "rt=present" || echo "rt=absent" 226 [ -e /tmp/mux-$(id -u) ] && echo "rt=present" || echo "rt=absent"
@@ -286,18 +228,18 @@ G
286 ) 228 )
287 GUID=$(printf '%s\n' "$PRE" | sed -n 's/^uid=//p') 229 GUID=$(printf '%s\n' "$PRE" | sed -n 's/^uid=//p')
288 printf '%s\n' "$PRE" | grep -qx 'muxes=0' || 230 printf '%s\n' "$PRE" | grep -qx 'muxes=0' ||
289 fail daemon "the freshly cloned guest already runs a mux: $PRE" 231 fail daemon "$MAC_BOX still runs a mux after the scrub: $PRE"
290 printf '%s\n' "$PRE" | grep -qx 'rt=absent' || 232 printf '%s\n' "$PRE" | grep -qx 'rt=absent' ||
291 fail daemon "the freshly cloned guest already has /tmp/mux-$GUID: $PRE" 233 fail daemon "$MAC_BOX still has /tmp/mux-$GUID after the scrub: $PRE"
292 [ -n "$GUID" ] || fail daemon "the guest would not say its uid" 234 [ -n "$GUID" ] || fail daemon "$MAC_BOX would not say its uid"
293 235
294 # MUX_SHELL_INTEGRATION=1 because the session leg below reads a real exit 236 # MUX_SHELL_INTEGRATION=1 because the session leg below reads a real exit
295 # code, and marks are opt-in (README). It also puts the daemon's shellint 237 # code, and marks are opt-in (README). It also puts the daemon's shellint
296 # shim directory inside the runtime directory this leg is about. 238 # shim directory inside the runtime directory this leg is about.
297 gssh 120 >/dev/null <<'G' || fail daemon "mux d start -d refused on the guest" 239 box_ssh 120 "$MAC_BOX" >/dev/null <<'G' || fail daemon "mux d start -d refused on $MAC_BOX"
298 MUX_SHELL_INTEGRATION=1 ./mux d start -d 240 MUX_SHELL_INTEGRATION=1 ./mux d start -d
299 G 241 G
300 DINFO=$(gssh 60 <<'G' 242 DINFO=$(box_ssh 60 "$MAC_BOX" <<'G'
301 echo "n=$(pgrep -x mux | wc -l | tr -d ' ')" 243 echo "n=$(pgrep -x mux | wc -l | tr -d ' ')"
302 echo "pid=$(pgrep -x mux | head -1)" 244 echo "pid=$(pgrep -x mux | head -1)"
303 pgrep -lx mux 2>/dev/null | sed 's/^/ps=/' 245 pgrep -lx mux 2>/dev/null | sed 's/^/ps=/'
@@ -312,12 +254,12 @@ DPID=$(printf '%s\n' "$DINFO" | sed -n 's/^pid=//p')
312 # does not hold the socket". A wrong count also covers "no daemon at all", 254 # does not hold the socket". A wrong count also covers "no daemon at all",
313 # so there is no separate emptiness check. 255 # so there is no separate emptiness check.
314 [ "$DN" = "1" ] || { 256 [ "$DN" = "1" ] || {
315 echo "mac FAIL: daemon: the guest runs ${DN:-<no answer>} mux processes after mux d start -d, want 1:" 257 echo "mac FAIL: daemon: $MAC_BOX runs ${DN:-<no answer>} mux processes after mux d start -d, want 1:"
316 printf '%s\n' "$DINFO" | sed -n 's/^ps=/ /p' 258 printf '%s\n' "$DINFO" | sed -n 's/^ps=/ /p'
317 exit 1; } 259 exit 1; }
318 260
319 SOCK="/tmp/mux-$GUID/muxd.sock" 261 SOCK="/tmp/mux-$GUID/muxd.sock"
320 gssh 60 >/dev/null <<G || fail daemon "daemon $DPID does not hold $SOCK" 262 box_ssh 60 "$MAC_BOX" >/dev/null <<G || fail daemon "daemon $DPID does not hold $SOCK"
321 . ./os_oracle.sh 263 . ./os_oracle.sh
322 pid_holds_unix_sock $DPID $SOCK 264 pid_holds_unix_sock $DPID $SOCK
323 G 265 G
@@ -325,7 +267,7 @@ G
325 # lsof reports, which resolves every symlink on the way, so a home directory 267 # lsof reports, which resolves every symlink on the way, so a home directory
326 # or a $TMPDIR reached through one would fail a correct exec unless the 268 # or a $TMPDIR reached through one would fail a correct exec unless the
327 # expected path is resolved the same way. 269 # expected path is resolved the same way.
328 DEXE=$(gssh 60 <<G 270 DEXE=$(box_ssh 60 "$MAC_BOX" <<G
329 . ./os_oracle.sh 271 . ./os_oracle.sh
330 echo "is=\$(pid_exe $DPID)" 272 echo "is=\$(pid_exe $DPID)"
331 echo "want=\$(real_path ./mux)" 273 echo "want=\$(real_path ./mux)"
@@ -335,21 +277,21 @@ _is=$(printf '%s\n' "$DEXE" | sed -n 's/^is=//p')
335 _want=$(printf '%s\n' "$DEXE" | sed -n 's/^want=//p') 277 _want=$(printf '%s\n' "$DEXE" | sed -n 's/^want=//p')
336 [ -n "$_want" ] && [ "$_is" = "$_want" ] || 278 [ -n "$_want" ] && [ "$_is" = "$_want" ] ||
337 fail daemon "daemon $DPID execs '$_is', not the installed '$_want'" 279 fail daemon "daemon $DPID execs '$_is', not the installed '$_want'"
338 RTMODE=$(gssh 60 <<G 280 RTMODE=$(box_ssh 60 "$MAC_BOX" <<G
339 . ./os_oracle.sh 281 . ./os_oracle.sh
340 file_mode /tmp/mux-$GUID 282 file_mode /tmp/mux-$GUID
341 G 283 G
342 ) 284 )
343 [ "$RTMODE" = "700" ] || 285 [ "$RTMODE" = "700" ] ||
344 fail daemon "/tmp/mux-$GUID is mode $RTMODE, want 700" 286 fail daemon "/tmp/mux-$GUID is mode $RTMODE, want 700"
345 ok daemon "daemon $DPID holds $SOCK, a 700 directory the guest had no \$XDG_RUNTIME_DIR for" 287 ok daemon "daemon $DPID holds $SOCK, a 700 directory $MAC_BOX had no \$XDG_RUNTIME_DIR for"
346 288
347 # ---- leg 6: attach — a real pty, a real session ------------------------ 289 # ---- leg 5: attach — a real pty, a real session ------------------------
348 # Before the session leg and not after it, though the brief lists them the 290 # Before the session leg and not after it, though the brief lists them the
349 # other way round: `mux a` attaches at 0x0 and JOINS, so it can only speak 291 # other way round: `mux a` attaches at 0x0 and JOINS, so it can only speak
350 # to a session that already exists (README). The pty attach is what makes 292 # to a session that already exists (README). The pty attach is what makes
351 # one, which makes it the leg that has to run first. 293 # one, which makes it the leg that has to run first.
352 ATT=$(gssh 240 <<'G' 294 ATT=$(box_ssh 240 "$MAC_BOX" <<'G'
353 ./ptyclient --cols 80 --rows 24 --out /tmp/mac-attach.cap --err /tmp/mac-attach.err -- ./mux <<'PC' 295 ./ptyclient --cols 80 --rows 24 --out /tmp/mac-attach.cap --err /tmp/mac-attach.err -- ./mux <<'PC'
354 expect \x1b[?1049h 30000 296 expect \x1b[?1049h 30000
355 settle 900 20000 297 settle 900 20000
@@ -361,28 +303,28 @@ waitexit 15000
361 PC 303 PC
362 echo "PTYRC=$?" 304 echo "PTYRC=$?"
363 G 305 G
364 ) || fail attach "the guest would not run ptyclient" 306 ) || fail attach "$MAC_BOX would not run ptyclient"
365 printf '%s\n' "$ATT" | grep -qx 'PTYRC=0' || { 307 printf '%s\n' "$ATT" | grep -qx 'PTYRC=0' || {
366 echo "mac FAIL: attach: ptyclient did not reach the JOURNEY-ok snapshot: $ATT" 308 echo "mac FAIL: attach: ptyclient did not reach the JOURNEY-ok snapshot: $ATT"
367 gssh 60 <<'G' | sed 's/^/ /' || true 309 box_ssh 60 "$MAC_BOX" <<'G' | sed 's/^/ /' || true
368 cat /tmp/mac-attach.err 2>/dev/null 310 cat /tmp/mac-attach.err 2>/dev/null
369 G 311 G
370 exit 1; } 312 exit 1; }
371 # The detach was a goodbye and not a tear: the session it typed into is 313 # The detach was a goodbye and not a tear: the session it typed into is
372 # still on the daemon, holding what it painted. 314 # still on the daemon, holding what it painted.
373 gssh 60 >/dev/null <<'G' || fail attach "the session did not survive the detach" 315 box_ssh 60 "$MAC_BOX" >/dev/null <<'G' || fail attach "the session did not survive the detach"
374 ./mux d dump | grep -q JOURNEY-ok 316 ./mux d dump | grep -q JOURNEY-ok
375 G 317 G
376 ok attach "a pty client typed into a session and detached, the grid kept it" 318 ok attach "a pty client typed into a session and detached, the grid kept it"
377 319
378 # ---- leg 7: session — one shot through the agent surface --------------- 320 # ---- leg 6: session — one shot through the agent surface ---------------
379 # $$ expands in the session's OWN shell, so the pid it prints is a pid only 321 # $$ expands in the session's OWN shell, so the pid it prints is a pid only
380 # the guest's process table can confirm — which is the assert nothing on 322 # the box's process table can confirm — which is the assert nothing on
381 # this side can fake. 323 # this side can fake.
382 AREPLY=$(gssh 90 <<'G' 324 AREPLY=$(box_ssh 90 "$MAC_BOX" <<'G'
383 ./mux a run --timeout 20000 'echo mac-$$' 325 ./mux a run --timeout 20000 'echo mac-$$'
384 G 326 G
385 ) || fail session "mux a run did not answer on the guest" 327 ) || fail session "mux a run did not answer on $MAC_BOX"
386 case "$AREPLY" in 328 case "$AREPLY" in
387 *'"mechanism":"marks"'*) ;; 329 *'"mechanism":"marks"'*) ;;
388 *) fail session "mux a run answered under no mechanism this leg can read: $AREPLY" ;; 330 *) fail session "mux a run answered under no mechanism this leg can read: $AREPLY" ;;
@@ -393,17 +335,17 @@ case "$AREPLY" in
393 esac 335 esac
394 SPID=$(printf '%s\n' "$AREPLY" | sed -n 's/.*mac-\([0-9][0-9]*\).*/\1/p') 336 SPID=$(printf '%s\n' "$AREPLY" | sed -n 's/.*mac-\([0-9][0-9]*\).*/\1/p')
395 [ -n "$SPID" ] || fail session "the reply carries no shell pid: $AREPLY" 337 [ -n "$SPID" ] || fail session "the reply carries no shell pid: $AREPLY"
396 gssh 60 >/dev/null <<G || fail session "shell $SPID is not a child of daemon $DPID" 338 box_ssh 60 "$MAC_BOX" >/dev/null <<G || fail session "shell $SPID is not a child of daemon $DPID"
397 . ./os_oracle.sh 339 . ./os_oracle.sh
398 pid_children $DPID | grep -qx $SPID 340 pid_children $DPID | grep -qx $SPID
399 G 341 G
400 ok session "mux a run answered under marks; shell $SPID is a child of daemon $DPID" 342 ok session "mux a run answered under marks; shell $SPID is a child of daemon $DPID"
401 343
402 # ---- leg 8: stop — and the runtime socket goes with it ----------------- 344 # ---- leg 7: stop — and the runtime socket goes with it -----------------
403 gssh 60 >/dev/null <<'G' || fail stop "mux d stop was refused on the guest" 345 box_ssh 60 "$MAC_BOX" >/dev/null <<'G' || fail stop "mux d stop was refused on $MAC_BOX"
404 ./mux d stop 346 ./mux d stop
405 G 347 G
406 END=$(gssh 60 <<G 348 END=$(box_ssh 60 "$MAC_BOX" <<G
407 . ./os_oracle.sh 349 . ./os_oracle.sh
408 _i=0 350 _i=0
409 while [ \$_i -lt 30 ]; do 351 while [ \$_i -lt 30 ]; do
@@ -422,10 +364,13 @@ ok stop "daemon $DPID is gone within 3 s and took $SOCK with it"
422 364
423 # ---- the pin ---------------------------------------------------------- 365 # ---- the pin ----------------------------------------------------------
424 # A literal, test/vm.sh's style: adding a leg means editing this by hand, 366 # A literal, test/vm.sh's style: adding a leg means editing this by hand,
425 # so a leg that stops running cannot pass by not being counted. 367 # so a leg that stops running cannot pass by not being counted. Seven and
426 [ "$OK_COUNT" = "8" ] || { 368 # not eight since 2026-09-04: the boot-a-clone leg left with the VM
427 echo "mac FAIL: $OK_COUNT legs ran, the pin says 8 —" 369 # lifecycle, and what replaced it is the preflight line above, which is not
370 # an `ok`.
371 [ "$OK_COUNT" = "7" ] || {
372 echo "mac FAIL: $OK_COUNT legs ran, the pin says 7 —"
428 echo " a leg was added (update the pin) or silently lost" 373 echo " a leg was added (update the pin) or silently lost"
429 exit 1 374 exit 1
430 } 375 }
431 echo "mac OK ($OK_COUNT legs on $VM_RUN, a pristine macOS on $MAC; boot ${VM_MS}ms)" 376 echo "mac OK ($OK_COUNT legs on $MAC_BOX, a bare macOS running what $MAC_BUILDER built)"