solvapay-python 0.9.0__tar.gz → 0.9.2__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 (188) hide show
  1. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/workflows/ci.yml +33 -3
  2. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/workflows/contract.yml +2 -2
  3. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/workflows/docs.yml +2 -2
  4. solvapay_python-0.9.2/.github/workflows/publish.yml +32 -0
  5. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.gitignore +1 -0
  6. solvapay_python-0.9.2/.osv-scanner.toml +6 -0
  7. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CHANGELOG.md +44 -0
  8. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/PKG-INFO +73 -11
  9. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/README.md +66 -6
  10. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/SECURITY.md +15 -2
  11. solvapay_python-0.9.2/docs/architecture/cold-start.md +70 -0
  12. solvapay_python-0.9.2/docs/troubleshooting.md +225 -0
  13. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/uv.lock +128 -122
  14. solvapay_python-0.9.2/examples/multi-framework-paywall/uv.lock +1585 -0
  15. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/mkdocs.yml +4 -1
  16. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/pyproject.toml +23 -7
  17. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/__init__.py +29 -2
  18. solvapay_python-0.9.2/tests/_baselines/cold_import.json +1 -0
  19. solvapay_python-0.9.2/tests/benchmarks/test_benchmarks.py +46 -0
  20. solvapay_python-0.9.2/tests/test_cold_import.py +66 -0
  21. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_invariants.py +32 -0
  22. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_redaction.py +37 -0
  23. solvapay_python-0.9.2/tests/test_webhook_timing.py +82 -0
  24. solvapay_python-0.9.2/tests/webhooks/__init__.py +0 -0
  25. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/api_baseline.json +4 -0
  26. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/uv.lock +689 -36
  27. solvapay_python-0.9.0/.github/workflows/publish.yml +0 -15
  28. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  29. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  30. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  31. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  32. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/dependabot.yml +0 -0
  33. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.python-version +0 -0
  34. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CODEOWNERS +0 -0
  35. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CODE_OF_CONDUCT.md +0 -0
  36. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CONTRIBUTING.md +0 -0
  37. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/LICENSE +0 -0
  38. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/SUPPORT.md +0 -0
  39. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/assets/agent-marketplace.png +0 -0
  40. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/changelog.d/README.md +0 -0
  41. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/architecture/layers.md +0 -0
  42. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/errors.md +0 -0
  43. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/guides/fastapi.md +0 -0
  44. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/guides/langchain.md +0 -0
  45. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/guides/mcp.md +0 -0
  46. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/idempotency.md +0 -0
  47. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/index.md +0 -0
  48. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/migration.md +0 -0
  49. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/client.md +0 -0
  50. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/exceptions.md +0 -0
  51. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/models.md +0 -0
  52. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/paywall.md +0 -0
  53. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/webhooks.md +0 -0
  54. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/rfcs/0001-spending-policy.md +0 -0
  55. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/rfcs/0002-openapi-investigation.md +0 -0
  56. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/webhooks.md +0 -0
  57. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/.env.example +0 -0
  58. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/.gitignore +0 -0
  59. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/README.md +0 -0
  60. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/claim.py +0 -0
  61. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/pyproject.toml +0 -0
  62. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/server.py +0 -0
  63. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/.env.example +0 -0
  64. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/.gitignore +0 -0
  65. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/README.md +0 -0
  66. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/agent.py +0 -0
  67. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/pyproject.toml +0 -0
  68. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/.env.example +0 -0
  69. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/.gitignore +0 -0
  70. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/.streamlit/config.toml +0 -0
  71. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/PLAN.md +0 -0
  72. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/README.md +0 -0
  73. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/agents.py +0 -0
  74. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/app.py +0 -0
  75. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/demo_customers.py +0 -0
  76. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/requirements.txt +0 -0
  77. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/sdk_gateway.py +0 -0
  78. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/ui_components.py +0 -0
  79. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/.env.example +0 -0
  80. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/.gitignore +0 -0
  81. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/README.md +0 -0
  82. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/agent_langchain.py +0 -0
  83. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/model.py +0 -0
  84. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/pyproject.toml +0 -0
  85. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/script_async.py +0 -0
  86. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/server_mcp.py +0 -0
  87. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/tool.py +0 -0
  88. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_async_client.py +0 -0
  89. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_config.py +0 -0
  90. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_http.py +0 -0
  91. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_stability.py +0 -0
  92. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/__init__.py +0 -0
  93. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/_recipe.py +0 -0
  94. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/httpx_transport.py +0 -0
  95. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/middleware.py +0 -0
  96. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/__init__.py +0 -0
  97. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/asgi.py +0 -0
  98. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/langchain.py +0 -0
  99. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/mcp.py +0 -0
  100. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/client.py +0 -0
  101. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/events.py +0 -0
  102. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/exceptions.py +0 -0
  103. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/fastapi.py +0 -0
  104. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/idempotency.py +0 -0
  105. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/langchain.py +0 -0
  106. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/models.py +0 -0
  107. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/__init__.py +0 -0
  108. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/_registry.py +0 -0
  109. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/checkout.py +0 -0
  110. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/customers.py +0 -0
  111. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/limits.py +0 -0
  112. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/merchant.py +0 -0
  113. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/plans.py +0 -0
  114. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/products.py +0 -0
  115. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/purchases.py +0 -0
  116. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/usage.py +0 -0
  117. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/__init__.py +0 -0
  118. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/core.py +0 -0
  119. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/decorators.py +0 -0
  120. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/meta.py +0 -0
  121. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/policy.py +0 -0
  122. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/resolvers.py +0 -0
  123. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/state.py +0 -0
  124. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall_state.py +0 -0
  125. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/py.typed +0 -0
  126. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/__init__.py +0 -0
  127. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/envelope.py +0 -0
  128. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/pipeline.py +0 -0
  129. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/replay.py +0 -0
  130. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/rotation.py +0 -0
  131. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/sign.py +0 -0
  132. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/verify.py +0 -0
  133. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/__init__.py +0 -0
  134. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_stability/__init__.py +0 -0
  135. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_stability/test_stable_returns_identity.py +0 -0
  136. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/__init__.py +0 -0
  137. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_aclose_cascade.py +0 -0
  138. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_error_wrapping.py +0 -0
  139. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_headers_case_insensitive.py +0 -0
  140. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_middleware_composition.py +0 -0
  141. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_protocol_conformance.py +0 -0
  142. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_recording_transport.py +0 -0
  143. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_retry_transport.py +0 -0
  144. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/__init__.py +0 -0
  145. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/test_asgi.py +0 -0
  146. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/test_langchain_protocol.py +0 -0
  147. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/test_mcp.py +0 -0
  148. {solvapay_python-0.9.0/tests/contract → solvapay_python-0.9.2/tests/benchmarks}/__init__.py +0 -0
  149. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/conftest.py +0 -0
  150. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/contract/README.md +0 -0
  151. {solvapay_python-0.9.0/tests/operations → solvapay_python-0.9.2/tests/contract}/__init__.py +0 -0
  152. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/contract/cassettes/.gitkeep +0 -0
  153. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/contract/test_contract_ops.py +0 -0
  154. {solvapay_python-0.9.0/tests/paywall → solvapay_python-0.9.2/tests/operations}/__init__.py +0 -0
  155. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/operations/test_namespace_api.py +0 -0
  156. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/operations/test_path_interpolation.py +0 -0
  157. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/operations/test_retry_safety_enum.py +0 -0
  158. {solvapay_python-0.9.0/tests/webhooks → solvapay_python-0.9.2/tests/paywall}/__init__.py +0 -0
  159. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
  160. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_payable_tool_meta.py +0 -0
  161. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_resolvers.py +0 -0
  162. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_split_classes.py +0 -0
  163. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_admin.py +0 -0
  164. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_api_version.py +0 -0
  165. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_async_client.py +0 -0
  166. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_checkout.py +0 -0
  167. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_config.py +0 -0
  168. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_customer.py +0 -0
  169. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_errors.py +0 -0
  170. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_http.py +0 -0
  171. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_idempotency.py +0 -0
  172. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_langchain.py +0 -0
  173. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_lifecycle.py +0 -0
  174. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_limits.py +0 -0
  175. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_packaging.py +0 -0
  176. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_paywall.py +0 -0
  177. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_paywall_state.py +0 -0
  178. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_webhook_events.py +0 -0
  179. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_webhooks.py +0 -0
  180. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_async_cache.py +0 -0
  181. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
  182. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_rotation.py +0 -0
  183. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_seen_cache_atomic.py +0 -0
  184. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_sign.py +0 -0
  185. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_webhook_pipeline.py +0 -0
  186. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/api_diff.py +0 -0
  187. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/importlinter.cfg +0 -0
  188. {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/lint_invariants.py +0 -0
@@ -4,8 +4,32 @@ on:
4
4
  push:
5
5
  branches: [main]
6
6
  pull_request:
7
+ schedule:
8
+ - cron: "0 7 * * *" # nightly security sweep
7
9
 
8
10
  jobs:
11
+ security:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+ - uses: astral-sh/setup-uv@v7
16
+ with:
17
+ enable-cache: true
18
+ - run: uv python install 3.12
19
+ - run: uv sync --all-extras --dev
20
+ - name: bandit (high-severity, high-confidence only)
21
+ run: uv run bandit -r src/ -lll
22
+ - name: osv-scanner (lockfile vuln DB cross-check)
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
+
32
+
9
33
  test:
10
34
  strategy:
11
35
  matrix:
@@ -25,8 +49,10 @@ jobs:
25
49
  python-version: "3.12"
26
50
  runs-on: ${{ matrix.os }}
27
51
  steps:
28
- - uses: actions/checkout@v4
29
- - uses: astral-sh/setup-uv@v3
52
+ - uses: actions/checkout@v6
53
+ with:
54
+ fetch-depth: 0
55
+ - uses: astral-sh/setup-uv@v7
30
56
  with:
31
57
  enable-cache: true
32
58
  - name: Set Python ${{ matrix.python-version }}
@@ -40,10 +66,14 @@ jobs:
40
66
  - run: uv run lint-imports --config tools/importlinter.cfg
41
67
  - run: uv run python tools/lint_invariants.py
42
68
  - name: pip-audit
43
- 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
44
72
  - name: Check changelog fragment on PRs touching src/
45
73
  if: github.event_name == 'pull_request'
74
+ shell: bash
46
75
  run: |
76
+ git fetch origin ${{ github.base_ref }}
47
77
  changed=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
48
78
  if echo "$changed" | grep -q "^src/"; then
49
79
  if ! echo "$changed" | grep -q "^changelog.d/"; then
@@ -9,8 +9,8 @@ jobs:
9
9
  contract:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
- - uses: actions/checkout@v4
13
- - uses: astral-sh/setup-uv@v3
12
+ - uses: actions/checkout@v6
13
+ - uses: astral-sh/setup-uv@v7
14
14
  with:
15
15
  enable-cache: true
16
16
  - run: uv sync --all-extras --dev
@@ -12,8 +12,8 @@ jobs:
12
12
  deploy:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/checkout@v4
16
- - uses: astral-sh/setup-uv@v3
15
+ - uses: actions/checkout@v6
16
+ - uses: astral-sh/setup-uv@v7
17
17
  with:
18
18
  enable-cache: true
19
19
  - run: uv sync --group docs
@@ -0,0 +1,32 @@
1
+ name: publish
2
+ on:
3
+ push:
4
+ tags: ["v*"]
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+ environment: pypi
9
+ permissions:
10
+ id-token: write
11
+ contents: write
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ - uses: astral-sh/setup-uv@v7
15
+ - run: uv build
16
+ - name: Generate CycloneDX SBOM
17
+ run: |
18
+ # Resolve runtime + all-extras deps from the lockfile and emit a CycloneDX SBOM.
19
+ uv export --no-dev --all-extras --no-emit-project --format requirements-txt > /tmp/sbom-requirements.txt
20
+ uv tool run --from cyclonedx-bom cyclonedx-py requirements \
21
+ --pyproject pyproject.toml \
22
+ --of JSON --sv 1.6 \
23
+ -o sbom.cdx.json \
24
+ /tmp/sbom-requirements.txt
25
+ - name: Publish to PyPI (Sigstore-signed via trusted publishing)
26
+ uses: pypa/gh-action-pypi-publish@release/v1
27
+ with:
28
+ attestations: true
29
+ - name: Attach SBOM to GitHub release
30
+ env:
31
+ GH_TOKEN: ${{ github.token }}
32
+ run: gh release upload "${GITHUB_REF_NAME}" sbom.cdx.json --clobber
@@ -13,3 +13,4 @@ htmlcov/
13
13
  .env.local
14
14
  *.egg
15
15
  .DS_Store
16
+ site/
@@ -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,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.2] — 2026-06-06
4
+
5
+ ### Features
6
+
7
+ - ``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.
8
+
9
+ 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).
10
+
11
+ 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.
12
+
13
+ ### Documentation
14
+
15
+ - ``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.
16
+
17
+ ``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).
18
+
19
+ ### Performance
20
+
21
+ - 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)``.
22
+
23
+
24
+ ---
25
+
26
+ ## [0.9.1] — 2026-06-05
27
+
28
+ Security & supply-chain quality patch (no public API change).
29
+
30
+ ### Features
31
+
32
+ - ``publish.yml``: PyPI uploads now ship PEP 740 attestations (Sigstore-backed via OIDC trusted publishing) and attach a CycloneDX SBOM (``sbom.cdx.json``) to each GitHub release.
33
+ - CI: new ``security`` job runs ``bandit -r src/ -lll`` (high-confidence high-severity only) and ``osv-scanner --lockfile=uv.lock`` as a cross-DB check against ``pip-audit``. Runs on PR and nightly cron.
34
+ - ``tests/test_redaction.py``: property-based test with Hypothesis (100 derandomized cases) drives random API keys, hex tokens, and webhook secrets through the HTTP transport and asserts the secret never appears in ``caplog.text`` at any log level.
35
+ - ``tests/test_webhook_timing.py``: smoke test that compares ``verify_webhook`` perf-counter distributions for perfect-match vs near-miss signatures, regressing if someone swaps ``hmac.compare_digest`` for ``==``.
36
+
37
+ ### Bug Fixes
38
+
39
+ - ``pyproject.toml``: tighten ``httpx`` to ``<0.29`` (closes deviation: was unbounded). FastAPI extra remains unbounded by design — documented in-file.
40
+
41
+ ### Documentation
42
+
43
+ - ``SECURITY.md``: explicit PCI-scope section — the SDK never transmits raw cardholder data; tokenization is server-side.
44
+
45
+ ---
46
+
3
47
  ## 0.9.0 — 2026-05-23
