# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-taint-aware-sink-blocking-as-a-hard-invariant-rather-than-a-best-effort-filter.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-taint-aware-sink-blocking-as-a-hard-invariant-rather-than-a-best-effort-filter.warrant.json
# Markdown SHA-256:   119294d561b700c54f4a8a5e94769bcf7f8f7209db079ff429375e01a793d366
# Code AST SHA-256:   5aada1e91e2656a2f731555b8d2c650f59e2e1e35ee57632de2caae980ddce44
# Verified Classes:   TaintSinkViolation, Sink
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class TaintSinkViolation(RuntimeError):
    """Raised when a value carrying untrusted-prompt taint reaches a hard sink."""


class Sink:
    HARD = "hard"      # irreversible / externally visible
    SOFT = "soft"      # internal / reversible


HARD_SINKS = {"shell_exec", "http_post", "send_message", "use_credential", "fs_delete"}


def enforce_sink(sink_name: str, value, provenance: dict) -> None:
    """
    provenance: {value_id: {"sources": [...], "path": [...]}}
    Raises TaintSinkViolation if the value is tainted and the sink is hard.
    """
    entry = provenance.get(id(value))
    if entry is None:
        return  # untainted, no provenance record

    sources = entry.get("sources", [])
    if not sources:
        return

    if sink_name in HARD_SINKS:
        path = " -> ".join(entry.get("path", [])) or "<unknown path>"
        raise TaintSinkViolation(
            f"tainted value reached hard sink {sink_name!r}; "
            f"sources={sources}; path={path}. "
            f"Declassify via an explicit, reviewed sanitizer or abort."
        )
    # soft sink: caller logs and continues
    return


def run_tool_call(tool_name: str, args: dict, provenance: dict) -> None:
    for arg in args.values():
        enforce_sink(tool_name, arg, provenance)
    # ... dispatch to actual tool only after all args pass ...
