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.
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/workflows/ci.yml +30 -2
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/workflows/contract.yml +2 -2
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/workflows/docs.yml +2 -2
- solvapay_python-0.9.1/.github/workflows/publish.yml +32 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.gitignore +1 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CHANGELOG.md +21 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/PKG-INFO +66 -11
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/README.md +61 -6
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/SECURITY.md +15 -2
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/uv.lock +128 -122
- solvapay_python-0.9.1/examples/multi-framework-paywall/uv.lock +1585 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/pyproject.toml +12 -6
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/__init__.py +1 -1
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_redaction.py +37 -0
- solvapay_python-0.9.1/tests/test_webhook_timing.py +82 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/uv.lock +673 -33
- solvapay_python-0.9.0/.github/workflows/publish.yml +0 -15
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/ISSUE_TEMPLATE/question.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.github/dependabot.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/.python-version +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CODEOWNERS +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CODE_OF_CONDUCT.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/CONTRIBUTING.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/LICENSE +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/SUPPORT.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/assets/agent-marketplace.png +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/changelog.d/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/architecture/layers.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/errors.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/guides/fastapi.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/guides/langchain.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/guides/mcp.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/idempotency.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/index.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/migration.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/client.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/exceptions.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/models.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/paywall.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/reference/webhooks.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/rfcs/0001-spending-policy.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/rfcs/0002-openapi-investigation.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/docs/webhooks.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/claim.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/fastmcp-paywall/server.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/agent.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/langchain-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/.streamlit/config.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/PLAN.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/agents.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/app.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/demo_customers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/requirements.txt +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/sdk_gateway.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/marketplace/ui_components.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/.env.example +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/.gitignore +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/agent_langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/model.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/script_async.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/server_mcp.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/examples/multi-framework-paywall/tool.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/mkdocs.yml +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_async_client.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_config.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_http.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_stability.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/_recipe.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/httpx_transport.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/_transport/middleware.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/asgi.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/adapters/mcp.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/client.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/events.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/exceptions.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/fastapi.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/idempotency.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/models.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/_registry.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/checkout.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/customers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/limits.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/merchant.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/plans.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/products.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/purchases.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/operations/usage.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/core.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/decorators.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/meta.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/policy.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/resolvers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall/state.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/paywall_state.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/py.typed +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/envelope.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/pipeline.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/replay.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/rotation.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/sign.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/src/solvapay/webhooks/verify.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_stability/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_stability/test_stable_returns_identity.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_aclose_cascade.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_error_wrapping.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_headers_case_insensitive.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_middleware_composition.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_protocol_conformance.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_recording_transport.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/_transport/test_retry_transport.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/test_asgi.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/test_langchain_protocol.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/adapters/test_mcp.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/conftest.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/README.md +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/cassettes/.gitkeep +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/contract/test_contract_ops.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/test_namespace_api.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/test_path_interpolation.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/operations/test_retry_safety_enum.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_payable_tool_meta.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_resolvers.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/paywall/test_split_classes.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_admin.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_api_version.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_async_client.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_checkout.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_config.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_customer.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_errors.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_http.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_idempotency.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_invariants.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_langchain.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_lifecycle.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_limits.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_packaging.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_paywall.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_paywall_state.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_webhook_events.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/test_webhooks.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/__init__.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_async_cache.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_rotation.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_seen_cache_atomic.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_sign.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tests/webhooks/test_webhook_pipeline.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tools/api_baseline.json +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tools/api_diff.py +0 -0
- {solvapay_python-0.9.0 → solvapay_python-0.9.1}/tools/importlinter.cfg +0 -0
- {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@
|
|
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 }}
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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.
|
|
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
|
|
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
27
|
Provides-Extra: fastapi
|
|
28
28
|
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
29
29
|
Provides-Extra: langchain
|
|
30
|
-
Requires-Dist: langchain-core<
|
|
30
|
+
Requires-Dist: langchain-core<1.5,>=0.3; extra == 'langchain'
|
|
31
31
|
Provides-Extra: mcp
|
|
32
|
-
Requires-Dist: fastmcp<
|
|
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
|
|
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**.
|
|
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
|
|
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,
|
|
311
|
-
|
|
|
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 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 |
|
|
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.
|
|
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
|
|
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,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,
|
|
275
|
-
|
|
|
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 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 |
|
|
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.
|
|
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
|
-
|
|
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 |
|