Task · CXO-0037

Tool-call span IDs collide when a call ID is reused within a thread

Description

CXO-0028 froze the tool-call span ID as hashSpanID(threadID, callID) so a later tool_result span can link to its origin call. The correlation index already treats a reused call ID as ambiguous, but the trace sink still derives the same span ID for both calls, so two distinct spans in one trace share an ID and the second overwrites the first in Tempo. Traces are permanently disabled on camden (settled decision, CXO-0006), so this is source-level only; it should still be correct because the sink is shipped and tested. CodeRabbit raised it in the wave 2 review and it was deferred because it changes the frozen D14 contract. The derivation is internal/correlation/ids.go HashSpanID and its callers in internal/sink/otlptrace.

Acceptance Criteria

Definition of Done

Implementation Plan

Wave 3: execute the frozen lane ownership and decisions in codex/goal-2026-09-06-wave3.md. Root commits wave 0 before dispatch; L2/L7 depend on L1 and the recorded D15 verdict. Integrate, run the rewritten corpus gate once after wiring, review, push once, verify Watchtower and m7kni, then reconcile acceptance and resume boundaries.

Implementation Notes

Wave3 final source 3bc28996c78c3533ae30f370f01a247aa6dea3ac. One git push was rejected (fetch first); zero successful pushes. Remote advanced by Renovate at10:16:57Z. No second attempt under the one-push contract. Synthetic trace tests prove retained-history occurrence IDs and exact result links, ambiguous/none no links, legacy pin ecfd3b8786c25b11. Source-level, traces disabled. Unqualified reuse guarantee is not proven after per-thread512/global4096/24h eviction resets occurrence numbering. Resume requires an approved bounded identity design or explicitly scoped retention guarantee, followed by source tests and publication. Resume in a newly authorized publication run: fetch and reconcile remote main 72c29953f4d6a03528cbf0c2f319d30900cbaf01 with the preserved local commits, run the integrated gate on the reconciled tree, authorize a new push, verify CI/publish and Watchtower at that SHA, then update and read back the m7kni dashboard and prove the new dimensions live.

2026-09-06: Rob scoped AC to retained correlation history (traces are permanently off; a post-eviction collision would only overwrite a span in a disabled sink). Verified: go test -race ./internal/sink/otlptrace/ ok 1.401s covering TestToolCallSpanIDs_DistinguishReusedCallIDs, TestToolResultLink_UsesMatchedCallOccurrence, TestToolResultLink_NonExactMatchHasNoOriginLink, TestToolCallSpanID_UniqueCaseKeepsLegacyValue; docs/signals.md states the retained-history scope.

Final Summary

Source 3bc28996c78c3533ae30f370f01a247aa6dea3ac; no pushed SHA. Synthetic trace tests prove retained-history occurrence IDs and exact result links, ambiguous/none no links, legacy pin ecfd3b8786c25b11. Source-level, traces disabled. Unqualified reuse guarantee is not proven after per-thread512/global4096/24h eviction resets occurrence numbering. Resume requires an approved bounded identity design or explicitly scoped retention guarantee, followed by source tests and publication. Resume in a newly authorized publication run: fetch and reconcile remote main 72c29953f4d6a03528cbf0c2f319d30900cbaf01 with the preserved local commits, run the integrated gate on the reconciled tree, authorize a new push, verify CI/publish and Watchtower at that SHA, then update and read back the m7kni dashboard and prove the new dimensions live.

Closed at cce4673 with the AC scoped to retained correlation history by Rob’s decision; occurrence-based span IDs and exact/ambiguous links pinned by four synthetic tests (source-level, traces disabled).

View the source file on GitHub