Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dc35e5bf2 | ||
|
|
c7ee3cb5ce | ||
|
|
a972249a2b |
@@ -268,6 +268,16 @@ esac
|
|||||||
_build_runtime_bin_prefix() {
|
_build_runtime_bin_prefix() {
|
||||||
local candidates=()
|
local candidates=()
|
||||||
if [ -n "$MOSAIC_RUNTIME_BIN" ]; then candidates+=("$MOSAIC_RUNTIME_BIN"); fi
|
if [ -n "$MOSAIC_RUNTIME_BIN" ]; then candidates+=("$MOSAIC_RUNTIME_BIN"); fi
|
||||||
|
# A host with no system Node gets one bootstrapped here by tools/install.sh, which
|
||||||
|
# records it in ~/.profile. The fleet unit runs `env -i ... bash --noprofile --norc`
|
||||||
|
# by design, so ~/.profile is never read and the directory has to be named here.
|
||||||
|
# The npm probe below cannot cover this: it reports a package prefix
|
||||||
|
# (~/.npm-global), never a Node runtime directory. It sits ahead of the npm probe so
|
||||||
|
# the bootstrapped runtime wins on a host that has both — that is the one the installer
|
||||||
|
# verified — while an explicit MOSAIC_RUNTIME_BIN still outranks it.
|
||||||
|
# Runtime binaries are `#!/usr/bin/env node`, so without this the pane resolves the
|
||||||
|
# binary and then dies on `env: 'node': No such file or directory`.
|
||||||
|
candidates+=("$PANE_HOME/.mosaic/node/current/bin")
|
||||||
if command -v npm >/dev/null 2>&1; then
|
if command -v npm >/dev/null 2>&1; then
|
||||||
local npm_prefix
|
local npm_prefix
|
||||||
npm_prefix=$(npm config get prefix 2>/dev/null) || true
|
npm_prefix=$(npm config get prefix 2>/dev/null) || true
|
||||||
|
|||||||
@@ -348,6 +348,93 @@ for blocked in LD_PRELOAD= BASH_ENV= MOSAIC_UNTRUSTED_SENTINEL=; do
|
|||||||
echo "$pane_environment" | grep -qF "$blocked" && fail "runtime pane received $blocked"
|
echo "$pane_environment" | grep -qF "$blocked" && fail "runtime pane received $blocked"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# #1256. On a host with no system Node, tools/install.sh bootstraps one into
|
||||||
|
# ~/.mosaic/node/ and writes that directory to ~/.profile. The fleet unit runs
|
||||||
|
# `env -i ... bash --noprofile --norc`, so ~/.profile is never read — correctly, by
|
||||||
|
# design — and _build_runtime_bin_prefix does not list the bootstrap directory. Its
|
||||||
|
# `npm config get prefix` branch cannot cover the gap either: the installer points
|
||||||
|
# npm's prefix at ~/.npm-global, so that branch contributes the npm-global directory
|
||||||
|
# and never the Node one, however it resolves.
|
||||||
|
#
|
||||||
|
# The property under test is not "the string is in PATH". It is that the pane can
|
||||||
|
# EXECUTE a Node-shebang runtime binary — which is what `mosaic` is
|
||||||
|
# (`#!/usr/bin/env node`) and what actually failed: measured on a greenfield VM as
|
||||||
|
# `env: 'node': No such file or directory` after a clean install that reported success.
|
||||||
|
#
|
||||||
|
# So this case runs the pane for real and requires it to have run. A PATH-substring
|
||||||
|
# assertion would pass on a fix that put the directory in the wrong position, and it
|
||||||
|
# would keep passing if the pane later stopped running for some unrelated reason.
|
||||||
|
: > "$TMUX_CALLS"
|
||||||
|
HOME_NODE="$ROOT/bootstrap-node/.config/mosaic"
|
||||||
|
write_generated "$HOME_NODE" "coder-node"
|
||||||
|
NODE_PANE_HOME="${HOME_NODE%/.config/mosaic}"
|
||||||
|
NODE_BOOTSTRAP_BIN="$NODE_PANE_HOME/.mosaic/node/current/bin"
|
||||||
|
mkdir -p "$NODE_BOOTSTRAP_BIN"
|
||||||
|
|
||||||
|
# The bootstrapped runtime. It records that it ran, which is the evidence this case
|
||||||
|
# turns on: no node reachable from the pane means no marker.
|
||||||
|
cat > "$NODE_BOOTSTRAP_BIN/node" <<'SHIM'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
env -0 > "${MOSAIC_HOME:?}/fleet/pane-environment"
|
||||||
|
SHIM
|
||||||
|
chmod +x "$NODE_BOOTSTRAP_BIN/node"
|
||||||
|
|
||||||
|
# write_generated plants its symlinks under the MOSAIC_HOME it is given; here the
|
||||||
|
# pane's HOME is the trusted parent, so the pane's view of "installed" is this
|
||||||
|
# directory instead. `pi` is what #1241 resolves against PANE_PATH; `mosaic` is what
|
||||||
|
# the pane then executes, and it is a Node script — not a bash script that would run
|
||||||
|
# anywhere and quietly hide the defect.
|
||||||
|
mkdir -p "$NODE_PANE_HOME/.npm-global/bin"
|
||||||
|
ln -sf "$FAKE_BIN/pi" "$NODE_PANE_HOME/.npm-global/bin/pi"
|
||||||
|
printf '#!/usr/bin/env node\n' > "$NODE_PANE_HOME/.npm-global/bin/mosaic"
|
||||||
|
chmod +x "$NODE_PANE_HOME/.npm-global/bin/mosaic"
|
||||||
|
|
||||||
|
# The npm branch is modelled ALIVE and still cannot close the gap, which is the
|
||||||
|
# stronger statement. An earlier draft of this case tried to model npm as absent —
|
||||||
|
# true on a real bootstrap host, where npm lives only in the Node directory — and it
|
||||||
|
# refused to run anywhere npm is in the system path, i.e. most machines. It was also
|
||||||
|
# the weaker claim: it would have proven only that a dead branch supplies nothing.
|
||||||
|
#
|
||||||
|
# On a bootstrap host the installer sets npm's prefix to ~/.npm-global. So even with
|
||||||
|
# `command -v npm` true and the branch executing, `npm config get prefix` yields the
|
||||||
|
# npm-global directory and never the Node one. The gap does not depend on whether
|
||||||
|
# that branch runs.
|
||||||
|
NODE_LAUNCHER_BIN="$ROOT/bootstrap-node-launcher-bin"
|
||||||
|
mkdir -p "$NODE_LAUNCHER_BIN"
|
||||||
|
ln -sf "$FAKE_BIN/tmux" "$NODE_LAUNCHER_BIN/tmux"
|
||||||
|
ln -sf "$FAKE_BIN/npm" "$NODE_LAUNCHER_BIN/npm"
|
||||||
|
|
||||||
|
/usr/bin/env -i \
|
||||||
|
"HOME=$NODE_PANE_HOME" \
|
||||||
|
"PATH=$NODE_LAUNCHER_BIN:/usr/bin:/bin" \
|
||||||
|
"MOSAIC_HOME=$HOME_NODE" \
|
||||||
|
"MOSAIC_TEST_TMUX_CALLS=$TMUX_CALLS" \
|
||||||
|
"MOSAIC_TEST_HOME=$NODE_PANE_HOME" \
|
||||||
|
"MOSAIC_TEST_NPM_PREFIX=$NODE_PANE_HOME/.npm-global" \
|
||||||
|
MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \
|
||||||
|
MOSAIC_TEST_EXECUTE_PANE=1 \
|
||||||
|
"MOSAIC_TEST_PANE_PID=$$" \
|
||||||
|
"$START" coder-node
|
||||||
|
|
||||||
|
[ -f "$HOME_NODE/fleet/pane-environment" ] || \
|
||||||
|
fail "pane could not execute a Node-shebang runtime: $NODE_BOOTSTRAP_BIN is absent from PANE_PATH (#1256)"
|
||||||
|
node_pane_environment=$(tr '\0' '\n' < "$HOME_NODE/fleet/pane-environment")
|
||||||
|
# Colon-pad and match a whole element. A regex with `(^|:)` after `.*` looks like it
|
||||||
|
# does this and does not: an anchor cannot match mid-pattern, so it silently requires
|
||||||
|
# a leading colon and rejects the directory in FIRST position — which is where THIS
|
||||||
|
# FIXTURE puts it: it runs under `env -i` with no MOSAIC_RUNTIME_BIN, so the bootstrap
|
||||||
|
# directory leads. That is a property of the fixture, not of the fix — in general the
|
||||||
|
# directory sits second, after MOSAIC_RUNTIME_BIN. The colon padding makes the
|
||||||
|
# assertion position-independent either way, which is why it is written this way and
|
||||||
|
# not with an anchor. That produced a failure reading "pane ran but PANE_PATH does not
|
||||||
|
# carry <dir>" against a PATH whose first element was that dir.
|
||||||
|
node_pane_path=":$(printf '%s\n' "$node_pane_environment" | sed -n 's/^PATH=//p' | head -1):"
|
||||||
|
case "$node_pane_path" in
|
||||||
|
*":$NODE_BOOTSTRAP_BIN:"*) ;;
|
||||||
|
*) fail "pane ran but PANE_PATH does not carry $NODE_BOOTSTRAP_BIN (PATH=$node_pane_path)" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
write_interaction_generated() {
|
write_interaction_generated() {
|
||||||
local home="$1"
|
local home="$1"
|
||||||
local agent="$2"
|
local agent="$2"
|
||||||
|
|||||||
@@ -97,34 +97,13 @@ printf '%s' "$MSG" | "${tmux_cmd[@]}" load-buffer -b "$BUF" -
|
|||||||
# would otherwise accumulate forever.
|
# would otherwise accumulate forever.
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
|
|
||||||
# 2) Submit, then POSITIVELY confirm submission by DRAFT TRANSITION, not by prompt
|
# 2) Submit, then POSITIVELY confirm submission; flush with another Enter if it is
|
||||||
# glyph. The historical bug was treating ABSENCE of a draft as delivery; the
|
# still a draft. Success requires positive evidence — the queued banner, OR the
|
||||||
# 2026-08 fix over-corrected to glyph inference (grep '❯|^>|│ >'), which locates
|
# REPL input box located AND clear of our message tail. The historical bug was
|
||||||
# only Claude Code's box and false-NEGATIVES every glyphless REPL (pi renders a
|
# treating ABSENCE of a draft as delivery: if the prompt glyph was never matched
|
||||||
# U+2500 rule, no glyph) — a delivered message reported "UNDELIVERED", driving a
|
# (wrong pane / prompt-glyph drift), an unsubmitted message read as "delivered"
|
||||||
# retry that duplicates it. Runtime-agnostic evidence: our message tail sits on
|
# and worker->lead relays stalled silently. We now default to UNCONFIRMED and only
|
||||||
# the INPUT line (located by the cursor row, not a glyph) BEFORE Enter, and has
|
# upgrade to delivered on positive evidence; anything we cannot confirm fails loud.
|
||||||
# LEFT it AFTER — that transition is positive proof of submission and needs no
|
|
||||||
# glyph. Absence alone still never means delivered: if we never saw our draft on
|
|
||||||
# the input line we stay UNCONFIRMED (wrong/dead pane), and a draft that never
|
|
||||||
# leaves the input line stays a DRAFT (exit 2), preserving both historical guards.
|
|
||||||
_cursor_line() { # echo the pane's current input (cursor) line, glyph-free
|
|
||||||
local cy line
|
|
||||||
cy=$("${tmux_cmd[@]}" display-message -p -t "$EFFECTIVE_TARGET" -F '#{cursor_y}' 2>/dev/null) || return 1
|
|
||||||
[ -n "$cy" ] || return 1
|
|
||||||
"${tmux_cmd[@]}" capture-pane -t "$EFFECTIVE_TARGET" -p 2>/dev/null | sed -n "$((cy + 1))p"
|
|
||||||
}
|
|
||||||
_draft_on_input() { # true iff our message tail is sitting on the input line now
|
|
||||||
[ -n "$snippet" ] || return 1
|
|
||||||
printf '%s' "$(_cursor_line)" | grep -qF "$snippet"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Baseline: after the paste, our draft must be on the input line. This is positive
|
|
||||||
# proof we are on the right pane and the paste landed — the anchor the transition
|
|
||||||
# check measures against.
|
|
||||||
saw_draft=0
|
|
||||||
_draft_on_input && saw_draft=1
|
|
||||||
|
|
||||||
status="unconfirmed"
|
status="unconfirmed"
|
||||||
for attempt in $(seq 1 $((RETRIES + 1))); do
|
for attempt in $(seq 1 $((RETRIES + 1))); do
|
||||||
"${tmux_cmd[@]}" send-keys -t "$EFFECTIVE_TARGET" Enter
|
"${tmux_cmd[@]}" send-keys -t "$EFFECTIVE_TARGET" Enter
|
||||||
@@ -134,26 +113,20 @@ for attempt in $(seq 1 $((RETRIES + 1))); do
|
|||||||
if printf '%s' "$pane" | grep -qF "$QUEUED_RE"; then
|
if printf '%s' "$pane" | grep -qF "$QUEUED_RE"; then
|
||||||
status="queued"; break
|
status="queued"; break
|
||||||
fi
|
fi
|
||||||
# POSITIVE draft evidence from a located prompt box, when one exists. This is the
|
# Locate the REPL input box (prompt glyph). If we cannot see it, we have NO
|
||||||
# cursor-row check's blind spot: a pane in COOKED mode (a plain shell whose
|
# evidence of submission state — stay UNCONFIRMED and retry; never infer delivery.
|
||||||
# foreground process never reads stdin) echoes our paste via the kernel line
|
|
||||||
# discipline and moves the cursor off it on Enter, which is indistinguishable from
|
|
||||||
# a real submit by cursor row alone. If a prompt box IS locatable and still carries
|
|
||||||
# our tail, that is affirmative proof the message was not consumed. Absence of a
|
|
||||||
# glyph is still never used for anything — that inference is the original E7 bug.
|
|
||||||
promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1)
|
promptline=$(printf '%s' "$pane" | grep -E '❯|^>|│ >' | tail -1)
|
||||||
if [ -n "$promptline" ] && [ -n "$snippet" ] && printf '%s' "$promptline" | grep -qF "$snippet"; then
|
if [ -z "$promptline" ]; then
|
||||||
|
status="unconfirmed"; continue
|
||||||
|
fi
|
||||||
|
# Input box located AND still carrying our tail => unsubmitted draft. Flush + retry.
|
||||||
|
# (Submitted messages scroll up into history; a draft stays on the ❯ line.)
|
||||||
|
if [ -n "$snippet" ] && printf '%s' "$promptline" | grep -qF "$snippet"; then
|
||||||
status="draft"; continue
|
status="draft"; continue
|
||||||
fi
|
fi
|
||||||
if [ "$saw_draft" = 1 ]; then
|
# Input box located AND clear of our tail => positively submitted. This is the
|
||||||
if _draft_on_input; then
|
# only path to success besides the queued banner.
|
||||||
status="draft"; continue # still on the input line => not submitted; flush + retry
|
status="delivered"; break
|
||||||
fi
|
|
||||||
status="delivered"; break # left the input line => positively submitted
|
|
||||||
fi
|
|
||||||
# No confirmed baseline yet: try to (re)acquire it; never infer delivery from absence.
|
|
||||||
if _draft_on_input; then saw_draft=1; status="draft"; continue; fi
|
|
||||||
status="unconfirmed"; continue
|
|
||||||
done
|
done
|
||||||
|
|
||||||
[ "$VERBOSE" = 1 ] && { echo "--- pane tail ($TARGET) ---"; printf '%s\n' "$pane" | tail -4; echo "---"; }
|
[ "$VERBOSE" = 1 ] && { echo "--- pane tail ($TARGET) ---"; printf '%s\n' "$pane" | tail -4; echo "---"; }
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Red-first regression test for E7 (#1017 task 2): the confirm-check must bind
|
|
||||||
# "delivered" to WHETHER THE MESSAGE WAS SUBMITTED, not to which runtime's prompt
|
|
||||||
# glyph is present. A pi seat renders a U+2500 rule input box with no ❯/^>/│ >
|
|
||||||
# glyph; send-message.sh:118 locates the box only by glyph, so a genuinely
|
|
||||||
# delivered message on a glyphless REPL falsely reports exit 2 "may be UNDELIVERED",
|
|
||||||
# and the operator's rc=2-driven retry duplicates it.
|
|
||||||
#
|
|
||||||
# Parameterized on $SEND: RED against the shipping blob (B and D fail), GREEN
|
|
||||||
# against a candidate patch. No pi; no fake HOME; hermetic throwaway socket.
|
|
||||||
#
|
|
||||||
# Submission counting is EXACT and terminal-echo-independent: the fixture message
|
|
||||||
# is `echo <tok> >>SINK`; each real submission appends one line. wc -l SINK ==
|
|
||||||
# number of times the REPL actually executed the send. This does not depend on how
|
|
||||||
# many times the marker string is painted on screen.
|
|
||||||
set -u
|
|
||||||
SEND="${SEND:?set SEND=/path/to/send-message.sh}"
|
|
||||||
SOCKET="glyphagnostic-$$"
|
|
||||||
TMP="$(mktemp -d)"
|
|
||||||
tmux() { command tmux -L "$SOCKET" "$@"; }
|
|
||||||
cleanup() { command tmux -L "$SOCKET" kill-server 2>/dev/null; rm -rf "$TMP"; }
|
|
||||||
trap cleanup EXIT
|
|
||||||
pass=0; fail=0
|
|
||||||
ok() { printf 'ok %s\n' "$1"; pass=$((pass+1)); }
|
|
||||||
no() { printf 'FAIL %s -- %s\n' "$1" "$2"; fail=$((fail+1)); }
|
|
||||||
|
|
||||||
mk() { tmux new-session -d -s "$1" -x 120 -y 40 -c "$TMP" "PS1='$2' exec bash --noprofile --norc -i"; sleep 0.5; }
|
|
||||||
subs() { [ -f "$1" ] && wc -l <"$1" | tr -d ' ' || echo 0; } # exact submission count
|
|
||||||
|
|
||||||
echo "SEND=$SEND tmux $(command tmux -V | awk '{print $2}')"
|
|
||||||
|
|
||||||
# --- A (control): glyph box (❯) that submits => exit 0, exactly one submission.
|
|
||||||
mk ctl '❯ '
|
|
||||||
SINK="$TMP/sink.ctl"
|
|
||||||
out=$("$SEND" -L "$SOCKET" -t ctl -m "echo x >>'$SINK'" 2>"$TMP/e.ctl"); rc=$?; sleep 0.4
|
|
||||||
if [ "$rc" = 0 ] && [ "$(subs "$SINK")" = 1 ]; then
|
|
||||||
ok "control: ❯-box submits => exit 0, exactly one submission"
|
|
||||||
else no "control: ❯-box submits => exit 0, one submission" "rc=$rc subs=$(subs "$SINK") err=[$(cat "$TMP/e.ctl")]"; fi
|
|
||||||
|
|
||||||
# --- B (THE false-rc regression): glyphless U+2500 box that SUBMITS. Message lands
|
|
||||||
# (subs==1) yet shipping reports exit 2. Must be exit 0.
|
|
||||||
mk sub $'──────── \n'
|
|
||||||
SINK="$TMP/sink.sub"
|
|
||||||
out=$("$SEND" -L "$SOCKET" -t sub -m "echo x >>'$SINK'" 2>"$TMP/e.sub"); rc=$?; sleep 0.4
|
|
||||||
if [ "$rc" = 0 ] && [ "$(subs "$SINK")" = 1 ]; then
|
|
||||||
ok "glyphless: U+2500 box that submits => exit 0 (delivered, not 'UNDELIVERED')"
|
|
||||||
else no "glyphless: U+2500 box that submits => exit 0" \
|
|
||||||
"rc=$rc subs=$(subs "$SINK")(delivered=$([ "$(subs "$SINK")" -ge 1 ] && echo yes||echo no)) err=[$(cat "$TMP/e.sub")]"; fi
|
|
||||||
|
|
||||||
# --- D (duplicate arm): operator follows the rc=2 stderr and retries once. On the
|
|
||||||
# glyphless box, shipping => two submissions (the reported duplicate). The
|
|
||||||
# property: one logical send => exactly one submission. Same fix closes it.
|
|
||||||
mk dup $'──────── \n'
|
|
||||||
SINK="$TMP/sink.dup"
|
|
||||||
tries=0
|
|
||||||
for attempt in 1 2; do
|
|
||||||
tries=$((tries+1))
|
|
||||||
out=$("$SEND" -L "$SOCKET" -t dup -m "echo x >>'$SINK'" 2>/dev/null); rc=$?
|
|
||||||
sleep 0.4
|
|
||||||
[ "$rc" = 0 ] && break # operator stops retrying only when told delivered
|
|
||||||
done
|
|
||||||
if [ "$(subs "$SINK")" = 1 ]; then
|
|
||||||
ok "duplicate: one logical send (rc-driven retry) => exactly one submission (tries=$tries)"
|
|
||||||
else no "duplicate: one logical send => exactly one submission" "submissions=$(subs "$SINK") tries=$tries"; fi
|
|
||||||
|
|
||||||
# --- E (faithful hung managed TUI, NOT a cooked shell): raw/no-echo, paints nothing.
|
|
||||||
# A cooked `sleep infinity` echoes the paste via the kernel line discipline and
|
|
||||||
# false-passes a cursor-row fix that is correct on real seats (measured). So: raw.
|
|
||||||
mk_rawstuck() { tmux new-session -d -s "$1" -x 120 -y 40 -c "$TMP" \
|
|
||||||
"bash --noprofile --norc -c 'stty -echo -icanon min 1 time 0 2>/dev/null; exec sleep infinity'"; sleep 0.5; }
|
|
||||||
mk_rawstuck estuck
|
|
||||||
SINK="$TMP/sink.estuck"
|
|
||||||
out=$("$SEND" -L "$SOCKET" -t estuck -r 1 -m "this stuck draft was never submitted" 2>/dev/null); rc=$?
|
|
||||||
sleep 0.3
|
|
||||||
if [ "$rc" != 0 ] && [ "$(subs "$SINK")" = 0 ]; then
|
|
||||||
ok "raw/no-echo stuck TUI (not submitted) => non-zero (no false delivered)"
|
|
||||||
else no "raw stuck TUI must NOT report delivered" "rc=$rc subs=$(subs "$SINK")"; fi
|
|
||||||
|
|
||||||
# --- F (busy/queued branch, your BUSY-not-runtime finding): glyphless pane rendering the
|
|
||||||
# queued banner, never consuming. QUEUED_RE :113 fires before the glyph grep => rc=0.
|
|
||||||
mk_busy() { tmux new-session -d -s "$1" -x 120 -y 40 -c "$TMP" \
|
|
||||||
"bash --noprofile --norc -c 'printf \"Press up to edit queued messages\n\"; exec sleep infinity'"; sleep 0.5; }
|
|
||||||
mk_busy ebusy
|
|
||||||
SINK="$TMP/sink.ebusy"
|
|
||||||
out=$("$SEND" -L "$SOCKET" -t ebusy -m "echo x >>'$SINK'" 2>/dev/null); rc=$?; sleep 0.3
|
|
||||||
if [ "$rc" = 0 ]; then
|
|
||||||
ok "busy/queued-banner glyphless => exit 0 (queued is delivery; runtime owns custody)"
|
|
||||||
else no "busy/queued-banner must report delivered" "rc=$rc"; fi
|
|
||||||
|
|
||||||
# --- C (historical-bug guard): unresolvable target. No pane ever carried our draft
|
|
||||||
# => must fail, never infer delivered from absence of a glyph/snippet.
|
|
||||||
if out=$("$SEND" -L "$SOCKET" -t "nonexistent-$$" -m "echo x >>'$TMP/sink.wrong'" 2>/dev/null); then
|
|
||||||
no "wrong-pane: unresolvable target must NOT report success" "expected non-zero, got 0"
|
|
||||||
else ok "wrong-pane: unresolvable target => non-zero (no false delivered)"; fi
|
|
||||||
|
|
||||||
echo "---"; echo "pass=$pass fail=$fail"
|
|
||||||
[ "$fail" = 0 ]
|
|
||||||
@@ -4,13 +4,10 @@
|
|||||||
#
|
#
|
||||||
# 1. DELIVERED — a REPL that renders a `❯ ` input box and submits on Enter
|
# 1. DELIVERED — a REPL that renders a `❯ ` input box and submits on Enter
|
||||||
# (text scrolls to history, box clears) => exit 0 "✓ delivered".
|
# (text scrolls to history, box clears) => exit 0 "✓ delivered".
|
||||||
# 2. DELIVERED — a pane with NO prompt glyph that DOES submit => exit 0. A pi
|
# 2. UNCONFIRMED — a pane with NO locatable prompt glyph. This is the exact
|
||||||
# seat is this fixture (U+2500 rule, no glyph). Reshaped for
|
# historical FALSE POSITIVE: pre-patch it printed "✓ delivered"
|
||||||
# #1257; see the note at the fixture for why the old exit-2
|
# exit 0; post-patch it MUST fail loud (exit 2, stderr
|
||||||
# assertion was wrong.
|
# "could not confirm submission").
|
||||||
# 2b. UNCONFIRMED— a glyphless pane that never submits (raw/no-echo hung TUI)
|
|
||||||
# => must fail loud. This carries the historical
|
|
||||||
# false-positive guard that fixture 2 used to be credited with.
|
|
||||||
# 3. DRAFT — a `❯ `-prompt pane that never submits (message stays on the
|
# 3. DRAFT — a `❯ `-prompt pane that never submits (message stays on the
|
||||||
# input line) => exit 2, stderr "unsubmitted draft".
|
# input line) => exit 2, stderr "unsubmitted draft".
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
@@ -40,44 +37,19 @@ else
|
|||||||
no "delivered: ❯-prompt REPL that submits => exit 0 ✓ delivered" "rc=$rc out=[$out] err=[$(cat "$TMP/e1")]"
|
no "delivered: ❯-prompt REPL that submits => exit 0 ✓ delivered" "rc=$rc out=[$out] err=[$(cat "$TMP/e1")]"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Fixture 2: NO prompt glyph, and the pane DOES submit (interactive bash).
|
# --- Fixture 2: NO prompt glyph (default bash PS1). THE regression: pre-patch this
|
||||||
# RESHAPED 2026-08-16 (#1257), deliberately. This fixture previously asserted
|
# was a silent false-positive "delivered"; post-patch it must be unconfirmed→exit 2.
|
||||||
# exit 2 here and was labelled "false-positive FIXED". That assertion was wrong,
|
|
||||||
# and locking it in is what kept E7 alive: the pane submits, so "delivered" is
|
|
||||||
# the truth, and a pi seat — whose input box is a bare U+2500 rule with no glyph
|
|
||||||
# — IS this fixture. Reporting exit 2 for it told operators a delivered message
|
|
||||||
# may be undelivered, and the retry that advice invites is the duplicate.
|
|
||||||
#
|
|
||||||
# The guard this fixture was reaching for is real and is NOT dropped: "never
|
|
||||||
# infer delivered from absence" is now enforced positively by fixture 2b below
|
|
||||||
# (glyphless AND not submitting => must fail) and by fixture 3 (locatable box
|
|
||||||
# still carrying our tail => draft). Absence alone decides nothing either way.
|
|
||||||
tmux -L "$SOCKET" new-session -d -s noglyph -c "$TMP" \
|
tmux -L "$SOCKET" new-session -d -s noglyph -c "$TMP" \
|
||||||
'PS1="sh-noglyph$ " exec bash --noprofile --norc -i'
|
'PS1="sh-noglyph$ " exec bash --noprofile --norc -i'
|
||||||
sleep 0.3
|
sleep 0.3
|
||||||
out=$("$SEND" -L "$SOCKET" -t "=noglyph" -m "verdict fixture two must fail loud" 2>"$TMP/e2"); rc=$?
|
if out=$("$SEND" -L "$SOCKET" -t "=noglyph" -m "verdict fixture two must fail loud" 2>"$TMP/e2"); then
|
||||||
if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -qF "✓ delivered"; then
|
no "unconfirmed: glyphless pane must NOT report success" "expected exit 2, got 0 (out=[$out])"
|
||||||
ok "delivered: glyphless pane that submits => exit 0 (runtime-agnostic, E7 FIXED)"
|
|
||||||
else
|
|
||||||
no "delivered: glyphless pane that submits => exit 0" "rc=$rc out=[$out] err=[$(cat "$TMP/e2")]"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Fixture 2b: NO prompt glyph AND never submits — a hung managed TUI holding the
|
|
||||||
# terminal in raw/no-echo, which is what a stuck agent seat actually is (measured
|
|
||||||
# on live pi: stty -echo -icanon). Nothing is echoed, nothing is consumed, so
|
|
||||||
# there is no positive evidence of submission and the tool MUST fail loud. This
|
|
||||||
# is the historical false-positive guard, kept as a positive test.
|
|
||||||
tmux -L "$SOCKET" new-session -d -s rawstuck -c "$TMP" \
|
|
||||||
'bash --noprofile --norc -c "stty -echo -icanon min 1 time 0 2>/dev/null; exec sleep infinity"'
|
|
||||||
sleep 0.3
|
|
||||||
if out=$("$SEND" -L "$SOCKET" -t "=rawstuck" -r 1 -m "verdict fixture two-b never submitted" 2>"$TMP/e2b"); then
|
|
||||||
no "unconfirmed: glyphless hung TUI must NOT report success" "expected non-zero, got 0 (out=[$out])"
|
|
||||||
else
|
else
|
||||||
rc=$?
|
rc=$?
|
||||||
if [ "$rc" -ne 0 ] && grep -qF "could not confirm submission" "$TMP/e2b"; then
|
if [ "$rc" -eq 2 ] && grep -qF "could not confirm submission" "$TMP/e2"; then
|
||||||
ok "unconfirmed: glyphless hung TUI (raw/no-echo) => non-zero + 'could not confirm submission'"
|
ok "unconfirmed: glyphless pane => exit 2 + 'could not confirm submission' (false-positive FIXED)"
|
||||||
else
|
else
|
||||||
no "unconfirmed: glyphless hung TUI => non-zero + stderr" "rc=$rc err=[$(cat "$TMP/e2b")]"
|
no "unconfirmed: glyphless pane => exit 2 + stderr" "rc=$rc err=[$(cat "$TMP/e2")]"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user