solvapay-python 0.9.0__tar.gz → 0.9.1__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 (181) hide show
  1. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/workflows/ci.yml +30 -2
  2. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/workflows/contract.yml +2 -2
  3. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/workflows/docs.yml +2 -2
  4. solvapay_python-0.9.1/.github/workflows/publish.yml +32 -0
  5. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.gitignore +1 -0
  6. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CHANGELOG.md +21 -0
  7. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/PKG-INFO +66 -11
  8. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/README.md +61 -6
  9. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/SECURITY.md +15 -2
  10. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/uv.lock +128 -122
  11. solvapay_python-0.9.1/examples/multi-framework-paywall/uv.lock +1585 -0
  12. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/pyproject.toml +12 -6
  13. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/__init__.py +1 -1
  14. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_redaction.py +37 -0
  15. solvapay_python-0.9.1/tests/test_webhook_timing.py +82 -0
  16. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/uv.lock +673 -33
  17. solvapay_python-0.9.0/.github/workflows/publish.yml +0 -15
  18. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  19. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  20. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  21. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  22. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/dependabot.yml +0 -0
  23. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.python-version +0 -0
  24. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CODEOWNERS +0 -0
  25. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CODE_OF_CONDUCT.md +0 -0
  26. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CONTRIBUTING.md +0 -0
  27. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/LICENSE +0 -0
  28. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/SUPPORT.md +0 -0
  29. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/assets/agent-marketplace.png +0 -0
  30. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/changelog.d/README.md +0 -0
  31. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/architecture/layers.md +0 -0
  32. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/errors.md +0 -0
  33. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/guides/fastapi.md +0 -0
  34. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/guides/langchain.md +0 -0
  35. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/guides/mcp.md +0 -0
  36. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/idempotency.md +0 -0
  37. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/index.md +0 -0
  38. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/migration.md +0 -0
  39. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/client.md +0 -0
  40. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/exceptions.md +0 -0
  41. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/models.md +0 -0
  42. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/paywall.md +0 -0
  43. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/webhooks.md +0 -0
  44. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/rfcs/0001-spending-policy.md +0 -0
  45. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/rfcs/0002-openapi-investigation.md +0 -0
  46. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/webhooks.md +0 -0
  47. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/.env.example +0 -0
  48. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/.gitignore +0 -0
  49. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/README.md +0 -0
  50. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/claim.py +0 -0
  51. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/pyproject.toml +0 -0
  52. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/server.py +0 -0
  53. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/.env.example +0 -0
  54. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/.gitignore +0 -0
  55. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/README.md +0 -0
  56. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/agent.py +0 -0
  57. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/pyproject.toml +0 -0
  58. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/.env.example +0 -0
  59. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/.gitignore +0 -0
  60. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/.streamlit/config.toml +0 -0
  61. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/PLAN.md +0 -0
  62. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/README.md +0 -0
  63. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/agents.py +0 -0
  64. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/app.py +0 -0
  65. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/demo_customers.py +0 -0
  66. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/requirements.txt +0 -0
  67. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/sdk_gateway.py +0 -0
  68. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/ui_components.py +0 -0
  69. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/.env.example +0 -0
  70. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/.gitignore +0 -0
  71. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/README.md +0 -0
  72. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/agent_langchain.py +0 -0
  73. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/model.py +0 -0
  74. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/pyproject.toml +0 -0
  75. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/script_async.py +0 -0
  76. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/server_mcp.py +0 -0
  77. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/tool.py +0 -0
  78. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/mkdocs.yml +0 -0
  79. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_async_client.py +0 -0
  80. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_config.py +0 -0
  81. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_http.py +0 -0
  82. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_stability.py +0 -0
  83. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/__init__.py +0 -0
  84. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/_recipe.py +0 -0
  85. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/httpx_transport.py +0 -0
  86. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/middleware.py +0 -0
  87. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/__init__.py +0 -0
  88. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/asgi.py +0 -0
  89. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/langchain.py +0 -0
  90. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/mcp.py +0 -0
  91. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/client.py +0 -0
  92. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/events.py +0 -0
  93. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/exceptions.py +0 -0
  94. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/fastapi.py +0 -0
  95. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/idempotency.py +0 -0
  96. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/langchain.py +0 -0
  97. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/models.py +0 -0
  98. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/__init__.py +0 -0
  99. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/_registry.py +0 -0
  100. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/checkout.py +0 -0
  101. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/customers.py +0 -0
  102. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/limits.py +0 -0
  103. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/merchant.py +0 -0
  104. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/plans.py +0 -0
  105. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/products.py +0 -0
  106. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/purchases.py +0 -0
  107. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/usage.py +0 -0
  108. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/__init__.py +0 -0
  109. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/core.py +0 -0
  110. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/decorators.py +0 -0
  111. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/meta.py +0 -0
  112. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/policy.py +0 -0
  113. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/resolvers.py +0 -0
  114. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/state.py +0 -0
  115. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall_state.py +0 -0
  116. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/py.typed +0 -0
  117. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/__init__.py +0 -0
  118. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/envelope.py +0 -0
  119. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/pipeline.py +0 -0
  120. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/replay.py +0 -0
  121. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/rotation.py +0 -0
  122. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/sign.py +0 -0
  123. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/verify.py +0 -0
  124. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/__init__.py +0 -0
  125. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_stability/__init__.py +0 -0
  126. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_stability/test_stable_returns_identity.py +0 -0
  127. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/__init__.py +0 -0
  128. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_aclose_cascade.py +0 -0
  129. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_error_wrapping.py +0 -0
  130. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_headers_case_insensitive.py +0 -0
  131. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_middleware_composition.py +0 -0
  132. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_protocol_conformance.py +0 -0
  133. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_recording_transport.py +0 -0
  134. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_retry_transport.py +0 -0
  135. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/__init__.py +0 -0
  136. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/test_asgi.py +0 -0
  137. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/test_langchain_protocol.py +0 -0
  138. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/test_mcp.py +0 -0
  139. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/conftest.py +0 -0
  140. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/README.md +0 -0
  141. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/__init__.py +0 -0
  142. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/cassettes/.gitkeep +0 -0
  143. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/test_contract_ops.py +0 -0
  144. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/__init__.py +0 -0
  145. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/test_namespace_api.py +0 -0
  146. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/test_path_interpolation.py +0 -0
  147. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/test_retry_safety_enum.py +0 -0
  148. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/__init__.py +0 -0
  149. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
  150. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_payable_tool_meta.py +0 -0
  151. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_resolvers.py +0 -0
  152. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_split_classes.py +0 -0
  153. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_admin.py +0 -0
  154. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_api_version.py +0 -0
  155. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_async_client.py +0 -0
  156. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_checkout.py +0 -0
  157. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_config.py +0 -0
  158. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_customer.py +0 -0
  159. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_errors.py +0 -0
  160. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_http.py +0 -0
  161. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_idempotency.py +0 -0
  162. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_invariants.py +0 -0
  163. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_langchain.py +0 -0
  164. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_lifecycle.py +0 -0
  165. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_limits.py +0 -0
  166. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_packaging.py +0 -0
  167. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_paywall.py +0 -0
  168. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_paywall_state.py +0 -0
  169. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_webhook_events.py +0 -0
  170. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_webhooks.py +0 -0
  171. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/__init__.py +0 -0
  172. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_async_cache.py +0 -0
  173. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
  174. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_rotation.py +0 -0
  175. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_seen_cache_atomic.py +0 -0
  176. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_sign.py +0 -0
  177. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_webhook_pipeline.py +0 -0
  178. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tools/api_baseline.json +0 -0
  179. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tools/api_diff.py +0 -0
  180. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tools/importlinter.cfg +0 -0
  181. {solvapay_python-0.9.0 → solvapay_python-0.9.1}/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: 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
