Skip to content

Bump to 2.6.0: surface the pairwise operator handle through the Gate - #111

Merged
vvillait88 merged 2 commits into
mainfrom
operator-handle
Aug 12, 2026
Merged

Bump to 2.6.0: surface the pairwise operator handle through the Gate#111
vvillait88 merged 2 commits into
mainfrom
operator-handle

Conversation

@vvillait88

Copy link
Copy Markdown
Contributor

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)
  • 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

  • Tests cover the new behavior, and the suite passes locally
  • Lint, format, and type checks pass
  • Docs and README examples updated if the public surface changed
  • No secrets, credentials, or personal data in the diff or the tests

Python half of the same capability as the node library, kept level with it.

Merchants keying durable state on identity (prepaid balances first) 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 held.

It rides the /v1/assess response the gate already fetches, so reading it is a
synchronous read like get_signer_verdict, costs no second round trip on a
merchant's hot path, and meters nothing extra.

project_operator_handle in identity/core.py is the single derivation both the
six adapters and Checkout call. Written twice it would drift, and the failure
is silent: one spelling's handle misses the other's rows and a buyer quietly
grows a second balance.

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 where its handler never runs.
Minor rather than patch: this adds exported surface (get_operator_handle on
every adapter, ctx.operator_handle on Checkout, project_operator_handle)
rather than only changing behavior.
@vvillait88 vvillait88 changed the title Surface the pairwise operator handle through the Gate Bump to 2.6.0: surface the pairwise operator handle through the Gate Aug 12, 2026
@vvillait88
vvillait88 merged commit de9811a into main Aug 12, 2026
7 checks passed
@vvillait88
vvillait88 deleted the operator-handle branch August 12, 2026 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant