# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-silent-semantic-loss-when-a-model-prunes-its-own-context-window.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-semantic-loss-when-a-model-prunes-its-own-context-window.warrant.json
# Markdown SHA-256:   e9e92bbdeacc6f07ad24e01a052263996559ab3145648911f983450f4e16d8be
# Code AST SHA-256:   c5469fb297a31399d2fe84213e23b2b5f52bc57a8611baba152a420af08688e1
# Verified Classes:   UnreconstructibleEvictionError, ContextItem
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class UnreconstructibleEvictionError(RuntimeError):
    """Raised when a retention policy evicts content that cannot be
    reconstructed from surviving context or an external store."""
    pass


class ContextItem:
    def __init__(self, item_id, kind, derivable, pointer=None):
        # kind: "derived" | "raw_observation" | "summary"
        self.item_id = item_id
        self.kind = kind
        self.derivable = derivable          # recomputable from surviving context?
        self.pointer = pointer              # external store handle, if any
        self.retention_score = 1.0


def evict(items, threshold, external_store_resolves):
    """Apply a priority-weighted eviction policy with the derivability invariant.

    Returns (surviving, evicted). Raises if any eviction is unreconstructible.
    """
    surviving, evicted = [], []
    for item in items:
        if item.retention_score >= threshold:
            surviving.append(item)
            continue

        # Below threshold: candidate for eviction. Enforce the invariant.
        if item.derivable:
            evicted.append(item)
            continue

        if item.pointer is not None and external_store_resolves(item.pointer):
            evicted.append(item)
            continue

        # Raw observation, non-derivable, no resolvable pointer: refuse.
        raise UnreconstructibleEvictionError(
            f"refusing to evict {item.item_id!r} (kind={item.kind!r}): "
            f"not derivable from surviving context and pointer "
            f"{item.pointer!r} does not resolve. "
            f"Re-frame or re-score before pruning."
        )
    return surviving, evicted


def re_frame(items, new_query_distribution):
    """On task re-frame, re-score all items and re-validate derivability.

    A re-frame can convert a derivable item into a retrievable-only item;
    this function must run before any subsequent eviction.
    """
    for item in items:
        if item.kind == "raw_observation":
            item.derivable = False  # raw observations are never re-derivable
        item.retention_score = new_query_distribution.score(item)
    # Any item whose derivability flipped must now carry a pointer or be floored.
    for item in items:
        if not item.derivable and item.pointer is None:
            item.retention_score = float("inf")  # floor: never evict
    return items
