# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-treat-prompt-injection-taint-labels-that-survive-a-tool-boundary-but-lose-their-provenance-chain.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-treat-prompt-injection-taint-labels-that-survive-a-tool-boundary-but-lose-their-provenance-chain.warrant.json
# Markdown SHA-256:   476a0966534c6081ceca780afcdaeb586c98ec4c4d29fa1334c219d50bf21377
# Code AST SHA-256:   14ff4955ef16356e16d46e1b79500e7191ebc58d787d355539263bc1a2c5743c
# Verified Classes:   ProvenanceLossError, Span
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Optional


class ProvenanceLossError(RuntimeError):
    """Raised when a tainted span crosses a boundary without a complete label tuple."""
    pass


@dataclass
class Span:
    text: str
    tainted: bool
    origin: Optional[str] = None
    trust_class: Optional[str] = None

    def is_label_complete(self) -> bool:
        return (not self.tainted) or (self.origin is not None and self.trust_class is not None)


def promote_through_boundary(span: Span) -> Span:
    """Enforce that taint without provenance becomes maximally untrusted, never default-trusted."""
    if span.tainted and not span.is_label_complete():
        # Do not silently drop; promote to the worst class so default-deny policies catch it.
        return Span(
            text=span.text,
            tainted=True,
            origin=span.origin,
            trust_class="external_unattributed",
        )
    return span


def gate_tool_call(span: Span, tool_name: str) -> None:
    """Default-deny on any taint whose class is not explicitly permitted."""
    if not span.tainted:
        return
    if not span.is_label_complete():
        # Producer violated the contract; fail closed at the consumer too.
        raise ProvenanceLossError(
            f"tainted span reached gate for tool={tool_name!r} with severed provenance"
        )
    if span.trust_class != "internal":
        raise ProvenanceLossError(
            f"blocked tool={tool_name!r}: span trust_class={span.trust_class!r} not permitted"
        )


def summarize_and_forward(raw: Span, tool_name: str) -> None:
    # A lossy transformation: taint preserved, provenance dropped.
    lossy = Span(text=raw.text[:200], tainted=raw.tainted, origin=None, trust_class=None)
    promoted = promote_through_boundary(lossy)
    gate_tool_call(promoted, tool_name)
