Feat/cep8 explicit gating#75
Conversation
Resolves ContextVM#74 by adding full support for the explicit gating payment lifecycle in the ContextVM TypeScript SDK. Includes server middleware for tracking authorization states, client support for auto-retrying intercepted -32042/-32043 errors, and transport modifications to negotiate payment modes.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds CEP-8 “explicit_gating” payment interaction mode support across Nostr client/server transports, including negotiation tags, server-side authorization gating, and client-side auto-retry behavior for -32042/-32043.
Changes:
- Introduces explicit-gating server middleware with canonical invocation hashing + authorization store.
- Adds client negotiation/disclosure plumbing (
payment_interactiontags) and client auto-retry handling for-32042/-32043. - Refactors shared server payment utilities and adds unit + transport-level tests.
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/transport/payments-flow.test.ts | Adds a transport-level test for explicit-gating behavior and retry flow. |
| src/transport/nostr-server/session-store.ts | Stores requested/effective payment interaction on server sessions. |
| src/transport/nostr-server/outbound-response-router.ts | Discloses effective payment_interaction on first response (CEP-8). |
| src/transport/nostr-server/inbound-coordinator.ts | Parses client payment_interaction request and sets per-request context. |
| src/transport/nostr-server-transport.ts | Exposes API to configure supported payment interaction mode. |
| src/transport/nostr-client/server-metadata-store.ts | Persists server-disclosed effective payment interaction mode. |
| src/transport/nostr-client/outbound-sender.ts | Stores raw JSON-RPC request into correlation metadata for retries. |
| src/transport/nostr-client/inbound-coordinator.ts | Captures server-disclosed payment_interaction tag and passes event id to response handler. |
| src/transport/nostr-client/correlation-store.ts | Extends pending request state to include the raw JSON-RPC request. |
| src/transport/nostr-client-transport.ts | Exposes get/set API for payment interaction negotiation and forwards response context. |
| src/transport/middleware.ts | Extends inbound middleware context with paymentInteraction. |
| src/transport/capability-negotiator.ts | Advertises requested payment_interaction tag from the client. |
| src/payments/types.ts | Adds explicit-gating types: interaction mode, error data shapes, canonical identity. |
| src/payments/server-transport-payments.ts | Wires explicit-gating middleware + discovery tags into server transport. |
| src/payments/server-payments.ts | Refactors shared helpers into a new utils module; adds paymentInteraction option. |
| src/payments/server-payments-utils.ts | New module for timeout/capability matching and resolvePrice type guards. |
| src/payments/server-explicit-gating.ts | Implements explicit-gating server middleware returning -32042/-32043. |
| src/payments/server-explicit-gating.test.ts | Unit tests for explicit-gating server middleware. |
| src/payments/constants.ts | Adds explicit-gating error codes and negotiation error code constant. |
| src/payments/client-payments.ts | Adds client-side explicit-gating handling, auto-retry, and pending backoff. |
| src/payments/client-payments.test.ts | Adds tests for -32042/-32043 handling and retry behavior. |
| src/payments/canonical-identity.ts | Computes canonical invocation hash/identity using JSON canonicalization + SHA-256. |
| src/payments/canonical-identity.test.ts | Unit tests for canonical hashing determinism and identity composition. |
| src/payments/authorization-store.ts | Adds LRU+TTL store for pending/granted paid authorizations. |
| src/payments/authorization-store.test.ts | Unit tests for authorization store behavior (TTL, pending, eviction). |
| src/core/constants.ts | Adds PAYMENT_INTERACTION to Nostr tag constants. |
| package.json | Adds json-canonicalize dependency. |
| bun.lock | Locks json-canonicalize dependency. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Fix integration test failure: remove getPendingRequestForEventId() guard from -32042/-32043 handlers. resolveResponse() consumes the correlation entry before the payment wrapper reads it, so the lookup always returned undefined. Use rawRequestCache as the authoritative source for retry requests instead. - Fix TS2769/TS2339 typecheck errors in client-payments.test.ts - Fix TS6133 unused parameter errors in server-explicit-gating.test.ts - Remove as any cast in server-transport-payments.ts - Wire -32602 error in inbound-coordinator.ts for unsupported modes - Delete accidental package-lock.json (project uses bun.lock)
|
@ContextVM-org Up for review! Lmk if the implementation need refining? |
…eraction learning - Server inbound-coordinator: set effectivePaymentInteraction = 'transparent' before early return when rejecting unsupported explicit_gating. Prevents inconsistent session state (requestedPaymentInteraction set but effectivePaymentInteraction undefined). - Client inbound-coordinator: move payment_interaction tag parsing before the initialize-event early return. The server sends this tag once on its first response, which was previously unreachable because the first event triggers setInitializeEvent() + return before the tag parsing code.
|
Thanks for this — it covers a lot of spec ground and the tests are thorough. I found two blocking issues, several significant ones, and a pervasive code-style violation that needs addressing across the diff. 🔴 Blocking1. Server ignores per-session
Fix: Both middlewares need an early guard: if (ctx.paymentInteraction !== 'explicit_gating') { await forward(message); return; }Then 2.
🟠 Significant3. CEP-8 says 4.
5. Unbounded
6. Retry/backoff behavior doesn't match the PR description The description claims "exponential backoff" with a "precise mathematically computed
Either implement true exponential backoff or update the description. 7. Confusing handler logic in the explicit-gating path In 8. Long stream-of-consciousness comments
🟡 Inline
|
|
The fix commits correctly resolve the two original blockers and clean up the inline-import pattern. Here's what still needs attention before merge. 🔴 Missing Tests (blocking)No test covers either of these paths, and both touch claim of 100% backward compatibility:
🟠 Redundancy~55 lines of price-resolution pipeline duplicated between 🟡 Consistency Gaps
🟢 FormattingTwo regressions from commit
|
Feat: CEP-8 Explicit Gating Lifecycle
Resolves: #74
Reference Spec: ContextVM/contextvm-docs#44
Description
This PR introduces full support for the Explicit Gating payment lifecycle (
explicit_gatingmode) in the ContextVM TypeScript SDK, as mandated by the latest CEP-8 specification updates.Previously, the SDK only supported the default
transparentnotification-based payment flow. With this update, servers can now strictly gate priced capabilities by returning JSON-RPC error responses (-32042 Payment Requiredand-32043 Payment Pending), effectively blocking execution until a verifiable payment is made.Key Changes & Architecture
-32042and-32043.payment_interactionto negotiation tags.CanonicalInvocationIdentityutilizing RFC-8785 JSON canonicalization (JCS) and SHA-256 to ensure idempotent matching between paid authorizations and retried executions.createExplicitGatingMiddleware):AuthorizationStorewith strict check-and-set atomicity.-32043with a precise mathematically computedretry_afterif a request races against an active payment verification.ClientSessionto track bothrequestedPaymentInteractionandeffectivePaymentInteraction.payment_interactionmode and safely fallback totransparentmode to prevent injection of untyped interactions.withClientPayments):onPaymentRequiredhandler hook.-32043errors capped at 5MAX_RETRIES, alongside robust memory management (timer cancellation on transport termination).Testing
AuthorizationStoreconcurrency, JCS hashing, and edge cases. (Timings have been buffered to prevent CI flakiness).payments-flow.test.ts) that runs a client request → intercepts-32042→ delegates to user payment logic → auto-retries → consumes authorization → and successfully returns the result.Notes-
AuthorizationStore.trySetPendingrelies on in-memory Maps and is therefore strictly single-process. Extensive doc comments have been added noting that distributed environments should implement an external Redis Redlock using theCanonicalInvocationIdentity.transparentcapability.