DENY if any check is false, and reason is the first false check in this order (spec §5.4). Protocol checks (P) are reproducible by anyone from the receipt's artifacts; trust checks (T) are Delegus's own attestation.Checks in evaluation order#
| # | Check | Passes when | DENY reason | Needs |
|---|---|---|---|---|
| 1 | P1 Protocol | Grant parses and conforms to §3 (incl. parentGrant absent) | GRANT_MALFORMED / UNSUPPORTED_FEATURE | — |
| 2 | T1 Trust | issuer is a registered Principal | ISSUER_UNKNOWN | — |
| 3 | T2 Trust | Principal is domain-verified | ISSUER_UNVERIFIED | T1 |
| 4 | T3 Trust | kid not marked compromised | ISSUER_KEY_COMPROMISED | T1 |
| 5 | P2 Protocol | kid names a verification method present in the DID document snapshot | ISSUER_KEY_NOT_FOUND | T1 |
| 6 | P3 Protocol | Grant signature verifies | GRANT_SIGNATURE_INVALID | P2 |
| 7 | P4 Protocol | now ≥ validFrom | GRANT_NOT_YET_VALID | P3 |
| 8 | P5 Protocol | now < validUntil | GRANT_EXPIRED | P3 |
| 9 | P6 Protocol | Status credential signed by Delegus; bit at statusListIndex is 0 | AUTHORITY_REVOKED | P3 |
| 10 | P7 Protocol | audience, if present, contains RP | AUDIENCE_MISMATCH | P3 |
| 11 | P8 Protocol | Proof parses and conforms to §4 (incl. jti format) | PROOF_MALFORMED | P3 |
| 12 | P9 Protocol | Proof signature verifies against Agent key | PROOF_SIGNATURE_INVALID | P8 |
| 13 | P10 Protocol | Proof iss == Grant sub | PROOF_AGENT_MISMATCH | P8 |
| 14 | P11 Protocol | Proof grant == Grant id | PROOF_GRANT_MISMATCH | P8 |
| 15 | P12 Protocol | Proof aud == RP DID | PROOF_AUDIENCE_MISMATCH | P8 |
| 16 | P13 Protocol | iat within window | PROOF_EXPIRED | P8 |
| 17 | P14 Protocol | action_hash == hash of submitted Action | PROOF_ACTION_MISMATCH | P8 |
| 18 | T5 Trust | 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 | P1–P14 |
| 19 | P15 Protocol | Action valid for its type | ACTION_MALFORMED | P14 |
| 20 | P16 Protocol | Some Capability matches action.type | ACTION_NOT_AUTHORIZED | P15 |
| 21 | P17 Protocol | All constraint keys known | UNKNOWN_CONSTRAINT | P16 |
| 22 | P18 Protocol | Currency authorized | CURRENCY_NOT_AUTHORIZED | P17 |
| 23 | P19 Protocol | amount ≤ maxAmount | AMOUNT_EXCEEDS_AUTHORITY | P18 |
| 24 | P20 Protocol | resource matches a pattern | RESOURCE_NOT_AUTHORIZED | P19 |
A check whose prerequisite (the Needs column) did not pass is reported as "skipped", so a receipt shows which checks passed before the first failure and which never ran. Between P5 and P6 the service reads the current revocation state, and between P14 and T5 it performs the atomic replay write; those are internal operations, not checks (§5.4). T4 and T6 do not exist; identifiers are never reused (§5.3).
Codes outside the check tables#
| Code | When |
|---|---|
UNSUPPORTED_FEATURE | Returned by P1 for a Grant that uses a reserved field, such as parentGrant, which must be absent in v0 (§3.2). |
ISSUER_NOT_TRUSTED | Reserved. Defined in §5.3, not produced by any v0.2 check. |
SERVICE_UNAVAILABLE | Delegus failed closed because it could not establish a fact it depends on: at T1 (registry unreachable, or a registered Principal whose DID document has not been resolved yet, so there is no identity snapshot to evaluate against), at P6 (current revocation state could not be established, §7) or at T5 (replay store). The affected check is reported false with this reason, and the result is a signed DENY receipt delivered with HTTP 200, not an HTTP error. On /verify, HTTP 503 is used only for failures before evaluation starts (the API key lookup) or after it ends (receipt persistence). /revoke also answers 503 until the revocation is durably recorded and visible to every verify node (§7); a caller retries rather than assuming success. |
What the broad codes cover#
Three codes cover more than their one-line check suggests. These are the engine's readings of the specification, recorded with the engine's source.
GRANT_MALFORMED(P1): besides syntax, an unknown header parameter, claim or member; an unknown Capability action; a lifetime over 90 days;nbf/expnot equal tovalidFrom/validUntil; a Grant over 8 KB.UNSUPPORTED_is onlyFEATURE parentGrantbeing present.PROOF_MALFORMED(P8): besides syntax, anhtuthat is not already in §4.3 canonical form (a query or fragment, an uppercase scheme or host, a default port, lowercase percent-escapes, or percent-encoded unreserved characters); ajtioutside 22–64 base64url characters; an unknown claim or header parameter; akidthat is notiss#<multibase>.UNKNOWN_(P17): evaluated over the Capabilities whose action equalsCONSTRAINT action.type. It includesmerchantCategory, which v0 cannot evaluate because the action has no wire field for it.- For
api:call, a method mismatch has no code of its own: methods are applied together with resources at P20, so it isRESOURCE_. P18 and P19 are vacuously true forNOT_ AUTHORIZED api:call.
Not a Delegus reason#
PROOF_ is not a reason code and never appears in a receipt. It is the SDK's ProofBindingError, raised on the relying party's side before /verify is called, when the Proof's htm/htu do not match the request the service actually received. Handle it as a client-side rejection; there is no receipt for it.
Reading a DENY#
A DENY is a signed receipt like any ALLOW (§6). It carries both protocol.result and trust.result, and the per-check outcomes, so you can see which checks passed before the first failure and which were skipped after it. When the DENY happens before P3 passes, the receipt omits principal, agent, grant_id, authority and expires_at (§6.4), because nothing about the Grant has been verified yet. Codes that begin with GRANT_ or ISSUER_ are about the company and its Grant; PROOF_ codes are about the agent's request; ACTION_, AMOUNT_, CURRENCY_, RESOURCE_ and UNKNOWN_ are about whether this action fits the granted authority.