fix(doctor): greenfield brain lock-in note (#1288, fred's finding 1)
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
On a host with ~/.mosaic but no ~/.mosaic/fleet/agents, the implicit resolution path passes silently with output byte-identical to a host with no brain at all. fred traced why this is the shape web1 will be in (#1288 comment 23133): nothing in product code ever creates ~/.mosaic/fleet/agents — the first fleet write resolves BEFORE creating (generated-env-boundary), decides legacy, then manufactures the evidence that keeps the host legacy. The three operator checks all agree and all point the wrong way; the doctor is the only one that can disagree. check_brain_home now emits a [NOTE] (not a WARN — nothing is broken yet) when ~/.mosaic exists at the default config home without fleet/agents, telling the operator the first 'mosaic fleet regen' locks in the legacy tree. Explicit MOSAIC_BRAIN_HOME misconfiguration still warns (behavior unchanged); custom homes stay silent (hermetic). test-brain-home-check.sh: +3 cases (greenfield note, no-mosaic pass, stray-mosaic-at-custom-home silence) — 11 total.
This commit is contained in:
@@ -51,6 +51,8 @@ fix() { fix_count=$((fix_count + 1)); echo "[FIX] $*"; }
|
|||||||
|
|
||||||
warn_count=0
|
warn_count=0
|
||||||
warn() { warn_count=$((warn_count + 1)); echo "[WARN] $*"; }
|
warn() { warn_count=$((warn_count + 1)); echo "[WARN] $*"; }
|
||||||
|
note() { echo "[NOTE] $*"; return 0; }
|
||||||
|
|
||||||
pass() {
|
pass() {
|
||||||
if [[ $VERBOSE -eq 1 ]]; then
|
if [[ $VERBOSE -eq 1 ]]; then
|
||||||
echo "[OK] $*"
|
echo "[OK] $*"
|
||||||
@@ -288,6 +290,18 @@ check_brain_home() {
|
|||||||
brain="$(resolve_brain_home)"
|
brain="$(resolve_brain_home)"
|
||||||
|
|
||||||
if [[ "$brain" == "$MOSAIC_HOME" ]]; then
|
if [[ "$brain" == "$MOSAIC_HOME" ]]; then
|
||||||
|
# Implicit-path greenfield case (#1288 comment 23133, fred's trace): nothing
|
||||||
|
# in product code creates ~/.mosaic/fleet/agents — the first fleet write
|
||||||
|
# resolves legacy (generated-env-boundary resolves before creating) and
|
||||||
|
# then manufactures the evidence that keeps the host legacy. On a host with
|
||||||
|
# ~/.mosaic but no fleet/agents, the three operator checks all agree and all
|
||||||
|
# point the wrong way; this doctor is the only one that can disagree, so it
|
||||||
|
# must say it — as a note, not a warn: nothing is broken yet.
|
||||||
|
if [[ "$(cd "$MOSAIC_HOME" 2>/dev/null && pwd -P)" == "$HOME/.config/mosaic" \
|
||||||
|
&& -d "$HOME/.mosaic" && ! -d "$HOME/.mosaic/fleet/agents" ]]; then
|
||||||
|
note "Fleet state home: $MOSAIC_HOME (legacy). NOTE: ~/.mosaic exists but carries no fleet/agents — the first 'mosaic fleet regen' on this host locks in the legacy tree. Create ~/.mosaic/fleet/agents first to adopt the brain."
|
||||||
|
return
|
||||||
|
fi
|
||||||
pass "Fleet state home: $MOSAIC_HOME (legacy single-tree; no brain adopted)"
|
pass "Fleet state home: $MOSAIC_HOME (legacy single-tree; no brain adopted)"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -39,22 +39,26 @@ eval "$(extract_function check_brain_home)"
|
|||||||
ROOT=$(mktemp -d)
|
ROOT=$(mktemp -d)
|
||||||
trap 'rm -rf "$ROOT"' EXIT
|
trap 'rm -rf "$ROOT"' EXIT
|
||||||
|
|
||||||
|
# note output is neither [OK] nor [WARN] — assert it directly in the case below.
|
||||||
run_case() {
|
run_case() {
|
||||||
# label, expect (ok|warn), then env assignments as arguments.
|
# label, expect (ok|warn|note), then env assignments as arguments.
|
||||||
# The check runs under `env` in a subshell, so its warn() also prints a
|
# The check runs under `env` in a subshell, so its warn() also prints a
|
||||||
# sentinel the parent counts — a subshell counter would never be visible.
|
# sentinel the parent counts — a subshell counter would never be visible.
|
||||||
local label="$1" expect="$2"
|
local label="$1" expect="$2"
|
||||||
shift 2
|
shift 2
|
||||||
local out warns
|
local out warns notes
|
||||||
out=$(env "$@" bash -c "warn() { echo \"[WARN] \$*\"; }; pass() { echo \"[OK] \$*\"; return 0; }; $(extract_function resolve_brain_home); $(extract_function check_brain_home); check_brain_home" 2>&1)
|
out=$(env "$@" bash -c "warn() { echo \"[WARN] \$*\"; }; note() { echo \"[NOTE] \$*\"; return 0; }; pass() { echo \"[OK] \$*\"; return 0; }; $(extract_function resolve_brain_home); $(extract_function check_brain_home); check_brain_home" 2>&1)
|
||||||
warns=$(printf '%s\n' "$out" | grep -c '^\[WARN\]' || true)
|
warns=$(printf '%s\n' "$out" | grep -c '^\[WARN\]' || true)
|
||||||
if [[ "$expect" == ok && "$warns" -eq 0 ]]; then
|
notes=$(printf '%s\n' "$out" | grep -c '^\[NOTE\]' || true)
|
||||||
|
if [[ "$expect" == ok && "$warns" -eq 0 && "$notes" -eq 0 ]]; then
|
||||||
echo "ok - $label"
|
echo "ok - $label"
|
||||||
elif [[ "$expect" == warn && "$warns" -gt 0 ]]; then
|
elif [[ "$expect" == warn && "$warns" -gt 0 ]]; then
|
||||||
echo "ok - $label (warned)"
|
echo "ok - $label (warned)"
|
||||||
|
elif [[ "$expect" == note && "$notes" -gt 0 ]]; then
|
||||||
|
echo "ok - $label (noted)"
|
||||||
else
|
else
|
||||||
echo "output: $out" >&2
|
echo "output: $out" >&2
|
||||||
fail "$label: expected $expect (warns=$warns)"
|
fail "$label: expected $expect (warns=$warns notes=$notes)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,4 +109,19 @@ chmod 700 "$ROOT/brain-clean/fleet/agents" "$ROOT/config6/fleet/agents"
|
|||||||
run_case "empty config-home agents dir alongside brain passes" ok \
|
run_case "empty config-home agents dir alongside brain passes" ok \
|
||||||
MOSAIC_HOME="$ROOT/config6" HOME="$ROOT" MOSAIC_BRAIN_HOME="$ROOT/brain-clean"
|
MOSAIC_HOME="$ROOT/config6" HOME="$ROOT" MOSAIC_BRAIN_HOME="$ROOT/brain-clean"
|
||||||
|
|
||||||
|
# ── greenfield brain-without-agents at the default home → note (#1288) ─────
|
||||||
|
mkdir -p "$ROOT/gf-home/.config/mosaic/fleet" "$ROOT/gf-home/.mosaic"
|
||||||
|
run_case "~/.mosaic without fleet/agents at default home notes the lock-in" note \
|
||||||
|
MOSAIC_HOME="$ROOT/gf-home/.config/mosaic" HOME="$ROOT/gf-home"
|
||||||
|
|
||||||
|
# ── no ~/.mosaic at all at the default home → clean pass ─────────────────
|
||||||
|
mkdir -p "$ROOT/plain-home/.config/mosaic/fleet"
|
||||||
|
run_case "no ~/.mosaic at default home passes silently" ok \
|
||||||
|
MOSAIC_HOME="$ROOT/plain-home/.config/mosaic" HOME="$ROOT/plain-home"
|
||||||
|
|
||||||
|
# ── custom (non-default) home with a stray ~/.mosaic → still silent ──────
|
||||||
|
mkdir -p "$ROOT/custom-home/fleet/agents" "$ROOT/custom-home/.mosaic"
|
||||||
|
run_case "custom home with stray ~/.mosaic stays silent" ok \
|
||||||
|
MOSAIC_HOME="$ROOT/custom-home" HOME="$ROOT/custom-home"
|
||||||
|
|
||||||
echo "ok - mosaic-doctor brain-home check"
|
echo "ok - mosaic-doctor brain-home check"
|
||||||
|
|||||||
Reference in New Issue
Block a user