# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-agent-transcript-compactor-refuse-to-reclaim-a-summarized-span.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-agent-transcript-compactor-refuse-to-reclaim-a-summarized-span.warrant.json
# Markdown SHA-256:   40bd902e48b9535cb2c8fb3aa246f8307409b680f7cbdc29526cea1ec60bba1f
# Code AST SHA-256:   a2923a7571cfc11c900d4d5eaf1af8b2c7c648d616a81da8e27f41e6a9492a9f
# Verified Classes:   CompactionRefusedError, EvidentialClass, Span
# Defensive Guards:   1 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass
from enum import Enum


class CompactionRefusedError(RuntimeError):
    """Raised when a span must not be lossily reclaimed under the evidence invariant."""
    pass


class EvidentialClass(Enum):
    NARRATIVE = "narrative"
    STRUCTURED = "structured"          # tool calls, paths, numbers, negations
    SIDE_EFFECTING = "side_effecting"  # committed writes / external mutations


@dataclass(frozen=True)
class Span:
    span_id: str
    evidential: EvidentialClass
    has_provenance_pointer: bool
    original_retrievable: bool
    body: bytes


def _content_hash(b: bytes) -> str:
    return hashlib.sha256(b).hexdigest()


def may_reclaim_lossily(span: Span) -> bool:
    """
    Return True only if a lossy summary is permitted to replace this span.

    Boundary:
      - NARRATIVE spans: always reclaimable.
      - STRUCTURED / SIDE_EFFECTING spans: reclaimable only if a provenance
        pointer exists AND the original is still retrievable. Otherwise the
        summary would be evidentially unrecoverable.
    """
    if span.evidential is EvidentialClass.NARRATIVE:
        return True

    if span.has_provenance_pointer and span.original_retrievable:
        return True

    raise CompactionRefusedError(
        f"span {span.span_id!r} is {span.evidential.value} but has no "
        f"retrievable provenance (pointer={span.has_provenance_pointer}, "
        f"retrievable={span.original_retrievable}); lossy reclamation would "
        f"make evidence-bearing content unrecoverable. Preserve verbatim or "
        f"emit a lossy-but-honest summary carrying a durable pointer."
    )


def compact(spans: list[Span]) -> list[dict]:
    """Partition spans into reclaimed vs. preserved; refuse on unsafe spans."""
    reclaimed, preserved = [], []
    for span in spans:
        try:
            if may_reclaim_lossily(span):
                reclaimed.append({
                    "span_id": span.span_id,
                    "summary": f"[lossy:{span.evidential.value}]",
                    "provenance": _content_hash(span.body) if span.has_provenance_pointer else None,
                })
            else:  # unreachable given the raise, kept for clarity
                preserved.append(span)
        except CompactionRefusedError:
            preserved.append(span)
    return [{"reclaimed": reclaimed, "preserved": [s.span_id for s in preserved]}]


if __name__ == "__main__":
    span = Span(
        span_id="sp-0042",
        evidential=EvidentialClass.SIDE_EFFECTING,
        has_provenance_pointer=False,
        original_retrievable=False,
        body=b'write_file(path="/etc/app/conf.yaml", mode="overwrite")',
    )
    try:
        may_reclaim_lossily(span)
    except CompactionRefusedError as e:
        print("REFUSED:", e)
