Delegus

ReceiptsLaunch

Delegus v0.3: what's new

Authority between strangers, one step further

Team Delegus

  • 13 min read

Delegus v0.3 is live. New relying parties start on v0.3. v0.3 runs in production as release candidate 1; v0.2 stays the normative text until v0.3 is frozen after an external cryptographic review.

Updated 25 Sep: every receipt is now kept on AWS where no one, including us, can change it. See "Receipts kept where no one can change them" below. Below: what's new in plain words, the v0.2 → v0.3 comparison, and the technical notes for developers.

What's new, in plain words#

Delegus answers one question for a service that receives an action from an AI agent it has never met: did the organization behind that agent actually authorize this, under these limits, right now? It checks before the action and signs the answer. v0.3 keeps that and adds the following.

Receipts reveal no more than they need to#

In v0.2 the receipt carried a copy of the permission it matched. In v0.3 it carries a sealed commitment to it instead. The permission itself, and the key that opens the commitment, go only to the service that asked. Anyone holding just the receipt can still check it's authentic and unmodified; they learn that a permission matched, and nothing about what it says.

Receipts kept where no one can change them#

Updated 25 Sep. Technically: S3 Object Lock in COMPLIANCE mode, plus a locked backup vault.

Every receipt is signed, and it's kept on AWS where no one, including us, can change it.

  • Before you get an answer, the receipt is already locked away. Every receipt issued from 25 Sep is written to write-once storage before the answer comes back. If that write fails, the check fails: you get a "try again" error (503), no receipt is kept and no budget is spent. The agent retries with a fresh request.
  • The private details stay private. A v0.3 receipt's private details (the keys that open its sealed commitments) are locked away the same way, as private evidence, and never served publicly.
  • Everything a receipt relies on stays locked with it. Evidence can be shared by many receipts. A daily job extends the lock on any evidence a newer receipt relies on, so everything a receipt references stays locked for at least seven years after that receipt.
  • Earlier receipts too. Every receipt issued before 25 Sep was copied into the locked storage, together with everything it references.
  • And a locked backup. A separate backup of the production database is kept daily for seven years, in a vault that's locked the same way.

Budgets across orders#

v0.2 limits each order on its own. v0.3 adds budgets that add up across orders: a total and a limit per day, week or month (on purchases), and a cap on how many times a permission can be used (on any action). The budget is spent as the very last step, only once every other check has passed, so a refused request never spends. Two requests racing for the last of a budget can't both take it.

Both sides of a transaction can show their authority#

v0.2 covered the buyer's side: purchases and API calls. v0.3 adds the seller's side: quoting a price, accepting an order and committing to fulfil it. When two companies' agents deal with each other, each side can show what its own organization allowed it to do.

The chain of reliance#

In v0.2 every decision stood alone. In v0.3 a later action can declare up to eight earlier decisions it relies on, and the service can say which ones it requires. Each is declared one of two ways:

  • "that decision was made": the earlier decision was genuine, an allow, and part of the same transaction;
  • "that authority still holds": all of that, plus the permission behind it hasn't expired or been withdrawn since.

Delegus checks every declared dependency before the new action runs. Earlier decisions are named by a sealed fingerprint, not an address, so naming one doesn't reveal which decision it is or let anyone look it up.

Knowing the company is real, and in good standing#

  • A verified company domain, to go live. Production checks need a company domain you've verified. You start a claim with a verified email address on that domain; the first verified claim owns the domain, and it's re-checked every day.
  • Free email stays in the sandbox. Accounts on free email providers (Gmail, Outlook, iCloud and the like) can't claim a domain, so they stay in the sandbox. They can still ask us about a pilot or Enterprise.
  • A clear no when a company is suspended. If a company is suspended or disputed after a report that its agents were misused, services checking those agents get a signed no, with the reason "issuer not trusted". So the service has proof of why it refused.
  • The service is held to the same standard. A relying party that's suspended, or that runs on a domain its organization hasn't verified, is refused before any check runs.
  • The Terms now forbid spoofing someone else's identity and squatting on other people's domains (acceptable use, Terms v1.1).

Privacy, written into the protocol#

Three principles are now part of the specification itself:

Verifiability does not imply discoverability.

Evidence should reveal no more than is necessary to verify the claim being made.

Receipt authenticity is public. Receipt reasoning is entitled.

In practice: anyone holding a receipt can check it's genuine. Re-deriving why it was decided takes material only the parties to it can get.

What stays the same#

