diff --git a/tests/gateway/test_session_db_replaced_fallback.py b/tests/gateway/test_session_db_replaced_fallback.py index 3f39e29824..21c9700666 100644 --- a/tests/gateway/test_session_db_replaced_fallback.py +++ b/tests/gateway/test_session_db_replaced_fallback.py @@ -55,7 +55,10 @@ def test_replaced_state_db_diverts_pending_without_fts_rebuild(tmp_path, monkeyp ) assert store._db._db_replaced is True - assert store._db._fts_runtime_rebuild_attempted is False + # No FTS surgery ran on either layer: state-level fail-open never + # detached, and the gateway one-shot rebuild was not consumed. + assert store._db._fts_enabled is True + assert store._db._fts_stale is False assert store._fts_rebuild_attempted is False _assert_diverted(tmp_path, sid, "after-replace") store.close_all_db_handles() @@ -91,7 +94,10 @@ def test_copyfile_replaced_state_db_diverts_pending_without_fts_rebuild( ) assert store._db._db_replaced is True - assert store._db._fts_runtime_rebuild_attempted is False + # No FTS surgery ran on either layer: state-level fail-open never + # detached, and the gateway one-shot rebuild was not consumed. + assert store._db._fts_enabled is True + assert store._db._fts_stale is False assert store._fts_rebuild_attempted is False _assert_diverted(tmp_path, sid, "after-cp") store.close_all_db_handles() diff --git a/tests/hermes_state/test_state_db_file_identity.py b/tests/hermes_state/test_state_db_file_identity.py index 645306dc4d..1877cf857a 100644 --- a/tests/hermes_state/test_state_db_file_identity.py +++ b/tests/hermes_state/test_state_db_file_identity.py @@ -50,8 +50,9 @@ def test_replace_with_new_inode_fails_loudly_without_fts_repair(tmp_path): db.append_message("live-sess", role="user", content="after-replace") assert db._db_replaced is True - assert db._fts_runtime_rebuild_attempted is False + # No FTS surgery ran: fail-open never detached the indexes. assert db._fts_enabled is True + assert db._fts_stale is False db.close() @@ -67,12 +68,18 @@ def test_second_write_after_halt_does_not_attempt_repair(tmp_path): db.append_message("s", role="user", content="first") with pytest.raises(StateDbReplacedError): db.append_message("s", role="user", content="second") - assert db._fts_runtime_rebuild_attempted is False + assert db._fts_enabled is True + assert db._fts_stale is False db.close() -def test_same_file_fts_corruption_still_rebuilds(tmp_path): - """Identity guard must not disable genuine in-file FTS recovery.""" +def test_same_file_fts_corruption_still_fails_open(tmp_path): + """Identity guard must not disable genuine in-file FTS recovery. + + Since 18ac3c4fb6 the live write path never rebuilds FTS in place; the + recovery contract is the fail-open detach (stale marker + triggers + dropped) followed by a successful canonical retry. + """ db = _make_db(tmp_path / "state.db", "s1", "hello world") _require_identity(db) identity = db._db_file_identity @@ -85,7 +92,9 @@ def test_same_file_fts_corruption_still_rebuilds(tmp_path): db.append_message("s1", role="user", content="healed append") assert db._db_file_identity == identity assert db._db_replaced is False - assert db._fts_runtime_rebuild_attempted is True + # Fail-open detach ran: canonical write landed, FTS marked stale. + assert db._fts_stale is True + assert db._fts_enabled is False db.close() @@ -116,8 +125,10 @@ def test_new_sessiondb_on_replaced_path_records_new_identity(tmp_path): reopened.close() -def test_malformed_error_on_replaced_file_skips_fts_rebuild(tmp_path): - """Even if SQLite surfaces malformed, identity mismatch blocks repair.""" +def test_fts_scoped_error_on_replaced_file_skips_fts_fail_open(tmp_path): + """Even FTS-provenance corruption must not authorize surgery on a + replaced file. (A generic malformed error never reaches fail-open at + all since the provenance classifier of #99652 rejects it earlier.)""" live = tmp_path / "state.db" other = tmp_path / "other.db" db = _make_db(live, "s", "a") @@ -127,10 +138,13 @@ def test_malformed_error_on_replaced_file_skips_fts_rebuild(tmp_path): os.replace(other, live) with pytest.raises(StateDbReplacedError): - db._try_runtime_fts_rebuild( - sqlite3.DatabaseError("database disk image is malformed") + db._enter_fts_fail_open( + sqlite3.DatabaseError( + 'fts5: corrupt structure record for table "messages_fts"' + ) ) - assert db._fts_runtime_rebuild_attempted is False + assert db._fts_enabled is True + assert db._fts_stale is False db.close() @@ -156,7 +170,8 @@ def test_copyfile_same_inode_fails_loudly_without_fts_repair(tmp_path): with pytest.raises(StateDbReplacedError, match="replaced underneath"): db.append_message("live-sess", role="user", content="after-cp") assert db._db_replaced is True - assert db._fts_runtime_rebuild_attempted is False + assert db._fts_enabled is True + assert db._fts_stale is False db.close()