Bump to 2.8.0: surface the pairwise operator handle through the Gate - #107
Merged
Conversation
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. The handle derives from the account instead, and is pairwise per merchant so handles never correlate across stores. It rides the /v1/assess response the gate already fetches, so reading it is a synchronous cache read like getSignerVerdict, costs no second round trip on a merchant's hot path, and meters nothing extra. An earlier draft resolved it lazily through a separate endpoint; that was dropped because it doubled calls on a metered path and would have shipped every merchant an unmetered endpoint. Available on the DENY branch too, unlike `data`: 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. All five adapters plus Checkout's ctx. Anything that is not a well-formed oph_ string reads as absent rather than passing through, so a half-configured API cannot hand a merchant a value it would write balance rows against.
Minor rather than patch: this adds exported surface (getOperatorHandle on every adapter, ctx.operatorHandle on Checkout, the OperatorHandle type) rather than only changing behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
getOperatorHandle(ctx)across all five identity adapters, plusctx.operatorHandleinsideCheckouthooks. It returns the stable pairwiseoph_...handle for the account behind the request's operator token.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/assessresponse the gate already fetches, so the accessor is a synchronous cache read exactly likegetSignerVerdict: no second round trip on a merchant's hot path, and nothing additional metered against their quota.An earlier draft of this resolved the handle lazily through a separate endpoint. That was dropped deliberately: it doubled the calls on a metered path, it would have shipped every merchant an endpoint that bypasses quota, and it forced the accessor to be async for no benefit a caller could see.
Available on the deny branch too, unlike
data. This is identity rather than a verdict, so a merchant recording a denial against the buyer gets 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
Public API
Additive only; no migration for existing callers.
getOperatorHandle(ctx)exported fromidentity/{hono,express,fastify};gate.operatorHandleon theidentity/{nextjs,web}wrapper surface.ctx.operatorHandleonCheckoutContext.OperatorHandletype andAssessResult.operator_handleexported from the root.Requires an API that returns
operator_handleon/v1/assess. Against an older API the field is simply absent and every accessor returnsundefined, so upgrading this package alone changes no behavior.Test plan
New
tests/operator_handle.test.ts(6 cases) covers the surface end to end against a stubbed assess: the handle reaches the handler; exactly one API call is made and it is assess (the claim most worth holding still, since a regression here silently reintroduces a second round trip); it survives the gate's own cache so a second request still keys correctly; it is present on the deny path; and wallet-path, malformed, and gate-never-ran cases all read as absent.Verified the tests FAIL without the wiring rather than only passing with it: removing the outcome plumbing drops the allow-path and cache-path cases immediately. Restored from a copy taken beforehand, then re-ran green.
Full local gates:
bun run lintclean,bun run typecheckclean across the package and examples,bun run test1814 passed / 4 skipped,bun run knipclean,bun run buildproducing CJS + ESM.Checklist