diff --git a/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt b/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt index fa48e012..a4e88867 100644 --- a/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt +++ b/packages/mosaic/framework/tools/quality/test-enumeration-exclusions.txt @@ -29,6 +29,10 @@ packages/mosaic/framework/tools/git/test-lane-brief-pr-linkage.sh | unmeasured i # --- tools/tmux: require a live tmux server --- packages/mosaic/framework/tools/tmux/test-send-message-socket.sh | requires a real tmux server on a throwaway socket; CI image ships no tmux; #1017 burndown (needs tmux in image or a signed permanent exclusion) packages/mosaic/framework/tools/tmux/test-send-message-verdict.sh | requires real tmux-pane fixtures on a throwaway socket; CI image ships no tmux; #1017 burndown (same condition as its sibling) +# The entry below is NOT covered by the #1017 signature block above: it was +# signed by jarvis-enhance (dragon-lin, 2026-08-24) at a later base, for the +# test added alongside the send-message.sh exact-target fix. +packages/mosaic/framework/tools/tmux/test-send-message-target.sh | requires a real tmux server on a throwaway socket, and specifically a MULTI-WINDOW session (the bug it guards is invisible on a single-window fixture); CI image ships no tmux; same burndown condition as its two siblings above # --- single-suite directories: unmeasured in CI --- diff --git a/packages/mosaic/framework/tools/tmux/send-message.sh b/packages/mosaic/framework/tools/tmux/send-message.sh index 56144d54..84e07bb9 100755 --- a/packages/mosaic/framework/tools/tmux/send-message.sh +++ b/packages/mosaic/framework/tools/tmux/send-message.sh @@ -64,13 +64,31 @@ if [ -n "$SOCKET_NAME" ]; then tmux_cmd+=(-L "$SOCKET_NAME") fi -# tmux accepts `=session` for some commands, but pane-level commands such as -# capture-pane require a pane-qualified target. Keep exact-session addressing -# convenient while avoiding accidental prefix matches. +# Normalise the target to an EXACT session plus a window part, because tmux +# resolves the two halves with different and individually dangerous defaults: +# +# * An unpinned name is a PREFIX match. With a session `foobar` alive and +# no session `foo`, `-t foo` resolves to `foobar` at rc=0, so a message is +# delivered, verified and reported OK against the wrong agent's pane. +# * A bare `=name` is not enough on its own: capture-pane REJECTS it +# ("can't find pane") while list-panes silently PREFIX-MATCHES it, so the +# validation below would pass on a session the capture cannot read. +# * A trailing `:` follows the session's ACTIVE window. Pinning `:0.0` +# instead addresses window 0 unconditionally, and since the paste, the +# Enter and the verifying capture all use EFFECTIVE_TARGET, a multi-window +# agent gets typed into window 0 and confirmed by reading window 0 -- +# a false "delivered" rather than a loud failure. +# +# Explicit tmux ids (%pane, @window, $session) are passed through untouched; +# prefixing `=` to them would break addressing that is already unambiguous. EFFECTIVE_TARGET=$TARGET -if [[ "$TARGET" == =* && "$TARGET" != *:* ]]; then - EFFECTIVE_TARGET="${TARGET}:0.0" -fi +case "$TARGET" in + =*|%*|@*|\$*) ;; + *) EFFECTIVE_TARGET="=$TARGET" ;; +esac +case "$EFFECTIVE_TARGET" in + =*) [[ "$EFFECTIVE_TARGET" == *:* ]] || EFFECTIVE_TARGET="${EFFECTIVE_TARGET}:" ;; +esac # Target must resolve to a live pane. if ! "${tmux_cmd[@]}" list-panes -t "$EFFECTIVE_TARGET" >/dev/null 2>&1; then diff --git a/packages/mosaic/framework/tools/tmux/test-send-message-target.sh b/packages/mosaic/framework/tools/tmux/test-send-message-target.sh new file mode 100755 index 00000000..2cbb12e2 --- /dev/null +++ b/packages/mosaic/framework/tools/tmux/test-send-message-target.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Target normalisation: send-message.sh must address an EXACT session and the +# session's ACTIVE window. Both halves have caused silent wrong-pane delivery: +# * an unpinned name prefix-matches, so a message for an absent session is +# delivered to a different agent and reported OK; +# * a `:0.0` pin addresses window 0 regardless of where the agent is, and +# because the paste, the Enter and the verifying capture share one target, +# the wrong window is also the window that confirms the send. +# Both rows below FAIL against the pre-fix script, which is the point of them. +set -euo pipefail + +SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd) +SEND_MESSAGE="$SCRIPT_DIR/send-message.sh" +SOCKET="mosaic-test-target-$RANDOM-$$" +TMPDIR=$(mktemp -d) +trap 'tmux -L "$SOCKET" kill-server >/dev/null 2>&1 || true; rm -rf "$TMPDIR"' EXIT + +fail() { echo "FAIL: $*" >&2; exit 1; } +command -v tmux >/dev/null 2>&1 || fail "tmux is required" + +tmux_() { tmux -L "$SOCKET" "$@"; } +newsess() { tmux_ new-session -d -s "$1" -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i'; } +hits() { tmux_ capture-pane -p -t "$1" 2>/dev/null | grep -cF "$2" || true; } + +# ── 1. an absent session must not prefix-match a live one ─────────────────── +newsess sibling-long +nonce="absent-target-$RANDOM" +rc=0; "$SEND_MESSAGE" -L "$SOCKET" -t sibling -m "$nonce" >/dev/null 2>&1 || rc=$? +[ "$rc" -ne 0 ] || fail "send to absent session 'sibling' returned rc=0 (prefix-matched)" +[ "$(hits sibling-long "$nonce")" -eq 0 ] || fail "message for absent 'sibling' was delivered to 'sibling-long'" + +# positive control: the detector above can see a real delivery +nonce_ok="control-$RANDOM" +"$SEND_MESSAGE" -L "$SOCKET" -t sibling-long -m "$nonce_ok" >/dev/null 2>&1 \ + || fail "send to a live session failed" +[ "$(hits sibling-long "$nonce_ok")" -gt 0 ] || fail "control: live delivery not observed — detector is blind" + +# ── 2. delivery follows the ACTIVE window, not window 0 ───────────────────── +# A single-window fixture cannot tell `=s:` from `=s:0.0`; the active window +# must be non-zero or this test proves nothing. +newsess multi +tmux_ new-window -t multi -c "$TMPDIR" 'PS1="❯ " exec bash --noprofile --norc -i' +tmux_ select-window -t multi:1 +active=$(tmux_ display-message -p -t multi '#{window_index}') +[ "$active" = "1" ] || fail "fixture setup: expected active window 1, got $active" + +for target in multi "=multi"; do + nonce="active-win-$RANDOM" + "$SEND_MESSAGE" -L "$SOCKET" -t "$target" -m "$nonce" >/dev/null 2>&1 \ + || fail "send to '$target' failed" + [ "$(hits multi:1 "$nonce")" -gt 0 ] || fail "'$target' did not deliver to the active window" + [ "$(hits multi:0 "$nonce")" -eq 0 ] || fail "'$target' delivered to window 0 instead of the active window" +done + +# ── 3. an explicit window part is preserved ───────────────────────────────── +nonce="explicit-win-$RANDOM" +"$SEND_MESSAGE" -L "$SOCKET" -t multi:0 -m "$nonce" >/dev/null 2>&1 || fail "send to 'multi:0' failed" +[ "$(hits multi:0 "$nonce")" -gt 0 ] || fail "explicit 'multi:0' did not deliver to window 0" + +# ── 4. a unique prefix of a live session is still refused ─────────────────── +nonce="prefix-$RANDOM" +rc=0; "$SEND_MESSAGE" -L "$SOCKET" -t mult -m "$nonce" >/dev/null 2>&1 || rc=$? +[ "$rc" -ne 0 ] || fail "send to prefix 'mult' returned rc=0" +[ "$(hits multi:1 "$nonce")" -eq 0 ] || fail "prefix 'mult' was delivered to 'multi'" + +echo "PASS: send-message.sh target normalisation"