diff --git a/internal/store/migrate_test.go b/internal/store/migrate_test.go new file mode 100644 index 0000000..59fb304 --- /dev/null +++ b/internal/store/migrate_test.go @@ -0,0 +1,33 @@ +package store + +import ( + "context" + "path/filepath" + "testing" +) + +func TestMigration0013AlertsLastSeen(t *testing.T) { + t.Parallel() + dir := t.TempDir() + st, err := Open(context.Background(), filepath.Join(dir, "rm.db")) + if err != nil { + t.Fatalf("open: %v", err) + } + defer st.Close() + + // Column must exist after migration. Best signal: PRAGMA table_info. + rows, err := st.DB().Query(`SELECT name FROM pragma_table_info('alerts')`) + if err != nil { + t.Fatalf("pragma: %v", err) + } + defer rows.Close() + cols := map[string]bool{} + for rows.Next() { + var n string + _ = rows.Scan(&n) + cols[n] = true + } + if !cols["last_seen_at"] { + t.Fatalf("alerts.last_seen_at not present after migration; cols=%v", cols) + } +} diff --git a/internal/store/migrations/0013_alerts_last_seen.sql b/internal/store/migrations/0013_alerts_last_seen.sql new file mode 100644 index 0000000..1acac1c --- /dev/null +++ b/internal/store/migrations/0013_alerts_last_seen.sql @@ -0,0 +1,16 @@ +-- 0013_alerts_last_seen.sql +-- +-- Add alerts.last_seen_at to support open-alert dedup with +-- recurrence-tracking. The engine bumps this column on every tick +-- where a rule still matches an existing open alert, so the UI can +-- render "still happening · Ns ago" without sending a fresh +-- notification. +-- +-- Column-level ALTER per CLAUDE.md (no rebuild — alerts has inbound +-- FK from acknowledged_by → users; rebuild would risk cascade). +-- Backfill last_seen_at = created_at for any pre-existing rows so +-- the column is non-null in practice (stays nullable in the schema +-- for forwards-compat with rows that haven't been touched yet). + +ALTER TABLE alerts ADD COLUMN last_seen_at TEXT; +UPDATE alerts SET last_seen_at = created_at WHERE last_seen_at IS NULL;