diff --git a/CLAUDE.md b/CLAUDE.md index 6d53a57..aff0954 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -83,7 +83,7 @@ Two identity types: wallet (`X-Wallet-Address`) and operator-token (`X-Operator- `create_session_on_missing` auto-mints a verification session when no identity is present AND when `wallet_not_trusted` carries fixable reasons (`kyc_required` / `kyc_pending` / `kyc_failed`) — both paths rewrite the denial to `identity_verification_required` before reaching `on_denied`. When the merchant omits `create_session_on_missing` from `CheckoutGateConfig`, `Checkout` auto-defaults it from `gate.api_key` + `gate.base_url` + `gate.context` + `gate.merchant_name`. Merchants that need `on_before_session` side effects (e.g. pre-minting an order_id) supply their own config to override. -**Getting a verify_url before paying.** On an identity-gated `Checkout`, the gate runs only on a settle leg, so a buyer with no identity reached the session 403 only by sending a payment credential first (an SPT buyer had to mint one). The discovery 402 carries an `identity_bootstrap` block (`build_identity_bootstrap()`) naming `X-Verification-Session: create` whenever the request has no identity header (`has_identity_header`), and a request carrying that header, no identity and no payment credential runs the gate, which answers with the same session-bearing 403. It is opt-in on purpose: scanners replay the Bazaar example body on a schedule, so minting on every identity-less 402 would create a session and (on goods stores) a pending order per probe. Gateless merchants neither advertise nor honor it. Parity with node-commerce. +**Getting a verify_url before paying.** On an identity-gated `Checkout`, the gate runs only on a settle leg, so a buyer with no identity reached the session 403 only by sending a payment credential first (an SPT buyer had to mint one). The discovery 402 carries an `identity_bootstrap` block (`build_identity_bootstrap()`) naming `X-Verification-Session: create` whenever the request has no identity header (`has_identity_header`), and a request carrying that header, no identity and no payment credential runs the gate, which answers with the same session-bearing 403. It is opt-in on purpose: scanners replay the Bazaar example body on a schedule, so minting on every identity-less 402 would create a session and (on goods stores) a pending order per probe. Gateless merchants neither advertise nor honor it. `build_verification_required_body(reason, message=?, agent_instructions=?, extra=?)` — canonical body builder for the `identity_verification_required` denial. Spreads `verify_url` / `session_id` / `poll_secret` / `poll_url` / `agent_instructions` from the gate-minted reason into a 4xx envelope with merchant-specific message + optional extras. Saves the per-merchant mapping boilerplate. @@ -131,7 +131,7 @@ async def gate_on_settle(request: Request) -> None: async def purchase(...): ... ``` -Anonymous POST flows through to the handler unauthenticated and gets a 402 with all rails + per-order pricing. Identity is verified at settle time on the retry leg (when the agent submits `X-Payment` / `Authorization: Payment`); `create_session_on_missing` still auto-mints a verification session there. The shared predicate is `should_run_conditional_gate` (from `agentscore_commerce.payment`): a payment credential OR `requests_verification_session` (`X-Verification-Session: create` with no identity and no payment), so a buyer can get the session 403 before paying. Every conditional adapter variant uses it; a hand-rolled wrap should too, and a merchant building its own 402 advertises the path by passing `build_identity_bootstrap()` into `build_402_body`'s `extra` as `identity_bootstrap`. Parity with node-commerce. The same wrap pattern works identically across all 6 framework adapters (fastapi, flask, django, aiohttp, sanic, middleware/ASGI). See `examples/multi_rail_merchant.py` and `examples/compliance_merchant.py`. +Anonymous POST flows through to the handler unauthenticated and gets a 402 with all rails + per-order pricing. Identity is verified at settle time on the retry leg (when the agent submits `X-Payment` / `Authorization: Payment`); `create_session_on_missing` still auto-mints a verification session there. The shared predicate is `should_run_conditional_gate` (from `agentscore_commerce.payment`): a payment credential OR `requests_verification_session` (`X-Verification-Session: create` with no identity and no payment), so a buyer can get the session 403 before paying. Every conditional adapter variant uses it; a hand-rolled wrap should too, and a merchant building its own 402 advertises the path by passing `build_identity_bootstrap()` into `build_402_body`'s `extra` as `identity_bootstrap`. The same wrap pattern works identically across all 6 framework adapters (fastapi, flask, django, aiohttp, sanic, middleware/ASGI). See `examples/multi_rail_merchant.py` and `examples/compliance_merchant.py`. ### `compatible_clients` field on emitted 402s