# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-of-a-context-pruning-policy-refuse-to-prune-the-mid-tool-call-boundary.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-of-a-context-pruning-policy-refuse-to-prune-the-mid-tool-call-boundary.warrant.json
# Markdown SHA-256:   6efbb26e9ac5fd401d694af9779c7598a4e025f439cbc8c3712289366c0c002d
# Code AST SHA-256:   e92c6f0d4ca5a3c6776bd588228265fb3d46acad009a3500645724217455228f
# Verified Classes:   PruneBoundaryViolation
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class PruneBoundaryViolation(RuntimeError):
    """Raised when a prune would split a structural unit or touch an open message."""
    pass


def prune_context(messages, prune_ids, pending_calls, open_ids):
    """
    messages: list of dicts with 'id', 'unit_id', 'role', 'closed'
    prune_ids: set of message ids the salience policy wants to evict
    pending_calls: set of unit_ids with an in-flight tool call
    open_ids: set of message ids still streaming
    """
    # 1. Never touch open messages.
    for mid in prune_ids:
        if mid in open_ids:
            raise PruneBoundaryViolation(
                f"prune set contains open/streaming message {mid!r}"
            )

    # 2. Close the prune set under unit membership (all-or-nothing per unit).
    by_unit = {}
    for m in messages:
        by_unit.setdefault(m["unit_id"], []).append(m["id"])

    prune_units = {m["unit_id"] for m in messages if m["id"] in prune_ids}
    closed_prune = set()
    for uid in prune_units:
        members = set(by_unit[uid])
        if not members.issubset(prune_ids):
            raise PruneBoundaryViolation(
                f"prune set splits unit {uid!r}; "
                f"missing {members - prune_ids!r}"
            )
        closed_prune |= members

    # 3. Never prune a unit with a pending call.
    for uid in prune_units:
        if uid in pending_calls:
            raise PruneBoundaryViolation(
                f"prune set contains unit {uid!r} with an in-flight tool call"
            )

    return closed_prune
