Skip to content

fix(profile): normalise sqlx migration checksums on import + pin sources to LF#27

Merged
InstaZDLL merged 1 commit into
mainfrom
fix/profile-import-checksum-drift
May 16, 2026
Merged

fix(profile): normalise sqlx migration checksums on import + pin sources to LF#27
InstaZDLL merged 1 commit into
mainfrom
fix/profile-import-checksum-drift

Conversation

@InstaZDLL
Copy link
Copy Markdown
Owner

@InstaZDLL InstaZDLL commented May 16, 2026

Summary

  • .waveflow archives produced by older builds (when migration .sql files were checked out with CRLF endings under Windows core.autocrlf=true and no .gitattributes) stored SHA-384 checksums in _sqlx_migrations.checksum computed on CRLF bytes. The current source tree has those same migrations checked out as LF, so sqlx::migrate! (compile-time macro that embeds the file bytes) ends up with LF-based checksums. Re-importing an old archive then trips sqlx's "migration X was previously applied but has been modified" guard even though the SQL is semantically identical — making every pre-existing .waveflow export unimportable on a current build.
  • New normalise_migration_checksums step in import_profile rewrites each row of the bundled _sqlx_migrations to the local migrator's checksum before sqlx's verification runs. Versions present in the archive but unknown to the local migrator (archive produced by a newer WaveFlow than the importing build) are rejected explicitly rather than silently dropped.
  • .gitattributes pins source files (*.sql, *.rs, *.ts, *.tsx, *.js, *.json, *.toml, *.yml, *.md, …) to LF so future archives stay byte-stable across contributors and the same drift cannot reoccur. Docs updated in docs/features/ui.md.

Root-cause evidence

Comparing the _sqlx_migrations.checksum rows inside a real exported .waveflow against SHA-384(file_bytes) of the current source:

Migration Archive checksum matches
20260411120000_initial CRLF
20260413000100_drop_local_metadata_caches CRLF
20260428000000_drop_metadata_fks LF
20260428000001_track_rating LF
20260428000002_track_audio_quality CRLF
20260428000003_track_musical_key CRLF
20260509000000_playlist_cover_hash CRLF
20260509100000_playlist_cover_is_auto CRLF
20260510221047_add_spotify_auth_provider LF
20260512230000_perf_indexes CRLF

Mixed CRLF/LF reflects the working-tree state at each build's time; the SQL content itself has only ever been touched by its introducing commit.

Test plan

  • cargo check --manifest-path src-tauri/Cargo.toml --all-targets
  • Dry-run against a real broken archive (Nayeon.waveflow): 7 checksums normalised, the 2 pending migrations (album_artist + invalidate_mtime_for_album_artist_backfill) apply cleanly, 996 tracks + 5 playlists recovered, new album.album_artist column present.
  • Manual import of a legacy archive through Settings → Stockage in a bun run tauri dev build.
  • Round-trip: export a profile on this branch, re-import on the same build, confirm no checksum errors.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed profile import failures caused by line-ending format differences between archives and local systems. Import now automatically normalizes migration checksums to ensure byte-stable compatibility across different platforms.
  • Documentation

    • Updated import documentation to describe the automatic migration checksum normalization behavior during profile restoration from archives.

Review Change Stack

…ces to LF

`.waveflow` archives produced by builds whose migration files were
checked out with CRLF (Windows `core.autocrlf=true`, no `.gitattributes`)
embedded SHA-384 checksums in `_sqlx_migrations.checksum` that differ
from the same SQL re-hashed today on an LF checkout. Re-importing such
an archive crashed with "migration X was previously applied but has been
modified" even though the DDL is byte-identical at the semantic level.

- `normalise_migration_checksums` rewrites each row in the imported
  `_sqlx_migrations` to the local migrator's checksum before
  `sqlx::migrate!` runs. Versions present in the archive but unknown
  locally are rejected (archive newer than app).
- `.gitattributes` pins source files (sql/rs/ts/tsx/js/json/toml/md/...)
  to LF so future archives stay byte-stable across contributors.
@github-actions github-actions Bot added scope: backend Rust/Tauri backend (src-tauri/) scope: docs Docs, README, assets type: fix Bug fix labels May 16, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

📝 Walkthrough

Walkthrough

This PR fixes profile import failures caused by line-ending mismatches in SQLx migration checksums. It enforces LF via .gitattributes, implements a migration checksum normalization helper in the import flow, and documents the new behavior for users.

Changes

Migration Checksum Normalization for Cross-Platform Archives

