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.
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/workflows/ci.yml +33 -3
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/workflows/contract.yml +2 -2
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/workflows/docs.yml +2 -2
- solvapay_python-0.9.2/.github/workflows/publish.yml +32 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.gitignore +1 -0
- solvapay_python-0.9.2/.osv-scanner.toml +6 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CHANGELOG.md +44 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/PKG-INFO +73 -11
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/README.md +66 -6
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/SECURITY.md +15 -2
- solvapay_python-0.9.2/docs/architecture/cold-start.md +70 -0
- solvapay_python-0.9.2/docs/troubleshooting.md +225 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/uv.lock +128 -122
- solvapay_python-0.9.2/examples/multi-framework-paywall/uv.lock +1585 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/mkdocs.yml +4 -1
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/pyproject.toml +23 -7
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/__init__.py +29 -2
- solvapay_python-0.9.2/tests/_baselines/cold_import.json +1 -0
- solvapay_python-0.9.2/tests/benchmarks/test_benchmarks.py +46 -0
- solvapay_python-0.9.2/tests/test_cold_import.py +66 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_invariants.py +32 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_redaction.py +37 -0
- solvapay_python-0.9.2/tests/test_webhook_timing.py +82 -0
- solvapay_python-0.9.2/tests/webhooks/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/api_baseline.json +4 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/uv.lock +689 -36
- solvapay_python-0.9.0/.github/workflows/publish.yml +0 -15
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/ISSUE_TEMPLATE/question.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.github/dependabot.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/.python-version +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CODEOWNERS +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CODE_OF_CONDUCT.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/CONTRIBUTING.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/LICENSE +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/SUPPORT.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/assets/agent-marketplace.png +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/changelog.d/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/architecture/layers.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/errors.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/guides/fastapi.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/guides/langchain.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/guides/mcp.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/idempotency.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/index.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/migration.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/client.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/exceptions.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/models.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/paywall.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/reference/webhooks.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/rfcs/0001-spending-policy.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/rfcs/0002-openapi-investigation.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/docs/webhooks.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/claim.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/fastmcp-paywall/server.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/agent.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/langchain-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/.streamlit/config.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/PLAN.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/agents.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/app.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/demo_customers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/requirements.txt +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/sdk_gateway.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/marketplace/ui_components.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/agent_langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/model.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/script_async.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/server_mcp.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/examples/multi-framework-paywall/tool.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_async_client.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_config.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_http.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_stability.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/_recipe.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/httpx_transport.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/_transport/middleware.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/asgi.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/adapters/mcp.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/client.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/events.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/exceptions.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/fastapi.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/idempotency.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/models.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/_registry.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/checkout.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/customers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/limits.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/merchant.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/plans.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/products.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/purchases.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/operations/usage.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/core.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/decorators.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/meta.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/policy.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/resolvers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall/state.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/paywall_state.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/py.typed +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/envelope.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/pipeline.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/replay.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/rotation.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/sign.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/src/solvapay/webhooks/verify.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_stability/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_stability/test_stable_returns_identity.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_aclose_cascade.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_error_wrapping.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_headers_case_insensitive.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_middleware_composition.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_protocol_conformance.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_recording_transport.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/_transport/test_retry_transport.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/test_asgi.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/test_langchain_protocol.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/adapters/test_mcp.py +0 -0
- {solvapay_python-0.9.0/tests/contract → solvapay_python-0.9.2/tests/benchmarks}/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/conftest.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/contract/README.md +0 -0
- {solvapay_python-0.9.0/tests/operations → solvapay_python-0.9.2/tests/contract}/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/contract/cassettes/.gitkeep +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/contract/test_contract_ops.py +0 -0
- {solvapay_python-0.9.0/tests/paywall → solvapay_python-0.9.2/tests/operations}/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/operations/test_namespace_api.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/operations/test_path_interpolation.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/operations/test_retry_safety_enum.py +0 -0
- {solvapay_python-0.9.0/tests/webhooks → solvapay_python-0.9.2/tests/paywall}/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_payable_tool_meta.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_resolvers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/paywall/test_split_classes.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_admin.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_api_version.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_async_client.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_checkout.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_config.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_customer.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_errors.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_http.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_idempotency.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_lifecycle.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_limits.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_packaging.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_paywall.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_paywall_state.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_webhook_events.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/test_webhooks.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_async_cache.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_rotation.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_seen_cache_atomic.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_sign.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tests/webhooks/test_webhook_pipeline.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/api_diff.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.2}/tools/importlinter.cfg +0 -0
- {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@
|
|
29
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
@@ -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.
|
|
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
|
|
25
|
-
Requires-Dist: pydantic<2.
|
|
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<
|
|
32
|
+
Requires-Dist: langchain-core<1.5,>=0.3; extra == 'langchain'
|
|
31
33
|
Provides-Extra: mcp
|
|
32
|
-
Requires-Dist: fastmcp<
|
|
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
|
|
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**.
|
|
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
|
|
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,
|
|
311
|
-
|
|
|
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 quality — PyPI attestations (PEP 740 / Sigstore), CycloneDX SBOM on releases, `bandit` + `osv-scanner` CI, Hypothesis-driven secret-redaction property tests, constant-time `verify_webhook` smoke test, explicit PCI-scope statement |
|
|
361
|
+
| v0.9.2 ✅ | Performance & stability quality — cold-import baseline harness, PEP 562 lazy adapter imports, `solvapay.sign_webhook` top-level re-export, microbenchmark harness (`solvapay[bench]`), troubleshooting docs |
|
|
362
|
+
| 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.
|
|
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
|
|
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**.
|
|
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
|
|
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,
|
|
275
|
-
|
|
|
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 quality — PyPI attestations (PEP 740 / Sigstore), CycloneDX SBOM on releases, `bandit` + `osv-scanner` CI, Hypothesis-driven secret-redaction property tests, constant-time `verify_webhook` smoke test, explicit PCI-scope statement |
|
|
323
|
+
| v0.9.2 ✅ | Performance & stability quality — cold-import baseline harness, PEP 562 lazy adapter imports, `solvapay.sign_webhook` top-level re-export, microbenchmark harness (`solvapay[bench]`), troubleshooting docs |
|
|
324
|
+
| 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.
|
|
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
|
-
|
|
17
|
-
|
|
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.
|