# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-mcp-server-refuse-to-resume-a-streamable-http-session-after-a-transport-level-reconnect.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-mcp-server-refuse-to-resume-a-streamable-http-session-after-a-transport-level-reconnect.warrant.json
# Markdown SHA-256:   af79cee5fae078197d50d3d3627716bb754b179e8d324b300339d6d458f5e0a0
# Code AST SHA-256:   dc06978b9b5a4226d5d6faab2d571cec41b752a2692e404af87be1223053e461
# Verified Classes:   ResumeGapError, SessionEventLog
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class ResumeGapError(RuntimeError):
    """Raised when a resume cursor points into evicted session history."""
    def __init__(self, cursor: int, floor_id: int, head_id: int):
        self.cursor = cursor
        self.floor_id = floor_id
        self.head_id = head_id
        super().__init__(
            f"resume_unavailable: cursor={cursor} precedes retained floor={floor_id} "
            f"(head={head_id}); session must be re-established"
        )


class SessionEventLog:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self._events: list[tuple[int, dict]] = []
        self._next_id = 1

    @property
    def floor_id(self) -> int:
        return self._events[0][0] if self._events else self._next_id

    @property
    def head_id(self) -> int:
        return self._next_id - 1

    def append(self, event: dict) -> int:
        eid = self._next_id
        self._next_id += 1
        self._events.append((eid, event))
        if len(self._events) > self.capacity:
            self._events.pop(0)  # eviction is allowed; resume across it is not
        return eid

    def resume_from(self, cursor: int) -> list[tuple[int, dict]]:
        # Invariant: the window (cursor, head] must be fully retained and contiguous.
        if cursor < self.floor_id - 1:
            raise ResumeGapError(cursor, self.floor_id, self.head_id)
        return [(eid, ev) for eid, ev in self._events if eid > cursor]
