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.
Client — sign the upstream events (policy.committed,
ai.inference.completed, guardrail.evaluated) with your Ed25519 key and open a case
(single approver, or a multi-approver quorum policy); learn how it was decided via a
sealed-decision callback or by polling case_status; emit
outcome.executed once it's sealed. Verify — check a record.ssp.json
offline with no server: canonical claim hash, the approvers' WebAuthn signatures, quorum satisfaction,
the Ed25519 trust signature, and the Rekor / RFC 3161 anchors. Every language, same depth.
From the Sovereign GitLab package registry (you supply a token with read_package_registry).
Current release: 0.4.0 everywhere. Python, npm and Maven come from the registry; Go resolves the
v0.4.0 git tag on its repo — every SDK is tagged v0.4.0 at the same commit.
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@v0.4.0
# @latest also works; releases are git tags, so pin the tag for a
# reproducible build rather than taking whatever main resolves to# .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.4.0</version>
</dependency>
<!-- plus the sovereign-sdk <repository> + a Deploy-Token header in settings.xml -->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.
due_at is required — every case carries an explicit approval SLA. Pass an optional
quorum policy for a multi-approver sign-off; omit it for a single approver.
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"},
due_at="2026-12-31T17:00:00Z", # required: approval SLA deadline
# optional — roles with counts ("Senior Credit Officer*2, Compliance") or
# named users ("@jdoe, @bofficer"); the required count comes from the slots
quorum={"dsl": "Senior Credit Officer*2, Compliance",
"mode": "parallelRoles", # or sequential / parallel / custom
"workflow": {"type": "ratify"}}) # or independent + vote/veto
print(resp["submission_id"], resp["approver_url"])import (
"fmt"
saa "git.xor.ma/sovereign/sovereign-sdk/saa-go-sdk"
)
signer, _ := saa.LoadSigner("saa-client.key") // generates + persists on first use
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},
DueAt: "2026-12-31T17:00:00Z", // required: approval SLA deadline
// Optional — roles with counts or named @users; count comes from the slots.
Quorum: map[string]any{
"dsl": "Senior Credit Officer*2, Compliance",
"mode": "parallelRoles", // or sequential / parallel / custom
"workflow": map[string]any{"type": "ratify"}, // or independent + vote/veto
},
})
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],
dueAt: "2026-12-31T17:00:00Z", // required: approval SLA deadline
// optional — roles with counts or named @users; count comes from the slots
quorum: { dsl: "Senior Credit Officer*2, Compliance",
mode: "parallelRoles", // or sequential / parallel / custom
workflow: { type: "ratify" } }, // or independent + vote/veto
});
console.log(resp.submission_id, resp.approver_url);import ma.xor.sovereign.saa.*;
import java.util.Map;
Signer signer = Signer.load("saa-client.key"); // generates + persists on first use
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));
in.dueAt = "2026-12-31T17:00:00Z"; // required: approval SLA deadline
// optional — roles with counts or named @users; count comes from the slots
in.quorum = Map.of("dsl", "Senior Credit Officer*2, Compliance",
"mode", "parallelRoles", // or sequential / parallel / custom
"workflow", Map.of("type", "ratify")); // or independent + vote/veto
Map<String,Object> resp = client.createCase(in);
System.out.println(resp.get("submission_id") + " " + resp.get("approver_url"));Register a webhook at create time, or poll. The webhook fires once, when the decision seals;
template is a Go text/template, so the payload shape is yours. Delivery retries three times
and the attempt count lands in case_status.
# 1. push — register a webhook at create time; it fires once, when sealed
resp = client.create_case(
..., # as above
callback={"url": "https://erp.example/hooks/sovereign-sealed",
"token": "one-time-secret", # Bearer + X-Callback-Token
"content_type": "application/json", # default
"template": '{"id":"{{.ledger_uuid}}","outcome":"{{.outcome}}"}'})
resp["callback"] # {"registered": True, "url": "..."}
# 2. pull — poll the case instead, or as a backstop for a missed webhook
s = client.case_status(resp["submission_id"])
s["status"] # "pending" | "sealed"
s["outcome"], s["ledger_uuid"], s["verify_url"] # once sealed
s.get("quorum") # {"required": 3, "collected": 1, ...} on a quorum case
s.get("callback") # {"attempts": 1, "delivered_at": "...", "last_error": ""}// 1. push — register a webhook at create time; it fires once, when sealed
resp, _ := client.CreateCase(saa.CaseInput{
/* ... as above ... */
Callback: &saa.Callback{
URL: "https://erp.example/hooks/sovereign-sealed",
Token: "one-time-secret", // Bearer + X-Callback-Token
ContentType: "application/json", // default
Template: `{"id":"{{.ledger_uuid}}","outcome":"{{.outcome}}"}`,
},
})
// resp["callback"] → {"registered": true, "url": "..."}
// 2. pull — poll the case instead, or as a backstop for a missed webhook
s, _ := client.CaseStatus(resp["submission_id"].(string))
// s["status"] "pending" | "sealed"
// s["outcome"], s["ledger_uuid"], s["verify_url"] once sealed
// s["quorum"] {"required": 3, "collected": 1, ...} on a quorum case
// s["callback"] {"attempts": 1, "delivered_at": "...", "last_error": ""}// 1. push — register a webhook at create time; it fires once, when sealed
const resp = await client.createCase({
..., // as above
callback: { url: "https://erp.example/hooks/sovereign-sealed",
token: "one-time-secret", // Bearer + X-Callback-Token
content_type: "application/json", // default
template: '{"id":"{{.ledger_uuid}}","outcome":"{{.outcome}}"}' },
});
resp.callback; // { registered: true, url: "..." }
// 2. pull — poll the case instead, or as a backstop for a missed webhook
const s = await client.caseStatus(resp.submission_id as string);
s.status; // "pending" | "sealed"
s.outcome; s.ledger_uuid; s.verify_url; // once sealed
s.quorum; // { required: 3, collected: 1, ... } on a quorum case
s.callback; // { attempts: 1, delivered_at: "...", last_error: "" }// 1. push — register a webhook at create time; it fires once, when sealed
in.callback = Map.of(
"url", "https://erp.example/hooks/sovereign-sealed",
"token", "one-time-secret", // Bearer + X-Callback-Token
"content_type", "application/json", // default
"template", "{\"id\":\"{{.ledger_uuid}}\",\"outcome\":\"{{.outcome}}\"}");
Map<String,Object> resp = client.createCase(in);
resp.get("callback"); // {"registered": true, "url": "..."}
// 2. pull — poll the case instead, or as a backstop for a missed webhook
Map<String,Object> s = client.caseStatus((String) resp.get("submission_id"));
s.get("status"); // "pending" | "sealed"
s.get("outcome"); s.get("ledger_uuid"); s.get("verify_url"); // once sealed
s.get("quorum"); // {"required": 3, "collected": 1, ...} on a quorum case
s.get("callback"); // {"attempts": 1, "delivered_at": "...", "last_error": ""}Template context: submission_id,
ledger_uuid, outcome, sealed_at, topic,
party, domain, subcategory, tenant,
verify_url, plus the sealed record and its claim.
Records carry the signer's role, never their name — and a ratify quorum has no
claim.human, so guard it: {{with .claim.human}}{{.role}}{{end}}.
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");Verify a sealed record.ssp.json with no server. The record alone covers the claim hash,
the WebAuthn signatures, quorum satisfaction and the trust wrap; add anchors.json and
timestamp.tsr for the Rekor and RFC 3161 checks.
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",
record_bytes=open("record.ssp.json", "rb").read(), # exact bytes, for the Rekor binding
anchors=json.load(open("anchors.json")), # optional: Rekor
tsa_token=open("timestamp.tsr", "rb").read(), # optional: RFC 3161
tsa_ca_pem=open("tsa-ca.pem").read()) # optional: chain the TSA signer
print(result) # per-check PASS / FAIL / SKIP + overall
assert result.ok
result.skipped # what was not coveredimport (
"fmt"
"os"
saa "git.xor.ma/sovereign/sovereign-sdk/saa-go-sdk"
)
record, _ := os.ReadFile("record.ssp.json")
trust, _ := os.ReadFile("trust-public.pem") // out-of-band
anchorsBytes, _ := os.ReadFile("anchors.json")
tsr, _ := os.ReadFile("timestamp.tsr")
tsaCA, _ := os.ReadFile("tsa-ca.pem")
var anchors map[string]any
json.Unmarshal(anchorsBytes, &anchors)
res, _ := saa.VerifyRecordFull(record, trust, "app.sovereign.xor.ma",
anchors, "", tsr, tsaCA) // anchors/token/CA optional — omit to skip
fmt.Println(res) // per-check PASS / FAIL / SKIP
// res.OK() == true ; res.Skipped() lists what was not coveredimport { readFileSync } from "node:fs";
import { verifyRecord } from "@sovereign/saa-sdk";
const record = readFileSync("record.ssp.json");
const result = verifyRecord(record, readFileSync("trust-public.pem", "utf8"), {
rpId: "app.sovereign.xor.ma",
recordBytes: record, // for the Rekor binding
anchors: JSON.parse(readFileSync("anchors.json", "utf8")),
tsaToken: readFileSync("timestamp.tsr"),
tsaCaPem: readFileSync("tsa-ca.pem", "utf8"),
});
console.log(result.toString()); // per-check PASS / FAIL / SKIP
// result.ok === true ; result.skipped lists what was not coveredimport ma.xor.sovereign.saa.*;
import java.nio.file.Files;
import java.nio.file.Paths;
byte[] record = Files.readAllBytes(Paths.get("record.ssp.json"));
byte[] trust = Files.readAllBytes(Paths.get("trust-public.pem")); // out-of-band
byte[] tsr = Files.readAllBytes(Paths.get("timestamp.tsr"));
byte[] tsaCA = Files.readAllBytes(Paths.get("tsa-ca.pem"));
Map<String,Object> anchors = (Map<String,Object>) Json.parse(
new String(Files.readAllBytes(Paths.get("anchors.json")), "UTF-8"));
VerifyResult res = Verify.verifyRecord(record, trust, "app.sovereign.xor.ma",
anchors, null, tsr, tsaCA); // anchors/token/CA optional — omit to skip
System.out.println(res); // per-check PASS / FAIL / SKIP
// res.ok() == true ; res.skipped() lists what was not coveredCanonicalization is RFC 8785-style JCS, byte-identical across the four SDKs — a record signed via
one verifies in any other. verify_record dispatches on the record's signatures,
so single-approver, ratify-quorum and independent-vote records are all handled without you picking a
shape.
| Check | Python | Go | TypeScript | Java |
|---|---|---|---|---|
| Canonical claim hash | ✓ | ✓ | ✓ | ✓ |
| Ed25519 trust signature | ✓ | ✓ | ✓ | ✓ |
| Ratify quorum — slot satisfaction against the anchored policy, distinct signers, every slot filled | ✓ | ✓ | ✓ | ✓ |
Independent resolution — per-vote claim-hash integrity, recomputed tally / veto and votes_root | ✓ | ✓ | ✓ | ✓ |
| WebAuthn user signature (COSE/CBOR) | ✓ | ✓ | ✓ | ✓ |
| Rekor anchor — SET, inclusion proof, entry binding | ✓ | ✓ | ✓ | ✓ |
| RFC 3161 timestamp — imprint, CMS signature, TSA chain | ✓ | ✓ | ✓ | ✓ |
SKIP, not PASS — it does not fail the record, and
does not count as verified.Dependencies used for verification:
| SDK | Depends on |
|---|---|
| Go | standard library — crypto/*, encoding/asn1 |
| TypeScript | Node built-ins — node:crypto |
| Java | BouncyCastle bcprov (also its Ed25519) |
| Python | cryptography; asn1crypto for RFC 3161
(rfc3161 extra) |
COSE/CBOR is decoded in-SDK everywhere; DER in-SDK in TypeScript only. A malformed token or key fails its check rather than raising.