DelegusDocsv0.2

Docs/DENY reason codes

Reference

DENY reason codes

Every reason a verify call can return, in the order the checks run. The table is generated from the delegus-base-v1 profile and spec §5.3; the check text is quoted, not paraphrased.

Rule. The decision is 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#

#CheckPasses whenDENY reasonNeeds
1P1 ProtocolGrant parses and conforms to §3 (incl. parentGrant absent)GRANT_MALFORMED / UNSUPPORTED_FEATURE
2T1 Trustissuer is a registered PrincipalISSUER_UNKNOWN
3T2 TrustPrincipal is domain-verifiedISSUER_UNVERIFIEDT1
4T3 Trustkid not marked compromisedISSUER_KEY_COMPROMISEDT1
5P2 Protocolkid names a verification method present in the DID document snapshotISSUER_KEY_NOT_FOUNDT1
6P3 ProtocolGrant signature verifiesGRANT_SIGNATURE_INVALIDP2
7P4 Protocolnow ≥ validFromGRANT_NOT_YET_VALIDP3
8P5 Protocolnow < validUntilGRANT_EXPIREDP3
9P6 ProtocolStatus credential signed by Delegus; bit at statusListIndex is 0AUTHORITY_REVOKEDP3
10P7 Protocolaudience, if present, contains RPAUDIENCE_MISMATCHP3
11P8 ProtocolProof parses and conforms to §4 (incl. jti format)PROOF_MALFORMEDP3
12P9 ProtocolProof signature verifies against Agent keyPROOF_SIGNATURE_INVALIDP8
13P10 ProtocolProof iss == Grant subPROOF_AGENT_MISMATCHP8
14P11 ProtocolProof grant == Grant idPROOF_GRANT_MISMATCHP8
15P12 ProtocolProof aud == RP DIDPROOF_AUDIENCE_MISMATCHP8
16P13 Protocoliat within windowPROOF_EXPIREDP8
17P14 Protocolaction_hash == hash of submitted ActionPROOF_ACTION_MISMATCHP8
18T5 TrustReplay: 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_UNAVAILABLEP1–P14
19P15 ProtocolAction valid for its typeACTION_MALFORMEDP14
20P16 ProtocolSome Capability matches action.typeACTION_NOT_AUTHORIZEDP15
21P17 ProtocolAll constraint keys knownUNKNOWN_CONSTRAINTP16
22P18 ProtocolCurrency authorizedCURRENCY_NOT_AUTHORIZEDP17
23P19 Protocolamount ≤ maxAmountAMOUNT_EXCEEDS_AUTHORITYP18
24P20 Protocolresource matches a patternRESOURCE_NOT_AUTHORIZEDP19

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#

CodeWhen
UNSUPPORTED_FEATUREReturned by P1 for a Grant that uses a reserved field, such as parentGrant, which must be absent in v0 (§3.2).
ISSUER_NOT_TRUSTEDReserved. Defined in §5.3, not produced by any v0.2 check.
SERVICE_UNAVAILABLEDelegus 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.

Not a Delegus reason#

PROOF_BINDING_MISMATCH 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_CONSTRAINT are about whether this action fits the granted authority.