The intent-anchored human-not-present (HNP) AP2 scenario, end to end. The user is offline; the shopping agent operates under the authority of two open mandates the user issued in advance.
- Spec:
spec/spec.md§12.2 - Source:
examples/human_not_present_intent.rs - Chain verification example:
examples/verify_chain.rs - Fixtures:
fixtures/scenarios/human-not-present/
To regenerate the fixtures locally:
cargo run --example human_not_present_intent -- --out target/local-hnp
diff -r fixtures/scenarios/human-not-present target/local-hnp # must be emptySame three identities as the HP scenario (see human-present.md §Cast).
The user issues an OpenCheckoutMandate constrained to:
LineItems(e.g. 2 × concert tickets close to main stage)AllowedMerchants(the merchant identity the agent will transact with)
The mandate is wrapped as an SD-JWT VC signed by the user's Ed25519 key with a cnf JWK delegating to a per-session merchant delegate key. The root token is the file at fixtures/scenarios/human-not-present/open_checkout_token.json; the decoded mandate payload is open_checkout_mandate.json.
In parallel the user issues an OpenPaymentMandate constrained to:
AmountRange(currency + max + optional min) — the budget capAllowedPayees(the merchant identity)PaymentReference(conditional_transaction_id=sha256(open_checkout_mandate_token)) — binds the payment to the checkout open mandate
The cnf JWK delegates to a per-session credentials-provider delegate key. Token: open_payment_token.json. Payload: open_payment_mandate.json.
The shopping agent presents the open checkout mandate to the merchant. The merchant calls evaluate_intent (in crates/ap2-actors/src/merchant.rs) and either:
FulfillOpenMandates— the merchant has enough information to proceed without calling the user back into session, orRequireUserInSession— the constraints aren't satisfied and the merchant rejects the intent.
In this scenario the merchant picks FulfillOpenMandates.
The merchant assembles a Checkout payload (line items, totals, expiry, links) and computes a compact JWS over it (checkout_jwt). The closed CheckoutMandate carries:
checkout_jwt— the merchant-signed compact JWTcheckout_hash—sha256_b64url(canonical_json(checkout_jwt))
The merchant then issues the closed CheckoutMandate as an SD-JWT extension on top of the user's open token, producing checkout_mandate_sd_jwt.json. The hop is joined with ~~; the cnf rotates from the user → merchant delegate.
The credentials provider verifies the open payment mandate's SD-JWT, mints a closed PaymentMandate bound to the closed checkout's checkout_hash, and issues it as an SD-JWT extension producing payment_mandate_sd_jwt.json. The cnf rotates from the user → credentials-provider delegate.
The MPP runs verify_and_settle (in crates/ap2-actors/src/mpp.rs):
MandateClient::verify_chainon the checkout SD-JWT — walks every hop, checks per-hopcnfrotation, audience, nonce, expiry.CheckoutMandateChain::parsethenverify— runs the AP2 constraint engine over the chain. ReturnsVec<Violation>; an empty vec is the only success signal.MandateClient::verify_chainon the payment SD-JWT.PaymentMandateChain::parsethenverify— checks line items, allowed payees, amount range, agent recurrence, budgets, payment-reference binding.- Builds the
CheckoutReceiptandPaymentReceipt, each carrying the prior-hop hash for offline replay.
The full audit pack is trace.json — same shape as the HP trace, recording every per-step input, digest, signer, and violation list.
ap2 chain inspect "$(cat fixtures/scenarios/human-not-present/checkout_mandate_sd_jwt.json | jq -r .token)"prints each hop's decoded payload as JSON. Add --jwks fixtures/keys/jwks.json to also verify the issuer signature on every hop.