# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-tool-call-span-corruption-when-genai-semantic-conventions-are-shared-across-agent-and-model-layers.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-tool-call-span-corruption-when-genai-semantic-conventions-are-shared-across-agent-and-model-layers.warrant.json
# Markdown SHA-256:   396a46e187ba6b515d6af09477af56e1696f6e40c5044d1e63906510213e872b
# Code AST SHA-256:   6db780cd9557c884e7b14da86af4928b1820cb65bfc305fbfbf04bc3e16f9c3f
# Verified Classes:   GenAIAttributeLeakError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class GenAIAttributeLeakError(RuntimeError):
    """Raised when a gen_ai.* attribute is present on a span whose own
    operation did not produce it (i.e., it was inherited from context)."""
    pass


# Attributes that assert the span's OWN operation produced the value.
LOCALLY_ASSERTED = frozenset({
    "gen_ai.usage.input_tokens",
    "gen_ai.usage.output_tokens",
    "gen_ai.request.model",
})

# Operation -> set of locally-asserted attributes it is allowed to carry.
ALLOWED_BY_OPERATION = {
    "chat": {"gen_ai.usage.input_tokens", "gen_ai.usage.output_tokens",
             "gen_ai.request.model"},
    "execute_tool": set(),  # tools consume no model tokens and have no model
    "invoke_agent": set(),  # agent is a coordinator; asserts nothing local
}


def validate_span_attributes(operation: str, attributes: dict,
                             inherited_keys: set) -> None:
    """Reject any locally-asserted attribute that was inherited, or that
    the operation is not permitted to assert."""
    allowed = ALLOWED_BY_OPERATION.get(operation)
    if allowed is None:
        raise GenAIAttributeLeakError(
            f"unknown gen_ai.operation.name={operation!r}"
        )
    for key in attributes:
        if key not in LOCALLY_ASSERTED:
            continue
        if key in inherited_keys:
            raise GenAIAttributeLeakError(
                f"{key} on operation={operation!r} was inherited from "
                f"enclosing context, not produced by this span's operation"
            )
        if key not in allowed:
            raise GenAIAttributeLeakError(
                f"{key} is not locally assertable by "
                f"operation={operation!r}"
            )


# Example that trips the guard: a tool span that leaked model context.
try:
    validate_span_attributes(
        operation="execute_tool",
        attributes={"gen_ai.tool.name": "search",
                    "gen_ai.request.model": "gpt-x"},
        inherited_keys={"gen_ai.request.model"},
    )
except GenAIAttributeLeakError as exc:
    # Correct response: strip the leaked attribute, do NOT drop the span.
    print(f"leak detected and contained: {exc}")