+ - name: osv-scanner (lockfile vuln DB cross-check)
30
+ run: osv-scanner --lockfile=uv.lock
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 }}
@@ -43,7 +69,9 @@ jobs:
43
69
  run: uv run pip-audit
44
70
  - name: Check changelog fragment on PRs touching src/
45
71
  if: github.event_name == 'pull_request'
72
+ shell: bash
46
73
  run: |
74
+ git fetch origin ${{ github.base_ref }}
47
75
  changed=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
48
76
  if echo "$changed" | grep -q "^src/"; then
49
77
  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/
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.1] — 2026-06-05
4
+
5
+ Security & supply-chain quality patch (no public API change).
6
+
7
+ ### Features
8
+
9
+ - ``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.
10
+ - 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.
11
+ - ``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.
12
+ - ``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 ``==``.
13
+
14
+ ### Bug Fixes
15
+
16
+ - ``pyproject.toml``: tighten ``httpx`` to ``<0.29`` (closes deviation: was unbounded). FastAPI extra remains unbounded by design — documented in-file.
17
+
18
+ ### Documentation
19
+
20
+ - ``SECURITY.md``: explicit PCI-scope section — the SDK never transmits raw cardholder data; tokenization is server-side.
21
+
22
+ ---
23
+
3
24
  ## 0.9.0 — 2026-05-23
