fix(#2279): versioned DB migrations via schema_versions table

Replace the try-and-ignore-duplicate-column approach in apply_migrations
with proper schema versioning using a shared schema_versions table.

## mechanism

New function: db::apply_versioned_migrations(conn, subsystem,
legacy_column, migrations). Tracks the applied-migration count in a
schema_versions table (one row per subsystem key). Only migrations past
the stored version run.

Legacy detection: pre-versioning databases have no schema_versions row.
The legacy_column tuple (table, column) identifies a column that exists
only in a fully-migrated legacy database. If present, all known
migrations are skipped. If absent, migrations start from 0.

## stores migrated

- broker: removes bespoke ensure_message_columns / ensure_reminder_columns.
  Unified into BROKER_MIGRATIONS (v1-v5). Legacy detector: messages.priority
  (added in the last pre-versioning migration).
- approvals: 4 historical migrations (v1-v4). Legacy detector:
  approvals.submitter.
- operator_questions: 3 historical migrations (v1-v3). Legacy detector:
  operator_questions.target.
- scheduled_prompts: 1 historical migration (v1). Legacy detector:
  scheduled_prompts.paused_at_unix.

apply_migrations removed (no callers).

## tests (db.rs)

- fresh_install_runs_all_migrations
- legacy_install_skips_all_migrations
- partial_migration_resumes_from_version
- already_at_latest_is_noop
- multiple_stores_in_same_db
This commit is contained in:
atlas 2026-07-10 14:50:39 +02:00 committed by mara
commit 7d36ec5e1f
5 changed files with 333 additions and 115 deletions

View file

@ -186,11 +186,17 @@ impl ScheduledPrompts {
.context("enable foreign keys")?;
conn.execute_batch(SCHEMA)
.context("apply scheduled_prompts schema")?;
// Migration: add paused_at_unix to existing databases.
crate::db::apply_migrations(
// Versioned migration: add paused_at_unix. Legacy databases are
// detected via the presence of this column (it was the only
// migration before versioning was introduced).
crate::db::apply_versioned_migrations(
&conn,
"scheduled_prompts",
&["ALTER TABLE scheduled_prompts ADD COLUMN paused_at_unix INTEGER"],
("scheduled_prompts", "paused_at_unix"),
&[
// v1: add paused_at_unix for per-schedule pause support.
"ALTER TABLE scheduled_prompts ADD COLUMN paused_at_unix INTEGER",
],
)?;
// Migration: recreate the due-rows index to also exclude paused
// rows. `CREATE INDEX IF NOT EXISTS` won't update an existing