"""Source connector Protocol and shared item types. A SourceConnector adapts a third-party data source (Gmail, Slack, ...) to the shared ScoutEngine interface. Each connector owns: * how to enumerate new items since the last poll (``list_new``) * how to fetch a single item's metadata cheaply (``fetch_metadata``) * how to fetch a single item's full content for in-memory triage (``fetch_content``) — this content MUST NOT be persisted by the engine * how to archive/trash an item (``archive``) for spam handling * optional push-notification setup (``setup_watch`` / ``renew_watch``) """ from __future__ import annotations from datetime import datetime from typing import Literal, Protocol from pydantic import BaseModel, Field class ItemRef(BaseModel): source_msg_ref: str received_at: datetime | None = None class ItemMetadata(BaseModel): subject: str | None = None sender: str | None = None snippet: str | None = None received_at: datetime | None = None class ItemContent(BaseModel): metadata: ItemMetadata body_text: str raw_headers: dict[str, str] = Field(default_factory=dict) class TriageVerdict(BaseModel): verdict: Literal["relevant", "spam"] reason: str confidence: float = Field(ge=0.0, le=1.0) class SourceConnector(Protocol): """Adapter for a third-party data source (Gmail, Slack, ...).""" source_type: str # e.g. "gmail" async def list_new(self, scout) -> list[ItemRef]: ... async def fetch_metadata(self, scout, ref: ItemRef) -> ItemMetadata: ... async def fetch_content(self, scout, ref: ItemRef) -> ItemContent: ... async def archive(self, scout, ref: ItemRef) -> None: ... async def setup_watch(self, scout) -> None: ... async def renew_watch(self, scout) -> None: ...