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

Attesto Go SDK

Official Go SDK for Attesto 2.0 Proofstream. The default API base URL is https://verify.attesto.eu. Use it from server-side, infrastructure, security tooling, CI, evidence exporters, and operator automation. Do not embed Attesto API keys in browser bundles, mobile apps, or public artifacts.

Install

go get git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go

The first release is VCS-resolved from the Attesto repository. It intentionally uses only the Go standard library.

Quickstart

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	attesto "git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go"
)

func main() {
	ctx := context.Background()
	client, err := attesto.NewClient(os.Getenv("ATTESTO_API_KEY"))
	if err != nil {
		log.Fatal(err)
	}

	stream, err := client.CreateStream(ctx, attesto.StreamCreateInput{
		UseCase:  "ai-governance",
		PolicyID: "policy-main",
	})
	if err != nil {
		log.Fatal(err)
	}

	receipt, err := client.LogEvent(ctx, stream.StreamID, attesto.EventInput{
		SourceRef:  "decision-42",
		OccurredAt: time.Now().UTC().Format(time.RFC3339Nano),
		Payload: attesto.M{
			"model": "risk-classifier",
			"score": 0.92,
		},
	})
	if err != nil {
		log.Fatal(err)
	}

fmt.Println(receipt.StreamEventID, receipt.EventHash)
}

Attesto stores source-system time separately from backend ingest time. OccurredAt must be RFC3339 with a timezone offset. The Go SDK fills it with time.Now().UTC() when omitted, but production integrations should pass the real upstream event timestamp whenever the source system provides one.

Committed payload number rule

When events are committed to a Proofstream, payload and metadata numbers must serialize identically across Python, Go, and JavaScript. Non-integer numbers and integers beyond ±(2^531) are rejected at ingestion (HTTP 422); encode decimals and large integers as strings (e.g. {"score": "0.87"}). This keeps cross-language commitment recomputation byte-exact (CanonicalJSON).

The SDK enforces the same rule locally before sending, so you see it at dev time rather than as a production 422. LogEvent / LogEvents return an *UnsafeNumberError (with .Path, the JSON path to the offending value). Set RequestOptions{SkipPreflight: true} to defer to the server.

// Commitment a Proofstream stores for a payload, byte-identical to the server
// (and to the Python / TypeScript SDKs):
commitment, _ := attesto.PayloadCommitment(map[string]any{"decision": "approve", "score_bp": 8700})
// commitment["canonical_payload_hash"] == server's stored hash

ok, _ := attesto.VerifyPayloadCommitment(myPayload, event) // recompute and compare

Verification

Remote verification uses Attesto's public /v2/verify API. Offline receipt verification uses ATTESTO-PROOFSTREAM-001 canonical JSON, domain-separated hashes, and Ed25519 signature verification locally.

report := attesto.VerifyReceiptOffline(receipt.Receipt, publicKeyHex)
if !report.OK {
	log.Fatalf("receipt failed verification: %v", report.Problems)
}

The offline trust model extends across the whole proof chain — all client-side:

ok, _ := attesto.VerifyInclusionProof(leafHash, proof, windowRoot)   // event in a window root
ok, _ = attesto.VerifyCheckpointRoot(windowHashes, checkpointRoot)   // windows fold to checkpoint root
ext := attesto.VerifyCheckpointExtension(previous, current)          // one checkpoint continues the previous
comp := attesto.VerifyCompleteness(events, 5, 8)                     // no events omitted in [5, 8]

VerifyCompleteness proves no events were omitted in a range: the sequence numbers must be gap-free and each event's prev_event_hash must chain to the previous event's event_hash.

Your SDK is a witness

The client remembers the last accepted (seqNo, eventHash) per stream and checks every new receipt links forward. If the server ever rewinds a sequence number or presents a divergent lineage, LogEvent / LogEvents return a *ForkDetectedError and the stored head is not advanced. The default store is in-memory; use a file store for fork detection across process invocations, or disable it.

// Persist across CLI invocations (atomic, 0600 at ~/.attesto/heads.json):
client, _ := attesto.NewClient(apiKey, attesto.WithHeadStore(attesto.NewFileHeadStore("")))

// Disable fork detection:
client, _ = attesto.NewClient(apiKey, attesto.WithHeadStore(nil))

Operator and Admin Endpoints

System-key clients are created with attesto.NewClient. Tenant/operator endpoints, including connector installation and Local Vault installation management, use attesto.NewBearerClient with a tenant bearer token obtained from the dashboard session flow.

Secrets returned once by connector creation are present only in the returned struct and are never logged by the SDK.

Description
Official Attesto Go SDK (read-only mirror of the sdk/go subtree). Module: go.attesto.eu/sdk
Readme 92 KiB
Languages
Go 100%