Layer / File(s) Summary
Line-ending policy enforcement
.gitattributes
.gitattributes enforces LF line endings for SQL, Rust, TypeScript, and other source files, plus lockfiles, to prevent CRLF/LF checksum drift in migrations and future archives.
Migration checksum normalization helper
src-tauri/src/commands/profile_io.rs
Expands sqlx imports and adds normalise_migration_checksums(db_path: &Path), which connects to the extracted database, loads stored _sqlx_migrations rows, compares each version's checksum against bundled migrations, updates mismatched checksums, and errors if the archive contains unknown migration versions.
Import flow integration and documentation
src-tauri/src/commands/profile_io.rs, docs/features/ui.md
import_profile calls normalise_migration_checksums before opening the imported profile pool and running migrations. Documentation explains the pre-migration normalization step and archive-version validation in the export/import guide.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hopped through checksum drift,
Where CRLF gave migrations a shift.
LF is law now, no more byte-mess,
Imports align in happy success!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes: normalizing sqlx migration checksums during profile import and pinning source files to LF line endings.
Description check ✅ Passed The PR description is comprehensive and addresses all key template sections including summary, root-cause evidence, and test plan. However, the 'How I tested' and 'Checklist' sections lack explicit detail.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/profile-import-checksum-drift

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the size: m 50-200 lines label May 16, 2026
@InstaZDLL InstaZDLL self-assigned this May 16, 2026
@InstaZDLL InstaZDLL enabled auto-merge (squash) May 16, 2026 10:00
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src-tauri/src/commands/profile_io.rs (1)

226-234: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rollback profile artifacts when post-extract finalization fails.

Line [226] and Line [233] can return early via ?, but this path does not clean up the newly inserted profile row/directory like the extract-failure path does. A rejected archive (e.g., unknown migration version) can leave an orphan profile entry and partial files.

Suggested fix
-    normalise_migration_checksums(&state.paths.profile_db(new_profile_id)).await?;
-
-    // 5. Open + close the imported pool once so any pending migrations
-    //    (the source might be older than the local schema) replay
-    //    immediately. This matches the create_profile flow and gives
-    //    the user a usable profile by the time the call returns.
-    let pool =
-        db::profile_db::open(&state.paths.profile_db(new_profile_id), &state.paths.app_db).await?;
-    pool.close().await;
+    let finalize_result = async {
+        normalise_migration_checksums(&state.paths.profile_db(new_profile_id)).await?;
+
+        // 5. Open + close the imported pool once so any pending migrations
+        //    (the source might be older than the local schema) replay
+        //    immediately. This matches the create_profile flow and gives
+        //    the user a usable profile by the time the call returns.
+        let pool = db::profile_db::open(
+            &state.paths.profile_db(new_profile_id),
+            &state.paths.app_db,
+        )
+        .await?;
+        pool.close().await;
+        Ok::<(), AppError>(())
+    }
+    .await;
+
+    if let Err(err) = finalize_result {
+        let _ = std::fs::remove_dir_all(state.paths.profile_dir(new_profile_id));
+        let _ = sqlx::query("DELETE FROM profile WHERE id = ?")
+            .bind(new_profile_id)
+            .execute(&state.app_db)
+            .await;
+        return Err(err);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/src/commands/profile_io.rs` around lines 226 - 234, The
post-extract finalization steps (normalise_migration_checksums and
db::profile_db::open) can early-return with ? and currently won't undo the newly
created profile row/directory for new_profile_id; ensure any error from
normalise_migration_checksums(&state.paths.profile_db(new_profile_id)) or
db::profile_db::open(&state.paths.profile_db(new_profile_id),
&state.paths.app_db) triggers cleanup: delete the inserted profile record and
remove the profile directory for new_profile_id before returning the error.
Implement this by replacing the bare ? with error-handling that calls a cleanup
helper (e.g., cleanup_profile_artifacts(state, new_profile_id) or inline logic
to remove the DB row and fs::remove_dir_all on
state.paths.profile_db(new_profile_id)) and then re-returns the error, and reuse
this cleanup on both failing calls; keep pool.close() after successful open
as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src-tauri/src/commands/profile_io.rs`:
- Around line 226-234: The post-extract finalization steps
(normalise_migration_checksums and db::profile_db::open) can early-return with ?
and currently won't undo the newly created profile row/directory for
new_profile_id; ensure any error from
normalise_migration_checksums(&state.paths.profile_db(new_profile_id)) or
db::profile_db::open(&state.paths.profile_db(new_profile_id),
&state.paths.app_db) triggers cleanup: delete the inserted profile record and
remove the profile directory for new_profile_id before returning the error.
Implement this by replacing the bare ? with error-handling that calls a cleanup
helper (e.g., cleanup_profile_artifacts(state, new_profile_id) or inline logic
to remove the DB row and fs::remove_dir_all on
state.paths.profile_db(new_profile_id)) and then re-returns the error, and reuse
this cleanup on both failing calls; keep pool.close() after successful open
as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8da8371d-5c4d-4af7-b704-0e8c039d60a6

📥 Commits

Reviewing files that changed from the base of the PR and between 813a38d and 4a38d59.

📒 Files selected for processing (3)
  • .gitattributes
  • docs/features/ui.md
  • src-tauri/src/commands/profile_io.rs

@InstaZDLL InstaZDLL merged commit 521e484 into main May 16, 2026
13 checks passed
@InstaZDLL InstaZDLL deleted the fix/profile-import-checksum-drift branch May 16, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: backend Rust/Tauri backend (src-tauri/) scope: docs Docs, README, assets size: m 50-200 lines type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant