T3 — Context propagates
Trace context (trace ID/span ID) is propagated across any new service/process boundary the diff introduces (e.g., passed in outbound headers), not dropped.
Python Implementation
T3: trace context is propagated across any new service/process boundary
the diff introduces (e.g. passed in outbound headers), not dropped.
rubric_store/definitions/traces.yaml tags this `both`; the static half is
scoped to HTTP-shaped outbound calls specifically (get/post/put/delete/
patch/request) — a DB call doesn't propagate trace context via HTTP headers,
so a generic outbound_call construct would produce nonsense findings for
one if this rule weren't scoped down first.
Three-way, not two-way, because "has a headers= argument with something
that looks like context injection" is a much stronger signal than "has a
headers= argument at all": a bare `headers={"Accept": "application/json"}`
technically has headers but propagates nothing.
The injection call itself is checked two ways, not just inline in the
`headers=` value: the idiomatic OTel pattern builds the dict on an earlier
line (`headers = {}`, `propagate.inject(headers)`, then
`headers=headers`) — checking only the argument's own text would miss this
entirely and call it "unclear" on almost all real code. So this also
searches the enclosing function for any `.inject(...)`-shaped call that
textually precedes the outbound call (same "textual precedence" idiom
go/ast_helpers.py uses for T1/R1, since Python has the identical problem
here: no cross-statement data-flow tracking in this pass).
Java Implementation
T3 for Java: trace context is propagated across a new service boundary. Not a straight port: Java's HTTP clients (HttpClient, OkHttp, Apache HttpClient) set headers via individual `.header(key, value)` builder calls on a *different* object (the request builder) than the one the outbound call itself is made on (`client.send(request, ...)`), not a single `headers=` argument the way Python's version can inspect directly. This checks the enclosing method for either an explicit trace-header `.header( "traceparent", ...)` call or a propagator's `.inject(...)` call, presence- anywhere (same tradeoff has_nearby_timeout_call already makes for R1) — Java's fluent builder chain is typically constructed across multiple statements, not necessarily right before the send. Scoped to outbound_call constructs whose method name looks HTTP-client- shaped (send/execute/get/post/put/delete/patch/newCall) — Java has no verb-named methods as uniform as Python's requests/httpx, so this is a broader, heuristic filter than the Python version's exact HTTP-verb match.
Go Implementation
T3 for Go: trace context is propagated across a new service boundary.
Go's OTel idiom injects context via `propagation.TraceContext{}.Inject(ctx,
propagation.HeaderCarrier(req.Header))` (or an equivalent Inject call) —
checked presence-anywhere in the enclosing function, same non-block-scoped
tradeoff as T1/R1/T2. Scoped to outbound_call constructs whose method name
looks HTTP-client-shaped (Do/Get/Post/Put/Delete/Patch on Go's net/http
client) — a DB driver call doesn't propagate context via HTTP headers.
Known, disclosed gap: a transport wrapped with otelhttp.NewTransport(...)
injects context automatically with no explicit .Inject(...) call anywhere
in the function — this rule can't see that and would flag it as
"unclear" rather than pass. Detecting transport-level instrumentation is
out of scope for this pass.