Skip to main content
Wave 0 defines the trace vocabulary before any OTLP receiver, database table, or instrumented provider exists. The implementation lives in voicegateway.telemetry and is intentionally internal: importing it does not start an exporter, alter a voice session, or pull in an OpenTelemetry SDK.

Scope

The contract models a trace as spans, events and links. It is transport- and storage-independent so the same record can later be built by native wrappers, OTLP ingestion, or a replay importer. Every model rejects unknown fields. Attribute keys are non-empty and unique within their collection. Attribute values support OpenTelemetry AnyValue: null, strings, booleans, signed 64-bit integers, finite doubles, bytes, arrays and nested maps. Non-OTLP JSON renders bytes as base64, following the OpenTelemetry non-OTLP representation; the future OTLP receiver will preserve native wire types.

W3C propagation

parse_traceparent() strictly validates incoming lower-case trace IDs and span IDs. Version 00 accepts exactly the four W3C fields. Higher versions retain the required core fields and ignore their extension fields. format_traceparent() emits version 00 when VoiceGateway creates a child span, carrying only the sampled bit permitted by that version. parse_tracestate() validates ordering, key uniqueness and the 32-member limit, then returns the accepted header unchanged. A transport adapter decides whether an invalid incoming header starts a fresh trace; the low-level parser raises instead of silently changing correlation. Task-local propagation uses a ContextVar. Callers must retain the token from set_trace_context() and pass it to reset_trace_context() so nested work restores the exact prior context.

Correlation and privacy

SpanAttributes.tenant_id is internal-authoritative. It is set from a verified principal at an ingestion boundary; an arbitrary external attribute with the same name never overrides it. correlation is a separate field from the free-form attributes collection, so that separation is structural rather than a convention a receiver has to remember. The half that is not yet structural is the boundary itself. No receiver exists, and this contract has a single tenant slot rather than one field for what the payload asserted and another for what the server derived. That is tracked as VG-SEC-015 in observability security contracts, filed under the same threat as VG-SEC-001, which is the live defect where a payload tenant_id beats the tenant resolved from the API key on a route that already ships. Read that gap before writing the receiver. This contract does not carry prompt text, transcripts, tool arguments, tool results, or encrypted payload bytes. Those belong to the security content contract and a later encrypted content plane.

Topology rules

  • A caller turn or logical LLM/tool operation is one parent span.
  • Each retry or provider fallback is a child attempt span sharing its parent trace ID; the attempt does not become an unrelated trace.
  • Provider acknowledgement and verification are distinct spans or events so an acknowledged write is never confused with a verified external effect.
  • Research, workflow, queue, and other background work use a span link when it is causal but not strictly nested work.
  • Timestamps are Unix epoch nanoseconds and an end time may not precede its start time.

Versioning and deferred work

SpanRecord.schema_version starts at 1. Stored records will retain that version so future readers can select a compatible decoder. Wave 0 intentionally does not add migrations, persistence, OTLP endpoints, SDK dependencies, GenAI semantic-convention constants, replay storage, UI views, or public root-package exports. Those land in later waves after their corresponding transport, security and retention decisions are implemented.