Skip to content

Tags: agentscore/python-commerce

Tags

v2.6.0

Toggle v2.6.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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

v2.5.18

Toggle v2.5.18's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

v2.5.17

Toggle v2.5.17's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

v2.5.16

Toggle v2.5.16's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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

v2.5.15

Toggle v2.5.15's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

v2.5.14

Toggle v2.5.14's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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

v2.5.12

Toggle v2.5.12's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

v2.5.11

Toggle v2.5.11's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>

v2.5.10

Toggle v2.5.10's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
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>