a73x

35504f14

test: the cold handoff is one ssh run, and the word is what says so

a73x   2026-08-29 14:38

Commit message
test: the cold handoff is one ssh run, and the word is what says so

`e2e_04_handoff.sh` (a): `HSHIMS -eq 1`, where three was the shape while
the client decided to start. One run is not the whole claim, though — a
client that spent its one run on the READING verb would show the same 1
and start nothing — so the shim now logs the remote command it was handed
and the leg pins `mux d endpoint --start` exactly once. The starting line
grep follows the verb that prints it.

`e2e_09_hosts.sh`: the claim stands and the mechanism changes, so the
shim log carries the command as well as the host. `mux hosts` and three
seconds of wall polling spell the asking word ZERO times; the picker
Enter spells it once, at the row it chose; the cold `mux HOST` spells it
once at box A. Every OS-side oracle is untouched — the /proc walk, the
socket, the pid.

Nothing was added or removed, so the suite's scenario pin does not move.

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

test/e2e_04_handoff.sh
Old New
@@ -7,9 +7,9 @@
7 # per daemon: the handoff dials the DEFAULT socket on the far side, and the 7 # per daemon: the handoff dials the DEFAULT socket on the far side, and the
8 # far side is this box, so each scenario's default socket has to be one of 8 # far side is this box, so each scenario's default socket has to be one of
9 # ours rather than the operator's. Two daemons, no more — the one a cold 9 # ours rather than the operator's. Two daemons, no more — the one a cold
10 # attach starts by running `mux d start` over the shim, and the key-mismatch 10 # attach gets because its own `mux d endpoint --start` ensured it over the
11 # one that cannot be shared because it holds a key nothing else on this box 11 # shim, and the key-mismatch one that cannot be shared because it holds a
12 # has. 12 # key nothing else on this box has.
13 SSHIM_DIR="${TMPDIR:-/tmp}/muxd-e2e-sshim-$$" 13 SSHIM_DIR="${TMPDIR:-/tmp}/muxd-e2e-sshim-$$"
14 defer_rm "$SSHIM_DIR" 14 defer_rm "$SSHIM_DIR"
15 SSHIM_PIDLOG="$SSHIM_DIR/pids" 15 SSHIM_PIDLOG="$SSHIM_DIR/pids"
@@ -21,6 +21,12 @@ export SSHIM_PIDLOG
21 # name a shell in one of them. 21 # name a shell in one of them.
22 SSHIM_COMMLOG="$SSHIM_DIR/comms" 22 SSHIM_COMMLOG="$SSHIM_DIR/comms"
23 export SSHIM_COMMLOG 23 export SSHIM_COMMLOG
24 # The remote COMMAND each run was handed, one line per run. The pidlog says
25 # how many ssh runs there were and the commlog says what they were; neither
26 # can tell a reading dial from an asking one, and that distinction is now
27 # the whole of how a daemon comes to exist.
28 SSHIM_ARGLOG="$SSHIM_DIR/args"
29 export SSHIM_ARGLOG
24 HRUN="${TMPDIR:-/tmp}/mux-e2e-hrun-$$" 30 HRUN="${TMPDIR:-/tmp}/mux-e2e-hrun-$$"
25 defer_rm "$HRUN" 31 defer_rm "$HRUN"
26 HRUN2="${TMPDIR:-/tmp}/mux-e2e-hrun2-$$" 32 HRUN2="${TMPDIR:-/tmp}/mux-e2e-hrun2-$$"
@@ -77,11 +83,13 @@ HDEADPORT=$(( 16000 + ($$ % 4000) ))
77 mkdir -p "$SSHIM_DIR" "$HRUN" "$HRUN2" 83 mkdir -p "$SSHIM_DIR" "$HRUN" "$HRUN2"
78 : > "$SSHIM_PIDLOG" 84 : > "$SSHIM_PIDLOG"
79 : > "$SSHIM_COMMLOG" 85 : > "$SSHIM_COMMLOG"
86 : > "$SSHIM_ARGLOG"
80 ssh_shim_head "$SSHIM_DIR/ssh" 87 ssh_shim_head "$SSHIM_DIR/ssh"
81 cat >> "$SSHIM_DIR/ssh" <<'SHIM' 88 cat >> "$SSHIM_DIR/ssh" <<'SHIM'
82 echo $$ >> "${SSHIM_PIDLOG:?}" 89 echo $$ >> "${SSHIM_PIDLOG:?}"
83 printf '%s %s\n' "$(cat /proc/$$/comm)" "$(cat /proc/$PPID/comm)" >> "${SSHIM_COMMLOG:?}" 90 printf '%s %s\n' "$(cat /proc/$$/comm)" "$(cat /proc/$PPID/comm)" >> "${SSHIM_COMMLOG:?}"
84 shift 91 shift
92 printf '%s\n' "$*" >> "${SSHIM_ARGLOG:?}"
85 exec /bin/sh -c "$*" 93 exec /bin/sh -c "$*"
86 SHIM 94 SHIM
87 chmod +x "$SSHIM_DIR/ssh" 95 chmod +x "$SSHIM_DIR/ssh"
@@ -98,8 +106,9 @@ HPATH="$SSHIM_DIR:$(dirname "$MUX"):$PATH"
98 HHOST="mux-e2e@127.0.0.1" 106 HHOST="mux-e2e@127.0.0.1"
99 HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST" 107 HCACHE="$XDG_CACHE_HOME/mux/hosts/$HHOST"
100 # (a) COLD: no daemon, no cache. The attach has to produce the daemon — 108 # (a) COLD: no daemon, no cache. The attach has to produce the daemon —
101 # `mux d endpoint` refuses a box that has none, and the CLIENT answers that 109 # the REMOTE does it, because an asked dial spells `mux d endpoint --start`
102 # by running `mux d start` over the shim — fetch coordinates over the shim, 110 # and that verb ensures a daemon before it announces — fetch coordinates
111 # over the shim,
103 # and END UP ON QUIC — and that last part is proven by observation rather than 112 # and END UP ON QUIC — and that last part is proven by observation rather than
104 # inference: the ssh the announce arrived on is killed by the client on 113 # inference: the ssh the announce arrived on is killed by the client on
105 # QUIC success, so once its pid is OBSERVED gone, a second marker still 114 # QUIC success, so once its pid is OBSERVED gone, a second marker still
@@ -116,18 +125,26 @@ wait_for "$OUT.h1" "cold-one" 25 || {
116 echo " A client that blocks here read no announce line off the ssh pipe." 125 echo " A client that blocks here read no announce line off the ssh pipe."
117 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } 126 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; }
118 127
119 # Exactly three ssh runs, and the LAST one's pid, from the log rather than 128 # Exactly ONE ssh run, from the log rather than from ps. Three was the cold
120 # from ps. Three is the cold shape and it is bounded: `mux d endpoint` on a 129 # shape while the CLIENT decided to start: `mux d endpoint` refused an empty
121 # box with no daemon, then `mux d start`, then `mux d endpoint` again. Never 130 # box, the client reaped the exit code to tell that refusal from ssh's own
122 # a fourth — the ask buys one start and one retry — and the count is what 131 # 255, ran `mux d start`, and asked again. The remote decides now, so the
123 # says so: a client that looped would show four here, not a hang. 132 # run that starts the daemon is the run that announces it.
124 HSHIMS=$(wc -l < "$SSHIM_PIDLOG") 133 HSHIMS=$(wc -l < "$SSHIM_PIDLOG")
125 [ "$HSHIMS" -eq 3 ] || { 134 [ "$HSHIMS" -eq 1 ] || {
126 echo "e2e FAIL: cold handoff ran $HSHIMS ssh invocations, want exactly 3" 135 echo "e2e FAIL: cold handoff ran $HSHIMS ssh invocations, want exactly 1"
127 echo " (mux d endpoint on an empty box, mux d start, mux d endpoint)" 136 echo " (one mux d endpoint --start, which ensures and announces)"
128 cat "$SSHIM_PIDLOG"; exit 1; } 137 cat "$SSHIM_PIDLOG"; cat "$SSHIM_ARGLOG"; exit 1; }
129 # Asked of /proc, not of the client: every one of those three children was 138 # The WORD, not just the count. A client that spent its one run on the
130 # `ssh` itself, sitting directly under `mux`. What this does NOT catch is a 139 # reading verb would show the same 1 here and would have started nothing —
140 # it is the flag that makes this cold attach work at all, and the flag is
141 # the only thing separating this run from a wall poll's.
142 HASKS=$(grep -c -- 'mux d endpoint --start$' "$SSHIM_ARGLOG" || true)
143 [ "$HASKS" -eq 1 ] || {
144 echo "e2e FAIL: cold handoff ran the asking word $HASKS times, want exactly 1"
145 cat "$SSHIM_ARGLOG"; exit 1; }
146 # Asked of /proc, not of the client: that child was `ssh` itself, sitting
147 # directly under `mux`. What this does NOT catch is a
131 # `/bin/sh -c` line put back — bash and dash both exec a single simple 148 # `/bin/sh -c` line put back — bash and dash both exec a single simple
132 # command in place, so the shell is gone by the time /proc is read. That 149 # command in place, so the shell is gone by the time /proc is read. That
133 # regression is build.zig folder rule 5's, measured there; this leg pins the 150 # regression is build.zig folder rule 5's, measured there; this leg pins the
@@ -137,9 +154,8 @@ HBADCOMM=$(grep -cv '^ssh mux$' "$SSHIM_COMMLOG" || true)
137 echo "e2e FAIL: the handoff spawned something other than ssh straight off mux:" 154 echo "e2e FAIL: the handoff spawned something other than ssh straight off mux:"
138 cat "$SSHIM_COMMLOG"; exit 1; } 155 cat "$SSHIM_COMMLOG"; exit 1; }
139 ok "the handoff's child IS ssh, forked off mux with no shell between" 156 ok "the handoff's child IS ssh, forked off mux with no shell between"
140 # The LAST, not the first: the first two are the refusal and the start, and 157 # The one run there was, and the one that carried the announce: its death
141 # both are already dead by construction. The ssh whose death proves QUIC 158 # is what proves QUIC took over.
142 # took over is the one that carried the announce.
143 HSHIMPID=$(tail -1 "$SSHIM_PIDLOG") 159 HSHIMPID=$(tail -1 "$SSHIM_PIDLOG")
144 wait_pid_gone "$HSHIMPID" "cold handoff: QUIC took over, so ssh must be gone" 160 wait_pid_gone "$HSHIMPID" "cold handoff: QUIC took over, so ssh must be gone"
145 # The second marker is typed only now, after the death was OBSERVED, so its 161 # The second marker is typed only now, after the death was OBSERVED, so its
@@ -156,20 +172,21 @@ wait_for "$OUT.h1" "cold-two" 20 || {
156 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; } 172 cat "$OUT.h1" "$OUT.h1.err" 2>/dev/null; exit 1; }
157 pipe_detach "cold handoff client" 173 pipe_detach "cold handoff client"
158 174
159 # The daemon the CLIENT started, by the pid `mux d start`'s own up-line 175 # The daemon the REMOTE started, by the pid its own up-line reported — the
160 # reported — the only handle this suite has on a process that is nobody's 176 # only handle this suite has on a process that is nobody's child. The line
161 # child. The line is `mux d start`'s now, not `mux d endpoint`'s, and it 177 # is `mux d endpoint`'s now, and it reaches this file the way ssh's own
162 # reaches this file the same way ssh's own stderr does: inherited. Same 178 # stderr does: inherited, because the entry dial is not `quiet`. Same
163 # accepted gap as the M13 blocks: a failure above leaves it untracked, and 179 # accepted gap as the M13 blocks: a failure above leaves it untracked, and
164 # the trap's stop-by-socket is what covers that. 180 # the trap's stop-by-socket is what covers that.
165 grep -q '^mux d: starting' "$OUT.h1.err" || { 181 grep -q '^mux d endpoint: starting' "$OUT.h1.err" || {
166 echo "e2e FAIL: cold handoff printed no mux d start line — the client did not" 182 echo "e2e FAIL: cold handoff printed no starting line — mux d endpoint --start"
167 echo " start the daemon that mux d endpoint refused to" 183 echo " did not ensure the daemon it was asked for"
168 cat "$OUT.h1.err"; exit 1; } 184 cat "$OUT.h1.err"; exit 1; }
169 HAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.h1.err" | head -1) 185 HAPID=$(sed -n 's/.* pid=\([0-9]*\).*/\1/p' "$OUT.h1.err" | head -1)
170 defer_kill "$HAPID" 186 defer_kill "$HAPID"
171 [ -n "$HAPID" ] || { 187 [ -n "$HAPID" ] || {
172 echo "e2e FAIL: mux d start's up-line carries no pid"; cat "$OUT.h1.err"; exit 1; } 188 echo "e2e FAIL: mux d endpoint --start's up-line carries no pid"
189 cat "$OUT.h1.err"; exit 1; }
173 kill -0 "$HAPID" 2>/dev/null || { 190 kill -0 "$HAPID" 2>/dev/null || {
174 echo "e2e FAIL: the handoff daemon (pid $HAPID) is not alive"; exit 1; } 191 echo "e2e FAIL: the handoff daemon (pid $HAPID) is not alive"; exit 1; }
175 192
test/e2e_09_hosts.sh
Old New
@@ -911,14 +911,17 @@ ok "the picker births on the host a digit names, forgets a host without ending i
911 # nothing on it therefore got a daemon — and a shell in session 0 — from a 911 # nothing on it therefore got a daemon — and a shell in session 0 — from a
912 # READ, and a `mux d stop` typed on that box was undone a second later. 912 # READ, and a `mux d stop` typed on that box was undone a second later.
913 # 913 #
914 # The rule now: a daemon starts when somebody asks. `mux HOST` and the 914 # The rule now: a daemon starts when somebody asks, and the ask is a WORD.
915 # picker's Enter ask, and answer a refusal by running `mux d start` over the 915 # `mux HOST` and the picker's Enter spell `mux d endpoint --start`, which
916 # same ssh; the poller, `mux hosts` and every redial report `unreachable` 916 # ensures a daemon and announces in the same run; the poller, `mux hosts`
917 # and leave the box alone. 917 # and every redial spell the bare verb, which can start nothing whatever
918 # the client does with the answer.
918 # 919 #
919 # The oracle is the OS, never the daemon: the socket INODE on the far side, 920 # The oracle is the OS, never the daemon: the socket INODE on the far side,
920 # and a walk of /proc for a `mux d run` born under that box's runtime dir. A 921 # and a walk of /proc for a `mux d run` born under that box's runtime dir. A
921 # daemon asked whether it exists cannot answer no. 922 # daemon asked whether it exists cannot answer no. The shim log is the
923 # other half — it records the remote command, so "a read started nothing"
924 # is checked against the word that ran and not only against the outcome.
922 # 925 #
923 # Two fake boxes, because one cannot show that a birth landed on the row 926 # Two fake boxes, because one cannot show that a birth landed on the row
924 # the user chose: the shim reads the host word and hands the remote command 927 # the user chose: the shim reads the host word and hands the remote command
@@ -951,7 +954,7 @@ case "$1" in
951 *) exit 96 ;; 954 *) exit 96 ;;
952 esac 955 esac
953 export XDG_RUNTIME_DIR 956 export XDG_RUNTIME_DIR
954 echo "$1" >> "${NSHIMLOG:?}" 957 printf '%s\n' "$*" >> "${NSHIMLOG:?}"
955 shift 958 shift
956 exec /bin/sh -c "$*" 959 exec /bin/sh -c "$*"
957 SHIM 960 SHIM
@@ -960,10 +963,12 @@ NPATH="$NSHIM:$(cd "$(dirname "$MUX")" && pwd):$PATH"
960 printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts" 963 printf 'nostart-a@127.0.0.1\nnostart-b@127.0.0.1\n' > "$NSTATE/mux/hosts"
961 964
962 # The /proc walk. `mux d run` and not any `mux` — the transient `mux d 965 # The /proc walk. `mux d run` and not any `mux` — the transient `mux d
963 # endpoint` and `mux d start` processes share the runtime dir and are not 966 # endpoint` processes share the runtime dir and are not daemons — and
964 # daemons — and `environ` rather than a name, because a daemon `mux d start` 967 # `environ` rather than the argv's `--sock`: the path the ensure forwards is
965 # spawns carries no --sock to grep for: it took the default from this 968 # whatever this variable resolved to, so the variable is the box's identity
966 # variable. The daemon spells `d run` because that is the argv spawn.zig 969 # and the argv is derived from it. The shim gives each fake host a runtime
970 # dir of its own, which is the whole of what makes them two machines. The
971 # daemon spells `d run` because that is the argv spawn.zig
967 # execs it with, which is also what makes `ps` legible. 972 # execs it with, which is also what makes `ps` legible.
968 nostart_daemons() { 973 nostart_daemons() {
969 _nd=0 974 _nd=0
@@ -1001,6 +1006,14 @@ nostart_empty "$NRUNB" "$NSOCKB" "mux hosts"
1001 # that never dialled: one line per box, at least. 1006 # that never dialled: one line per box, at least.
1002 grep -q 'nostart-a@' "$NSHIMLOG" && grep -q 'nostart-b@' "$NSHIMLOG" || { 1007 grep -q 'nostart-a@' "$NSHIMLOG" && grep -q 'nostart-b@' "$NSHIMLOG" || {
1003 echo "e2e FAIL: no-start: mux hosts dialled neither box:"; cat "$NSHIMLOG"; exit 1; } 1008 echo "e2e FAIL: no-start: mux hosts dialled neither box:"; cat "$NSHIMLOG"; exit 1; }
1009 # ...and it ran the READING word. The emptiness above is what a bare
1010 # `mux d endpoint` leaves behind; `--start` here would be a read that
1011 # starts, which is the whole bug, and it would leave the boxes empty only
1012 # by luck of the spawn failing.
1013 NSTARTS=$(grep -c -- '--start' "$NSHIMLOG" || true)
1014 [ "$NSTARTS" -eq 0 ] || {
1015 echo "e2e FAIL: no-start: mux hosts spelled the asking word $NSTARTS times:"
1016 cat "$NSHIMLOG"; exit 1; }
1004 1017
1005 # (2) and (3) in one wall. The wall polls both boxes once a second — three 1018 # (2) and (3) in one wall. The wall polls both boxes once a second — three
1006 # seconds of that must change nothing — and then Enter on ROW 2 asks, which 1019 # seconds of that must change nothing — and then Enter on ROW 2 asks, which
@@ -1036,6 +1049,17 @@ NCB=$(nostart_daemons "$NRUNB")
1036 echo "e2e FAIL: no-start: picker Enter left $NCB mux d run under $NRUNB, want 1"; exit 1; } 1049 echo "e2e FAIL: no-start: picker Enter left $NCB mux d run under $NRUNB, want 1"; exit 1; }
1037 [ -S "$NSOCKB" ] || { 1050 [ -S "$NSOCKB" ] || {
1038 echo "e2e FAIL: no-start: picker Enter started nothing on box B ($NSOCKB)"; exit 1; } 1051 echo "e2e FAIL: no-start: picker Enter started nothing on box B ($NSOCKB)"; exit 1; }
1052 # ONE ask in the whole run, and it is box B's. Three seconds of polling
1053 # both boxes ran the reading word many times over and the asking word
1054 # never; the Enter ran it once. A poll that had inherited the flag would
1055 # show a count here in the tens, and box A would not be empty above.
1056 NASKS=$(grep -c -- '--start' "$NSHIMLOG" || true)
1057 [ "$NASKS" -eq 1 ] || {
1058 echo "e2e FAIL: no-start: the picker leg spelled the asking word $NASKS times, want 1"
1059 grep -- '--start' "$NSHIMLOG"; exit 1; }
1060 grep -q -- 'nostart-b@.*--start' "$NSHIMLOG" || {
1061 echo "e2e FAIL: no-start: the one ask did not go to box B, the row Enter chose:"
1062 grep -- '--start' "$NSHIMLOG"; exit 1; }
1039 # ...and it is a real session, named `0` off the daemon's own empty list. 1063 # ...and it is a real session, named `0` off the daemon's own empty list.
1040 dump_session "$NSOCKB" 0 | grep -q 'ns-born-ok' || { 1064 dump_session "$NSOCKB" 0 | grep -q 'ns-born-ok' || {
1041 echo "e2e FAIL: no-start: box B's session 0 never ran the marker:" 1065 echo "e2e FAIL: no-start: box B's session 0 never ran the marker:"
@@ -1079,11 +1103,19 @@ set -e
1079 echo "e2e FAIL: no-start: the reconnect leg exited $RC:" 1103 echo "e2e FAIL: no-start: the reconnect leg exited $RC:"
1080 cat "$OUT.nspc2"; echo "--- stderr ---"; cat "$OUT.nscap2.err"; exit 1; } 1104 cat "$OUT.nspc2"; echo "--- stderr ---"; cat "$OUT.nscap2.err"; exit 1; }
1081 nostart_empty "$NRUNB" "$NSOCKB" "a picker-born tile reconnecting after mux d stop" 1105 nostart_empty "$NRUNB" "$NSOCKB" "a picker-born tile reconnecting after mux d stop"
1106 # Still ONE ask in the log. `nostart_empty` above says no daemon came back;
1107 # this says no redial even SPELLED the word that could have brought one —
1108 # the outcome and the argv, because a redial that asked and failed would
1109 # leave the box empty too and pass the check above vacuously.
1110 NASKS_3B=$(grep -c -- '--start' "$NSHIMLOG" || true)
1111 [ "$NASKS_3B" -eq 1 ] || {
1112 echo "e2e FAIL: no-start: a reconnect spelled the asking word — log holds $NASKS_3B, want 1"
1113 grep -- '--start' "$NSHIMLOG"; exit 1; }
1082 1114
1083 # (4) A cold `mux HOST` entry, on the box the wall left alone. One daemon, 1115 # (4) A cold `mux HOST` entry, on the box the wall left alone. One daemon,
1084 # and the client said so in its own voice — `mux d start`'s progress reaches 1116 # and the REMOTE said so in its own voice — `mux d endpoint --start`'s
1085 # the user's terminal over the same inherited stderr ssh's own diagnostics 1117 # progress reaches the user's terminal over the same inherited stderr ssh's
1086 # ride on. 1118 # own diagnostics ride on.
1087 pipe_mux "$OUT.nsa" "$OUT.nsa.err" env SHELL=/bin/sh XDG_STATE_HOME="$NSTATE" \ 1119 pipe_mux "$OUT.nsa" "$OUT.nsa.err" env SHELL=/bin/sh XDG_STATE_HOME="$NSTATE" \
1088 PATH="$NPATH" timeout 60 "$MUX" "nostart-a@127.0.0.1" 1120 PATH="$NPATH" timeout 60 "$MUX" "nostart-a@127.0.0.1"
1089 pipe_send 'printf "ns-cold-%%s\\n" ok\n' 1121 pipe_send 'printf "ns-cold-%%s\\n" ok\n'
@@ -1095,9 +1127,25 @@ NCA=$(nostart_daemons "$NRUNA")
1095 cat "$OUT.nsa.err"; exit 1; } 1127 cat "$OUT.nsa.err"; exit 1; }
1096 [ -S "$NSOCKA" ] || { 1128 [ -S "$NSOCKA" ] || {
1097 echo "e2e FAIL: no-start: the cold entry started nothing on box A"; exit 1; } 1129 echo "e2e FAIL: no-start: the cold entry started nothing on box A"; exit 1; }
1098 [ "$(grep -c '^mux d: starting' "$OUT.nsa.err")" = "1" ] || { 1130 [ "$(grep -c '^mux d endpoint: starting' "$OUT.nsa.err")" = "1" ] || {
1099 echo "e2e FAIL: no-start: the cold entry did not run mux d start exactly once:" 1131 echo "e2e FAIL: no-start: the cold entry did not ensure a daemon exactly once:"
1100 cat "$OUT.nsa.err"; exit 1; } 1132 cat "$OUT.nsa.err"; exit 1; }
1133 # ...by ONE ssh run carrying the asking word at box A. The count is
1134 # cumulative over this whole block, so box B's picker ask above is the
1135 # other one.
1136 NASKS_A=$(grep -c -- 'nostart-a@.*--start' "$NSHIMLOG" || true)
1137 [ "$NASKS_A" -eq 1 ] || {
1138 echo "e2e FAIL: no-start: the cold entry spelled the asking word $NASKS_A times at box A, want 1"
1139 grep -- '--start' "$NSHIMLOG"; exit 1; }
1140 # TWO in the whole block, and no more: the picker Enter at box B and this
1141 # entry at box A. Every other line in that log — two `mux hosts` dials, some
1142 # seconds of two-host polling, and every redial of two walls — is the
1143 # reading word. The total is what says the count above is not one ask
1144 # hiding among several.
1145 NASKS_ALL=$(grep -c -- '--start' "$NSHIMLOG" || true)
1146 [ "$NASKS_ALL" -eq 2 ] || {
1147 echo "e2e FAIL: no-start: $NASKS_ALL asks in the whole block, want exactly 2"
1148 grep -- '--start' "$NSHIMLOG"; exit 1; }
1101 # ...and box A goes down by the daemon's own verb, which makes the teardown 1149 # ...and box A goes down by the daemon's own verb, which makes the teardown
1102 # one more assertion rather than a daemon handed to the trap: what a `mux d 1150 # one more assertion rather than a daemon handed to the trap: what a `mux d
1103 # stop` owes on a box somebody started is exactly what an untouched box 1151 # stop` owes on a box somebody started is exactly what an untouched box