agent: lumotia — Phase A.3 remove dead migration_sentinel method + fix architecture-map claim

Phase A.3 finding: AppPaths::migration_sentinel was added with intent
during the rebrand-architecture phase but never wired to any caller.
Exhaustive grep across crates/ src-tauri/ src/ docs/ surfaces:

  - 1 definition (paths.rs)
  - 1 architecture-map description that ASSERTS the method is in use
  - 0 production callers
  - 0 test references

Both boot-time migrations (migrate_legacy_data_dir +
migrate_tauri_app_data_dir_with_paths) are idempotent by construction:
each re-probes the legacy path via Path::exists() on every boot and
short-circuits on the steady state. A sentinel file would optimise the
probe but is not required for correctness; one syscall per legacy
candidate at startup is negligible.

Per the atomiser principle of removing dead surface area rather than
keeping stale promises:
  - Delete AppPaths::migration_sentinel entirely
  - Update docs/architecture-map/.../core-paths.md to describe the actual
    idempotency model (re-probing) rather than the sentinel pattern that
    was never implemented
  - Steer future migrations toward storage/src/migrations.rs schema_version
    (transactional, survives backup/restore) rather than reintroducing
    filesystem sentinels

Verification:
- cargo test -p lumotia-core paths::: 17/17 (no test relied on the method)
- cargo clippy -p lumotia-core --all-targets -- -D warnings: clean

Phase A.4 (stray-magnotia string scan): clean. Every magnotia reference
in the tree is legitimate — migration source paths, documentation, or
test fixtures. The rebrand cascade was thorough.
This commit is contained in:
2026-05-14 07:23:02 +01:00
parent 43d319fd5a
commit 18a64f5c56
2 changed files with 16 additions and 9 deletions

View File

@@ -49,10 +49,6 @@ impl AppPaths {
pub fn llm_models_dir(&self) -> PathBuf {
self.models_dir().join("llm")
}
pub fn migration_sentinel(&self, name: &str) -> PathBuf {
self.app_data_dir.join(format!(".{name}.sentinel"))
}
}
pub fn app_paths() -> AppPaths {

View File

@@ -36,7 +36,6 @@ impl AppPaths {
pub fn models_dir(&self) -> PathBuf; // <root>/models
pub fn speech_model_dir(&self, id: &ModelId) -> PathBuf; // <root>/models/<id>
pub fn llm_models_dir(&self) -> PathBuf; // <root>/models/llm
pub fn migration_sentinel(&self, name: &str) -> PathBuf; // <root>/.<name>.sentinel
}
pub fn app_paths() -> AppPaths;
@@ -56,9 +55,23 @@ The `resolve_app_data_dir` function picks the root by `cfg(target_os = ...)`:
The Linux legacy-path branch keeps existing users on `~/.lumotia` (early dogfooding default) without forcing a migration when the canonical XDG location is preferred.
### Sentinel files — `crates/core/src/paths.rs:53`
### Migration idempotency — no sentinel files
`migration_sentinel(name) -> <root>/.<name>.sentinel`. Used for one-shot data migrations outside the SQLite schema (for example, a one-off file-system reorganisation). The pattern is: write the sentinel after the migration runs successfully; check for the sentinel on next startup; skip the migration if the sentinel exists.
There is no sentinel-file pattern. The two boot-time migrations
(`migrate_legacy_data_dir` for the legacy magnotia data dir, and
`migrate_tauri_app_data_dir_with_paths` for the Tauri bundle-identifier
move) are both idempotent by construction: each cheaply re-probes for
the legacy path via `Path::exists()` on every boot and short-circuits
on the steady state. A sentinel file would optimise that probe but is
not required for correctness, and the cost is one syscall per legacy
candidate — negligible at startup.
If a future migration needs cross-restart "this ran already" state
(e.g. a destructive one-shot reorganisation that mutates the legacy
path in place), reach for `crates/storage/src/migrations.rs`
(the SQLite schema_version table) rather than reintroducing a
filesystem sentinel — schema_version is transactional and survives
backup/restore, sentinel files don't.
## Data flow / contract
@@ -77,8 +90,6 @@ The Linux legacy-path branch keeps existing users on `~/.lumotia` (early dogfood
- **`std::env::var(...).unwrap_or_else(|_| "/tmp".to_string())` is the fallback for missing `HOME`.** Should never trigger in practice; defensive.
- **`AppPaths::current()` reads env vars at every call.** Cheap, but repeated calls are wasteful. Slice 2 caches a `OnceLock<AppPaths>` so the value is resolved once at startup.
- **No Windows fallback for missing `LOCALAPPDATA`.** Falls through to `.` (current working directory). On a misconfigured Windows host this could write the database next to the binary. Not great; the impact is limited to first-run scenarios where the env is broken.
- **Sentinel files are hidden on Unix (`.<name>.sentinel`) but visible on Windows.** Acceptable; sentinels live alongside `lumotia.db` so the user sees both.
## See also
- [Storage file paths](storage-file-paths.md) — re-exports.