# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-lossy-compaction-from-silent-task-state-truncation-under-hard-token-budgets.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-lossy-compaction-from-silent-task-state-truncation-under-hard-token-budgets.warrant.json
# Markdown SHA-256:   62a0c4cacaa04efa59e90f8cd80327a3912d4caf257fca5e3858b476c643defa
# Code AST SHA-256:   422ae6b24fbe85972f14c3c98075426feda58c92a1e8c9183f132e2bec0ceba9
# Verified Classes:   CompactionFailure, StateClass, StateUnit, CompactionPlan
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from enum import Enum


class CompactionFailure(RuntimeError):
    """Raised when protected task state cannot fit the hard token budget."""
    pass


class StateClass(Enum):
    PROTECTED = "protected"   # verbatim only; never summarized
    DERIVABLE = "derivable"   # recoverable from environment/store
    REDUNDANT = "redundant"   # evict first


@dataclass
class StateUnit:
    key: str
    tokens: int
    cls: StateClass
    text: str


@dataclass
class CompactionPlan:
    preserved: list = field(default_factory=list)
    dropped: list = field(default_factory=list)
    externalized: list = field(default_factory=list)


def compact(units, hard_budget, external_store):
    """Compact context under a hard token budget without silent truncation.

    Evicts REDUNDANT, then DERIVABLE. PROTECTED units are never summarized;
    if they do not fit verbatim, they are externalized with a pointer, and
    if even that cannot fit, compaction fails loudly.
    """
    plan = CompactionPlan()
    remaining = hard_budget

    for u in units:
        if u.cls is StateClass.REDUNDANT:
            plan.dropped.append(u)

    for u in units:
        if u.cls is StateClass.DERIVABLE:
            if u.tokens <= remaining:
                plan.preserved.append(u)
                remaining -= u.tokens
            else:
                plan.dropped.append(u)

    protected = [u for u in units if u.cls is StateClass.PROTECTED]
    for u in protected:
        if u.tokens <= remaining:
            plan.preserved.append(u)
            remaining -= u.tokens
        else:
            # Never summarize protected state. Externalize with a pointer.
            pointer = external_store.put(u.key, u.text)
            pointer_tokens = len(pointer)
            if pointer_tokens > remaining:
                raise CompactionFailure(
                    f"protected state '{u.key}' ({u.tokens} tok) cannot fit "
                    f"even as a {pointer_tokens}-tok pointer; budget "
                    f"{hard_budget} insufficient. Refusing lossy compaction."
                )
            plan.externalized.append((u.key, pointer))
            remaining -= pointer_tokens

    return plan
