"""Internal AP inbox dispatch for synchronous inter-service writes. Each service can register handlers for (activity_type, object_type) pairs. When an internal AP activity arrives, it is routed to the matching handler. This mirrors the federated ``dispatch_inbox_activity()`` pattern but for HMAC-authenticated internal service-to-service calls. """ from __future__ import annotations import logging from typing import Callable, Awaitable from sqlalchemy.ext.asyncio import AsyncSession log = logging.getLogger(__name__) # Registry: (activity_type, object_type) → async handler(session, body) → dict _internal_handlers: dict[tuple[str, str], Callable[[AsyncSession, dict], Awaitable[dict]]] = {} def register_internal_handler( activity_type: str, object_type: str, handler: Callable[[AsyncSession, dict], Awaitable[dict]], ) -> None: """Register a handler for an internal AP activity type + object type pair.""" key = (activity_type, object_type) if key in _internal_handlers: log.warning("Overwriting internal handler for %s", key) _internal_handlers[key] = handler async def dispatch_internal_activity(session: AsyncSession, body: dict) -> dict: """Route an internal AP activity to the correct handler. Returns the handler result dict. Raises ValueError for unknown types. """ activity_type = body.get("type", "") obj = body.get("object") or {} object_type = obj.get("type", "") if isinstance(obj, dict) else "" key = (activity_type, object_type) handler = _internal_handlers.get(key) if handler is None: raise ValueError(f"No internal handler for {key}") log.info("Dispatching internal activity %s", key) return await handler(session, body)