4
48
 
5
49
  Production polish + C1 adversarial-review closure. API-version pinning, idempotency TTL, webhook secret rotation, ASGI adapter, retry/recording transports, contract tests, lint automation, doc site, supply-chain hardening.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solvapay-python
3
- Version: 0.9.0
3
+ Version: 0.9.2
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
@@ -21,15 +21,17 @@ Classifier: Topic :: Office/Business :: Financial
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.10
24
- Requires-Dist: httpx>=0.27
25
- Requires-Dist: pydantic<2.13,>=2.6
24
+ Requires-Dist: httpx<0.29,>=0.27
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
30
- Requires-Dist: langchain-core<0.4,>=0.3; extra == 'langchain'
32
+ Requires-Dist: langchain-core<1.5,>=0.3; extra == 'langchain'
31
33
  Provides-Extra: mcp
32
- Requires-Dist: fastmcp<0.5,>=0.4; extra == 'mcp'
34
+ Requires-Dist: fastmcp<4,>=3.2.0; extra == 'mcp'
33
35
  Provides-Extra: retry
34
36
  Requires-Dist: tenacity<10,>=8.2; extra == 'retry'
35
37
  Description-Content-Type: text/markdown
@@ -138,16 +140,23 @@ if limits.within_limits:
138
140
 
