Advanced and compatibility reference
Read this only for a task explicitly routed here by the main skill. This preserves existing lower-level capabilities; it is not the default new-application path. Check the installed declarations and the deployment’s operation support before using an older route. Never fall back from refused request-bound authorization to JWT/admin authorization. Retry advice here applies only when the operation is known safe to repeat; it never authorizes blindly repeating transaction submissions, native approval POSTs or signing requests with an uncertain outcome.
DCQL rules
Section titled “DCQL rules”A slot’s access policy is a DCQL query (the OpenID Foundation’s Digital Credentials Query Language, as used by OpenID4VP). The rule is the wallet request: what the policy asks for is exactly what a wallet is asked to present. The SDK’s evaluator mirrors the network’s reference implementation and has local tests. Cross-language conformance requires separate evidence; do not infer it from local tests.
Shape of a rule
Section titled “Shape of a rule”{ "credentials": [{ "id": "e", "format": "jwt_vc_json", "meta": {"type_values": [["EmployeeOf"]]}, "claims": [ {"path": ["iss"], "values": ["did:web:hr.acme.example"]}, {"path": ["credentialSubject", "dept"], "values": ["Engineering"]} ] }]}Rules that the validator enforces:
- Every credential query must carry an issuer constraint: a
claimsentry with path exactly["iss"]. Withvaluesit pins the accepted issuers; withoutvaluesit explicitly accepts any verifiable issuer. A rule without it throwsDcqlMalformedErrorwith a message that names the credential id and says to add the entry. formatis one of three values; any other is rejected as malformed.jwt_vc_jsonmatchesmeta.type_values([["EmployeeOf"]]),dc+sd-jwtmatchesmeta.vct_values(["EmployeeOf"]), andoauth+access-token+dpopauthorizes with an access token from the slot owner’s own identity provider (see below). Claim paths follow the format: ajwt_vc_jsoncredential nests its subject claims (["credentialSubject", "dept"], as above), while an SD-JWT VC carries them at the top level (["dept"]). Copying the nested path into adc+sd-jwtrule produces a rule nothing can satisfy, and it fails at the verifier, not atvalidateDcql.- A claim entry with
valuesmatches on JSON equality ("3"does not match3).valuesis a set of accepted values, so one rule can admit several distinct holders —{"path":["sub"],"values":["did:demo:alice","did:demo:bob"]}lets either of them satisfy the query on their own. That is how several people share one slot (one treasury EOA, one archive) without sharing key material and without either needing the other’s consent; it is not a quorum — for “two must approve”, see the dual-control policy intasra-sign-and-decrypt. - Several credentials are combined with
credential_sets; absent, every listed credential is required. - Whole rule ≤
DCQL_MAX_RULE_LEN(4096 bytes, measured on the raw text before parsing; the constant is exported fromtasra-sdk). - Identity-scoped rules add
kk_identity_scope_claimandkk_scope_namespace— see the next section; a slot that serves IBE extraction needs them.
OAuth rules — bring your own identity provider
Section titled “OAuth rules — bring your own identity provider”A rule may authorize with an access token your users already hold from your own IdP, sender-constrained with DPoP (RFC 9449), instead of a verifiable credential. It is ordinary DCQL over the token’s own claims — the format is the only thing that changes:
{"credentials": [{ "id": "t", "format": "oauth+access-token+dpop", "meta": {"max_age_secs": 300}, "claims": [ {"path": ["iss"], "values": ["https://idp.example.com/realms/acme"]}, {"path": ["aud", null], "values": ["https://agent.example/authz/43113"]}, {"path": ["realm_access", "roles", null], "values": ["tasra-user"]} ]}]}Four extra constraints are MANDATORY here, and each throws DcqlMalformedError.
An access token is a bearer-shaped artifact from a tenant’s own IdP with none of a
credential’s trust scaffolding — no issuer registry, no status list, and an audience the
IdP chooses — so the rule is the only place any of it can be pinned:
- The issuer set must be pinned. The
["iss"]entry needsvalues; the “explicitly open” form that is fine for credentials would mean “any IdP on the internet willing to mint a token naming our audience”. Each value must behttps://and carry no query or fragment — the issuer is concatenated with/.well-known/openid-configurationand compared byte-for-byte at discovery. - The audience must be constrained — a claims entry with path exactly
["aud", null]and non-emptyvalues. Without it, a token minted for another relying party of the same IdP authorizes here: the classic confused deputy. meta.max_age_secsis required, within1..=86400. There is no status list for an access token, so freshness is the revocation signal: a leaver’s session stops minting tokens, and this bounds how long an already-minted one keeps working.type_values/vct_valuesare forbidden. An access token has no credential type, and accepting them would let a rule look constrained while constraining nothing.
Derive the audience rather than typing it: platformAudience(origin, chainId) from
tasra-sdk/committee, which also has the DPoP htu derivation. isOauthFormat(format)
tells you whether a format is in this family.
Authorizing against such a slot is a different surface, not openSession: it runs
through the verifier agent as an oauth session kind — see
tasra-oid4vp-wallet-and-verifier-agent.
Identity-scoped rules
Section titled “Identity-scoped rules”A plain rule answers who may operate. An identity-scoped rule also answers which
identity they may operate on, and it is what IBE extraction requires: against a plain
rule every keeper refuses an extraction with HTTP 403 rule carries no identity-scope binding on any satisfied credential query; it cannot authorize an identity-scoped operation (tasra-ibe-identity-scoped).
{ "credentials": [{ "id": "officer", "format": "dc+sd-jwt", "meta": {"vct_values": ["EmployeeOf"]}, "claims": [ {"path": ["iss"], "values": ["did:web:hr.acme.example"]}, {"path": ["dept"], "values": ["Procurement"]}, {"path": ["scope"]} ], "kk_identity_scope_claim": ["scope"], "kk_scope_namespace": "issuer" }]}kk_identity_scope_claimis the path of the claim carrying this holder’s grant. It must also appear underclaims— as{"path": ["scope"]}does above, with novalues, so any grant matches — or the rule is malformed: a query must not be satisfiable without the grant being present to check.kk_scope_namespaceis required alongside it and is"issuer"or"any". Under"issuer"the requested identity’s first/-segment must byte-equal the credential’s verifiediss, so identities root at whoever granted them —did:web:hr.acme.example/tender/acme-rfp-014/2026-Q4for the rule above."any"lifts that gate and therefore requires a pinned issuer set (valueson the["iss"]entry), or anyone could self-issue a credential granting scope over anyone.- The grant grammar is three forms and nothing else (
scopeCovers, exported):gcovers exactlyg;p/*coverspand anything strictly beneathp/; bare*covers everything. The/boundary is load-bearing —patient:12/*does not coverpatient:123. No mid-path wildcards, no regex. A grant or identity overMAX_IDENTITY_LEN(1024 bytes, exported) or containing NUL matches nothing.
import {evaluateIdentityScoped, scopeCovers, MAX_IDENTITY_LEN} from 'tasra-sdk'
scopeCovers('did:web:hr.acme.example/tender/*', 'did:web:hr.acme.example/tender/acme-rfp-014/2026-Q4') // truescopeCovers('did:web:hr.acme.example/tender/acme-rfp-099/*', 'did:web:hr.acme.example/tender/acme-rfp-014') // falseevaluateIdentityScoped(rule, views, identity) // the whole check: rule + credentials + this identityBecause the rule is committed at slot creation, an IBE slot must be created with the
scope binding already in it (tasra-create-slot).
Using the evaluator
Section titled “Using the evaluator”Every function takes the rule as JSON text (a string), never as an object.
import {validateDcql, evaluateDcql, selectDcql, canonicalizeDcql, jsonCredential, DcqlMalformedError} from 'tasra-sdk'import type {CredentialView, DcqlQuery, DcqlSelection} from 'tasra-sdk' // DcqlQuery types the object BEFORE you JSON.stringify it
const rule = JSON.stringify({credentials: [/* as above */]}) // or the raw JSON string you store
try { validateDcql(rule) } catch (e) { if (e instanceof DcqlMalformedError) showError(e.message) }
// A CredentialView is built from a decoded credential: `types` is matched against// meta.type_values, and claim paths such as ["iss"] or ["credentialSubject","dept"]// resolve against `body`. `decoded` here is the parsed JWT-VC payloads:// Array<{iss: string, vc: {type: string[], credentialSubject: Record<string, unknown>}}>const views: CredentialView[] = decoded.map(p => jsonCredential({format: 'jwt_vc_json', types: p.vc.type, body: {iss: p.iss, ...p.vc}}),)const ok = evaluateDcql(rule, views) // boolean: do these credentials satisfy the rule?const pick = selectDcql(rule, views) // {satisfied, credentials, unsatisfied}: `credentials` are the // same view objects you passed in (the minimal satisfying set), // `unsatisfied` the credential-query ids nothing satisfied; // with no credentials, `satisfied: false` and every query id // in `unsatisfied`const canon = canonicalizeDcql(rule) // string: RFC 8785 form — what is committed on-chainevaluateIdentityScoped(rule, views, identity) is the identity-scoped variant
(see tasra-ibe-identity-scoped). RecipientStore / canAccess gate a
recipient’s own store against a rule client-side.
The rule commitment
Section titled “The rule commitment”The chain never stores the rule, only ruleCommitment(salt, rule) from
tasra-sdk/chain, a salted hash over the canonical bytes. createSlot
mints the salt and returns it; verifyRuleCommitment(rule, ruleSalt, onChainRuleCommitment)
checks a candidate rule against a slot. The clear rule is provisioned to the
keepers separately (see tasra-create-slot). This includes OAuth DCQL: adding
BYOIDP does not change its canonicalization or replace the policy with a hash.
Use rule in creation/provisioning SDK arguments. dcqlRule is rejected,
even alongside an identical rule. The current keeper transport fields remain
dcql_rule and dcql_salt; the SDK does not expose them as argument aliases.
In TasraClient.slots.create, supply this JSON string as policy and select
authType: 'oauth' for OAuth rules; the default is 'oid4vp' for credential rules.
Creation validates that every query belongs to the selected authorization family.
Common mistakes
Section titled “Common mistakes”- Passing the rule as an object. Every function wants the JSON string.
- Omitting the
["iss"]entry. The rule is rejected as malformed. - Writing
["issuer"]or a nested issuer path. Only exactly["iss"]counts. - Expecting
"3"to match3, or whitespace/key order to change the commitment. Values compare as JSON; canonicalisation removes formatting. - Listing two entries under
credentialswithoutcredential_sets: that requires both in one presentation, and wallets present one credential at a time. Declarecredential_setswith single-credential options instead. - Evaluating an identity-scoped rule with plain
evaluateDcql. It refuses; useevaluateIdentityScoped. - Naming a claim in
kk_identity_scope_claimthat is not also inclaims.DcqlMalformedError: the grant has to be part of the match. - Expecting a scope grant to be a prefix match.
pdoes not coverp/x; writep/*. Andp/*never reaches a longer sibling segment. - Writing an identity that does not start with the granting issuer’s DID under
kk_scope_namespace: "issuer". The committee authorizes nothing. - Writing an
oauth+access-token+dpopquery the way you would a credential one — leaving["iss"]open, omitting["aud", null], forgettingmeta.max_age_secs, or carryingtype_values. All four are malformed for this format specifically; the relaxations that are safe for a credential are not safe for a bearer token.
Where to read more
Section titled “Where to read more”node_modules/tasra-sdk/dist/auth/oid4vp.d.tsforQuery,CredentialView,Selection,ValidateOptions— re-exported fromtasra-sdkasDcqlQuery,CredentialView,DcqlSelection.typesmay be a superset of atype_valuesoption;bodyneeds only the keys the rule’s claim paths reference.
