fix(install): preserve user fleet data on re-seed + refresh active units (#631)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

CRITICAL data-loss in the routine update path. `mosaic update` auto-runs
install.sh keep-mode sync (#610); the rsync --delete honored PRESERVE_PATHS but
fleet/ was not listed, so the sync WIPED ~/.config/mosaic/fleet/roster.yaml (and
fleet/run, fleet/agents). Any user running `mosaic update` lost their fleet.

PRIMARY (data-loss):
- install.sh PRESERVE_PATHS += fleet/*.yaml, fleet/agents, fleet/run. The
  framework still SEEDS fleet/examples + fleet/roles + fleet/roster.schema.json
  (synced); the operator's roster, custom rosters, per-agent env, and heartbeat
  run dir are preserved.
- Made the cp (no-rsync) fallback GLOB-AWARE so fleet/*.yaml is preserved there
  too; fixed the restore to re-glob per pattern (restores only the user file,
  not the freshly-synced fleet/ dir).
- file-adapter.ts (TS installer): mirrored the preserve list for dual-installer
  parity. (syncDirectory is copy-only — never --delete — so it never had the
  bug; this is parity + belt-and-suspenders.)

SECONDARY (stale active units):
- refreshActiveFleetUnits(): the re-seed updates ~/.config/mosaic/systemd/user
  but systemd runs ~/.config/systemd/user, so shipped unit fixes (#627) did not
  take effect after update. `mosaic update` now copies the fresh mosaic-*.service
  → the active dir + daemon-reload (best-effort, only when a fleet is installed).

Verified: bash F6 fixture (roster/custom-yaml/agents/run survive + examples
refreshed + schema seeded), 20/20 migration matrix; TS file-adapter keep-mode
test; 2 refreshActiveFleetUnits unit tests. tsc/eslint/prettier/sanitize clean.

Refs #631

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
This commit is contained in:
2026-06-22 15:29:37 -05:00
parent d539d61e0e
commit b55deb4cc3
9 changed files with 216 additions and 13 deletions

View File

@@ -61,7 +61,25 @@ MOSAIC_HOME="$T5" MOSAIC_INSTALL_MODE=bogus MOSAIC_SYNC_ONLY=1 bash "$INSTALL" >
chk "F5 failure: invalid mode rejected (nonzero exit)" "[ $rc -ne 0 ]"
chk "F5 failure: SOUL + credentials intact" "grep -q orig '$T5/SOUL.md' && grep -q keepme '$T5/credentials/c.json'"
rm -rf "$T1" "$T2" "$T3" "$T4" "$T5"
# F6 — keep-mode re-seed (the `mosaic update` path) MUST NOT wipe user fleet data.
# Regression for the roster-loss bug: fleet/ was not in PRESERVE_PATHS.
T6=$(mktemp -d); mkdir -p "$T6/fleet/examples" "$T6/fleet/run" "$T6/fleet/agents"
printf '# persona\n' > "$T6/SOUL.md" # makes it a recognized existing install (→ keep mode)
printf 'version: 1\nagents:\n - name: coder0\n' > "$T6/fleet/roster.yaml"
printf 'version: 1\nagents:\n - name: custom\n' > "$T6/fleet/my-fleet.yaml"
printf 'ts=x\n' > "$T6/fleet/run/coder0.hb"
printf 'MOSAIC_AGENT_NAME=coder0\n' > "$T6/fleet/agents/coder0.env"
printf '# stale preset\n' > "$T6/fleet/examples/general.yaml"
echo 3 > "$T6/.framework-version"
run "$T6" keep
chk "F6 reseed: user roster.yaml SURVIVES keep-mode sync" "grep -q coder0 '$T6/fleet/roster.yaml'"
chk "F6 reseed: other user fleet/*.yaml survives (glob)" "[ -f '$T6/fleet/my-fleet.yaml' ]"
chk "F6 reseed: per-agent env (fleet/agents) survives" "[ -f '$T6/fleet/agents/coder0.env' ]"
chk "F6 reseed: heartbeat run dir (fleet/run) survives" "[ -f '$T6/fleet/run/coder0.hb' ]"
chk "F6 reseed: framework examples ARE refreshed (not preserved stale)" "grep -q orchestrator '$T6/fleet/examples/general.yaml'"
chk "F6 reseed: framework roster.schema.json seeded" "[ -f '$T6/fleet/roster.schema.json' ]"
rm -rf "$T1" "$T2" "$T3" "$T4" "$T5" "$T6"
echo
echo "RESULT: $pass passed, $fail failed"
[ "$fail" -eq 0 ]