139
141
  ## Idempotency
140
142
 
141
- All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys from request content:
143
+ All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys:
142
144
 
143
145
  ```python
144
146
  from solvapay.idempotency import from_payload
145
147
 
148
+ # Default: key includes UTC date — rolls at midnight, bounds replay past server TTL
146
149
  key = from_payload("track_usage", customer_ref, product_ref, "requests", units)
147
150
  sv.usage.track(..., idempotency_key=key) # retry-safe
151
+
152
+ # Hourly bucket (high-frequency ops)
153
+ key = from_payload("charge", customer_ref, time_bucket="hour")
154
+
155
+ # Pure payload hash — caller manages TTL externally
156
+ key = from_payload("idempotent_op", ref, time_bucket=None)
148
157
  ```
149
158
 
150
- Retried POSTs **must reuse the same key**. Key should change only when the logical request changes.
159
+ Retried POSTs **must reuse the same key**. A bucket roll (midnight / hour boundary) produces a new key — the server treats it as a new request.
151
160
 
152
161
  ---
153
162
 
@@ -174,7 +183,7 @@ except SolvaPayError as e:
174
183
  ... # catch-all
175
184
  ```
176
185
 
177
- No built-in retries by design. Layer `tenacity` manually. `solvapay[retry]` RetryTransport ships in v0.9.
186
+ No built-in retries by default. `solvapay[retry]` ships `RetryTransport` exponential backoff with jitter, 3 attempts, respects `OpSpec.retry_safety` (won't retry non-idempotent ops without an idempotency key). Or layer `tenacity` manually.
178
187
 
179
188
  ---
180
189
 
@@ -193,6 +202,30 @@ pipeline = WebhookPipeline(
193
202
  envelope = pipeline.process(body=request.body, signature=request.headers["sv-signature"])
194
203
  ```
