from dataclasses import dataclass from typing import Protocol from uuid import UUID from domain.sandbox import SandboxSession from usecase.interface import Logger, Metrics, Tracer class SandboxSessionStateSource(Protocol): def list_active_sessions(self) -> list[SandboxSession]: ... class SandboxSessionRegistry(Protocol): def replace_all(self, sessions: list[SandboxSession]) -> None: ... def count_active(self) -> int: ... @dataclass(frozen=True, slots=True) class SandboxSessionReconciler: state_source: SandboxSessionStateSource registry: SandboxSessionRegistry logger: Logger metrics: Metrics tracer: Tracer def execute(self) -> list[SandboxSession]: with self.tracer.start_span( 'adapter.sandbox.reconcile_sessions', ) as span: try: sessions_by_chat_id: dict[UUID, SandboxSession] = {} discovered_sessions = self.state_source.list_active_sessions() span.set_attribute('sandbox.discovered_count', len(discovered_sessions)) for session in sorted( discovered_sessions, key=lambda item: item.created_at, ): sessions_by_chat_id[session.chat_id] = session sessions = list(sessions_by_chat_id.values()) self.registry.replace_all(sessions) active_count = self.registry.count_active() self.metrics.set('sandbox.active.count', active_count) span.set_attribute('sandbox.active_count', active_count) span.set_attribute('sandbox.result', 'reconciled') self.logger.info( 'sandbox_reconciled', attrs={ 'session_count': active_count, }, ) return sessions except Exception as exc: span.set_attribute('sandbox.result', 'error') span.record_error(exc) raise