4
25
 
5
26
  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.1
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,15 @@ 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
27
  Provides-Extra: fastapi
28
28
  Requires-Dist: fastapi>=0.110; extra == 'fastapi'
29
29
  Provides-Extra: langchain
30
- Requires-Dist: langchain-core<0.4,>=0.3; extra == 'langchain'
30
+ Requires-Dist: langchain-core<1.5,>=0.3; extra == 'langchain'
31
31
  Provides-Extra: mcp
32
- Requires-Dist: fastmcp<0.5,>=0.4; extra == 'mcp'
32
+ Requires-Dist: fastmcp<4,>=3.2.0; extra == 'mcp'
33
33
  Provides-Extra: retry
34
34
  Requires-Dist: tenacity<10,>=8.2; extra == 'retry'
35
35
  Description-Content-Type: text/markdown
@@ -138,16 +138,23 @@ if limits.within_limits:
138
138
 
139
139
  ## Idempotency
140
140
 
141
- All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys from request content:
141
+ All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys:
142
142
 
143
143
  ```python
144
144
  from solvapay.idempotency import from_payload
145
145
 
146
+ # Default: key includes UTC date — rolls at midnight, bounds replay past server TTL
146
147
  key = from_payload("track_usage", customer_ref, product_ref, "requests", units)
147
148
  sv.usage.track(..., idempotency_key=key) # retry-safe
149
+
150
+ # Hourly bucket (high-frequency ops)
151
+ key = from_payload("charge", customer_ref, time_bucket="hour")
152
+
153
+ # Pure payload hash — caller manages TTL externally
154
+ key = from_payload("idempotent_op", ref, time_bucket=None)
148
155
  ```
149
156
 
150
- Retried POSTs **must reuse the same key**. Key should change only when the logical request changes.
157
+ 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
158
 
152
159
  ---
153
160
 
@@ -174,7 +181,7 @@ except SolvaPayError as e:
174
181
  ... # catch-all
175
182
  ```
176
183
 
177
- No built-in retries by design. Layer `tenacity` manually. `solvapay[retry]` RetryTransport ships in v0.9.
184
+ 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
185
 
179
186
  ---
180
187
 
@@ -193,6 +200,29 @@ pipeline = WebhookPipeline(
193
200
  envelope = pipeline.process(body=request.body, signature=request.headers["sv-signature"])
194
201
  ```
195
202
 
203
+ **Secret rotation** — pass multiple secrets; primary tried first, secondary on mismatch:
204
+
205
+ ```python
206
+ pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
207
+ ```
208
+
209
+ **Sign a webhook** (testing / outbound fanout):
210
+
211
+ ```python
212
+ from solvapay.webhooks import sign_webhook
213
+
214
+ header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
215
+ # → "t=1716470000,v1=abc123..."
216
+ ```
217
+
218
+ **ASGI adapter** — mount directly in FastAPI / Starlette / Litestar:
219
+
220
+ ```python
221
+ from solvapay.adapters.asgi import webhook_app
222
+
223
+ app.mount("/webhook", webhook_app(pipeline, on_event=handle))
224
+ ```
225
+
196
226
  **Typed events** — discriminated union over 13 event types:
197
227
 
198
228
  ```python
@@ -279,6 +309,8 @@ pip install solvapay-python # core
279
309
  pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
280
310
  pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
281
311
  pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
312
+ pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
313
+ pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
282
314
  ```
283
315
 
284
316
  ## Environment variables
@@ -302,17 +334,40 @@ pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
302
334
 
