Proof of Authorisation¶
The Xybern Authorisation Layer does not just authorise every agent action before it executes, it can prove it did, to anyone, without asking them to trust Xybern.
Authorise every action. Prove every authorisation.
Three artifacts make up the proof surface:
| Artifact | What it is | Who it's for |
|---|---|---|
| Authorisation Receipt | A per-action record proving one specific action was authorised, refused, escalated, or terminated before execution, with the rules and Risk Verdict that decided it and the Vault certificate that makes it independently verifiable | Engineers, counterparties, anyone disputing a single action |
| Proof of Authorisation Pack | A signed, self-contained bundle covering a period: every outcome, per-agent breakdown, receipts, the covered Vault entries with their signatures, and an ECDSA signature over the whole pack | Whoever asks your customer to prove their agents are controlled: an auditor, a regulator, an insurer, a procurement team |
| Authorisation Coverage | A live view of which agents operate under a declared authority (Agent Boundary, Intent Contract, agent-scoped Mandate) and which still hold standing, unscoped authority | You, to close the gaps |
Everything is built on the Provenance Vault: every decision is already hash-chained and asymmetrically signed the moment it happens, so proofs are generated from records that were sealed before anyone asked for them, never reconstructed after the fact.
Authorisation Receipts¶
Every enforcement decision can be exported as a standalone receipt.
from xybern import Xybern
client = Xybern(api_key="xb_live_...")
receipt = client.enforce.get_receipt("enf_ab12cd34ef56")
curl -H "X-API-Key: xb_live_..." \
https://www.xybern.com/api/v1/enforce/decisions/enf_ab12cd34ef56/receipt
Response shape:
{
"ok": true,
"receipt": {
"format": "xybern-authorisation-receipt/v1",
"receipt_id": "rcpt_enf_ab12cd34ef56",
"workspace_id": "ws_...",
"issued_at": "2026-07-08T10:00:00Z",
"action": {
"action_type": "send_payment",
"summary": "Refund of $180 to customer #4411",
"content_sha256": "9f2c...",
"occurred_at": "2026-07-08T09:58:41Z",
"agent": {"agent_id": "agt_...", "name": "Refund Agent"}
},
"authorisation": {
"outcome": "authorised after review",
"decision": "escalate",
"rules_triggered": [{"name": "Refunds above $100 need review"}],
"verdict": {"dimensions": {"blast_radius": {"score": 41}}},
"escalation": {
"status": "approved",
"resolved_by": "usr_...",
"resolved_at": "2026-07-08T09:59:30Z"
}
},
"proof": {
"format": "xybern-decision-certificate/v1",
"signing_keys": ["..."],
"entries": ["..."]
},
"proof_available": true
}
}
The proof block is a complete Vault certificate: the sealed entry, its chain position, the ECDSA P-256 signature, the public keys, and the exact hashing recipe. A recipient can verify it offline; altering any field breaks the verification.
Outcomes are reported in plain authorisation vocabulary: authorised, refused, escalated, authorised after review, refused after review, terminated.
The agent's own reason for each step¶
Receipts distinguish two kinds of reasoning: the authorisation reasoning (why the Authorisation Layer decided what it decided) and the agent's stated reason (why the agent says it took the step). Pass the latter at intercept time:
client.enforce.intercept(
action_type="send_payment",
action_content="Refund $180 to customer #4411",
metadata={"connector": "stripe", "amount_usd": 180},
reasoning="Customer reported a duplicate charge on order #4411; refund approved per returns policy.",
)
The raw API accepts the same as a top-level agent_reasoning field (or metadata.agent_reasoning). MCP tool calls that carry a reasoning, justification, or reason argument are captured automatically by the gateway. The stated reason is stored with the decision, sealed into the Vault entry, and rendered on every receipt and in the PDF appendix of every pack, so a reviewer sees, per step, what the agent tried to do, with which tools and parameters, why the agent says it did it, and why it was authorised or refused.
In the dashboard, every decision panel has a Download Authorisation Receipt button next to its Vault proof.
On whose authority: Charter version and lineage¶
Every receipt now answers two further questions, computed at the moment of decision and never reconstructed later.
Which Charter was in force. authorisation.charter carries the Charter version hash, who signed that version, when, and how many mandates it contained, plus the compiled rule set hash. The version hash is a plain sha256 over the canonical mandate list, so anyone holding the receipt can fetch the version (GET /v1/enforce/charter/versions/<hash>) and recompute the hash without any secret. A later change to the Charter creates a new version and never rewrites what earlier decisions were taken under.
On whose authority the action rested. authorisation.lineage is the chain from the accountable human to the action: the human who signed the Charter version, the mandates that spoke on this decision, the agent's access profile, the intent contract, the temporal window or break-glass event if any, the full delegation chain from the root grant to the leaf grant with the principal it acted for, the agent's credential, the session, and the action. authorisation.lineage_text is the same in one sentence, in the language of the dashboard.
"charter": {
"hash": "3f9a1c...e2",
"signed_by": "cfo@bank.example",
"signed_at": "2026-09-11T08:12:04Z",
"mandate_count": 7,
"policy_set_hash": "b71d...09"
},
"lineage": {
"format": "xybern-lineage-v1",
"accountable": {"kind": "human", "id": "cfo@bank.example", "via": "charter", "charter_hash": "3f9a1c...e2"},
"mandates": [{"mandate_id": "mnd_2c1e...", "outcome_text": "No payment above SAR 500,000 without a named approver", "status": "active"}],
"access_profile": {"profile_id": "apf_...", "name": "Treasury operations", "status": "active"},
"delegation": {"grant_id": "dlg_9b...", "source_agent_id": "agt_treasury", "root_agent_id": "agt_treasury",
"chain": [{"grant_id": "dlg_9b...", "source_agent_id": "agt_treasury", "target_agent_id": "agt_payments", "depth": 1}],
"principal_authority": {"agent_id": "agt_treasury", "intersection": "applied", "status": "active"}},
"agent": {"agent_id": "agt_payments", "name": "Payments Agent", "identity_verified": true},
"session": {"session_id": "ses_..."},
"action": {"decision_id": "enf_...", "action_type": "send_payment", "decision": "allow"}
},
"lineage_text": "This action rests on the authority of cfo@bank.example, who signed Charter version 3f9a1c... (7 mandates), the mandate that spoke: “No payment above SAR 500,000 without a named approver”, within the access profile “Treasury operations”, delegated from agt_treasury via grant dlg_9b..., evaluated against both authorities, acting agent Payments Agent with a verified credential, in session ses_...."
When no Charter is in force the lineage says so (accountable.kind is none) rather than inventing an authority. Both blocks are also sealed inside the Vault entry of the decision and shown in the decision dialog under On whose authority.
Charter versions are listed at GET /v1/enforce/charter/versions and in the dashboard's Charter view; the SDK exposes list_charter_versions() and get_charter_version(hash).
Proof of Authorisation Packs¶
A pack answers the question "prove your agents only did what they were authorised to do between these dates" in one signed document.
pack = client.enforce.create_proof_pack(
from_date="2026-06-01T00:00:00",
to_date="2026-06-30T23:59:59",
name="June, all agents",
)
packs = client.enforce.list_proof_packs()
full = client.enforce.get_proof_pack(pack["pack"]["pack_id"], verify=True)
curl -X POST -H "X-API-Key: xb_live_..." -H "Content-Type: application/json" \
-d '{"from_date": "2026-06-01T00:00:00", "to_date": "2026-06-30T23:59:59"}' \
https://www.xybern.com/api/v1/enforce/proof-packs
Each pack contains:
- a plain-language statement of what the pack attests to
- the summary: total actions, how many were authorised, refused, escalated, terminated, and how escalations were resolved by humans
- a per-agent breakdown
- the coverage snapshot at generation time
- one receipt per action (up to 2,000 per pack; narrower periods give complete coverage)
- the covered Vault entries with their asymmetric signatures and the public keys
- an
integrityblock: the SHA-256 of the canonical pack body and an ECDSA P-256 signature over it
Pack generation is itself sealed to the Vault, so the record that a proof was produced is as tamper-evident as the proofs inside it.
Share links¶
Every pack gets a read-only share link, https://www.xybern.com/proof/<pack_id>?t=<token>, that requires no Xybern account. The page re-verifies the pack signature server-side on every view, shows the summary, and offers the signed JSON and a PDF. Revoke or rotate the link at any time from the dashboard or the share endpoint; a revoked link stops working immediately.
Verifying a pack independently¶
- Download the signed JSON.
- Remove the
integrityblock and recomputesha256over the canonical JSON (sort_keys=True, separators(",", ":")). - Check the ECDSA P-256 signature in
integrity.signatureagainst the public keys enclosed invault_proof.signing_keys. - Optionally verify each enclosed Vault entry with the same keys and the enclosed hashing recipe.
No Xybern access, account, or goodwill required at any step.
Proof of Constraint Observance and Authority Time Travel¶
A proof pack can also be generated over a constraint (a rule, a mandate, a mission, an invariant or an action family) and a window: the channels the layer controlled, the actions the constraint spoke to, the violations observed and a coverage statement, signed and verifiable like any pack. Every decision also answers Authority at decision time, the authority as sealed when it was made and what changed since. Both are described in Passports, Time Travel, Constraint Observance, retention.
Authorisation Coverage¶
{
"coverage": {
"headline": {
"agents_total": 12,
"agents_covered": 9,
"coverage_pct": 75,
"standing_authority_agents": 2,
"workspace_charter_active": true,
"actions_recent": 4180
},
"agents": [
{
"agent_id": "agt_...",
"name": "Refund Agent",
"access_profile": "active",
"intent_contract": true,
"covered": true,
"standing_authority": false,
"actions_recent": 812
}
]
}
}
An agent is covered when it operates under at least one declared authority: an active or shadow Agent Boundary, an active Intent Contract, or an agent-scoped Mandate. An agent with recent activity and none of these holds standing authority, the thing the Authorisation Layer exists to eliminate. The dashboard's Proof of Authorisation tab shows the same view with one-click paths to close each gap.
Endpoint reference¶
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/v1/enforce/decisions/<id>/receipt |
Authorisation Receipt for one decision |
POST |
/v1/enforce/proof-packs |
Generate and sign a pack (from_date, to_date, agent_id, name, all optional) |
GET |
/v1/enforce/proof-packs |
List packs |
GET |
/v1/enforce/proof-packs/<pack_id> |
Full signed pack document, ?verify=1 re-verifies server-side |
GET |
/v1/enforce/coverage |
Authorisation coverage, ?window_days=30 |
GET |
/proof/<pack_id>?t=<token> |
Public read-only verification page (no auth) |
SDK: get_receipt, create_proof_pack, list_proof_packs, get_proof_pack, get_authorisation_coverage, available from xybern 1.20.0; intercept(reasoning=...) from 1.21.0.