a73x

7c02ae1a

test: the cross-OS gate starts its Linux daemon on the box's own default key

a73x   2026-09-04 10:16

Commit message
test: the cross-OS gate starts its Linux daemon on the box's own default key

The first run of the gate found that a Mac attaching to a Linux daemon
fell back to the ssh pipe every time, and the cause is neither the network
nor macOS. `mux d endpoint` resolves the key it ANNOUNCES from
$MUX_KEY_FILE or the default path and never asks the daemon which key its
listener holds, creating the default file on the spot when there is none.
A daemon started `--quic ADDR --key SOMEWHERE_ELSE` therefore announces a
key that cannot authenticate against itself, every handoff dies in the
silence a wrong PSK produces, and the client says "unreachable, attaching
over ssh". Measured on this fixture: listener f47491fa..., announce
d90e0af1..., a packet capture on the daemon's side showing the client's
Initials answered and the handshake never completing.

So leg 5 now starts the Linux daemon the way a person does after
`mux d keygen` — `--quic ADDR` and no `--key` — with the run's key at that
box's default paths, and reads the announce back to pin that it names this
run's key at this run's port. A gate that grades the ssh fallback while
reporting QUIC is worse than no gate.

Two other findings from the same run. `/tmp/mux-<uid>` on the Mac is made
by any mux at all, a plain client included, so the pristine check reads
the SOCKET inside it: pgrep already says nothing is running. And leg 7's
entry dial makes the Mac create ~/.config/mux/key, which is the product's
own behaviour on any box a person dials; the run records whether that file
was there beforehand and removes it on the way out if it was not.

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

