Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Two identity types: wallet (`X-Wallet-Address`) and operator-token (`X-Operator-

### Operator handle: what durable merchant state keys on

`get_operator_handle(request)` (per-adapter; Flask takes no argument and reads `g`; `ctx.operator_handle` inside `Checkout` hooks) returns the stable pairwise `oph_...` handle for the ACCOUNT behind the request's operator token.
`get_operator_handle(request)` (per-adapter; Flask takes no argument and reads `g`; `ctx.operator_handle` inside `Checkout` hooks, readable from `compute_pricing` onward; the gate that populates it runs AFTER `pre_validate`, so a `pre_validate` read is always `None` and zero-settles whatever keys on it) returns the stable pairwise `oph_...` handle for the ACCOUNT behind the request's operator token.

**Key state on this, never on the token.** An `opc_` lives 24h and rotates silently off a 90-day refresh, so anything keyed on the token instance is stranded daily, and revoking a leaked token would forfeit a prepaid balance. The handle derives from the account, so rotation, expiry and revocation are all free. It is pairwise per consuming merchant, so the same buyer presents an unrelated handle at every store and handles never correlate across them.

Expand Down
6 changes: 6 additions & 0 deletions agentscore_commerce/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ class CheckoutContext:
survives the token rotating, expiring or being revoked, whereas state keyed on the token
instance is stranded every time one rotates. ``None`` when no gate is configured, on
wallet or AIT paths, on anonymous discovery legs, or when the API has no handle salt.

ORDERING: populated by the gate, which runs AFTER ``pre_validate``, so it is readable
from ``compute_pricing`` onward (``mint_recipients``, ``compose_mppx``, ``on_settled``
included) and ALWAYS ``None`` inside ``pre_validate``. A ``pre_validate`` read passes
every keyless-dev test and zero-settles every production request whose flow keys on it
(a live store shipped exactly that).
"""
capture_wallet: Callable[..., Any] | None = None
"""Capture the signer wallet under the operator credential the gate resolved
Expand Down