Files
Lumotia/docs/issues/c3-migrations-atomicity.md
Jake 26c7307607
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled
agent: lumotia-rebrand — docs, scripts, root config, residuals
Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.

transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
  -> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
  as immutable audit trail). Includes architecture-map references
  to magnotia_core::*, magnotia_storage::*, etc. now pointing at
  lumotia_*; dev-setup.md tracing output examples (lumotia_startup
  target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
  audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
  hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
  crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
  crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
  doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
  ("lumotia_task_sync"); magnotia_locale i18n localStorage key
  renamed + migration shim added; CSS keyframe names
  magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
  system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
  keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
  wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
  to "lumotia era" earlier — restored).

Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
  legacy detection strings in legacy_and_target_paths() so the
  migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
  — migration call sites reference the legacy magnotia keys
  deliberately.
- docs/handovers/ — historical audit trail.

cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:38:03 +01:00

2.9 KiB

RB-02 CRITICAL: multi-statement migrations can half-apply

Severity: CRITICAL Path: crates/storage/src/migrations.rs:263-299 Source: 2026-04-22 code review Labels: release-blocker, critical, data-integrity, storage Status: RESOLVED (2026-04-22)

Resolution

Extracted run_migrations_slice(pool, migrations) as the single code path that applies pending migrations. For each pending version it opens a Transaction via pool.begin(), applies every split statement on that transaction, records the schema_version row inside the same transaction, and finally tx.commit()s. A failure anywhere in the sequence — statement, version insert, commit — rolls the whole migration back.

run_migrations delegates to run_migrations_slice(pool, MIGRATIONS) and the test helper run_migrations_up_to to a filtered subset, so only one version of the apply logic exists.

Regression test multi_statement_migration_rolls_back_on_failure feeds a poisoned v9 migration (CREATE TABLE poison_marker; SELECT this_function_does_not_exist()) through run_migrations_slice. The call returns Err, and post-call SELECT COUNT(*) FROM poison_marker fails with "no such table" while MAX(schema_version) remains at 8.

SQLite DDL participates in transactions, so this is sufficient for the Lumotia schema. If any future migration needs a statement that implicitly commits (VACUUM, REINDEX, ATTACH) — none do today — it must be split into its own non-transactional migration. Reviewer's job to flag.

Problem

run_migrations executes each statement individually and only records the schema version after the full migration succeeds. If a multi-statement migration (v5, v6, v8 — any containing more than one CREATE / ALTER / UPDATE) fails mid-run, or the process is killed between statements, the schema can end up partially changed while still appearing unapplied. The next startup replays the same migration against the mutated database, which can fail in confusing ways or corrupt data further.

Acceptance

  • Every migration runs inside a single BEGIN / COMMIT transaction.
  • The version row update happens inside the same transaction — atomic success or no change.
  • Regression test: a migration that panics partway through leaves the database at the previous schema version with no partial changes visible on restart.

Fix scope

Medium. Wrap each migration in pool.begin() / tx.commit(). The version update and the migration statements all execute on the same Transaction handle. Needs careful review of any migration that uses implicit commits (SQLite VACUUM, REINDEX, ATTACH — none of which Lumotia currently uses, but the review pattern should guard against future additions).

Dependencies

  • Coupled with RB-03 (any v9 migration adding the transcript-profile FK must itself be transactional — this fix is a prerequisite).