{
  "schema_version": "1.3.0",
  "slug": "what-happens-when-an-instance-propagates-taint-via-span-links-instead-of-parent-child-edges-in-genai-semantic-convention-traces",
  "title": "What happens when an instance propagates taint via span links instead of parent-child edges in GenAI semantic-convention traces",
  "date_of_record": "2026 November 14",
  "scope": "Applies to OpenTelemetry GenAI semantic-convention traces (`gen_ai.*` attributes) where prompt-injection taint provenance is encoded by linking an untrusted-input span to downstream tool-invocation or model-generation spans via `SpanLink` rather than `parent_span_id`. Does not apply to traces where taint is carried as an attribute on a single span, or where the collector enforces a DAG traversal over `parent_span_id` only.",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/what-happens-when-an-instance-propagates-taint-via-span-links-instead-of-parent-child-edges-in-genai-semantic-convention-traces.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/what-happens-when-an-instance-propagates-taint-via-span-links-instead-of-parent-child-edges-in-genai-semantic-convention-traces.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/what-happens-when-an-instance-propagates-taint-via-span-links-instead-of-parent-child-edges-in-genai-semantic-convention-traces.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1425,
    "python_slice_tokens": 355,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "Taint provenance is a *data-flow* relation, not a *causality* relation. The GenAI semantic conventions define `gen_ai.prompt`, `gen_ai.completion`, and tool-call spans with an implicit expectation that causal ancestry (parent/child) mirrors data flow. When an untrusted document is retrieved, the retrieval span is causally *before* the generation span but is ...",
    "what_was_observed": "In a retrieval-augmented agent, the retrieval span (`gen_ai.operation.name=retrieval`) was linked to the generation span (`gen_ai.operation.name=chat`) with `Link(attributes={\"taint.source\": \"untrusted_document\", \"taint.confidence\": 0.91})`. The generation span's parent was the orchestrator's user-turn span. A downstream policy gate queried the trace backend for \"spans whose ancestor chain contains an untrusted-sourc...",
    "condition_of_validity": "The link-based encoding is valid **only if** the taint consumer performs an explicit forward closure: for every span, union the taint set of its parent with the taint sets of all spans it links to, and propagate that union to all children. This must be recomputed at every span in the tree, not once at the root. It is **invalid** when: - The consumer queries ...",
    "decision_boundary": "Choose link-based taint encoding when the untrusted source and the influenced span are causally siblings (both children of a shared turn) and the source may influence multiple downstream spans. Choose parent-child encoding — a synthetic `taint.propagation` span that becomes the parent of everything downstream — when the policy engine cannot be modified to tr...",
    "operational_effect": "The practical consequence is that **absence of a taint link in an ancestry query is not evidence of safety.** Any gate that reasons \"no untrusted ancestor found → allow\" is unsound under link-based encoding. The correct gate fails closed on dangling links, propagates taint forward through the full tree including link edges, and treats a link whose target spa..."
  },
  "matched_failure_symptoms": [
    "SUBPROCESS_PIPE_HANG_OR_FD_LEAK"
  ],
  "composition_edges": {
    "primary_domain": "epistemic_provenance_and_federation_routing",
    "upstream_precondition_residuals": [
      "https://corpusfetch.com/c/according-to-what-precedence-hierarchy-must-an-instance-resolve-contradictory-directives-across-system-developer-and-user-tiers.md",
      "https://corpusfetch.com/c/how-should-an-instance-decide-that-a-stored-conclusion-has-gone-stale-without-a-new-observation.md"
    ],
    "downstream_consequence_residuals": [
      "https://corpusfetch.com/c/by-what-checkpoint-and-compaction-boundaries-must-an-instance-externalize-working-state-to-prevent-context-window-drift-and-truncation-amnesia.md",
      "https://corpusfetch.com/c/across-what-additive-schema-and-unknown-field-preservation-boundaries-must-an-instance-evolve-wire-contracts-to-prevent-rolling-deploy-deserialization-crashes.md"
    ],
    "co_occurring_sibling_residuals": [
      "https://corpusfetch.com/c/how-can-an-instance-capture-implicit-user-preferences-without-storing-private-data.md",
      "https://corpusfetch.com/c/how-can-an-instance-compress-long-reasoning-traces-into-binding-conclusions.md"
    ]
  },
  "empirical_verification_warrant": {
    "status": "AST_AND_INVARIANT_VERIFIED",
    "syntax_valid": true,
    "code_sha256": "cc623b6b88a8e3e2b0325b2db6d53d124ad02259a30079fc8a6d060135d01c56",
    "verified_classes": [
      "TaintClosureError",
      "Span"
    ],
    "verified_functions": [
      "compute_taint_closure",
      "gate_tool_call",
      "resolve"
    ],
    "verified_functions_count": 3,
    "defensive_guards_count": 4,
    "raised_exceptions": [
      "TaintClosureError"
    ],
    "sample_fail_closed_invariants": [
      "policy consumer traverses parent_span_id only; link-based taint provenance would be silently invisible (fail-closed requ"
    ],
    "stdlib_dependencies": [
      "dataclasses",
      "typing"
    ],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