195
204
 
205
+ **Secret rotation** — pass multiple secrets; primary tried first, secondary on mismatch:
206
+
207
+ ```python
208
+ pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
209
+ ```
210
+
211
+ **Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
212
+
213
+ ```python
214
+ from solvapay import sign_webhook # top-level (v0.9.2+)
215
+ # from solvapay.webhooks import sign_webhook # subpackage path also works
216
+
217
+ header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
218
+ # → "t=1716470000,v1=abc123..."
219
+ ```
220
+
221
+ **ASGI adapter** — mount directly in FastAPI / Starlette / Litestar:
222
+
223
+ ```python
224
+ from solvapay.adapters.asgi import webhook_app
225
+
226
+ app.mount("/webhook", webhook_app(pipeline, on_event=handle))
227
+ ```
228
+
196
229
  **Typed events** — discriminated union over 13 event types:
197
230
 
198
231
  ```python
@@ -279,6 +312,9 @@ pip install solvapay-python # core
279
312
  pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
280
313
  pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
281
314
  pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
315
+ pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
316
+ pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
317
+ pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
282
318
  ```
283
319
 
284
320
  ## Environment variables
@@ -302,17 +338,43 @@ pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
302
338
 
303
339
  ---
304
340
 
341
+ ## API version pinning
342
+
343
+ Pin the API version your code was written against — prevents silent breakage when the server evolves:
344
+
345
+ ```python
346
+ sv = SolvaPay(api_version="2026-05-22") # sends Solvapay-Version header
347
+ sv = SolvaPay(api_version=None) # omit header (use server default)
348
+ ```
349
+
350
+ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
351
+
352
+ ---
353
+
305
354
  ## Roadmap
