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>
Completes the offline verification stack (P1.2 -> P1.1 -> P1.3) in all three
SDKs, each a faithful port of the backend windows.py / checkpoints.py math on
top of the frozen canonical/domain-hash primitives:
- verify_inclusion_proof: fold a window inclusion proof to the window root
(domain attesto.v2.window; left sibling -> node(sibling,current), right ->
node(current,sibling)).
- verify_checkpoint_root: recompute a checkpoint root from window hashes
(domain attesto.v2.checkpoint), with an odd node at any level **promoted
unchanged** rather than duplicated/hashed with itself (the place a naive
Merkle port silently diverges).
- verify_checkpoint_extension: current.from_seq_no == previous.to_seq_no + 1
and current.previous_checkpoint_hash == previous.checkpoint_hash.
- verify_completeness: proves no events were omitted in a range -- gap-free
seq_no coverage plus prev_event_hash chaining to the previous event_hash.
New corpus golden-vectors/sdk-parity/inclusion.json (5-leaf window exercising
the promoted odd node, 3-window checkpoint root, extension + completeness
negatives), exported from the backend functions. Proven: Python = TypeScript =
Go = backend agree on every case. READMEs updated per SDK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds payload_commitment / metadata_commitment / verify_payload_commitment
and assert_commitment_safe_numbers to the Python, TypeScript, and Go SDKs,
each building on the frozen canonical_json/domain_hash primitives (no change
to their byte output). The number preflight is a byte-for-byte port of the
backend assert_commitment_safe_numbers (floats rejected, |int| > 2^53-1
rejected, bool exempt) and is wired into the v2 log_event / log_events send
path, raising a typed AttestoUnsafeNumberError with the JSON path so the rule
fails at dev time rather than as a production 422; preflight=False /
SkipPreflight defers to the server.
New shared corpus golden-vectors/sdk-parity/canonical-numbers.json (15 accept
+ 8 reject), accept-hashes generated from the backend _commitment. Proven:
Python = TypeScript = Go = backend produce byte-identical commitment hashes
for every accept vector and identical reject paths (the Go float64-vs-Python-
int serialization parity holds). READMEs updated per SDK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FIX 11 — reject cross-language-divergent numbers in committed payloads.
append_stream_event now rejects non-integer numbers and integers beyond
±(2^53−1) in payload/metadata at ingestion (HTTP 422), so a customer
recomputing a commitment in a Go/TS verifier can never read it as
tampering. Documented in the protocol spec + all three SDK READMEs; 9
tests.
FIX 12 — Nova IVC chain-continuity invariant. record_ivc_epoch now takes
a per-stream transaction-scoped advisory lock and fails closed (409) if
an epoch does not extend the latest verified epoch's next_state_root or
would skip an unproven checkpoint — so two concurrent provers cannot fork
Attesto's own lane and a failed proof cannot be chained over. The worker
stops the pass on a failed proof (break, not continue) and always
backfills the oldest unproven checkpoint first so holes heal. Tests cover
both 409 paths plus in-order record + replay.
FIX 13 — pin the e2e v1 checkpoint shape. Config now requires
PROOFSTREAM_WINDOW_MAX_EVENTS=1 (alongside CHECKPOINT_MAX_WINDOWS=4) when
Nova is enabled, and the worker refuses to close an aged checkpoint below
the 4-window shape (which would be unprovable). Documented as the
deliberate fail-closed v1 behavior; config + worker tests.
VERIFY-1 — investigation: the standard production ingestion path
(SDK → append_stream_event → persist_stream_event_receipt) does NOT write
the nova_e2e boundary metadata the prover requires; no writer exists in
backend/app, and _e2e_vector_for_checkpoint requires (not derives) it. So
Nova proofs currently cover golden-vector/harness inputs, not live
customer receipts — the open Route A/B item. Recorded in
CRYPTO_REVIEW_CHECKLIST as a coverage-scope note; no Route A/B
implementation in this release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>