# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-emit-gen-ai-agent-turn-id-versus-inherit-the-parent-turn-for-agent-to-agent-correlation.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-emit-gen-ai-agent-turn-id-versus-inherit-the-parent-turn-for-agent-to-agent-correlation.warrant.json
# Markdown SHA-256:   75124fbb4c4e937307f4416a963600e7aac83e3006b2f92602e6ea93bab62de1
# Code AST SHA-256:   9b129dda96adc319909163d7acbe60953530535a52504db2a5cbf03befcdff62
# Verified Classes:   TurnBoundaryViolation, SpanContext
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass


class TurnBoundaryViolation(RuntimeError):
    """Raised when a span inherits a parent turn across a delegation boundary."""


@dataclass(frozen=True)
class SpanContext:
    agent_id: str
    turn_id: str
    parent_turn_id: str | None
    # True iff the parent turn is blocked awaiting this span to completion.
    synchronous_continuation: bool
    # True iff the span can be retried/rescheduled without the parent re-running.
    independently_retryable: bool
    # True iff the span's lifetime can outlive the parent turn.
    outlives_parent: bool


def assign_turn_id(
    *,
    parent: SpanContext,
    child_agent_id: str,
    synchronous_continuation: bool,
    independently_retryable: bool,
    outlives_parent: bool,
    proposed_turn_id: str,
) -> SpanContext:
    """Return the child span context, enforcing the turn-inheritance invariant.

    Inheritance of parent.turn_id is permitted only for synchronous
    continuations that are neither independently retryable nor able to
    outlive the parent turn. Otherwise a fresh turn id is required.
    """
    inherits = proposed_turn_id == parent.turn_id

    delegation_boundary = (
        not synchronous_continuation
        or independently_retryable
        or outlives_parent
    )

    if inherits and delegation_boundary:
        raise TurnBoundaryViolation(
            f"agent {child_agent_id!r} reused turn_id {parent.turn_id!r} "
            f"across a delegation boundary "
            f"(sync={synchronous_continuation}, "
            f"retryable={independently_retryable}, "
            f"outlives_parent={outlives_parent}); "
            f"mint a fresh gen_ai.agent.turn.id and set "
            f"gen_ai.agent.parent.turn.id={parent.turn_id!r}"
        )

    if not inherits:
        return SpanContext(
            agent_id=child_agent_id,
            turn_id=proposed_turn_id,
            parent_turn_id=parent.turn_id,
            synchronous_continuation=False,
            independently_retryable=independently_retryable,
            outlives_parent=outlives_parent,
        )

    # Legitimate continuation: inherit the turn, no parent link needed.
    return SpanContext(
        agent_id=child_agent_id,
        turn_id=parent.turn_id,
        parent_turn_id=parent.parent_turn_id,
        synchronous_continuation=True,
        independently_retryable=False,
        outlives_parent=False,
    )
