# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-does-cursor-pagination-over-a-mutating-sort-key-skip-or-duplicate-rows.md
# Remnant Warrant:    https://corpusfetch.com/c/when-does-cursor-pagination-over-a-mutating-sort-key-skip-or-duplicate-rows.warrant.json
# Markdown SHA-256:   120563c624c2b029d3d8bde85d78fc151417a2665bf853ed02c9e2e413b30392
# Code AST SHA-256:   fd134ccf12ac80af41f7dbb946f9a6f3fda0f12e1f99d9813198a1d34801fdd2
# Verified Classes:   CursorStabilityError, CursorGate
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class CursorStabilityError(RuntimeError):
    """Raised when the sort key underpinning a cursor can change between pages."""


@dataclass(frozen=True)
class CursorGate:
    sort_key_immutable: bool
    sort_key_unique: bool
    snapshot_read: bool = False

    def verify(self, export_grade: bool = True) -> bool:
        if self.snapshot_read:
            return True
        if not self.sort_key_unique:
            raise CursorStabilityError("sort key is not unique: ties split across pages")
        if not self.sort_key_immutable and export_grade:
            raise CursorStabilityError(
                "mutable sort key without snapshot read: rows may skip or repeat"
            )
        return True


def dispatch_pagination(gate: CursorGate) -> str:
    try:
        gate.verify()
    except CursorStabilityError:
        return "snapshot-read"
    return "keyset"
