# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-that-retrieval-anchored-summarization-has-silently-broken-its-recall-guarantee.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-that-retrieval-anchored-summarization-has-silently-broken-its-recall-guarantee.warrant.json
# Markdown SHA-256:   8eddff7199b70a62a85725072e024ff30ee6e9f87f06c5ba8db947d056561aa7
# Code AST SHA-256:   9842385afbc6350cd7a2d34d5c0a081bbefbb279b6e84379243561a5b823e88f
# Verified Classes:   RecallGuaranteeViolation, CompactionResult
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import re
from dataclasses import dataclass

ANCHOR_RE = re.compile(r"\[\[([A-Za-z0-9:_\-/\.]+)\]\]")
ELISION_RE = re.compile(r"\b(etc\.|and related|as noted|the above|earlier)\b", re.IGNORECASE)


class RecallGuaranteeViolation(RuntimeError):
    """Raised when a compacted summary can no longer resolve its own anchors in isolation."""
    pass


@dataclass
class CompactionResult:
    summary: str
    resolvable_keys: set[str]


def check_anchor_isolation(summary: str, resolver, min_anchor_density: float = 0.5) -> None:
    anchors = ANCHOR_RE.findall(summary)
    elisions = len(ELISION_RE.findall(summary))
    if not anchors:
        raise RecallGuaranteeViolation(
            "summary contains no opaque anchors; every elision is orphaned"
        )
    density = len(anchors) / (len(anchors) + elisions)
    if density < min_anchor_density:
        raise RecallGuaranteeViolation(
            f"anchor density {density:.2f} below floor {min_anchor_density:.2f}; "
            "pointers have been paraphrased into prose"
        )
    unresolved = [a for a in anchors if not resolver(a)]
    if unresolved:
        raise RecallGuaranteeViolation(
            f"anchors unresolvable without dropped context: {unresolved}"
        )


def compact_with_guarantee(raw_context: str, prior: CompactionResult | None, summarize, resolver) -> CompactionResult:
    result = summarize(raw_context)
    # Re-compaction must be idempotent on the anchor set.
    if prior is not None:
        new_keys = set(ANCHOR_RE.findall(result.summary))
        if new_keys != prior.resolvable_keys:
            raise RecallGuaranteeViolation(
                "re-compaction drifted the anchor set; escalate to full re-expansion"
            )
    check_anchor_isolation(result.summary, resolver)
    return CompactionResult(result.summary, set(ANCHOR_RE.findall(result.summary)))
