# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-self-authored-context-pruning-from-silently-dropping-the-evidence-it-was-meant-to-preserve.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-self-authored-context-pruning-from-silently-dropping-the-evidence-it-was-meant-to-preserve.warrant.json
# Markdown SHA-256:   943926132ee03dbda4f8c4f447ab8ae89ee295ae63347a28ba7bf8d35e9aedc1
# Code AST SHA-256:   7241e4c4f54372d5ccad26fc4425d1a17376141b656d14de6c6eb792953e0167
# Verified Classes:   ProvenanceOrphanError, ConflictAsymmetryError
# Defensive Guards:   4 fail-closed invariants (zero external deps)

class ProvenanceOrphanError(RuntimeError):
    """Raised when a retained assertion has lost the evidence that justified it."""
    pass


class ConflictAsymmetryError(RuntimeError):
    """Raised when one side of a recorded tension survived pruning and the other did not."""
    pass


def commit_pruning(window, policy, epoch_id, policy_epoch_id):
    # Invariant 3: a policy may not be applied to the window that authored it.
    if policy_epoch_id == epoch_id:
        raise ProvenanceOrphanError(
            f"policy authored in epoch {epoch_id} cannot be applied to the same epoch"
        )

    retained = policy.select(window.tokens)

    # Invariant 1: every retained assertion must point at a retained or external source.
    for tok in retained:
        if tok.kind == "assertion":
            src = tok.provenance
            if src is None:
                raise ProvenanceOrphanError(
                    f"assertion {tok.id!r} has no provenance pointer"
                )
            if src not in retained and not window.external_store.contains(src):
                raise ProvenanceOrphanError(
                    f"assertion {tok.id!r} points at pruned source {src!r} "
                    f"and no external copy exists"
                )

    # Invariant 2: conflict pairs survive together or not at all.
    for pair in window.conflict_pairs():
        a_in = pair.a in retained
        b_in = pair.b in retained
        if a_in != b_in:
            raise ConflictAsymmetryError(
                f"conflict pair {pair.id!r} split: a={a_in} b={b_in}"
            )

    window.replace(retained)
    return retained
