# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-lossy-transcript-summarization-as-a-hard-invariant-violation-rather-than-a-recoverable-degradation.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-lossy-transcript-summarization-as-a-hard-invariant-violation-rather-than-a-recoverable-degradation.warrant.json
# Markdown SHA-256:   63b61024722e5155ff19800a009c301dedabfe4a1831084e65a452990ae2026a
# Code AST SHA-256:   f23f364014ce41b58037c7959d5484730d171e8a6d46696d38d3ad57e567aa64
# Verified Classes:   PreservationInvariantViolation, PreservationSet, CompactionResult
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import FrozenSet


class PreservationInvariantViolation(RuntimeError):
    """Raised when a lossy compaction claims preservation it did not deliver.

    This is a hard failure, not a recoverable degradation: the summary must
    not be consumed, and the runtime must rehydrate from source or halt.
    """


@dataclass(frozen=True)
class PreservationSet:
    commitments: FrozenSet[str]
    pending_tool_handles: FrozenSet[str]
    unresolved_errors: FrozenSet[str]

    def required(self) -> FrozenSet[str]:
        return self.commitments | self.pending_tool_handles | self.unresolved_errors


@dataclass
class CompactionResult:
    summary: str
    # Self-attested by the compactor. NEVER trusted as ground truth.
    self_reported_retained: FrozenSet[str] = field(default_factory=frozenset)


def verify_compaction(
    pre_transcript: str,
    required: PreservationSet,
    result: CompactionResult,
) -> None:
    """Verify against the pre-compaction transcript, not the summary.

    Raises PreservationInvariantViolation if any required element is missing
    from the summary, or if the compactor's self-report disagrees with source.
    """
    missing = {item for item in required.required() if item not in result.summary}
    if missing:
        raise PreservationInvariantViolation(
            f"compaction dropped required invariants: {sorted(missing)}"
        )

    # Self-report must be a subset of what survived; a mismatch means the
    # compactor is lying about its own guarantee.
    dishonest = result.self_reported_retained - required.required()
    if dishonest:
        raise PreservationInvariantViolation(
            f"compactor self-report claims retention of non-required items: {sorted(dishonest)}"
        )

    # Cross-check against source: every required item must actually appear in
    # the pre-compaction transcript. If it doesn't, the preservation set was
    # derived from the summary and is therefore unsound.
    for item in required.required():
        if item not in pre_transcript:
            raise PreservationInvariantViolation(
                f"preservation set element {item!r} absent from source transcript"
            )


def consume_summary(
    pre_transcript: str,
    required: PreservationSet,
    result: CompactionResult,
    consumer_requires: FrozenSet[str],
) -> str:
    """Gate summary consumption on verified invariants.

    A consumer may only receive a summary if its required invariants are a
    subset of the verified preservation set. Otherwise fail closed.
    """
    verify_compaction(pre_transcript, required, result)

    unverified = consumer_requires - required.required()
    if unverified:
        raise PreservationInvariantViolation(
            f"consumer requires unverified invariants: {sorted(unverified)}"
        )
    return result.summary
