Summary
Every migration ends with INSERT OR IGNORE INTO migrations (version, applied_at, description) VALUES (N, ...) — except v13 and v18, which skip it entirely. v17 also has an unguarded let _ = ALTER TABLE ... ADD COLUMN second_factor_used that swallows real errors as well as "duplicate column".
Location
crates/librefang-memory/src/migration.rs:461-529 (v13)
crates/librefang-memory/src/migration.rs:633-641 (v18)
crates/librefang-memory/src/migration.rs:626 (v17 unguarded ALTER)
Failure
- Operator queries
SELECT * FROM migrations ORDER BY version for diagnostics; sees gaps at v13 and v18 → assumes those migrations didn't apply.
- Schema is actually current (
user_version=23 is set unconditionally at end of run_migrations), but the audit log lies.
- Tooling that uses the
migrations table as ground truth (e.g. report.rs) makes wrong decisions; an automated rollback that "v18 wasn't applied" leaves a stranded totp_lockout table.
- v17's
let _ = ALTER ... ADD COLUMN second_factor_used succeeds silently for "already exists" AND for any other ALTER failure (lock, disk, schema). Downstream INSERTs that include the column then fail at runtime, not upgrade time.
Fix
- Add the missing
INSERT OR IGNORE INTO migrations rows for v13 and v18.
- Replace v17's
let _ = ALTER ... with if !column_exists(conn, "approval_audit", "second_factor_used") { conn.execute(...)?; } — same pattern as v14/v15/v16.
Summary
Every migration ends with
INSERT OR IGNORE INTO migrations (version, applied_at, description) VALUES (N, ...)— except v13 and v18, which skip it entirely. v17 also has an unguardedlet _ = ALTER TABLE ... ADD COLUMN second_factor_usedthat swallows real errors as well as "duplicate column".Location
crates/librefang-memory/src/migration.rs:461-529(v13)crates/librefang-memory/src/migration.rs:633-641(v18)crates/librefang-memory/src/migration.rs:626(v17 unguarded ALTER)Failure
SELECT * FROM migrations ORDER BY versionfor diagnostics; sees gaps at v13 and v18 → assumes those migrations didn't apply.user_version=23is set unconditionally at end ofrun_migrations), but the audit log lies.migrationstable as ground truth (e.g. report.rs) makes wrong decisions; an automated rollback that "v18 wasn't applied" leaves a strandedtotp_lockouttable.let _ = ALTER ... ADD COLUMN second_factor_usedsucceeds silently for "already exists" AND for any other ALTER failure (lock, disk, schema). Downstream INSERTs that include the column then fail at runtime, not upgrade time.Fix
INSERT OR IGNORE INTO migrationsrows for v13 and v18.let _ = ALTER ...withif !column_exists(conn, "approval_audit", "second_factor_used") { conn.execute(...)?; }— same pattern as v14/v15/v16.