test/xos.sh
Old New
@@ -134,6 +134,13 @@ lssh() {
134 # ---- the local side is a hermetic laptop ------------------------------ 134 # ---- the local side is a hermetic laptop ------------------------------
135 # Every XDG dir mux reads, isolated. Short root because a unix socket path 135 # Every XDG dir mux reads, isolated. Short root because a unix socket path
136 # caps at 107 bytes. 136 # caps at 107 bytes.
137 # Whether the run is responsible for ~/.config/mux on the Mac. Leg 7's entry
138 # dial makes the Mac run `mux d endpoint`, which CREATES the default key file
139 # when there is none — the product's own behaviour on any box a person dials,
140 # not something the harness can spell away, since mux writes that ssh line
141 # itself. Recorded in leg 3 and undone in the cleanup, so a Mac that had no
142 # mux key before this gate has none after it.
143 MAC_CFG_MADE=no
137 SCRATCH=$(mktemp -d /tmp/muxxos.XXXXXX) 144 SCRATCH=$(mktemp -d /tmp/muxxos.XXXXXX)
138 export XDG_STATE_HOME="$SCRATCH/st" XDG_RUNTIME_DIR="$SCRATCH/rt" 145 export XDG_STATE_HOME="$SCRATCH/st" XDG_RUNTIME_DIR="$SCRATCH/rt"
139 export XDG_CONFIG_HOME="$SCRATCH/cf" XDG_CACHE_HOME="$SCRATCH/ca" 146 export XDG_CONFIG_HOME="$SCRATCH/cf" XDG_CACHE_HOME="$SCRATCH/ca"
@@ -150,9 +157,11 @@ sleep 0.3
150 pkill -x mux 2>/dev/null || true 157 pkill -x mux 2>/dev/null || true
151 rm -rf ~/xos 158 rm -rf ~/xos
152 L 159 L
153 mssh 60 >/dev/null 2>&1 <<'M' || true 160 mssh 60 >/dev/null 2>&1 <<M || true
154 $HOME/.local/bin/mux d stop 2>/dev/null || true 161 \$HOME/.local/bin/mux d stop 2>/dev/null || true
155 rm -rf $HOME/xos 162 rm -rf \$HOME/xos
163 [ "$MAC_CFG_MADE" = yes ] && rm -rf \$HOME/.config/mux
164 exit 0
156 M 165 M
157 rm -rf "$SCRATCH" 166 rm -rf "$SCRATCH"
158 exit "$_rc" 167 exit "$_rc"
@@ -267,7 +276,8 @@ leg
267 MPRE=$(mssh 60 <<'M' 276 MPRE=$(mssh 60 <<'M'
268 echo "uid=$(id -u)" 277 echo "uid=$(id -u)"
269 echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')" 278 echo "muxes=$(pgrep -x mux 2>/dev/null | wc -l | tr -d ' ')"
270 [ -e /tmp/mux-$(id -u) ] && echo "rt=present" || echo "rt=absent" 279 [ -e /tmp/mux-$(id -u)/muxd.sock ] && echo "rt=present" || echo "rt=absent"
280 [ -e $HOME/.config/mux ] && echo "cfg=present" || echo "cfg=absent"
271 ifconfig 2>/dev/null | sed -n 's/^[[:space:]]*inet \([0-9.]*\) .*/ip=\1/p' 281 ifconfig 2>/dev/null | sed -n 's/^[[:space:]]*inet \([0-9.]*\) .*/ip=\1/p'
272 M 282 M
273 ) 283 )
@@ -277,14 +287,21 @@ printf '%s\n' "$MPRE" | grep -qx 'muxes=0' || {
277 echo "xos FAIL: reset: $MAC already runs a mux, and this gate will not kill it." 287 echo "xos FAIL: reset: $MAC already runs a mux, and this gate will not kill it."
278 echo " Stop it there (mux d stop) or run this against another Mac." 288 echo " Stop it there (mux d stop) or run this against another Mac."
279 exit 1; } 289 exit 1; }
290 # The SOCKET and not the directory. `pgrep` above already says no mux is
291 # running; what is left to rule out is a daemon serving at the path the
292 # Linux→Mac legs will dial. The directory itself is made by any mux at all,
293 # a plain client included, so on a Mac that has ever run one it is simply
294 # there — refusing on it would refuse every machine the gate is for.
280 printf '%s\n' "$MPRE" | grep -qx 'rt=absent' || 295 printf '%s\n' "$MPRE" | grep -qx 'rt=absent' ||
281 fail reset "$MAC already has /tmp/mux-$MAC_UID; the daemon this gate meets must be the one it starts" 296 fail reset "$MAC already serves /tmp/mux-$MAC_UID/muxd.sock; the daemon this gate meets must be the one it starts"
282 # The address the QUIC legs dial, asserted against the Mac's own interface 297 # The address the QUIC legs dial, asserted against the Mac's own interface
283 # list rather than assumed from a variable: a stale MUX_MAC_IP would make 298 # list rather than assumed from a variable: a stale MUX_MAC_IP would make
284 # leg 8 grade some other machine's silence. 299 # leg 8 grade some other machine's silence.
285 printf '%s\n' "$MPRE" | grep -qx "ip=$MAC_IP" || 300 printf '%s\n' "$MPRE" | grep -qx "ip=$MAC_IP" ||
286 fail reset "$MAC does not hold $MAC_IP; set MUX_MAC_IP. It answered: $(printf '%s' "$MPRE" | tr '\n' ' ')" 301 fail reset "$MAC does not hold $MAC_IP; set MUX_MAC_IP. It answered: $(printf '%s' "$MPRE" | tr '\n' ' ')"
287 MAC_SOCK="/tmp/mux-$MAC_UID/muxd.sock" 302 MAC_SOCK="/tmp/mux-$MAC_UID/muxd.sock"
303 # Leg 7's dial will create this if it is not there; see MAC_CFG_MADE.
304 if printf '%s\n' "$MPRE" | grep -qx 'cfg=absent'; then MAC_CFG_MADE=yes; fi
288 305
289 # The Linux box IS a throwaway, so it gets test/vm.sh's scorched earth. The 306 # The Linux box IS a throwaway, so it gets test/vm.sh's scorched earth. The
290 # reset is the hermeticity: each run starts from the mux-less box a new user 307 # reset is the hermeticity: each run starts from the mux-less box a new user
@@ -339,7 +356,20 @@ ok reset "$LAN reset to a mux-less box and given '$LAN_VER'; $MAC left running n
339 # `mux d keygen` always writes the default path, so the isolated 356 # `mux d keygen` always writes the default path, so the isolated
340 # XDG_CONFIG_HOME above is what puts it in the scratch tree. It is copied to 357 # XDG_CONFIG_HOME above is what puts it in the scratch tree. It is copied to
341 # ~/xos/key on both boxes and named on every QUIC command line; the Mac's 358 # ~/xos/key on both boxes and named on every QUIC command line; the Mac's
342 # own ~/.config/mux/key is never read and never written. 359 # own ~/.config/mux/key is never written by the HARNESS (leg 7's dial makes
360 # the product write it, which MAC_CFG_MADE undoes).
361 #
362 # On the Linux box the same bytes ALSO go to that box's default key path,
363 # and leg 5's daemon is started with no `--key` at all, which is how a
364 # person starts one after `mux d keygen`. That is not decoration. `mux d
365 # endpoint` resolves the key it ANNOUNCES from $MUX_KEY_FILE or the default
366 # path — it never asks the daemon which key its listener actually holds — so
367 # a daemon started with `--quic ... --key SOMEWHERE_ELSE` announces a key
368 # that does not authenticate against itself, creating the default file on
369 # the spot if it has to. Every ssh handoff to such a daemon then dies in
370 # silence and falls back to the pipe. Measured on this fixture 2026-09-04
371 # and reported; it is not a macOS difference, and the two spellings agree
372 # again the moment the listener's key IS the default one.
343 leg 373 leg
344 "$MUX" d keygen >/dev/null 2>&1 || fail keys "mux d keygen refused under $XDG_CONFIG_HOME" 374 "$MUX" d keygen >/dev/null 2>&1 || fail keys "mux d keygen refused under $XDG_CONFIG_HOME"
345 cp "$XDG_CONFIG_HOME/mux/key" "$SCRATCH/key" 375 cp "$XDG_CONFIG_HOME/mux/key" "$SCRATCH/key"
@@ -350,6 +380,21 @@ chmod 600 "$SCRATCH/key"
350 scp -q $SSH_OPTS "$SCRATCH/key" "$LAN":xos/key || fail keys "could not install the key on $LAN" 380 scp -q $SSH_OPTS "$SCRATCH/key" "$LAN":xos/key || fail keys "could not install the key on $LAN"
351 # shellcheck disable=SC2086 # SSH_OPTS is words on purpose 381 # shellcheck disable=SC2086 # SSH_OPTS is words on purpose
352 scp -q $SSH_OPTS "$SCRATCH/key" "$MAC":xos/key || fail keys "could not install the key on $MAC" 382 scp -q $SSH_OPTS "$SCRATCH/key" "$MAC":xos/key || fail keys "could not install the key on $MAC"
383 # BOTH default key paths on the Linux box, because two different
384 # environments resolve "the default key" there and they must not disagree:
385 # everything the harness runs sees the prelude's isolated XDG_CONFIG_HOME,
386 # while the `mux d endpoint` at the far end of the MAC's entry dial is a
387 # fresh ssh with the box's own environment and resolves ~/.config/mux/key.
388 # Same bytes at both, so whichever one a process picks is this run's key.
389 lssh 30 >/dev/null <<'L' || fail keys "could not install the key at the default paths on $LAN"
390 set -e
391 for d in "$HOME/.config/mux" "${XDG_CONFIG_HOME:-$HOME/.config}/mux"; do
392 mkdir -p "$d"
393 chmod 700 "$d"
394 cp "$HOME/xos/key" "$d/key"
395 chmod 600 "$d/key"
396 done
397 L
353 # 0600 on both, because quic.Key.load refuses a group- or other-readable key 398 # 0600 on both, because quic.Key.load refuses a group- or other-readable key
354 # exactly as ssh refuses a private key, and scp does not carry the mode. 399 # exactly as ssh refuses a private key, and scp does not carry the mode.
355 KMODES=$( 400 KMODES=$(
@@ -377,8 +422,22 @@ ok keys "one 32-byte PSK at ~/xos/key, mode 600 on both boxes"
377 # this same daemon and marks are opt-in. 422 # this same daemon and marks are opt-in.
378 leg 423 leg
379 lssh 90 >/dev/null <<L || fail mac-to-linux "mux d start -d --quic was refused on $LAN" 424 lssh 90 >/dev/null <<L || fail mac-to-linux "mux d start -d --quic was refused on $LAN"
380 MUX_SHELL_INTEGRATION=1 ~/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT --key ~/xos/key 425 MUX_SHELL_INTEGRATION=1 ~/.local/bin/mux d start -d --quic 0.0.0.0:$QUIC_PORT
381 L 426 L
427 # The listener's key and the key `mux d endpoint` announces, side by side.
428 # They are the same file here by construction (leg 4), and this reads them
429 # back rather than trusting that: an announce that names a key the listener
430 # does not hold is invisible from the client — the handshake simply never
431 # answers — and leg 5 would then be grading the ssh fallback while reporting
432 # QUIC.
433 ANN=$(lssh 30 <<'L'
434 ~/.local/bin/mux d endpoint
435 L
436 )
437 case "$ANN" in
438 "endpoint $QUIC_PORT $(od -An -tx1 -v < "$SCRATCH/key" | tr -d ' \n')") ;;
439 *) fail mac-to-linux "$LAN announces '$ANN', which is not this run's key at port $QUIC_PORT" ;;
440 esac
382 LPID=$(lssh 30 <<'L' 441 LPID=$(lssh 30 <<'L'
383 pgrep -x mux | head -1 442 pgrep -x mux | head -1
384 L 443 L