# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-prune-its-own-context-window.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-prune-its-own-context-window.warrant.json
# Markdown SHA-256:   eaf870dc83f3ae46cf3b69cdded7b736fee035a786cf5878e65401bdbcbd1d55
# Code AST SHA-256:   e77c4b6c0011ac59480c3dc522ddd978fd6306f7bdd29752ab88f72432745539
# Verified Classes:   UnsafePruneError, ContextBuffer
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass, field


class UnsafePruneError(RuntimeError):
    """Raised when a self-edit would destroy the evidence needed to validate it."""


@dataclass
class ContextBuffer:
    anchor: str                      # non-editable provenance region
    spans: dict[str, str] = field(default_factory=dict)
    edit_counter: int = 0
    pre_edit_hash: str = ""
    negative_constraints: dict[str, str] = field(default_factory=dict)

    def _hash(self) -> str:
        body = "".join(f"{k}:{v}" for k, v in sorted(self.spans.items()))
        return hashlib.sha256((self.anchor + body).encode()).hexdigest()

    def prune(self, span_id: str, justification: str, retrieval_key: str | None) -> None:
        if span_id not in self.spans:
            raise UnsafePruneError(f"unknown span {span_id!r}")
        if span_id == "anchor":
            raise UnsafePruneError("refusing to prune anchor region")
        if span_id in self.negative_constraints and retrieval_key is None:
            raise UnsafePruneError(
                f"span {span_id!r} is sole carrier of a negative constraint "
                "and has no retrieval key; pruning would be obliteration"
            )
        if not justification.strip():
            raise UnsafePruneError("prune has no surviving justification")
        # justify-then-prune: write provenance into the anchor BEFORE mutation
        self.pre_edit_hash = self._hash()
        self.edit_counter += 1
        self.anchor += f"\n[edit {self.edit_counter}] pruned {span_id!r}: {justification}"
        del self.spans[span_id]

    def verify_provenance(self) -> bool:
        if self.edit_counter == 0:
            return True
        return self.pre_edit_hash != self._hash() or self.edit_counter > 0
