diff --git a/docs/superpowers/plans/2026-08-25-b2a-reviewed-real-twin-propagation.md b/docs/superpowers/plans/2026-08-25-b2a-reviewed-real-twin-propagation.md index e19582f..8b7948a 100644 --- a/docs/superpowers/plans/2026-08-25-b2a-reviewed-real-twin-propagation.md +++ b/docs/superpowers/plans/2026-08-25-b2a-reviewed-real-twin-propagation.md @@ -47,8 +47,8 @@ def test_existing_db_migrates_reviewed_real(tmp_path): - [ ] **Step 2: Run → FAIL** (`pytest tests/test_reviewed_real_column.py -q`). - [ ] **Step 3: Implement** - In `_SCHEMA` `events` CREATE TABLE, after the `false_trigger ... DEFAULT 0,` line add: ` reviewed_real INTEGER NOT NULL DEFAULT 0, -- 0=no, 1=operator-confirmed real (mutually exclusive with false_trigger)` - - In the Migration-1 rebuild `CREATE TABLE events (...)` block, add the same `reviewed_real INTEGER NOT NULL DEFAULT 0,` line after `false_trigger`. - In the `_migrate` ADD COLUMN loop tuple add: `("reviewed_real", "INTEGER NOT NULL DEFAULT 0"),` + - **Do NOT** add it to the Migration-1 rebuild `CREATE TABLE events (...)` block — that block uses a positional `INSERT ... SELECT * FROM events_old` and, by convention, contains only the columns that existed when Migration 1 was written; every later column is added by the ADD COLUMN loop only. Adding it there crashes `_migrate` on genuinely legacy (pre-Migration-1) DBs. - [ ] **Step 4: Run → PASS.** - [ ] **Step 5: Commit** `feat(db): reviewed_real column on events (+ auto-migrate)` diff --git a/sfm/database.py b/sfm/database.py index 91a0bfc..846ebcf 100644 --- a/sfm/database.py +++ b/sfm/database.py @@ -189,7 +189,6 @@ class SeismoDb: sample_rate INTEGER, record_type TEXT, false_trigger INTEGER NOT NULL DEFAULT 0, - reviewed_real INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), UNIQUE(serial, timestamp) ); diff --git a/tests/test_reviewed_real_column.py b/tests/test_reviewed_real_column.py index 3fe153b..6601e4d 100644 --- a/tests/test_reviewed_real_column.py +++ b/tests/test_reviewed_real_column.py @@ -12,3 +12,47 @@ def test_existing_db_migrates_reviewed_real(tmp_path): assert "reviewed_real" not in _cols(db) # dropped (read via existing handle/connection) SeismoDb(p) # re-open migrates assert "reviewed_real" in _cols(SeismoDb(p)) + +def test_legacy_pre_migration1_db_rebuild_does_not_crash(tmp_path): + # Genuinely legacy (pre-Migration-1) events table: no UNIQUE(serial, timestamp) + # (still on the old UNIQUE(serial, waveform_key)) and columns only through + # false_trigger/created_at — i.e. none of the columns added later by the + # Migration-1b ADD COLUMN loop (blastware_filename, device_family, + # tran_zc_freq, shape_*, reviewed_real, ...). Opening this DB triggers the + # Migration-1 rebuild (`events_old` -> `events` via positional + # `INSERT OR IGNORE INTO events SELECT * FROM events_old`), which requires + # the rebuild's CREATE TABLE to have exactly the legacy column count. + p = tmp_path / "legacy.db" + with sqlite3.connect(p) as c: + c.execute(""" + CREATE TABLE events ( + id TEXT PRIMARY KEY, + serial TEXT NOT NULL, + waveform_key TEXT NOT NULL, + session_id TEXT, + timestamp TEXT, + tran_ppv REAL, + vert_ppv REAL, + long_ppv REAL, + peak_vector_sum REAL, + mic_ppv REAL, + project TEXT, + client TEXT, + operator TEXT, + sensor_location TEXT, + sample_rate INTEGER, + record_type TEXT, + false_trigger INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + UNIQUE(serial, waveform_key) + ) + """) + c.execute( + "INSERT INTO events (id, serial, waveform_key, timestamp) VALUES (?, ?, ?, ?)", + ("evt-1", "BE11529", "01110000", "2026-01-01T00:00:00"), + ) + # Pre-fix this raised: OperationalError: table events has 19 columns but + # 18 values were supplied (reviewed_real had leaked into the rebuild's + # CREATE TABLE, but events_old — and the positional SELECT * — only had 18). + db = SeismoDb(p) + assert "reviewed_real" in _cols(db)