agent_trace.to_otel returns flat span dicts that are not valid OTLP/JSON
(no resourceSpans / scopeSpans nesting, no proper attribute encoding,
times not as uint64 strings). This shapes a list of spans into the envelope an
OpenTelemetry collector ingests directly via its file exporter.
Pure standard library (json); imports no PySide6. Times are supplied by
the caller (no wall clock), so the envelope is byte-stable and CI-testable.
from je_auto_control import spans_to_otlp, write_otlp
spans = [{
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"name": "run_suite",
"start_unix_nano": started_ns, "end_unix_nano": ended_ns,
"attributes": {"ok": True, "cases": 12},
}]
payload = spans_to_otlp(spans, resource_attrs={"service.name": "autocontrol"})
write_otlp(payload, "trace.otlp.json")spans_to_otlp wraps spans in the resourceSpans → scopeSpans → spans
structure: trace/span IDs stay hex, times become uint64 strings, and attributes
are encoded as OTLP KeyValue entries (stringValue / intValue /
boolValue / doubleValue). attributes_to_otlp exposes that attribute
conversion, and write_otlp writes the payload as JSON. The result is what an
OpenTelemetry collector's file exporter reads — pairing with trace_context
for the IDs and agent_trace for the span data.
AC_spans_to_otlp wraps spans (with optional resource_attrs) into
{payload}. It is exposed as the MCP tool ac_spans_to_otlp and as a
Script Builder command under Report.