# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-task-critical-state-from-recoverable-context-before-compaction-truncates-it.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-task-critical-state-from-recoverable-context-before-compaction-truncates-it.warrant.json
# Markdown SHA-256:   52c00cb88170e8a807121968a852a73ad60deec8d63fd6417bafa3d478ce0e5f
# Code AST SHA-256:   54c65750656f6fcb1e600c27e057ea657f04cc406889a5ab1c6afa8e19e62972
# Verified Classes:   CompactionInvariantError, SegmentClass, Segment, Ledger
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from enum import Enum


class CompactionInvariantError(RuntimeError):
    """Raised when compaction would drop irrecoverable state."""


class SegmentClass(Enum):
    RECOVERABLE = "recoverable"
    IRRECOVERABLE = "irrecoverable"
    DERIVED = "derived"


@dataclass
class Segment:
    text: str
    cls: SegmentClass
    pointer: str | None = None  # how to regenerate, if RECOVERABLE
    depends_on: list[str] = field(default_factory=list)


@dataclass
class Ledger:
    decisions: list[str] = field(default_factory=list)
    constraints: list[str] = field(default_factory=list)
    rejections: list[str] = field(default_factory=list)


def compact(segments: list[Segment], budget_tokens: int) -> tuple[list[Segment], Ledger]:
    ledger = Ledger()

    # Phase 1: extract every irrecoverable fact into the durable ledger.
    for seg in segments:
        if seg.cls is SegmentClass.IRRECOVERABLE:
            ledger.decisions.append(seg.text)
        elif seg.cls is SegmentClass.DERIVED:
            # A derived conclusion without its inputs is unfalsifiable.
            if not seg.depends_on:
                raise CompactionInvariantError(
                    f"DERIVED segment has no recorded inputs: {seg.text!r}"
                )
            ledger.decisions.append(seg.text)

    # Phase 2: verify the ledger itself fits; if not, refuse rather than corrupt.
    ledger_tokens = sum(len(t.split()) for t in ledger.decisions)
    if ledger_tokens > budget_tokens:
        raise CompactionInvariantError(
            f"irrecoverable ledger ({ledger_tokens} tok) exceeds budget "
            f"({budget_tokens} tok); defer compaction"
        )

    # Phase 3: retain ledger-bearing segments plus recoverable pointers only.
    kept = [s for s in segments if s.cls is not SegmentClass.RECOVERABLE]
    kept += [
        Segment(text=f"[recoverable: {s.pointer}]", cls=SegmentClass.RECOVERABLE)
        for s in segments
        if s.cls is SegmentClass.RECOVERABLE and s.pointer
    ]
    return kept, ledger


def assert_ledger_survives(pre: Ledger, post: Ledger) -> None:
    for fact in pre.decisions:
        if fact not in post.decisions:
            raise CompactionInvariantError(
                f"irrecoverable fact lost across compaction: {fact!r}"
            )
