# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-refuse-to-auto-compact-when-fidelity-scoring-is-self-referential.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-refuse-to-auto-compact-when-fidelity-scoring-is-self-referential.warrant.json
# Markdown SHA-256:   d670b1c314360d3dfac93887d608d196b5126afd44e84fb905b3b2ebd2fff26e
# Code AST SHA-256:   5333d8369672b72bf528fae33c7e73233da3ad049c721b2149445f176c6546c3
# Verified Classes:   UnpinnedCompactionError, DecisionRelevantSet, FidelityScorer
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import FrozenSet


class UnpinnedCompactionError(RuntimeError):
    """Raised when auto-compaction would be gated by a self-referential fidelity score."""


@dataclass(frozen=True)
class DecisionRelevantSet:
    prohibitions: FrozenSet[str] = field(default_factory=frozenset)
    numeric_commitments: FrozenSet[str] = field(default_factory=frozenset)
    failed_attempts: FrozenSet[str] = field(default_factory=frozenset)

    def is_empty(self) -> bool:
        return not (self.prohibitions or self.numeric_commitments or self.failed_attempts)

    def size(self) -> int:
        return len(self.prohibitions) + len(self.numeric_commitments) + len(self.failed_attempts)


@dataclass(frozen=True)
class FidelityScorer:
    reference_is_pinned: bool
    threshold: float = 0.80


def authorize_compaction(
    relevant: DecisionRelevantSet,
    scorer: FidelityScorer,
    self_score: float,
) -> bool:
    """Return True only if compaction is safe to auto-apply.

    Refuses when the decision-relevant set is non-empty and the scorer's
    reference is not pinned, because a self-referential score cannot detect
    the loss of the very items that would lower it.
    """
    if relevant.is_empty():
        return True

    if not scorer.reference_is_pinned:
        raise UnpinnedCompactionError(
            f"refusing auto-compaction: {relevant.size()} decision-relevant item(s) "
            f"present but fidelity reference is unpinned; self-score {self_score:.2f} "
            f"is not evidence of preservation"
        )

    return self_score >= scorer.threshold
