Skip to content

Verifiable Intent (commerce gating)

The SD-JWT credential chain for commerce-gated agent actions, Immediate vs Autonomous mandates, and constraints.

Verifiable Intent (VI) is Revka’s cryptographic authorization layer for commerce-gated agent actions — paying for something, checking out a cart, or any high-stakes transaction where you need provable, scoped consent rather than a soft approval prompt. Instead of trusting that the agent “should” only spend within bounds, VI binds a chain of signed SD-JWT credentials so a verifier can prove the final values were authorized by the user, within the limits the user set.

Use this page when you are integrating Revka into a payment or checkout flow and want the agent to act on a user’s behalf without handing it unbounded spending authority. VI is an internal subsystem that gates commerce tool calls; it is complementary to, not a replacement for, the Approval Manager, OTP gating, and the security model as a whole.

VI models authorization as a chain of signed credentials, each layer binding to the one above it by a hash. A verifier walks the chain top to bottom and rejects the transaction if any binding, signature, timestamp, or constraint check fails.

LayerWho signs itWhat it carries
L1Credential provider → userThe payment-instrument SD-JWT (pan_last_four, scheme, card binding via cnf). Issued externally — out of scope for Revka.
L2User → agent / verifierA KB-SD-JWT that delegates intent. Either Immediate (final values) or Autonomous (constraint-bounded).
L3aAgent → payment networkAgent-signed final payment values (payment_instrument, payment_amount, payee, transaction_id). Autonomous mode only.
L3bAgent → merchantAgent-signed final checkout values (checkout_jwt, checkout_hash, resolved line_items). Autonomous mode only.

Each binding is computed as B64U(SHA-256(ASCII(parent))):

  • L2’s sd_hash must equal the hash of the serialized L1.
  • L3b’s checkout_hash must equal the hash of its checkout_jwt.
  • L3a’s transaction_id must equal L3b’s checkout_hash (the cross-reference that ties the payment to the specific checkout).

All monetary amounts are integer minor-units (cents) per ISO 4217 — 27999 means $279.99. This eliminates floating-point and decimal ambiguity across the chain. Signing uses ECDSA P-256 (ES256); hashes are SHA-256, base64url-encoded without padding.

The L2 mandate mode determines how much latitude the agent has. It is the central decision when you design a VI flow.

The mode is inferred from the mandate vct (verifiable credential type):

vctMode
mandate.checkout, mandate.paymentImmediate
mandate.checkout.open, mandate.payment.openAutonomous

A mandate pair is always required — a checkout mandate and a payment mandate. An incomplete pair fails verification (IncompleteMandatePair). In Immediate mode the L2 must not carry a cnf agent-key binding; in Autonomous mode it must (a ModeMismatch otherwise).

Constraints live in the L2 Autonomous mandates and bound what the agent may do. During verification they are evaluated against a fulfillment object (the verifier-constructed view of what the agent actually proposes: merchant, payee, amount, currency, line items). The constraint type strings match the VI spec exactly:

ConstrainttypeBounds
Allowed merchantmandate.checkout.allowed_merchantCheckout merchant must match one entry in allowed_merchants.
Line itemsmandate.checkout.line_itemsEach cart item must appear in some entry’s acceptable_items (empty acceptable_items = wildcard); total quantity must not exceed the sum of entry quantity values.
Allowed payeepayment.allowed_payeePayment payee must match one entry in allowed_payees.
Payment amountpayment.amountPer-transaction range. Optional min / max (cents) plus currency; currency must match the fulfillment.
Payment budgetpayment.budgetCumulative cap (max, cents). At the agent level this checks a single transaction ≤ cap; cumulative tracking across transactions is the payment network’s responsibility.
Payment recurrencepayment.recurrenceMerchant-managed recurring payment (frequency, start_date, optional end_date / number). Pass-through at the agent level — enforced by the payment network.
Agent recurrencepayment.agent_recurrenceAgent-managed recurring purchase (frequency, start_date, optional end_date / max_occurrences). Pass-through at the agent level.
Payment referencepayment.referenceCross-reference binding (conditional_transaction_id) tying the payment mandate to the checkout mandate. Verified structurally, not against fulfillment.

