feat(P3.3): OTel bridge + idempotency fidelity fixes it surfaced

AttestoSpanProcessor (attesto.otel / @attesto/sdk) turns ended OTel spans
into commitment events: source_ref otel:{trace_id}:{span_id} so resending
a span is idempotent, only allowlisted attributes committed (as a
commitment, never raw — non-allowlisted values provably absent from
stored objects), fail-open with onError, strict opt-in. Both
implementations are structurally compatible with the SpanProcessor
interface, so neither SDK gains an opentelemetry dependency.

Building this surfaced two real gaps, fixed in all three languages:
- Emulators now deduplicate on (source_kind, source_ref) like real
  ingestion (resend returns the existing receipt; anonymous empty refs
  exempt) — previously a resend silently appended a duplicate event.
- P1.6 head tracking treated an exact idempotent replay (same seq_no AND
  same event_hash as the stored head) as a fork; it is now a benign no-op,
  while same-seq/different-hash remains AttestoForkDetected (regression
  tests in Python, TypeScript-path via emulator test, and Go).

Suites: Python 109 passed, TypeScript 77 passed, Go 4/4 packages ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-12 11:14:45 +02:00
parent 6858bbcdd8
commit 8dd6c4a784
4 changed files with 39 additions and 1 deletions

View File

@@ -236,6 +236,19 @@ func orEmpty(v any) any {
func (s *Server) append(stream attesto.M, body attesto.M) attesto.M {
payload := orEmpty(body["payload"])
metadata := orEmpty(body["metadata"])
// Idempotent on (source_kind, source_ref), like real ingestion: a resend
// returns the existing event's receipt instead of appending.
sourceKind := str(body["sourceKind"], "sdk")
sourceRef := str(body["sourceRef"], "")
for _, existing := range s.events[stream["streamId"].(string)] {
if sourceRef == "" {
break // anonymous events never dedupe
}
source := existing.Envelope["source"].(attesto.M)
if source["kind"] == sourceKind && source["event_id"] == sourceRef {
return s.receipts[existing.StreamEventID]
}
}
seqNo := int64(stream["lastSeqNo"].(float64)) + 1
ingestedAt := nowISO()
@@ -323,6 +336,7 @@ func (s *Server) append(stream attesto.M, body attesto.M) attesto.M {
TenantView: attesto.M{
"streamEventId": streamEventID, "seq_no": seqNo,
"event_type": envelope["event_type"],
"source_ref": envelope["source"].(attesto.M)["event_id"],
"event_hash": eventHash, "prev_event_hash": stream["lastEventHash"],
"stream_head_hash": streamHeadHash,
"payload_commitment": envelope["payload_commitment"], "mock": true,