From 324f98d3872a01cc613bbb6dfc9dcdd6aaa6375f Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Tue, 18 Aug 2026 14:18:39 -0700 Subject: [PATCH] docs: state the operatorHandle hook-ordering contract The gate that populates ctx.operatorHandle runs AFTER preValidate, so the field is readable from computePricing onward and always undefined inside preValidate. A preValidate read compiles, passes every keyless-dev test, and zero-settles every production request whose flow keys on it; a live store shipped exactly that, so the contract now lives on the field's JSDoc where the mistake gets made. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 2 +- src/checkout.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2dfe05f..0c53dd9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,7 +84,7 @@ Denial reason codes: `missing_identity`, `identity_verification_required`, `toke ### Operator handle: what durable merchant state keys on -`getOperatorHandle(ctx)` (per-adapter; `gate.operatorHandle` on the Next.js / Web Fetch wrapper surface, `ctx.operatorHandle` inside `Checkout` hooks) returns the stable pairwise `oph_...` handle for the ACCOUNT behind the request's operator token. +`getOperatorHandle(ctx)` (per-adapter; `gate.operatorHandle` on the Next.js / Web Fetch wrapper surface, `ctx.operatorHandle` inside `Checkout` hooks, readable from `computePricing` onward; the gate that populates it runs AFTER `preValidate`, so a `preValidate` read is always `undefined` 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/src/checkout.ts b/src/checkout.ts index 8399eb3..0948d5c 100644 --- a/src/checkout.ts +++ b/src/checkout.ts @@ -289,7 +289,13 @@ export interface CheckoutContext { * token instance is stranded every time one rotates. * * `undefined` when no gate is configured, on wallet or AIT paths, on anonymous discovery - * legs, or when the API has no handle salt configured. */ + * legs, or when the API has no handle salt configured. + * + * ORDERING: populated by the gate, which runs AFTER `preValidate`, so it is readable + * from `computePricing` onward (`mintRecipients`, `composeMppx`, `onSettled` included) + * and ALWAYS `undefined` inside `preValidate`. A `preValidate` read compiles, passes + * every keyless-dev test, and zero-settles every production request whose flow keys on + * it (a live store shipped exactly that). */ operatorHandle?: OperatorHandle; }