# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-taint-provenance-is-lost-at-mcp-tool-and-a2a-task-boundaries.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-taint-provenance-is-lost-at-mcp-tool-and-a2a-task-boundaries.warrant.json
# Markdown SHA-256:   45d1136e2cf229d4c537b6f9c573e87fae4d0fbad815d430cd4acd94d04a97d7
# Code AST SHA-256:   0673e7d8ab264a3c991ea368a2ded0b60a2670f5fcd7cbb4deb1908f7cc163c1
# Verified Classes:   ProvenanceMismatchError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hashlib
import json


class ProvenanceMismatchError(RuntimeError):
    """Raised when a cross-boundary value is tainted locally but unlabeled on the wire."""

    pass


def canonical_hash(value: object) -> str:
    encoded = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
    return hashlib.sha256(encoded.encode("utf-8")).hexdigest()


def check_inbound_provenance(
    envelope: dict,
    taint_store: dict,
    argument_keys: list,
) -> None:
    """Validate that every tainted argument carries a matching provenance label.

    taint_store maps content-hash -> provenance descriptor.
    envelope must contain a 'provenance' mapping of argument_key -> label.
    """
    labels = envelope.get("provenance")
    if labels is None:
        labels = {}

    for key in argument_keys:
        arg = envelope.get("arguments", {}).get(key)
        if arg is None:
            continue
        h = canonical_hash(arg)
        if h in taint_store and key not in labels:
            raise ProvenanceMismatchError(
                f"argument '{key}' is tainted (hash {h[:12]}) but envelope carries no provenance label"
            )
        if key in labels and h not in taint_store:
            raise ProvenanceMismatchError(
                f"argument '{key}' carries label '{labels[key]}' but hash {h[:12]} is not in taint store"
            )
