agent: lumotia — Phase A.6 dogfood drill for rebrand migration on real OS paths

scripts/dogfood-rebrand-drill.sh — end-to-end probe that launches the real
lumotia binary against synthetic legacy magnotia state on disk, then
verifies both migration paths produced the expected outcome:

  1. paths.rs: ~/.local/share/magnotia/ -> ~/.local/share/lumotia/, including
     magnotia.db -> lumotia.db rename + non-DB companion files carried along
     by the directory rename.
  2. tauri_app_data_migration.rs: ~/.local/share/uk.co.corbel.magnotia/
     copied via atomic staging to ~/.local/share/consulting.corbel.lumotia/,
     with legacy preserved as a backup and staging dir cleaned up.

Closes the last gap in Phase A: every other test (paths::tests + storage
integration test + localStorageMigration.test.ts) uses synthetic in-process
state. The drill is the only verification that the real binary's startup
hook calls migrate_legacy_data_dir + migrate_tauri_app_data_dir_with_paths
against real OS path resolution.

Two modes:
  (default)            Sandbox: HOME=<tempdir>, faithful on Linux. NOT
                       faithful on macOS — Tauri 2 uses
                       NSSearchPathForDirectoriesInDomains which ignores
                       HOME overrides. Drill refuses to start in sandbox
                       mode on macOS rather than silently writing to the
                       user's real Application Support tree.
  --against-real-home  Real $HOME. Refuses to start if any lumotia data
                       already exists at the real paths (no clobbering
                       real user data). Cleans up planted state on exit
                       unless --keep is passed.

Eight probes covering: data-dir rename outcome, db file rename, legacy
removal, companion file survival, Tauri app_data_dir copy, legacy-backup
preservation, staging-dir cleanup, and lumotia_startup log line presence.

README: documents the drill alongside cargo test + npm test in the
Testing section, with the macOS caveat clearly flagged.

Not run as part of this commit — the drill launches a Tauri WebView
window for a few seconds. Jake to invoke when ready to dogfood.
This commit is contained in:
2026-05-14 07:40:33 +01:00
parent 206ac6219d
commit 2aac366f32
2 changed files with 291 additions and 0 deletions

272
scripts/dogfood-rebrand-drill.sh Executable file
View File

