fix(recovery): register lazy state.db tables in one schema map; cover .recover lane and count-mismatch loss

Follow-up to the salvaged #100350 commits: replace the per-table
'if table == "delivery_obligations"' branches in session_recovery.py and
session_lost_and_found.py with a single _AUXILIARY_TABLE_SCHEMAS registry
(table -> destination DDL initializer) that both the SQL-level and the
lost_and_found lanes consume, so the next lazily-created state.db table is
one entry, not three code paths. The .recover lane now iterates
_CANONICAL_TABLES + _AUXILIARY_TABLES instead of a duplicated literal list.

Tests: the .recover direct-copy lane creates the missing ledger on the
destination; a source-vs-destination obligation count mismatch fails
verification (complete=False) instead of reporting a clean salvage.
Docs: state.db table inventory lists delivery_obligations.

Addresses #100313
This commit is contained in:
Teknium
2026-09-01 21:28:23 -07:00
parent 805d506d6c
commit 5dfd1e77a8
4 changed files with 125 additions and 24 deletions
+80
View File
@@ -753,3 +753,83 @@ def test_recovery_without_delivery_ledger_is_not_lossy(tmp_path: Path) -> None:
def test_recovery_flags_delivery_obligation_count_mismatch_as_loss(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""A source-vs-destination ledger count mismatch must not verify as complete.
The destination table is created through the registered initializer; a
real SQL trigger that silently drops one row stands in for the "rows went
missing on the way over" failure the verifier has to catch.
"""
from hermes_cli import session_recovery
source = tmp_path / "state.db"
output = tmp_path / "recovered.db"
_make_source(source)
now = 1_720_000_000.0
_insert_delivery_obligations(
source,
[
("ob-a", "k", "telegram", "chat-1", None, "a", "pending", 0, now, now, None, None, None, "default"),
("ob-b", "k", "telegram", "chat-1", None, "b", "pending", 0, now, now, None, None, None, "default"),
],
)
real_init = session_recovery._AUXILIARY_TABLE_SCHEMAS["delivery_obligations"]
def lossy_init(conn: sqlite3.Connection) -> None:
real_init(conn)
conn.execute(
"""CREATE TRIGGER drop_ob_b BEFORE INSERT ON delivery_obligations
WHEN NEW.obligation_id = 'ob-b' BEGIN SELECT RAISE(IGNORE); END"""
)
monkeypatch.setitem(
session_recovery._AUXILIARY_TABLE_SCHEMAS, "delivery_obligations", lossy_init
)
report = recover_session_database(source, output, work_dir=tmp_path)
assert report["verification"]["table_counts"]["delivery_obligations"] == 1
assert report["complete"] is False
assert any(
"delivery_obligations count is 1, expected 2" in error
for error in report["verification"]["errors"]
)
def test_lost_and_found_direct_copy_creates_lazy_delivery_ledger(tmp_path: Path) -> None:
"""The .recover lane copies the ledger even though SessionDB never made it."""
from hermes_cli.session_lost_and_found import _copy_direct_tables
recovered_source = tmp_path / "lost_and_found.db"
now = 1_720_000_000.0
_insert_delivery_obligations(
recovered_source,
[
("ob-1", "k", "telegram", "chat-1", None, "one", "pending", 0, now, now, None, None, None, "default"),
("ob-2", "k", "telegram", "chat-1", None, "two", "failed", 3, now, now, None, None, "boom", "default"),
],
)
output = tmp_path / "rebuilt.db"
SessionDB(db_path=output).close()
lf_conn = sqlite3.connect(str(recovered_source), isolation_level=None)
dest = sqlite3.connect(str(output), isolation_level=None)
try:
assert not dest.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='delivery_obligations'"
).fetchall()
copied = _copy_direct_tables(lf_conn, dest)
assert copied["delivery_obligations"] == 2
rows = dest.execute(
"SELECT obligation_id, state, last_error FROM delivery_obligations ORDER BY obligation_id"
).fetchall()
finally:
lf_conn.close()
dest.close()
assert rows == [("ob-1", "pending", None), ("ob-2", "failed", "boom")]