"""Contract-first mapping spec for future v2 backend adapters. This module intentionally does not call OpenViking, EverMemOS, or Obsidian. It documents the stable Gateway control-plane fields that may be persisted in outbox payload refs, SQLite metadata_json, audit summaries, and related control records. It is not a validator for transient runtime adapter request objects: the Gateway may pass conversation content to a backend during the current request lifecycle, but must not persist that content in SQLite/outbox/audit control-plane records. """ from __future__ import annotations from typing import Final, NamedTuple from .backend_contracts import BackendCommitResult, BackendOperation, BackendRetrieveResult, BackendWriteResult from .schemas_v2 import BackendType CONTROL_PLANE_PERSISTED_PAYLOAD_FIELDS: Final[frozenset[str]] = frozenset( { "event_id", "gateway_id", "workspace_id", "user_id", "agent_id", "session_id", "turn_id", "namespace", "source_type", "source_event_id", "backend_type", "operation", "payload_ref", "metadata", "trace", "role", } ) CONTROL_PLANE_PAYLOAD_FIELDS: Final[frozenset[str]] = CONTROL_PLANE_PERSISTED_PAYLOAD_FIELDS DISALLOWED_PAYLOAD_FIELDS: Final[frozenset[str]] = frozenset( { "content", "raw_request", "messages", "conversation", "transcript", } ) class AdapterMappingSpec(NamedTuple): backend_type: BackendType operation: BackendOperation adapter_method: str backend_capability: str result_model: type[BackendWriteResult] | type[BackendCommitResult] | type[BackendRetrieveResult] allowed_payload_fields: frozenset[str] = CONTROL_PLANE_PERSISTED_PAYLOAD_FIELDS ADAPTER_MAPPING_SPECS: Final[tuple[AdapterMappingSpec, ...]] = ( AdapterMappingSpec( backend_type=BackendType.OPENVIKING, operation=BackendOperation.INGEST_TURN, adapter_method="ingest_conversation_turn", backend_capability="session archive append / resource context organization", result_model=BackendWriteResult, ), AdapterMappingSpec( backend_type=BackendType.OPENVIKING, operation=BackendOperation.COMMIT_SESSION, adapter_method="commit_session_v2", backend_capability="session commit and session archive ref creation", result_model=BackendCommitResult, ), AdapterMappingSpec( backend_type=BackendType.OPENVIKING, operation=BackendOperation.RETRIEVE_CONTEXT, adapter_method="retrieve_context_v2", backend_capability="runtime session/resource context retrieval", result_model=BackendRetrieveResult, ), AdapterMappingSpec( backend_type=BackendType.EVERMEMOS, operation=BackendOperation.INGEST_TURN, adapter_method="ingest_message", backend_capability="message-level memory ingestion", result_model=BackendWriteResult, ), AdapterMappingSpec( backend_type=BackendType.EVERMEMOS, operation=BackendOperation.COMMIT_SESSION, adapter_method="extract_profile_long_term_v2", backend_capability="episodic/profile/long-term extraction", result_model=BackendCommitResult, ), AdapterMappingSpec( backend_type=BackendType.EVERMEMOS, operation=BackendOperation.RETRIEVE_CONTEXT, adapter_method="retrieve_context_v2", backend_capability="episodic/profile/long-term memory retrieval", result_model=BackendRetrieveResult, ), AdapterMappingSpec( backend_type=BackendType.OBSIDIAN, operation=BackendOperation.CREATE_REVIEW_DRAFT, adapter_method="create_review_draft_v2", backend_capability="human review draft creation for high-risk/high-conflict candidates", result_model=BackendWriteResult, ), ) def get_adapter_mapping_spec(backend_type: BackendType, operation: BackendOperation) -> AdapterMappingSpec: for spec in ADAPTER_MAPPING_SPECS: if spec.backend_type == backend_type and spec.operation == operation: return spec raise KeyError(f"No v2 adapter mapping for {backend_type.value}:{operation.value}") def validate_control_plane_payload(payload: dict[str, object]) -> None: """Validate only persisted control-plane payloads, not runtime adapter requests.""" blocked = sorted(DISALLOWED_PAYLOAD_FIELDS.intersection(payload)) if blocked: raise ValueError(f"Control-plane persisted payload includes disallowed content fields: {', '.join(blocked)}") def validate_control_plane_persisted_payload(payload: dict[str, object]) -> None: validate_control_plane_payload(payload)