# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-that-hierarchical-summarization-has-crossed-the-lossy-recovery-checkpoint-boundary.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-that-hierarchical-summarization-has-crossed-the-lossy-recovery-checkpoint-boundary.warrant.json
# Markdown SHA-256:   f317bc5931a0207206654416aaaebb8e71073892fc4cfdc98b4f5c95f019a7e6
# Code AST SHA-256:   4847ceb6ce6c8efa39810a5ffe1c142bd898cf8e3b929603dcf926110611c8f7
# Verified Classes:   LossyCheckpointError, SummaryNode, Checkpoint
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Optional


class LossyCheckpointError(RuntimeError):
    """Raised when a summary node references a referent that is no longer reachable."""


@dataclass
class SummaryNode:
    node_id: str
    text: str
    # identifiers the summary elides but depends on
    referents: set[str] = field(default_factory=set)
    # explicit prohibitions that must never be compressed away
    negative_constraints: list[str] = field(default_factory=list)


@dataclass
class Checkpoint:
    node: SummaryNode
    reachable: set[str]  # referents resolvable in live context or durable store
    raw_negative_constraints: list[str]


def validate_checkpoint(cp: Checkpoint, previously_orphaned: Optional[set[str]] = None) -> None:
    """Enforce the lossy-recovery boundary before a checkpoint is treated as authoritative."""
    orphans = cp.node.referents - cp.reachable
    if orphans:
        raise LossyCheckpointError(
            f"node {cp.node.node_id!r} references unreachable referents: {sorted(orphans)}"
        )

    missing_constraints = set(cp.raw_negative_constraints) - set(cp.node.negative_constraints)
    if missing_constraints:
        raise LossyCheckpointError(
            f"node {cp.node.node_id!r} dropped negative constraints: {sorted(missing_constraints)}"
        )

    if previously_orphaned is not None and orphans != previously_orphaned:
        raise LossyCheckpointError(
            f"node {cp.node.node_id!r} is not idempotent: orphan set changed "
            f"{sorted(previously_orphaned)} -> {sorted(orphans)}"
        )


def compact(node: SummaryNode, reachable: set[str], raw_constraints: list[str]) -> Checkpoint:
    cp = Checkpoint(node=node, reachable=reachable, raw_negative_constraints=raw_constraints)
    validate_checkpoint(cp)
    return cp