@@ -0,0 +1,272 @@
#!/usr/bin/env bash
# Lumotia rebrand-migration dogfood drill.
#
# Launches the real lumotia binary against synthetic legacy magnotia state
# planted on disk, then probes the post-startup state to confirm both
# migration paths ran:
# 1. paths.rs: ~/.local/share/magnotia/ -> ~/.local/share/lumotia/
# magnotia.db -> lumotia.db
# 2. tauri_app_data_migration.rs:
# ~/.local/share/uk.co.corbel.magnotia/ copied via staging to
# ~/.local/share/consulting.corbel.lumotia/ (legacy preserved
# as a backup)
#
# Modes:
# (default) Sandbox. Sets HOME=<tempdir>, plants legacy state
# inside, launches the binary, verifies, tears down.
# Faithful on Linux. NOT faithful on macOS (Tauri 2
# uses NSSearchPathForDirectoriesInDomains which
# ignores HOME overrides and would write to your
# real Application Support tree).
# --against-real-home Real $HOME. Refuses to run if any lumotia data
# already exists at the real paths. Backs up the
# legacy planting before running so cleanup can
# restore your real-home tree to its pre-drill
# state.
#
# Flags:
# --keep Keep the sandbox dir / preserved backups after the
# run for manual inspection.
# --timeout SECS How long to wait for the binary to come up and run
# the migration. Default 20s. Bump on slow hardware
# or when running under a debugger.
# --binary PATH Override the default ./target/debug/lumotia.
#
# Exit codes:
# 0 all probes passed
# 1 a probe failed (migration did not produce the expected on-disk state)
# 2 argument error
# 3 preflight check failed (real-home already has lumotia data, binary
# missing, unsupported platform for sandbox mode, ...)
set -euo pipefail
MODE="sandbox"
KEEP=false
TIMEOUT_SECS=20
BINARY="./target/debug/lumotia"
# ---- arg parsing -----------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--against-real-home) MODE="real-home"; shift ;;
--keep) KEEP=true; shift ;;
--timeout) TIMEOUT_SECS="$2"; shift 2 ;;
--binary) BINARY="$2"; shift 2 ;;
-h|--help)
sed -n '2,33p' "$0" | sed 's/^# \{0,1\}//'
exit 0 ;;
*) echo "Unknown option: $1" >&2; exit 2 ;;
esac
done
# ---- preflight -------------------------------------------------------------
if [[ ! -x "$BINARY" ]]; then
echo "FAIL: binary not found at $BINARY. Run 'cargo build -p lumotia' first." >&2
exit 3
fi
PLATFORM="$(uname -s)"
if [[ "$MODE" == "sandbox" && "$PLATFORM" == "Darwin" ]]; then
echo "FAIL: sandbox mode is not faithful on macOS." >&2
echo " Tauri 2 uses NSSearchPathForDirectoriesInDomains which ignores HOME overrides." >&2
echo " Either run on Linux or use --against-real-home (with backup discipline)." >&2
exit 3
fi
# Real-home mode: refuse to run if the user already has lumotia data on
# disk. We'd risk merging fake planted state into real data on cleanup.
if [[ "$MODE" == "real-home" ]]; then
REAL_HOME="$HOME"
REAL_LUMOTIA_DATA="${XDG_DATA_HOME:-$REAL_HOME/.local/share}/lumotia"
REAL_LUMOTIA_TAURI="${XDG_DATA_HOME:-$REAL_HOME/.local/share}/consulting.corbel.lumotia"
for p in "$REAL_LUMOTIA_DATA" "$REAL_LUMOTIA_TAURI" "$REAL_HOME/.lumotia"; do
if [[ -e "$p" ]]; then
echo "FAIL: $p already exists. Refusing to run in real-home mode against existing data." >&2
echo " Either back it up manually and remove the original, or run in sandbox mode." >&2
exit 3
fi
done
fi
# ---- sandbox setup ---------------------------------------------------------
if [[ "$MODE" == "sandbox" ]]; then
SANDBOX="$(mktemp -d -t lumotia-dogfood-XXXXXX)"
export HOME="$SANDBOX"
unset XDG_DATA_HOME
PLANT_ROOT="$SANDBOX/.local/share"
echo "Sandbox: $SANDBOX (HOME overridden)"
else
SANDBOX="" # signal: don't tear down a sandbox at the end
PLANT_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}"
echo "Real home: planting under $PLANT_ROOT"
fi
LOG_FILE="$(mktemp -t lumotia-dogfood-binary-XXXXXX.log)"
cleanup() {
if [[ -n "${BINARY_PID:-}" ]] && kill -0 "$BINARY_PID" 2>/dev/null; then
kill -TERM "$BINARY_PID" 2>/dev/null || true
sleep 0.5
kill -KILL "$BINARY_PID" 2>/dev/null || true
fi
if [[ "$KEEP" == "false" && -n "$SANDBOX" && -d "$SANDBOX" ]]; then
rm -rf "$SANDBOX"
fi
if [[ "$KEEP" == "false" && "$MODE" == "real-home" ]]; then
# Real-home cleanup: remove planted legacy state AND any
# post-migration artefacts we created. We refused to start if
# any of these existed, so removing them now is safe.
rm -rf \
"$PLANT_ROOT/magnotia" \
"$PLANT_ROOT/uk.co.corbel.magnotia" \
"$PLANT_ROOT/lumotia" \
"$PLANT_ROOT/consulting.corbel.lumotia"
fi
}
trap cleanup EXIT INT TERM
# ---- plant synthetic legacy state -----------------------------------------
mkdir -p "$PLANT_ROOT/magnotia/recordings/2026-05-13"
# Plant a sentinel non-DB file inside the legacy data dir to confirm the
# migration sweeps the whole tree, not just magnotia.db.
echo "fake-wav-bytes-for-dogfood-drill" > "$PLANT_ROOT/magnotia/recordings/2026-05-13/clip.wav"
# An empty file at magnotia.db is enough for the file-level rename probe.
# The Rust integration test (crates/storage/tests/legacy_db_migration.rs)
# covers the "DB still openable after rename" path with a real SQLite;
# this drill is about the LIVE BINARY at startup, not schema integrity.
: > "$PLANT_ROOT/magnotia/magnotia.db"
mkdir -p "$PLANT_ROOT/uk.co.corbel.magnotia/localStorage/leveldb"
echo "sentinel-leveldb-bytes" > "$PLANT_ROOT/uk.co.corbel.magnotia/localStorage/leveldb/000003.log"
echo '{"main":{"x":100,"y":200,"width":1280,"height":720}}' > "$PLANT_ROOT/uk.co.corbel.magnotia/window-state.json"
echo "Planted synthetic legacy state. Launching $BINARY..."
# ---- launch binary in background, give it time to run migrations ----------
# RUST_LOG forces lumotia_startup tracing events to disk regardless of any
# default filter. The setup hook logs `migrated legacy magnotia data dir`
# at info level on a successful rename.
RUST_LOG="lumotia_startup=info,lumotia=info" \
"$BINARY" >"$LOG_FILE" 2>&1 &
BINARY_PID=$!
# Wait for either: the migration log line to appear, OR the timeout.
DEADLINE=$(( $(date +%s) + TIMEOUT_SECS ))
MIGRATED_DATA_DIR_SEEN=false
MIGRATED_TAURI_DIR_SEEN=false
while (( $(date +%s) < DEADLINE )); do
if grep -q "migrated legacy magnotia data dir to lumotia" "$LOG_FILE" 2>/dev/null; then
MIGRATED_DATA_DIR_SEEN=true
fi
if grep -qE "Migrated|migrated.*tauri.*app_data|copied legacy Tauri" "$LOG_FILE" 2>/dev/null; then
MIGRATED_TAURI_DIR_SEEN=true
fi
if [[ "$MIGRATED_DATA_DIR_SEEN" == "true" ]]; then
# Give the Tauri side another moment after the data-dir migration.
sleep 1
break
fi
sleep 0.5
done
# SIGTERM the binary cleanly. We don't need it running for the probes —
# all probes inspect the post-migration filesystem state.
if kill -0 "$BINARY_PID" 2>/dev/null; then
kill -TERM "$BINARY_PID" || true
fi
wait "$BINARY_PID" 2>/dev/null || true
# ---- probes ----------------------------------------------------------------
PROBES_PASSED=0
PROBES_FAILED=0
report_probe() {
local name="$1"; local outcome="$2"; local detail="${3:-}"
if [[ "$outcome" == "PASS" ]]; then
printf ' PASS %s\n' "$name"
PROBES_PASSED=$(( PROBES_PASSED + 1 ))
else
printf ' FAIL %s %s\n' "$name" "$detail"
PROBES_FAILED=$(( PROBES_FAILED + 1 ))
fi
}
printf '\nProbe results:\n'
# 1. Data-dir migration: target exists at the new path.
if [[ -d "$PLANT_ROOT/lumotia" ]]; then
report_probe "data-dir rename produced ~/.local/share/lumotia/" PASS
else
report_probe "data-dir rename produced ~/.local/share/lumotia/" FAIL "(missing)"
fi
# 2. Data-dir migration: lumotia.db is at the new path.
if [[ -f "$PLANT_ROOT/lumotia/lumotia.db" ]]; then
report_probe "magnotia.db renamed to lumotia.db at new path" PASS
else
report_probe "magnotia.db renamed to lumotia.db at new path" FAIL "(missing)"
fi
# 3. Data-dir migration: legacy magnotia tree is gone (rename moved it).
if [[ ! -d "$PLANT_ROOT/magnotia" ]]; then
report_probe "legacy ~/.local/share/magnotia/ removed by rename" PASS
else
report_probe "legacy ~/.local/share/magnotia/ removed by rename" FAIL "(still on disk)"
fi
# 4. Data-dir migration: non-DB companion file carried along.
if [[ -f "$PLANT_ROOT/lumotia/recordings/2026-05-13/clip.wav" ]]; then
report_probe "non-DB companion file carried along by directory rename" PASS
else
report_probe "non-DB companion file carried along by directory rename" FAIL "(missing)"
fi
# 5. Tauri migration: webview/localStorage copied to new bundle id path.
if [[ -f "$PLANT_ROOT/consulting.corbel.lumotia/localStorage/leveldb/000003.log" ]]; then
report_probe "Tauri app_data_dir copied to consulting.corbel.lumotia/" PASS
else
report_probe "Tauri app_data_dir copied to consulting.corbel.lumotia/" FAIL "(localStorage missing)"
fi
# 6. Tauri migration: legacy uk.co.corbel.magnotia preserved as backup.
if [[ -f "$PLANT_ROOT/uk.co.corbel.magnotia/localStorage/leveldb/000003.log" ]]; then
report_probe "legacy Tauri dir preserved as backup" PASS
else
report_probe "legacy Tauri dir preserved as backup" FAIL "(missing — migration should NOT delete legacy)"
fi
# 7. Tauri migration: staging dir cleaned up.
if [[ ! -d "$PLANT_ROOT/consulting.corbel.lumotia.migrating" ]]; then
report_probe "staging dir consulting.corbel.lumotia.migrating cleaned up" PASS
else
report_probe "staging dir consulting.corbel.lumotia.migrating cleaned up" FAIL "(staging leaked)"
fi
# 8. Log line: lumotia_startup migration event appeared.
if [[ "$MIGRATED_DATA_DIR_SEEN" == "true" ]]; then
report_probe "lumotia_startup logged the data-dir migration" PASS
else
report_probe "lumotia_startup logged the data-dir migration" FAIL "(no log line within ${TIMEOUT_SECS}s)"
fi
# ---- summary --------------------------------------------------------------
printf '\nLog file: %s\n' "$LOG_FILE"
if [[ "$KEEP" == "true" && -n "$SANDBOX" ]]; then
printf 'Sandbox preserved at: %s\n' "$SANDBOX"
fi
printf '\nPassed: %s / Failed: %s\n' "$PROBES_PASSED" "$PROBES_FAILED"
if (( PROBES_FAILED > 0 )); then
printf '\nDrill FAILED. Inspect %s for clues.\n' "$LOG_FILE"
exit 1
fi
printf '\nDrill PASSED. Rebrand migration runs end-to-end against real OS paths.\n'