agentscore-commerce 2.1.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/workflows/security.yml +8 -2
  2. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.gitignore +2 -0
  3. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/CLAUDE.md +11 -3
  4. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/PKG-INFO +2 -2
  5. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/README.md +1 -1
  6. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/__init__.py +3 -1
  7. agentscore_commerce-2.2.0/agentscore_commerce/_warnings.py +39 -0
  8. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/how_to_pay.py +17 -6
  9. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/checkout.py +222 -37
  10. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/checkout_compute_first.py +100 -3
  11. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/checkout_hooks.py +4 -2
  12. agentscore_commerce-2.2.0/agentscore_commerce/errors.py +40 -0
  13. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/__init__.py +2 -1
  14. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/_response.py +33 -0
  15. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/core.py +4 -4
  16. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/middleware.py +4 -4
  17. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/policy.py +2 -3
  18. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/types.py +4 -4
  19. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/compose_rails.py +41 -1
  20. agentscore_commerce-2.2.0/agentscore_commerce/payment/constants.py +16 -0
  21. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/dispatch.py +19 -3
  22. agentscore_commerce-2.2.0/agentscore_commerce/payment/mppx_failures.py +59 -0
  23. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/pay_to_address.py +35 -7
  24. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/payment_intent.py +11 -1
  25. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/compliance_merchant.py +11 -5
  26. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/compute_first_merchant.py +5 -1
  27. agentscore_commerce-2.2.0/osv-scanner.toml +25 -0
  28. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/pyproject.toml +1 -1
  29. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout.py +25 -0
  30. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_compute_first.py +1 -1
  31. agentscore_commerce-2.2.0/tests/test_checkout_compute_first_ofac.py +207 -0
  32. agentscore_commerce-2.2.0/tests/test_checkout_wallet_ofac_default.py +259 -0
  33. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_compose_rails.py +13 -1
  34. agentscore_commerce-2.2.0/tests/test_mppx_failures.py +39 -0
  35. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_pay_to_address.py +27 -1
  36. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_dispatch.py +12 -2
  37. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_policy.py +5 -5
  38. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_response.py +42 -0
  39. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_stripe_multichain.py +5 -1
  40. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/uv.lock +130 -153
  41. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  42. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  43. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  44. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/dependabot.yml +0 -0
  45. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/workflows/ci.yml +0 -0
  46. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/.github/workflows/publish.yml +0 -0
  47. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/CODE_OF_CONDUCT.md +0 -0
  48. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/CONTRIBUTING.md +0 -0
  49. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/LICENSE +0 -0
  50. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/SECURITY.md +0 -0
  51. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/_headers.py +0 -0
  52. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/_mppx_receipt.py +0 -0
  53. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/_redis.py +0 -0
  54. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/api/__init__.py +0 -0
  55. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/__init__.py +0 -0
  56. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/accepted_methods.py +0 -0
  57. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/agent_instructions.py +0 -0
  58. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/agent_memory.py +0 -0
  59. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/body.py +0 -0
  60. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/identity.py +0 -0
  61. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/pricing.py +0 -0
  62. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/receipt.py +0 -0
  63. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/respond_402.py +0 -0
  64. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/challenge/validation_error.py +0 -0
  65. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/__init__.py +0 -0
  66. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/agentscore_content.py +0 -0
  67. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/bazaar.py +0 -0
  68. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/llms_txt.py +0 -0
  69. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/openapi.py +0 -0
  70. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/probe.py +0 -0
  71. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/redemption_md.py +0 -0
  72. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/robots_tag.py +0 -0
  73. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/skill_md.py +0 -0
  74. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/well_known.py +0 -0
  75. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/well_known_mpp.py +0 -0
  76. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/discovery/well_known_x402.py +0 -0
  77. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/_denial.py +0 -0
  78. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/a2a.py +0 -0
  79. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/address.py +0 -0
  80. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/aiohttp.py +0 -0
  81. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/cache.py +0 -0
  82. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/default_denied.py +0 -0
  83. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/django.py +0 -0
  84. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/fastapi.py +0 -0
  85. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/flask.py +0 -0
  86. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/py.typed +0 -0
  87. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/sanic.py +0 -0
  88. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/sessions.py +0 -0
  89. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/signer.py +0 -0
  90. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/tokens.py +0 -0
  91. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/ucp.py +0 -0
  92. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/identity/ucp_jwks.py +0 -0
  93. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/__init__.py +0 -0
  94. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/_core.py +0 -0
  95. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/aiohttp.py +0 -0
  96. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/asgi.py +0 -0
  97. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/django.py +0 -0
  98. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/fastapi.py +0 -0
  99. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/flask.py +0 -0
  100. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/middleware/sanic.py +0 -0
  101. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/__init__.py +0 -0
  102. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/amounts.py +0 -0
  103. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/default_rails.py +0 -0
  104. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/directive.py +0 -0
  105. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/headers.py +0 -0
  106. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/idempotency.py +0 -0
  107. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/lazy.py +0 -0
  108. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/mppx_server.py +0 -0
  109. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/network_kind.py +0 -0
  110. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/networks.py +0 -0
  111. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/payment_header.py +0 -0
  112. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/rail_spec.py +0 -0
  113. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/rails.py +0 -0
  114. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/settlement_override.py +0 -0
  115. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/signer.py +0 -0
  116. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/solana.py +0 -0
  117. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/usdc.py +0 -0
  118. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/wwwauthenticate.py +0 -0
  119. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402.py +0 -0
  120. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402_server.py +0 -0
  121. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402_settle.py +0 -0
  122. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/x402_validation.py +0 -0
  123. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/payment/zero_settle.py +0 -0
  124. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/quote_cache.py +0 -0
  125. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/__init__.py +0 -0
  126. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/mppx_stripe.py +0 -0
  127. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/pi_cache.py +0 -0
  128. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/simulate_deposit.py +0 -0
  129. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/agentscore_commerce/stripe_multichain/simulate_dispatch.py +0 -0
  130. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/README.md +0 -0
  131. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/api_provider.py +0 -0
  132. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/identity_only.py +0 -0
  133. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/multi_rail_merchant.py +0 -0
  134. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/per_product_policy_merchant.py +0 -0
  135. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/signed_ucp_merchant.py +0 -0
  136. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/examples/stripe_multichain_merchant.py +0 -0
  137. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/lefthook.yml +0 -0
  138. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/ruff.toml +0 -0
  139. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/scripts/regenerate_cross_lang_fixtures.py +0 -0
  140. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/__init__.py +0 -0
  141. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/conftest.py +0 -0
  142. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-agentscore-gate-blocked.json +0 -0
  143. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-agentscore-gate-full.json +0 -0
  144. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-capability.json +0 -0
  145. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-emoji-keys.json +0 -0
  146. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-es256-rails.json +0 -0
  147. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-extras-int.json +0 -0
  148. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-int-boundary.json +0 -0
  149. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-minimal.json +0 -0
  150. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-multikey.json +0 -0
  151. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/node-unicode.json +0 -0
  152. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-agentscore-gate-blocked.json +0 -0
  153. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-agentscore-gate-full.json +0 -0
  154. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-capability.json +0 -0
  155. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-emoji-keys.json +0 -0
  156. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-es256-rails.json +0 -0
  157. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-extras-int.json +0 -0
  158. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-int-boundary.json +0 -0
  159. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-minimal.json +0 -0
  160. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-multikey.json +0 -0
  161. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/fixtures/cross-lang/py-unicode.json +0 -0
  162. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_a2a.py +0 -0
  163. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_a2a_jws_roundtrip.py +0 -0
  164. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_address.py +0 -0
  165. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_agent_memory_emitter.py +0 -0
  166. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_aiohttp.py +0 -0
  167. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_amounts.py +0 -0
  168. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_api_reexport.py +0 -0
  169. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_cache.py +0 -0
  170. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_challenge.py +0 -0
  171. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_compute_first_adapters.py +0 -0
  172. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_compute_first_settle.py +0 -0
  173. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_checkout_coverage_gaps.py +0 -0
  174. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_classify_orchestration_error.py +0 -0
  175. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_core.py +0 -0
  176. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_coverage_fillers.py +0 -0
  177. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_default_denied.py +0 -0
  178. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_default_rails.py +0 -0
  179. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_default_read_only_on_denied.py +0 -0
  180. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_denial.py +0 -0
  181. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_discovery.py +0 -0
  182. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_dispatch.py +0 -0
  183. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_django.py +0 -0
  184. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_extract_owner_scope.py +0 -0
  185. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_fastapi.py +0 -0
  186. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_flask.py +0 -0
  187. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_gate_quota_info.py +0 -0
  188. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_get_signer_verdict.py +0 -0
  189. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_idempotency_helper.py +0 -0
  190. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_integration.py +0 -0
  191. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_internal_helpers.py +0 -0
  192. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_lifted_helpers.py +0 -0
  193. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_load_ucp_signing_key_from_env.py +0 -0
  194. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_middleware.py +0 -0
  195. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_mppx_receipt.py +0 -0
  196. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_network_kind.py +0 -0
  197. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_directive.py +0 -0
  198. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_handlers.py +0 -0
  199. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_header.py +0 -0
  200. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_headers.py +0 -0
  201. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_misc.py +0 -0
  202. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_servers.py +0 -0
  203. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_payment_signer.py +0 -0
  204. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_pricing.py +0 -0
  205. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_public_surface.py +0 -0
  206. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_quote_cache.py +0 -0
  207. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_quote_cache_redis.py +0 -0
  208. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_rail_spec.py +0 -0
  209. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_rate_limit.py +0 -0
  210. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_redis_internal.py +0 -0
  211. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_robots_tag.py +0 -0
  212. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_sanic.py +0 -0
  213. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_seamless_helpers.py +0 -0
  214. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_sessions.py +0 -0
  215. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_signer_match.py +0 -0
  216. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_simulate_dispatch.py +0 -0
  217. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_skill_md.py +0 -0
  218. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_solana.py +0 -0
  219. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_solana_mpp_rail_spec_post_init.py +0 -0
  220. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_tokens.py +0 -0
  221. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_ucp.py +0 -0
  222. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_ucp_cross_lang.py +0 -0
  223. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_ucp_jwks.py +0 -0
  224. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_validation_error.py +0 -0
  225. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_x402_settle_extra.py +0 -0
  226. {agentscore_commerce-2.1.0 → agentscore_commerce-2.2.0}/tests/test_zero_settle.py +0 -0
