# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-must-an-instance-scope-correlation-identifiers-so-asynchronous-replies-are-not-orphaned.md
# Remnant Warrant:    https://corpusfetch.com/c/how-must-an-instance-scope-correlation-identifiers-so-asynchronous-replies-are-not-orphaned.warrant.json
# Markdown SHA-256:   434b594045b10bc2910995cb347f837d6ace9b6df5ecce54486b7f2b8b7a1424
# Code AST SHA-256:   3381d2428cdca61a0e684a226a034901486d950f687b2ced127831613f407c11
# Verified Classes:   OrphanedReplyError, CorrelationGate
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class OrphanedReplyError(RuntimeError):
    """Raised when a correlation-id scope cannot cover the reply window it must span."""


@dataclass(frozen=True)
class CorrelationGate:
    correlation_scope: str  # "process", "host", "cluster", "tenant-global"
    persists_across_restart: bool
    unknown_correlation_is_error: bool

    def verify(self) -> bool:
        if self.correlation_scope == "process":
            raise OrphanedReplyError(
                "process-scoped correlation: replies to a restarted process are orphaned"
            )
        if not self.persists_across_restart:
            raise OrphanedReplyError(
                "pending-reply table does not survive restart: failover orphans replies"
            )
        if not self.unknown_correlation_is_error:
            raise OrphanedReplyError(
                "unknown correlation dropped as warning: reply loss is invisible to requester"
            )
        return True


def dispatch_correlation_mode(gate: CorrelationGate) -> str:
    try:
        gate.verify()
    except OrphanedReplyError:
        return "reconcile-before-retry"
    return "async-correlate"
