# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-loses-taint-provenance-at-a-tool-call-boundary-during-prompt-injection-defense.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-loses-taint-provenance-at-a-tool-call-boundary-during-prompt-injection-defense.warrant.json
# Markdown SHA-256:   d82af72c59f50d157f769a6dbe52864826c176eedcd77914aa7a95940d90471b
# Code AST SHA-256:   1e4396bf2312bd7396f0dc96388312c59e75b7df59a1a0c6286fd57a7ed3de34
# Verified Classes:   Taint, TaintLaunderingError, ToolResult
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from enum import IntEnum


class Taint(IntEnum):
    CLEAN = 0
    UNTRUSTED = 1

    def join(self, other: "Taint") -> "Taint":
        return Taint(max(int(self), int(other)))


class TaintLaunderingError(RuntimeError):
    """Raised when a tool result's taint is derived from channel trust
    rather than origin provenance, laundering untrusted bytes as clean."""


@dataclass(frozen=True)
class ToolResult:
    payload: str
    origin: str | None          # e.g. "https://evil.example", "mailbox:user"
    tool_trust: Taint           # trust of the *channel*, not the bytes


def compute_result_taint(result: ToolResult, origin_trust: dict[str, Taint]) -> Taint:
    """Taint must be the join of origin taint and any inherited taint.
    Channel trust is deliberately NOT consulted for the payload label."""
    if result.origin is None:
        # No provenance -> cannot establish origin -> worst case.
        return Taint.UNTRUSTED
    if result.origin not in origin_trust:
        return Taint.UNTRUSTED
    return origin_trust[result.origin].join(Taint.CLEAN)


def assert_no_laundering(result: ToolResult, computed: Taint) -> None:
    """Detect the boundary failure: channel trust used as payload taint."""
    if result.tool_trust == Taint.CLEAN and computed == Taint.UNTRUSTED:
        # This is the *correct* outcome (trusted channel, untrusted origin),
        # not an error. The error is the inverse: declaring clean because
        # the channel is trusted while origin is untrusted.
        return
    if computed == Taint.CLEAN and result.tool_trust == Taint.CLEAN:
        if result.origin is not None and origin_trust_get(result.origin) == Taint.UNTRUSTED:
            raise TaintLaunderingError(
                f"origin {result.origin!r} is untrusted but result was labeled clean "
                f"via channel trust of tool; taint laundering at tool boundary"
            )


def origin_trust_get(origin: str) -> Taint:
    # Placeholder for the runtime's origin trust registry.
    return Taint.UNTRUSTED


def ingest_tool_result(result: ToolResult, origin_trust: dict[str, Taint]) -> Taint:
    computed = compute_result_taint(result, origin_trust)
    assert_no_laundering(result, computed)
    return computed