@@ -27,7 +27,10 @@ jobs:
27
27
  chmod +x osv-scanner
28
28
 
29
29
  - name: Scan dependencies
30
- run: ./osv-scanner scan source --lockfile=uv.lock --format=table
30
+ # `--config osv-scanner.toml` honors the in-repo allowlist (e.g. PYSEC-2025-183;
31
+ # see the toml for justifications). Keep the pip-audit `--ignore-vuln` flags below
32
+ # in sync with the same IDs.
33
+ run: ./osv-scanner scan source --lockfile=uv.lock --config=osv-scanner.toml --format=table
31
34
 
32
35
  pip-audit:
33
36
  name: Python Audit
@@ -44,6 +47,9 @@ jobs:
44
47
  run: pip install pip-audit
45
48
 
46
49
  - name: Audit dependencies
50
+ # `--ignore-vuln` allowlist mirrors `osv-scanner.toml` (e.g. PYSEC-2025-183;
51
+ # see that file for justifications). Keep both lists in sync.
47
52
  run: |
48
53
  uv export --format requirements-txt --no-hashes > requirements.txt
49
- pip-audit -r requirements.txt --disable-pip --no-deps
54
+ pip-audit -r requirements.txt --disable-pip --no-deps \
55
+ --ignore-vuln PYSEC-2025-183
@@ -17,3 +17,5 @@ venv/
17
17
  *.swo
18
18
  .DS_Store
19
19
  .coverage
20
+ coverage/
21
+ htmlcov/
@@ -11,7 +11,7 @@ Every helper is extracted from a real consumer, not speculated.
11
11
  | `agentscore_commerce` (top-level) | `Checkout` orchestrator (the 2.0 high-level surface): one config object + hooks (pre_validate, compute_pricing, mint_recipients, compose_mppx, on_settled, gate), auto-derived x402+pympp servers, per-framework adapters `handle_fastapi`/`handle_flask`/`handle_django`/`handle_aiohttp`/`handle_sanic`, signed UCP routes via `mount_ucp_routes_{fastapi,flask,django,aiohttp,sanic}`, optional `discovery_probe` config for x402-crawler auto-routing. Plus `compute_first_checkout` — variable-cost pay-per-result helper (compute-first + exact-x402). Scope is exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); does NOT use x402-upto (Permit2) or Settlement-Overrides — variable cost is captured by running the work pre-settle and emitting a 402 at the exact computed price. `create_quote_cache` — content-hash quote cache used by the compute-first helper (in-memory by default; pass `redis_url` for distributed deployments). `create_default_on_denied` — canonical `on_denied(reason)` factory matching `Checkout`'s gate hook (handles `wallet_signer_mismatch`, `wallet_not_trusted` unfixable fallback, `payment_required`, `token_expired`/`invalid_credential`/`api_error`); merchants pass `merchant_name` + `support_email` and override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy. `has_payment_header` — discriminator that splits discovery legs (no payment credential → 402) from settle legs (`payment-signature` / `x-payment` / `Authorization: Payment <jwt>`); `has_x402_header` / `has_mppx_header` — granular dispatch helpers (x402 vs MPP credential present) for routes that branch on rail. `default_read_only_on_denied(reason)` — canonical `on_denied` for read-only resource gates (`GET /orders/:id`): collapses every denial to 401 `unauthorized` + `Cache-Control: no-store` while still spreading `denial_reason_to_body` so `agent_instructions` / `verify_url` ride through. Returns a `DefaultOnDeniedResult(body, status, headers)` dataclass; FastAPI / Flask / aiohttp / Sanic `on_denied` callbacks accept an optional 3-tuple `(body, status, headers)` — convert with `lambda req, reason: (r := default_read_only_on_denied(reason), (r.body, r.status, r.headers or {}))[1]` or a named wrapper. Django + ASGI middleware adapters return Response objects directly; construct `JsonResponse(r.body, status=r.status, headers=r.headers)` / `JSONResponse(content=r.body, status_code=r.status, headers=r.headers)`. `extract_owner_scope(headers) -> OwnerScope` — pull canonical owner identity from `X-Wallet-Address` / `X-Operator-Token` with safe token hashing; pair with a wallet-or-token-scoped resource query so plaintext tokens never leave the request. Plus factories: `pricing_result` (cents → typed `PricingResult`), `validation_response_{fastapi,flask,django,aiohttp,sanic}` (4xx envelope per framework), `Receipt`/`ReceiptNextSteps`/`ProductInfo`/`ShippingAddress` (canonical 200-receipt dataclasses, universal across goods + API merchants) |