Fail closed: anything unknown, missing or unavailable is a no. One check, before the action. A signed receipt for every decision, allow or deny. Revocation that every later check sees. The same three steps: grant, verify, revoke.

Status#

v0.3 is a release candidate (rc1), not frozen. The construction that seals references to earlier decisions is going through an external cryptographic review; if it changes, a second release candidate follows. v0.2 receipts still verify. If you use our SDK, upgrade to @delegus/sdk 0.2.0: version 0.1.4 can't read v0.3 receipts and refuses them.

v0.2 → v0.3 at a glance#

In v0.2In v0.3Why it matters
What a receipt showsA copy of the matched permission, in the receiptA sealed commitment to it; the permission and its opening go only to the service that askedA receipt can be shown without handing over a company's permission
LimitsPer order onlyBudgets across orders: a total and per day, week or month (purchases), and a number of uses (any action). Spent last, so a refusal never spendsAn agent can't stay under the per-order limit while running up the total
Whose authorityBuyer side: purchases and API callsSeller side too: quote, accept, fulfilBoth parties to a deal can show what their own organization allowed
Decisions over timeEach decision stood aloneA later action can declare up to 8 earlier decisions it relies on, in one of two modes, checked before it actsThe next party can rely on an earlier decision, and check it, without having been there
The company behind the agent"Issuer not trusted" was reserved but never usedA company suspended or disputed after a misuse report gets a signed no, with that reasonThe service can show why it refused
Going liveNo domain proof was needed to go liveProduction needs a verified company domain; free-email accounts stay in the sandboxLive checks come from organizations that proved they control their domain
PrivacyNot stated in the protocolThree privacy principles written into the spec; receipts private by defaultCheckable without being discoverable
Receipt sizeEnforced by the service (422 RECEIPT_TOO_LARGE), not by the specA 4,096-byte limit written into the v0.3 spec, with the complete receipt determined before anything is written and the request's one-time proof not spentAn oversized receipt can't leave a half-recorded decision behind
Stays the sameFail closed; one check before the act; a signed receipt for every decision; revocation every later check sees; grant, verify, revokeSameThe foundation doesn't move

Pending: v0.3 is rc1. The dependency commitment awaits the external cryptographic review; if it changes, rc2 follows.

Technical notes for developers#

v0.3 runs in production as release candidate 1; v0.2 stays the normative text until v0.3 is frozen after an external cryptographic review.

Versions and profile#

  • Profile delegus-base-v3, trust policy delegus-trust-v3, receipt delegus-receipt-v2.
  • delegus-base-v1 (v0.2) stays published and frozen; an intermediate v2 profile was superseded by v3 and never published.
  • Evaluation order: P1 … P20 → P21 → D1 → (per entry) D2 … D7 → [consume] → T7. Revocation-state reads, including every CURRENT_AUTHORITY dependency's, happen in the bracketed step before P6.
  • 50 v0.3 conformance vectors, including suspended, disputed and reinstated issuers, in @delegus/conformance 0.3.0, run with --v3. The v0.2 set is unchanged. @delegus/conformance 0.3.0 is on npm.
  • The hosted conformance runner stays on v0.2.

Grants: vocabulary and budgets#

  • Five action types. From v0.2: commerce:purchase, api:call. New, seller-side: commerce:quote (maxAmount, currency), commerce:accept (maxAmount, currency), commerce:fulfil (no constraints; matched on resources).
  • New budget constraint: { total?, perPeriod?: { amount, period: "day" | "week" | "month" }, uses? }. total and perPeriod apply to commerce:purchase only; uses applies to any action type. Periods are anchored at validFrom, in UTC.
  • merchantCategory is not defined in v0.3; a Grant carrying it fails P17.
  • Under a v0.2 profile, the v0.3 action types make a Grant malformed (P1).

Request#

  • action.relies_on: 1 to 8 entries, each exactly { fingerprint: "sha256:<64 hex>", mode: "PRIOR_DECISION" | "CURRENT_AUTHORITY" }, no repeats, no default mode; signed through action_hash (P14), validated at P15 (ACTION_MALFORMED).
  • requires (relying party only): the same shape, 1 to 8 entries. Malformed → HTTP 400 REQUIREMENTS_MALFORMED before evaluation (no receipt, jti not consumed). Sent by a relying party not on delegus-base-v3 → HTTP 400 REQUIREMENTS_NOT_SUPPORTED.
  • An Action never carries a transaction handle.

