solvapay-python 0.9.1__tar.gz → 0.9.3__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 (190) hide show
  1. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/ci.yml +11 -9
  2. solvapay_python-0.9.3/.osv-scanner.toml +6 -0
  3. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CHANGELOG.md +30 -0
  4. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/PKG-INFO +13 -3
  5. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/README.md +10 -2
  6. solvapay_python-0.9.3/docs/architecture/cold-start.md +70 -0
  7. solvapay_python-0.9.3/docs/architecture/layers.md +84 -0
  8. solvapay_python-0.9.3/docs/troubleshooting.md +225 -0
  9. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/mkdocs.yml +4 -1
  10. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/pyproject.toml +12 -2
  11. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/__init__.py +29 -2
  12. solvapay_python-0.9.3/tests/_baselines/cold_import.json +1 -0
  13. solvapay_python-0.9.3/tests/benchmarks/test_benchmarks.py +46 -0
  14. solvapay_python-0.9.3/tests/test_cold_import.py +92 -0
  15. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_invariants.py +32 -0
  16. solvapay_python-0.9.3/tests/test_layer_dag.py +35 -0
  17. solvapay_python-0.9.3/tests/webhooks/__init__.py +0 -0
  18. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tools/api_baseline.json +4 -0
  19. solvapay_python-0.9.3/tools/importlinter.cfg +32 -0
  20. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/uv.lock +44 -5
  21. solvapay_python-0.9.1/docs/architecture/layers.md +0 -46
  22. solvapay_python-0.9.1/tools/importlinter.cfg +0 -42
  23. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  24. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  25. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  26. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/dependabot.yml +0 -0
  28. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/contract.yml +0 -0
  29. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/docs.yml +0 -0
  30. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/publish.yml +0 -0
  31. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.gitignore +0 -0
  32. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.python-version +0 -0
  33. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CODEOWNERS +0 -0
  34. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CODE_OF_CONDUCT.md +0 -0
  35. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CONTRIBUTING.md +0 -0
  36. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/LICENSE +0 -0
  37. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/SECURITY.md +0 -0
  38. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/SUPPORT.md +0 -0
  39. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/assets/agent-marketplace.png +0 -0
  40. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/changelog.d/README.md +0 -0
  41. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/errors.md +0 -0
  42. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/guides/fastapi.md +0 -0
  43. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/guides/langchain.md +0 -0
  44. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/guides/mcp.md +0 -0
  45. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/idempotency.md +0 -0
  46. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/index.md +0 -0
  47. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/migration.md +0 -0
  48. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/client.md +0 -0
  49. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/exceptions.md +0 -0
  50. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/models.md +0 -0
  51. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/paywall.md +0 -0
  52. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/webhooks.md +0 -0
  53. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/rfcs/0001-spending-policy.md +0 -0
  54. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/rfcs/0002-openapi-investigation.md +0 -0
  55. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/webhooks.md +0 -0
  56. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/.env.example +0 -0
  57. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/.gitignore +0 -0
  58. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/README.md +0 -0
  59. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/claim.py +0 -0
  60. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/pyproject.toml +0 -0
  61. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/server.py +0 -0
  62. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/uv.lock +0 -0
  63. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/.env.example +0 -0
  64. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/.gitignore +0 -0
  65. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/README.md +0 -0
  66. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/agent.py +0 -0
  67. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/pyproject.toml +0 -0
  68. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/.env.example +0 -0
  69. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/.gitignore +0 -0
  70. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/.streamlit/config.toml +0 -0
  71. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/PLAN.md +0 -0
  72. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/README.md +0 -0
  73. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/agents.py +0 -0
  74. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/app.py +0 -0
  75. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/demo_customers.py +0 -0
  76. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/requirements.txt +0 -0
  77. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/sdk_gateway.py +0 -0
  78. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/ui_components.py +0 -0
  79. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/.env.example +0 -0
  80. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/.gitignore +0 -0
  81. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/README.md +0 -0
  82. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/agent_langchain.py +0 -0
  83. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/model.py +0 -0
  84. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/pyproject.toml +0 -0
  85. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/script_async.py +0 -0
  86. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/server_mcp.py +0 -0
  87. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/tool.py +0 -0
  88. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/uv.lock +0 -0
  89. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_async_client.py +0 -0
  90. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_config.py +0 -0
  91. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_http.py +0 -0
  92. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_stability.py +0 -0
  93. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/__init__.py +0 -0
  94. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/_recipe.py +0 -0
  95. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/httpx_transport.py +0 -0
  96. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/middleware.py +0 -0
  97. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/__init__.py +0 -0
  98. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/asgi.py +0 -0
  99. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/langchain.py +0 -0
  100. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/mcp.py +0 -0
  101. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/client.py +0 -0
  102. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/events.py +0 -0
  103. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/exceptions.py +0 -0
  104. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/fastapi.py +0 -0
  105. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/idempotency.py +0 -0
  106. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/langchain.py +0 -0
  107. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/models.py +0 -0
  108. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/__init__.py +0 -0
  109. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/_registry.py +0 -0
  110. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/checkout.py +0 -0
  111. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/customers.py +0 -0
  112. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/limits.py +0 -0
  113. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/merchant.py +0 -0
  114. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/plans.py +0 -0
  115. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/products.py +0 -0
  116. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/purchases.py +0 -0
  117. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/usage.py +0 -0
  118. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/__init__.py +0 -0
  119. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/core.py +0 -0
  120. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/decorators.py +0 -0
  121. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/meta.py +0 -0
  122. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/policy.py +0 -0
  123. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/resolvers.py +0 -0
  124. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/state.py +0 -0
  125. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall_state.py +0 -0
  126. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/py.typed +0 -0
  127. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/__init__.py +0 -0
  128. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/envelope.py +0 -0
  129. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/pipeline.py +0 -0
  130. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/replay.py +0 -0
  131. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/rotation.py +0 -0
  132. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/sign.py +0 -0
  133. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/verify.py +0 -0
  134. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/__init__.py +0 -0
  135. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_stability/__init__.py +0 -0
  136. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_stability/test_stable_returns_identity.py +0 -0
  137. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/__init__.py +0 -0
  138. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_aclose_cascade.py +0 -0
  139. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_error_wrapping.py +0 -0
  140. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_headers_case_insensitive.py +0 -0
  141. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_middleware_composition.py +0 -0
  142. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_protocol_conformance.py +0 -0
  143. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_recording_transport.py +0 -0
  144. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_retry_transport.py +0 -0
  145. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/__init__.py +0 -0
  146. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/test_asgi.py +0 -0
  147. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/test_langchain_protocol.py +0 -0
  148. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/test_mcp.py +0 -0
  149. {solvapay_python-0.9.1/tests/contract → solvapay_python-0.9.3/tests/benchmarks}/__init__.py +0 -0
  150. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/conftest.py +0 -0
  151. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/contract/README.md +0 -0
  152. {solvapay_python-0.9.1/tests/operations → solvapay_python-0.9.3/tests/contract}/__init__.py +0 -0
  153. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/contract/cassettes/.gitkeep +0 -0
  154. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/contract/test_contract_ops.py +0 -0
  155. {solvapay_python-0.9.1/tests/paywall → solvapay_python-0.9.3/tests/operations}/__init__.py +0 -0
  156. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/operations/test_namespace_api.py +0 -0
  157. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/operations/test_path_interpolation.py +0 -0
  158. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/operations/test_retry_safety_enum.py +0 -0
  159. {solvapay_python-0.9.1/tests/webhooks → solvapay_python-0.9.3/tests/paywall}/__init__.py +0 -0
  160. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
  161. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_payable_tool_meta.py +0 -0
  162. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_resolvers.py +0 -0
  163. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_split_classes.py +0 -0
  164. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_admin.py +0 -0
  165. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_api_version.py +0 -0
  166. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_async_client.py +0 -0
  167. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_checkout.py +0 -0
  168. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_config.py +0 -0
  169. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_customer.py +0 -0
  170. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_errors.py +0 -0
  171. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_http.py +0 -0
  172. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_idempotency.py +0 -0
  173. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_langchain.py +0 -0
  174. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_lifecycle.py +0 -0
  175. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_limits.py +0 -0
  176. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_packaging.py +0 -0
  177. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_paywall.py +0 -0
  178. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_paywall_state.py +0 -0
  179. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_redaction.py +0 -0
  180. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_webhook_events.py +0 -0
  181. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_webhook_timing.py +0 -0
  182. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_webhooks.py +0 -0
  183. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_async_cache.py +0 -0
  184. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
  185. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_rotation.py +0 -0
  186. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_seen_cache_atomic.py +0 -0
  187. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_sign.py +0 -0
  188. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_webhook_pipeline.py +0 -0
  189. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tools/api_diff.py +0 -0
  190. {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tools/lint_invariants.py +0 -0
@@ -19,15 +19,15 @@ jobs:
19
19
  - run: uv sync --all-extras --dev
20
20
  - name: bandit (high-severity, high-confidence only)
21
21
  run: uv run bandit -r src/ -lll
22
- - name: Install osv-scanner
23
- run: |
24
- curl -fsSL -o /tmp/osv-scanner.tar.gz \
25
- https://github.com/google/osv-scanner/releases/download/v1.9.2/osv-scanner_linux_amd64.tar.gz
26
- mkdir -p /tmp/osv && tar -xzf /tmp/osv-scanner.tar.gz -C /tmp/osv
27
- sudo mv /tmp/osv/osv-scanner /usr/local/bin/osv-scanner
28
- osv-scanner --version
29
22
  - name: osv-scanner (lockfile vuln DB cross-check)
30
- run: osv-scanner --lockfile=uv.lock
23
+ # PYSEC-2026-196: vuln in the GitHub runner's bundled pip, not in any
24
+ # solvapay-python dependency (pip is pulled into uv.lock transitively
25
+ # by build tooling). Ignore — runner image hygiene is GitHub's job.
26
+ run: |
27
+ docker run --rm -v "${PWD}:/src" -w /src \
28
+ ghcr.io/google/osv-scanner:latest \
29
+ scan source --lockfile=/src/uv.lock \
30
+ --config=/src/.osv-scanner.toml
31
31
 
32
32
 
33
33
  test:
@@ -66,7 +66,9 @@ jobs:
66
66
  - run: uv run lint-imports --config tools/importlinter.cfg
67
67
  - run: uv run python tools/lint_invariants.py
68
68
  - name: pip-audit
69
- run: uv run pip-audit
69
+ # PYSEC-2026-196: vuln in the GitHub runner's bundled pip (26.1.1, fixed in 26.1.2).
70
+ # Not a vuln in any solvapay-python dependency — runner-side hygiene only.
71
+ run: uv run pip-audit --ignore-vuln PYSEC-2026-196
70
72
  - name: Check changelog fragment on PRs touching src/
71
73
  if: github.event_name == 'pull_request'
72
74
  shell: bash
@@ -0,0 +1,6 @@
1
+ [[IgnoredVulns]]
2
+ id = "PYSEC-2026-196"
3
+ # pip 26.1.1 vuln, fixed in 26.1.2. Pip lands in uv.lock transitively via build
4
+ # tooling on the GitHub runner — not a solvapay-python runtime/optional dep.
5
+ # Runner image hygiene is GitHub's responsibility.
6
+ reason = "Runner-side pip — not a project dependency"
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.3] — 2026-06-07
4
+
5
+ ### Features
6
+
7
+ - **Layer DAG now hard-enforced via single layered `import-linter` contract.** `tools/importlinter.cfg` replaces three sparse `forbidden` contracts with one `type = layers` contract covering the full HLD §V1.1 DAG (`_transport → _http → models → operations → client → paywall|webhooks → adapters → fastapi|langchain`). Closes the L2 `models` purity gap and the L5 `paywall`/`webhooks` framework-neutrality gap. `exhaustive = true` flags any new top-level module that ships without an explicit layer assignment. `ignore_type_checking_imports = true` strips `TYPE_CHECKING` blocks from the graph so cross-layer type hints do not trip the gate. CI-only change; no public-API impact.
8
+
9
+
10
+ ## [0.9.2] — 2026-06-06
11
+
12
+ ### Features
13
+
14
+ - ``solvapay.sign_webhook`` promoted to top-level namespace (alongside ``verify_webhook``), closing the inconsistency flagged in v0.9.0 deviation #3. The existing ``solvapay.webhooks.sign_webhook`` path is unchanged.
15
+
16
+ Cold-import baseline harness at ``tests/test_cold_import.py``: measures ``python -X importtime`` cumulative time and asserts within 1.5x of a committed baseline. The hard <200 ms CI gate is a v1.0 feature (HLD §V1.20).
17
+
18
+ Microbenchmark harness at ``tests/benchmarks/``: measures ``SolvaPay()`` construction, ``verify_webhook`` per-call, and ``from_payload`` derivation. Opt-in via ``solvapay[bench]`` extra. Excluded from default pytest run.
19
+
20
+ ### Documentation
21
+
22
+ - ``docs/troubleshooting.md``: top-10 user errors with symptom, cause, and fix. Covers missing env vars, async-in-sync deadlock, missing extras, contract test skips, Windows paths, deprecation warnings, paywall checkout URL, ``verify_webhook`` errors, mypy mock patterns, and PyPI dist-name confusion.
23
+
24
+ ``docs/architecture/cold-start.md``: explains the ``python -X importtime`` baseline harness, PEP 562 lazy adapter import pattern, and the v1.0 <150 ms cold-start budget (HLD §V1.20).
25
+
26
+ ### Performance
27
+
28
+ - PEP 562 lazy adapter imports: bare ``import solvapay`` no longer loads ``fastmcp``, ``langchain-core``, or ``fastapi``. Framework adapter modules load on first attribute access only. ``solvapay.adapters`` is accessible via ``__getattr__`` and appears in ``dir(solvapay)``.
29
+
30
+
31
+ ---
32
+
3
33
  ## [0.9.1] — 2026-06-05