12
12
  | `agentscore_commerce.identity.{fastapi,flask,django,aiohttp,sanic,middleware}` | Trust gate middleware (KYC, age, sanctions on both account name and signer wallet, jurisdiction). Each adapter exports a conditional variant that wraps the gate so it fires only on settle legs (anonymous discovery flows through and gets a 402 with all rails): FastAPI / ASGI expose `ConditionalAgentScoreGate`, Django exposes `ConditionalAgentScoreMiddleware`, Flask + Sanic expose `conditional_agentscore_gate(app, ...)`, aiohttp exposes `conditional_agentscore_gate_middleware(...)`. Adapters export ONLY framework-specific surface (gate classes / fns, accessors, `capture_wallet`); shared helpers like `has_payment_header` / `denial_reason_to_body` import from their canonical home (`agentscore_commerce.payment` and `agentscore_commerce.identity` respectively). The existing `agentscore_gate(app, ...)` and `AgentScoreGate` accept an optional `condition=` callable for inline gating. |
13
13
  | `agentscore_commerce.identity.policy` | Per-product compliance helpers: `PolicyBlock`, `build_gate_from_policy`, `run_gate_with_enforcement`, `shipping_country_allowed`, `shipping_state_allowed`, `validate_shipping_against_policy` (one-call country+state validator that raises `CheckoutValidationError` with the canonical envelope on miss) |
14
- | `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps `x402[evm]>=2.9` + `cdp-sdk` for `facilitator="coinbase"`; install via the `coinbase` extra), `build_x402_accepts_for_402` (build the 402's `accepts[]` from the registered scheme; derives the right `extra.name` per network), `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` (canonical 4-rail `rails` dict factory: merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. When a caller flips `network` without pinning `token` / `chain_id`, the underlying dataclass derives them from the network: Base Sepolia → Sepolia USDC + chain_id 84532, Solana devnet → devnet USDC mint. Explicit overrides always win. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) AND the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`)), `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` (per-call intent factory replacing the hand-rolled `[("tempo/charge", {...}), ("solana/charge", {...}), ("stripe/charge", {...})]` list; auto-handles USD→atomic conversion for Solana), `process_x402_settle` (verify+settle in one call), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6`), `is_evm_network`/`is_solana_network` (CAIP-2 discriminators that hide the `startswith("eip155:")` / `startswith("solana:")` prefix matching), `has_payment_header` (settle-leg vs discovery-leg discriminator), `parse_did_pkh_address` (parses ``did:pkh:<family>:<chain>:<addr>`` into a `PaymentSigner`), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
14
+ | `agentscore_commerce.payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `create_x402_server` (wraps `x402[evm]>=2.9` + `cdp-sdk` for `facilitator="coinbase"`; install via the `coinbase` extra), `build_x402_accepts_for_402` (build the 402's `accepts[]` from the registered scheme; derives the right `extra.name` per network), `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` (canonical 4-rail `rails` dict factory: merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. When a caller flips `network` without pinning `token` / `chain_id`, the underlying dataclass derives them from the network: Base Sepolia → Sepolia USDC + chain_id 84532, Solana devnet → devnet USDC mint. Explicit overrides always win. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) AND the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`)), `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` (per-call intent factory replacing the hand-rolled `[("tempo/charge", {...}), ("solana/charge", {...}), ("stripe/charge", {...})]` list; auto-handles USD→atomic conversion for Solana; auto-drops the `stripe/charge` rail with a one-time `logging.warning` when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable — many Stripe accounts also reject PI creation below the floor with `amount_too_small`; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning), `process_x402_settle` (verify+settle in one call), `create_mppx_server` (wraps `pympp[server,tempo,stripe]>=0.6`), `is_evm_network`/`is_solana_network` (CAIP-2 discriminators that hide the `startswith("eip155:")` / `startswith("solana:")` prefix matching), `has_payment_header` (settle-leg vs discovery-leg discriminator), `parse_did_pkh_address` (parses ``did:pkh:<family>:<chain>:<addr>`` into a `PaymentSigner`), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header |
15
15
  | `agentscore_commerce.discovery` | Discovery probe (`is_discovery_probe_request`, `build_discovery_probe_response`), Bazaar wrapper, `/.well-known/mpp.json`, `llms.txt` builder, `skill.md` builder (Claude-Skill-compatible agent-discovery manifest), `build_redemption_skill_md` (delivery-neutral; printed/emailed/API-trial codes all covered via `delivery_intro`/`body_shape`/`body_rules`/`extra_recovery_rows` overrides), `build_merchant_index_json` + `standard_endpoint_descriptions(kind=)` (canonical `/` discovery body for goods or API merchants), `build_success_next_steps` (universal Passport-active success block), `build_agentscore_onboarding_steps`, OpenAPI snippets, `NoindexNonDiscoveryMiddleware` ASGI middleware. Plus the UCP/JWKS publish surface: `build_signed_ucp_response`, `build_signed_jwks_response`, `well_known_preflight_response`, `default_a2a_services`, `bootstrap_ucp_signing_key`, framework-neutral `SignedDiscoveryResponse` + per-framework wrappers `signed_response_{fastapi,flask,django,aiohttp,sanic}` |
16
16
  | `agentscore_commerce.challenge` | 402-body builders: accepted_methods, identity_metadata (auto-attached by `Checkout` when wallet header present), how_to_pay, agent_instructions, build_402_body, pricing, agent_memory, `build_validation_error` (4xx body builder), `Receipt`/`ReceiptNextSteps`/`ProductInfo`/`ShippingAddress` (canonical 200-receipt dataclasses) |
17
17
  | `agentscore_commerce.stripe_multichain` | Multichain PaymentIntent helper (`create_multichain_payment_intent` returns `MultichainPaymentIntentResult`; read `result.deposit_addresses[network]` directly), `create_pay_to_address_from_stripe_pi(authorization_header=, amount_cents=, stripe=, pi_cache=, networks=, metadata=, order_id=, preferred_network=)` — one-call per-order payTo resolver matching `Checkout.mint_recipients`: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `pi_cache.has_address` check); on the discovery leg, mints a fresh PI via `create_multichain_payment_intent` and caches the addresses + PI mapping. Testnet simulator (`simulate_crypto_deposit`, `simulate_deposit_if_test_mode`), `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=, stripe_version=)` (dispatches the simulator based on a Checkout / compute_first_checkout settle outcome; replaces the per-merchant rail-switch + thin `simulate_deposit_if_testnet(addr, network)` wrapper), `network_for_outcome` (outcome → simulator network arg, handles both Checkout-shaped `rail_key` and compute-first-shaped `mpp_method`, accepts bare scheme names AND `<scheme>/charge` forms), `create_pi_cache`, `create_mppx_stripe` |
