dc0a1f75
docs: simplify-round record; tools/collect.sh — the bundle in one go
a73x 2026-08-13 15:26
Commit message
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" | ||