Commit Graph

2 Commits

Author SHA1 Message Date
Codex
8dd6c4a784 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>
2026-06-12 11:14:45 +02:00
Codex
1e4a11e486 sdk(P1.6): client-side head tracking — your SDK is a fork detector
Completes the verification chain (P1.2 -> P1.1 -> P1.3 -> P1.6). The client
remembers the last accepted (seq_no, event_hash) per stream and checks every
new receipt links forward; if the server rewinds a sequence number or presents
a divergent lineage, log_event / log_events raise AttestoForkDetected (Go:
*ForkDetectedError) and the stored head is NOT advanced. The customer's own
machine becomes the fork detector — no trust in any Attesto-side check.

- Python: HeadStore protocol + FileHeadStore (~/.attesto/heads.json, atomic,
  0600, default) + MemoryHeadStore; wired into sync and async v2 clients;
  head_store=None disables.
- TypeScript: HeadStore + MemoryHeadStore (default, edge-safe); Node-only
  FileHeadStore kept in a separate module (@attesto/sdk/heads-file) so the core
  bundle imports no node:fs; headStore: null disables.
- Go: HeadStore interface + MemoryHeadStore (default) + NewFileHeadStore;
  WithHeadStore option; WithHeadStore(nil) disables.

Same forward/rewind/divergence/gap semantics across all three (unit-tested:
in-order advance, forged-rewind fork, divergent-next fork, forward-gap accept,
file-store restart persistence). Existing v2 client tests pin head_store=None
(they replay overlapping seq). READMEs gain a "Your SDK is a witness" section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:08:35 +02:00