Entity matching (merchants and payees) follows spec precedence: match by id when both sides have one, otherwise by the (name, website) pair.

A few evaluation rules worth knowing:

  • An empty allowlist (merchant, payee, or line items) is treated as unsatisfiable and always violates — it never silently allows everything.
  • If the fulfillment lacks merchant or payee info, the corresponding allowlist check is skipped (cannot validate, per spec) — but a missing amount fails payment.amount and payment.budget.
  • Each violation carries a machine-readable kind (AmountOutOfRange, BudgetExceeded, CurrencyMismatch, MerchantNotAllowed, PayeeNotAllowed, LineItemViolation, …) so a policy engine can branch on the reason without parsing prose.
[
{
"type": "mandate.checkout.allowed_merchant",
"allowed_merchants": [
{ "name": "Example Store", "website": "https://store.example.com" }
]
},
{
"type": "payment.amount",
"currency": "USD",
"min": 10000,
"max": 40000
},
{
"type": "payment.budget",
"currency": "USD",
"max": 50000
}
]

This authorizes the agent to check out at one named merchant for between $100.00 and $400.00 per transaction, capped at $500.00.

VI is surfaced to the agent loop through the vi_verify tool. It is a read-class operation (gated by the security policy as a Read) and exposes three operations via the operation argument:

operationPurposeRequired args
verify_bindingCheck the sd_hash binding between two credential layers.sd_hash, serialized_parent
verify_timestampsValidate iat / exp with a 300-second clock-skew tolerance.iat, exp
evaluate_constraintsValidate a constraint array against fulfillment data.constraints, fulfillment
{
"operation": "evaluate_constraints",
"constraints": [
{ "type": "payment.amount", "currency": "USD", "min": 10000, "max": 40000 }
],
"fulfillment": { "amount": 27999, "currency": "USD" }
}

Response (the tool’s output is a JSON string):

{
"all_satisfied": true,
"results": [
{ "constraint_type": "payment.amount", "satisfied": true, "violations": [] }
]
}

When any constraint is violated, all_satisfied is false, the offending entry lists its violations, and the tool reports failure with the error one or more constraints violated.

{
"operation": "verify_binding",
"sd_hash": "<expected B64U(SHA-256(parent))>",
"serialized_parent": "<serialized parent SD-JWT>"
}

A match returns sd_hash binding verified; a mismatch returns a VI/SdHashMismatch error.

VI verification is off by default. Enable it in ~/.revka/config.toml:

[verifiable_intent]
enabled = false # turn on VI credential verification for commerce tool calls
strictness = "strict" # "strict" | "permissive"
KeyTypeDefaultMeaning
enabledboolfalseEnable VI credential verification on commerce tool calls.
strictnessstring"strict"Constraint-evaluation strictness mode (see below).

The strictness setting controls how unknown constraint types are handled during chain verification:

  • strict — an unrecognized constraint type is a violation (fail-closed). Recommended for production: a constraint the verifier does not understand should never silently pass.
  • permissive — an unrecognized constraint type is skipped with a warning (fail-open). Useful only when interoperating with newer mandates whose constraint types your build predates.

For config precedence and how config.toml is resolved, see the configuration overview.

When VI is enabled and the agent attempts a commerce action, the verifier walks the chain in order. Conceptually:

  1. Timestampsiat / exp on each layer are checked with ±300 s skew tolerance. Expired or not-yet-valid credentials are rejected (Expired / NotYetValid).

  2. Bindings — L2 sd_hash is checked against L1; in Autonomous mode L3b checkout_hash is checked against its checkout_jwt, and L3a transaction_id is cross-referenced to L3b checkout_hash.

  3. Mode — the mandate vct values fix the mode (Immediate vs Autonomous), and the presence/absence of the agent-key cnf is validated for that mode.

  4. Constraints — in Autonomous mode, every L2 constraint is evaluated against the fulfillment the agent proposes. Any unsatisfied constraint blocks the transaction.

Only when every check passes does the gated commerce tool proceed. Because each error has a machine-readable kind, the surrounding policy logic can decide whether to block, retry, or escalate.