chore: consolidate new foundation and archive v1 (#1495)
This commit is contained in:
+216
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env bash
|
||||
# CI-fit regression suite for the #1408 legacy-socket guard in
|
||||
# start-agent-session.sh.
|
||||
#
|
||||
# Same hermeticity contract as test-agent-session-broker-preflight.sh: a fake
|
||||
# tmux on PATH that scripts its own answers, a real unix socket in a tmpdir so
|
||||
# the broker preflight passes, env -i with a fake HOME. No case depends on host
|
||||
# state.
|
||||
#
|
||||
# The failure this suite is written down to catch: during a socket cutover a
|
||||
# seat's session still lives on the DEFAULT tmux socket while the launcher
|
||||
# targets the named one. The declared-socket has-session check cannot see the
|
||||
# legacy session (measured 2026-08-24: rc=1, script proceeds), so launch
|
||||
# creates a same-name duplicate — and comms delivery, which addresses sessions
|
||||
# by NAME, cannot tell the two apart. The guard refuses with its own code
|
||||
# (exit 76, after 75 broker-absent) BEFORE any tmux mutation.
|
||||
#
|
||||
# Cases:
|
||||
# 1. legacy session present -> exit 76, message names seat-on-legacy-socket
|
||||
# + both sockets' roles, and NO tmux session was created.
|
||||
# 2. legacy session absent -> proceeds PAST the guard (the run then stops at
|
||||
# a later precondition; asserted: exit != 76, stderr lacks the guard's
|
||||
# code, proving the guard was not the refusal).
|
||||
# 3. MOSAIC_TMUX_SOCKET empty (single-socket host) -> guard is inert: the
|
||||
# default-socket probe must not fire at all.
|
||||
#
|
||||
# Sabotage control, run by the developer (not in-suite): remove the guard
|
||||
# block, re-run — case 1 fails (exit is not 76), cases 2-3 still pass;
|
||||
# restore byte-identically.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WORK_DIR="${MOSAIC_TEST_WORK_DIR:-$PWD/.mosaic-test-work/agent-session-legacy-socket-guard}"
|
||||
FAKE_HOME="$WORK_DIR/home"
|
||||
BIN_DIR="$WORK_DIR/bin"
|
||||
SOCK_DIR="$WORK_DIR/sockets"
|
||||
LOG_FILE="$WORK_DIR/tmux-calls.log"
|
||||
LEGACY_FLAG="$WORK_DIR/legacy-session-present"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$FAKE_HOME/.config/mosaic/fleet/agents" "$BIN_DIR" "$SOCK_DIR"
|
||||
chmod 700 "$FAKE_HOME/.config/mosaic" "$FAKE_HOME/.config/mosaic/fleet/agents"
|
||||
chmod 750 "$FAKE_HOME/.config/mosaic/fleet"
|
||||
cat > "$FAKE_HOME/.config/mosaic/fleet/agents/lsguard-test.env.generated" <<'ENVEOF'
|
||||
MOSAIC_AGENT_NAME=lsguard-test
|
||||
MOSAIC_GIT_IDENTITY=lsguard-test
|
||||
MOSAIC_AGENT_CLASS=worker
|
||||
MOSAIC_AGENT_RUNTIME=pi
|
||||
MOSAIC_AGENT_MODEL=
|
||||
MOSAIC_AGENT_REASONING=
|
||||
MOSAIC_AGENT_TOOL_POLICY=code
|
||||
MOSAIC_AGENT_WORKDIR=/tmp
|
||||
MOSAIC_TMUX_SOCKET=mosaic-fleet
|
||||
ENVEOF
|
||||
chmod 600 "$FAKE_HOME/.config/mosaic/fleet/agents/lsguard-test.env.generated"
|
||||
|
||||
# A projection with NO named socket, for case 3. Same file minus the socket line.
|
||||
sed '/^MOSAIC_TMUX_SOCKET=/d; s/lsguard-test/lsguard-nosock/' \
|
||||
"$FAKE_HOME/.config/mosaic/fleet/agents/lsguard-test.env.generated" \
|
||||
> "$FAKE_HOME/.config/mosaic/fleet/agents/lsguard-nosock.env.generated"
|
||||
echo 'MOSAIC_TMUX_SOCKET=' >> "$FAKE_HOME/.config/mosaic/fleet/agents/lsguard-nosock.env.generated"
|
||||
chmod 600 "$FAKE_HOME/.config/mosaic/fleet/agents/lsguard-nosock.env.generated"
|
||||
|
||||
# Ownership identity the launcher validates before anything touches tmux:
|
||||
# a 0600 uuid file plus a tmux global environment that matches it exactly.
|
||||
mkdir -p "$FAKE_HOME/.config/mosaic/fleet/run"
|
||||
chmod 750 "$FAKE_HOME/.config/mosaic/fleet/run"
|
||||
OWNER_UUID="aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
||||
printf '%s' "$OWNER_UUID" > "$FAKE_HOME/.config/mosaic/fleet/run/holder-owner"
|
||||
chmod 600 "$FAKE_HOME/.config/mosaic/fleet/run/holder-owner"
|
||||
|
||||
# The exact env block assert_owned_tmux_server expects; the socket value differs
|
||||
# per case, so cases rewrite it via write_tmux_env before each run.
|
||||
write_tmux_env() {
|
||||
printf '%s\n' \
|
||||
"HOME=$FAKE_HOME" \
|
||||
'PATH=/usr/bin:/bin' \
|
||||
"PWD=$FAKE_HOME" \
|
||||
"MOSAIC_FLEET_OWNER=$OWNER_UUID" \
|
||||
'MOSAIC_TMUX_HOLDER=_holder' \
|
||||
"MOSAIC_TMUX_SOCKET=$1" > "$WORK_DIR/tmux-env"
|
||||
}
|
||||
|
||||
# ─── Fake tmux ──────────────────────────────────────────────────────────────
|
||||
# Scripted answers: a DEFAULT-socket has-session (argv carries no -L) answers
|
||||
# by the flag file; every named-socket call succeeds (holder present, no
|
||||
# existing session is fine for these cases since refusal happens first).
|
||||
cat > "$BIN_DIR/tmux" <<SH
|
||||
#!/usr/bin/env bash
|
||||
printf 'tmux %s\n' "\$*" >> "$LOG_FILE"
|
||||
if [[ "\$*" == *new-session* ]]; then
|
||||
echo "TMUX-NEW-SESSION-INVOKED" >> "$LOG_FILE"
|
||||
fi
|
||||
if [[ "\$*" == *show-environment* ]]; then
|
||||
cat "$WORK_DIR/tmux-env"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "\$*" == *has-session* ]]; then
|
||||
# holder session always present; the seat's DEFAULT-socket presence is the
|
||||
# flag file; the seat is never already-running on the NAMED socket.
|
||||
[[ "\$*" == *_holder* ]] && exit 0
|
||||
if [[ "\$1" == "-L" ]]; then exit 1; fi
|
||||
[[ -e "$LEGACY_FLAG" ]] && exit 0 || exit 1
|
||||
fi
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "$BIN_DIR/tmux"
|
||||
|
||||
for bin in mosaic pi claude; do
|
||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$BIN_DIR/$bin"
|
||||
chmod +x "$BIN_DIR/$bin"
|
||||
done
|
||||
|
||||
# Real socket so the #1292 broker preflight passes and the run reaches the guard.
|
||||
# Same idiom as the broker-preflight suite: AF_UNIX binds cap at 108 path bytes,
|
||||
# so the socket lives at a SHORT /tmp path held by a detached python holder (a
|
||||
# foreground bind would close on exit; -S on a closed-but-unlinked path fails).
|
||||
LIVE_SOCK="/tmp/mosaic-lsguard-$RANDOM-$$.sock"
|
||||
trap 'rm -f "$LIVE_SOCK"' EXIT
|
||||
rm -f "$LIVE_SOCK"
|
||||
cat > "$SOCK_DIR/holder.py" <<'PY'
|
||||
import socket, sys, time
|
||||
path = sys.argv[1]
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.bind(path)
|
||||
s.listen(1)
|
||||
time.sleep(120)
|
||||
PY
|
||||
python3 "$SOCK_DIR/holder.py" "$LIVE_SOCK" >/dev/null 2>"$SOCK_DIR/holder.err" &
|
||||
for _ in $(seq 1 50); do
|
||||
[ -S "$LIVE_SOCK" ] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[ -S "$LIVE_SOCK" ] || { echo "FAIL: could not create live socket" >&2; exit 1; }
|
||||
|
||||
run_session_script() {
|
||||
local agent="$1"; shift
|
||||
(
|
||||
cd "$WORK_DIR"
|
||||
env -i HOME="$FAKE_HOME" PATH="$BIN_DIR:/usr/bin:/bin" \
|
||||
GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null \
|
||||
MOSAIC_HOME="$FAKE_HOME/.config/mosaic" \
|
||||
MOSAIC_LEASE_BROKER_SOCKET="$LIVE_SOCK" \
|
||||
"$@" \
|
||||
bash "$SCRIPT_DIR/start-agent-session.sh" "$agent"
|
||||
)
|
||||
}
|
||||
|
||||
fail=0
|
||||
assert() {
|
||||
local desc="$1" expected="$2" actual="$3"
|
||||
[[ "$expected" == "$actual" ]] || { echo "FAIL: $desc — expected '$expected', got '$actual'" >&2; fail=1; }
|
||||
}
|
||||
assert_contains() {
|
||||
local desc="$1" haystack="$2" needle="$3"
|
||||
[[ "$haystack" == *"$needle"* ]] || { echo "FAIL: $desc — missing '$needle'" >&2; fail=1; }
|
||||
}
|
||||
assert_not_contains() {
|
||||
local desc="$1" haystack="$2" needle="$3"
|
||||
if [[ "$haystack" == *"$needle"* ]]; then
|
||||
echo "FAIL: $desc — must not contain '$needle'" >&2
|
||||
fail=1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ─── 1. Legacy session present → exit 76, no tmux mutation. ─────────────────
|
||||
write_tmux_env "mosaic-fleet"
|
||||
: > "$LOG_FILE"; touch "$LEGACY_FLAG"
|
||||
stderr_file="$WORK_DIR/stderr-1.tmp"
|
||||
set +e
|
||||
run_session_script lsguard-test >/dev/null 2>"$stderr_file"
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$stderr_file")
|
||||
assert "legacy present exit code" "76" "$rc"
|
||||
assert_contains "names the failure" "$err" "FAIL_LAUNCH seat-on-legacy-socket"
|
||||
assert_contains "names the agent" "$err" "lsguard-test"
|
||||
assert_contains "names the target socket" "$err" "mosaic-fleet"
|
||||
assert_not_contains "no session created" "$(cat "$LOG_FILE")" "TMUX-NEW-SESSION-INVOKED"
|
||||
|
||||
# ─── 2. Legacy session absent → guard is not the refusal. ───────────────────
|
||||
write_tmux_env "mosaic-fleet"
|
||||
: > "$LOG_FILE"; rm -f "$LEGACY_FLAG"
|
||||
stderr_file="$WORK_DIR/stderr-2.tmp"
|
||||
set +e
|
||||
run_session_script lsguard-test >/dev/null 2>"$stderr_file"
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$stderr_file")
|
||||
if [[ "$rc" == "76" ]]; then
|
||||
echo "FAIL: legacy absent must not exit 76" >&2; fail=1
|
||||
fi
|
||||
assert_not_contains "guard code absent from stderr" "$err" "seat-on-legacy-socket"
|
||||
|
||||
# ─── 3. Empty MOSAIC_TMUX_SOCKET → guard inert, no default-socket probe. ────
|
||||
write_tmux_env ""
|
||||
: > "$LOG_FILE"; touch "$LEGACY_FLAG" # even with a legacy session present
|
||||
stderr_file="$WORK_DIR/stderr-3.tmp"
|
||||
set +e
|
||||
run_session_script lsguard-nosock >/dev/null 2>"$stderr_file"
|
||||
rc=$?
|
||||
set -e
|
||||
err=$(cat "$stderr_file")
|
||||
if [[ "$rc" == "76" ]]; then
|
||||
echo "FAIL: empty socket must never exit 76 (single-socket host)" >&2; fail=1
|
||||
fi
|
||||
assert_not_contains "guard code absent on single-socket host" "$err" "seat-on-legacy-socket"
|
||||
|
||||
rm -f "$LEGACY_FLAG"
|
||||
if [[ "$fail" -ne 0 ]]; then
|
||||
echo "start-agent-session legacy-socket guard regression FAILED" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "start-agent-session legacy-socket guard regression passed"
|
||||
Reference in New Issue
Block a user