@@ -80,11 +80,19 @@ Two identity types: wallet (`X-Wallet-Address`) and operator-token (`X-Operator-
80
80
 
81
81
  `DenialReason` codes (`missing_identity`, `identity_verification_required`, `token_expired`, `invalid_credential`, `wallet_signer_mismatch`, `wallet_auth_requires_wallet_signing`, `wallet_not_trusted`, `api_error`, `payment_required`) each carry a structured `agent_instructions` JSON block describing concrete recovery actions. See `agentscore_commerce/identity/_response.py` for the canned action copy.
82
82
 
83
- `create_session_on_missing` auto-mints a verification session when no identity is present and returns 403 with `verify_url` + poll instructions. `get_signer_verdict(request)` (per-adapter) returns the cached `signer_match` + `signer_sanctions` verdicts the gate composed on its primary `/v1/assess` call (single round trip; merchants build a 403 with `build_signer_mismatch_body(result=verdict.signer_match)` when `kind != "pass"`).
83
+ `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.
84
+
85
+ `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.
86
+
87
+ `get_signer_verdict(request)` (per-adapter) returns the cached `signer_match` + `signer_sanctions` verdicts the gate composed on its primary `/v1/assess` call (single round trip; merchants build a 403 with `build_signer_mismatch_body(result=verdict.signer_match)` when `kind != "pass"`).
84
88
 
85
89
  Captured wallets: `capture_wallet(...)` is fire-and-forget. Reads `operator_token` stashed during gating and POSTs to `/v1/credentials/wallets`. No-ops for wallet-authenticated requests.
86
90
 
87
- Wallet-signer-match + signer-sanctions: the gate adapter calls `extract_payment_signer(x402_header)` pre-evaluate and passes `signer={address, network}` to the SDK's `assess`. The API returns both `signer_match` (wallet-binding) and `signer_sanctions` (OFAC SDN wallet-address) on the same response; commerce caches the raw body alongside the projected verdicts so `get_signer_verdict` is a pure cache read. Under `policy.require_sanctions_clear`, an OFAC SDN signer hit already flips `decision -> deny` before the handler runs.
91
+ Wallet-signer-match + signer-sanctions: the gate adapter calls `extract_payment_signer(x402_header)` pre-evaluate and passes `signer={address, network}` to the SDK's `assess`. The API returns both `signer_match` (wallet-binding) and `signer_sanctions` (OFAC SDN wallet-address) on the same response; commerce caches the raw body alongside the projected verdicts so `get_signer_verdict` is a pure cache read. **Wallet-OFAC SDN enforcement on the `signer` block is unconditional** whenever a signer is present — no `policy.require_sanctions_clear` opt-in required. An SDN hit (or `sanctions_check_unavailable`) flips `decision -> deny` and the gate returns 403 before the handler runs.
92
+
93
+ ### Always-on wallet OFAC default for gateless merchants
94
+
95
+ Merchants who do NOT configure a `gate` on `Checkout` (or `compute_first_checkout`) still get wallet OFAC SDN enforcement on every settle leg. The SDK resolves the API key from `AGENTSCORE_API_KEY` (or `gate.api_key` if a partial gate config is present), reads `AGENTSCORE_BASE_URL` for staging/dev overrides, extracts the payment signer, and calls `/v1/assess` with the signer block. SDN hit → 403 with `wallet_not_trusted` + `decision_reasons: ["sanctions_flagged"]`. No key → one-time `[checkout]` / `[<name>.compute_first]` warning + skip (dev/testnet pattern; see `agentscore_commerce/_warnings.py`). Stripe SPT (no extractable wallet signer) skips silently — Stripe runs its own OFAC screen at customer creation. `_has_identity_gate()` detects identity-bearing flags (`require_kyc`, `require_sanctions_clear`, `min_age`, `allowed/blocked_jurisdictions`); when none are set, the 402 body omits `agent_memory` and per-rail commands strip the `-H 'X-Operator-Token: ...'` flag (gateless merchants have no operator to identify). Gate configured WITHOUT `api_key` falls through to the wallet-OFAC-only path so the merchant gets the strict-liability floor instead of silently allowing.
88
96
 
89
97
  ### Fail-open (opt-in)
90
98
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentscore-commerce
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Agent commerce SDK for Python — identity middleware (FastAPI, Flask, Django, AIOHTTP, Sanic, ASGI) + payment helpers + 402 builders + discovery + Stripe multichain. The full merchant-side toolkit for AgentScore-powered agent commerce.
5
5
  Project-URL: Homepage, https://agentscore.sh
6
6
  Project-URL: Repository, https://github.com/agentscore/python-commerce
@@ -224,7 +224,7 @@ For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM
224
224
 
225
225
  For the `on_denied` hook on Checkout's gate config, `create_default_on_denied(merchant_name=, support_email=, ...)` returns the canonical denial callback that handles `wallet_signer_mismatch` / `wallet_not_trusted` unfixable fallback / `payment_required` / `token_expired` / `invalid_credential` / `api_error`. Merchants override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy and keep their own merchant-specific branches (e.g. wine merchants add a fixable-denial-with-session branch on top).
226
226
 
227
- `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` builds the canonical four-rail `rails` dict so merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. Flipping `network` alone is enough: Base Sepolia derives Sepolia USDC + chain_id 84532, Solana devnet derives the devnet USDC mint. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) and the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`). `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` builds the per-call mppx intent list. `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=)` dispatches the Stripe testnet simulator from `on_settled` based on the rail family (no per-merchant rail switch needed).
227
+ `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` builds the canonical four-rail `rails` dict so merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. Flipping `network` alone is enough: Base Sepolia derives Sepolia USDC + chain_id 84532, Solana devnet derives the devnet USDC mint. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) and the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`). `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` builds the per-call mppx intent list. The helper auto-drops `stripe/charge` (with a one-time `logging.warning`) when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning. `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=)` dispatches the Stripe testnet simulator from `on_settled` based on the rail family (no per-merchant rail switch needed).
228
228
 
229
229
  ## Payment helpers
230
230
 
@@ -176,7 +176,7 @@ For **variable-cost pay-per-result** endpoints (per-result search, per-token LLM
176
176
 
177
177
  For the `on_denied` hook on Checkout's gate config, `create_default_on_denied(merchant_name=, support_email=, ...)` returns the canonical denial callback that handles `wallet_signer_mismatch` / `wallet_not_trusted` unfixable fallback / `payment_required` / `token_expired` / `invalid_credential` / `api_error`. Merchants override `wallet_not_trusted_message` / `payment_required_message` / `support_context` for vendor-specific copy and keep their own merchant-specific branches (e.g. wine merchants add a fixable-denial-with-session branch on top).
178
178
 
179
- `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` builds the canonical four-rail `rails` dict so merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. Flipping `network` alone is enough: Base Sepolia derives Sepolia USDC + chain_id 84532, Solana devnet derives the devnet USDC mint. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) and the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`). `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` builds the per-call mppx intent list. `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=)` dispatches the Stripe testnet simulator from `on_settled` based on the rail family (no per-merchant rail switch needed).
179
+ `build_default_checkout_rails(tempo=, x402_base=, solana_mpp=, stripe=)` builds the canonical four-rail `rails` dict so merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chain_id/token boilerplate. Flipping `network` alone is enough: Base Sepolia derives Sepolia USDC + chain_id 84532, Solana devnet derives the devnet USDC mint. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) and the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`). `build_mppx_compose_rails(amount_usd=, tempo_recipient=, solana_recipient=, ...)` builds the per-call mppx intent list. The helper auto-drops `stripe/charge` (with a one-time `logging.warning`) when `amount_usd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable; sub-50-cent APIs pass `include_stripe=False` explicitly to silence the warning. `simulate_deposit_for_outcome(outcome=, deposit_address=, get_payment_intent_id=, stripe_secret_key=)` dispatches the Stripe testnet simulator from `on_settled` based on the rail family (no per-merchant rail switch needed).
180
180
 
