# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-prune-or-rewrite-its-own-context-window-mid-task.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-prune-or-rewrite-its-own-context-window-mid-task.warrant.json
# Markdown SHA-256:   310c062d66527fc85b0c1b820c96e24ceadf370339e096b06ac48ae70d8c414d
# Code AST SHA-256:   92714270f30362c0765b36723901038288bf54499dc7101038fae83f3c7fcdc6
# Verified Classes:   UnsafeContextMutationError, Span, ContextWindow
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Optional


class UnsafeContextMutationError(RuntimeError):
    """Raised when a lossy self-mutation would destroy a load-bearing span
    with no verifiable external copy while an obligation remains open."""
    pass


@dataclass
class Span:
    span_id: str
    literal_bearing: bool          # contains exact literals a later step may need
    external_copy: Optional[str] = None   # pointer to durable copy, if any
    pinned: bool = False           # constraint / pointer-index spans


@dataclass
class ContextWindow:
    spans: dict = field(default_factory=dict)
    open_obligations: int = 0
    pinned_pointer_index: bool = True

    def mutate(self, span_id: str, *, lossy: bool) -> None:
        span = self.spans.get(span_id)
        if span is None:
            raise UnsafeContextMutationError(f"unknown span {span_id!r}")

        # Re-ranking / reordering is always safe.
        if not lossy:
            return

        # Lossy mutation of pinned meta-level spans is never allowed.
        if span.pinned:
            raise UnsafeContextMutationError(
                f"refusing lossy mutation of pinned span {span_id!r}"
            )

        # Lossy mutation with an open obligation requires a verifiable,
        # pinned external copy. Relevance alone is not a valid gate.
        if self.open_obligations > 0 and span.literal_bearing:
            if span.external_copy is None:
                raise UnsafeContextMutationError(
                    f"lossy mutation of literal-bearing span {span_id!r} "
                    f"with {self.open_obligations} open obligation(s) and "
                    f"no external copy; defer or spill instead"
                )
            if not self.pinned_pointer_index:
                raise UnsafeContextMutationError(
                    f"external copy for {span_id!r} exists but pointer index "
                    f"is unpinned; pointer may be pruned in the same pass"
                )

        # Safe to drop from the live window; external copy remains reachable.
        del self.spans[span_id]


def compact(window: ContextWindow, candidates: list) -> None:
    for span_id in candidates:
        window.mutate(span_id, lossy=True)
