a73x

dc0a1f75

docs: simplify-round record; tools/collect.sh — the bundle in one go

a73x   2026-08-13 15:26

Commit message
docs: simplify-round record; tools/collect.sh — the bundle in one go

Five Importants found post-approval, all in the coverage gaps; the
declined-simplifications list so they stay declined; the pipeline-
exit-code doctrine from the re-review's near-miss. collect.sh gathers
versions, key FINGERPRINTS (never bytes), daemon logs, stats, hub
tiles, and per-box state over ssh into one pasteable bundle — the
M17 key-drift hunt, mechanized. First run against the live estate
found the field-trial wall had died unnoticed, which is the point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

docs/decisions.md
Old New
@@ -2981,3 +2981,46 @@ must be painted at the origin (`clear` first); EOF on a piped mux
2981 client aborts the QUIC dial mid-handshake — probes must hold stdin 2981 client aborts the QUIC dial mid-handshake — probes must hold stdin
2982 open and detach with 0x1c, or rc=1 "did not answer" lies about the 2982 open and detach with 0x1c, or rc=1 "did not answer" lies about the
2983 network. 2983 network.
2984
2985 ### M17 simplify round (2026-08-13, post-field-verdict)
2986
2987 Three fresh-eyes reviewers (hub / core+shell / test+build) over the
2988 shipped milestone, then three sequential fix batches, each re-reviewed
2989 by its finder before push. Five Importants survived three prior review
2990 rounds, and every one clustered where the coverage wasn't: the hub's
2991 drain gating (buffered browser bytes invisible to poll — fixed by
2992 draining unconditionally, which also made the drain unit-testable for
2993 the first time), the badge state machine living in the DOM (now a
2994 status field with terminal-state precedence; revive-on-applied-frame
2995 is what makes a restarted session reappear on the wall), macOS metaKey,
2996 the wsclient stand-in re-attaching at the authoritative grid (the
2997 passivity contract's own fixture violating it, latently), and marker
2998 polls that had lost their asserts in copy-editing.
2999
3000 Line-count honesty, recorded because both the fixer and the reviewer
3001 converged on it: the drainBrowser dedup was NOT a line win (+7 code
3002 lines after paying for the seam) — it was a one-owner win whose new
3003 seam produced the stranded-bytes pin at all. Estimates priced bodies;
3004 seams cost signatures.
3005
3006 Declined simplifications, so nobody re-proposes them: sharing the
3007 fixtures' verb loops (three shared names, zero shared actions — an
3008 abstraction tax); merging the native/wasm build blocks (comment-per-
3009 module is the file's organizing style; no drift existed); merging
3010 pump/dialLoop (three concerns interleaved to save lines the two
3011 extractions already saved); std.json for /tiles (it emits invalid-UTF8
3012 slices as integer arrays — argv labels would change shape); the M13
3013 stop scenarios keep their spelled-out teardowns (muxd stop is their
3014 subject; a pin must not flow through machinery other tests can
3015 pressure into loosening).
3016
3017 Process doctrine, banked from the re-review's own near-miss: `make
3018 test 2>&1 | tail` reports TAIL's exit code — a green claim over a
3019 failing build. Capture status before piping. The companion of
3020 "legible catches depend on test order": a catch that prints is
3021 worthless if the harness reads the wrong exit.
3022
3023 Owed cheaply later, filed as observations not blockers: a
3024 handTarget() seam so mux_main's idle_ms threading gets a pin;
3025 verify.js's wall-attach sequence covers the shell's call set but the
3026 page's boot stays browser-only (the banked headless item).
tools/collect.sh
Old New
@@ -0,0 +1,103 @@
1 #!/bin/sh
2 # One-shot diagnostic bundle: everything mux knows how to say about
3 # itself, in one text stream, ordered for reading by a human or a model.
4 #
5 # tools/collect.sh [--sock PATH]... [--host SSHHOST]... [--hub PORT] [--log-lines N]
6 #
7 # Collects: binary versions, the local daemon log, `muxd stats` per
8 # --sock, the hub's /tiles if --hub names a port, and per --host (over
9 # ssh): version, stats, log tail, key FINGERPRINT. Key material itself
10 # is never read — fingerprints are what a key-drift diagnosis needs,
11 # and were what the M17 field trial had to assemble by hand.
12 #
13 # Screen contents are NOT collected: `muxd dump` shows whatever the
14 # session shows, and a diagnostic bundle should be safe to paste.
15 set -u
16
17 LINES=100
18 SOCKS=""; HOSTS=""; HUB=""
19 while [ $# -gt 0 ]; do
20 case "$1" in
21 --sock) SOCKS="$SOCKS $2"; shift 2 ;;
22 --host) HOSTS="$HOSTS $2"; shift 2 ;;
23 --hub) HUB="$2"; shift 2 ;;
24 --log-lines) LINES="$2"; shift 2 ;;
25 *) echo "usage: collect.sh [--sock PATH]... [--host SSHHOST]... [--hub PORT] [--log-lines N]" >&2
26 exit 2 ;;
27 esac
28 done
29
30 section() { printf '\n===== %s =====\n' "$1"; }
31
32 # Every line of the bundle is stamped once, up top: the logs themselves
33 # carry no timestamps (a known gap, on the roadmap), so the collection
34 # instant is the only clock the bundle has.
35 printf 'mux diagnostic bundle: %s on %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$(uname -n)"
36
37 section "versions (local)"
38 for b in muxd mux muxweb; do
39 p=$(command -v "$b" 2>/dev/null) || { echo "$b: not on PATH"; continue; }
40 printf '%s: %s (%s)\n' "$b" "$("$p" --version 2>&1)" "$p"
41 done
42
43 section "local key fingerprint"
44 KEY="${MUX_KEY_FILE:-$HOME/.config/mux/key}"
45 if [ -f "$KEY" ]; then
46 printf '%s %s (%s bytes)\n' "$(sha256sum "$KEY" | cut -c1-16)" "$KEY" "$(wc -c < "$KEY")"
47 else
48 echo "no key at $KEY"
49 fi
50
51 section "local daemon log (last $LINES lines)"
52 LOG="${XDG_STATE_HOME:-$HOME/.local/state}/mux/muxd.log"
53 if [ -f "$LOG" ]; then
54 printf '%s (%s bytes)\n' "$LOG" "$(wc -c < "$LOG")"
55 tail -n "$LINES" "$LOG"
56 else
57 echo "no log at $LOG (a foreground daemon logs to its own stderr)"
58 fi
59
60 section "local daemons (observed, not claimed)"
61 pgrep -a muxd 2>/dev/null || echo "no muxd processes"
62 pgrep -a muxweb 2>/dev/null || echo "no muxweb processes"
63
64 for s in $SOCKS; do
65 section "stats: $s"
66 muxd stats --sock "$s" 2>&1
67 done
68
69 if [ -n "$HUB" ]; then
70 section "hub on 127.0.0.1:$HUB"
71 if command -v curl >/dev/null 2>&1; then
72 printf 'tiles: '; curl -sf "http://127.0.0.1:$HUB/tiles" 2>&1 || echo "(no answer)"
73 printf '\n'
74 else
75 echo "curl not available; skipped"
76 fi
77 fi
78
79 for h in $HOSTS; do
80 section "box: $h"
81 # One ssh round trip per box, everything in one remote shell. BatchMode
82 # so an interactive prompt fails fast instead of hanging the bundle.
83 ssh -o BatchMode=yes -o ConnectTimeout=10 "$h" '
84 printf "version: %s\n" "$(muxd --version 2>&1)"
85 k=~/.config/mux/key
86 if [ -f "$k" ]; then
87 printf "key: %s %s\n" "$(sha256sum "$k" | cut -c1-16)" "$k"
88 else
89 echo "key: none"
90 fi
91 pgrep -a muxd 2>/dev/null || echo "no muxd processes"
92 echo "stats: $(muxd stats 2>&1)"
93 l=${XDG_STATE_HOME:-$HOME/.local/state}/mux/muxd.log
94 if [ -f "$l" ]; then
95 printf "log %s (%s bytes), last '"$LINES"' lines:\n" "$l" "$(wc -c < "$l")"
96 tail -n '"$LINES"' "$l"
97 else
98 echo "no log at $l"
99 fi
100 ' 2>&1 || echo "(ssh to $h failed)"
101 done
102
103 section "end of bundle"