181
181
  ## Payment helpers
182
182
 
@@ -20,7 +20,6 @@ from agentscore_commerce.checkout import (
20
20
  CheckoutRailSpec,
21
21
  CheckoutRequest,
22
22
  CheckoutResult,
23
- CheckoutValidationError,
24
23
  DiscoveryProbeConfig,
25
24
  MppxComposeOutcome,
26
25
  PricingResult,
@@ -49,6 +48,7 @@ from agentscore_commerce.checkout_compute_first import (
49
48
  compute_first_checkout,
50
49
  )
51
50
  from agentscore_commerce.checkout_hooks import make_mppx_compose_hook
51
+ from agentscore_commerce.errors import CheckoutValidationError
52
52
 
53
53
  # Re-export the most commonly used helpers at the package root so consumers
54
54
  # don't have to remember which submodule each one lives in. Mirrors node's
@@ -96,6 +96,7 @@ from agentscore_commerce.identity import (
96
96
  build_jwks_response,
97
97
  build_signer_mismatch_body,
98
98
  build_ucp_profile,
99
+ build_verification_required_body,
99
100
  create_default_on_denied,
100
101
  default_read_only_on_denied,
101
102
  denial_reason_status,
@@ -230,6 +231,7 @@ __all__ = [
230
231
  "build_mppx_compose_rails",
231
232
  "build_signer_mismatch_body",
232
233
  "build_ucp_profile",
234
+ "build_verification_required_body",
233
235
  "compute_first_checkout",
234
236
  "create_default_on_denied",
235
237
  "create_quote_cache",
@@ -0,0 +1,39 @@
1
+ """Shared one-shot warning helpers for the SDK.
2
+
3
+ Module-level state ensures each warning fires at most once per process,
4
+ regardless of how many ``Checkout`` / ``compute_first_checkout`` instances
5
+ trigger it. Mirrors ``node-commerce/src/_warnings.ts``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+
12
+ _warned_no_api_key = False
13
+
14
+
15
+ def warn_missing_api_key_once(label: str) -> None:
16
+ """Emit a one-time warning when AGENTSCORE_API_KEY is unset.
17
+
18
+ Triggered on a settle path that would otherwise enforce wallet OFAC SDN
19
+ sanctions. Both ``Checkout`` and ``compute_first_checkout`` route through
20
+ this so a single multi-surface app sees the warning ONCE, not once per
21
+ surface.
22
+
23
+ ``label`` is the caller's identifier for the log message; e.g.
24
+ ``"checkout"`` or the compute-first handler's ``name``.
25
+ """
26
+ global _warned_no_api_key
27
+ if _warned_no_api_key:
28
+ return
29
+ _warned_no_api_key = True
30
+ logging.getLogger(__name__).warning(
31
+ f"[{label}] AGENTSCORE_API_KEY is not set — wallet OFAC SDN sanctions are NOT being enforced. "
32
+ "Set the env var to enable strict-liability protection on settle."
33
+ )
34
+
35
+
36
+ def _reset_warned_no_api_key() -> None:
37
+ """Test-only: reset the warn-once flag."""
38
+ global _warned_no_api_key
39
+ _warned_no_api_key = False
@@ -43,7 +43,7 @@ async def build_how_to_pay(
43
43
  retry_body_json: str,
44
44
  total_usd: float | str,
45
45
  rails: dict[str, TempoRailSpec | X402BaseRailSpec | SolanaMppRailSpec | StripeRailSpec],
46
- op_token_placeholder: str = "<your_opc_token>", # noqa: S107 — literal placeholder, not a secret
46
+ op_token_placeholder: str | None = "<your_opc_token>", # noqa: S107 — literal placeholder, not a secret
47
47
  max_spend: float | str | None = None,
48
48
  decimals: int = 2,
49
49
  ) -> dict[str, Any]:
@@ -62,6 +62,12 @@ async def build_how_to_pay(
62
62
  ≥ $1 the default is still ``ceil(total) + 1`` formatted at ``decimals``; for
63
63
  sub-dollar totals the default uses ``total.toFixed(decimals)`` so the cap
64
64
  flag reflects the real amount instead of always being ``"1.00"``.
65
+
66
+ ``op_token_placeholder`` defaults to ``"<your_opc_token>"``. Pass ``None``
67
+ (gateless merchants) to strip the ``-H 'X-Operator-Token: ...'`` snippet
68
+ from every rail command — appropriate when the merchant doesn't run an
69
+ identity gate. The wallet OFAC SDN default (TEC-311) does NOT need an
70
+ operator token, so gateless merchants emit cleaner commands.
65
71
  """
66
72
  total_num = float(total_usd) if isinstance(total_usd, str) else total_usd
67
73
  if max_spend is not None:
@@ -70,7 +76,9 @@ async def build_how_to_pay(
70
76
  max_spend_str = f"{math.ceil(total_num) + 1:.{decimals}f}"
71
77
  else:
72
78
  max_spend_str = f"{total_num:.{decimals}f}"
73
- op_token = op_token_placeholder
79
+ gateless = op_token_placeholder is None
80
+ op_token = op_token_placeholder or "<your_opc_token>"
81
+ op_token_header_flag = "" if gateless else f"-H 'X-Operator-Token: {op_token}' "
74
82
  block: dict[str, Any] = {}
75
83
 
76
84
  tempo = rails.get("tempo")
@@ -79,11 +87,11 @@ async def build_how_to_pay(
79
87
  chain_id = tempo.chain_id
80
88
  recommend = tempo.recommend
81
89
  tempo_command = (
82
- f"tempo request -X POST -H 'X-Operator-Token: {op_token}' -H 'Content-Type: application/json' "
90
+ f"tempo request -X POST {op_token_header_flag}-H 'Content-Type: application/json' "
83
91
  f"--json '{retry_body_json}' --max-spend {max_spend_str} {url}"
84
92
  )
85
93
  pay_command = (
86
- f"agentscore-pay pay POST {url} --chain tempo -H 'X-Operator-Token: {op_token}' "
94
+ f"agentscore-pay pay POST {url} --chain tempo {op_token_header_flag}"
87
95
  f"-H 'Content-Type: application/json' -d '{retry_body_json}' --max-spend {max_spend_str}"
88
96
  )
89
97
  entry: dict[str, Any] = {
@@ -115,7 +123,7 @@ async def build_how_to_pay(
115
123
  f"least ${max_spend_str}. If the CLI is not installed, run the setup commands above first."
116
124
  ),
117
125
  "command": (
118
- f"agentscore-pay pay POST {url} --chain base -H 'X-Operator-Token: {op_token}' "
126
+ f"agentscore-pay pay POST {url} --chain base {op_token_header_flag}"
119
127
  f"-H 'Content-Type: application/json' -d '{retry_body_json}' --max-spend {max_spend_str}"
120
128
  ),
121
129
  "what_it_does": (
@@ -135,7 +143,7 @@ async def build_how_to_pay(
135
143
  f"is at least ${max_spend_str}. If the CLI is not installed, run the setup commands above first."
136
144
  ),
137
145
  "command": (
138
- f"agentscore-pay pay POST {url} --chain solana -H 'X-Operator-Token: {op_token}' "
146
+ f"agentscore-pay pay POST {url} --chain solana {op_token_header_flag}"
139
147
  f"-H 'Content-Type: application/json' -d '{retry_body_json}' --max-spend {max_spend_str}"
140
148
  ),
141
149
  "what_it_does": (
@@ -182,6 +190,9 @@ async def build_how_to_pay(
182
190
  ),
183
191
  (
184
192
  f"link-cli mpp pay {url} --spend-request-id $SPEND_ID --method POST "
193
+ f"--data '{retry_body_json}' --output-json"
194
+ if gateless
195
+ else f"link-cli mpp pay {url} --spend-request-id $SPEND_ID --method POST "
185
196
  f"--data '{retry_body_json}' --header 'X-Operator-Token: {op_token}' --output-json"
186
197
  ),
187
198
  ]
@@ -71,6 +71,7 @@ import json
71
71
  import uuid
72
72
  from collections.abc import Awaitable, Callable, Mapping
73
73
  from dataclasses import dataclass, field
74
+ from decimal import Decimal
74
75
  from typing import Any, Literal, TypeAlias
75
76
 
76
77
  from agentscore_commerce._headers import normalize_headers_to_lowercase
@@ -83,6 +84,9 @@ from agentscore_commerce.challenge.how_to_pay import build_how_to_pay
83
84
  from agentscore_commerce.challenge.pricing import PricingBlock, build_pricing_block
84
85
  from agentscore_commerce.challenge.respond_402 import Respond402Result, respond_402
85
86
  from agentscore_commerce.challenge.validation_error import build_validation_error
87
+ from agentscore_commerce.errors import CheckoutValidationError
88
+ from agentscore_commerce.payment.constants import STRIPE_MIN_CHARGE_USD
89
+ from agentscore_commerce.payment.mppx_failures import classify_mppx_failure
86
90
  from agentscore_commerce.payment.payment_header import has_mppx_header, has_x402_header
87
91
  from agentscore_commerce.payment.rail_spec import (
88
92
  RecipientLike,
@@ -134,31 +138,6 @@ def _spec_method_name(spec: CheckoutRailSpec) -> str:
134
138
  return "stripe/spt" # StripeRailSpec is the only remaining variant in CheckoutRailSpec.
135
139
 
136
140
 
137
- class CheckoutValidationError(Exception):
138
- """Raised from a :attr:`Checkout.pre_validate` hook to short-circuit with a 4xx.
139
-
140
- Checkout catches this and emits the canonical ``{error, next_steps}`` envelope
141
- via :func:`build_validation_error` so merchants don't have to construct
142
- ``JSONResponse`` themselves in the pre-validate path.
143
- """
144
-
145
- def __init__(
146
- self,
147
- *,
148
- code: str,
149
- message: str,
150
- action: str = "fix_request",
151
- status: int = 400,
152
- extra: dict[str, Any] | None = None,
153
- ) -> None:
154
- super().__init__(message)
155
- self.code = code
156
- self.message = message
157
- self.action = action
158
- self.status = status
159
- self.extra = extra
160
-
161
-
162
141
  @dataclass
163
142
  class CheckoutRequest:
164
143
  """Framework-neutral HTTP request input to :meth:`Checkout.handle`.
@@ -512,6 +491,14 @@ class MppxComposeOutcome:
512
491
  headers without parsing the JSON body."""
513
492
  raw: Any = None
514
493
  """The underlying pympp compose result for ``on_settled`` introspection."""
494
+ failure_reason: str | None = None
495
+ """For ``status=402``: optional reason string captured from the swallowed
496
+ inner verifier exception (e.g. ``"keychain validation failed: KeyNotFound"``
497
+ when a Tempo signer isn't enrolled). When set, ``_handle_mppx`` runs
498
+ :func:`classify_mppx_failure` and returns a typed envelope
499
+ (``tempo_key_not_registered``, etc.) instead of the generic
500
+ ``payment_proof_invalid``. Populated by :func:`make_mppx_compose_hook` when
501
+ pympp's verifier raises; custom hooks can set it explicitly to opt in."""
515
502
 
516
503
 
517
504
  @dataclass
@@ -719,6 +706,30 @@ class Checkout:
719
706
  body's ``extensions`` field so Bazaar crawlers and other spec-compliant
720
707
  clients read the route's declared input/output schema."""
721
708
 
709
+ def _has_identity_gate(self) -> bool:
710
+ """Return True when the merchant configured an identity-bearing flag.
711
+
712
+ The identity-bearing flags are ``require_kyc``, ``require_sanctions_clear``
713
+ (name screening on the KYC identity), ``min_age``, or jurisdiction
714
+ lists. Wallet OFAC SDN enforcement (the always-on default) does NOT
715
+ count as an identity gate; agents don't need an AgentScore credential
716
+ to satisfy it.
717
+
718
+ Used to conditionally emit AgentScore identity boilerplate in 402
719
+ bodies (``agent_memory``, ``X-Operator-Token`` references in per-rail
720
+ commands).
721
+ """
722
+ g = self.gate
723
+ if g is None:
724
+ return False
725
+ return bool(
726
+ g.require_kyc
727
+ or g.require_sanctions_clear
728
+ or g.min_age is not None
729
+ or (g.allowed_jurisdictions and len(g.allowed_jurisdictions) > 0)
730
+ or (g.blocked_jurisdictions and len(g.blocked_jurisdictions) > 0)
731
+ )
732
+
722
733
  async def _get_x402_server(self) -> Any:
723
734
  """Resolve the x402 server.
724
735
 
@@ -912,13 +923,22 @@ class Checkout:
912
923
 
913
924
  ctx.pricing = await _maybe_await(self.compute_pricing(ctx))
914
925
 
915
- # Per-request gate: runs on the settle leg only (anonymous discovery
916
- # passes through to 402). Sets ctx.assess["identity_status"]; 403s
917
- # short-circuit. Merchant-supplied per_request_policy resolves the
918
- # policy block (read from the product row, tier, etc.).
926
+ # Per-request compliance: runs on the settle leg only (anonymous
927
+ # discovery passes through to 402).
928
+ #
929
+ # Two paths converge here:
930
+ # - Merchants with an explicit ``gate`` config run the full identity
931
+ # policy (KYC / age / sanctions / jurisdiction) via ``_run_gate``.
932
+ # - Merchants WITHOUT a ``gate`` config still get wallet OFAC SDN
933
+ # enforcement via ``_run_wallet_sanctions_only`` — the always-on
934
+ # strict-liability default. Falls back to AGENTSCORE_API_KEY env
935
+ # var when set; logs a warning and skips when no key is set
936
+ # (dev/testnet pattern).
919
937
  has_payment_header = has_x402_header(request.headers) or has_mppx_header(request.headers)
920
- if self.gate is not None and has_payment_header:
921
- gate_result = await self._run_gate(ctx)
938
+ if has_payment_header:
939
+ gate_result = (
940
+ await self._run_gate(ctx) if self.gate is not None else await self._run_wallet_sanctions_only(ctx)
941
+ )
922
942
  if gate_result is not None:
923
943
  return gate_result
924
944
 
@@ -1415,6 +1435,13 @@ class Checkout:
1415
1435
  result = await _maybe_await(gate.run_gate(ctx))
1416
1436
  return self._coerce_run_gate_result(ctx, result)
1417
1437
 
1438
+ # Gate configured without an API key — full policy enforcement requires
1439
+ # /v1/assess access, which we can't reach. Fall through to wallet OFAC
1440
+ # SDN enforcement (the strict-liability default) so the merchant still
1441
+ # gets the basic protection layer instead of silently allowing.
1442
+ if not gate.api_key:
1443
+ return await self._run_wallet_sanctions_only(ctx)
1444
+
1418
1445
  # 2. per_request_policy resolves per-product compliance (e.g. wine vs
1419
1446
  # generic merch). Return None to skip the gate entirely for this request.
1420
1447
  policy: Any = None
@@ -1513,6 +1540,117 @@ class Checkout:
1513
1540
  )
1514
1541
  return None
1515
1542
 
1543
+ async def _run_wallet_sanctions_only(self, ctx: CheckoutContext) -> CheckoutResult | None:
1544
+ """Wallet OFAC SDN enforcement.
1545
+
1546
+ Runs on settle (payment header present) when either ``self.gate`` is
1547
+ None OR a gate is configured but has no ``api_key`` to reach
1548
+ ``/v1/assess`` for full policy enforcement (fallback to the
1549
+ strict-liability default).
1550
+
1551
+ Env knobs:
1552
+ - ``AGENTSCORE_API_KEY`` — required. No key → one-time warning + skip
1553
+ (dev/testnet pattern; production should always configure a key).
1554
+ - ``AGENTSCORE_BASE_URL`` — optional override for staging/dev API
1555
+ (e.g. ``https://api-dev.agentscore.sh`` or ``http://localhost:3002``).
1556
+
1557
+ Stripe SPT (no extractable wallet signer) → skip silently; Stripe runs
1558
+ its own OFAC screen on the buyer's Stripe account at customer creation.
1559
+
1560
+ Calls ``/v1/assess`` with the signer wallet as both the primary address
1561
+ and the signer block. The API enforces signer-sanctions unconditionally
1562
+ when a signer is present (no policy flag needed). Denies on OFAC SDN
1563
+ hit; fail-closed on unavailable lookup (strict liability — falsely
1564
+ allowing a sanctioned settle is an OFAC violation, falsely denying a
1565
+ clean buyer is just bad UX).
1566
+ """
1567
+ import os
1568
+
1569
+ from agentscore_commerce._warnings import warn_missing_api_key_once
1570
+
1571
+ api_key = (self.gate.api_key if self.gate is not None else None) or os.environ.get("AGENTSCORE_API_KEY")
1572
+ if not api_key:
1573
+ warn_missing_api_key_once("checkout")
1574
+ return None
1575
+
1576
+ from agentscore_commerce.payment.signer import extract_payment_signer, read_x402_payment_header
1577
+
1578
+ x402_header = read_x402_payment_header(ctx.request.headers)
1579
+ authorization_header: str | None = None
1580
+ for header_key, header_value in ctx.request.headers.items():
1581
+ if header_key.lower() == "authorization":
1582
+ authorization_header = header_value
1583
+ break
1584
+ signer = extract_payment_signer(x402_header, authorization_header=authorization_header)
1585
+ if signer is None:
1586
+ # Stripe SPT path — no wallet signer, no OFAC check possible. Stripe
1587
+ # screens its own customer accounts; we have nothing to add here.
1588
+ return None
1589
+
1590
+ from agentscore_commerce.api import AgentScore
1591
+ from agentscore_commerce.identity._denial import denial_reason_status
1592
+ from agentscore_commerce.identity._response import denial_reason_to_body
1593
+ from agentscore_commerce.identity.types import DenialReason
1594
+
1595
+ base_url = os.environ.get("AGENTSCORE_BASE_URL")
1596
+ client_kwargs: dict[str, Any] = {"api_key": api_key}
1597
+ if base_url:
1598
+ client_kwargs["base_url"] = base_url
1599
+ client = AgentScore(**client_kwargs)
1600
+ from agentscore.errors import (
1601
+ AgentScoreError,
1602
+ InvalidCredentialError,
1603
+ TokenExpiredError,
1604
+ )
1605
+
1606
+ try:
1607
+ result = await client.aassess(
1608
+ address=signer.address,
1609
+ signer={"address": signer.address, "network": signer.network},
1610
+ )
1611
+ except (TokenExpiredError, InvalidCredentialError) as err:
1612
+ # 401 — credential issues map to invalid_credential. These are
1613
+ # unusual on the wallet-OFAC-only path (no operator_token) but
1614
+ # carried for parity with node's mapping.
1615
+ reason = DenialReason(
1616
+ code="invalid_credential" if isinstance(err, InvalidCredentialError) else "token_expired",
1617
+ message=str(err),
1618
+ )
1619
+ return CheckoutResult(
1620
+ status=denial_reason_status(reason),
1621
+ body=denial_reason_to_body(reason),
1622
+ headers={},
1623
+ reference_id=ctx.reference_id,
1624
+ settled=False,
1625
+ )
1626
+ except (AgentScoreError, Exception) as err:
1627
+ # 503 — API outage or network failure. Fail-closed: strict-liability.
1628
+ reason = DenialReason(code="api_error", message=str(err))
1629
+ return CheckoutResult(
1630
+ status=denial_reason_status(reason),
1631
+ body=denial_reason_to_body(reason),
1632
+ headers={},
1633
+ reference_id=ctx.reference_id,
1634
+ settled=False,
1635
+ )
1636
+
1637
+ decision = result.get("decision") if isinstance(result, dict) else None
1638
+ if decision == "deny":
1639
+ decision_reasons = result.get("decision_reasons") or [] if isinstance(result, dict) else []
1640
+ reason = DenialReason(
1641
+ code="wallet_not_trusted",
1642
+ reasons=list(decision_reasons),
1643
+ decision=decision,
1644
+ )
1645
+ return CheckoutResult(
1646
+ status=denial_reason_status(reason),
1647
+ body=denial_reason_to_body(reason),
1648
+ headers={},
1649
+ reference_id=ctx.reference_id,
1650
+ settled=False,
1651
+ )
1652
+ return None
1653
+
1516
1654
  def _coerce_run_gate_result(
1517
1655
  self,
1518
1656
  ctx: CheckoutContext,
@@ -1776,10 +1914,25 @@ class Checkout:
1776
1914
  )
1777
1915
  return await self._build_success(ctx, outcome)
1778
1916
  # _handle_mppx is only invoked when an ``Authorization: Payment`` header
1779
- # was present, so a 402 here means mppx REJECTED the credential. Surface
1780
- # as 400 ``payment_proof_invalid`` (the canonical "regenerate the
1781
- # credential" denial), echoing mppx's fresh WWW-Authenticate so the
1782
- # agent's retry signs against the new directive id.
1917
+ # was present, so a 402 here means mppx REJECTED the credential. Try to
1918
+ # classify the swallowed inner error (e.g. Tempo ``KeyNotFound``) into a
1919
+ # typed envelope agents can route on; fall back to the generic
1920
+ # ``payment_proof_invalid`` regenerate hint otherwise.
1921
+ classified = classify_mppx_failure(composed.failure_reason)
1922
+ if classified is not None:
1923
+ return CheckoutResult(
1924
+ status=classified.status,
1925
+ body=build_validation_error(
1926
+ code=classified.code,
1927
+ message=classified.message,
1928
+ next_steps=classified.next_steps,
1929
+ extra=classified.extra or None,
1930
+ ),
1931
+ headers=dict(composed.headers or {}),
1932
+ reference_id=ctx.reference_id,
1933
+ settled=False,
1934
+ settle_phase="verify_failed",
1935
+ )
1783
1936
  return CheckoutResult(
1784
1937
  status=400,
1785
1938
  body=build_validation_error(
@@ -1801,9 +1954,34 @@ class Checkout:
1801
1954
  if ctx.pricing is None:
1802
1955
  msg = "Checkout._emit_402: pricing not computed"
1803
1956
  raise RuntimeError(msg)
1804
- await self._resolve_recipients(ctx)
1957
+ try:
1958
+ await self._resolve_recipients(ctx)
1959
+ except CheckoutValidationError as err:
1960
+ return CheckoutResult(
1961
+ status=err.status,
1962
+ body=build_validation_error(
1963
+ code=err.code,
1964
+ message=err.message,
1965
+ next_steps={"action": err.action, "user_message": err.message},
1966
+ extra=err.extra,
1967
+ ),
1968
+ headers={},
1969
+ reference_id=ctx.reference_id,
1970
+ settled=False,
1971
+ settle_phase="mint_recipients_failed",
1972
+ )
1805
1973
  emit_rails = _apply_recipient_overrides(self.rails, ctx.recipients)
1806
1974
 
1975
+ # Auto-drop stripe when priced below Stripe's $0.50 USD minimum so the
1976
+ # emitted accepted_methods + how_to_pay stay consistent with what the
1977
+ # mppx compose layer will actually accept (see build_mppx_compose_rails).
1978
+ # Without this, the 402 body advertises a stripe rail that has no
1979
+ # matching WWW-Authenticate challenge — agents see it offered but any
1980
+ # SPT pay attempt fails. The compose-time auto-drop emits the
1981
+ # user-facing warn; here we just strip the slot from the discovery body.
1982
+ if Decimal(str(ctx.pricing.amount_usd)) < STRIPE_MIN_CHARGE_USD and "stripe" in emit_rails:
1983
+ emit_rails = {k: v for k, v in emit_rails.items() if k != "stripe"}
1984
+
1807
1985
  accepted = await build_accepted_methods(
1808
1986
  tempo=_pick(emit_rails, "tempo", TempoRailSpec),
1809
1987
  x402_base=_pick(emit_rails, "x402_base", X402BaseRailSpec),
@@ -1822,6 +2000,10 @@ class Checkout:
1822
2000
  total_usd=f"{ctx.pricing.amount_usd:.{pricing_decimals}f}",
1823
2001
  rails=how_to_pay_rails,
1824
2002
  decimals=pricing_decimals,
2003
+ # Merchants without an identity-bearing policy flag get clean commands
2004
+ # without an X-Operator-Token header — agents don't need one to satisfy
2005
+ # wallet OFAC enforcement (the always-on default per TEC-311).
2006
+ op_token_placeholder=None if not self._has_identity_gate() else "<your_opc_token>",
1825
2007
  )
1826
2008
  pricing_block = ctx.pricing.block or build_pricing_block(
1827
2009
  subtotal_cents=ctx.pricing.amount_usd * 100,
@@ -1871,7 +2053,10 @@ class Checkout:
1871
2053
  pricing=pricing_block,
1872
2054
  amount_usd=f"{ctx.pricing.amount_usd:.{pricing_decimals}f}",
1873
2055
  retry_body=ctx.request.body,
1874
- agent_memory=first_encounter_agent_memory(first_encounter=True),
2056
+ # Merchants without an identity-bearing gate get a clean 402: no
2057
+ # AgentScore-identity bootstrap describing a verification flow they
2058
+ # don't run. Wallet OFAC (the always-on default) doesn't need it.
2059
+ agent_memory=first_encounter_agent_memory(first_encounter=self._has_identity_gate()),
1875
2060
  product=ctx.pricing.product,
1876
2061
  extra=ctx.pricing.body_extras,
1877
2062
  x402=X402PaymentRequired(