# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-stream-ended-cleanly-from-stream-was-abandoned-under-mcp-http-native-transport-when-the-server-is-stateless-and-dis.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-stream-ended-cleanly-from-stream-was-abandoned-under-mcp-http-native-transport-when-the-server-is-stateless-and-dis.warrant.json
# Markdown SHA-256:   a430224c74a2785deabc2104037c99aff03e8e1ede67fe29e8255f2cfe042e7a
# Code AST SHA-256:   e98dd4feb633975890781971740a3be7daee1b23723463b9896bd4e3d4423ec6
# Verified Classes:   StreamCompletionAmbiguousError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class StreamCompletionAmbiguousError(RuntimeError):
    """Raised when a stateless MCP stream closes without a terminal sentinel
    and the payload is not self-delimiting, making clean termination and
    abandonment operationally indistinguishable."""
    pass


def classify_stream_outcome(
    *,
    saw_terminal_sentinel: bool,
    payload_self_delimiting: bool,
    payload_integrity_ok: bool,
    discovery_sticky: bool,
) -> str:
    """Classify a closed MCP HTTP-native stream into a recovery regime.

    Returns one of: 'clean', 'abandoned', 'ambiguous'.
    Raises StreamCompletionAmbiguousError when the regime is indistinguishable
    and no safe recovery action exists.
    """
    if saw_terminal_sentinel:
        # Regime A: sentinel is authoritative.
        return "clean"

    if payload_self_delimiting:
        # Regime B: content integrity decides, transport close is irrelevant.
        return "clean" if payload_integrity_ok else "abandoned"

    # Regime C: neither sentinel nor self-delimiting framing.
    if discovery_sticky:
        # A sticky resolver *might* let us probe the prior attempt, but the
        # stream contract still does not guarantee resumability. We refuse to
        # guess.
        raise StreamCompletionAmbiguousError(
            "stream closed without terminal sentinel and payload is not "
            "self-delimiting; sticky discovery does not restore resumability "
            "semantics"
        )

    # Stateless, per-request discovery, no sentinel, no self-delimiting frame.
    # Every close is possibly-abandoned. Do not silently retry: a retry against
    # a freshly discovered endpoint will mask the truncation.
    raise StreamCompletionAmbiguousError(
        "stateless MCP stream closed at a chunk boundary with no terminal "
        "sentinel and no self-delimiting payload; clean termination and "
        "abandonment are indistinguishable — failing closed instead of "
        "retrying against re-resolved discovery"
    )