303
335
  ---
304
336
 
337
+ ## API version pinning
338
+
339
+ Pin the API version your code was written against — prevents silent breakage when the server evolves:
340
+
341
+ ```python
342
+ sv = SolvaPay(api_version="2026-05-22") # sends Solvapay-Version header
343
+ sv = SolvaPay(api_version=None) # omit header (use server default)
344
+ ```
345
+
346
+ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
347
+
348
+ ---
349
+
305
350
  ## Roadmap
306
351
 
307
352
  | Version | Theme |
308
353
  |---------|-------|
309
354
  | 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 |
355
+ | 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
+ | 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 |
357
+ | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
312
358
 
313
359
  ---
314
360
 
315
361
  ## Status
316
362
 
317
- **v0.8.0** — V1 architecture spine + AI-agent moat. `mypy --strict` clean (43 files). 261 tests. 89% line coverage.
363
+ **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
+
365
+ **Supply chain & security posture:**
366
+ - PyPI trusted publishing with PEP 740 attestations (Sigstore)
367
+ - CycloneDX 1.6 SBOM attached to every release
368
+ - CI runs `bandit -r src/ -lll`, `osv-scanner --lockfile=uv.lock`, and `pip-audit` (cross-DB vuln check) on PR and nightly cron
369
+ - Constant-time HMAC comparison (`hmac.compare_digest`) regression-smoked
370
+ - Hypothesis-driven property tests assert no API key, hex token, or webhook secret leaks into log output at any level
371
+ - Tightened upper bounds on volatile deps (`httpx<0.29`, `pydantic<2.14`, `langchain-core<1.5`)
372
+
318
373
  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,29 @@ 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):
174
+
175
+ ```python
176
+ from solvapay.webhooks import sign_webhook
177
+
178
+ header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
179
+ # → "t=1716470000,v1=abc123..."
180
+ ```
181
+
182
+ **ASGI adapter** — mount directly in FastAPI / Starlette / Litestar:
183
+
184
+ ```python
185
+ from solvapay.adapters.asgi import webhook_app
186
+
187
+ app.mount("/webhook", webhook_app(pipeline, on_event=handle))
188
+ ```
189
+
160
190
  **Typed events** — discriminated union over 13 event types:
161
191
 
162
192
  ```python
@@ -243,6 +273,8 @@ pip install solvapay-python # core
243
273
  pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
244
274
  pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
245
275
  pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
276
+ pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
277
+ pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
246
278
  ```
247
279
 
248
280
  ## Environment variables
@@ -266,17 +298,40 @@ pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
266
298
 
267
299
  ---
268
300
 
301
+ ## API version pinning
302
+
303
+ Pin the API version your code was written against — prevents silent breakage when the server evolves:
304
+
305
+ ```python
306
+ sv = SolvaPay(api_version="2026-05-22") # sends Solvapay-Version header
307
+ sv = SolvaPay(api_version=None) # omit header (use server default)
308
+ ```
309
+
310
+ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
311
+
312
+ ---
313
+
269
314
  ## Roadmap
270
315
 
271
316
  | Version | Theme |
272
317
  |---------|-------|
273
318
  | 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 |
319
+ | 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
+ | 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 |
321
+ | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
276
322
 
277
323
  ---
278
324
 
279
325
  ## Status
280
326
 
281
- **v0.8.0** — V1 architecture spine + AI-agent moat. `mypy --strict` clean (43 files). 261 tests. 89% line coverage.
327
+ **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
+
329
+ **Supply chain & security posture:**
330
+ - PyPI trusted publishing with PEP 740 attestations (Sigstore)
331
+ - CycloneDX 1.6 SBOM attached to every release
332
+ - CI runs `bandit -r src/ -lll`, `osv-scanner --lockfile=uv.lock`, and `pip-audit` (cross-DB vuln check) on PR and nightly cron
333
+ - Constant-time HMAC comparison (`hmac.compare_digest`) regression-smoked
334
+ - Hypothesis-driven property tests assert no API key, hex token, or webhook secret leaks into log output at any level
335
+ - Tightened upper bounds on volatile deps (`httpx<0.29`, `pydantic<2.14`, `langchain-core<1.5`)
336
+
282
337
  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 |