# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-silent-event-loss-when-resuming-mcp-http-native-sse-streams-under-per-tool-oauth-resource-indicators.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-event-loss-when-resuming-mcp-http-native-sse-streams-under-per-tool-oauth-resource-indicators.warrant.json
# Markdown SHA-256:   b3cfcc1f57fa7301b4ac48a022e61ea047ee2aa4957598f6aee4c2b1d6566306
# Code AST SHA-256:   a1fa324a929ee41a31684a25a8d34fb89a7275382a5f9b73fcb9944d1c4b156c
# Verified Classes:   StreamResumeAuthorizationError, BufferedEvent, ResumeRequest
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field


class StreamResumeAuthorizationError(RuntimeError):
    """Raised when a resumed SSE stream cannot legally replay a buffered event."""


@dataclass
class BufferedEvent:
    event_id: int
    tool_resource_indicator: str  # e.g. "https://mcp.example.com/tools/fetch"


@dataclass
class ResumeRequest:
    session_id: str
    last_event_id: int
    token_audiences: frozenset[str]
    buffer: list[BufferedEvent] = field(default_factory=list)


def plan_replay(req: ResumeRequest) -> list[BufferedEvent]:
    """
    Decide which buffered events may be replayed to a resuming client.

    Invariant: the resume token's `aud` set must be a superset of the resource
    indicators of every buffered event at or after the cursor. If it is not,
    we must NOT silently skip the event (that loses tool results) and we must
    NOT advance the cursor past it. We raise so the caller can emit a resumable
    error frame and leave `Last-Event-ID` at the last delivered event.
    """
    pending = [e for e in req.buffer if e.event_id > req.last_event_id]
    if not pending:
        return []

    required = {e.tool_resource_indicator for e in pending}
    missing = required - req.token_audiences
    if missing:
        offending = next(e for e in pending if e.tool_resource_indicator in missing)
        raise StreamResumeAuthorizationError(
            f"cannot resume session {req.session_id!r} at event "
            f"{req.last_event_id}: buffered event {offending.event_id} requires "
            f"resource indicator {offending.tool_resource_indicator!r} not present "
            f"in token audience {sorted(req.token_audiences)!r}; "
            f"re-authorize with a superset audience or abandon the stream"
        )

    return pending


def handle_resume(req: ResumeRequest) -> list[BufferedEvent]:
    try:
        return plan_replay(req)
    except StreamResumeAuthorizationError:
        # Caller must emit `event: error` and keep the client's cursor unchanged.
        raise
