# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-summarization-induced-invariant-collapse-in-auto-compaction-of-long-horizon-agent-transcripts.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-summarization-induced-invariant-collapse-in-auto-compaction-of-long-horizon-agent-transcripts.warrant.json
# Markdown SHA-256:   e27504d9e2550e481cc511264b1a419c28b67e3a4c3e11df358d4aba365b6832
# Code AST SHA-256:   a7965ec926b42dbe20340efc164c51f372ee5acd3dcf5c7464e9495a6d66158a
# Verified Classes:   ObligationIntegrityError, Obligation, ObligationLedger
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Iterable


class ObligationIntegrityError(RuntimeError):
    """Raised when a post-compaction reference cannot be bound to the obligation ledger."""


@dataclass(frozen=True)
class Obligation:
    literal: str
    polarity: str  # "require" | "forbid"
    origin_turn: int


@dataclass
class ObligationLedger:
    obligations: dict[str, Obligation] = field(default_factory=dict)

    def record(self, ob: Obligation) -> None:
        self.obligations[ob.literal] = ob

    def retract(self, literal: str) -> None:
        self.obligations.pop(literal, None)

    def contains(self, literal: str) -> bool:
        return literal in self.obligations


def gate_side_effect(
    ledger: ObligationLedger,
    referenced_literals: Iterable[str],
    *,
    action: str,
) -> None:
    """Block any side-effecting action whose literals are not bound to the ledger,
    or whose preconditions include a forbidden obligation."""
    for lit in referenced_literals:
        ob = ledger.obligations.get(lit)
        if ob is None:
            raise ObligationIntegrityError(
                f"action={action!r} references {lit!r} which is absent from the "
                f"obligation ledger; summary paraphrase is not a valid binding. "
                f"Rehydrate the original span before executing."
            )
        if ob.polarity == "forbid":
            raise ObligationIntegrityError(
                f"action={action!r} touches forbidden literal {lit!r} "
                f"(origin turn {ob.origin_turn}); compaction must not soften "
                f"a prohibition into a preference."
            )


def verify_compaction(
    ledger_before: ObligationLedger,
    ledger_after: ObligationLedger,
    *,
    user_retractions: Iterable[str] = (),
) -> None:
    """Invariant: compaction may not silently drop obligations."""
    retracted = set(user_retractions)
    lost = set(ledger_before.obligations) - set(ledger_after.obligations) - retracted
    if lost:
        raise ObligationIntegrityError(
            f"compaction dropped {len(lost)} obligation(s) without explicit "
            f"retraction: {sorted(lost)}. Treat as data loss, not summarization."
        )
