# 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-transport-sessions-across-an-sse-to-streamable-http-migration.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-event-loss-when-resuming-mcp-http-native-transport-sessions-across-an-sse-to-streamable-http-migration.warrant.json
# Markdown SHA-256:   02cac30206ed0b9b78ad075a39e04f62e0c778927805f0839d445fa630b1e013
# Code AST SHA-256:   3e52f7fe6d536c8bd27c9bcdc29a7b53c752375505a3dfb5661d906571cb3f73
# Verified Classes:   ResumeGapError, EventLog
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class ResumeGapError(RuntimeError):
    """Raised when a client's Last-Event-ID cannot be proven contiguous with the log head."""
    def __init__(self, session_id: str, last_event_id: str, log_head: str):
        self.session_id = session_id
        self.last_event_id = last_event_id
        self.log_head = log_head
        super().__init__(
            f"session {session_id}: cursor {last_event_id} is not contiguous "
            f"with log head {log_head}; refusing silent resume"
        )


class EventLog:
    def __init__(self, retained_ids: list[str]):
        # Ordered, durable ids currently retained for the session.
        self._ids = retained_ids

    def head(self) -> str:
        return self._ids[-1]

    def contains(self, event_id: str) -> bool:
        return event_id in self._ids

    def contiguous_from(self, event_id: str) -> bool:
        if not self.contains(event_id):
            return False
        idx = self._ids.index(event_id)
        # No holes between cursor and head.
        return idx == len(self._ids) - 1 or all(
            self._ids[i] != self._ids[i + 1] for i in range(idx, len(self._ids) - 1)
        )


def resume_session(session_id: str, last_event_id: str, log: EventLog) -> list[str]:
    if last_event_id is None:
        # Fresh subscription: no cursor, no continuity claim.
        return list(log._ids)

    if not log.contains(last_event_id):
        raise ResumeGapError(session_id, last_event_id, log.head())

    if not log.contiguous_from(last_event_id):
        raise ResumeGapError(session_id, last_event_id, log.head())

    idx = log._ids.index(last_event_id)
    return list(log._ids[idx + 1:])
