sovereign/sdk-guides
sdks Python · Go · TypeScript · Java protocol SSP 1.0 verification offline

SDK guides

Client + offline verification for the Sovereign Sign-off Protocol. Sign the upstream lifecycle, open a case for a human to sign, and verify a sealed Sovereign Record — in your language. Pick a language once and every snippet on this page follows.

two capabilities, four languages

Client — sign the upstream events (policy.committed, ai.inference.completed, guardrail.evaluated) with your Ed25519 key and open a case; emit outcome.executed once it's sealed. Verify — check a record.ssp.json offline: canonical claim hash and the Ed25519 trust signature (the Python SDK also verifies the WebAuthn user signature and the Rekor / RFC 3161 anchors).

1 · Install

From the Sovereign GitLab package registry (you supply a token with read_package_registry).

pip install sovereign-saa-sdk \
  --index-url "https://__token__:<read-token>@git.xor.ma/api/v4/groups/sovereign%2Fsovereign-sdk/-/packages/pypi/simple"
# with GOPRIVATE set and a token in ~/.netrc for git.xor.ma
go get git.xor.ma/sovereign/sovereign-sdk/saa-go-sdk@latest
# .npmrc
@sovereign:registry=https://git.xor.ma/api/v4/groups/sovereign%2Fsovereign-sdk/-/packages/npm/
//git.xor.ma/api/v4/groups/sovereign%2Fsovereign-sdk/-/packages/npm/:_authToken=<read-token>

npm install @sovereign/saa-sdk
<dependency>
  <groupId>ma.xor.sovereign</groupId>
  <artifactId>saa-sdk</artifactId>
  <version>0.1.0</version>
</dependency>
<!-- plus the sovereign-sdk <repository> + a Deploy-Token header in settings.xml -->

2 · Open a case

Sign the upstream events and open a case via the client API. The response carries the submission_id and the approver_url where a human signs.

from sovereign.saa import Client, Signer, base_claim

signer = Signer.load("saa-client.key")          # generates + persists on first use
client = Client("https://app.sovereign.xor.ma/api", api_key="saa-dev-client-key",
                signer=signer, tenant="acme-bank")

events = [signer.sign_event({**base_claim("ai.inference.completed", "acme-bank", "subj-001"),
          "result": {"recommendation": "approve", "risk_score": 0.18, "threshold": 0.70}})]

resp = client.create_case(
    domain="finance", artifact=b"Pay vendor ACME 12,750 USD", filename="wire.txt",
    mime_type="text/plain", client_events=events, subject="subj-001",
    context={"transaction_id": "TXN-1", "amount_usd": 12750, "account_id": "ACC-1"})
print(resp["submission_id"], resp["approver_url"])
signer, _ := saa.LoadSigner("saa-client.key")
client, _ := saa.NewClient("https://app.sovereign.xor.ma/api", "saa-dev-client-key",
    saa.WithSigner(signer), saa.WithTenant("acme-bank"))

ai := saa.BaseClaim("ai.inference.completed", "acme-bank", "subj-001")
ai["result"] = map[string]any{"recommendation": "approve", "risk_score": 0.18, "threshold": 0.70}
ev, _ := signer.SignEvent(ai)

resp, _ := client.CreateCase(saa.CaseInput{
    Domain: "finance", Artifact: []byte("Pay vendor ACME 12,750 USD"),
    Filename: "wire.txt", MimeType: "text/plain", Subject: "subj-001",
    Context:      map[string]any{"transaction_id": "TXN-1", "amount_usd": 12750, "account_id": "ACC-1"},
    ClientEvents: []map[string]any{ev},
})
fmt.Println(resp["submission_id"], resp["approver_url"])
import { Client, Signer, baseClaim } from "@sovereign/saa-sdk";

const signer = Signer.load("saa-client.key");   // generates + persists on first use
const client = new Client("https://app.sovereign.xor.ma/api", "saa-dev-client-key",
  { signer, tenant: "acme-bank" });

const ai = signer.signEvent({ ...baseClaim("ai.inference.completed", "acme-bank", "subj-001"),
  result: { recommendation: "approve", risk_score: 0.18, threshold: 0.7 } });

const resp = await client.createCase({
  domain: "finance", artifact: new TextEncoder().encode("Pay vendor ACME 12,750 USD"),
  filename: "wire.txt", mimeType: "text/plain", subject: "subj-001",
  context: { transaction_id: "TXN-1", amount_usd: 12750, account_id: "ACC-1" },
  clientEvents: [ai],
});
console.log(resp.submission_id, resp.approver_url);
Signer signer = Signer.load("saa-client.key");
SaaClient client = new SaaClient("https://app.sovereign.xor.ma/api",
    "saa-dev-client-key", signer, "acme-bank", /*insecureTls*/ false);

Map<String,Object> ai = Signer.baseClaim("ai.inference.completed", "acme-bank", "subj-001");
ai.put("result", Map.of("recommendation", "approve", "risk_score", 0.18, "threshold", 0.70));

SaaClient.CaseInput in = new SaaClient.CaseInput();
in.domain = "finance";
in.artifact = "Pay vendor ACME 12,750 USD".getBytes("UTF-8");
in.filename = "wire.txt"; in.mimeType = "text/plain"; in.subject = "subj-001";
in.context = Map.of("transaction_id", "TXN-1", "amount_usd", 12750, "account_id", "ACC-1");
in.clientEvents = java.util.Arrays.asList(signer.signEvent(ai));

Map<String,Object> resp = client.createCase(in);
System.out.println(resp.get("submission_id") + " " + resp.get("approver_url"));

3 · Record the outcome

After the decision is sealed, emit a client-signed outcome.executed linked to the ledger entry.

client.record_outcome(ledger_uuid, status="executed")
client.RecordOutcome(ledgerUUID, "executed")
await client.recordOutcome(ledgerUuid, "executed");
client.recordOutcome(ledgerUuid, "executed");

4 · Verify a record offline

Verify a sealed record.ssp.json with no server. The Sovereign trust public key is supplied out-of-band — never read from the record — so a forged bundle cannot self-certify.

import json
from sovereign.saa import verify_record

record = json.load(open("record.ssp.json"))
trust = open("trust-public.pem").read()         # out-of-band

result = verify_record(record, trust, rp_id="app.sovereign.xor.ma")
print(result)            # per-check PASS/FAIL + overall
assert result.ok
record, _ := os.ReadFile("record.ssp.json")
trust, _ := os.ReadFile("trust-public.pem")     // out-of-band

res, _ := saa.VerifyRecord(record, trust)
fmt.Println(res)         // per-check PASS/FAIL
// res.OK() == true
import { readFileSync } from "node:fs";
import { verifyRecord } from "@sovereign/saa-sdk";

const result = verifyRecord(
  readFileSync("record.ssp.json"),
  readFileSync("trust-public.pem", "utf8"));    // out-of-band
console.log(result.toString());
// result.ok === true
byte[] record = Files.readAllBytes(Paths.get("record.ssp.json"));
byte[] trust  = Files.readAllBytes(Paths.get("trust-public.pem"));  // out-of-band

VerifyResult res = Verify.verifyRecord(record, trust);
System.out.println(res); // per-check PASS/FAIL
// res.ok() == true

Notes

Canonicalization is RFC 8785-style JCS (key-sorted, compact, UTF-8) and is byte-identical across all four SDKs — a record signed via one verifies in any other. Verification depth in this release:

CheckPythonGoTypeScriptJava
Canonical claim hash
Ed25519 trust signature
WebAuthn user signature
Rekor inclusion · RFC 3161 timestamp