Response (beside the receipt, outside its signed payload)#

  • authority and authority_opening: the matched Capability in plaintext, and the opening of the receipt's authority_commitment. Present exactly when the receipt pins that commitment (on ALLOW, and on a DENY decided after a Capability matched).
  • capability_key_opening: when evidence.consumption pins capability_key_commitment.
  • relies_on: per-dependency results, in Action order, { fingerprint, mode, checks: { D2 … D7 }, evidence_commitment? }. Check sha256(JCS(relies_on)) == relies_on_hash.
  • opening (ALLOW only): { fingerprint, nonce }, the commitment a later action can declare, returned only to this relying party. Pass on the fingerprint, never the nonce.
  • A response whose beside-fields don't match what the receipt pins must be treated as invalid.

Receipt v2#

  • New fields: authority_commitment (tag delegus:authority:v1, a nonce commitment over JCS(matched Capability), spec §R.6), transaction (a pairwise handle, ALLOW only, 22 characters of base64url), requires_hash, relies_on_hash, evidence.consumption with capability_key_commitment (tag delegus:capability-key:v1) when a budget applies, protocol.checks.P21, D1–D7 and trust.checks.T7.
  • The v0.2 receipt's plaintext authority is gone; it's committed by authority_commitment.
  • A managed principal's receipt says verification "managed", never "domain".
  • Public verification: signature, body equals payload, v2 shape and results agreement. Entitled reproduction: also opens the commitments and reproduces the dependency checks from the disclosure package (GET /decisions/{id}/disclosure), which goes only to the principal or the relying party of the receipt.
  • Size: the JWS signing input must not exceed 4,096 bytes; the complete receipt is determined before any write. Oversized → 422 RECEIPT_TOO_LARGE.

Receipt storage (updated 25 Sep)#

  • Written before the answer. Every receipt issued from 25 Sep has its compact JWS written to the evidence store (S3 Object Lock, COMPLIANCE mode, seven years) before the response is returned. For a v0.3 receipt, its private record (the openings) is written too. On the v0.3 path, both writes happen after signing and before the spend.
  • A failed write fails the check. If the store write fails, the answer is 503 SERVICE_UNAVAILABLE: no receipt row, nothing spent. The Proof's jti is used, so a retry needs a fresh Proof.
  • Private kinds aren't served. GET /evidence answers 404 for the receipt JWS, private records and consumption snapshots, exactly as for an unknown hash. Public evidence kinds are served as before.
  • Locks follow reliance. A daily maintenance job extends the retention (PutObjectRetention, only ever later) on any evidence a newer receipt depends on, so everything a receipt references stays locked for at least seven years after that receipt's evaluated_at.
  • Backfill. Every receipt issued before 25 Sep was copied into the locked store, with every object it references, with no failures.
  • Database backups. A separate backup vault keeps daily production database backups for seven years under a vault lock.

Trust: T1 under delegus-trust-v3#

  • T1 is "issuer is a registered Principal in good standing". Staff mark a Principal suspended or disputed on a misuse report, with a reason, audited, and lift it the same way; an issued receipt is never altered. A suspended or disputed Principal fails T1 with ISSUER_NOT_TRUSTED: a signed DENY, and the Proof's jti is consumed as for any DENY. An unregistered one stays ISSUER_UNKNOWN. ISSUER_NOT_TRUSTED stays reserved under delegus-trust-v1 (v0.2), which is untouched.
  • A hosted principal passes T2 by its own proof or by its organization's verified domain claim.

Refusals before evaluation (no receipt, the Proof not spent)#

  • 403 RP_SUSPENDED: the relying party isn't in good standing.
  • 403 RP_UNVERIFIED: a customer-hosted relying party whose organization doesn't hold the verified claim on its domain.
  • 403 CLAIM_REQUIRED: registering a hosted relying party or principal without a claim on its domain.
  • 409 PRODUCTION_REQUIRES_DOMAIN: turning production on for an organization with no verified domain.

Domain claims#

  • A claim starts only from a signed-in user with a verified email on the claimed domain (or a parent of it within its registrable domain), through the console, with the acceptable-use terms recorded. Organization API keys read and check claims but never start one.
  • Free email providers and public suffixes never claim. Registrable domains follow the Public Suffix List (tldts 7.4.15), recorded on each claim.
  • The first verified claim owns the domain; other pending claims on it close. Owned claims are re-verified every 24 hours; a failed check has a 72-hour grace, then lapses; 30 days lapsed closes it. A pending claim expires after 7 days.

