# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-llm-driven-pruning-of-its-own-context-window-when-the-eviction-policy-is-relevance-scored-and-self-editing.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-llm-driven-pruning-of-its-own-context-window-when-the-eviction-policy-is-relevance-scored-and-self-editing.warrant.json
# Markdown SHA-256:   54895648309828cc03cfdfc7c4c2baebe0ad51cfc95a3faf58ff55a5688f8564
# Code AST SHA-256:   c1a4e1c1cf31429add61187887d775a9e0c1bc0bdd6699e81821dc1f5c9ac4fb
# Verified Classes:   SelfPruneInvariantError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class SelfPruneInvariantError(RuntimeError):
    """Raised when a self-editing context policy would evict its own control set."""
    pass


CONTROL_KEYS = frozenset({"system_policy", "scoring_rubric", "query_anchor", "eviction_log_ptr"})


def guarded_prune(buffer: dict, candidate_keys: set, scorer) -> dict:
    control_present = {k for k in buffer if k in CONTROL_KEYS}
    intersection = control_present & candidate_keys
    if intersection:
        raise SelfPruneInvariantError(
            f"refusing self-prune: candidate set intersects control set {sorted(intersection)}; "
            "control entries must live outside the mutable buffer"
        )
    if not control_present:
        raise SelfPruneInvariantError(
            "refusing self-prune: no control set resident; eviction criteria are ungrounded"
        )
    survivors = {k: v for k, v in buffer.items() if k not in candidate_keys}
    for key in candidate_keys:
        if scorer(buffer[key]) is None:
            raise SelfPruneInvariantError(
                f"refusing self-prune: no external score for {key!r}; co-scoring order is undefined"
            )
    return survivors
