solvapay-python 0.9.2__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 (192) hide show
  1. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/CHANGELOG.md +7 -0
  2. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/PKG-INFO +4 -1
  3. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/README.md +3 -0
  4. solvapay_python-0.9.3/docs/architecture/layers.md +84 -0
  5. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/pyproject.toml +1 -1
  6. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/__init__.py +1 -1
  7. solvapay_python-0.9.3/tests/_baselines/cold_import.json +1 -0
  8. solvapay_python-0.9.3/tests/test_cold_import.py +92 -0
  9. solvapay_python-0.9.3/tests/test_layer_dag.py +35 -0
  10. solvapay_python-0.9.3/tools/importlinter.cfg +32 -0
  11. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/uv.lock +28 -2
  12. solvapay_python-0.9.2/docs/architecture/layers.md +0 -46
  13. solvapay_python-0.9.2/tests/_baselines/cold_import.json +0 -1
  14. solvapay_python-0.9.2/tests/test_cold_import.py +0 -66
  15. solvapay_python-0.9.2/tools/importlinter.cfg +0 -42
  16. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  17. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  18. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/question.yml +0 -0
  19. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/dependabot.yml +0 -0
  21. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/workflows/ci.yml +0 -0
  22. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/workflows/contract.yml +0 -0
  23. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/workflows/docs.yml +0 -0
  24. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.github/workflows/publish.yml +0 -0
  25. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.gitignore +0 -0
  26. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.osv-scanner.toml +0 -0
  27. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/.python-version +0 -0
  28. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/CODEOWNERS +0 -0
  29. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/CODE_OF_CONDUCT.md +0 -0
  30. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/CONTRIBUTING.md +0 -0
  31. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/LICENSE +0 -0
  32. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/SECURITY.md +0 -0
  33. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/SUPPORT.md +0 -0
  34. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/assets/agent-marketplace.png +0 -0
  35. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/changelog.d/README.md +0 -0
  36. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/architecture/cold-start.md +0 -0
  37. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/errors.md +0 -0
  38. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/guides/fastapi.md +0 -0
  39. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/guides/langchain.md +0 -0
  40. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/guides/mcp.md +0 -0
  41. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/idempotency.md +0 -0
  42. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/index.md +0 -0
  43. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/migration.md +0 -0
  44. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/reference/client.md +0 -0
  45. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/reference/exceptions.md +0 -0
  46. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/reference/models.md +0 -0
  47. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/reference/paywall.md +0 -0
  48. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/reference/webhooks.md +0 -0
  49. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/rfcs/0001-spending-policy.md +0 -0
  50. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/rfcs/0002-openapi-investigation.md +0 -0
  51. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/troubleshooting.md +0 -0
  52. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/docs/webhooks.md +0 -0
  53. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/.env.example +0 -0
  54. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/.gitignore +0 -0
  55. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/README.md +0 -0
  56. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/claim.py +0 -0
  57. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/pyproject.toml +0 -0
  58. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/server.py +0 -0
  59. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/fastmcp-paywall/uv.lock +0 -0
  60. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/langchain-paywall/.env.example +0 -0
  61. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/langchain-paywall/.gitignore +0 -0
  62. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/langchain-paywall/README.md +0 -0
  63. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/langchain-paywall/agent.py +0 -0
  64. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/langchain-paywall/pyproject.toml +0 -0
  65. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/.env.example +0 -0
  66. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/.gitignore +0 -0
  67. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/.streamlit/config.toml +0 -0
  68. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/PLAN.md +0 -0
  69. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/README.md +0 -0
  70. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/agents.py +0 -0
  71. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/app.py +0 -0
  72. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/demo_customers.py +0 -0
  73. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/requirements.txt +0 -0
  74. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/sdk_gateway.py +0 -0
  75. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/marketplace/ui_components.py +0 -0
  76. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/.env.example +0 -0
  77. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/.gitignore +0 -0
  78. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/README.md +0 -0
  79. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/agent_langchain.py +0 -0
  80. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/model.py +0 -0
  81. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/pyproject.toml +0 -0
  82. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/script_async.py +0 -0
  83. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/server_mcp.py +0 -0
  84. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/tool.py +0 -0
  85. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/examples/multi-framework-paywall/uv.lock +0 -0
  86. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/mkdocs.yml +0 -0
  87. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_async_client.py +0 -0
  88. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_config.py +0 -0
  89. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_http.py +0 -0
  90. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_stability.py +0 -0
  91. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_transport/__init__.py +0 -0
  92. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_transport/_recipe.py +0 -0
  93. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_transport/httpx_transport.py +0 -0
  94. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/_transport/middleware.py +0 -0
  95. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/adapters/__init__.py +0 -0
  96. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/adapters/asgi.py +0 -0
  97. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/adapters/langchain.py +0 -0
  98. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/adapters/mcp.py +0 -0
  99. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/client.py +0 -0
  100. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/events.py +0 -0
  101. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/exceptions.py +0 -0
  102. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/fastapi.py +0 -0
  103. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/idempotency.py +0 -0
  104. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/langchain.py +0 -0
  105. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/models.py +0 -0
  106. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/__init__.py +0 -0
  107. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/_registry.py +0 -0
  108. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/checkout.py +0 -0
  109. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/customers.py +0 -0
  110. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/limits.py +0 -0
  111. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/merchant.py +0 -0
  112. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/plans.py +0 -0
  113. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/products.py +0 -0
  114. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/purchases.py +0 -0
  115. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/operations/usage.py +0 -0
  116. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/__init__.py +0 -0
  117. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/core.py +0 -0
  118. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/decorators.py +0 -0
  119. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/meta.py +0 -0
  120. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/policy.py +0 -0
  121. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/resolvers.py +0 -0
  122. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall/state.py +0 -0
  123. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/paywall_state.py +0 -0
  124. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/py.typed +0 -0
  125. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/__init__.py +0 -0
  126. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/envelope.py +0 -0
  127. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/pipeline.py +0 -0
  128. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/replay.py +0 -0
  129. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/rotation.py +0 -0
  130. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/sign.py +0 -0
  131. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/src/solvapay/webhooks/verify.py +0 -0
  132. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/__init__.py +0 -0
  133. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_stability/__init__.py +0 -0
  134. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_stability/test_stable_returns_identity.py +0 -0
  135. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/__init__.py +0 -0
  136. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_aclose_cascade.py +0 -0
  137. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_error_wrapping.py +0 -0
  138. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_headers_case_insensitive.py +0 -0
  139. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_middleware_composition.py +0 -0
  140. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_protocol_conformance.py +0 -0
  141. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_recording_transport.py +0 -0
  142. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/_transport/test_retry_transport.py +0 -0
  143. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/adapters/__init__.py +0 -0
  144. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/adapters/test_asgi.py +0 -0
  145. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/adapters/test_langchain_protocol.py +0 -0
  146. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/adapters/test_mcp.py +0 -0
  147. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/benchmarks/__init__.py +0 -0
  148. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/benchmarks/test_benchmarks.py +0 -0
  149. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/conftest.py +0 -0
  150. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/contract/README.md +0 -0
  151. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/contract/__init__.py +0 -0
  152. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/contract/cassettes/.gitkeep +0 -0
  153. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/contract/test_contract_ops.py +0 -0
  154. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/operations/__init__.py +0 -0
  155. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/operations/test_namespace_api.py +0 -0
  156. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/operations/test_path_interpolation.py +0 -0
  157. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/operations/test_retry_safety_enum.py +0 -0
  158. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/paywall/__init__.py +0 -0
  159. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
  160. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/paywall/test_payable_tool_meta.py +0 -0
  161. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/paywall/test_resolvers.py +0 -0
  162. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/paywall/test_split_classes.py +0 -0
  163. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_admin.py +0 -0
  164. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_api_version.py +0 -0
  165. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_async_client.py +0 -0
  166. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_checkout.py +0 -0
  167. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_config.py +0 -0
  168. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_customer.py +0 -0
  169. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_errors.py +0 -0
  170. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_http.py +0 -0
  171. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_idempotency.py +0 -0
  172. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_invariants.py +0 -0
  173. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_langchain.py +0 -0
  174. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_lifecycle.py +0 -0
  175. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_limits.py +0 -0
  176. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_packaging.py +0 -0
  177. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_paywall.py +0 -0
  178. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_paywall_state.py +0 -0
  179. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_redaction.py +0 -0
  180. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_webhook_events.py +0 -0
  181. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_webhook_timing.py +0 -0
  182. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/test_webhooks.py +0 -0
  183. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/__init__.py +0 -0
  184. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/test_async_cache.py +0 -0
  185. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
  186. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/test_rotation.py +0 -0
  187. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/test_seen_cache_atomic.py +0 -0
  188. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/test_sign.py +0 -0
  189. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tests/webhooks/test_webhook_pipeline.py +0 -0
  190. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tools/api_baseline.json +0 -0
  191. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tools/api_diff.py +0 -0
  192. {solvapay_python-0.9.2 → solvapay_python-0.9.3}/tools/lint_invariants.py +0 -0
