Skip to main content

Go SDK

The client package submits application inputs, reads current authority capabilities and recovers exact signed operations. Process references identify the destination and realm; current grants determine access. An admission receipt establishes acceptance of an input, independently of its processing or result. The shared Zig implementation owns canonical encoding, signatures, response validation and recovery. Go owns typed values, cancellation and native lifetime.

The module is github.com/spoke-sh/transit/sdks/go. Its public entry point is github.com/spoke-sh/transit/sdks/go/client. Catalog, query and Station handler packages are not exposed. The raw client has no Station library dependency.

Installation​

Build from a matching source revision using the binding tasks. Use the pinned Go 1.26.7 toolchain, Linux amd64 and cgo with a C compiler. The package has no external Go module dependencies. The native libraries retain their build's Nix runtime closure; portable native artifacts and public module releases are not provided by these tasks.

The source build produces two local installation archives:

  • transit-go-0.2.0.tar.gz: the Go module and generated ABI resources.
  • transit-go-native-0.2.0.tar.gz: lib/libtransit_client.so.

Extract them into separate SDK and native installation directories. In a consumer module, require github.com/spoke-sh/transit/sdks/go v0.2.0 and use a Go replace directive pointing to the extracted SDK module. Build with CGO_ENABLED=1. Set Config.NativeDirectory to the installed native lib directory. The runtime needs that directory and the native Nix closure, independently of the Go source and build cache. It does not need LD_LIBRARY_PATH or a Zig compiler at runtime.

Open checks every required native symbol, the complete current ABI definition and supported operation kinds before creating a handle. Missing or mismatched libraries reject; no alternate library or protocol is selected. Other platforms and builds with cgo disabled retain the value types but reject native open. Package versions do not establish protocol compatibility. Follow the current-v1 policy.

Prepare, save and dispatch​

package application

import (
"context"
"github.com/spoke-sh/transit/sdks/go/client"
)

func Send(ctx context.Context, config client.Config, credential client.Credential,
peer client.ProcessRef, sequence client.Sequence, value []byte,
persist func([]byte) error) (client.AdmissionReceipt, error) {
caller, err := client.Open(config)
if err != nil { return client.AdmissionReceipt{}, err }
defer caller.Close()

pending, err := caller.PrepareSend(ctx, peer, value, credential, sequence)
if err != nil { return client.AdmissionReceipt{}, err }
if err := persist(pending.Export()); err != nil {
return client.AdmissionReceipt{}, err
}
return caller.Dispatch(ctx, pending)
}

Supply credentials explicitly with NewCredential(realm, principal, seedFile). The seed file contains 32 bytes encoded as hexadecimal. Import and construction read no credentials and start no network work. The seed is read only when signing a prepared send or capability query. Temporary key buffers are cleared after use.

PrepareSend creates private owned work without submission. Export returns a copy of the exact recovery envelope. Persist it durably before dispatch if the application needs recovery after process loss. The envelope contains application data and a signature, but no private seed. Its presence does not prove acceptance. The complete example saves and syncs that file before a separate dispatch invocation.

RecoverSend imports the original envelope and validates its native operation kind; RecoverMutation accepts other native mutations. Both work without the original signing seed. Dispatch and DispatchMutation attempt the same frozen operation once. They never assign a new sequence, alter the payload, or retry automatically. The authority checks the original signature and current grants.

Native counters are bounded by MaxCounter (2^53 - 1). Realm, process and sequence values are positive; principal zero is valid. A send contains at most MaxValueBytes (256 bytes). Constructors and operations reject invalid values. The current profile and credentials must be selected explicitly; locally loaded ABI support does not establish server capability or authorization.

Cancellation and ownership​

Methods accept context.Context. An already cancelled context prevents admission. Cancellation after admission stops waiting while the client continues to own native work. For dispatch, the returned error matches both ErrUnknownOutcome and the context cause. Inspect *DispatchError with errors.As and retain its copied Recovery() bytes. Cancellation does not undo an accepted input.

One operation may be active per client. Concurrent calls return ErrBusy, including after an await is cancelled while native I/O remains active. Separate clients sharing a principal need an application-owned sequence allocator. Native I/O uses the configured timeout, 1 millisecond through 60 seconds; zero selects 30 seconds. Context cancellation does not shorten that native timeout.

Close stops admission, joins active work, destroys native ownership and releases the client's worker. It is idempotent and safe to call concurrently. Use defer caller.Close(); garbage collection does not release an open client. The last successfully observed admission and mutation receipts remain available through LastAdmission and LastMutation, including after cancellation or close. Each receipt carries its original operation Identity; compare it with the pending operation before attributing the receipt to a particular attempt. A failed later attempt does not replace previously observed evidence. Go values and exported bytes remain valid after closing the client.

Every native call runs on one owned POSIX thread with at least the ABI's 8 MiB stack. Inputs crossing cgo use C-owned memory; returned buffers are copied before native release. Loaded native code stays mapped until process exit while detached native I/O threads finish returning. Repeated opens reuse the same mapping, with at most sixteen distinct loaded images. Keep installed library files immutable: replacement or removal rejects a subsequent open of that configured path.

Errors and diagnostics​

Use errors.Is for categories such as ErrAccessDenied, ErrBusy, ErrClosed, ErrIncompatibleNative, ErrReadFailed and ErrUnknownOutcome. errors.As exposes *Error with native status, bounded authority code and optional owned diagnostic. Diagnostic() returns a copy. Malformed optional detail is discarded and marks DiagnosticInvalid; it never changes the primary outcome. All formatting verbs exclude diagnostic content, credential paths and saved request bytes.

Read failure establishes no write outcome. A denied retry does not undo an earlier acceptance. Follow caller semantics for current authorization, exact recovery and uncertainty.

Development and verification​

The Go binding tasks cover formatting, vetting, race detection, strict cgo pointer checks and generated-resource drift. The installed-consumer task builds the installation archives and exercises an external consumer and the real example against a disk-backed authority. The proof ledger records executed coverage.