# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-emit-a-genai-agent-span-when-tool-call-child-spans-are-already-in-flight.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-emit-a-genai-agent-span-when-tool-call-child-spans-are-already-in-flight.warrant.json
# Markdown SHA-256:   61770012c91d3cb5c016acace4326cb56a8add215227263ee4981409e9d844c5
# Code AST SHA-256:   5099a2e3be7e57d4f31db6639baf50ffaed2bb2f34fddfe6bdfbd2ad3987acf5
# Verified Classes:   AgentSpanInvariantError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class AgentSpanInvariantError(RuntimeError):
    """Raised when an agent span would be emitted over live or already-created tool children."""
    pass


def open_agent_span(loop_entered: bool, live_tool_children: int, first_tool_dispatched: bool) -> None:
    # Condition 1: parent must exist before any owned tool span is created.
    if not loop_entered:
        raise AgentSpanInvariantError(
            "agent span opened after loop entry; condition 1 (parent-before-child) violated"
        )
    if first_tool_dispatched and live_tool_children > 0:
        # Lazy creation path: a tool span already exists without this parent.
        raise AgentSpanInvariantError(
            "cannot open agent span: tool child already dispatched without parent"
        )


def close_agent_span(live_tool_children: int) -> None:
    # Conditions 2 and 4: agent span must outlive every owned tool span.
    if live_tool_children > 0:
        raise AgentSpanInvariantError(
            f"refusing to close agent span with {live_tool_children} tool children in flight; "
            "hold open or mark gen_ai.agent.truncated=true"
        )
