Source delegus-spec-v0.2.md · sha256 aafffde5066d29018417320222a7d544c567381e195b18e1ab6c9709df442a1f
Status: Frozen. Errata applied — 1: revocation-read / replay-write ordering (§5.3, §5.4, §7) and acceptance test (§15); 2: historical verification clock (§5.4, §6.4). This is the sole normative implementation source for v0.2. It consolidates the v0.2 draft, the Freeze Resolutions, the Threat Model, and the Final Freeze Recommendations. Earlier drafts are decision history only; engineers and conformance tests MUST NOT depend on them. Changes after this point require v0.3 or an explicit numbered erratum.
Acceptance criterion: An engineer at Company B who has never seen Company A's agent can integrate Delegus in an afternoon and safely accept or reject its action with one API call.
Normative language: MUST / SHOULD / MAY per RFC 2119.
Licence (non-normative): This specification text is licensed under the Creative Commons Attribution 4.0 International licence (CC BY 4.0, https://creativecommons.org/licenses/by/4.0/). Attribution: "Delegus Specification v0.2, © Delegus (delegus.ai)". The reference engine (@delegus/core, including the conformance vectors) and the SDK (@delegus/sdk) are licensed separately under Apache-2.0. This statement is metadata: it changes no normative text, no profile document and no trust-configuration descriptor.
0. Constitution#
OPEN (published, versioned, reproducible by anyone): Grant semantics · Proof format · protocol verification rules (§5.2) · Receipt format · Outcome Attestation format · portable identity model.
DELEGUS (the service): issuer trust · live verification (replay, revocation freshness) · evidence preservation · decision signing · adapter network · outcome graph · risk models · economic assurance.
Design constraints:
- An ordinary backend engineer can implement the protocol layer from this document with a standard JOSE library.
- Given the same inputs and the same evidence snapshot, the protocol result is identical everywhere. The Delegus decision is deterministic given inputs + snapshot + trust configuration.
- Fail closed. Anything unrecognized, missing, ambiguous, or unavailable is DENY.
- No per-transaction state except replay protection and the receipt/outcome ledger.
- Someone must be able to verify a Delegus artifact without calling Delegus.
1. Terminology#
| Term | Meaning |
|---|---|
| Principal | The organization on whose behalf an agent acts. Identified by a DID. Verified by domain control in v0. |
| Agent | Software holding an Ed25519 private key. Identified by did:key. |
| Relying Party (RP) | The party receiving an action and calling /verify. Registered; identified by a DID. |
| Grant | A Principal's signed statement of what an Agent may do, for whom, until when. |
| Proof | A short-lived Agent signature binding its key to one request, one RP, one Grant. |
| Action | The RP's description of what the Agent attempts, in the constraint vocabulary. |
| Decision | ALLOW or DENY with a machine-readable reason. |
| Receipt | The Decision plus its evidence snapshot, signed by Delegus. |
| Outcome Attestation | An append-only event reported after a decision. |
2. Canonical domain and identifiers#
2.0 Delegus domain as cryptographic infrastructure#
The canonical domain is delegus.ai. No persistent production artifact (Grant, Receipt, status-list credential, managed identity) may be issued until Delegus controls this domain. Frozen identifiers:
did:web:delegus.ai Delegus root DID (receipt and status-list signing keys)
did:web:delegus.ai:org:<slug> managed Principal identities
did:web:delegus.ai:rp:<slug> managed Relying Party identities
https://delegus.ai/ns/grant/v1 Grant JSON-LD context
https://status.delegus.ai/... published status-list credentials
did:web:delegus.ai#receipt-<n> receipt signing key identifiers- Development and test environments MUST use explicitly non-production identifiers (e.g.
did:web:dev.) that are visually and cryptographically distinct from production. Test signing keys MUST never appear in the production DID document.delegus. ai - Production credentials MUST NOT be issued against a temporary domain with intent to migrate. Changing the canonical domain after launch is a protocol migration, not a branding change.
https:/is production infrastructure with the same availability and TLS discipline as/ delegus. ai/ . well- known/ did. json /verify. Receipt keys are rotated with overlapping validity and are never removed, only retired.
2.1 Principal — managed by default, open by design#
- DID method in v0:
did:web. Two sources, equal in the protocol:- Managed:
did:web:delegus., created byai:org:<slug> delegus..org. create() - Customer-hosted:
did:web:acme., accepted at onboarding.com
- Managed:
- DID documents are resolved at onboarding and refreshed asynchronously at most every 5 minutes. The engine evaluates against the cached snapshot; the receipt records that snapshot's hash.
- Migration from managed to customer-hosted is a new DID; existing Grants remain valid to
validUntil. - Key type Ed25519, verification method
Multikey, max 5 keys. Key statesactive | retired | compromisedare Delegus trust data, not DID-document data. - Domain verification: DNS TXT at
_ordelegus. <domain> https:/containing a Delegus-issued token. Re-verified every 24 h. Failure sets the Principal/ <domain>/ . well- known/ delegus. json unverified. - Reserved for adapters (not v0): Entra, Google, SPIFFE, enterprise PKI, other VC issuers. Adapters MUST materialize their verification state before it reaches the hot path.
2.2 Agent#
did:key, Ed25519 (did:key:z6Mk...). The key is the identifier; no resolution.- Generated locally by the SDK.
POST /agentsregisters the public key for inventory and the agent-level kill switch (POST /revokes every Grant naming that Agent). Registration is not consulted byagents/ {did}/ disable /verify; the Grant's signature is the binding. - SDK signer interface MUST be pluggable: in-memory (dev default), KMS, HSM/TPM.
2.3 Relying Party#
- Registered at onboarding;
did:web:delegus.or customer-hostedai:rp:<slug> did:web. - Authenticates to
/verifywith an API key that resolves to the RP DID — the audience for Proofs and the reporter identity for Outcome Attestations.
3. The Grant#
3.0 Semantic model and serialization profiles#
The Grant is defined by the fields and semantics in §3.2–3.3, independent of encoding. A serialization profile maps those fields to bytes and MUST NOT add or remove semantics.
| Profile | Status |
|---|---|
| VC 2.0 + compact JWS (VC-JOSE-COSE) | v0 — the only profile the engine accepts |
| VC 2.0 + SD-JWT | reserved |
EUDI dc+sd-jwt | reserved (eIDAS "powers and mandates to represent legal persons") |
| COSE | reserved |
3.1 JOSE header (v0 profile)#
| # | Field | Value | Rule |
|---|---|---|---|
| 1 | alg | EdDSA | MUST. No other algorithm accepted. |
| 2 | typ | vc+jwt | MUST. |
| 3 | kid | Principal key DID URL, e.g. did:web:acme.com#key-1 | MUST. |
3.2 Fields#
| # | Field | Type | Rule |
|---|---|---|---|
| 4 | @context | array | MUST be exactly ["https://www.w3.org/ns/credentials/v2", "https://delegus.ai/ns/grant/v1"]. |
| 5 | type | array | MUST be exactly ["VerifiableCredential", "DelegusGrant"]. |
| 6 | id | string | urn:delegus:grant:<ULID>. |
| 7 | issuer | string | Principal DID. MUST equal the DID portion of kid. |
| 8 | validFrom | RFC 3339 UTC | MUST. |
| 9 | validUntil | RFC 3339 UTC | MUST. validUntil − validFrom ≤ 90 days. |
| 10 | credentialSubject.id | string | Agent DID. |
| 11 | credentialSubject.principal | string | Root Principal DID. v0: MUST equal issuer. Reserved for chains. |
| 12 | credentialSubject.authority | array of Capability | 1–16 entries. |
| 13 | credentialSubject.audience | array of RP DIDs | OPTIONAL. Absent = any registered RP. |
| 14 | credentialStatus | BitstringStatusListEntry | MUST. statusPurpose: "revocation", statusListIndex, statusListCredential. |
| 15 | parentGrant | string | RESERVED. MUST be absent in v0 → DENY UNSUPPORTED_FEATURE. Future: child authority ⊆ parent authority. |
| 16–20 | iss, sub, jti, nbf, exp | registered claims | MUST equal issuer, credentialSubject.id, id, validFrom, validUntil. |
Encoded size ≤ 8 KB.
3.3 Capability and constraint vocabulary (v0)#
{ "action": "commerce:purchase", "constraints": { "maxAmount": 50000, "currency": "USD" }, "resources": ["*"] }action—<namespace>:<verb>. v0 defines exactly two:commerce:purchase—maxAmount(integer, minor units),currency(ISO 4217, single), OPTIONALmerchantCategory(array of strings).api:call—methods(array of uppercase HTTP methods),resources(URI globs;*and**only).
constraints— keys MUST all be defined for that action type in this spec version. Unknown key → DENYUNKNOWN_.CONSTRAINT resources— OPTIONAL glob array matched againstaction.resource. Absent =["*"].- Authorization: at least one Capability matches; a Capability matches iff
actionis equal and every constraint is satisfied. Per-action limits only; no cumulative limits. All amounts are integers in minor units; no floats anywhere in the protocol.
3.4 Example payload#
{
"@context": ["https://www.w3.org/ns/credentials/v2", "https://delegus.ai/ns/grant/v1"],
"type": ["VerifiableCredential", "DelegusGrant"],
"id": "urn:delegus:grant:01J9X4QK7M3Y2Z8N6P5R4T3V2W",
"issuer": "did:web:acme.com",
"validFrom": "2026-09-09T00:00:00Z",
"validUntil": "2026-10-01T00:00:00Z",
"credentialSubject": {
"id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"principal": "did:web:acme.com",
"authority": [
{ "action": "commerce:purchase", "constraints": { "maxAmount": 50000, "currency": "USD" } }
]
},
"credentialStatus": {
"id": "https://status.delegus.ai/acme.com/1#4127",
"type": "BitstringStatusListEntry",
"statusPurpose": "revocation",
"statusListIndex": "4127",
"statusListCredential": "https://status.delegus.ai/acme.com/1"
},
"iss": "did:web:acme.com",
"sub": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
"jti": "urn:delegus:grant:01J9X4QK7M3Y2Z8N6P5R4T3V2W",
"nbf": 1788912000,
"exp": 1790812800
}4. The Proof#
A compact JWS signed by the Agent key, modeled on DPoP (RFC 9449), binding the key to one request, one RP, one Grant.
4.1 Header#
| Field | Value |
|---|---|
alg | EdDSA (MUST) |
typ | delegus-proof+jwt (MUST) |
kid | Agent DID URL, e.g. did:key:z6Mk...#z6Mk... (MUST) |
4.2 Claims#
| Field | Rule |
|---|---|
iss | Agent DID. MUST equal Grant sub. |
aud | RP DID. MUST equal the DID of the RP calling /verify. |
grant | MUST equal Grant id. |
jti | Security nonce. MUST contain ≥ 128 cryptographically random bits from a CSPRNG. SDKs SHOULD generate 16 random bytes encoded as base64url without padding. MUST be 22–64 ASCII characters. Not a sortable identifier. |
iat | Seconds since epoch. MUST satisfy |now − iat| ≤ window (default 60 s, RP-configurable, max 300 s). |
htm | HTTP method, uppercase ASCII. |
htu | Target URI per §4.3. |
action_hash | sha256:<64 lowercase hex> of the UTF-8 bytes of JCS(action) (RFC 8785). |
4.3 Canonicalization#
htu: per RFC 9449 §4.3 — lowercase scheme://lowercase host [:port only if non-default] path. No query, no fragment. Percent-encoding normalized per RFC 3986 §6.2.2 (uppercase hex in escapes; unreserved characters decoded). Path otherwise exact: no trailing-slash normalization. RPs behind proxies MUST configurepublicBaseUrlin the SDK sohtureflects the URL the Agent targeted.action: RFC 8785 JCS over the exact JSON the RP submits to/verify. All numbers MUST be integers. Strings are hashed as given; no Unicode normalization. Both SDKs constructactionfrom the same wire fields (type,resource,amount,currencyforcommerce:purchase;type,method,resourceforapi:call).- Proof signing input is the standard JWS signing input.
4.4 Transport and binding profiles#
The Agent sends Delegus- and Delegus- headers to the RP. The RP forwards both plus its own Action object to /verify, constructing the Action from what it actually received. Only the http binding profile exists in v0; any other → PROOF_MALFORMED.
5. POST /verify#
5.1 Request#
{ "grant": "<JWS>", "proof": "<JWS>", "action": { "type": "commerce:purchase", "resource": "order_92813", "amount": 42700, "currency": "USD" } }Authentication: RP API key. action.type is required; other fields per action type; unknown fields → ACTION_.
5.2 Protocol layer — delegus-base-v1 (OPEN)#
Reproducible by anyone given: Grant, Proof, Action, RP DID, a clock, the issuer's DID document snapshot, and the status-list credential snapshot. Each check yields true | false | "skipped".
| # | Check | DENY reason |
|---|---|---|
| P1 | Grant parses and conforms to §3 (incl. parentGrant absent) | GRANT_MALFORMED / UNSUPPORTED_FEATURE |
| P2 | kid names a verification method present in the DID document snapshot | ISSUER_KEY_NOT_FOUND |
| P3 | Grant signature verifies | GRANT_SIGNATURE_INVALID |
| P4 | now ≥ validFrom | GRANT_NOT_YET_VALID |
| P5 | now < validUntil | GRANT_EXPIRED |
| P6 | Status credential signed by Delegus; bit at statusListIndex is 0 | AUTHORITY_REVOKED |
| P7 | audience, if present, contains RP | AUDIENCE_MISMATCH |
| P8 | Proof parses and conforms to §4 (incl. jti format) | PROOF_MALFORMED |
| P9 | Proof signature verifies against Agent key | PROOF_SIGNATURE_INVALID |
| P10 | Proof iss == Grant sub | PROOF_AGENT_MISMATCH |
| P11 | Proof grant == Grant id | PROOF_GRANT_MISMATCH |
| P12 | Proof aud == RP DID | PROOF_AUDIENCE_MISMATCH |
| P13 | iat within window | PROOF_EXPIRED |
| P14 | action_hash == hash of submitted Action | PROOF_ACTION_MISMATCH |
| P15 | Action valid for its type | ACTION_MALFORMED |
| P16 | Some Capability matches action.type | ACTION_NOT_AUTHORIZED |
| P17 | All constraint keys known | UNKNOWN_CONSTRAINT |
| P18 | Currency authorized | CURRENCY_NOT_AUTHORIZED |
| P19 | amount ≤ maxAmount | AMOUNT_EXCEEDS_AUTHORITY |
| P20 | resource matches a pattern | RESOURCE_NOT_AUTHORIZED |
The normative text of this table, published at a stable URL, is the profile document; its SHA-256 is protocol. in every receipt.
5.3 Trust and service layer (DELEGUS) — delegus-trust-v1 #
| # | Check | DENY reason |
|---|---|---|
| T1 | issuer is a registered Principal | ISSUER_UNKNOWN |
| T2 | Principal is domain-verified | ISSUER_UNVERIFIED |
| T3 | kid not marked compromised | ISSUER_KEY_COMPROMISED |
| T5 | Replay: SET replay:{rp_did}:{jti} 1 NX PX {2 × window_ms} returns OK. Executed only after P1–P14 pass, as a separate internal operation from the revocation-version read that precedes P6 (§7). Store unavailable → SERVICE_UNAVAILABLE. | PROOF_REPLAYED / SERVICE_UNAVAILABLE |
delegus- is exactly T1, T2, T3, T5. Not configurable per RP. ISSUER_ is a reserved reason code. (T4 and T6 do not exist; identifiers are not reused.)
5.4 Canonical evaluation order and reason selection#
P1 → T1 → T2 → T3 → P2 → P3 → P4 → P5
→ [read status_ver; refresh cached status credential if needed]
→ P6 → P7 → P8 → … → P14
→ [atomic replay SET NX]
→ T5 → P15 → … → P20The bracketed steps are internal operations, not checks; they are shown to fix where the revocation-state read and the replay write occur relative to the checks.
Decision is DENY if any check is false; reason is the first false in this order. protocol.result is PASS iff all P-checks are true; trust.result is PASS iff all T-checks are true. Both are reported even on DENY; checks whose prerequisites failed are "skipped". now is Delegus server time, recorded in the receipt as evaluated_at; historical re-verification uses that value as now (§6.4).
5.5 Hot-path constraint#
/verify MUST perform no external DID resolution, status-list retrieval, issuer lookup, DNS lookup, or third-party network request. Internal low-latency infrastructure (Redis, Delegus-owned storage) MAY be accessed. Latency objective: p50 ≤ 30 ms, p99 ≤ 200 ms.
5.6 Response#
The response body is the Receipt (§6) as JSON, including the receipt JWS.
6. The Decision Receipt#
6.1 Purpose#
A Receipt lets any party later prove: at time T, Agent A acting for Principal B presented Grant C to RP D for Action E; Delegus evaluated it against this identity state, this revocation state, these rules, and this trust configuration, and decided X. It remains provable after keys rotate, DID documents change, and status lists are replaced.
6.2 Schema#
{
"receipt_version": "delegus-receipt-v1",
"receipt_id": "drc_01K4Q7ZP9X2M3N4R5S6T7V8W9Y",
"decision": "ALLOW",
"reason": null,
"evaluated_at": "2026-09-09T14:03:27.412Z",
"relying_party": "did:web:delegus.ai:rp:seller",
"principal": { "id": "did:web:acme.com", "verification": "domain" },
"agent": "did:key:z6Mk...",
"grant_id": "urn:delegus:grant:01J9X4...",
"grant_hash": "sha256:...",
"proof_hash": "sha256:...",
"request_hash": "sha256:...",
"authority": { "action": "commerce:purchase", "constraints": { "maxAmount": 50000, "currency": "USD" } },
"expires_at": "2026-10-01T00:00:00Z",
"evidence": {
"issuer_resolution": {
"did": "did:web:acme.com",
"did_document_hash": "sha256:...",
"resolved_at": "2026-09-09T14:00:11Z"
},
"status": {
"credential_url": "https://status.delegus.ai/acme.com/1",
"credential_hash": "sha256:...",
"valid_from": "2026-09-09T14:03:00Z",
"checked_at": "2026-09-09T14:03:27.410Z",
"status_list_index": "4127"
},
"trust_config_version": "trust_0042",
"trust_config_hash": "sha256:..."
},
"protocol": {
"result": "PASS",
"profile": "delegus-base-v1",
"profile_hash": "sha256:...",
"checks": { "P1": true, "P2": true, "P20": true }
},
"trust": {
"result": "PASS",
"policy_version": "delegus-trust-v1",
"checks": { "T1": true, "T2": true, "T3": true, "T5": true }
},
"receipt": "<compact JWS>"
}6.3 Hash encoding#
One encoding everywhere: sha256:<64 lowercase hex chars>. Inputs are the exact bytes Delegus saw:
| Field | Bytes hashed |
|---|---|
grant_hash | ASCII of the compact JWS string as received |
proof_hash | ASCII of the compact JWS string as received |
request_hash (= Proof action_hash when P14 passes) | UTF-8 of JCS(action) |
evidence.issuer_resolution.did_document_hash | raw response body bytes as fetched |
evidence.status.credential_hash | ASCII of the status-list credential compact JWS |
evidence.trust_config_hash | UTF-8 bytes of the immutable trust-configuration descriptor named by trust_config_version |
protocol.profile_hash | raw UTF-8 bytes of the published profile document |
6.4 Signature and rules#
receiptis a non-detached compact JWS:alg: EdDSA,typ: delegus-,receipt+jwt kid: did:web:delegus., payload =ai#receipt- <n> JCS(receipt object minus "receipt"). The JWS string is the canonical receipt; the JSON body is a decoded convenience and MUST equal the payload after JCS.- On DENY where P3 did not pass,
principal,agent,grant_id,authority,expires_atare omitted. Delegus never echoes an unauthenticated attribution. - Offline verification: given the receipt, the Delegus receipt public key, and the artifacts named by
did_,document_ hash credential_hash,profile_hash, andtrust_(retrievable by hash — §8), a third party can re-runconfig_ hash delegus-base-v1and confirmprotocol.result. Thetrustblock is Delegus's attestation and is not reproducible by design. - Historical verification clock (normative): when reproducing the protocol result represented by a Receipt, the verifier MUST use the Receipt's authenticated
evaluated_atvalue asnowfor every time-dependent protocol check (P4, P5, P13). It MUST NOT use its own wall-clock time. Becauseevaluated_atis inside the signed payload, the historical evaluation time cannot be altered without invalidating the Delegus signature.
live /verify: now = Delegus server clock (recorded as evaluated_at)
offline receipt verification: now = signed receipt.evaluated_at7. POST /revoke#
{ "grant_id": "urn:delegus:grant:01J9X4..." }Authenticated as the issuing Principal.
Consistency guarantee (normative): POST /revoke MUST NOT return success until the revocation is durably recorded and every subsequent successful /verify anywhere is guaranteed to evaluate against revocation state at least as new as that write. Once a successful revoke response has been returned, a later verification MUST NOT return ALLOW for that Grant.
POST /revokeMUST be idempotent. Revoking an already-revoked Grant returns success with the existing revoked state.- Revocation is permanent; there is no un-revoke.
- Delegus MUST fail closed (
SERVICE_) if it cannot establish current revocation state.UNAVAILABLE - Internal coordination MAY use any implementation that preserves the guarantee. Reference design: one Bitstring Status List per Principal (131,072 bits);
/revokesets the bit, re-signs the Principal's status-list credential, and incrementsstatus_in the internal revocation store; each engine node cachesver:{principal} {principal → (version, signed credential)}. - Ordering within
/verify(normative): before P6, the engine obtains the currentstatus_from the internal revocation store and ensures its cached signed status credential is at least that version. After P1–P14 pass, T5 atomically consumes the Proofver:{principal} jti. These are two distinct internal operations; in the worst case/verifyperforms two internal round trips. They MAY be optimized or coalesced (e.g. readingstatus_verat request start, which still observes every revocation that completed before the request began) provided the canonical evaluation order (§5.4), the rule that T5 executes only after P1–P14, and the global read-after-write revocation guarantee are all preserved. Implementations MUST NOT reorder P6 after T5 or execute T5 before P14 to save a round trip. - Published credentials at
statusListCredentialURLs serve the latest signed list withCache-, for third-party verifiers only.Control: max- age=30
8. Evidence preservation (DELEGUS obligation)#
Delegus MUST retain, addressable by hash and retrievable by any party holding a receipt that references them: every DID document snapshot, status-list credential snapshot, profile document, and trust-configuration descriptor referenced by a receipt; the receipt itself; and its outcome ledger. Retention: 7 years from evaluated_at (open question 8). GET / returns the exact bytes.
9. Outcome Attestations#
9.1 Principle#
Outcomes are events, not states. Nothing is overwritten. Events record what the reporter observed, never a judgment about the agent; judgments arrive only as dispute_.
9.2 Endpoint#
POST / — authenticated as the RP named in the receipt. Append-only.
{ "type": "dispute_opened", "occurred_at": "2026-09-14T09:12:00Z", "evidence_ref": "https://seller.example/disputes/8812", "note": "buyer claims goods not ordered" }Delegus adds attestation_id (oat_), reporter (RP DID), recorded_at. Corrections are new events with correction_. No edits, no deletions. Ordering is by recorded_at.
9.3 v0 vocabulary#
fulfilled · cancelled · dispute_opened · dispute_ (resolution: agent_) · chargeback_ · chargeback_ · loss_reported (loss_amount minor units and currency required). Unknown type → 400. loss_amount is per event; Delegus never sums or nets.
9.4 Reporters and signatures#
v0 reporter: the RP named in the receipt only. reporter_ (JWS over JCS of the event by the RP key) is OPTIONAL in v0 and REQUIRED from the day any assurance product reads the ledger. Reserved reporter roles (Principal, adjudicator) are not accepted in v0.
10. Data provenance and reuse rights#
Every decision and outcome record carries tenant (RP), reporter, principal, and a reuse_rights tag set at contract time. v0 defaults: used for the tenant's own audit and retrieval; for service operation (replay, abuse prevention); and for Delegus-internal, aggregate, non-identifying risk analysis. No cross-tenant disclosure of any Principal's or Agent's history and no per-entity scoring exposed to any party. Any broader reuse requires an explicit rights change recorded against the tenant.
11. Key lifecycle#
- Rotate: add key N+1
active, mark Nretired. Grants signed by N remain valid tovalidUntil; new Grants MUST use anactivekey. - Compromise:
POST /marks the key; every Grant it signed DENIES (T3) immediately.keys/ {id}/ compromise - Agent compromise: revoke its Grants or use the kill switch; a new key is a new Agent.
12. Out of scope for v0#
Delegation chains and attenuation (field reserved) · cumulative or per-period limits · RP-side policy · non-HTTP binding profiles · non-JWS Grant profiles · external identity adapters · agent key rotation in place · un-revoke · any bureau, score, or cross-tenant history product · any guarantee, warranty, or economic assurance.
13. Threat Model (normative scope of protection)#
13.1 v0 explicitly protects against#
| ID | Threat | Delivered by |
|---|---|---|
| TM-01 | Forged Grant | P2, P3 |
| TM-02 | Modified Grant | P3 |
| TM-03 | Stolen Grant used without the Agent key | P9, P10 |
| TM-04 | Proof replay at the same RP | P13, T5 |
| TM-05 | Proof forwarded to or replayed at a different RP | P12 |
| TM-06 | Action tampering between Agent and Delegus | P14 |
| TM-07 | Expired or not-yet-valid authority | P4, P5 |
| TM-08 | Revoked authority | P6 + §7 guarantee |
| TM-09 | Issuer key compromise, after Delegus is notified | T3 |
| TM-10 | Stale identity evidence going unrecorded | Receipt pins did_document_hash, resolved_at; refresh ≤ 5 min |
| TM-11 | Unknown or unverified issuer | T1, T2 |
| TM-12 | Status-list substitution or forgery | P6 (Delegus-signed); receipt pins credential_hash |
| TM-13 | Grant used at an RP the Principal excluded | P7 |
| TM-14 | Evidence altered after the fact | Content-addressed store referenced by hash from signed receipts |
13.2 v0 explicitly does not protect against#
| ID | Threat | Note |
|---|---|---|
| NP-01 | A malicious Principal intentionally authorizing malicious activity | Delegus attests authority, not intent |
| NP-02 | A compromised active Principal key before Delegus learns of it | Bounded by Grant lifetime and key count |
| NP-03 | A compromised Agent private key | Bounded by Grant scope and expiry; KMS/HSM signer recommended |
| NP-04 | Business fraud unrelated to authority | Transaction assurance, not verification assurance (§14) |
| NP-05 | Semantic ambiguity in poorly defined actions | Mitigated only by the closed constraint vocabulary; RPs own the mapping |
| NP-06 | False or mistaken Outcome Attestations | Reporter-tagged, append-only; not adjudicated in v0 |
| NP-07 | A domain-verified Principal impersonating another organization via a lookalike domain | Domain control ≠ organizational identity; roadmap: KYB |
| NP-08 | An RP misreporting the action it received | The receipt records the RP's own submission |
| NP-09 | Compromise of Delegus's own signing keys | Trust root for the trust block; HSM custody; open-protocol reproducibility |
| NP-10 | Compromise of DNS or the web PKI | Shared with TLS; out of scope |
13.3 Relationship to future assurance#
Any verification guarantee MUST be expressed as an explicit subset of §13.1. Nothing in §13.2 is ever covered by verification assurance; NP-04 is the boundary with transaction assurance, a different product.
14. Non-normative: assurance progression#
SIGNED — every decision carries a Delegus signature (v0)
RECORDED — every decision has a pinned evidence snapshot + outcome ledger (v0)
GUARANTEED — verification assurance over a stated subset of §13.1 (earned)
RISK-SCORED — counterparty / action risk from decision + outcome data (earned)
GUARANTEED — economic assurance for covered conditions, direct or with a regulated partner (earned)15. Implementation contract#
Build order:
packages/core— pure Grant/Proof parsing, canonicalization, P-checks, receipt assembly and signing. No I/O; snapshots and clock injected.- Conformance suite — ALLOW plus one deterministic vector for every DENY reason, including
htunormalization, JCS,jtiformat, replay, revocation, and malformed inputs. The engine cannot merge until every vector passes. apps/api— Principal registry, domain verification, status lists, replay protection, evidence preservation, receipt persistence, Outcome Attestations.packages/sdk+ CLI —delegus.,org. create() agent.create(),grant.create(),agent.sign(),delegus., outcome reporting.verify() apps/demo— buyer/seller: authorized 42700 →ALLOW; revoke Grant; same action with a fresh Proof →AUTHORITY_. (A fresh Proof isolates revocation from replay; the replay path is covered separately by the conformance suite.)REVOKED
The afternoon integration (relying party, complete):
import { Delegus } from "@delegus/sdk";
const delegus = new Delegus({ apiKey: process.env.DELEGUS_API_KEY, publicBaseUrl: "https://seller.example" });
app.post("/orders", async (req, res) => {
const d = await delegus.verify({
grant: req.header("Delegus-Grant"),
proof: req.header("Delegus-Proof"),
action: { type: "commerce:purchase", resource: req.body.orderId, amount: req.body.amountMinor, currency: req.body.currency },
});
if (d.decision !== "ALLOW") return res.status(403).json({ reason: d.reason });
// proceed; persist d.receipt with the order
});16. Remaining open questions (non-blocking)#
- Minor units vs. decimals — spec: minor units.
- Proof window 60 s / 300 s.
- Replay scope per-RP.
- Grant max lifetime 90 days.
- Grant by reference as an alternative to by value.
loss_amountper event (spec) — confirm with first assurance design.- When reporter signatures become REQUIRED.
- Evidence retention period and storage model.
decisionvs.receiptas the primary API noun.
v0.2 is frozen.