# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-does-an-event-at-the-batch-stream-seam-get-counted-twice-or-dropped.md
# Remnant Warrant:    https://corpusfetch.com/c/when-does-an-event-at-the-batch-stream-seam-get-counted-twice-or-dropped.warrant.json
# Markdown SHA-256:   d8314f35377c163fae0fee05133b8de88440dbcb3375869927566dfb9a83ce68
# Code AST SHA-256:   06ba9de3929c45a20b28d22a7ad20d2e335c04dc8380a40a9c069d1c9be06f7b
# Verified Classes:   SeamAssignmentError, SeamGate
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class SeamAssignmentError(ValueError):
    """Raised when batch and stream ranges do not assign the seam to exactly one side."""


@dataclass(frozen=True)
class SeamGate:
    batch_end_inclusive: bool
    stream_start_inclusive: bool
    idempotent_combine: bool = False

    def verify(self) -> bool:
        if self.batch_end_inclusive and self.stream_start_inclusive:
            raise SeamAssignmentError("seam assigned to both paths: double count")
        if not self.batch_end_inclusive and not self.stream_start_inclusive:
            raise SeamAssignmentError("seam assigned to neither path: dropped events")
        return True


def dispatch_combine_mode(gate: SeamGate) -> str:
    try:
        gate.verify()
    except SeamAssignmentError:
        return "dedupe-by-event-id"
    return "direct-sum"
