feat: emit INTERNAL span kind for same-service sync calls#231
Conversation
Per the OTel SpanKind spec, INTERNAL represents an operation that does not cross a remote boundary. Sync calls targeting an operation on the caller's own service now emit INTERNAL spans instead of CLIENT; cross- service sync calls keep CLIENT, and async/producer callees keep CONSUMER/PRODUCER regardless of service. Roots still always win as SERVER. spanKindFor now takes the parent operation, threaded through walkTrace (which already had it) and planTrace/planRejectionSpan (new parameter) so realtime plan mode matches. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019nKG5nUHLBi6KNegsvqR1V
Adversarial review resultsRan an 8-angle adversarial review of this PR (line-by-line diff scan, removed-behavior audit, cross-file tracing, reuse/simplification/efficiency/altitude checks, conventions). Findings, most severe first: Confirmed — will fix in follow-up commits1. Replay mode diverges from generation for same-service children — 2. String comparison where pointer comparison is guaranteed equivalent — 3. Duplicated test topology — Considered and refuted
Design notes (no change planned)
Fixes for 1–3 incoming. 🤖 Generated with Claude Code Generated by Claude Code |
…onfig Address adversarial review findings on #231: - Replay mode now derives INTERNAL for a recorded child span on its parent's service, matching spanKindFor's rules for generated traces (previously every non-root replayed span was stamped CLIENT). - spanKindFor compares *Service pointers instead of names; BuildTopology guarantees all operations on a service share one Service instance. - The same-service test topology is a shared helper instead of being duplicated between engine_test.go and plan_test.go. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019nKG5nUHLBi6KNegsvqR1V
Back-fill the 0.9.1, 0.9.2, and 0.10.0 sections that were released via GoReleaser but never recorded in CHANGELOG.md, and move already-shipped work out of [Unreleased]: replay mode (#209, 0.10.0), PRODUCER span kind (#214, 0.9.2), and reader-based replay entry points (#216, 0.9.2). Promote the genuinely-unreleased work to [0.11.0]: the INTERNAL span-kind change (#213/#231), the public pkg/synth trace-generation API (#199/#234), and the sampling trace-integrity invariants (#74/#235/#236), plus the OTLP exporter dependency bumps (#228, #229). Note that 0.9.2 was tagged after 0.10.0 from a later commit, so the next minor release is 0.11.0, and fix the compare links. Claude-Session: https://claude.ai/code/session_01G1HxLtj1Uu2Y3K6Ea5P9zR Co-authored-by: Claude <[email protected]>
Closes #213.
Per the OTel SpanKind spec,
INTERNALrepresents an operation that does not cross a remote/process boundary. motel previously emittedCLIENTfor every non-root sync callee, including calls to another operation on the same service.Changes
spanKindFornow takes the parent operation and returnsSpanKindInternalwhen a sync callee lives on the same service as its caller. Precedence is unchanged otherwise: roots always win asSERVER, thenproducer→PRODUCER, thenasync→CONSUMER, then same-service →INTERNAL, elseCLIENT.walkTracealready carried the parent operation;planTrace/planRejectionSpan(realtime plan mode) gained aparentparameter so both paths agree, including rejection spans.checkout.submit → checkout.validatevalidates fine (verified end-to-end viamotel run --stdout: SERVER root, INTERNAL same-service child, CLIENT cross-service grandchild).Tests
TestSameServiceSyncCallSpanKindIsInternal: same-service sync callee isINTERNAL, its cross-service downstream call is stillCLIENT, root staysSERVER.TestSameServiceAsyncCallSpanKindIsConsumer: async precedence pinned — a same-service fire-and-forget callee staysCONSUMER.TestPlanTraceSameServiceSyncCallKind: plan-mode parity for the same topology.TestSyncCallSpanKindIsClient, producer/consumer tests) unchanged and passing.Docs
cmd/motel/README.md: kind-selection rules documented in thecallssection, with a note on modelling in-process sub-operations.CHANGELOG.md: entry under Unreleased → Changed (behavior change for existing configs with same-service sync calls).docs/explanation/property-testing.md: clarified that the "non-root spans are CLIENT" property holds because the generator only produces cross-service calls.🤖 Generated with Claude Code
https://claude.ai/code/session_019nKG5nUHLBi6KNegsvqR1V
Generated by Claude Code