4
34
 
5
35
  Security & supply-chain quality patch (no public API change).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solvapay-python
3
- Version: 0.9.1
3
+ Version: 0.9.3
4
4
  Summary: Community Python SDK for SolvaPay (agent-native payment rails)
5
5
  Project-URL: Homepage, https://github.com/dhruv-sanan/solvapay-python
6
6
  Project-URL: Issues, https://github.com/dhruv-sanan/solvapay-python/issues
@@ -24,6 +24,8 @@ Requires-Python: >=3.10
24
24
  Requires-Dist: httpx<0.29,>=0.27
25
25
  Requires-Dist: pydantic<2.14,>=2.6
26
26
  Provides-Extra: asgi
27
+ Provides-Extra: bench
28
+ Requires-Dist: pytest-benchmark<6,>=4.0; extra == 'bench'
27
29
  Provides-Extra: fastapi
28
30
  Requires-Dist: fastapi>=0.110; extra == 'fastapi'
29
31
  Provides-Extra: langchain
@@ -206,10 +208,11 @@ envelope = pipeline.process(body=request.body, signature=request.headers["sv-sig
206
208
  pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
207
209
  ```
208
210
 
209
- **Sign a webhook** (testing / outbound fanout):
211
+ **Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
210
212
 
211
213
  ```python
212
- from solvapay.webhooks import sign_webhook
214
+ from solvapay import sign_webhook # top-level (v0.9.2+)
215
+ # from solvapay.webhooks import sign_webhook # subpackage path also works
213
216
 
214
217
  header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
215
218
  # → "t=1716470000,v1=abc123..."
@@ -311,6 +314,7 @@ pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-c
311
314
  pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
312
315
  pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
313
316
  pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
317
+ pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
314
318
  ```
315
319
 
316
320
  ## Environment variables
@@ -354,12 +358,18 @@ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
354
358
  | v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
355
359
  | v0.9 ✅ | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, ASGI adapter, secret rotation, `sign_webhook`, contract tests, lint automation, MkDocs site, supply-chain hygiene |
356
360
  | v0.9.1 ✅ | Security & supply-chain quality — PyPI attestations (PEP 740 / Sigstore), CycloneDX SBOM on releases, `bandit` + `osv-scanner` CI, Hypothesis-driven secret-redaction property tests, constant-time `verify_webhook` smoke test, explicit PCI-scope statement |
361
+ | v0.9.2 ✅ | Performance & stability quality — cold-import baseline harness, PEP 562 lazy adapter imports, `solvapay.sign_webhook` top-level re-export, microbenchmark harness (`solvapay[bench]`), troubleshooting docs |
362
+ | v0.9.3 ✅ | Layer DAG hard-enforcement — single `type = layers` `import-linter` contract replaces three sparse `forbidden` contracts; closes the L2 `models` purity gap and the L5 `paywall`/`webhooks` framework-neutrality gap; `exhaustive = true` flags any unlayered top-level module; `ignore_type_checking_imports = true` keeps cross-layer type hints lint-clean |
357
363
  | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
358
364
 
359
365
  ---
360
366
 
361
367
  ## Status
362
368
 
369
+ **v0.9.3** — Layer DAG hard-enforcement quality patch. `tools/importlinter.cfg` now ships a single `type = layers` contract covering the full HLD §V1.1 DAG (`_transport → _http → models → operations → client → paywall|webhooks → adapters → fastapi|langchain`), replacing three sparse `forbidden` contracts. `exhaustive = true` flags any new top-level module that ships without an explicit layer assignment; `ignore_type_checking_imports = true` strips `TYPE_CHECKING` blocks from the dependency graph so cross-layer type hints do not trip the gate. New `tests/test_layer_dag.py` shells the contract from pytest as a belt-and-suspenders gate. `docs/architecture/layers.md` refreshed. CI-only change; no public-API impact. 306 tests. 87% line coverage. `mypy --strict` clean (45 files).
370
+
371
+ **v0.9.2** — Performance & stability quality patch. Cold-import baseline harness (`tests/test_cold_import.py`, 1.5x regression gate). PEP 562 lazy adapter imports — bare `import solvapay` no longer loads framework adapter modules. `solvapay.sign_webhook` promoted to top-level (alongside `verify_webhook`). Microbenchmark harness under `tests/benchmarks/` (opt-in `solvapay[bench]`). New docs: `docs/troubleshooting.md` (top-10 errors) and `docs/architecture/cold-start.md`. 305 tests. 87% line coverage. `mypy --strict` clean (45 files).
372
+
363
373
  **v0.9.1** — Security & supply-chain quality patch. PyPI uploads now carry PEP 740 attestations (Sigstore-backed via OIDC trusted publishing); each GitHub release ships a CycloneDX SBOM (`sbom.cdx.json`). `mypy --strict` clean (45 files). 302 tests. 87% line / 85% branch coverage.
364
374
 
365
375
  **Supply chain & security posture:**
@@ -170,10 +170,11 @@ envelope = pipeline.process(body=request.body, signature=request.headers["sv-sig
170
170
  pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
171
171
  ```
172
172
 
173
- **Sign a webhook** (testing / outbound fanout):
173
+ **Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
174
174
 
175
175
  ```python
176
- from solvapay.webhooks import sign_webhook
176
+ from solvapay import sign_webhook # top-level (v0.9.2+)
177
+ # from solvapay.webhooks import sign_webhook # subpackage path also works
177
178
 
178
179
  header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
179
180
  # → "t=1716470000,v1=abc123..."
@@ -275,6 +276,7 @@ pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-c
275
276
  pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
276
277
  pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
277
278
  pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
279
+ pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
278
280
  ```
279
281
 
280
282
  ## Environment variables
@@ -318,12 +320,18 @@ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
318
320
  | v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
319
321
  | v0.9 ✅ | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, ASGI adapter, secret rotation, `sign_webhook`, contract tests, lint automation, MkDocs site, supply-chain hygiene |
320
322
  | v0.9.1 ✅ | Security & supply-chain quality — PyPI attestations (PEP 740 / Sigstore), CycloneDX SBOM on releases, `bandit` + `osv-scanner` CI, Hypothesis-driven secret-redaction property tests, constant-time `verify_webhook` smoke test, explicit PCI-scope statement |
323
+ | v0.9.2 ✅ | Performance & stability quality — cold-import baseline harness, PEP 562 lazy adapter imports, `solvapay.sign_webhook` top-level re-export, microbenchmark harness (`solvapay[bench]`), troubleshooting docs |
324
+ | v0.9.3 ✅ | Layer DAG hard-enforcement — single `type = layers` `import-linter` contract replaces three sparse `forbidden` contracts; closes the L2 `models` purity gap and the L5 `paywall`/`webhooks` framework-neutrality gap; `exhaustive = true` flags any unlayered top-level module; `ignore_type_checking_imports = true` keeps cross-layer type hints lint-clean |
321
325
  | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
322
326
 
323
327
  ---
324
328
 
325
329
  ## Status
326
330
 
331
+ **v0.9.3** — Layer DAG hard-enforcement quality patch. `tools/importlinter.cfg` now ships a single `type = layers` contract covering the full HLD §V1.1 DAG (`_transport → _http → models → operations → client → paywall|webhooks → adapters → fastapi|langchain`), replacing three sparse `forbidden` contracts. `exhaustive = true` flags any new top-level module that ships without an explicit layer assignment; `ignore_type_checking_imports = true` strips `TYPE_CHECKING` blocks from the dependency graph so cross-layer type hints do not trip the gate. New `tests/test_layer_dag.py` shells the contract from pytest as a belt-and-suspenders gate. `docs/architecture/layers.md` refreshed. CI-only change; no public-API impact. 306 tests. 87% line coverage. `mypy --strict` clean (45 files).
332
+
333
+ **v0.9.2** — Performance & stability quality patch. Cold-import baseline harness (`tests/test_cold_import.py`, 1.5x regression gate). PEP 562 lazy adapter imports — bare `import solvapay` no longer loads framework adapter modules. `solvapay.sign_webhook` promoted to top-level (alongside `verify_webhook`). Microbenchmark harness under `tests/benchmarks/` (opt-in `solvapay[bench]`). New docs: `docs/troubleshooting.md` (top-10 errors) and `docs/architecture/cold-start.md`. 305 tests. 87% line coverage. `mypy --strict` clean (45 files).
334
+
327
335
  **v0.9.1** — Security & supply-chain quality patch. PyPI uploads now carry PEP 740 attestations (Sigstore-backed via OIDC trusted publishing); each GitHub release ships a CycloneDX SBOM (`sbom.cdx.json`). `mypy --strict` clean (45 files). 302 tests. 87% line / 85% branch coverage.
328
336
 
329
337
  **Supply chain & security posture:**
@@ -0,0 +1,70 @@
1
+ # Cold-Start Performance
2
+
3
+ ## What we measure
4
+
5
+ `python -X importtime -c "import solvapay"` prints a line per imported module:
6
+
7
+ ```
8
+ import time: <self_µs> | <cumulative_µs> | <module_name>
9
+ ```
10
+
11
+ The `cumulative_µs` column for the root `solvapay` module is the total cold-import cost — the number we track.
12
+
13
+ ## The baseline harness (`tests/test_cold_import.py`)
14
+
15
+ Introduced in v0.9.2, `tests/test_cold_import.py` runs the above command via `subprocess` and:
16
+
17
+ 1. **First run** — writes `tests/_baselines/cold_import.json` with the measured value.
18
+ 2. **Subsequent runs** — asserts the current measurement is within **1.5×** of the baseline.
19
+
20
+ The 1.5× regression factor is intentionally loose — it catches gross regressions (importing a new heavy dependency eagerly) without failing on normal M-series vs CI machine variance.
21
+
22
+ The committed baseline is re-measured and updated whenever a structural change affects import cost (e.g. lazy adapter imports in v0.9.2).
23
+
24
+ ### Current baseline
25
+
26
+ | Version | M-series (macOS) | What changed |
27
+ |---------|-----------------|--------------|
28
+ | v0.9.1 | ~88 ms | Initial measurement |
29
+ | v0.9.2 | ~100 ms | PEP 562 `__getattr__` + `__dir__` added; adapters lazy |
30
+
31
+ ## PEP 562 lazy adapter imports
32
+
33
+ Adapter modules (`solvapay.adapters.mcp`, `solvapay.adapters.langchain`, etc.) depend on optional heavy packages — `fastmcp`, `langchain-core`, `fastapi`. These are only installed when the user runs `pip install solvapay-python[mcp]` etc.
34
+
35
+ Before v0.9.2, accessing `solvapay.adapters` required an explicit subpackage import. In v0.9.2, `solvapay/__init__.py` defines a PEP 562 `__getattr__` that loads `solvapay.adapters` lazily on first attribute access:
36
+
37
+ ```python
38
+ # In solvapay/__init__.py
39
+ _LAZY_MODULES = {"adapters": "solvapay.adapters"}
40
+
41
+ def __getattr__(name: str) -> object:
42
+ if name in _LAZY_MODULES:
43
+ import importlib
44
+ mod = importlib.import_module(_LAZY_MODULES[name])
45
+ globals()[name] = mod # cache for subsequent accesses
46
+ return mod
47
+ raise AttributeError(f"module 'solvapay' has no attribute {name!r}")
48
+
49
+ def __dir__() -> list[str]:
50
+ return sorted(set(globals()) | set(_LAZY_MODULES))
51
+ ```
52
+
53
+ This ensures:
54
+ - `import solvapay` — **zero adapter cost** regardless of which extras are installed.
55
+ - `solvapay.adapters` — triggers a single load on first access, cached thereafter.
56
+ - `from solvapay.adapters import mcp` — works via Python's normal subpackage import path (unaffected by `__getattr__`).
57
+ - `dir(solvapay)` — lists `"adapters"` per PEP 562 convention.
58
+
59
+ ## v1.0 hard budget (HLD §V1.20)
60
+
61
+ v0.9.2 ships **measurement only**. The hard gate is a v1.0 feature:
62
+
63
+ | Environment | Budget |
64
+ |-------------|--------|
65
+ | M-series Mac | < 150 ms |
66
+ | x86 CI | < 300 ms |
67
+
68
+ v1.0 achieves this primarily via **`@cached_property` lazy namespace materialization** — `SolvaPay.__init__` will no longer eagerly construct `customers`, `checkout`, `limits`, etc. Each namespace is constructed on first access (`sv.customers.ensure(...)`) and cached as an instance attribute. This eliminates the linear startup cost as V2/V3 expand the namespace count.
69
+
70
+ See `HLD.md §V1.3 RN1-v2` and `§V1.20` for the full contract.
@@ -0,0 +1,84 @@
1
+ # Layer Architecture (HLD V1.1)
2
+
3
+ The SDK is organized into strict layers. Each layer may only import from layers below it.
4
+ Violations fail CI via `import-linter` (see `tools/importlinter.cfg`).
5
+
6
+ From **v0.9.3** the DAG is enforced as a single `type = layers` contract, replacing the
7
+ earlier three sparse `forbidden` contracts. `exhaustive = true` flags any new top-level
8
+ module that ships without an explicit layer assignment.
9
+
10
+ ## Layer DAG
11
+
12
+ ```
13
+ L7 solvapay.fastapi — Top-level adapter shims (re-export from L6.adapters.*)
14
+ solvapay.langchain
15
+ L6 solvapay.adapters — Framework adapters (MCP, LangChain, ASGI, FastAPI)
16
+ L5 solvapay.paywall — Paywall / AsyncPaywall primitives
17
+ solvapay.webhooks — Webhook verify + pipeline
18
+ L4 solvapay.client — SolvaPay / AsyncSolvaPay public facades
19
+ solvapay._async_client
20
+ L3 solvapay.operations — Resource namespace operations (sv.customers.ensure etc.)
21
+ L2 solvapay.models — Pydantic request/response models
22
+ L1 solvapay._http — HTTP-client wrapper around the transport kernel
23
+ L0 solvapay._transport — Transport Protocol + httpx impl + middleware
24
+ ```
25
+
26
+ > **Note.** The HLD ships an aspirational L1 `_wire` slot that is currently unused.
27
+ > The shipped layout uses L1 for `_http` (the existing transport-kernel wrapper).
28
+ > When `_wire` ships, it will sit between `_transport` and `_http`.
29
+
30
+ ## Allowed imports
31
+
32
+ A module at L_n may import from any module at L_(n-1) or below, plus the layer-independent
33
+ leaf modules (`exceptions`, `_stability`, `_config`, `idempotency`, `events`, `paywall_state`).
34
+
35
+ | Layer | May import |
36
+ |-------|------------|
37
+ | L0 `_transport` | stdlib, httpx, leaf modules |
38
+ | L1 `_http` | L0 + leaf modules |
39
+ | L2 `models` | stdlib, pydantic, leaf modules |
40
+ | L3 `operations` | L0–L2 + leaf modules |
41
+ | L4 `client` / `_async_client` | L0–L3 + leaf modules |
42
+ | L5 `paywall` / `webhooks` | L0–L4 + leaf modules |
43
+ | L6 `adapters` | L0–L5 + leaf modules |
44
+ | L7 `fastapi` / `langchain` (top-level shims) | L0–L6 + leaf modules |
45
+ | `tests/` | everything (permissive; tests live outside the `solvapay` container) |
46
+
47
+ Sibling modules on the same row (e.g. `paywall` ↔ `webhooks`, or `client` ↔ `_async_client`)
48
+ may not import each other. Each is independently constrained against the layers below.
49
+
50
+ ## Why L7 = stability tier, not function (DG2 lock)
51
+
52
+ `solvapay.experimental` (reserved for v1.0) groups features by **stability**, not by
53
+ domain. A webhook adapter that is still unstable lives in L6 (adapters) while it's being
54
+ proven, then graduates to the canonical path once stable. This prevents the
55
+ `experimental` namespace from becoming a permanent home for half-finished features.
56
+
57
+ ## Leaf modules (layer-independent)
58
+
59
+ These are listed in `exhaustive_ignores` in the contract because they have no upward edges
60
+ and no downward consumers beyond the boundary primitives:
61
+
62
+ - `solvapay.exceptions`
63
+ - `solvapay._stability`
64
+ - `solvapay._config`
65
+ - `solvapay.idempotency`
66
+ - `solvapay.events`
67
+ - `solvapay.paywall_state`
68
+
69
+ ## TYPE_CHECKING imports
70
+
71
+ The contract sets `ignore_type_checking_imports = true`. Imports inside `if TYPE_CHECKING:`
72
+ blocks are stripped from the dependency graph, so cross-layer type hints (e.g.
73
+ `from solvapay.client import SolvaPay` for annotation purposes) do not trip the gate.
74
+ `mypy` still resolves these via the standard `from __future__ import annotations`
75
+ pattern enforced by `tools/lint_invariants.py`.
76
+
77
+ ## Enforcement
78
+
79
+ ```bash
80
+ ~/.local/bin/uv run lint-imports --config tools/importlinter.cfg
81
+ ```
82
+
83
+ Runs in CI on every push and PR. Also wrapped by `tests/test_layer_dag.py` so the
84
+ contract is exercised by `pytest` for local feedback.
@@ -0,0 +1,225 @@
1
+ # Troubleshooting
2
+
3
+ Common errors and how to fix them.
4
+
5
+ ---
6
+
7
+ ## 1. `AuthenticationError` — API key not set
8
+
9
+ **Symptom:**
10
+ ```
11
+ solvapay.exceptions.AuthenticationError: No API key provided.
12
+ ```
13
+
14
+ **Cause:** `SOLVAPAY_SECRET_KEY` env var is not set and no `api_key=` argument was passed.
15
+
16
+ **Fix:**
17
+ ```bash
18
+ export SOLVAPAY_SECRET_KEY=sk_sandbox_...
19
+ ```
20
+ Or pass it explicitly:
21
+ ```python
22
+ sv = SolvaPay(api_key="sk_sandbox_...")
23
+ ```
24
+
25
+ ---
26
+
27
+ ## 2. Async-in-sync deadlock
28
+
29
+ **Symptom:**
30
+ ```
31
+ RuntimeError: cannot run event loop while another event loop is running
32
+ ```
33
+ Or a call to `await sv.customers.ensure(...)` hangs indefinitely.
34
+
35
+ **Cause:** Mixing `AsyncSolvaPay` with sync framework code, or running async SDK methods from a Jupyter notebook without `await`.
36
+
37
+ **Fix:** Use `SolvaPay` (sync) for sync frameworks (FastAPI route handlers are async — use `AsyncSolvaPay` there). In Jupyter, `await` the call directly or use `asyncio.run()` in a standalone script.
38
+
39
+ ```python
40
+ # FastAPI — use AsyncSolvaPay
41
+ @app.get("/check")
42
+ async def check(customer_ref: str):
43
+ async with AsyncSolvaPay() as sv:
44
+ result = await sv.limits.acheck(customer_ref=customer_ref, product_ref="prd_x")
45
+ return result
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 3. `ModuleNotFoundError` — missing optional extra
51
+
52
+ **Symptom:**
53
+ ```
54
+ ModuleNotFoundError: No module named 'fastmcp'
55
+ ModuleNotFoundError: No module named 'langchain_core'
56
+ ModuleNotFoundError: No module named 'tenacity'
57
+ ```
58
+
59
+ **Cause:** Optional adapter or middleware not installed.
60
+
61
+ **Fix:** Install the relevant extra:
62
+ ```bash
63
+ pip install solvapay-python[mcp] # fastmcp
64
+ pip install solvapay-python[langchain] # langchain-core
65
+ pip install solvapay-python[retry] # tenacity (RetryTransport)
66
+ pip install solvapay-python[fastapi] # fastapi
67
+ pip install solvapay-python[bench] # pytest-benchmark (dev only)
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 4. Contract tests skip silently
73
+
74
+ **Symptom:**
75
+ ```
76
+ 3 skipped — SOLVAPAY_SANDBOX_KEY not set
77
+ ```
78
+
79
+ **Cause:** Sandbox tests require a live sandbox key and are intentionally skipped in CI unless the secret is configured.
80
+
81
+ **Fix:** For local sandbox testing:
82
+ ```bash
83
+ export SOLVAPAY_SANDBOX_KEY=sk_sandbox_...
84
+ export SOLVAPAY_TEST_CUSTOMER_REF=cus_...
85
+ export SOLVAPAY_TEST_PRODUCT_REF=prd_...
86
+ uv run pytest -m contract
87
+ ```
88
+
89
+ ---
90
+
91
+ ## 5. Windows virtualenv / `uv` path issues
92
+
93
+ **Symptom:**
94
+ ```
95
+ FileNotFoundError: [WinError 2] The system cannot find the file specified
96
+ ```
97
+ Or `uv run` fails to find the installed package.
98
+
99
+ **Cause:** Windows uses different path separators and virtualenv activation conventions.
100
+
101
+ **Fix:** Use `uv` from the project root — it manages the `.venv` automatically:
102
+ ```bat
103
+ cd path\to\solvapay-python
104
+ uv sync --all-extras --dev
105
+ uv run pytest
106
+ ```
107
+ Do not manually activate the venv; let `uv run` handle it.
108
+
109
+ ---
110
+
111
+ ## 6. `DeprecationWarning: Flat method deprecated` in test output
112
+
113
+ **Symptom:**
114
+ ```
115
+ DeprecationWarning: Flat method deprecated. Use sv.customers.ensure(...) instead.
116
+ ```
117
+
118
+ **Cause:** Old flat-method API (e.g. `sv.ensure_customer(...)`) is still being called. These shims emit a `DeprecationWarning` on every call and will be removed in v2.0.
119
+
120
+ **Fix:** Migrate to the namespace API:
121
+ ```python
122
+ # Old (deprecated)
123
+ sv.ensure_customer("ext_ref")
124
+
125
+ # New (stable)
126
+ sv.customers.ensure("ext_ref")
127
+ ```
128
+
129
+ To silence the warning in tests that still use the old path intentionally, add to `pyproject.toml`:
130
+ ```toml
131
+ filterwarnings = ["ignore:Flat method deprecated:DeprecationWarning"]
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 7. `PaywallRequired` raised with `checkout_url=None`
137
+
138
+ **Symptom:**
139
+ ```python
140
+ except PaywallRequired as e:
141
+ print(e.checkout_url) # None — no redirect URL available
142
+ ```
143
+
144
+ **Cause:** In v0.8/v0.9, the SDK does NOT automatically call `checkout.create_session()` on every gate hit. Auto-minting is triggered only when the `LimitResponse` from the server includes a `checkoutUrl` field.
145
+
146
+ **Fix:** Use `paywall_state.gate()` for a fully enriched decision, or construct the checkout URL yourself:
147
+ ```python
148
+ from solvapay.paywall_state import gate
149
+
150
+ decision = gate(sv, customer_ref=ref, product_ref="prd_x")
151
+ if decision.state != PaywallState.OK:
152
+ redirect_to(decision.checkout_url)
153
+ ```
154
+
155
+ Note: in v1.0 this behavior changes — see `HLD.md §V1.6 PW4`.
156
+
157
+ ---
158
+
159
+ ## 8. `verify_webhook` raises `ValueError`
160
+
161
+ **Symptom:**
162
+ ```
163
+ ValueError: No valid signature found
164
+ ValueError: Request timestamp too old
165
+ ```
166
+
167
+ **Cause:**
168
+ - Wrong secret (key rotation in progress, or `SOLVAPAY_WEBHOOK_SECRET` not matching the SolvaPay dashboard secret).
169
+ - System clock drift exceeding `tolerance` (default 300 seconds).
170
+
171
+ **Fix:**
172
+ ```python
173
+ # Check your secret matches the SolvaPay dashboard exactly.
174
+ verify_webhook(body=raw_body, signature=header, secret=os.environ["SOLVAPAY_WEBHOOK_SECRET"])
175
+
176
+ # For testing with sign_webhook, pass a recent timestamp:
177
+ from solvapay.webhooks import sign_webhook
178
+ sig = sign_webhook(body, secret, timestamp=int(time.time()))
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 9. `mypy --strict` errors in paywall tests
184
+
185
+ **Symptom:**
186
+ ```
187
+ error: Argument "client" to "Paywall" has incompatible type "MagicMock"; expected "SolvaPay"
188
+ ```
189
+
190
+ **Cause:** `MagicMock(spec=SolvaPay)` does not know about instance attrs like `.limits`, `.customers` set in `__init__`. The spec reflects the class, not the instance.
191
+
192
+ **Fix:** Use plain `MagicMock()` without `spec=`:
193
+ ```python
194
+ # WRONG
195
+ client = MagicMock(spec=SolvaPay) # AttributeError on .limits
196
+
197
+ # CORRECT
198
+ client = MagicMock()
199
+ client.limits.check.return_value = LimitResponse(within_limits=True, remaining=5)
200
+ ```
201
+
202
+ Add `# type: ignore[arg-type]` on the `Paywall(client=...)` line to satisfy mypy strict.
203
+
204
+ ---
205
+
206
+ ## 10. `pip install solvapay` vs `pip install solvapay-python`
207
+
208
+ **Symptom:**
209
+ ```
210
+ ERROR: No matching distribution found for solvapay
211
+ ```
212
+ Or: installed `solvapay` but `from solvapay import SolvaPay` is empty / old.
213
+
214
+ **Cause:** The distribution is named `solvapay-python` on PyPI (avoids namespace conflict before official adoption). The import name is still `solvapay`.
215
+
216
+ **Fix:**
217
+ ```bash
218
+ pip install solvapay-python # correct
219
+ pip install solvapay-python[mcp] # with MCP adapter
220
+ ```
221
+
222
+ The import surface is unchanged:
223
+ ```python
224
+ from solvapay import SolvaPay, AsyncSolvaPay
225
+ ```
@@ -33,7 +33,9 @@ nav:
33
33
  - reference/models.md
34
34
  - reference/webhooks.md
35
35
  - reference/paywall.md
36
- - Architecture: architecture/layers.md
36
+ - Architecture:
37
+ - Layers: architecture/layers.md
38
+ - Cold-Start: architecture/cold-start.md
37
39
  - Guides:
38
40
  - MCP: guides/mcp.md
39
41
  - LangChain: guides/langchain.md
@@ -42,3 +44,4 @@ nav:
42
44
  - Idempotency: idempotency.md
43
45
  - Webhooks: webhooks.md
44
46
  - Migration: migration.md
47
+ - Troubleshooting: troubleshooting.md
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "solvapay-python"
7
- version = "0.9.1"
7
+ version = "0.9.3"
8
8
  description = "Community Python SDK for SolvaPay (agent-native payment rails)"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -39,6 +39,7 @@ langchain = ["langchain-core>=0.3,<1.5"]
39
39
  mcp = ["fastmcp>=3.2.0,<4"]
40
40
  asgi = [] # raw ASGI; no extra deps required
41
41
  retry = ["tenacity>=8.2,<10"]
42
+ bench = ["pytest-benchmark>=4.0,<6"]
42
43
 
43
44
  [project.urls]
44
45
  Homepage = "https://github.com/dhruv-sanan/solvapay-python"
@@ -94,7 +95,11 @@ ignore_missing_imports = true
94
95
  [tool.pytest.ini_options]
95
96
  testpaths = ["tests"]
96
97
  asyncio_mode = "auto"
97
- markers = ["contract: sandbox contract tests (require SOLVAPAY_SANDBOX_KEY)"]
98
+ markers = [
99
+ "contract: sandbox contract tests (require SOLVAPAY_SANDBOX_KEY)",
100
+ "benchmark: microbenchmarks — run with: uv run pytest tests/benchmarks/ --benchmark-only",
101
+ ]
102
+ addopts = "--ignore=tests/benchmarks"
98
103
  filterwarnings = [
99
104
  # Flat shim methods intentionally emit DeprecationWarning (removed in v2.0).
100
105
  # stacklevel=2 makes the warning appear from the test file, so module filter
@@ -130,3 +135,8 @@ showcontent = true
130
135
  directory = "removal"
131
136
  name = "Removals"
132
137
  showcontent = true
138
+
139
+ [[tool.towncrier.type]]
140
+ directory = "performance"
141
+ name = "Performance"
142
+ showcontent = true
@@ -37,7 +37,7 @@ from solvapay.exceptions import (
37
37
  )
38
38
  from solvapay.models import BalanceResponse, Merchant, Plan, PlatformConfig, Product
39
39
  from solvapay.paywall import PaywallRequired
40
- from solvapay.webhooks import verify_webhook
40
+ from solvapay.webhooks import sign_webhook, verify_webhook
41
41
 
42
42
  # Register stable exports in MANIFEST (HLD V1.2).
43
43
  # stable(X) returns X unchanged — isinstance() continues to work (HLD SM1).
@@ -54,6 +54,7 @@ stable(APIServerError)
54
54
  stable(APIConnectionError)
55
55
  stable(APITimeoutError)
56
56
  stable(PaywallRequired)
57
+ stable(sign_webhook)
57
58
  stable(verify_webhook)
58
59
  stable(BalanceResponse)
59
60
  stable(Product)
@@ -99,10 +100,36 @@ __all__ = [
99
100
  "SolvaPayAPIError",
100
101
  "SolvaPayError",
101
102
  "WebhookEvent",
103
+ # adapters exposed lazily via __getattr__ below (PEP 562)
104
+ "adapters",
102
105
  "deprecated",
103
106
  "experimental",
104
107
  "paywall",
108
+ "sign_webhook",
105
109
  "stable",
106
110
  "verify_webhook",
107
111
  ]
108
- __version__ = "0.9.1"
112
+ __version__ = "0.9.3"
113
+
114
+ # PEP 562 — lazy adapter submodule access.
115
+ # Adapters drag in optional heavy deps (fastmcp, langchain-core, fastapi).
116
+ # __getattr__ defers their load until first access; they never load on bare
117
+ # `import solvapay` for users who don't use framework adapters.
118
+ _LAZY_MODULES = {
119
+ "adapters": "solvapay.adapters",
120
+ }
121
+
122
+
123
+ def __getattr__(name: str) -> object:
124
+ if name in _LAZY_MODULES:
125
+ import importlib
126
+
127
+ mod = importlib.import_module(_LAZY_MODULES[name])
128
+ globals()[name] = mod
129
+ return mod
130
+ raise AttributeError(f"module 'solvapay' has no attribute {name!r}")
131
+
132
+
133
+ def __dir__() -> list[str]:
134
+ # PEP 562: include lazy names alongside normal module attrs.
135
+ return sorted(set(globals()) | set(_LAZY_MODULES))
@@ -0,0 +1 @@
1
+ {"darwin": 99.82}