# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-a-tool-call-that-passes-every-static-capability-check-dynamic-taint-re-derivation-across-mcp-tool-call-boundaries.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-a-tool-call-that-passes-every-static-capability-check-dynamic-taint-re-derivation-across-mcp-tool-call-boundaries.warrant.json
# Markdown SHA-256:   6d51bcacf85030015764ff30dbc6b5ee2e50fc567893e610b2d3f3f4b906be57
# Code AST SHA-256:   8dd2c0e7342d30083cad8d1eb20b7f799941088cf3f37b53cc28c55ef61487b8
# Verified Classes:   TaintLaunderingError, DerivationGraph
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class TaintLaunderingError(RuntimeError):
    """Raised when a sink invocation carries data whose derivation graph
    reaches an UNTRUSTED source without a recorded declassification edge."""


class DerivationGraph:
    def __init__(self):
        self._parents = {}   # node_id -> set(node_id)
        self._taint = {}     # node_id -> bool (True = UNTRUSTED)
        self._declassified = set()

    def source(self, node_id):
        self._parents.setdefault(node_id, set())
        self._taint[node_id] = True
        return node_id

    def derive(self, node_id, from_nodes):
        # Taint is preserved through derivation. Shape is never consulted.
        self._parents[node_id] = set(from_nodes)
        self._taint[node_id] = any(
            self._taint.get(p, True) for p in from_nodes
        ) or not from_nodes
        return node_id

    def declassify(self, node_id, approver):
        if not approver:
            raise TaintLaunderingError(
                f"declassification of {node_id!r} requires an approver"
            )
        self._declassified.add(node_id)

    def is_tainted(self, node_id):
        if node_id in self._declassified:
            return False
        return self._taint.get(node_id, True)


def guard_sink_call(graph, sink_authority, source_authority, arg_node):
    """Refuse a capability-legal call whose argument is taint-illegal."""
    # No privilege to launder: sink cannot exceed the source's authority.
    if sink_authority.issubset(source_authority):
        return
    if graph.is_tainted(arg_node):
        raise TaintLaunderingError(
            f"argument {arg_node!r} derives from an UNTRUSTED source and "
            f"sink authority {sorted(sink_authority)} exceeds source "
            f"authority {sorted(source_authority)}; no declassification "
            f"edge recorded"
        )