306
355
 
307
356
  | Version | Theme |
308
357
  |---------|-------|
309
358
  | v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
310
- | v0.9 | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, contract tests, lint automation, doc site (MkDocs Material), supply-chain hygiene |
311
- | v1.0 | Gated on founder signalOpenAPI-generated models, full secret rotation, production hardening |
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 |
360
+ | v0.9.1 | Security & supply-chain qualityPyPI 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
+ | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
312
363
 
313
364
  ---
314
365
 
315
366
  ## Status
316
367
 
317
- **v0.8.0** — V1 architecture spine + AI-agent moat. `mypy --strict` clean (43 files). 261 tests. 89% line coverage.
368
+ **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).
369
+
370
+ **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.
371
+
372
+ **Supply chain & security posture:**
373
+ - PyPI trusted publishing with PEP 740 attestations (Sigstore)
374
+ - CycloneDX 1.6 SBOM attached to every release
375
+ - CI runs `bandit -r src/ -lll`, `osv-scanner --lockfile=uv.lock`, and `pip-audit` (cross-DB vuln check) on PR and nightly cron
376
+ - Constant-time HMAC comparison (`hmac.compare_digest`) regression-smoked
377
+ - Hypothesis-driven property tests assert no API key, hex token, or webhook secret leaks into log output at any level
378
+ - Tightened upper bounds on volatile deps (`httpx<0.29`, `pydantic<2.14`, `langchain-core<1.5`)
379
+
318
380
  Community SDK, not official. Proposal: [solvapay/solvapay-sdk#187](https://github.com/solvapay/solvapay-sdk/issues/187).
@@ -102,16 +102,23 @@ if limits.within_limits:
102
102
 
103
103
  ## Idempotency
104
104
 
105
- All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys from request content:
105
+ All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys:
106
106
 
107
107
  ```python
108
108
  from solvapay.idempotency import from_payload
109
109
 
110
+ # Default: key includes UTC date — rolls at midnight, bounds replay past server TTL
110
111
  key = from_payload("track_usage", customer_ref, product_ref, "requests", units)
111
112
  sv.usage.track(..., idempotency_key=key) # retry-safe
113
+
114
+ # Hourly bucket (high-frequency ops)
115
+ key = from_payload("charge", customer_ref, time_bucket="hour")
116
+
117
+ # Pure payload hash — caller manages TTL externally
118
+ key = from_payload("idempotent_op", ref, time_bucket=None)
112
119
  ```
113
120
 
114
- Retried POSTs **must reuse the same key**. Key should change only when the logical request changes.
121
+ Retried POSTs **must reuse the same key**. A bucket roll (midnight / hour boundary) produces a new key — the server treats it as a new request.
115
122
 
116
123
  ---
117
124
 
@@ -138,7 +145,7 @@ except SolvaPayError as e:
138
145
  ... # catch-all
139
146
  ```
140
147
 
141
- No built-in retries by design. Layer `tenacity` manually. `solvapay[retry]` RetryTransport ships in v0.9.
148
+ No built-in retries by default. `solvapay[retry]` ships `RetryTransport` exponential backoff with jitter, 3 attempts, respects `OpSpec.retry_safety` (won't retry non-idempotent ops without an idempotency key). Or layer `tenacity` manually.
142
149
 
143
150
  ---
144
151
 
@@ -157,6 +164,30 @@ pipeline = WebhookPipeline(
157
164
  envelope = pipeline.process(body=request.body, signature=request.headers["sv-signature"])
158
165
  ```
159
166
 
167
+ **Secret rotation** — pass multiple secrets; primary tried first, secondary on mismatch:
168
+
169
+ ```python
170
+ pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
171
+ ```
172
+
173
+ **Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
174
+
175
+ ```python
176
+ from solvapay import sign_webhook # top-level (v0.9.2+)
177
+ # from solvapay.webhooks import sign_webhook # subpackage path also works
178
+
179
+ header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
180
+ # → "t=1716470000,v1=abc123..."
181
+ ```
182
+
183
+ **ASGI adapter** — mount directly in FastAPI / Starlette / Litestar:
184
+
185
+ ```python
186
+ from solvapay.adapters.asgi import webhook_app
187
+
188
+ app.mount("/webhook", webhook_app(pipeline, on_event=handle))
189
+ ```
190
+
160
191
  **Typed events** — discriminated union over 13 event types:
161
192
 
162
193
  ```python
@@ -243,6 +274,9 @@ pip install solvapay-python # core
243
274
  pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
244
275
  pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
245
276
  pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
277
+ pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
278
+ pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
279
+ pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
246
280
  ```
247
281
 
248
282
  ## Environment variables
@@ -266,17 +300,43 @@ pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
266
300
 
267
301
  ---
268
302
 
303
+ ## API version pinning
304
+
305
+ Pin the API version your code was written against — prevents silent breakage when the server evolves:
306
+
307
+ ```python
308
+ sv = SolvaPay(api_version="2026-05-22") # sends Solvapay-Version header
309
+ sv = SolvaPay(api_version=None) # omit header (use server default)
310
+ ```
311
+
312
+ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
313
+
314
+ ---
315
+
269
316
  ## Roadmap
270
317
 
271
318
  | Version | Theme |
272
319
  |---------|-------|
273
320
  | v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
274
- | v0.9 | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, contract tests, lint automation, doc site (MkDocs Material), supply-chain hygiene |
275
- | v1.0 | Gated on founder signalOpenAPI-generated models, full secret rotation, production hardening |
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 |
322
+ | v0.9.1 | Security & supply-chain qualityPyPI 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
+ | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
276
325
 
277
326
  ---
278
327
 
279
328
  ## Status
280
329
 
281
- **v0.8.0** — V1 architecture spine + AI-agent moat. `mypy --strict` clean (43 files). 261 tests. 89% line coverage.
330
+ **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).
331
+
332
+ **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.
333
+
334
+ **Supply chain & security posture:**
335
+ - PyPI trusted publishing with PEP 740 attestations (Sigstore)
336
+ - CycloneDX 1.6 SBOM attached to every release
337
+ - CI runs `bandit -r src/ -lll`, `osv-scanner --lockfile=uv.lock`, and `pip-audit` (cross-DB vuln check) on PR and nightly cron
338
+ - Constant-time HMAC comparison (`hmac.compare_digest`) regression-smoked
339
+ - Hypothesis-driven property tests assert no API key, hex token, or webhook secret leaks into log output at any level
340
+ - Tightened upper bounds on volatile deps (`httpx<0.29`, `pydantic<2.14`, `langchain-core<1.5`)
341
+
282
342
  Community SDK, not official. Proposal: [solvapay/solvapay-sdk#187](https://github.com/solvapay/solvapay-sdk/issues/187).
@@ -13,8 +13,17 @@ This is a **community** Python SDK. It is not an official SolvaPay product.
13
13
  - SolvaPay server-side vulnerabilities
14
14
  - Issues requiring a SolvaPay account to reproduce
15
15
 
16
- **PCI scope:** The SDK never transmits raw cardholder data (PAN, CVV, expiry).
17
- It only exchanges SolvaPay API keys and customer references over HTTPS.
16
+ ## PCI Scope
17
+
18
+ The SDK **never transmits raw cardholder data (PAN, CVV, expiry, track data)**.
19
+ All tokenization happens **server-side** at the SolvaPay API; the SDK only
20
+ exchanges SolvaPay API keys, customer references, and tokenized identifiers
21
+ over HTTPS. Integrators using this SDK do not bring PAN data into their
22
+ own application scope through it.
23
+
24
+ If you discover any code path that would cause raw cardholder data to traverse
25
+ the SDK boundary, treat it as a high-severity vulnerability and report it via
26
+ the channel below.
18
27
 
19
28
  ## Reporting a Vulnerability
20
29
 
@@ -25,6 +34,9 @@ Please include:
25
34
  - Steps to reproduce
26
35
  - Any proof-of-concept code (privately)
27
36
 
37
+ Encrypt sensitive payloads with the maintainer's public key on request.
38
+ Do **not** open public GitHub issues for suspected vulnerabilities.
39
+
28
40
  **Response SLA:** Best-effort; typically within 7 days.
29
41
  **Disclosure policy:** 90-day coordinated disclosure. Public CVE filed only for confirmed SDK bugs.
30
42
 
@@ -32,5 +44,6 @@ Please include:
32
44
 
33
45
  | Version | Supported |
34
46
  |---------|-----------|
47
+ | 0.9.x | Yes |
35
48
  | 0.8.x | Yes |
36
49
  | < 0.8 | No — upgrade recommended |
@@ -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.