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:
@@ -80,3 +80,20 @@ func TestFileHeadStorePersistsAndIs0600(t *testing.T) {
|
||||
t.Error("expected fork on reopened store")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExactReplayOfStoredHeadIsBenign(t *testing.T) {
|
||||
// [P3.3 regression] A deduplicated resend returns the same receipt; the
|
||||
// head tracker must treat (same seqNo, same eventHash) as a no-op.
|
||||
store := NewMemoryHeadStore()
|
||||
first := EventReceipt{StreamID: "str_x", SeqNo: 1, EventHash: "h1"}
|
||||
if err := checkAndAdvanceHead(store, first); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkAndAdvanceHead(store, first); err != nil {
|
||||
t.Fatalf("exact replay must be benign, got %v", err)
|
||||
}
|
||||
fork := EventReceipt{StreamID: "str_x", SeqNo: 1, EventHash: "h2"}
|
||||
if err := checkAndAdvanceHead(store, fork); err == nil {
|
||||
t.Fatal("same seq with different hash must be a fork")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user