New reason codes#

  • Budgets: AUTHORITY_EXHAUSTED (P21 no headroom; T7 lost the race). T7 store unavailable → SERVICE_UNAVAILABLE.
  • Dependencies: D1 DEPENDENCY_NOT_DECLARED · D2 DEPENDENCY_COMMITMENT_MISMATCH · D3 DEPENDENCY_RECEIPT_INVALID · D4 DEPENDENCY_NOT_ALLOW · D5 DEPENDENCY_TRANSACTION_MISMATCH · D6 DEPENDENCY_AUTHORITY_EXPIRED · D7 DEPENDENCY_ISSUER_NOT_TRUSTED / DEPENDENCY_ISSUER_KEY_COMPROMISED / DEPENDENCY_AUTHORITY_REVOKED. D7 (CURRENT_AUTHORITY only) checks that the dependency's Principal is in good standing, that the key that signed its Grant isn't marked compromised, and that the Grant isn't revoked. For PRIOR_DECISION, D6 and D7 are "skipped".
  • Issuer: ISSUER_NOT_TRUSTED (T1, delegus-trust-v3).
  • Requests: REQUIREMENTS_MALFORMED, REQUIREMENTS_NOT_SUPPORTED (HTTP 400, before evaluation).

SDK#

  • Version: @delegus/sdk 0.2.0, with @delegus/core 0.2.0, both on npm.
  • protocol: "auto" is the new default for verify() and delegus.mcp(). The API answers with the receipt of the relying party's own profile, so the same code works against a v0.2 relying party and one on delegus-base-v3. The receipt version is read from inside the signed payload. "v0.2" and "v0.3" pin one format.
  • v1 receipts are checked harder too: the signature must verify under the service's published keys, the body must equal the signed payload, and every receipt, v1 or v2, must be bound to the request sent (grant_hash and proof_hash equal the hashes of the Grant and Proof you sent). Otherwise ResponseIntegrityError, and delegus.mcp() refuses the call.
  • New calls: decisions.get() returns authority, authority_opening and capability_key_opening to a receipt's own parties; disclosure(receiptId) fetches the dependency disclosure package.
  • MCP: @delegus/mcp-gateway 0.2.0 (on npm) takes --protocol / DELEGUS_PROTOCOL (auto by default, or v0.2 / v0.3). Python: delegus-core 0.2.0 and delegus-mcp 0.2.0, on PyPI.
  • Checks in the v3 path (fail closed with ResponseIntegrityError): the receipt signature verifies under the service's published keys; the body equals the signed payload; decision, reason and results agree; authority_opening opens authority_commitment to authority, and capability_key_opening opens capability_key_commitment; relies_on_hash and requires_hash match; the opening opens its fingerprint; on ALLOW, every required field is present.
  • Builders: actionsV3 (purchase, quote, accept, fulfil, apiCall, withDependencies).
  • Receipts: verifyReceiptV2Public and verifyReceiptV2Entitled (also receipts.verifyV2Public / receipts.verifyV2Entitled).
  • Personal-data lint: lintAuthority(authority) flags email addresses, phone numbers and Luhn-valid card numbers in capability strings. It warns by default (warnings on the grant result) and refuses with PersonalDataError only when you pass refusePersonalData: true.
  • CLI: delegus receipt verify reads v2 receipts: public by default; entitled with --entitled plus --authority, --authority-opening, --capability-key-opening, --relies-on, --requires, --priors, and --did-document for offline keys. delegus verify --v3 [--requires]. v1 receipts take the v0.2 path as before.

Migration#

  • New relying parties start on v0.3.
  • Going live needs a verified company domain (409 PRODUCTION_REQUIRES_DOMAIN otherwise).
  • v0.2 receipts still verify. v1 receipts take the v0.2 path in the new SDK and CLI.
  • Upgrade the SDK. This is required. @delegus/sdk 0.1.4 treats a v2 receipt as malformed and fails closed with a TypeError ("Delegus returned a malformed receipt body"), so a 0.1.4 client can't complete a v0.3 check.
  • Grants using merchantCategory: remove it (it fails P17).

Not in v0.3#

  • Accepting other delegation formats (import or mapping of foreign authority formats).
  • A general disclosure capability, auditor roles and disclosure levels.
  • Requirements by kind ("a purchase receipt in this transaction") rather than exact fingerprint.
  • Dependencies on receipts from another issuer.
  • Money budgets on commerce:quote and commerce:accept (an open question).

Status#

v0.3-rc1: a release candidate, not frozen. Freezing needs an external cryptographic review of the dependency commitment (§R) and its byte framing, and every v0.3 conformance vector passing. If the review changes the construction, rc2 follows. Until then, v0.2 stays the normative text.