a73x

5a2ed83f

test: the restored wall creates its local lines and refuses its remote one

a73x   2026-08-25 19:32

Commit message
test: the restored wall creates its local lines and refuses its remote one

One daemon serving both a socket and QUIC, a wall file naming three
sessions it does not have, and a probe before the wall runs so the leg
cannot pass vacuously. After a no-argv `mux wall`, the two --sock lines
exist at the tile's rect and the quic:// one does not exist at all.

test/e2e.sh
Old New
@@ -380,6 +380,22 @@ SOCK60="${TMPDIR:-/tmp}/muxd-e2e-lpheal-$$.sock"
380 SOCK61="${TMPDIR:-/tmp}/muxd-e2e-lpdegrade-$$.sock" 380 SOCK61="${TMPDIR:-/tmp}/muxd-e2e-lpdegrade-$$.sock"
381 SOCK62="${TMPDIR:-/tmp}/muxd-e2e-promptA-$$.sock" 381 SOCK62="${TMPDIR:-/tmp}/muxd-e2e-promptA-$$.sock"
382 SOCK63="${TMPDIR:-/tmp}/muxd-e2e-promptB-$$.sock" 382 SOCK63="${TMPDIR:-/tmp}/muxd-e2e-promptB-$$.sock"
383 # The hydrated-create leg. One daemon, and it serves QUIC as well as its
384 # socket: the leg's whole point is the LOCAL/remote split, so both halves
385 # have to be the same run of the same daemon or the comparison is between
386 # two rigs rather than between two kinds of line. Short socket name on
387 # purpose — the assertion reads a label bar back off a 70-column render.
388 SOCK64="${TMPDIR:-/tmp}/muxd-e2e-hyd-$$.sock"
389 # Base 6000, below the 5000-spaced band the other QUIC ports share: every
390 # base from 11000 up is already taken, and a DUPLICATED base passes only
391 # for as long as the other leg's daemon happens to be dead by the time
392 # this one binds. A collision reads as this daemon's failure, not the
393 # suite's, which is why the base has to be the leg's own.
394 HYPORT=$(( 6000 + ($$ % 4000) ))
395 HYKEY="${TMPDIR:-/tmp}/mux-e2e-hydkey-$$"
396 HYSTATE="${TMPDIR:-/tmp}/mux-e2e-hyd-state-$$"
397 HYWALL="$HYSTATE/mux/wall"
398 D65PID=""
383 D54PID="" 399 D54PID=""
384 D55PID="" 400 D55PID=""
385 D56PID="" 401 D56PID=""
@@ -1335,6 +1351,7 @@ cleanup() {
1335 [ -n "${D62PID:-}" ] && kill "$D62PID" 2>/dev/null || true 1351 [ -n "${D62PID:-}" ] && kill "$D62PID" 2>/dev/null || true
1336 [ -n "${D63PID:-}" ] && kill "$D63PID" 2>/dev/null || true 1352 [ -n "${D63PID:-}" ] && kill "$D63PID" 2>/dev/null || true
1337 [ -n "${D64PID:-}" ] && kill "$D64PID" 2>/dev/null || true 1353 [ -n "${D64PID:-}" ] && kill "$D64PID" 2>/dev/null || true
1354 [ -n "${D65PID:-}" ] && kill "$D65PID" 2>/dev/null || true
1338 # The ssh-agents the forwarding legs start. Not mux processes and so not 1355 # The ssh-agents the forwarding legs start. Not mux processes and so not
1339 # the leak sweep's business, but they are daemons this file forked: left 1356 # the leak sweep's business, but they are daemons this file forked: left
1340 # alive they outlive the suite holding a private key, which is the one 1357 # alive they outlive the suite holding a private key, which is the one
@@ -1387,6 +1404,7 @@ cleanup() {
1387 [ -S "$SOCK59" ] && "$MUXD" stop --sock "$SOCK59" 2>/dev/null || true 1404 [ -S "$SOCK59" ] && "$MUXD" stop --sock "$SOCK59" 2>/dev/null || true
1388 [ -S "$SOCK60" ] && "$MUXD" stop --sock "$SOCK60" 2>/dev/null || true 1405 [ -S "$SOCK60" ] && "$MUXD" stop --sock "$SOCK60" 2>/dev/null || true
1389 [ -S "$SOCK61" ] && "$MUXD" stop --sock "$SOCK61" 2>/dev/null || true 1406 [ -S "$SOCK61" ] && "$MUXD" stop --sock "$SOCK61" 2>/dev/null || true
1407 [ -S "$SOCK64" ] && "$MUXD" stop --sock "$SOCK64" 2>/dev/null || true
1390 1408
1391 # ---- the leak sweep (hygiene kit, 6a) ---- 1409 # ---- the leak sweep (hygiene kit, 6a) ----
1392 # Here rather than at the bottom of the file, which `set -e` reaches only 1410 # Here rather than at the bottom of the file, which `set -e` reaches only
@@ -1403,7 +1421,7 @@ cleanup() {
1403 "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" "$D37PID" \ 1421 "$D32PID" "$D33PID" "$D34PID" "$D35PID" "$D36PID" "$D37PID" \
1404 "$D38PID" "$D39PID" "$D40PID" "$D41PID" "$D42PID" "$D43PID" "$D54PID" \ 1422 "$D38PID" "$D39PID" "$D40PID" "$D41PID" "$D42PID" "$D43PID" "$D54PID" \
1405 "$D55PID" "$D56PID" "$D57PID" "$D58PID" "$D59PID" \ 1423 "$D55PID" "$D56PID" "$D57PID" "$D58PID" "$D59PID" \
1406 "$D60PID" "$D61PID" "$D62PID" "$D63PID" "$D64PID" 1424 "$D60PID" "$D61PID" "$D62PID" "$D63PID" "$D64PID" "$D65PID"
1407 _leak=0 1425 _leak=0
1408 leak_sweep "$_rc" || _leak=1 1426 leak_sweep "$_rc" || _leak=1
1409 1427
@@ -1499,7 +1517,11 @@ cleanup() {
1499 "$OUT.lpdfa" "$OUT.lpdstop" "$SOCK61" \ 1517 "$OUT.lpdfa" "$OUT.lpdstop" "$SOCK61" \
1500 "$OUT.pra.d" "$OUT.prb.d" "$OUT.pra" "$OUT.pra.err" "$OUT.prcap" "$OUT.prcap.err" \ 1518 "$OUT.pra.d" "$OUT.prb.d" "$OUT.pra" "$OUT.pra.err" "$OUT.prcap" "$OUT.prcap.err" \
1501 "$OUT.prgrid" \ 1519 "$OUT.prgrid" \
1502 "$OUT.prpc" "$OUT.prfb" "$OUT.prastop" "$OUT.prbstop" "$SOCK62" "$SOCK63" 1520 "$OUT.prpc" "$OUT.prfb" "$OUT.prastop" "$OUT.prbstop" "$SOCK62" "$SOCK63" \
1521 "$OUT.hyd.d" "$OUT.hydcap" "$OUT.hydcap.err" "$OUT.hydpc" "$OUT.hydgrid" \
1522 "$OUT.hydsta" "$OUT.hydstb" "$OUT.hydghost" "$OUT.hydpre" "$OUT.hydfa" \
1523 "$OUT.hydstop" "$OUT.hydfcap" "$OUT.hydfcap.err" "$OUT.hydfpc" \
1524 "$OUT.hydfold" "$OUT.hydfgrid" "$SOCK64" "$HYKEY"
1503 # ...and the non-tty capture that leg's session feeds. 1525 # ...and the non-tty capture that leg's session feeds.
1504 rm -f "$OUT.nogate" 1526 rm -f "$OUT.nogate"
1505 # ...and its other half: the paste capture and the file nvim wrote, which 1527 # ...and its other half: the paste capture and the file nvim wrote, which
@@ -8510,8 +8532,167 @@ D64PID=""
8510 rm -rf "$PRSTATE" 8532 rm -rf "$PRSTATE"
8511 ok "Ctrl-\\ : adds a tile by spelling: born on another daemon, recorded, refusals narrated, Esc eats the line" 8533 ok "Ctrl-\\ : adds a tile by spelling: born on another daemon, recorded, refusals narrated, Esc eats the line"
8512 8534
8513 [ "$OK_COUNT" = "73" ] || { 8535 # ---- a saved local line comes back as a fresh session -------------------
8514 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 73 —" 8536 #
8537 # The wall file is attach history, and the daemon that held those sessions
8538 # dies on every reboot. Joining only meant the wall came back as a grid of
8539 # [refused] tiles nobody could do anything with but forget them by hand.
8540 # A restored line naming the LOCAL socket now attaches-or-creates.
8541 #
8542 # The remote half is the same wall, the same run and the same daemon,
8543 # reached the other way: a `quic://` line for a session that does not
8544 # exist stays [refused] and creates nothing. Recreating a shell on your
8545 # own box is cheap and expected; spawning one on another host out of a
8546 # saved file is not, and the split is the whole ruling.
8547 #
8548 # Daemon truth, not paint, answers "was it created": `muxa status` on a
8549 # name the daemon does not have exits non-zero, which is the same probe
8550 # the leg runs BEFORE the wall so it cannot pass vacuously. The render
8551 # oracle answers the other question — what the human was looking at —
8552 # because a bar left saying [refused] is invisible to a grep of the
8553 # emitted bytes.
8554 #
8555 # Both roads a restored line can take are here, because they are two call
8556 # sites of the one rule: the no-argv `mux wall` below runs the startup tile
8557 # loop, and the `Ctrl-\ w` half at the end of this leg runs `hydrate()`.
8558 head -c 32 /dev/urandom > "$HYKEY"
8559 chmod 600 "$HYKEY"
8560 "$MUXD" run --sock "$SOCK64" --shell /bin/sh \
8561 --quic "127.0.0.1:$HYPORT" --key "$HYKEY" --quic-idle-ms 15000 > "$OUT.hyd.d" 2>&1 &
8562 D65PID=$!
8563 wait_sock "$SOCK64" "$OUT.hyd.d" "hydrate-create daemon never bound"
8564
8565 # The wall file names three sessions this daemon does not have. Written by
8566 # hand rather than earned by an attach, because an attach that earned the
8567 # line would also have created the session — and then there would be
8568 # nothing for the restore to create.
8569 mkdir -p "$HYSTATE/mux"
8570 printf -- '--sock %s#hya\n--sock %s#hyb\nquic://127.0.0.1:%s#hyghost\n' \
8571 "$SOCK64" "$SOCK64" "$HYPORT" > "$HYWALL"
8572
8573 # Vacuity guard: none of the three exists yet. A leg whose daemon already
8574 # had them would pass without the restore creating anything.
8575 for _hy in hya hyb hyghost; do
8576 timeout 20 "$MUXA" status --sock "$SOCK64" --session "$_hy" > "$OUT.hydpre" 2>&1 && {
8577 echo "e2e FAIL: hydrate-create: session $_hy existed before the wall ran:"
8578 cat "$OUT.hydpre"; exit 1; }
8579 done
8580
8581 # 70x36: under the aspect rule (70 < 2*36) the cut is stacked, so each of
8582 # the three tiles gets the full width and its label bar survives the
8583 # render intact — the socket path is in that label.
8584 set +e
8585 XDG_STATE_HOME="$HYSTATE" timeout 90 "$PTYCLIENT" --cols 70 --rows 36 \
8586 --out "$OUT.hydcap" --err "$OUT.hydcap.err" -- \
8587 "$MUX" wall --key "$HYKEY" > "$OUT.hydpc" 2>&1 <<'EOF'
8588 expect [up] 25000
8589 settle 1000 25000
8590 send printf 'hyd-%s\n' marker\n
8591 expect hyd-marker 15000
8592 settle 600 15000
8593 send \x1cd
8594 waitexit 10000
8595 EOF
8596 RC=$?
8597 set -e
8598 [ "$RC" -eq 0 ] || {
8599 echo "e2e FAIL: hydrate-create: ptyclient exited $RC:"
8600 cat "$OUT.hydpc" "$OUT.hydcap.err"; exit 1; }
8601
8602 # The local lines created their sessions, at the width of the stripe that
8603 # created them. The width does not discriminate create from join on its own
8604 # (a join's resize doorbell lands at 70 too, and the daemon refuses a create
8605 # under min_session_cols) — the muxa exit code above is what says created.
8606 # It is here to catch a session that came up at some OTHER client's size.
8607 timeout 20 "$MUXA" status --sock "$SOCK64" --session hya > "$OUT.hydsta" 2>&1 || {
8608 echo "e2e FAIL: hydrate-create: the saved line for hya did not create it:"
8609 cat "$OUT.hydsta"; exit 1; }
8610 grep -q '"cols":70' "$OUT.hydsta" || {
8611 echo "e2e FAIL: hydrate-create: hya exists but not at the tile's width:"
8612 cat "$OUT.hydsta"; exit 1; }
8613 timeout 20 "$MUXA" status --sock "$SOCK64" --session hyb > "$OUT.hydstb" 2>&1 || {
8614 echo "e2e FAIL: hydrate-create: the saved line for hyb did not create it:"
8615 cat "$OUT.hydstb"; exit 1; }
8616
8617 # ...and the created session runs a real shell: the marker typed into the
8618 # focused tile (tile 0, the first wall line) is the shell's own work.
8619 timeout 20 "$MUXA" capture --sock "$SOCK64" --session hya > "$OUT.hydfa" 2>&1
8620 grep -q "hyd-marker" "$OUT.hydfa" || {
8621 echo "e2e FAIL: hydrate-create: the marker never reached the created session:"
8622 cat "$OUT.hydfa"; exit 1; }
8623
8624 # The remote line created NOTHING. Same daemon, same run, same file — only
8625 # the spelling differs.
8626 timeout 20 "$MUXA" status --sock "$SOCK64" --session hyghost > "$OUT.hydghost" 2>&1 && {
8627 echo "e2e FAIL: hydrate-create: a quic:// wall line created a session:"
8628 cat "$OUT.hydghost"; exit 1; }
8629
8630 # What the human saw: two tiles up, the remote one still refused.
8631 "$RENDER" --cols 70 --rows 36 < "$OUT.hydcap" > "$OUT.hydgrid" || {
8632 echo "e2e FAIL: hydrate-create: render oracle failed"
8633 cat "$OUT.hydgrid"; exit 1; }
8634 grep -q -- "#hya \[up\]" "$OUT.hydgrid" || {
8635 echo "e2e FAIL: hydrate-create: the hya tile never said [up]:"
8636 cat "$OUT.hydgrid"; exit 1; }
8637 grep -q -- "#hyb \[up\]" "$OUT.hydgrid" || {
8638 echo "e2e FAIL: hydrate-create: the hyb tile never said [up]:"
8639 cat "$OUT.hydgrid"; exit 1; }
8640 grep -q -- "#hyghost \[refused\]" "$OUT.hydgrid" || {
8641 echo "e2e FAIL: hydrate-create: the remote tile did not say [refused]:"
8642 cat "$OUT.hydgrid"; exit 1; }
8643 # The OTHER road a restored line takes: `Ctrl-\ w`. A no-argv `mux wall`
8644 # runs the startup tile loop; the fold runs `hydrate()`, a different call
8645 # site of the same rule, and the ruling covers both. Same daemon, same
8646 # state home, one more ptyclient — a `mux TARGET` is the entry tile, and
8647 # the fold brings the rest of the file in around it.
8648 #
8649 # The file is rewritten to name a session this daemon does not have. The
8650 # entry attach then records its OWN line into the same file (attach
8651 # history), which the fold dedupes by spelling — so what the fold has left
8652 # to add is exactly the absent one, and creating it is the whole claim.
8653 printf -- '--sock %s#hyfold\n' "$SOCK64" > "$HYWALL"
8654 timeout 20 "$MUXA" status --sock "$SOCK64" --session hyfold > "$OUT.hydpre" 2>&1 && {
8655 echo "e2e FAIL: hydrate-create: session hyfold existed before the fold ran:"
8656 cat "$OUT.hydpre"; exit 1; }
8657
8658 set +e
8659 XDG_STATE_HOME="$HYSTATE" timeout 90 "$PTYCLIENT" --cols 70 --rows 36 \
8660 --out "$OUT.hydfcap" --err "$OUT.hydfcap.err" -- \
8661 "$MUX" --sock "$SOCK64" --session hya > "$OUT.hydfpc" 2>&1 <<'EOF'
8662 settle 1000 25000
8663 send printf 'hydf-%s\n' pre\n
8664 expect hydf-pre 20000
8665 settle 600 20000
8666 send \x1cw
8667 settle 1500 25000
8668 send \x1cd
8669 waitexit 10000
8670 EOF
8671 RC=$?
8672 set -e
8673 [ "$RC" -eq 0 ] || {
8674 echo "e2e FAIL: hydrate-create: fold ptyclient exited $RC:"
8675 cat "$OUT.hydfpc" "$OUT.hydfcap.err"; exit 1; }
8676
8677 # Daemon truth again: the fold-born tile created the session its line named.
8678 timeout 20 "$MUXA" status --sock "$SOCK64" --session hyfold > "$OUT.hydfold" 2>&1 || {
8679 echo "e2e FAIL: hydrate-create: the folded line for hyfold did not create it:"
8680 cat "$OUT.hydfold"; exit 1; }
8681 # ...and it is a tile on the screen, not merely a session on the daemon.
8682 "$RENDER" --cols 70 --rows 36 < "$OUT.hydfcap" > "$OUT.hydfgrid" || {
8683 echo "e2e FAIL: hydrate-create: fold render oracle failed"
8684 cat "$OUT.hydfgrid"; exit 1; }
8685 grep -q -- "#hyfold \[up\]" "$OUT.hydfgrid" || {
8686 echo "e2e FAIL: hydrate-create: the fold-born tile never said [up]:"
8687 cat "$OUT.hydfgrid"; exit 1; }
8688
8689 assert_stopped "$SOCK64" "$D65PID" "hydrate-create" "$OUT.hydstop"
8690 D65PID=""
8691 rm -rf "$HYSTATE"
8692 ok "a saved local line comes back as a fresh session, by wall and by fold; a remote one joins only"
8693
8694 [ "$OK_COUNT" = "74" ] || {
8695 echo "e2e FAIL: $OK_COUNT scenario checkpoints ran, the pin says 74 —"
8515 echo " a scenario was added (update the pin) or silently lost" 8696 echo " a scenario was added (update the pin) or silently lost"
8516 exit 1 8697 exit 1
8517 } 8698 }