# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-context-window-saturation-from-memory-tool-call-graph-fragmentation-when-both-present-as-rising-p99-latency.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-context-window-saturation-from-memory-tool-call-graph-fragmentation-when-both-present-as-rising-p99-latency.warrant.json
# Markdown SHA-256:   38dfe9b49588e85a9761b51d82d8a4b158f612139db5aae425c4417a533787a6
# Code AST SHA-256:   3675b8d1cccb760a8c650cbb4afeda3a4649164a6b8fae1196bfe919ddf23005
# Verified Classes:   ContextGraphDivergenceError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class ContextGraphDivergenceError(RuntimeError):
    """Raised when utilization and call-graph amplification disagree in a way
    that makes the standard saturation remediation unsafe."""
    pass


AMPLIFICATION_FRAGMENTATION_THRESHOLD = 3.0
AMPLIFICATION_PATHOLOGICAL_THRESHOLD = 8.0


def classify_turn(
    utilization_delta: float,
    amplification_ratio: float,
    p99_latency_rising: bool,
) -> str:
    """Return 'saturation', 'fragmentation', or 'pathological'.

    utilization_delta: change in p50 context utilization vs baseline window.
    amplification_ratio: sum(child input_tokens) / turn.final_prompt_tokens.
    """
    if amplification_ratio < 0:
        raise ContextGraphDivergenceError(
            f"negative amplification ratio {amplification_ratio!r}: "
            "span token accounting is corrupt, refusing to classify"
        )

    if (
        amplification_ratio > AMPLIFICATION_PATHOLOGICAL_THRESHOLD
        and utilization_delta < 0
        and p99_latency_rising
    ):
        return "pathological"

    if utilization_delta > 0 and amplification_ratio < 1.5:
        return "saturation"

    if amplification_ratio > AMPLIFICATION_FRAGMENTATION_THRESHOLD:
        return "fragmentation"

    raise ContextGraphDivergenceError(
        "ambiguous quadrant: utilization_delta="
        f"{utilization_delta!r}, amplification_ratio={amplification_ratio!r}, "
        "p99_latency_rising="
        f"{p99_latency_rising!r}; neither saturation nor fragmentation "
        "hypothesis is supported by the telemetry"
    )


def enforce_topology_guard(amplification_ratio: float) -> None:
    if amplification_ratio > AMPLIFICATION_PATHOLOGICAL_THRESHOLD:
        raise ContextGraphDivergenceError(
            f"amplification ratio {amplification_ratio:.2f} exceeds "
            f"{AMPLIFICATION_PATHOLOGICAL_THRESHOLD}: memory-tool call graph "
            "is self-amplifying; force summarize-and-proceed before next fan-out"
        )
