Tags: agentscore/python-commerce
Tags
Bump to 2.6.0: surface the pairwise operator handle through the Gate (#… …111) ## Summary Adds `get_operator_handle(request)` across all six identity adapters, plus `ctx.operator_handle` inside `Checkout` hooks. It returns the stable pairwise `oph_...` handle for the account behind the request's operator token. This is the Python half of the same capability landing in the node library, kept level with it. **What it is for.** Merchants keying durable state on identity, prepaid balances above all, need a value that outlives a credential. An `opc_` lives 24h and rotates silently off a 90-day refresh, so state keyed on the token instance is stranded daily, and revoking a leaked token would forfeit the balance it was holding. The handle derives from the account, so rotation, expiry and revocation are free, and it is pairwise per merchant so handles never correlate across stores. **It costs nothing extra.** The handle rides the `/v1/assess` response the gate already fetches, so the accessor is a synchronous read exactly like `get_signer_verdict`: no second round trip on a merchant's hot path, and nothing additional metered against their quota. **One derivation, not six.** `project_operator_handle(raw)` in `identity/core.py` is what every adapter and `Checkout` calls. Written twice it would drift, and that failure is silent: one spelling's handle simply misses the other's rows, so a buyer quietly grows a second balance while every uniqueness check still passes. **Stashed before the allow/deny branch**, so it is available on denials too. This is identity rather than a verdict, and a merchant recording a denial against the buyer needs it on exactly the path where its handler never runs. Anything that is not a well-formed `oph_` string reads as absent rather than being passed through, so a half-configured API can never hand a merchant a value it would write balance rows against. ## Type of change - [ ] Bug fix (no breaking change) - [x] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [ ] Docs, tests, or internal maintenance only ## Public API Additive only; no migration for existing callers. - `get_operator_handle(request)` exported from `identity.{fastapi,middleware,django,aiohttp,sanic}`; the Flask variant takes no argument and reads `g`, matching its sibling accessors. - `ctx.operator_handle` on `CheckoutContext`. - `project_operator_handle` exported from `identity.core`. Requires an API that returns `operator_handle` on `/v1/assess`. Against an older API the field is simply absent and every accessor returns `None`, so upgrading this package alone changes no behavior. ## Test plan New `tests/test_operator_handle.py` (17 cases): the projection accepts a well-formed handle and refuses every non-usable shape (absent, empty, wrong prefix, non-string, `None`, wallet-path response), and each of the six adapters is exercised on its own state mechanism, including Flask both inside and outside an application context. Full local gates: `ruff check` and `ruff format --check` clean, `ty check agentscore_commerce/` clean, `pytest` 1861 passed / 4 skipped with coverage 95.35% against the 95% floor. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed - [x] No secrets, credentials, or personal data in the diff or the tests
Bump to 2.5.18: agentscore-py 2.6.4, x402 2.19.0, and routine deps (#110 ) ## Summary Version bump to 2.5.18 plus the full dependency sweep, together so one release carries both. - **`agentscore-py` floor raised to 2.6.4**, the version just published from the sdk repo. - **`x402` 2.18.0 to 2.19.0.** This was held on a previous pass for lacking protocol-field evidence; the evidence was gathered rather than the hold re-carried. Both wheels were unpacked and compared, and every protocol-governed field count is identical (`maxAmountRequired` 16, `x402Version` 105, `decimals` 2300, `payTo` 26, `asset` 1333, `scheme` 1068), so nothing on the wire contract moved. Five files differ, and the one that matters is a security fix: `x402_http_server_base` normalized the request path by decoding percent-escapes globally, so a decoded byte could create a segment boundary the router never saw, and a wildcard tail carrying a decoded line feed failed to match its own route, skipping payment verification and settlement. 2.19.0 decodes one segment at a time, re-escapes any separator a decode yields, and compiles the route regex with `DOTALL`. - **`stripe` 15.5.0, `trio` 0.34.0, `ty` 0.0.70.** 2.19.0's only `constants.py` change is Monad Mainnet's USDC name (`"USD Coin"` to `"USDC"`), a chain we do not settle on. Base mainnet keeps `"USD Coin"` in both versions, which is the value the EIP-712 domain hash depends on, so signature verification on our rail is untouched. That was checked by reading the network block rather than inferred from the field name. Six transitives stay behind latest (`websockets`, `pydantic-core`, `eth-keyfile`, `construct-typing`, `parsimonious`, `jsonalias`). Each is constrained by its parent (pydantic, solana, web3, sanic, eth-abi, solders) rather than by anything this repo declares, so forcing them would override a peer's own resolution. They move when their parents move. This release should land after the Solana confirmation-timeout fix (#109) so the published version carries both. ## Type of change - [ ] Bug fix (no breaking change) - [ ] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [x] Docs, tests, or internal maintenance only ## Public API None. No exported type, function signature, wire format, or response shape changes. The `x402` peer moves within its existing `>=2.9,<3` range, so no caller migration. ## Test plan Full local gate set on the upgraded lock: `ruff check` and `ruff format --check` (215 files) clean, `ty check agentscore_commerce/` clean, and `pytest tests/` at 1841 passed / 4 skipped with coverage 95.36% against the 95% floor. No test changes were needed since no behavior in this repo changed; the x402 fix is upstream and covered by its own suite. Reproduce with `uv sync --all-extras && uv run pytest tests/`. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed - [x] No secrets, credentials, or personal data in the diff or the tests Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Take pympp 0.10.0, fix an unusable Stripe SPT example, release 2.5.17 (… …#106) ## Summary Three things, all evidence-driven rather than precautionary. **pympp 0.9.1 to 0.10.0**, inside the declared `>=0.6,<1` range. The initial instinct was to pin under it, since pympp sits on the MPP settle path and 0.10.0 rewrites the Tempo intent validators from calldata length checks to real ABI decoding. What settled it against pinning: - The executable pympp surface in this package is exactly two imports, `Receipt` and `Credential`. Both resolve on 0.10.0. - The full suite passes identically on 0.9.1 and 0.10.0, same 1841 tests, same 95.36% coverage. - On the Tempo side, `client.py`, `account.py` and `__init__.py` are **byte-identical** between the two releases, so no signing or domain-separation behavior moved. This is not the mppx-0.8.0 class of change that binds agent, verifier and merchants together. **A Stripe SPT example that could never have worked.** The `mppx_stripe` module docstring told merchants to write `from mpp.server import Mppx` and `Mppx.create(methods=[...])`. `Mppx` has never existed in any pympp release (checked 0.9.1 and 0.10.0 sources directly), and `Mpp.create` takes a single `method` rather than a list, so a merchant copying the example hit `ImportError` on the first line and then an API shape that does not exist. Corrected against the installed package and pympp's own README, with the docstring's imports executed to prove they resolve. The working code in `mppx_server.py` was always correct; only the example was wrong. **agentscore-py 2.6.3**, published minutes before this branch was finalized. ## Type of change - [x] Bug fix (no breaking change) - [ ] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [ ] Docs, tests, or internal maintenance only ## Public API None changed. No exported symbol, submodule, or wire shape moved. The observable changes for a merchant are the `agentscore-py` floor and the version bump. The docstring correction changes documentation only, but it is worth calling out for anyone who copied the old example: `from mpp.server import Mppx` becomes `from mpp.server import Mpp`, and `Mpp.create` takes `method=` (singular) rather than `methods=[...]`, so a merchant accepting Stripe SPT alongside another rail builds one `Mpp` per method. That was already true of the shipped behavior; the docs just described something else. ## Test plan - `uv sync --all-extras --all-groups --upgrade`, then the full gate set: - `uv run ruff check .` all checks passed - `uv run ruff format --check .` 215 files already formatted - `uv run ty check agentscore_commerce/` all checks passed - `uv run pytest tests/`: 1841 passed, 4 skipped, 95.36% coverage (gate is 95%) - osv-scanner v2.4.0 over `uv.lock`: no issues found Baseline captured on 0.9.1 before the bump, so the "identical" claim is a comparison rather than an assertion. The docstring fix was verified by parsing the docstring out of the module and executing its import lines against the installed package, not by reading it. `uv.lock` was re-locked after the version bump, because it embeds the project's own version and a bare `pyproject.toml` bump leaves the two files disagreeing with nothing to catch it. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed - [x] No secrets, credentials, or personal data in the diff or the tests Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sweep deps, take agentscore-py 2.6.2, cut 2.5.16 (#105) ## Summary Dependency sweep and a version bump so this can be released. - `cryptography` 49 to **50**, `uvicorn` 0.52.0 to 0.52.1, `coverage` 7.15.2 to 7.15.3, `bitarray` 3.9.2 to 3.10.0. - The `agentscore-py` floor moves to **>=2.6.2**, the version published from python-sdk#71 today. - Version to **2.5.16**. `cryptography` is a transitive major that the declared range already allowed, so it would have arrived on the next lock refresh regardless. The suite was run against it rather than the bump being taken on trust. The `agentscore-py` floor move is about the *declared* floor rather than resolution: `>=2.6.1` already accepted 2.6.2, and leaving it low is what lets the lock drift back under the version this package is actually tested against. Unlike the two SDK releases today, this version bump has a substantive reason: the commits since v2.5.15 touched shipped source (`aip/http_signature.py`, `aip/jwks.py`), not only CI and tests. `uv.lock` was re-locked after the version bump as well as after the dependency changes. The lock embeds the project's own version, so a `pyproject.toml` bump leaves the two files disagreeing about what the package is with nothing erroring and no gate noticing; it moved 2.5.15 to 2.5.16 here. ## Type of change - [ ] Bug fix (no breaking change) - [ ] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [x] Docs, tests, or internal maintenance only ## Public API **None.** No exported class, function signature, wire format, or 402 body shape changed. The source touched since the last release was spelling inside AIP modules. The only declared-dependency change a consumer sees is the `agentscore-py` floor moving to `>=2.6.2`. That is a patch release of our own SDK with no API change, so no migration follows. ## Test plan - `uv run pytest`: **1841 passed**, 4 skipped, 95.36% coverage against a 95% floor - `ruff check .` and `ruff format --check .`: clean across 215 files - `uv run ty check`: clean - `uv run vulture agentscore_commerce/ --min-confidence 80`: clean, run through the project's own CI invocation rather than a bare `vulture` Reproduce with `uv sync --all-extras --all-groups` followed by those four commands. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed (not applicable) - [x] No secrets, credentials, or personal data in the diff or the tests
deps: lockfile refresh (fastapi 0.141.1 and transitives) (#102) ## Summary Lockfile refresh from the workspace-wide maintenance sweep. Ten packages moved, all transitive or dev tooling, the most notable being `fastapi` 0.140.0 to 0.141.1. No source changes and no direct-dependency range edits. ## Type of change - [ ] Bug fix (no breaking change) - [ ] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [x] Docs, tests, or internal maintenance only ## Public API None. `fastapi` is a peer the middleware integrates against rather than something re-exported, and its declared range is unchanged, so an installing consumer resolves exactly as before. No exported type, signature, or wire format changes. ## Test plan Ran the repo's full Python gate set with exit codes read directly: `ruff check` clean, `ruff format --check` clean at 215 files, `ty check` clean, `pytest` 1841 passed with 4 skipped, and `vulture --min-confidence 80` clean over the package. Every sync used `--all-extras --all-groups` rather than a bare `uv sync`, which would resolve base deps only and quietly strip the venv's extras and dependency groups, breaking test collection. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed - [x] No secrets, credentials, or personal data in the diff or the tests --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Take agentscore-py 2.6.0 (#99) ## Summary Raises the `agentscore-py` floor from `>=2.5.2` to `>=2.6.0`, which is the release adding the optional `refreshable` field to `CredentialItem`. Nothing in this library reads that field, so this is a floor bump rather than a behavior change. The old floor already resolved 2.6.0, so this does not change what a fresh install gets today. It states the intended baseline explicitly instead, which is what stops a consumer pinning an older SDK and getting a shape this library was not tested against. Matches the node-commerce change. Patch bump to 2.5.14. ## Type of change - [ ] Bug fix (no breaking change) - [ ] New feature (no breaking change) - [ ] Breaking change (existing callers must update) - [x] Docs, tests, or internal maintenance only ## Public API None. No exported type, signature, or wire-format change in this package. The SDK's own addition is `NotRequired`, so nothing re-exported through here changes shape. A consumer already on `agentscore-py < 2.6.0` will now resolve an upgrade rather than stay put. That upgrade is itself additive. ## Test plan `uv run pytest` (1841 pass, 4 skipped, 95.36% against a 95% floor), `uv run ruff check`, and `uv run ty check` all clean with 2.6.0 installed. `uv sync --all-extras --all-groups` used throughout so extras and dev groups resolve. ## Checklist - [x] Tests cover the new behavior, and the suite passes locally - [x] Lint, format, and type checks pass - [x] Docs and README examples updated if the public surface changed - [x] No secrets, credentials, or personal data in the diff or the tests
Dep sweep: uv lock refresh (x402 2.16, transitives) (#95) Part of today's org-wide dependency pass. Full-resolution upgrade via uv sync --upgrade --all-extras --all-groups: x402 2.15.0 -> 2.16.0 (within the declared range; the 402/settle suites all pass), ty 0.0.62, certifi + transitives. No manifest range changes; no pinned deps moved. 1841 tests green at 95.36% coverage (Tier A bar), ruff and ty clean. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fix(x402): derive WWW-Authenticate realm from host, not full endpoint… … URL (#93) Surfaced by `npx mppx@latest validate` against store: the challenge realm was `https://agents.agentscore.com/purchase` (full URL with path) while martin/sayer (Node) emit the bare host. Root cause: the Python Checkout auto-derived the MPP realm from the full endpoint `url`. The Node SDK passes `new URL(APP_URL).host`; the WWW-Authenticate realm should be the bare protection-space host. `_realm_from_url` normalizes the derived realm to the host, restoring parity. Falls back to the input unchanged when it has no parseable host (already-bare host, relative path). Advisory-only (the challenge was fully valid and settles); this clears the validator warning and aligns the two SDKs. Unit test covers full-URL, host:port, bare-host, and relative-path cases. Full suite green (1841 passed, 95.36% coverage), ruff + ty clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
fix(x402): strip extensions/resource from paymentPayload before facil… …itator (parity with node 2.7.5) (#92) ## Parity with node-commerce 2.7.5 x402 clients echo the 402 challenge's `extensions` (Bazaar input schema) + `resource` into the payload alongside the signed `payload` + `accepted`. The Coinbase facilitator's `/x402/verify` validates the payment payload against its `x402V2PaymentPayload` schema, which is `{ x402Version, payload, accepted }` and admits neither `extensions` nor `resource`: their presence makes the payload match no union branch (`must match one of [x402V2PaymentPayload, x402V1PaymentPayload]`) and settle fails. Routes with a large echoed Bazaar schema fail while small ones slip through. ## Fix `process_x402_settle` now runs `strip_unsigned_x402_payload_fields` before coercing to the typed model whose `model_dump(by_alias, exclude_none)` reaches the facilitator, dropping only `extensions` + `resource` and keeping `accepted` (which CDP requires, and which `verify_x402_request` reads for network/payTo). Neither stripped block is part of the EIP-3009 signature. Confirmed against the live CDP facilitator: the stripped accepted-nested payload passes schema and reaches signature verify; a flat `{ x402Version, scheme, network, payload }` is rejected with `requires 'accepted'`. ## Tests Unit: drop-both / keep-accepted, unchanged-when-absent (same object), single-field strip, non-dict passthrough. Integration: `process_x402_settle` forwards a payload to BOTH verify and settle with `accepted` kept and `extensions`/`resource` gone. Full suite green (1840 passed, 95.36% coverage), ruff + ty clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
PreviousNext