diff --git a/CLAUDE.md b/CLAUDE.md index 7217a7f..e588377 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. diff --git a/agentscore_commerce/checkout.py b/agentscore_commerce/checkout.py index ba1963d..4ab8a64 100644 --- a/agentscore_commerce/checkout.py +++ b/agentscore_commerce/checkout.py @@ -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