diff --git a/gently/mesh/capability_provider.py b/gently/mesh/capability_provider.py index 7dfc6fd1..d82f691c 100644 --- a/gently/mesh/capability_provider.py +++ b/gently/mesh/capability_provider.py @@ -64,7 +64,8 @@ def _get_system_info() -> dict[str, Any]: if platform.system() == "Windows": import ctypes - kernel32 = ctypes.windll.kernel32 + # ctypes.windll exists only on Windows; this branch is platform-guarded. + kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] mem_status = ctypes.c_ulonglong() kernel32.GetPhysicallyInstalledSystemMemory(ctypes.byref(mem_status)) ram_gb = round(mem_status.value / (1024 * 1024), 1) diff --git a/gently/mesh/discovery.py b/gently/mesh/discovery.py index 878a9b81..ca6394ab 100644 --- a/gently/mesh/discovery.py +++ b/gently/mesh/discovery.py @@ -76,7 +76,10 @@ def __init__( self._known_ids: set = set() self.transport: asyncio.DatagramTransport | None = None - def connection_made(self, transport: asyncio.DatagramTransport): + def connection_made(self, transport: asyncio.BaseTransport) -> None: + # DatagramProtocol always receives a DatagramTransport here; narrow for + # the typed attribute (and matches the asyncio.BaseProtocol signature). + assert isinstance(transport, asyncio.DatagramTransport) self.transport = transport def datagram_received(self, data: bytes, addr: tuple): diff --git a/gently/mesh/peer_client.py b/gently/mesh/peer_client.py index c6db282b..b7fd81a7 100644 --- a/gently/mesh/peer_client.py +++ b/gently/mesh/peer_client.py @@ -29,7 +29,7 @@ def __init__(self, pairing_manager=None, audit_log=None): self._audit_log = audit_log self._pinning_verified: set = set() # track first-success per peer - async def _ensure_session(self): + async def _ensure_session(self) -> aiohttp.ClientSession: if self._session is None or self._session.closed: timeout = aiohttp.ClientTimeout(total=settings.mesh.fetch_timeout_s) # Use permissive SSL context — we verify by cert fingerprint, not CA chain @@ -39,6 +39,7 @@ async def _ensure_session(self): connector = aiohttp.TCPConnector(ssl=ssl_ctx) self._session = aiohttp.ClientSession(timeout=timeout, connector=connector) self._pinning_verified.clear() + return self._session def _auth_headers(self, peer: PeerInfo) -> dict[str, str]: """Build auth headers for a trusted peer.""" @@ -103,12 +104,12 @@ async def fetch_peer_info(self, peer: PeerInfo) -> dict[str, Any] | None: Returns the parsed JSON dict on success, or None on failure. """ - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/mesh/status" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return await resp.json() @@ -126,12 +127,12 @@ async def fetch_peer_info(self, peer: PeerInfo) -> dict[str, Any] | None: async def fetch_peer_campaigns(self, peer: PeerInfo) -> list | None: """GET /api/campaigns from a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/campaigns" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) data = await resp.json() @@ -144,12 +145,12 @@ async def fetch_peer_campaigns(self, peer: PeerInfo) -> list | None: async def fetch_campaign_export(self, peer: PeerInfo, campaign_id: str) -> dict | None: """GET /api/campaigns/{id}/export from a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/campaigns/{campaign_id}/export" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return await resp.json() @@ -167,12 +168,12 @@ async def join_campaign( hostname: str, ) -> bool: """POST /api/campaigns/{id}/join on a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/campaigns/{campaign_id}/join" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.post( + async with session.post( url, json={ "instance_id": instance_id, @@ -199,12 +200,12 @@ async def claim_item( hostname: str, ) -> bool: """POST /api/campaigns/{id}/items/{item_id}/claim on a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/campaigns/{campaign_id}/items/{item_id}/claim" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.post( + async with session.post( url, json={ "instance_id": instance_id, @@ -229,12 +230,12 @@ async def unclaim_item( item_id: str, ) -> bool: """POST /api/campaigns/{id}/items/{item_id}/unclaim on a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/campaigns/{campaign_id}/items/{item_id}/unclaim" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.post(url, headers=headers, ssl=ssl_fp) as resp: + async with session.post(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return resp.status == 200 @@ -253,7 +254,7 @@ async def update_item_status( outcome: str | None = None, ) -> bool: """POST /api/campaigns/{id}/items/{item_id}/status on a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/campaigns/{campaign_id}/items/{item_id}/status" body: dict[str, Any] = {"status": status} if outcome is not None: @@ -261,7 +262,7 @@ async def update_item_status( headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.post(url, json=body, headers=headers, ssl=ssl_fp) as resp: + async with session.post(url, json=body, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return resp.status == 200 @@ -305,15 +306,15 @@ async def _pairing_request( self, peer: PeerInfo, method: str, path: str, json_body: dict | None = None ) -> dict | None: """Make an HTTP request trying HTTPS first, then HTTP (for pre-pairing).""" - await self._ensure_session() + session = await self._ensure_session() base = f"{peer.ip_address}:{peer.viz_port}" for scheme in ("https", "http"): url = f"{scheme}://{base}{path}" try: if method == "GET": - req = self._session.get(url) + req = session.get(url) else: - req = self._session.post(url, json=json_body or {}) + req = session.post(url, json=json_body or {}) async with req as resp: if resp.status == 200: return await resp.json() @@ -350,12 +351,12 @@ async def confirm_pair_remote( async def fetch_peer_sessions(self, peer: PeerInfo) -> list | None: """GET /api/data/sessions from a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/data/sessions" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) data = await resp.json() @@ -368,12 +369,12 @@ async def fetch_peer_sessions(self, peer: PeerInfo) -> list | None: async def fetch_peer_session_detail(self, peer: PeerInfo, session_id: str) -> dict | None: """GET /api/data/sessions/{id} from a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/data/sessions/{session_id}" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return await resp.json() @@ -385,12 +386,12 @@ async def fetch_peer_session_detail(self, peer: PeerInfo, session_id: str) -> di async def fetch_peer_coverage(self, peer: PeerInfo) -> dict | None: """GET /api/data/coverage from a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/data/coverage" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return await resp.json() @@ -402,12 +403,12 @@ async def fetch_peer_coverage(self, peer: PeerInfo) -> dict | None: async def fetch_peer_stage_distribution(self, peer: PeerInfo) -> dict | None: """GET /api/data/stages from a peer.""" - await self._ensure_session() + session = await self._ensure_session() url = f"{peer.base_url}/api/data/stages" headers = self._auth_headers(peer) ssl_fp = self._ssl_for_peer(peer) try: - async with self._session.get(url, headers=headers, ssl=ssl_fp) as resp: + async with session.get(url, headers=headers, ssl=ssl_fp) as resp: if resp.status == 200: self._log_pinning_success(peer, ssl_fp) return await resp.json() diff --git a/pyproject.toml b/pyproject.toml index 561326b8..f4f8e417 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -218,9 +218,6 @@ module = [ "gently.harness.session.timeline", "gently.harness.tools.registry", "gently.log_config", - "gently.mesh.capability_provider", - "gently.mesh.discovery", - "gently.mesh.peer_client", "gently.ml.data_loader", "gently.ml.federated", "gently.organisms.celegans.developmental_tracker",