Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03eda02c20 | ||
|
|
3b4055017e |
@@ -35,6 +35,18 @@ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
||||||
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}"
|
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}"
|
||||||
|
|
||||||
|
# Normalize the ambient umask so directory modes are a property of the installer
|
||||||
|
# and not of whatever shell invoked it (#1236). Debian/Ubuntu ship umask 002, so
|
||||||
|
# every `mkdir -p` below yielded 0775 — and the fleet env boundary rejects any
|
||||||
|
# managed directory with `mode & 0o022`, which made `mosaic fleet init --write`
|
||||||
|
# impossible on a stock install of those distros. Fedora/RHEL ship 022 and did
|
||||||
|
# not trip it, so the product worked or did not depending on the operator's
|
||||||
|
# login shell. 022 is what this script already assumes it produces: see the
|
||||||
|
# umask note in make_durable_snapshot, which restores to the ambient value
|
||||||
|
# precisely so "every later sync copy and new framework dir" gets 0644/0755.
|
||||||
|
# Now that value is 022 rather than whatever was inherited.
|
||||||
|
umask 022
|
||||||
|
|
||||||
# Deliberately parsed from "$@" (a real, explicit, per-invocation argument) —
|
# Deliberately parsed from "$@" (a real, explicit, per-invocation argument) —
|
||||||
# never an environment variable — so this opt-out can never sit silently
|
# never an environment variable — so this opt-out can never sit silently
|
||||||
# inherited in a shell profile. See #869 Point-1 C2.
|
# inherited in a shell profile. See #869 Point-1 C2.
|
||||||
@@ -696,6 +708,52 @@ sync_framework
|
|||||||
mkdir -p "$TARGET_DIR/memory"
|
mkdir -p "$TARGET_DIR/memory"
|
||||||
mkdir -p "$TARGET_DIR/credentials"
|
mkdir -p "$TARGET_DIR/credentials"
|
||||||
|
|
||||||
|
# Three directories must be 0700, not merely not-group-writable (#1236).
|
||||||
|
# The fleet code guards them with two different masks in two different
|
||||||
|
# languages, and the strict one wins:
|
||||||
|
#
|
||||||
|
# assertPrivateManagedDirectory (fleet-reconciler.js, `mode & 0o077`)
|
||||||
|
# -> MOSAIC_HOME and MOSAIC_HOME/fleet, checked before the roster lock is
|
||||||
|
# taken, so every mutating `mosaic fleet` command dies at 0755.
|
||||||
|
# assert_private_directory (tools/fleet/start-agent-session.sh, `mode & 077`)
|
||||||
|
# -> MOSAIC_HOME/fleet/agents, checked before a pane is ever spawned.
|
||||||
|
#
|
||||||
|
# Their laxer siblings (`mode & 0o022`) accept 0755, which is why normalizing
|
||||||
|
# the umask above is necessary and not sufficient — a correct umask-022 install
|
||||||
|
# still produces 0755 and still cannot run `mosaic fleet init --write`. Say the
|
||||||
|
# strict modes outright rather than inferring them from a umask.
|
||||||
|
#
|
||||||
|
# Only these. The rest of the tree is content, stays 0755, and is only ever
|
||||||
|
# reached by the 0o022 checks, which 0755 satisfies.
|
||||||
|
chmod 700 "$TARGET_DIR" 2>/dev/null || \
|
||||||
|
warn "Could not set 0700 on $TARGET_DIR — 'mosaic fleet' mutations will fail as unsafe-permissions."
|
||||||
|
if [[ -d "$TARGET_DIR/fleet" ]]; then
|
||||||
|
chmod 700 "$TARGET_DIR/fleet" 2>/dev/null || \
|
||||||
|
warn "Could not set 0700 on $TARGET_DIR/fleet — 'mosaic fleet' mutations will fail as unsafe-permissions."
|
||||||
|
fi
|
||||||
|
# fleet/agents does not exist on a first install — the CLI creates it 0700 on
|
||||||
|
# demand. It is chmod'd here for the UPGRADE case: a tree built under umask 002
|
||||||
|
# has it at 0775, and the repair sweep below cannot rescue it, because stripping
|
||||||
|
# group/other write from 0755 leaves 0750 and `mode & 077` is still non-zero.
|
||||||
|
if [[ -d "$TARGET_DIR/fleet/agents" ]]; then
|
||||||
|
chmod 700 "$TARGET_DIR/fleet/agents" 2>/dev/null || \
|
||||||
|
warn "Could not set 0700 on $TARGET_DIR/fleet/agents — agent sessions will fail to start as unsafe-permissions."
|
||||||
|
fi
|
||||||
|
# credentials/ holds secrets and was never meant to be group-readable either.
|
||||||
|
# It is not on the fleet boundary, so a failure here breaks nothing — but it is
|
||||||
|
# the one directory where a silently-failed chmod leaves secrets group-readable,
|
||||||
|
# which is precisely the failure worth a line in the output.
|
||||||
|
chmod 700 "$TARGET_DIR/credentials" 2>/dev/null || \
|
||||||
|
warn "Could not set 0700 on $TARGET_DIR/credentials — stored secrets may be readable by other users on this host."
|
||||||
|
|
||||||
|
# Repair an existing tree. The umask above only governs directories this run
|
||||||
|
# creates, so a host installed under umask 002 before this fix keeps its 0775
|
||||||
|
# dirs through every upgrade and stays broken. Strips group/other WRITE only —
|
||||||
|
# never read or execute — so it can repair the boundary violation without
|
||||||
|
# changing who can traverse or read anything. Scoped to directories: file modes
|
||||||
|
# are the manifest's business, not this fix's.
|
||||||
|
find "$TARGET_DIR" -type d -perm /022 -exec chmod go-w {} + 2>/dev/null || true
|
||||||
|
|
||||||
# Reconcile contract files from defaults/ into the framework root: framework-owned
|
# Reconcile contract files from defaults/ into the framework root: framework-owned
|
||||||
# files (CONSTITUTION/AGENTS/STANDARDS) are overwritten every upgrade (a divergent
|
# files (CONSTITUTION/AGENTS/STANDARDS) are overwritten every upgrade (a divergent
|
||||||
# copy is backed up once); user-seeded files (TOOLS) are written on first install only.
|
# copy is backed up once); user-seeded files (TOOLS) are written on first install only.
|
||||||
|
|||||||
@@ -128,14 +128,6 @@ EOF
|
|||||||
sleep 30
|
sleep 30
|
||||||
EOF
|
EOF
|
||||||
chmod 700 "$AGENT_BIN/mosaic"
|
chmod 700 "$AGENT_BIN/mosaic"
|
||||||
# The launcher resolves the roster's runtime against PANE_PATH before it
|
|
||||||
# spawns anything (#1241), so the runtime this projection names has to be
|
|
||||||
# present here even though the fake `mosaic` above never execs it.
|
|
||||||
cat > "$AGENT_BIN/pi" <<'EOF'
|
|
||||||
#!/bin/sh
|
|
||||||
sleep 30
|
|
||||||
EOF
|
|
||||||
chmod 700 "$AGENT_BIN/pi"
|
|
||||||
server_environment_before=$(tmux -L "$TEST_SOCKET" show-environment -g | sort)
|
server_environment_before=$(tmux -L "$TEST_SOCKET" show-environment -g | sort)
|
||||||
server_sessions_before=$(tmux -L "$TEST_SOCKET" list-sessions | sort)
|
server_sessions_before=$(tmux -L "$TEST_SOCKET" list-sessions | sort)
|
||||||
if /usr/bin/env -i HOME="$HOLDER_HOME" PATH=/usr/bin:/bin MOSAIC_HOME="$AGENT_HOME" \
|
if /usr/bin/env -i HOME="$HOLDER_HOME" PATH=/usr/bin:/bin MOSAIC_HOME="$AGENT_HOME" \
|
||||||
|
|||||||
@@ -286,36 +286,6 @@ _build_runtime_bin_prefix() {
|
|||||||
MOSAIC_RUNTIME_BIN_PREFIX=$(_build_runtime_bin_prefix)
|
MOSAIC_RUNTIME_BIN_PREFIX=$(_build_runtime_bin_prefix)
|
||||||
PANE_PATH=${MOSAIC_RUNTIME_BIN_PREFIX:+${MOSAIC_RUNTIME_BIN_PREFIX}:}/usr/local/bin:/usr/bin:/bin
|
PANE_PATH=${MOSAIC_RUNTIME_BIN_PREFIX:+${MOSAIC_RUNTIME_BIN_PREFIX}:}/usr/local/bin:/usr/bin:/bin
|
||||||
|
|
||||||
# #1241. The pane runs `mosaic yolo <runtime>` under PANE_PATH with a cleared
|
|
||||||
# environment. A binary missing from *that* path is a pane that dies in under a
|
|
||||||
# second, inside a session nobody is attached to, with its diagnostic scrolled
|
|
||||||
# into a pane tmux then destroys. Resolve both here, before any effect, where
|
|
||||||
# the failure is still attributable to the thing that caused it.
|
|
||||||
#
|
|
||||||
# `mosaic yolo <runtime>` runs checkRuntime(runtime) and the binary it looks for
|
|
||||||
# is named exactly like the runtime, so resolving the runtime name is the same
|
|
||||||
# question the pane will ask a moment later — asked while an operator can still
|
|
||||||
# see the answer.
|
|
||||||
_resolve_in_pane_path() {
|
|
||||||
PATH="$PANE_PATH" command -v -- "$1" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Exit 69 (EX_UNAVAILABLE): the seat cannot be provided. Distinguished from the
|
|
||||||
# 64 (EX_USAGE) rejections above, which mean the projection itself was bad —
|
|
||||||
# here the data is fine and the host is not ready. Callers tell the individual
|
|
||||||
# cases apart by `code=`, the same way fail_env's many codes share exit 64.
|
|
||||||
fail_launch() {
|
|
||||||
local code="$1"
|
|
||||||
shift
|
|
||||||
echo "ERROR: agent launch aborted: code=${code} agent=${AGENT_NAME} $*" >&2
|
|
||||||
exit 69
|
|
||||||
}
|
|
||||||
|
|
||||||
for required_binary in mosaic "$MOSAIC_AGENT_RUNTIME"; do
|
|
||||||
_resolve_in_pane_path "$required_binary" >/dev/null ||
|
|
||||||
fail_launch missing-binary "'${required_binary}' is not on the pane PATH (${PANE_PATH})"
|
|
||||||
done
|
|
||||||
|
|
||||||
_ensure_claude_workdir_trusted() {
|
_ensure_claude_workdir_trusted() {
|
||||||
local workdir="$1"
|
local workdir="$1"
|
||||||
local resolved
|
local resolved
|
||||||
@@ -414,19 +384,6 @@ if [ -n "$PANE_PID" ]; then
|
|||||||
_start_heartbeat_sidecar "$AGENT_NAME" "$PANE_PID" \
|
_start_heartbeat_sidecar "$AGENT_NAME" "$PANE_PID" \
|
||||||
"$MOSAIC_HEARTBEAT_RUN_DIR" "$MOSAIC_HEARTBEAT_INTERVAL" || \
|
"$MOSAIC_HEARTBEAT_RUN_DIR" "$MOSAIC_HEARTBEAT_INTERVAL" || \
|
||||||
echo "WARNING: heartbeat sidecar could not be started for $AGENT_NAME" >&2
|
echo "WARNING: heartbeat sidecar could not be started for $AGENT_NAME" >&2
|
||||||
elif _tmux has-session -t "=${AGENT_NAME}:0.0" 2>/dev/null; then
|
|
||||||
# #1241. Session present, no pane PID after a second of retries. Whatever this
|
|
||||||
# is, it is not a seat an operator can use, so it is not a success either.
|
|
||||||
fail_launch pane-pid-unresolved \
|
|
||||||
"tmux reports the session but no pane PID after 5 attempts"
|
|
||||||
else
|
else
|
||||||
# #1241. This branch used to print a WARNING about the heartbeat sidecar and
|
echo "WARNING: could not resolve pane PID for $AGENT_NAME — heartbeat sidecar not started" >&2
|
||||||
# exit 0. It is not a heartbeat problem: tmux destroys a session when its pane
|
|
||||||
# command exits, so an absent session one second after new-session means the
|
|
||||||
# runtime died on startup. Reporting it as success is what let `fleet start`
|
|
||||||
# return 0 over three dead panes — the launcher knew, and said the wrong thing
|
|
||||||
# at the wrong severity to the wrong layer.
|
|
||||||
fail_launch pane-did-not-survive \
|
|
||||||
"the pane exited immediately and tmux destroyed the session;" \
|
|
||||||
"run 'mosaic yolo ${MOSAIC_AGENT_RUNTIME}' in ${MOSAIC_AGENT_WORKDIR} to see why"
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -23,26 +23,8 @@ index=0
|
|||||||
if [ "${args[0]:-}" = -L ]; then index=2; fi
|
if [ "${args[0]:-}" = -L ]; then index=2; fi
|
||||||
case "${args[$index]:-}" in
|
case "${args[$index]:-}" in
|
||||||
has-session)
|
has-session)
|
||||||
# The holder always answers. MOSAIC_TEST_HELD_SESSIONS lets a case add
|
|
||||||
# other targets that should answer too — without it there is no way to
|
|
||||||
# model "tmux still reports the session" for a non-holder agent, and the
|
|
||||||
# launcher's pane-pid-unresolved branch is unreachable from this harness.
|
|
||||||
#
|
|
||||||
# A listed target answers only AFTER new-session, because the launcher asks
|
|
||||||
# this question twice about the same name: once before launching, where a
|
|
||||||
# yes means "already running, nothing to do, exit 0", and once after, where
|
|
||||||
# a yes means "the session survived". A shim that answered yes to both
|
|
||||||
# would short-circuit at the first and never reach the branch under test —
|
|
||||||
# it would look like coverage and measure the idempotency path instead.
|
|
||||||
for argument in "${args[@]}"; do
|
for argument in "${args[@]}"; do
|
||||||
[ "$argument" = '=_holder:0.0' ] && exit 0
|
[ "$argument" = '=_holder:0.0' ] && exit 0
|
||||||
case " ${MOSAIC_TEST_HELD_SESSIONS:-} " in
|
|
||||||
*" $argument "*)
|
|
||||||
if tr '\0' '\n' < "${MOSAIC_TEST_TMUX_CALLS:?}" | grep -qxF new-session; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
@@ -80,30 +62,6 @@ env -0 > "${MOSAIC_HOME:?}/fleet/pane-environment"
|
|||||||
SHIM
|
SHIM
|
||||||
chmod +x "$FAKE_BIN/mosaic"
|
chmod +x "$FAKE_BIN/mosaic"
|
||||||
|
|
||||||
# The runtime the rosters below name. The launcher resolves it against PANE_PATH
|
|
||||||
# before spawning (#1241), so it has to exist somewhere the pane would find it —
|
|
||||||
# not merely on the launcher's own PATH.
|
|
||||||
printf '#!/usr/bin/env bash\nexit 0\n' > "$FAKE_BIN/pi"
|
|
||||||
chmod +x "$FAKE_BIN/pi"
|
|
||||||
|
|
||||||
# PANE_PATH is derived partly from `npm config get prefix`. Left to the real npm
|
|
||||||
# it would splice whatever the host has installed into the path under test, and
|
|
||||||
# the missing-binary cases below would pass or fail by accident of the machine.
|
|
||||||
cat > "$FAKE_BIN/npm" <<'SHIM'
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
printf '%s\n' "${MOSAIC_TEST_NPM_PREFIX:-/nonexistent}"
|
|
||||||
SHIM
|
|
||||||
chmod +x "$FAKE_BIN/npm"
|
|
||||||
|
|
||||||
# PANE_PATH always ends in the system path. A host that installs these there can
|
|
||||||
# not measure the missing-binary cases at all, and a green run would mean
|
|
||||||
# nothing — so say so instead of passing.
|
|
||||||
for host_binary in mosaic pi; do
|
|
||||||
if PATH=/usr/local/bin:/usr/bin:/bin command -v "$host_binary" >/dev/null 2>&1; then
|
|
||||||
fail "host provides '$host_binary' in the system path; missing-binary cases are not measurable here"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
write_generated() {
|
write_generated() {
|
||||||
local home="$1"
|
local home="$1"
|
||||||
local agent="$2"
|
local agent="$2"
|
||||||
@@ -123,19 +81,6 @@ MOSAIC_TMUX_SOCKET=mosaic-test
|
|||||||
EOF
|
EOF
|
||||||
chmod 600 "$home/fleet/agents/$agent.env.generated"
|
chmod 600 "$home/fleet/agents/$agent.env.generated"
|
||||||
mkdir -p "$home/work"
|
mkdir -p "$home/work"
|
||||||
install_pane_binaries "$home"
|
|
||||||
}
|
|
||||||
|
|
||||||
# `$PANE_HOME/.npm-global/bin` is one of the prefixes the launcher folds into
|
|
||||||
# PANE_PATH, so this is the pane's own view of "installed", distinct from the
|
|
||||||
# launcher's PATH. Tests that need a binary *absent* remove it from here.
|
|
||||||
install_pane_binaries() {
|
|
||||||
local pane_home="$1"
|
|
||||||
mkdir -p "$pane_home/.npm-global/bin"
|
|
||||||
local binary
|
|
||||||
for binary in mosaic pi; do
|
|
||||||
ln -sf "$FAKE_BIN/$binary" "$pane_home/.npm-global/bin/$binary"
|
|
||||||
done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run_start() {
|
run_start() {
|
||||||
@@ -143,7 +88,6 @@ run_start() {
|
|||||||
local agent="$2"
|
local agent="$2"
|
||||||
HOME="$home" PATH="$FAKE_BIN:$PATH" MOSAIC_TEST_TMUX_CALLS="$TMUX_CALLS" \
|
HOME="$home" PATH="$FAKE_BIN:$PATH" MOSAIC_TEST_TMUX_CALLS="$TMUX_CALLS" \
|
||||||
MOSAIC_TEST_PANE_PID="${MOSAIC_TEST_PANE_PID:-}" \
|
MOSAIC_TEST_PANE_PID="${MOSAIC_TEST_PANE_PID:-}" \
|
||||||
MOSAIC_TEST_HELD_SESSIONS="${MOSAIC_TEST_HELD_SESSIONS:-}" \
|
|
||||||
MOSAIC_TEST_HOME="$home" \
|
MOSAIC_TEST_HOME="$home" \
|
||||||
MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \
|
MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \
|
||||||
MOSAIC_HOME="$home" "$START" "$agent"
|
MOSAIC_HOME="$home" "$START" "$agent"
|
||||||
@@ -154,10 +98,7 @@ run_start() {
|
|||||||
HOME_VALID="$ROOT/valid"
|
HOME_VALID="$ROOT/valid"
|
||||||
AGENT_VALID="coder0"
|
AGENT_VALID="coder0"
|
||||||
write_generated "$HOME_VALID" "$AGENT_VALID"
|
write_generated "$HOME_VALID" "$AGENT_VALID"
|
||||||
# A live pane PID is part of what "valid launch" means. Until #1241 this case
|
run_start "$HOME_VALID" "$AGENT_VALID"
|
||||||
# ran with none, so the suite's one success path was itself a dead pane the
|
|
||||||
# launcher reported as fine.
|
|
||||||
MOSAIC_TEST_PANE_PID=$$ run_start "$HOME_VALID" "$AGENT_VALID"
|
|
||||||
valid_args=$(tr '\0' '\n' < "$TMUX_CALLS")
|
valid_args=$(tr '\0' '\n' < "$TMUX_CALLS")
|
||||||
echo "$valid_args" | grep -qF new-session || fail "valid generated projection did not reach tmux"
|
echo "$valid_args" | grep -qF new-session || fail "valid generated projection did not reach tmux"
|
||||||
echo "$valid_args" | grep -qF 'mosaic' || fail "fixed mosaic launcher command missing"
|
echo "$valid_args" | grep -qF 'mosaic' || fail "fixed mosaic launcher command missing"
|
||||||
@@ -304,13 +245,6 @@ PANE_BASH_ENV="$ROOT/pane-boundary.bash-env"
|
|||||||
printf 'MOSAIC_RUNTIME_BIN=%s\n' "$FAKE_BIN" > \
|
printf 'MOSAIC_RUNTIME_BIN=%s\n' "$FAKE_BIN" > \
|
||||||
"$HOME_PANE_BOUNDARY/fleet/agents/coder-pane-boundary.env.local"
|
"$HOME_PANE_BOUNDARY/fleet/agents/coder-pane-boundary.env.local"
|
||||||
chmod 600 "$HOME_PANE_BOUNDARY/fleet/agents/coder-pane-boundary.env.local"
|
chmod 600 "$HOME_PANE_BOUNDARY/fleet/agents/coder-pane-boundary.env.local"
|
||||||
# This case does not go through run_start, so its pane binaries come from
|
|
||||||
# MOSAIC_RUNTIME_BIN=$FAKE_BIN in the env.local written above — not from the
|
|
||||||
# symlinks install_pane_binaries planted under the generated home, which this
|
|
||||||
# launcher never consults because HOME here is the trusted parent. That is a
|
|
||||||
# legitimate resolution path, but it means dropping MOSAIC_RUNTIME_BIN from
|
|
||||||
# this case on the belief that the symlinks cover it would break the #1241
|
|
||||||
# binary check rather than exercise it.
|
|
||||||
LD_PRELOAD='/not/loaded/by-clean-bootstrap.so' \
|
LD_PRELOAD='/not/loaded/by-clean-bootstrap.so' \
|
||||||
BASH_ENV="$PANE_BASH_ENV" \
|
BASH_ENV="$PANE_BASH_ENV" \
|
||||||
MOSAIC_UNTRUSTED_SENTINEL='must-not-reach-pane' \
|
MOSAIC_UNTRUSTED_SENTINEL='must-not-reach-pane' \
|
||||||
@@ -324,7 +258,6 @@ PATH="$PANE_STALE_PATH" \
|
|||||||
"MOSAIC_TEST_HOME=$PANE_TRUSTED_HOME" \
|
"MOSAIC_TEST_HOME=$PANE_TRUSTED_HOME" \
|
||||||
MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \
|
MOSAIC_TEST_FLEET_OWNER=123e4567-e89b-12d3-a456-426614174000 \
|
||||||
MOSAIC_TEST_EXECUTE_PANE=1 \
|
MOSAIC_TEST_EXECUTE_PANE=1 \
|
||||||
"MOSAIC_TEST_PANE_PID=$$" \
|
|
||||||
"$START" coder-pane-boundary
|
"$START" coder-pane-boundary
|
||||||
pane_args=$(tr '\0' '\n' < "$TMUX_CALLS")
|
pane_args=$(tr '\0' '\n' < "$TMUX_CALLS")
|
||||||
echo "$pane_args" | grep -qxF "HOME=$PANE_TRUSTED_HOME" || \
|
echo "$pane_args" | grep -qxF "HOME=$PANE_TRUSTED_HOME" || \
|
||||||
@@ -459,75 +392,6 @@ echo "$interaction_policy_args" | grep -qF 'new-session' && \
|
|||||||
echo "$output" | grep -qF 'operator interaction service requires runtime pi' || \
|
echo "$output" | grep -qF 'operator interaction service requires runtime pi' || \
|
||||||
fail "interaction pinned-policy check did not follow strict parsing"
|
fail "interaction pinned-policy check did not follow strict parsing"
|
||||||
|
|
||||||
# #1241. The pane runs `mosaic yolo <runtime>` against PANE_PATH. A binary
|
|
||||||
# missing from that path is a launch failure, and it has to be named before the
|
|
||||||
# session is created — after it, the diagnostic dies with the pane.
|
|
||||||
assert_missing_pane_binary_rejected() {
|
|
||||||
local binary="$1"
|
|
||||||
local home="$ROOT/missing-$binary"
|
|
||||||
local agent="coder-missing-$binary"
|
|
||||||
write_generated "$home" "$agent"
|
|
||||||
rm -f "$home/.npm-global/bin/$binary"
|
|
||||||
|
|
||||||
: > "$TMUX_CALLS"
|
|
||||||
local output
|
|
||||||
if output=$(MOSAIC_TEST_PANE_PID=$$ run_start "$home" "$agent" 2>&1); then
|
|
||||||
fail "launch succeeded with '$binary' absent from the pane PATH"
|
|
||||||
fi
|
|
||||||
echo "$output" | grep -qF 'code=missing-binary' || fail "missing '$binary' diagnostic missing"
|
|
||||||
echo "$output" | grep -qF "'$binary'" || fail "missing-binary diagnostic did not name $binary"
|
|
||||||
if tr '\0' '\n' < "$TMUX_CALLS" | grep -qF new-session; then
|
|
||||||
fail "launcher created a session it knew would die ($binary absent)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_missing_pane_binary_rejected mosaic
|
|
||||||
assert_missing_pane_binary_rejected pi
|
|
||||||
|
|
||||||
# #1241. tmux destroys a session when its pane command exits, so no pane PID a
|
|
||||||
# second after new-session means the runtime died on startup. This used to be a
|
|
||||||
# WARNING about the heartbeat sidecar followed by exit 0 — three layers above it
|
|
||||||
# then reported a fleet that was not running.
|
|
||||||
: > "$TMUX_CALLS"
|
|
||||||
HOME_DEAD_PANE="$ROOT/dead-pane"
|
|
||||||
write_generated "$HOME_DEAD_PANE" "coder-dead-pane"
|
|
||||||
if output=$(MOSAIC_TEST_PANE_PID='' run_start "$HOME_DEAD_PANE" coder-dead-pane 2>&1); then
|
|
||||||
fail "launcher reported success over a pane that did not survive"
|
|
||||||
fi
|
|
||||||
echo "$output" | grep -qF 'code=pane-did-not-survive' || fail "dead-pane diagnostic missing"
|
|
||||||
if echo "$output" | grep -qiF 'heartbeat'; then
|
|
||||||
fail "dead pane is still being reported as a heartbeat-sidecar problem"
|
|
||||||
fi
|
|
||||||
tr '\0' '\n' < "$TMUX_CALLS" | grep -qF new-session || \
|
|
||||||
fail "dead-pane case did not reach the launch it is measuring"
|
|
||||||
|
|
||||||
# #1241, the other way a pane fails. Above, tmux destroyed the session and
|
|
||||||
# has-session said so. Here the session is still there and no PID comes back
|
|
||||||
# after the retries — a different fault (the pane is alive but unusable, or
|
|
||||||
# tmux is answering inconsistently) that an operator has to be told apart from
|
|
||||||
# a runtime that died on startup.
|
|
||||||
#
|
|
||||||
# This case exists because the branch that handles it shipped with nothing able
|
|
||||||
# to reach it: the shim answered has-session only for the holder, so every
|
|
||||||
# non-holder agent landed in the session-is-gone branch no matter what. A
|
|
||||||
# defensive branch nothing exercises is the same shape as the bug this whole
|
|
||||||
# change is about, one layer down.
|
|
||||||
: > "$TMUX_CALLS"
|
|
||||||
HOME_NO_PID="$ROOT/pane-no-pid"
|
|
||||||
write_generated "$HOME_NO_PID" "coder-no-pid"
|
|
||||||
if output=$(MOSAIC_TEST_PANE_PID='' MOSAIC_TEST_HELD_SESSIONS='=coder-no-pid:0.0' \
|
|
||||||
run_start "$HOME_NO_PID" coder-no-pid 2>&1); then
|
|
||||||
fail "launcher reported success over a session with no resolvable pane PID"
|
|
||||||
fi
|
|
||||||
echo "$output" | grep -qF 'code=pane-pid-unresolved' || \
|
|
||||||
fail "session-present/no-PID was not reported as pane-pid-unresolved: $output"
|
|
||||||
if echo "$output" | grep -qF 'code=pane-did-not-survive'; then
|
|
||||||
fail "a session tmux still reports was diagnosed as a destroyed session"
|
|
||||||
fi
|
|
||||||
if echo "$output" | grep -qiF 'heartbeat'; then
|
|
||||||
fail "an unresolvable pane PID is still being reported as a heartbeat-sidecar problem"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Exact stop derives the socket exclusively from the validated generated
|
# Exact stop derives the socket exclusively from the validated generated
|
||||||
# projection and ignores an ambient socket supplied by the caller.
|
# projection and ignores an ambient socket supplied by the caller.
|
||||||
: > "$TMUX_CALLS"
|
: > "$TMUX_CALLS"
|
||||||
|
|||||||
Reference in New Issue
Block a user