from __future__ import annotations import datetime import logging from types import SimpleNamespace import pytest import sqlalchemy from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from langbot.pkg.cloud.directory import ( DirectoryDelta, DirectoryEvent, DirectoryEventBatch, DirectoryMember, DirectoryProjectionUnavailableError, DirectorySnapshot, DirectoryWorkspace, ) from langbot.pkg.cloud.directory_projection import DirectoryProjectionService from langbot.pkg.entity.persistence.base import Base from langbot.pkg.entity.persistence.cloud_directory import DirectoryProjectionInbox, DirectoryProjectionState from langbot.pkg.entity.persistence.user import User from langbot.pkg.entity.persistence.workspace import ( Workspace, WorkspaceExecutionState, WorkspaceMembership, ) from langbot.pkg.persistence.tenant_uow import PersistenceScope, TenantUnitOfWork pytestmark = pytest.mark.asyncio INSTANCE_UUID = 'instance-directory-test' WORKSPACE_UUID = '10000000-0000-0000-0000-000000000001' SECOND_WORKSPACE_UUID = '10000000-0000-0000-0000-000000000002' ACCOUNT_UUID = '20000000-0000-0000-0000-000000000001' MEMBERSHIP_UUID = '30000000-0000-0000-0000-000000000001' SECOND_MEMBERSHIP_UUID = '30000000-0000-0000-0000-000000000002' class _Persistence: def __init__(self, engine): self.engine = engine def directory_projection_uow(self, instance_uuid: str) -> TenantUnitOfWork: return TenantUnitOfWork( self.engine, scope=PersistenceScope.directory(instance_uuid), ) def get_db_engine(self): return self.engine class _Provider: def __init__( self, snapshots: list[DirectorySnapshot], batches: list[DirectoryEventBatch] | None = None, deltas: list[DirectoryDelta] | None = None, ) -> None: self.snapshots = snapshots self.batches = list(batches or []) self.deltas = list(deltas or []) self.snapshot_calls = 0 self.delta_calls = 0 self.after_cursors: list[int] = [] async def fetch_snapshot(self, instance_uuid: str) -> DirectorySnapshot: assert instance_uuid == INSTANCE_UUID snapshot = self.snapshots[min(self.snapshot_calls, len(self.snapshots) - 1)] self.snapshot_calls += 1 return snapshot async def fetch_events( self, instance_uuid: str, after_cursor: int, limit: int, ) -> DirectoryEventBatch: assert instance_uuid == INSTANCE_UUID assert limit == 100 self.after_cursors.append(after_cursor) if self.batches: return self.batches.pop(0) return DirectoryEventBatch( instance_uuid=instance_uuid, after_cursor=after_cursor, cursor=after_cursor, high_water_cursor=after_cursor, events=[], ) async def fetch_workspaces( self, instance_uuid: str, workspace_uuids: tuple[str, ...], ) -> DirectoryDelta: assert instance_uuid == INSTANCE_UUID assert workspace_uuids == (WORKSPACE_UUID,) delta = self.deltas[min(self.delta_calls, len(self.deltas) - 1)] self.delta_calls += 1 return delta @pytest.fixture async def projection_context(tmp_path): engine = create_async_engine(f'sqlite+aiosqlite:///{tmp_path / "directory-projection.db"}') async with engine.begin() as connection: await connection.run_sync(Base.metadata.create_all) application = SimpleNamespace( persistence_mgr=_Persistence(engine), logger=logging.getLogger('test-directory-projection'), ) session_factory = async_sessionmaker(engine, expire_on_commit=False) yield application, session_factory await engine.dispose() def _member(*, revision: int = 1, role: str = 'member') -> DirectoryMember: return DirectoryMember( membership_uuid=MEMBERSHIP_UUID, account_uuid=ACCOUNT_UUID, normalized_email='owner@example.com', display_name='Workspace Owner', account_status='active', role=role, membership_status='active', projection_revision=revision, joined_at=datetime.datetime(2026, 7, 24, tzinfo=datetime.UTC), ) def _workspace( *, revision: int = 1, status: str = 'active', name: str = 'Personal Workspace', role: str = 'member', execution_generation: int = 1, ) -> DirectoryWorkspace: return DirectoryWorkspace( uuid=WORKSPACE_UUID, name=name, slug='personal-workspace', type='personal', status=status, created_by_account_uuid=ACCOUNT_UUID, projection_revision=revision, execution_generation=execution_generation, members=[_member(revision=revision, role=role)], ) def _snapshot( cursor: int, *, workspaces: list[DirectoryWorkspace] | None = None, ) -> DirectorySnapshot: return DirectorySnapshot( instance_uuid=INSTANCE_UUID, cursor=cursor, generated_at=datetime.datetime(2026, 7, 24, 12, cursor % 60, tzinfo=datetime.UTC), workspaces=[_workspace(revision=max(cursor, 1))] if workspaces is None else workspaces, ) def _delta( *, workspaces: list[DirectoryWorkspace] | None = None, requested_workspace_uuids: tuple[str, ...] = (WORKSPACE_UUID,), ) -> DirectoryDelta: return DirectoryDelta( instance_uuid=INSTANCE_UUID, requested_workspace_uuids=requested_workspace_uuids, generated_at=datetime.datetime(2026, 7, 24, 12, 30, tzinfo=datetime.UTC), workspaces=[_workspace(revision=2)] if workspaces is None else workspaces, ) async def test_initial_snapshot_projects_core_owned_rows(projection_context): application, session_factory = projection_context service = DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, ) await service.initialize() service.require_ready() async with session_factory() as session: account = await session.scalar(sqlalchemy.select(User)) workspace = await session.get(Workspace, WORKSPACE_UUID) membership = await session.scalar(sqlalchemy.select(WorkspaceMembership)) execution = await session.get(WorkspaceExecutionState, WORKSPACE_UUID) state = await session.get(DirectoryProjectionState, INSTANCE_UUID) assert account is not None assert account.uuid == ACCOUNT_UUID assert account.source == 'cloud_projection' assert account.account_type == 'space' assert account.password == '' assert workspace is not None assert workspace.source == 'cloud_projection' assert membership is not None assert membership.role == 'developer' assert execution is not None assert execution.source == 'cloud' assert execution.write_fenced is False assert state is not None assert state.cursor == 1 assert state.snapshot_coverage_cursor == 1 async def test_same_cursor_equivocation_and_rollback_fail_closed(projection_context): application, _session_factory = projection_context service = DirectoryProjectionService( application, _Provider([_snapshot(2)]), INSTANCE_UUID, ) await service.initialize() conflicting = _snapshot( 2, workspaces=[_workspace(revision=2, name='Conflicting Name')], ) with pytest.raises(DirectoryProjectionUnavailableError, match='conflicting contents'): await service.apply_snapshot(conflicting) with pytest.raises(DirectoryProjectionUnavailableError, match='rolled back'): await service.apply_snapshot(_snapshot(1)) async def test_execution_generation_cannot_change_without_directory_revision(projection_context): application, _session_factory = projection_context service = DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, ) await service.initialize() conflicting = _snapshot( 2, workspaces=[_workspace(revision=1, execution_generation=2)], ) with pytest.raises(DirectoryProjectionUnavailableError, match='execution state has conflicting contents'): await service.apply_snapshot(conflicting) async def test_event_limit_matches_single_delta_request_limit(projection_context): application, _session_factory = projection_context with pytest.raises(ValueError, match='between 1 and 100'): DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, event_limit=101, ) async def test_archived_and_absent_workspaces_are_execution_fenced(projection_context): application, session_factory = projection_context service = DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, ) await service.initialize() await service.apply_snapshot( _snapshot( 2, workspaces=[_workspace(revision=2, status='archived')], ) ) async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) execution = await session.get(WorkspaceExecutionState, WORKSPACE_UUID) membership = await session.scalar(sqlalchemy.select(WorkspaceMembership)) assert workspace.status == 'archived' assert execution.state == 'inactive' assert execution.write_fenced is True assert membership.status == 'removed' await service.apply_snapshot(_snapshot(3, workspaces=[])) async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) execution = await session.get(WorkspaceExecutionState, WORKSPACE_UUID) assert workspace.status == 'archived' assert execution.state == 'inactive' assert execution.write_fenced is True async def test_event_poll_fetches_workspace_delta_and_records_receipt(projection_context): application, session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000001', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) provider = _Provider( [_snapshot(1)], [batch], [_delta(workspaces=[_workspace(revision=2, name='Updated Workspace')])], ) service = DirectoryProjectionService(application, provider, INSTANCE_UUID) await service.initialize() await service.sync_once() async with session_factory() as session: account = await session.scalar(sqlalchemy.select(User).where(User.uuid == ACCOUNT_UUID)) workspace = await session.get(Workspace, WORKSPACE_UUID) state = await session.get(DirectoryProjectionState, INSTANCE_UUID) inbox = await session.scalar(sqlalchemy.select(DirectoryProjectionInbox)) assert account.projection_revision == 1 assert workspace.name == 'Updated Workspace' assert state.cursor == 2 assert inbox.event_uuid == event.uuid assert inbox.applied_at is not None assert provider.snapshot_calls == 1 assert provider.delta_calls == 1 async def test_directory_delta_does_not_skip_unfetched_event_cursors(projection_context): application, session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000003', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=3, events=[event], ) provider = _Provider( [_snapshot(1)], [batch], [_delta(workspaces=[_workspace(revision=3, name='Latest Workspace')])], ) service = DirectoryProjectionService(application, provider, INSTANCE_UUID) await service.initialize() await service.sync_once() async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) state = await session.get(DirectoryProjectionState, INSTANCE_UUID) assert workspace.name == 'Latest Workspace' assert state.cursor == 2 async def test_entitlement_event_advances_cursor_without_refetching_directory(projection_context): application, session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000002', aggregate_uuid=WORKSPACE_UUID, event_type='entitlement.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'entitlement_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) provider = _Provider([_snapshot(1)], [batch]) service = DirectoryProjectionService(application, provider, INSTANCE_UUID) await service.initialize() await service.sync_once() await service.apply_snapshot(_snapshot(2, workspaces=[_workspace(revision=1)])) async with session_factory() as session: state = await session.get(DirectoryProjectionState, INSTANCE_UUID) inbox = await session.scalar(sqlalchemy.select(DirectoryProjectionInbox)) assert provider.snapshot_calls == 1 assert state.cursor == 2 assert inbox.event_uuid == event.uuid assert inbox.applied_at is not None async def test_missing_workspace_in_delta_fences_only_requested_workspace(projection_context): application, session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000004', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) provider = _Provider([_snapshot(1)], [batch], [_delta(workspaces=[])]) service = DirectoryProjectionService(application, provider, INSTANCE_UUID) await service.initialize() await service.sync_once() async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) execution = await session.get(WorkspaceExecutionState, WORKSPACE_UUID) membership = await session.scalar(sqlalchemy.select(WorkspaceMembership)) assert workspace.status == 'archived' assert workspace.projection_revision == 2 assert execution.state == 'inactive' assert execution.write_fenced is True assert execution.desired_state_revision == 2 assert membership.status == 'removed' assert membership.projection_revision == 2 stale_same_cursor_snapshot = _snapshot( 2, workspaces=[_workspace(revision=2, status='active')], ) with pytest.raises(DirectoryProjectionUnavailableError, match='conflicting contents'): await service.apply_snapshot(stale_same_cursor_snapshot) async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) execution = await session.get(WorkspaceExecutionState, WORKSPACE_UUID) assert workspace.status == 'archived' assert execution.write_fenced is True async def test_each_replica_consumes_events_with_its_own_cursor(projection_context): application, session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000005', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) updated = _delta(workspaces=[_workspace(revision=2, name='Replica-safe Workspace')]) first_provider = _Provider([_snapshot(1)], [batch], [updated]) second_provider = _Provider([_snapshot(1)], [batch], [updated]) first = DirectoryProjectionService(application, first_provider, INSTANCE_UUID) second = DirectoryProjectionService(application, second_provider, INSTANCE_UUID) await first.initialize() await second.initialize() await first.sync_once() await second.sync_once() await second.sync_once() async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) state = await session.get(DirectoryProjectionState, INSTANCE_UUID) inbox_rows = (await session.scalars(sqlalchemy.select(DirectoryProjectionInbox))).all() assert workspace.name == 'Replica-safe Workspace' assert state.cursor == 2 assert len(inbox_rows) == 1 assert first_provider.after_cursors == [1] assert second_provider.after_cursors == [1, 2] async def test_snapshot_coverage_allows_lagging_replica_to_replay_receipts(projection_context): application, session_factory = projection_context event_two = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000008', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) event_three = DirectoryEvent( cursor=3, uuid='40000000-0000-0000-0000-000000000009', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=3, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 3}, created_at=datetime.datetime(2026, 7, 24, 12, 3, tzinfo=datetime.UTC), ) batch_two = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=3, events=[event_two], ) batch_three = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=2, cursor=3, high_water_cursor=3, events=[event_three], ) lagging_provider = _Provider( [_snapshot(1)], [batch_two, batch_three], [ _delta(workspaces=[_workspace(revision=2, name='Intermediate Workspace')]), _delta(workspaces=[_workspace(revision=3, name='Current Workspace')]), ], ) lagging = DirectoryProjectionService(application, lagging_provider, INSTANCE_UUID) leading = DirectoryProjectionService( application, _Provider([_snapshot(3, workspaces=[_workspace(revision=3, name='Current Workspace')])]), INSTANCE_UUID, ) await lagging.initialize() await leading.initialize() await lagging.sync_once() await lagging.sync_once() lagging.require_ready() async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) state = await session.get(DirectoryProjectionState, INSTANCE_UUID) inbox_rows = ( await session.scalars(sqlalchemy.select(DirectoryProjectionInbox).order_by(DirectoryProjectionInbox.cursor)) ).all() assert workspace.name == 'Current Workspace' assert state.cursor == 3 assert state.snapshot_coverage_cursor == 3 assert [row.cursor for row in inbox_rows] == [2, 3] assert lagging_provider.after_cursors == [1, 2] async def test_initialize_retries_snapshot_superseded_by_another_replica(projection_context): application, _session_factory = projection_context leading = DirectoryProjectionService( application, _Provider([_snapshot(2)]), INSTANCE_UUID, ) await leading.initialize() racing_provider = _Provider([_snapshot(1), _snapshot(2)]) racing = DirectoryProjectionService(application, racing_provider, INSTANCE_UUID) await racing.initialize() await racing.sync_once() racing.require_ready() assert racing_provider.snapshot_calls == 2 assert racing_provider.after_cursors == [2] async def test_page_below_signed_high_water_does_not_renew_readiness(projection_context): application, _session_factory = projection_context monotonic = [10.0] event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000010', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=3, events=[event], ) service = DirectoryProjectionService( application, _Provider([_snapshot(1)], [batch], [_delta()]), INSTANCE_UUID, sync_interval_seconds=5, max_staleness_seconds=60, monotonic_time=lambda: monotonic[0], ) await service.initialize() await service.sync_once() monotonic[0] = 70.0 with pytest.raises(DirectoryProjectionUnavailableError, match='stale'): service.require_ready() async def test_empty_page_cannot_hide_shared_projection_progress(projection_context): application, _session_factory = projection_context lagging = DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, ) leading = DirectoryProjectionService( application, _Provider([_snapshot(2)]), INSTANCE_UUID, ) await lagging.initialize() await leading.initialize() with pytest.raises(DirectoryProjectionUnavailableError, match='has not consumed'): await lagging.sync_once() async def test_event_payload_revision_mismatch_fails_closed(projection_context): application, _session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000006', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 3}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) service = DirectoryProjectionService( application, _Provider([_snapshot(1)], [batch], [_delta()]), INSTANCE_UUID, ) await service.initialize() with pytest.raises(DirectoryProjectionUnavailableError, match='conflicting revision'): await service.sync_once() async def test_workspace_delta_older_than_event_fails_closed(projection_context): application, _session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000007', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=3, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 3}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) service = DirectoryProjectionService( application, _Provider([_snapshot(1)], [batch], [_delta(workspaces=[_workspace(revision=2)])]), INSTANCE_UUID, ) await service.initialize() with pytest.raises(DirectoryProjectionUnavailableError, match='older'): await service.sync_once() async def test_stale_workspace_tombstone_revision_fails_closed(projection_context): application, session_factory = projection_context event = DirectoryEvent( cursor=2, uuid='40000000-0000-0000-0000-000000000011', aggregate_uuid=WORKSPACE_UUID, event_type='directory.changed', revision=2, payload={'workspace_uuid': WORKSPACE_UUID, 'directory_revision': 2}, created_at=datetime.datetime(2026, 7, 24, 12, 2, tzinfo=datetime.UTC), ) batch = DirectoryEventBatch( instance_uuid=INSTANCE_UUID, after_cursor=1, cursor=2, high_water_cursor=2, events=[event], ) service = DirectoryProjectionService( application, _Provider( [_snapshot(1, workspaces=[_workspace(revision=3)])], [batch], [_delta(workspaces=[])], ), INSTANCE_UUID, ) await service.initialize() with pytest.raises(DirectoryProjectionUnavailableError, match='tombstone revision rolled back'): await service.sync_once() async with session_factory() as session: workspace = await session.get(Workspace, WORKSPACE_UUID) membership = await session.scalar(sqlalchemy.select(WorkspaceMembership)) execution = await session.get(WorkspaceExecutionState, WORKSPACE_UUID) assert workspace.status == 'active' assert membership.status == 'active' assert execution.write_fenced is False async def test_conflicting_account_projection_across_workspaces_fails_closed(projection_context): application, _session_factory = projection_context conflicting_member = DirectoryMember( membership_uuid=SECOND_MEMBERSHIP_UUID, account_uuid=ACCOUNT_UUID, normalized_email='owner@example.com', display_name='Workspace Owner', account_status='blocked', role='member', membership_status='active', projection_revision=99, joined_at=datetime.datetime(2026, 7, 24, tzinfo=datetime.UTC), ) second_workspace = DirectoryWorkspace( uuid=SECOND_WORKSPACE_UUID, name='Second Workspace', slug='second-workspace', type='team', status='active', created_by_account_uuid=ACCOUNT_UUID, projection_revision=99, execution_generation=1, members=[conflicting_member], ) snapshot = _snapshot( 1, workspaces=[_workspace(revision=1), second_workspace], ) service = DirectoryProjectionService(application, _Provider([snapshot]), INSTANCE_UUID) with pytest.raises(DirectoryProjectionUnavailableError, match='conflicting account projections'): await service.initialize() async def test_account_projection_revision_advances_when_account_fields_change(projection_context): application, session_factory = projection_context service = DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, ) await service.initialize() blocked_member = _member(revision=2).model_copy(update={'account_status': 'blocked'}) blocked_workspace = _workspace(revision=2).model_copy(update={'members': (blocked_member,)}) await service.apply_snapshot(_snapshot(2, workspaces=[blocked_workspace])) async with session_factory() as session: account = await session.scalar(sqlalchemy.select(User).where(User.uuid == ACCOUNT_UUID)) assert account.status == 'disabled' assert account.projection_revision == 2 async def test_readiness_expires_on_monotonic_staleness(projection_context): application, _session_factory = projection_context monotonic = [10.0] service = DirectoryProjectionService( application, _Provider([_snapshot(1)]), INSTANCE_UUID, sync_interval_seconds=5, max_staleness_seconds=60, monotonic_time=lambda: monotonic[0], ) await service.initialize() service.require_ready() monotonic[0] = 70.0 with pytest.raises(DirectoryProjectionUnavailableError, match='stale'): service.require_ready() async def test_snapshot_for_another_instance_is_rejected(projection_context): application, _session_factory = projection_context other = _snapshot(1).model_copy(update={'instance_uuid': 'other-instance'}) service = DirectoryProjectionService(application, _Provider([other]), INSTANCE_UUID) with pytest.raises(DirectoryProjectionUnavailableError, match='another LangBot instance'): await service.initialize()