# Under what conditions must an instance treat OpenTelemetry GenAI semantic-convention parent-child span links as authoritative across organizational boundaries **Date of Record:** 2026 November 14 **Scope:** Applies to agentic systems that emit or consume OTel GenAI spans (`gen_ai.*` attributes) where a child task span produced by an agent in org A is linked to a parent span produced by an orchestrator in org B. Does not apply to intra-org traces where a single trace backend controls span ID minting, nor to spans that carry no cross-org `traceparent` propagation. ## Core Distinction The GenAI semantic conventions define *attribute* schemas (model, operation, token counts, agent identity) but deliberately do not define an authority model for span *identity* across trust domains. Two distinct things get conflated under "semantic convention compliance": 1. **Schema conformance** — the span carries the required `gen_ai.*` attributes with valid types and enumerated values. This is checkable locally against the spec version. 2. **Link authority** — the claim that a child span's `parent_span_id` correctly identifies the causal parent, and that the child is entitled to inherit the parent's trace context, sampling decision, and agent-identity chain. Schema conformance is composable across orgs. Link authority is not. A child span can be 100% schema-conformant while its parent link is forged, stale, or referencing a span the consuming org never minted and cannot verify. The boundary failure is treating the first as evidence of the second. ## What Was Observed In a cross-org agent pipeline, orchestrator (org B) emits task span `S_parent` with `trace_id=T`, `span_id=P`. It dispatches work to a sub-agent hosted by org A via a signed task envelope. Org A's agent emits child span `S_child` with `traceparent` header `00-T-P-01` and `gen_ai.agent.id` set to its own identity. Three failure modes surfaced: - **Recycled parent span IDs.** Org A's tracing SDK, under high fan-out, reused a `span_id` value within the same `trace_id` after a 32-bit collision in a non-cryptographic ID generator. The child linked to the wrong sibling. Schema validation passed; the parent-child graph silently mis-attributed latency and token cost to the wrong orchestrator task. - **Stale propagation across retries.** Org B retried the envelope after a timeout. The retry carried the *same* `traceparent` but a new logical task. Org A emitted a second child under the same `P`, producing a 2:1 child-parent ratio that the consumer's graph builder rejected as a cycle-free but non-tree structure, dropping both children. - **Identity laundering.** A third-party agent (org C) received a forwarded `traceparent` from org A and emitted a child claiming `gen_ai.agent.id` of org A's agent. The consumer, validating only schema, attributed org C's token spend to org A's cost center. In all three, `gen_ai.*` attribute validation returned clean. The invariant that broke was: *a parent_span_id is authoritative only if the consuming party can verify the minting party minted it and minted it once.* ## Condition of Validity A cross-org parent-child link is authoritative for an instance **if and only if** all of the following hold: 1. The parent `span_id` was minted by a party whose signing key the consumer trusts for the parent's `trace_id` namespace. 2. The link is accompanied by a nonce or dispatch ID that binds the specific child emission to a specific parent dispatch (defeating retry duplication). 3. The child's `gen_ai.agent.id` is asserted by the same signer that owns the child span's minting authority — not merely present as an attribute. 4. The consumer has observed the parent span *before* accepting the child, or has a verifiable parent-minted manifest attesting the parent's existence and expected child cardinality. If any condition fails, the link is *schema-conformant but non-authoritative*. The instance must downgrade it to an unverified reference, not a causal edge. ## Decision Boundary The precise boundary: **span ID uniqueness is a local guarantee that does not survive org boundaries.** Within one org, the tracing backend can guarantee no `span_id` collision within a `trace_id` and can enforce single-mint. Across orgs, each party mints IDs under its own generator; collision-freedom is a per-party property, and the consumer cannot assume the union is collision-free without a namespace or signer binding. Therefore: - Do **not** treat `traceparent` presence as proof of link authority. - Do **not** merge cross-org child spans into the parent's causal graph until conditions 1–4 are checked. - **Do** accept the span's `gen_ai.*` attributes for local accounting (token counts, model names) even when the link is non-authoritative — attribute validity and link authority are independent. ## Operational Effect When the boundary is respected, the consumer maintains two graphs: an *attribute graph* (all spans, used for cost and model telemetry, tolerant of unverified links) and an *authority graph* (only links passing conditions 1–4, used for causal attribution, SLA, and blame). Divergence between the two graphs is the signal that a cross-org link failed authority — it should page the integration owner, not the model owner. When the boundary is ignored, the failure is silent: dashboards show plausible latency and cost, mis-attributed to the wrong org, until a billing or incident review surfaces the mismatch weeks later. The instance must also enforce expected child cardinality per parent dispatch. A parent dispatch that yields two children under the same nonce is a retry-duplication event, not a fan-out, and the second child must be quarantined rather than merged. ```python from dataclasses import dataclass class LinkAuthorityError(RuntimeError): """Raised when a cross-org parent-child span link is schema-conformant but fails authority conditions and must not enter the causal graph.""" @dataclass(frozen=True) class SpanLink: trace_id: str parent_span_id: str child_span_id: str child_agent_id: str dispatch_nonce: str parent_signer_trusted: bool child_agent_signer_matches: bool parent_observed: bool def admit_link(link: SpanLink, seen_nonces: dict[str, str]) -> bool: """Return True only if the link is authoritative per conditions 1-4. Non-authoritative links are routed to the attribute graph by the caller; this function never silently accepts them as causal edges. """ if not link.parent_signer_trusted: raise LinkAuthorityError( f"parent span {link.parent_span_id} in trace {link.trace_id} " "was not minted by a trusted signer; link is non-authoritative" ) if not link.child_agent_signer_matches: raise LinkAuthorityError( f"child agent {link.child_agent_id} identity is not asserted by " f"the span minting signer for {link.child_span_id}; possible identity laundering" ) if not link.parent_observed: raise LinkAuthorityError( f"parent span {link.parent_span_id} has not been observed or attested; " "cannot accept child as causal edge" ) prior = seen_nonces.get(link.dispatch_nonce) if prior is not None and prior != link.child_span_id: # Retry duplication: same dispatch nonce, different child. Quarantine. raise LinkAuthorityError( f"dispatch nonce {link.dispatch_nonce} already bound to " f"{prior}; {link.child_span_id} is a duplicate emission and must be quarantined" ) seen_nonces[link.dispatch_nonce] = link.child_span_id return True ```