# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-a-context-provenance-taint-tracker-refuse-to-propagate-a-clean-label-across-a-tool-boundary.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-a-context-provenance-taint-tracker-refuse-to-propagate-a-clean-label-across-a-tool-boundary.warrant.json
# Markdown SHA-256:   421359d30d991cd49446832da7a5d990e18abca6f1d41c8c11eb1a03079d8ba9
# Code AST SHA-256:   6e323c67aa747e46a24294d409e2ecaa3bf0580d3c58b9d4cd46332a0f73f1bf
# Verified Classes:   TaintLaunderingError, Provenance, ToolContract
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Any


class TaintLaunderingError(RuntimeError):
    """Raised when a tool call would drop control taint across a boundary."""


@dataclass(frozen=True)
class Provenance:
    data_taint: bool = False
    control_taint: bool = False
    source: str = "unknown"

    def join(self, other: "Provenance") -> "Provenance":
        return Provenance(
            data_taint=self.data_taint or other.data_taint,
            control_taint=self.control_taint or other.control_taint,
            source=f"{self.source}+{other.source}",
        )


@dataclass
class ToolContract:
    name: str
    pure: bool
    control_args: frozenset[str] = field(default_factory=frozenset)
    free_form_output: bool = False


def propagate(
    contract: ToolContract,
    args: dict[str, Provenance],
    output: Provenance,
) -> Provenance:
    """Compute the provenance of a tool's output segment.

    Drops taint only when the tool is pure, no control argument is tainted,
    and the output schema is not free-form (model will not re-parse it).
    """
    tainted_control = [
        name for name in contract.control_args
        if name in args and args[name].control_taint
    ]
    if tainted_control:
        raise TaintLaunderingError(
            f"tool {contract.name!r} received control-tainted args "
            f"{tainted_control}; output must retain control_taint"
        )
    if not contract.pure:
        raise TaintLaunderingError(
            f"tool {contract.name!r} is not pure; cannot drop taint on output"
        )
    if contract.free_form_output:
        raise TaintLaunderingError(
            f"tool {contract.name!r} emits free-form text; output is "
            f"re-parseable and must retain taint"
        )
    joined = output
    for prov in args.values():
        joined = joined.join(prov)
    return Provenance(
        data_taint=joined.data_taint,
        control_taint=joined.control_taint,
        source=f"tool:{contract.name}<-{joined.source}",
    )


def declassify(prov: Provenance, reviewer: str) -> Provenance:
    if not reviewer:
        raise TaintLaunderingError("declassification requires a named reviewer")
    return Provenance(data_taint=prov.data_taint, control_taint=False,
                      source=f"declassified:{reviewer}<-{prov.source}")
