test(hosted-rooms): pin the coordination store off the master state.db; label sidecars
Invariant test for the #103489 salvage: a profile gateway and the root gateway resolve the same hosted-room coordination file at the install root, and that file is never the root SessionDB (`state.db`). Every profile gateway starts the hosted-room worker unconditionally, so the old mapping made each profile process a long-lived writer on the master session store — the simultaneous-restart corruption in #102120 and the main offender in the #103339 / #103490 fleet reports. Also: WAL-fallback labels say `shared-state.db`, and test fakes take the store path from `default_db_path()` instead of hardcoding `state.db`. Reported-by: #102120 #103339 (@RChina) #103490 Refs #102120 #103339 #103490
This commit is contained in:
@@ -114,7 +114,7 @@ class HostedRoomPolicyCheckpoint:
|
||||
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(self.db_path, timeout=10)
|
||||
conn.row_factory = sqlite3.Row
|
||||
apply_wal_with_fallback(conn, db_label="state.db (room policy checkpoint)")
|
||||
apply_wal_with_fallback(conn, db_label="shared-state.db (room policy checkpoint)")
|
||||
return conn
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -423,7 +423,7 @@ def local_authority_gateway_id() -> str:
|
||||
|
||||
|
||||
_connect = partial(
|
||||
connect, db_label="state.db (hosted_rooms)", ready=_schema_is_current,
|
||||
connect, db_label="shared-state.db (hosted_rooms)", ready=_schema_is_current,
|
||||
initialize=lambda conn: _initialize_schema(conn), lock_retries=_JOURNAL_MODE_LOCK_RETRIES)
|
||||
|
||||
|
||||
|
||||
@@ -1339,3 +1339,26 @@ def test_room_log_page_bound_counts_bytes_not_characters(tmp_path, monkeypatch):
|
||||
assert [event["seq"] for event in page["events"]] == [1]
|
||||
assert page_bytes(page) <= budget
|
||||
assert page["has_more"] is True
|
||||
|
||||
|
||||
def test_default_db_path_never_names_the_master_session_store(tmp_path, monkeypatch):
|
||||
"""Hosted-room coordination lives beside, never inside, the master ``state.db``.
|
||||
|
||||
Every profile gateway starts the hosted-room worker, so a store resolved to the
|
||||
root session DB made every profile process a long-lived writer on state.db —
|
||||
the multi-profile restart corruption in #102120 / #103339 / #103490. The store
|
||||
is shared across profiles (one file at the install root) but is not the
|
||||
SessionDB file the root gateway owns.
|
||||
"""
|
||||
root = tmp_path / ".hermes"
|
||||
profile_home = root / "profiles" / "bot1"
|
||||
profile_home.mkdir(parents=True)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(profile_home))
|
||||
from_profile = rooms.default_db_path()
|
||||
monkeypatch.setenv("HERMES_HOME", str(root))
|
||||
from_root = rooms.default_db_path()
|
||||
|
||||
assert from_profile == from_root, "one coordination file per install"
|
||||
assert from_root.parent == root
|
||||
assert from_root.name != "state.db"
|
||||
|
||||
@@ -6,6 +6,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.hosted_rooms import default_db_path as hosted_rooms_default_db_path
|
||||
import tui_gateway.server as srv
|
||||
from tui_gateway import methods_groups
|
||||
|
||||
@@ -346,7 +347,7 @@ def test_register_peer_route_probes_scope_and_persists_via_service(home, monkeyp
|
||||
}
|
||||
|
||||
class FakeService:
|
||||
db_path = home / "state.db"
|
||||
db_path = hosted_rooms_default_db_path()
|
||||
|
||||
def register_peer_route(self, **kwargs):
|
||||
captured["registered"] = kwargs
|
||||
@@ -376,7 +377,7 @@ def test_register_peer_route_probes_scope_and_persists_via_service(home, monkeyp
|
||||
|
||||
def test_register_rejects_plaintext_non_loopback(home, monkeypatch):
|
||||
class FakeService:
|
||||
db_path = home / "state.db"
|
||||
db_path = hosted_rooms_default_db_path()
|
||||
|
||||
monkeypatch.setattr(srv, "get_hosted_room_service", lambda: FakeService())
|
||||
response = srv._methods["groups.peer.register"](
|
||||
@@ -398,7 +399,7 @@ def test_register_requires_roomlink_protocol_v2(home, monkeypatch):
|
||||
from gateway.hosted_room_peer import catalog_mapping
|
||||
|
||||
class FakeService:
|
||||
db_path = home / "state.db"
|
||||
db_path = hosted_rooms_default_db_path()
|
||||
|
||||
monkeypatch.setattr(srv, "get_hosted_room_service", lambda: FakeService())
|
||||
response = srv._methods["groups.peer.register"](
|
||||
@@ -914,7 +915,7 @@ def test_disband_stops_and_revokes_before_tombstoning(home, monkeypatch):
|
||||
calls = []
|
||||
|
||||
class FakeService:
|
||||
db_path = home / "state.db"
|
||||
db_path = hosted_rooms_default_db_path()
|
||||
|
||||
def stop_room(self, room_id, **_kwargs):
|
||||
calls.append(("stop", room_id))
|
||||
@@ -933,7 +934,7 @@ def test_failed_remote_revocation_keeps_room_recoverable(home, monkeypatch):
|
||||
_create_room()
|
||||
|
||||
class FakeService:
|
||||
db_path = home / "state.db"
|
||||
db_path = hosted_rooms_default_db_path()
|
||||
|
||||
def stop_room(self, _room_id, **_kwargs):
|
||||
return 1
|
||||
@@ -958,7 +959,7 @@ def test_disband_does_not_revoke_routes_while_stop_is_unacknowledged(
|
||||
calls = []
|
||||
|
||||
class FakeService:
|
||||
db_path = home / "state.db"
|
||||
db_path = hosted_rooms_default_db_path()
|
||||
|
||||
def stop_room(self, _room_id, **kwargs):
|
||||
calls.append(("stop", kwargs["require_acknowledged"]))
|
||||
|
||||
Reference in New Issue
Block a user