From 180bec464382fc13c1ea28f7cec401e0da14678b Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 11 Jun 2026 19:24:20 +0200 Subject: [PATCH] sdk+backend(P1.9): protocol-version handshake on every request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three SDKs now send X-Attesto-Sdk (attesto-/) and X-Attesto-Protocol (ATTESTO-PROOFSTREAM-001/0.1-alpha) on every request. A new backend ProtocolVersionMiddleware logs both headers (operators can see the SDK/protocol mix in traffic) and, when the protocol header is present on a /v2 request and names a different protocol identifier or major version, answers 426 Upgrade Required with a structured body (error/supported/received/hint). Absent or unparseable headers change nothing — old clients and curl stay fully compatible (test-asserted, including /v1 never being handshake-gated). SDKs surface the 426 as a typed error: Python AttestoProtocolMismatch, TypeScript AttestoProtocolMismatch, Go IsProtocolMismatch(err) over *APIError (Go-idiomatic). Tests cover the mismatch rules, the 426 mapping, and that the handshake headers are actually sent. Co-Authored-By: Claude Fable 5 --- client.go | 11 +++++++++++ version.go | 3 +++ 2 files changed, 14 insertions(+) diff --git a/client.go b/client.go index 7469b57..9e32217 100644 --- a/client.go +++ b/client.go @@ -534,6 +534,9 @@ func (c *Client) requestRaw(ctx context.Context, method, path string, values url req.Header.Set("Authorization", "Bearer "+c.bearer) req.Header.Set("User-Agent", c.userAgent) req.Header.Set("X-Attesto-SDK", c.userAgent) + // [P1.9] protocol handshake: the backend answers 426 when it speaks a + // different protocol generation (ErrProtocolMismatch via APIError). + req.Header.Set("X-Attesto-Protocol", ProtocolHandshake) if body != nil { req.Header.Set("Content-Type", "application/json") } @@ -577,6 +580,14 @@ func (e *APIError) Error() string { return fmt.Sprintf("attesto api error: status=%d message=%s", e.StatusCode, e.Message) } +// IsProtocolMismatch reports whether err is a 426 protocol-handshake rejection: +// the backend speaks a different protocol generation than this SDK announced in +// X-Attesto-Protocol. Upgrade the SDK to a release that speaks it. +func IsProtocolMismatch(err error) bool { + var apiErr *APIError + return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusUpgradeRequired +} + func decodeResponse(resp *http.Response, out any) error { defer resp.Body.Close() if resp.StatusCode == http.StatusNoContent { diff --git a/version.go b/version.go index 96cbd86..3af03b0 100644 --- a/version.go +++ b/version.go @@ -5,4 +5,7 @@ const ( DefaultBaseURL = "https://verify.attesto.eu" ProofstreamProtocol = "ATTESTO-PROOFSTREAM-001" ProtocolVersionAlpha = "0.1-alpha" + // ProtocolHandshake is sent as X-Attesto-Protocol on every request; the + // backend answers 426 when it speaks a different protocol generation. + ProtocolHandshake = ProofstreamProtocol + "/" + ProtocolVersionAlpha )