@@ -1,5 +1,12 @@
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
+
3
10
  ## [0.9.2] — 2026-06-06
4
11
 
5
12
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solvapay-python
3
- Version: 0.9.2
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
@@ -359,12 +359,15 @@ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
359
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
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
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 |
362
363
  | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
363
364
 
364
365
  ---
365
366
 
366
367
  ## Status
367
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
+
368
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).
369
372
 
370
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.
@@ -321,12 +321,15 @@ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
321
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
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
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 |
324
325
  | v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
325
326
 
326
327
  ---
327
328
 
328
329
  ## Status
329
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
+
330
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).
331
334
 
332
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.
@@ -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.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "solvapay-python"
7
- version = "0.9.2"
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" }
@@ -109,7 +109,7 @@ __all__ = [
109
109
  "stable",
110
110
  "verify_webhook",
111
111
  ]
112
- __version__ = "0.9.2"
112
+ __version__ = "0.9.3"
113
113
 
114
114
  # PEP 562 — lazy adapter submodule access.
115
115
  # Adapters drag in optional heavy deps (fastmcp, langchain-core, fastapi).
@@ -0,0 +1 @@
1
+ {"darwin": 99.82}
@@ -0,0 +1,92 @@
1
+ """Cold-import baseline harness.
2
+
3
+ Measures ``python -X importtime -c "import solvapay"`` and compares against
4
+ a committed per-platform baseline.
5
+
6
+ Behaviour:
7
+ - **CI** (``CI=true`` env var set by GitHub Actions): always prints the
8
+ measurement; never fails. GitHub runners have no persistent baseline across
9
+ fresh checkouts, so asserting would produce false failures on every run.
10
+ - **Local**: writes a per-platform baseline on first run; subsequent runs assert
11
+ within 1.5x. Catches cold-import regressions introduced during development.
12
+
13
+ Platform keys: ``sys.platform`` (``darwin``, ``linux``, ``win32``).
14
+ The hard <200 ms CI gate is a v1.0 feature (HLD §V1.20); v0.9.2 measures only.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ import re
22
+ import subprocess
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ BASELINE_PATH = Path(__file__).parent / "_baselines" / "cold_import.json"
27
+ BASELINE_PATH.parent.mkdir(exist_ok=True)
28
+ REGRESSION_FACTOR = 1.5
29
+ PLATFORM = sys.platform # e.g. "darwin", "linux", "win32"
30
+ IN_CI = os.environ.get("CI", "").lower() in ("true", "1")
31
+
32
+
33
+ def _measure_import_ms() -> float:
34
+ result = subprocess.run(
35
+ [sys.executable, "-X", "importtime", "-c", "import solvapay"],
36
+ capture_output=True,
37
+ text=True,
38
+ )
39
+ # importtime writes to stderr.
40
+ # Each line: "import time: <self_us> | <cumulative_us> | <module>"
41
+ lines = [ln for ln in result.stderr.splitlines() if "import time:" in ln]
42
+ if not lines:
43
+ raise RuntimeError(f"No importtime output captured.\nstderr:\n{result.stderr}")
44
+ # The outermost module (solvapay) has the highest cumulative value.
45
+ cumulative_us = max(
46
+ int(m.group(1)) for ln in lines if (m := re.search(r"import time:\s+\d+\s+\|\s+(\d+)", ln))
47
+ )
48
+ return cumulative_us / 1000.0 # µs → ms
49
+
50
+
51
+ def test_cold_import_baseline() -> None:
52
+ """Measure cold-import time. Assert vs baseline locally; print-only in CI."""
53
+ current_ms = _measure_import_ms()
54
+
55
+ if IN_CI:
56
+ # CI has no persistent state; just report the number.
57
+ print(f"\n[cold-import] CI platform={PLATFORM} current={current_ms:.1f} ms (no assertion)")
58
+ assert current_ms < 2000, (
59
+ f"Cold-import took {current_ms:.0f} ms — suspiciously slow even for CI"
60
+ )
61
+ return
62
+
63
+ # Local: per-platform baseline regression gate.
64
+ baselines: dict[str, float] = {}
65
+ if BASELINE_PATH.exists():
66
+ raw = json.loads(BASELINE_PATH.read_text())
67
+ # Support legacy flat format {"cold_import_ms": X}.
68
+ if "cold_import_ms" in raw and not any(k in raw for k in ("darwin", "linux", "win32")):
69
+ baselines = {PLATFORM: raw["cold_import_ms"]}
70
+ else:
71
+ baselines = raw
72
+
73
+ if PLATFORM not in baselines:
74
+ assert current_ms < 2000, (
75
+ f"Cold-import took {current_ms:.0f} ms on first run — suspiciously slow"
76
+ )
77
+ baselines[PLATFORM] = round(current_ms, 2)
78
+ BASELINE_PATH.write_text(json.dumps(baselines, sort_keys=True))
79
+ print(f"\n[cold-import] {PLATFORM} baseline written: {current_ms:.1f} ms")
80
+ return
81
+
82
+ baseline_ms: float = baselines[PLATFORM]
83
+ limit_ms = baseline_ms * REGRESSION_FACTOR
84
+
85
+ print(
86
+ f"\n[cold-import] platform={PLATFORM} current={current_ms:.1f} ms "
87
+ f"baseline={baseline_ms:.1f} ms limit={limit_ms:.1f} ms"
88
+ )
89
+ assert current_ms <= limit_ms, (
90
+ f"Cold-import regression on {PLATFORM}: {current_ms:.1f} ms > {limit_ms:.1f} ms "
91
+ f"(baseline={baseline_ms:.1f} ms x {REGRESSION_FACTOR})"
92
+ )
@@ -0,0 +1,35 @@
1
+ """Belt-and-suspenders enforcement of the HLD §V1.1 layer DAG.
2
+
3
+ The primary gate is the CI step that runs ``lint-imports`` directly. This
4
+ test shells out to the same command so that ``pytest`` also fails locally
5
+ if someone introduces an upward import without re-running ``lint-imports``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shutil
11
+ import subprocess
12
+ from pathlib import Path
13
+
14
+ import pytest
15
+
16
+ REPO_ROOT = Path(__file__).resolve().parent.parent
17
+ CONFIG = REPO_ROOT / "tools" / "importlinter.cfg"
18
+
19
+
20
+ def test_layered_dag_contract_holds() -> None:
21
+ lint_imports = shutil.which("lint-imports")
22
+ if lint_imports is None:
23
+ pytest.skip("lint-imports not on PATH; CI runs it directly")
24
+ result = subprocess.run(
25
+ [lint_imports, "--config", str(CONFIG)],
26
+ cwd=REPO_ROOT,
27
+ capture_output=True,
28
+ text=True,
29
+ check=False,
30
+ )
31
+ assert result.returncode == 0, (
32
+ f"lint-imports failed (rc={result.returncode}).\n"
33
+ f"stdout:\n{result.stdout}\n"
34
+ f"stderr:\n{result.stderr}"
35
+ )
@@ -0,0 +1,32 @@
1
+ [importlinter]
2
+ root_package = solvapay
3
+ include_external_packages = False
4
+ # TYPE_CHECKING blocks vanish from the import graph; cross-layer type hints
5
+ # do not trip the layered DAG check below. mypy still sees them via the
6
+ # `from __future__ import annotations` + `TYPE_CHECKING:` pattern.
7
+ ignore_type_checking_imports = True
8
+
9
+ [importlinter:contract:layered-dag]
10
+ name = solvapay layer DAG (HLD V1.1)
11
+ type = layers
12
+ exhaustive = true
13
+ exhaustive_ignores =
14
+ exceptions
15
+ _stability
16
+ _config
17
+ idempotency
18
+ events
19
+ paywall_state
20
+ layers =
21
+ fastapi | langchain
22
+ adapters
23
+ paywall | webhooks
24
+ client | _async_client
25
+ operations
26
+ models
27
+ _http
28
+ _transport
29
+ containers =
30
+ solvapay
31
+ ignore_imports =
32
+ solvapay.paywall.state -> solvapay.paywall_state
@@ -2003,6 +2003,15 @@ wheels = [
2003
2003
  { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
2004
2004
  ]
2005
2005
 
2006
+ [[package]]
2007
+ name = "py-cpuinfo"
2008
+ version = "9.0.0"
2009
+ source = { registry = "https://pypi.org/simple" }
2010
+ sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" }
2011
+ wheels = [
2012
+ { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" },
2013
+ ]
2014
+
2006
2015
  [[package]]
2007
2016
  name = "py-key-value-aio"
2008
2017
  version = "0.4.4"
@@ -2291,6 +2300,19 @@ wheels = [
2291
2300
  { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
2292
2301
  ]
2293
2302
 
2303
+ [[package]]
2304
+ name = "pytest-benchmark"
2305
+ version = "5.2.3"
2306
+ source = { registry = "https://pypi.org/simple" }
2307
+ dependencies = [
2308
+ { name = "py-cpuinfo" },
2309
+ { name = "pytest" },
2310
+ ]
2311
+ sdist = { url = "https://files.pythonhosted.org/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" }
2312
+ wheels = [
2313
+ { url = "https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" },
2314
+ ]
2315
+
2294
2316
  [[package]]
2295
2317
  name = "pytest-cov"
2296
2318
  version = "7.1.0"
@@ -2692,7 +2714,7 @@ wheels = [
2692
2714
 
2693
2715
  [[package]]
2694
2716
  name = "solvapay-python"
2695
- version = "0.9.1"
2717
+ version = "0.9.3"
2696
2718
  source = { editable = "." }
2697
2719
  dependencies = [
2698
2720
  { name = "httpx" },
@@ -2700,6 +2722,9 @@ dependencies = [
2700
2722
  ]
2701
2723
 
2702
2724
  [package.optional-dependencies]
2725
+ bench = [
2726
+ { name = "pytest-benchmark" },
2727
+ ]
2703
2728
  fastapi = [
2704
2729
  { name = "fastapi" },
2705
2730
  ]
@@ -2742,9 +2767,10 @@ requires-dist = [
2742
2767
  { name = "httpx", specifier = ">=0.27,<0.29" },
2743
2768
  { name = "langchain-core", marker = "extra == 'langchain'", specifier = ">=0.3,<1.5" },
2744
2769
  { name = "pydantic", specifier = ">=2.6,<2.14" },
2770
+ { name = "pytest-benchmark", marker = "extra == 'bench'", specifier = ">=4.0,<6" },
2745
2771
  { name = "tenacity", marker = "extra == 'retry'", specifier = ">=8.2,<10" },
2746
2772
  ]
2747
- provides-extras = ["fastapi", "langchain", "mcp", "asgi", "retry"]
2773
+ provides-extras = ["fastapi", "langchain", "mcp", "asgi", "retry", "bench"]
2748
2774
 
2749
2775
  [package.metadata.requires-dev]
2750
2776
  dev = [
@@ -1,46 +0,0 @@
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
- ## Layer DAG
7
-
8
- ```
9
- L0 solvapay._transport — Transport Protocol + httpx impl + middleware
10
- L1 solvapay._wire — Wire codec (reserved; currently unused)
11
- L2 solvapay.models — Pydantic request/response models
12
- L3 solvapay.operations — Resource namespace operations (sv.customers.ensure etc.)
13
- L4 solvapay.client — SolvaPay / AsyncSolvaPay public facades
14
- solvapay._async_client
15
- L5 solvapay.paywall — Paywall / AsyncPaywall primitives
16
- solvapay.webhooks — Webhook verify + pipeline
17
- L6 solvapay.adapters — Framework adapters (MCP, LangChain, ASGI)
18
- L7 solvapay.experimental — Experimental features (reserved)
19
- L8 tests/ — Test suite (permissive; may import L0–L7)
20
- ```
21
-
22
- ## Allowed imports
23
-
24
- | Layer | May import |
25
- |-------|-----------|
26
- | L0 `_transport` | stdlib, httpx, `exceptions` |
27
- | L1 `_wire` | L0 |
28
- | L2 `models` | stdlib, pydantic |
29
- | L3 `operations` | L0, L1, L2 |
30
- | L4 `client` | L0–L3, `_config`, `exceptions` |
31
- | L5 `paywall` | L0–L4 |
32
- | L5 `webhooks` | L0–L2, `exceptions` |
33
- | L6 `adapters` | L0–L5 |
34
- | L7 `experimental` | L0–L6 |
35
- | L8 `tests` | L0–L7 (no `from solvapay._*` outside `tests/internal/`) |
36
-
37
- ## Why L7 = stability tier, not function (DG2 lock)
38
-
39
- `solvapay.experimental` groups features by **stability**, not by domain. A webhook adapter
40
- that is still unstable lives in L6 (adapters) while it's being proven, then graduates to
41
- the canonical path once stable. This prevents the "experimental" namespace from becoming a
42
- permanent home for half-finished features.
43
-
44
- ## Enforcement
45
-
46
- `uv run lint-imports --config tools/importlinter.cfg` — run in CI on every push.
@@ -1 +0,0 @@
1
- {"cold_import_ms": 99.82}
@@ -1,66 +0,0 @@
1
- """Cold-import baseline harness.
2
-
3
- Runs ``python -X importtime -c "import solvapay"`` via subprocess, parses the
4
- highest cumulative time from the importtime output, and writes a baseline on
5
- first run. Subsequent runs assert the current time is within 1.5x the baseline.
6
-
7
- CI prints the absolute ms value so regressions are visible in the test name.
8
- The hard <200 ms gate is a v1.0 feature (HLD §V1.20); this only measures.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import json
14
- import re
15
- import subprocess
16
- import sys
17
- from pathlib import Path
18
-
19
- BASELINE_PATH = Path(__file__).parent / "_baselines" / "cold_import.json"
20
- BASELINE_PATH.parent.mkdir(exist_ok=True)
21
- REGRESSION_FACTOR = 1.5
22
-
23
-
24
- def _measure_import_ms() -> float:
25
- result = subprocess.run(
26
- [sys.executable, "-X", "importtime", "-c", "import solvapay"],
27
- capture_output=True,
28
- text=True,
29
- )
30
- # importtime writes to stderr.
31
- # Each line: "import time: <self_us> | <cumulative_us> | <module>"
32
- lines = [ln for ln in result.stderr.splitlines() if "import time:" in ln]
33
- if not lines:
34
- raise RuntimeError(f"No importtime output captured.\nstderr:\n{result.stderr}")
35
- # The outermost module (solvapay) has the highest cumulative value.
36
- cumulative_us = max(
37
- int(m.group(1)) for ln in lines if (m := re.search(r"import time:\s+\d+\s+\|\s+(\d+)", ln))
38
- )
39
- return cumulative_us / 1000.0 # µs → ms
40
-
41
-
42
- def test_cold_import_baseline() -> None:
43
- """Measure cold-import time and assert within 1.5x of committed baseline."""
44
- current_ms = _measure_import_ms()
45
-
46
- if not BASELINE_PATH.exists():
47
- # Sanity: import must complete in <2 s even on first run (no regression).
48
- assert current_ms < 2000, (
49
- f"Cold-import took {current_ms:.0f} ms on first run — suspiciously slow"
50
- )
51
- BASELINE_PATH.write_text(json.dumps({"cold_import_ms": round(current_ms, 2)}))
52
- print(f"\n[cold-import] Baseline written: {current_ms:.1f} ms")
53
- return
54
-
55
- baseline = json.loads(BASELINE_PATH.read_text())
56
- baseline_ms: float = baseline["cold_import_ms"]
57
- limit_ms = baseline_ms * REGRESSION_FACTOR
58
-
59
- print(
60
- f"\n[cold-import] current={current_ms:.1f} ms "
61
- f"baseline={baseline_ms:.1f} ms limit={limit_ms:.1f} ms"
62
- )
63
- assert current_ms <= limit_ms, (
64
- f"Cold-import regression: {current_ms:.1f} ms > {limit_ms:.1f} ms "
65
- f"(baseline={baseline_ms:.1f} ms x {REGRESSION_FACTOR})"
66
- )
@@ -1,42 +0,0 @@
1
- [importlinter]
2
- root_packages =
3
- solvapay
4
- include_external_packages = True
5
-
6
- [importlinter:contract:no-upward-imports]
7
- name = Layer DAG — no upward imports (HLD V1.1)
8
- type = forbidden
9
-
10
- # L0: _transport must not import higher layers
11
- source_modules =
12
- solvapay._transport
13
- forbidden_modules =
14
- solvapay.client
15
- solvapay._async_client
16
- solvapay.paywall
17
- solvapay.webhooks
18
- solvapay.adapters
19
- solvapay.operations
20
-
21
- [importlinter:contract:no-client-importing-adapters]
22
- name = Layer DAG — client (L4) must not import adapters (L6) or experimental (L7)
23
- type = forbidden
24
-
25
- source_modules =
26
- solvapay.client
27
- solvapay._async_client
28
-
29
- forbidden_modules =
30
- solvapay.adapters
31
- solvapay.experimental
32
-
33
- [importlinter:contract:operations-no-paywall]
34
- name = Layer DAG — operations (L3) must not import paywall (L5) or adapters (L6)
35
- type = forbidden
36
-
37
- source_modules =
38
- solvapay.operations
39
-
40
- forbidden_modules =
41
- solvapay.paywall
42
- solvapay.adapters
File without changes