# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-auto-compaction-of-agent-transcripts-rather-than-accept-semantic-loss-bounds.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-auto-compaction-of-agent-transcripts-rather-than-accept-semantic-loss-bounds.warrant.json
# Markdown SHA-256:   ee92aff75e4a457748b2c5f9080d7862280a335e46e0f45272a7b8f7c5c4ef3c
# Code AST SHA-256:   4b7b68796a94c4d3be8492b559abf594531a3c9d55f1220c424d36eb05243fdc
# Verified Classes:   CompactionRefusedError, Span
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Iterable


class CompactionRefusedError(RuntimeError):
    """Raised when auto-compaction would evict a rehydration anchor."""


@dataclass(frozen=True)
class Span:
    span_id: str
    kind: str          # "reasoning" | "tool_result" | "state_assertion" | ...
    text: str
    epoch: int


ANCHOR_KINDS = frozenset({"tool_result", "state_assertion", "file_write_ack"})


def assert_compaction_safe(
    eviction_set: Iterable[Span],
    rehydration_index_epoch: int,
    current_epoch: int,
    index_rebuild_window: int = 2,
) -> None:
    """Enforce the refusal boundary before applying an epsilon-bounded compaction.

    Refuses when (a) an anchor span is in the eviction set, or (b) the
    rehydration index was rebuilt from post-compaction text too recently
    for the anchor set to be a guaranteed superset of the eviction set.
    """
    evicted = list(eviction_set)

    # Condition (b): index freshness relative to the compaction epoch.
    if current_epoch - rehydration_index_epoch <= index_rebuild_window:
        raise CompactionRefusedError(
            f"rehydration index rebuilt at epoch {rehydration_index_epoch}, "
            f"current epoch {current_epoch}: anchor set not guaranteed to "
            f"cover eviction set; spill losslessly instead of compacting"
        )

    # Condition (a): structural anchor eviction.
    anchors = [s for s in evicted if s.kind in ANCHOR_KINDS]
    if anchors:
        ids = ", ".join(s.span_id for s in anchors)
        raise CompactionRefusedError(
            f"compaction would evict {len(anchors)} state-establishing "
            f"anchor(s) [{ids}]; epsilon bound does not cover dangling "
            f"rehydration pointers — refuse and spill losslessly"
        )


def maybe_compact(
    eviction_set: Iterable[Span],
    rehydration_index_epoch: int,
    current_epoch: int,
) -> str:
    try:
        assert_compaction_safe(
            eviction_set=eviction_set,
            rehydration_index_epoch=rehydration_index_epoch,
            current_epoch=current_epoch,
        )
    except CompactionRefusedError:
        # Escalate to lossless spill; do not accept the epsilon bound.
        return "spill_lossless"
    return "compact"
