diff --git a/CLAUDE.md b/CLAUDE.md index 3760076..8c6a58e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,15 +8,15 @@ Every helper is extracted from a real consumer, not speculated. | Subpath | What it is | |---|---| -| `@agent-score/commerce` (top-level) | `Checkout` orchestrator (the 2.0 high-level surface) for fixed-price one-shot endpoints: one config object + hooks (preValidate, computePricing, mintRecipients, composeMppx, onSettled, gate), auto-derived x402+mppx servers, per-framework adapters `handleHono`/`handleExpress`/`handleFastify`/`handleNextjs`/`handleWeb`, signed UCP routes via `mountUcpRoutes{Hono,Express,Fastify}`, optional `discoveryProbe` config for x402-crawler auto-routing. Plus `computeFirstCheckout` — variable-cost pay-per-result helper (compute-first + exact-x402). Scope is exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); does NOT use x402-upto (Permit2) or Settlement-Overrides — variable cost is captured by running the work pre-settle and emitting a 402 at the exact computed price. `createQuoteCache` — content-hash quote cache used by the compute-first helper (in-memory by default; pass `redisUrl` for distributed deployments). `createResultCache` — the neutral primitive under it: keyed JSON-value cache with the same body-hash key builder; use it to cache any probe-leg result a Checkout-class merchant replays on the settle leg (e.g. a paid upstream call made in `preValidate`). `createDefaultOnDenied` — canonical `onDenied(reason)` factory matching `Checkout`'s gate hook (handles `wallet_signer_mismatch`, `wallet_not_trusted` unfixable fallback, `payment_required`, `token_expired`/`invalid_credential`/`api_error`); merchants pass `merchantName` + `supportEmail` and override `walletNotTrustedMessage` / `paymentRequiredMessage` / `supportContext` for vendor-specific copy. `shouldRunConditionalGate` / `requestsVerificationSession` / `hasIdentityHeader` / `VERIFICATION_SESSION_HEADER`: the conditional-gate predicate (payment credential or an opt-in pre-payment verification-session request) and its parts; `buildIdentityBootstrap`: the matching `identity_bootstrap` 402 block. `hasPaymentHeader` — discriminator that splits discovery legs (no payment credential → 402) from settle legs (`payment-signature` / `x-payment` / `Authorization: Payment `); `hasX402Header` / `hasMppxHeader` — granular dispatch helpers (x402 vs MPP credential present) for routes that branch on rail. `malformedPaymentCredential` — wire-shape gate for payment credentials (not base64 JSON, not token-shaped → reject); `Checkout` runs it before any merchant hook by default (`credentialPreCheck: false` opts out). `defaultReadOnlyOnDenied(reason)` — canonical `onDenied` for read-only resource gates (`GET /orders/:id`): collapses every denial to 401 `unauthorized` + `Cache-Control: no-store` while still spreading `denialReasonToBody` so `agent_instructions` / `verify_url` ride through. `extractOwnerScope(headers) → { walletAddress?, operatorTokenHash? }` — pull canonical owner identity from `X-Wallet-Address` / `X-Operator-Token` with safe token hashing; pair with a wallet-or-token-scoped resource query so plaintext tokens never leave the request. Plus factories: `pricingResult` (cents → typed PricingResult with optional `decimals` for sub-cent precision), `validationResponse{Hono,Express,Fastify,Nextjs,Web}` (4xx envelope per framework) | -| `@agent-score/commerce/identity/{hono,express,fastify,nextjs,web}` | Trust gate middleware (KYC, age, sanctions, jurisdiction). Each adapter exports a `conditionalAgentscoreGate(options)` variant (Next.js / Web Fetch use the wrapper form `withConditionalAgentScoreGate(opts, handler)` / `createConditionalAgentScoreGate(opts) => guard(req)`) that fires only on settle legs — discovery legs (no payment credential) flow through and the handler emits a 402 with all rails. Adapters export ONLY framework-specific surface (gate fns, accessors, `captureWallet`); shared helpers like `hasPaymentHeader` / `denialReasonToBody` import from their canonical home (`@agent-score/commerce/payment` and `@agent-score/commerce` respectively). | +| `@agent-score/commerce` (top-level) | `Checkout` orchestrator for fixed-price one-shot endpoints: one config object + hooks (preValidate, computePricing, mintRecipients, composeMppx, onSettled, gate), auto-derived x402+mppx servers, per-framework adapters `handleHono`/`handleExpress`/`handleFastify`/`handleNextjs`/`handleWeb`, signed UCP routes via `mountUcpRoutes{Hono,Express,Fastify}`, optional `discoveryProbe` config for x402-crawler auto-routing. Plus `computeFirstCheckout`: variable-cost pay-per-result helper (compute-first + exact-x402). Scope is exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); does NOT use x402-upto (Permit2) or Settlement-Overrides: variable cost is captured by running the work pre-settle and emitting a 402 at the exact computed price. `createQuoteCache`: content-hash quote cache used by the compute-first helper (in-memory by default; pass `redisUrl` for distributed deployments). `createResultCache`: the neutral primitive under it: keyed JSON-value cache with the same body-hash key builder; use it to cache any probe-leg result a Checkout-class merchant replays on the settle leg (e.g. a paid upstream call made in `preValidate`). `createDefaultOnDenied`: canonical `onDenied(reason)` factory matching `Checkout`'s gate hook (handles `wallet_signer_mismatch`, `wallet_not_trusted` unfixable fallback, `payment_required`, `token_expired`/`invalid_credential`/`api_error`); merchants pass `merchantName` + `supportEmail` and override `walletNotTrustedMessage` / `paymentRequiredMessage` / `supportContext` for vendor-specific copy. `shouldRunConditionalGate` / `requestsVerificationSession` / `hasIdentityHeader` / `VERIFICATION_SESSION_HEADER`: the conditional-gate predicate (payment credential or an opt-in pre-payment verification-session request) and its parts; `buildIdentityBootstrap`: the matching `identity_bootstrap` 402 block. `hasPaymentHeader`: discriminator that splits discovery legs (no payment credential → 402) from settle legs (`payment-signature` / `x-payment` / `Authorization: Payment `); `hasX402Header` / `hasMppxHeader`: granular dispatch helpers (x402 vs MPP credential present) for routes that branch on rail. `malformedPaymentCredential`: wire-shape gate for payment credentials (not base64 JSON, not token-shaped → reject); `Checkout` runs it before any merchant hook by default (`credentialPreCheck: false` opts out). `defaultReadOnlyOnDenied(reason)`: canonical `onDenied` for read-only resource gates (`GET /orders/:id`): collapses every denial to 401 `unauthorized` + `Cache-Control: no-store` while still spreading `denialReasonToBody` so `agent_instructions` / `verify_url` ride through. `extractOwnerScope(headers) → { walletAddress?, operatorTokenHash? }`: pull canonical owner identity from `X-Wallet-Address` / `X-Operator-Token` with safe token hashing; pair with a wallet-or-token-scoped resource query so plaintext tokens never leave the request. Plus factories: `pricingResult` (cents → typed PricingResult with optional `decimals` for sub-cent precision), `validationResponse{Hono,Express,Fastify,Nextjs,Web}` (4xx envelope per framework) | +| `@agent-score/commerce/identity/{hono,express,fastify,nextjs,web}` | Trust gate middleware (KYC, age, sanctions, jurisdiction). Each adapter exports a `conditionalAgentscoreGate(options)` variant (Next.js / Web Fetch use the wrapper form `withConditionalAgentScoreGate(opts, handler)` / `createConditionalAgentScoreGate(opts) => guard(req)`) that fires only on settle legs: discovery legs (no payment credential) flow through and the handler emits a 402 with all rails. Adapters export ONLY framework-specific surface (gate fns, accessors, `captureWallet`); shared helpers like `hasPaymentHeader` / `denialReasonToBody` import from their canonical home (`@agent-score/commerce/payment` and `@agent-score/commerce` respectively). | | `@agent-score/commerce/identity/policy` | Framework-agnostic per-product / per-tier compliance policy helpers: `PolicyBlock`, `buildGateFromPolicy`, `runGateWithEnforcement`, `shippingCountryAllowed`, `shippingStateAllowed`, `validateShippingAgainstPolicy` (one-call country+state validator that raises `CheckoutValidationError` with the canonical envelope on miss) | -| `@agent-score/commerce/payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `createX402Server` (peer-dep `@x402/core` + `@coinbase/x402` for the Coinbase facilitator), `buildX402AcceptsFor402` (one-call helper for the 402-emit path: builds the requirements via the registered scheme so `extra.name` matches the on-chain USDC contract per network), `buildDefaultCheckoutRails({tempo?, x402Base?, solanaMpp?, stripe?})` (canonical 4-rail `rails` dict factory: merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chainId/token boilerplate. When a caller overrides `network` without pinning `token` / `chainId`, the helper derives them from the network: Base Sepolia → Sepolia USDC + chainId 84532, Solana devnet → devnet USDC mint. Explicit overrides always win. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) AND the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`)), `buildMppxComposeRails({amountUsd, tempoRecipient?, solanaRecipient?, ...})` (per-call intent factory replacing the hand-rolled `[['tempo/charge',{...}],['solana/charge',{...}],['stripe/charge',{...}]]` array; auto-handles USD→atomic conversion for Solana; auto-drops the `stripe/charge` rail with a one-time `console.warn` when `amountUsd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable — many Stripe accounts also reject PI creation below the floor with `amount_too_small`; sub-50-cent APIs pass `includeStripe: false` explicitly to silence the warning), `createMppxServer` (peer-dep `mppx`; the solana rail settles against a static treasury recipient whose USDC ATA is pre-funded out-of-band. `@solana/mpp` keeps the primary recipient's ATA out of scope on the client, so no self-referential split or ATA-creation flag is emitted; `@solana/mpp` 0.7.0 additionally rejects a primary-recipient split in fee-sponsored mode), `composeMppxRequest` (typed wrapper around `mppx.compose(...intents)(request)`; replaces the `(mppx as any).compose(...)` cast in custom `composeMppx` hooks), `mppxChallengeHeaders` (one-call extractor for the 402 path's `Object.fromEntries(challenge.headers)`), `processX402Settle` (verify+settle in one call), `isEvmNetwork`/`isSolanaNetwork` (CAIP-2 discriminators that hide the `startsWith('eip155:')` / `startsWith('solana:')` prefix matching), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header | +| `@agent-score/commerce/payment` | Networks/USDC/rails registries, paymentauth.org directive builders, `createX402Server` (peer-dep `@x402/core` + `@coinbase/x402` for the Coinbase facilitator), `buildX402AcceptsFor402` (one-call helper for the 402-emit path: builds the requirements via the registered scheme so `extra.name` matches the on-chain USDC contract per network), `buildDefaultCheckoutRails({tempo?, x402Base?, solanaMpp?, stripe?})` (canonical 4-rail `rails` dict factory: merchants pass per-rail overrides instead of redeclaring the recipient sentinel + network/chainId/token boilerplate. When a caller overrides `network` without pinning `token` / `chainId`, the helper derives them from the network: Base Sepolia → Sepolia USDC + chainId 84532, Solana devnet → devnet USDC mint. Explicit overrides always win. Solana's `network` field accepts both CAIP-2 (`solana:5eykt4UsFv8…` / `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`) AND the raw `@solana/mpp` form (`mainnet-beta` / `devnet` / `localnet`)), `buildMppxComposeRails({amountUsd, tempoRecipient?, solanaRecipient?, ...})` (per-call intent factory replacing the hand-rolled `[['tempo/charge',{...}],['solana/charge',{...}],['stripe/charge',{...}]]` array; auto-handles USD→atomic conversion for Solana; auto-drops the `stripe/charge` rail with a one-time `console.warn` when `amountUsd < 0.50` since Stripe's fixed ~$0.30 fee makes sub-50-cent charges unprofitable: many Stripe accounts also reject PI creation below the floor with `amount_too_small`; sub-50-cent APIs pass `includeStripe: false` explicitly to silence the warning), `createMppxServer` (peer-dep `mppx`; the solana rail settles against a static treasury recipient whose USDC ATA is pre-funded out-of-band. `@solana/mpp` keeps the primary recipient's ATA out of scope on the client, so no self-referential split or ATA-creation flag is emitted; `@solana/mpp` 0.7.0 additionally rejects a primary-recipient split in fee-sponsored mode), `composeMppxRequest` (typed wrapper around `mppx.compose(...intents)(request)`; replaces the `(mppx as any).compose(...)` cast in custom `composeMppx` hooks), `mppxChallengeHeaders` (one-call extractor for the 402 path's `Object.fromEntries(challenge.headers)`), `processX402Settle` (verify+settle in one call), `isEvmNetwork`/`isSolanaNetwork` (CAIP-2 discriminators that hide the `startsWith('eip155:')` / `startsWith('solana:')` prefix matching), dispatch-by-network, signer extraction, WWW-Authenticate header, Settlement-Overrides header | | `@agent-score/commerce/discovery` | Discovery probe middleware (`isDiscoveryProbeRequest`, `buildDiscoveryProbeResponse`; the x402 sample envelope always carries a v2 `resource` and threads optional `extensions`, and `Checkout` fills both from its own `url`/`resourceInfo`/`discoveryExtensions`, because envelope validators (mppx, x402scan's shared engine) hard-require `resource` and read the Bazaar example input to build valid probe bodies), Bazaar wrapper, `/.well-known/mpp.json` builder, `llms.txt` builder, `skill.md` builder (Claude-Skill-compatible agent-discovery manifest), `buildRedemptionSkillMd` (delivery-neutral; printed/emailed/API-trial codes all covered via `deliveryIntro`/`bodyShape`/`bodyRules`/`extraRecoveryRows` overrides), `buildMerchantIndexJson` + `standardEndpointDescriptions({kind})` (canonical `/` discovery body for goods or API merchants), `buildSuccessNextSteps` (universal Passport-active success block), `buildAgentscoreOnboardingSteps`, OpenAPI snippets, `noindexNonDiscoveryPaths` Hono middleware. Plus the UCP/JWKS publish surface: `buildSignedUcpResponse`, `buildSignedJwksResponse`, `wellKnownPreflightResponse`, `defaultA2aServices`, `bootstrapUcpSigningKey`, framework-neutral `SignedDiscoveryResponse` + per-framework wrappers `signedResponse{Hono,Express,Fastify,Nextjs,Web}` | | `@agent-score/commerce/challenge` | 402-body builders: accepted_methods, identity metadata (auto-attached by `Checkout` when wallet header present), how_to_pay, agent_instructions, build402Body, pricing, agent_memory, `buildValidationError` (4xx body builder), `Receipt` (canonical 200-receipt shape) | -| `@agent-score/commerce/stripe-multichain` | Multichain PaymentIntent helper (`createMultichainPaymentIntent` returns `{ paymentIntentId, depositAddresses }`; read `depositAddresses[network]` directly), `createPayToAddressFromStripePI({request, amountCents, stripe, piCache, networks?, staticRecipients?, metadata?, orderId?, preferredNetwork?})` — one-call per-order payTo resolver matching `Checkout.mintRecipients`: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `piCache.hasAddress` check OR a `staticRecipients` match — the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by `staticRecipients`, caches the merged map, registers static addresses with `piCache.cacheAddress`. `mintMultichainRecipients({...same opts}) => { recipients, paymentIntentId?, reusedFromCredential }` — structured variant that returns the full per-rail map; prefer this when the merchant's `mintRecipients` hook needs all rail addresses (typical multi-rail merchant), and to avoid the "returned-string-is-ambiguous" trap on the settle leg when `staticRecipients` is configured (the bound recipient might be the solana static, not the tempo per-PI). Use `staticRecipients: { solana: '' }` for low-margin endpoints where rotating per-PI Solana addresses can't absorb MPP spec §13.6's ~$0.50 ATA rent per call — the SDK skips Stripe minting on that network and reuses the static recipient forever; pair with a one-time external USDC pre-funding of the recipient's ATA and every settle pays only the per-tx fee. Testnet simulator (`simulateCryptoDeposit`, `simulateDepositIfTestMode`), `simulateDepositForOutcome({outcome, depositAddress, getPaymentIntentId, stripeSecretKey, stripeVersion?})` (dispatches the simulator based on a Checkout / computeFirstCheckout settle outcome; replaces the per-merchant rail-switch + thin `simulateDepositIfTestnet(addr, network)` wrapper), `networkForOutcome` (outcome → simulator network arg, handles both Checkout-shaped `railKey` and computeFirstCheckout-shaped `mppMethod`, accepts bare scheme names AND `/charge` forms), `createPiCache`, `createMppxStripe` | -| `@agent-score/commerce/api` | Re-exports `AgentScore` + `AgentScoreError` from `@agent-score/sdk` | -| `@agent-score/commerce/middleware/{hono,express,fastify,nextjs,web}` | Framework-specific rate-limit middleware. Hono / Express / Fastify expose middleware factories (`rateLimitHono`, `rateLimitExpress`, `rateLimitFastify`); Next.js exposes `withRateLimit(opts, handler)`; Web Fetch exposes `createRateLimit(opts) => guard(req)`. Shared core: `windowSeconds` (default 60), `maxRequests` (default 60), `keyResolver` (default first hop of `x-forwarded-for`), `redisUrl` (optional; lazy-imports `ioredis` when set, falls back to in-memory `Map` otherwise). Mount globally with `app.use('*', rateLimitHono())` before any payment route. `ioredis` is an optional peer dep — merchants without Redis don't install it and get in-memory state per process. | +| `@agent-score/commerce/stripe-multichain` | Multichain PaymentIntent helper (`createMultichainPaymentIntent` returns `{ paymentIntentId, depositAddresses }`; read `depositAddresses[network]` directly), `createPayToAddressFromStripePI({request, amountCents, stripe, piCache, networks?, staticRecipients?, metadata?, orderId?, preferredNetwork?})`: one-call per-order payTo resolver matching `Checkout.mintRecipients`: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `piCache.hasAddress` check OR a `staticRecipients` match: the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by `staticRecipients`, caches the merged map, registers static addresses with `piCache.cacheAddress`. `mintMultichainRecipients({...same opts}) => { recipients, paymentIntentId?, reusedFromCredential }`: structured variant that returns the full per-rail map; prefer this when the merchant's `mintRecipients` hook needs all rail addresses (typical multi-rail merchant), and to avoid the "returned-string-is-ambiguous" trap on the settle leg when `staticRecipients` is configured (the bound recipient might be the solana static, not the tempo per-PI). Use `staticRecipients: { solana: '' }` for low-margin endpoints where rotating per-PI Solana addresses can't absorb MPP spec §13.6's ~$0.50 ATA rent per call: the SDK skips Stripe minting on that network and reuses the static recipient forever; pair with a one-time external USDC pre-funding of the recipient's ATA and every settle pays only the per-tx fee. Testnet simulator (`simulateCryptoDeposit`, `simulateDepositIfTestMode`), `simulateDepositForOutcome({outcome, depositAddress, getPaymentIntentId, stripeSecretKey, stripeVersion?})` (dispatches the simulator based on a Checkout / computeFirstCheckout settle outcome; replaces the per-merchant rail-switch + thin `simulateDepositIfTestnet(addr, network)` wrapper), `networkForOutcome` (outcome → simulator network arg, handles both Checkout-shaped `railKey` and computeFirstCheckout-shaped `mppMethod`, accepts bare scheme names AND `/charge` forms), `createPiCache`, `createMppxStripe` | +| `@agent-score/commerce/api` | Re-exports `AgentScore`, `AgentScoreError`, `AGENTSCORE_TEST_ADDRESSES` and `isAgentScoreTestAddress` from `@agent-score/sdk` | +| `@agent-score/commerce/middleware/{hono,express,fastify,nextjs,web}` | Framework-specific rate-limit middleware. Hono / Express / Fastify expose middleware factories (`rateLimitHono`, `rateLimitExpress`, `rateLimitFastify`); Next.js exposes `withRateLimit(opts, handler)`; Web Fetch exposes `createRateLimit(opts) => guard(req)`. Shared core: `windowSeconds` (default 60), `maxRequests` (default 60), `keyResolver` (default first hop of `x-forwarded-for`), `redisUrl` (optional; lazy-imports `ioredis` when set, falls back to in-memory `Map` otherwise). Mount globally with `app.use('*', rateLimitHono())` before any payment route. `ioredis` is an optional peer dep: merchants without Redis don't install it and get in-memory state per process. | ## Architecture @@ -49,11 +49,11 @@ Peer-dep pattern: payment/x402/mppx/stripe modules `dynamic import` at runtime, | `compute-first-merchant.ts` | Pay-per-result variable-cost merchant via the compute-first + exact-x402 helper (`computeFirstCheckout`). Exact-mode rails only (x402-exact Base, tempo/charge, solana/charge, Stripe SPT); deliberately scoped out of x402-upto (Permit2) and Settlement-Overrides. Probe runs the work + caches by body content-hash; settle replays the cached result at the computed exact price. Pairs with `rateLimitHono` since the probe leg runs work pre-payment. | | `compliance-merchant.ts` | Regulated-goods merchant: full compliance gate + custom `onDenied` composing the denial helpers (`verificationAgentInstructions`, `isFixableDenial`, `buildContactSupportNextSteps`, `denialReasonToBody`/`denialReasonStatus`) | | `per-product-policy-merchant.ts` | Multi-product merchant where each product carries its own compliance policy: wine has hard gate (KYC + 21 + state allowlist), tee has none (anonymous), limited print uses `enforcement: 'soft'` (request KYC, accept anonymous, stamp `identity_status: 'unverified'`). Demonstrates `PolicyBlock`, `buildGateFromPolicy`, `runGateWithEnforcement`, `shippingCountryAllowed`, `shippingStateAllowed`. | -| `signed-ucp-merchant.ts` | Signed UCP profile (`/.well-known/ucp`) + JWKS endpoint (`/.well-known/jwks.json`). AgentScore's `agentscore-profile+jws` is a vendor extension on top of UCP for trust-mode verifiers (regulated-commerce, AP2-aware) that opt into auditable cryptographic provenance — UCP §6 itself does NOT mandate signing; production UCP merchants commonly ship unsigned. Wires ephemeral-for-dev / env-JWK-for-prod signing, kid rotation, and `Cache-Control` posture. Uses `generateUCPSigningKey`, `signUCPProfile`, `buildJWKSResponse`, `UCPSigningKey.fromJWK`, `UCPVerificationError`. Demonstrates the payment-handler builders (`mppPaymentHandler`, `x402PaymentHandler`, `stripeSptPaymentHandler` — see "Payment-handler builders" below). | +| `signed-ucp-merchant.ts` | Signed UCP profile (`/.well-known/ucp`) + JWKS endpoint (`/.well-known/jwks.json`). AgentScore's `agentscore-profile+jws` is a vendor extension on top of UCP for trust-mode verifiers (regulated-commerce, AP2-aware) that opt into auditable cryptographic provenance: UCP §6 itself does NOT mandate signing; production UCP merchants commonly ship unsigned. Wires ephemeral-for-dev / env-JWK-for-prod signing, kid rotation, and `Cache-Control` posture. Uses `generateUCPSigningKey`, `signUCPProfile`, `buildJWKSResponse`, `UCPSigningKey.fromJWK`, `UCPVerificationError`. Demonstrates the payment-handler builders (`mppPaymentHandler`, `x402PaymentHandler`, `stripeSptPaymentHandler`: see "Payment-handler builders" below). | ## Payment-handler builders -The SDK ships protocol-rooted builders for the AgentScore-published payment handlers — vendors compose UCP `payment_handlers` blocks by spreading these helpers instead of hand-writing the verbose binding wrapper: +The SDK ships protocol-rooted builders for the AgentScore-published payment handlers: vendors compose UCP `payment_handlers` blocks by spreading these helpers instead of hand-writing the verbose binding wrapper: ```ts import { buildUCPProfile, mppPaymentHandler, x402PaymentHandler, stripeSptPaymentHandler } from '@agent-score/commerce'; @@ -68,7 +68,7 @@ buildUCPProfile({ }); ``` -Each helper returns `{ [reverse-DNS-key]: [binding] }` so spreading composes the parent map. The handler `version`, spec URL, and schema URL are owned by the helpers (`src/identity/ucp.ts`) — bumping a handler spec version is a one-line change there. mpp + x402 share the same `networks: [{ network, recipient?, ...extras }]` config shape so consumers parse both identically. Recipient is optional — omit when the merchant uses per-order recipients (e.g. Stripe-derived deposit addresses); the authoritative recipient still ships in the 402 body. +Each helper returns `{ [reverse-DNS-key]: [binding] }` so spreading composes the parent map. The handler `version`, spec URL, and schema URL are owned by the helpers (`src/identity/ucp.ts`): bumping a handler spec version is a one-line change there. mpp + x402 share the same `networks: [{ network, recipient?, ...extras }]` config shape so consumers parse both identically. Recipient is optional: omit when the merchant uses per-order recipients (e.g. Stripe-derived deposit addresses); the authoritative recipient still ships in the 402 body. ## Identity model @@ -76,11 +76,11 @@ Two header-based identity types: wallet (`X-Wallet-Address`) and operator-token Denial reason codes: `missing_identity`, `identity_verification_required`, `token_expired`, `invalid_credential`, `wallet_signer_mismatch`, `wallet_auth_requires_wallet_signing`, `wallet_not_trusted`, `api_error`, `payment_required`. Each carries a structured `agent_instructions` JSON block describing concrete recovery actions. See `src/_response.ts` and `src/core.ts` for the canned action copy. -`createSessionOnMissing` auto-mints a verification session when no identity is present AND when `wallet_not_trusted` carries fixable reasons (`kyc_required` / `kyc_pending` / `kyc_failed`) — both paths rewrite the denial to `identity_verification_required` before reaching `onDenied`, so merchants only need to handle one code. When the merchant omits `createSessionOnMissing` from the gate config, `Checkout` auto-defaults it from `gate.apiKey` + `gate.baseUrl` + `gate.context` + `gate.merchantName` — every gated route gets the bootstrap UX out of the box. Merchants that need per-request session context or `onBeforeSession` side effects (goods merchants pre-minting an order_id) supply their own config to override. +`createSessionOnMissing` auto-mints a verification session when no identity is present AND when `wallet_not_trusted` carries fixable reasons (`kyc_required` / `kyc_pending` / `kyc_failed`): both paths rewrite the denial to `identity_verification_required` before reaching `onDenied`, so merchants only need to handle one code. When the merchant omits `createSessionOnMissing` from the gate config, `Checkout` auto-defaults it from `gate.apiKey` + `gate.baseUrl` + `gate.context` + `gate.merchantName`: every gated route gets the bootstrap UX out of the box. Merchants that need per-request session context or `onBeforeSession` side effects (goods merchants pre-minting an order_id) supply their own config to override. **Getting a verify_url before paying.** On an identity-gated `Checkout`, the gate runs only on a settle leg, so a buyer with no identity used to reach the session 403 only by sending a payment credential first (an SPT buyer had to mint one). The discovery 402 now carries an `identity_bootstrap` block (`buildIdentityBootstrap()`) naming `X-Verification-Session: create` whenever the request has no identity header (`hasIdentityHeader`), and a request carrying that header, no identity and no payment credential runs the gate, which answers with the same session-bearing 403. It is opt-in on purpose: scanners replay the Bazaar example body on a schedule, so minting on every identity-less 402 would create a session and (on goods stores) a pending order per probe. Gateless merchants neither advertise nor honor it. -`buildVerificationRequiredBody(reason, opts?)` — canonical body builder for the `identity_verification_required` denial. Spreads `verify_url` / `session_id` / `poll_secret` / `poll_url` / `agent_instructions` from the gate-minted reason into a 4xx envelope with merchant-specific `error.message` and (optionally) `agentInstructions` + `extra` overrides. Saves ~10 LOC of duplicated mapping per merchant. +`buildVerificationRequiredBody(reason, opts?)`: canonical body builder for the `identity_verification_required` denial. Spreads `verify_url` / `session_id` / `poll_secret` / `poll_url` / `agent_instructions` from the gate-minted reason into a 4xx envelope with merchant-specific `error.message` and (optionally) `agentInstructions` + `extra` overrides. Saves ~10 LOC of duplicated mapping per merchant. `getSignerVerdict(ctx)` (per-adapter) returns the cached `signer_match` + `signer_sanctions` verdicts the gate composed on its primary `/v1/assess` call (single round trip; merchants build a 403 with `buildSignerMismatchBody({ result: verdict.signer_match })` when `kind !== 'pass'`). @@ -98,11 +98,11 @@ Anything that is not a well-formed `oph_` string reads as absent rather than bei Captured wallets: `captureWallet(ctx, { walletAddress, network, idempotencyKey })` is fire-and-forget; reads `operator_token` stashed during gating and POSTs to `/v1/credentials/wallets`. No-ops for wallet-authenticated requests. -Wallet-signer-match + signer-sanctions: the gate adapter calls `extractPaymentSigner(request, x402PaymentHeader)` pre-evaluate (covers x402 EIP-3009 `from`, Tempo MPP `did:pkh:eip155` source, Solana MPP `did:pkh:solana` source, plus a Solana `TransferChecked` authority fallback decoded from the credential's signed-tx payload via the optional `@solana/kit` peer) and passes `signer: { address, network }` to the SDK's `assess`. The API returns both `signer_match` (wallet-binding) and `signer_sanctions` (OFAC SDN wallet-address) on the same response; commerce caches both projected verdicts so `getSignerVerdict` is a pure cache read. **Wallet-OFAC SDN enforcement on the `signer` block is unconditional** whenever a signer is present — no `policy.require_sanctions_clear` opt-in required. An SDN hit (or `sanctions_check_unavailable`) flips `decision -> deny` and the gate returns 403 before the handler runs. +Wallet-signer-match + signer-sanctions: the gate adapter calls `extractPaymentSigner(request, x402PaymentHeader)` pre-evaluate (covers x402 EIP-3009 `from`, Tempo MPP `did:pkh:eip155` source, Solana MPP `did:pkh:solana` source, plus a Solana `TransferChecked` authority fallback decoded from the credential's signed-tx payload via the optional `@solana/kit` peer) and passes `signer: { address, network }` to the SDK's `assess`. The API returns both `signer_match` (wallet-binding) and `signer_sanctions` (OFAC SDN wallet-address) on the same response; commerce caches both projected verdicts so `getSignerVerdict` is a pure cache read. **Wallet-OFAC SDN enforcement on the `signer` block is unconditional** whenever a signer is present: no `policy.require_sanctions_clear` opt-in required. An SDN hit (or `sanctions_check_unavailable`) flips `decision -> deny` and the gate returns 403 before the handler runs. ### Always-on wallet OFAC default for gateless merchants -Merchants who do NOT configure a `gate` on `Checkout` (or `computeFirstCheckout`) still get wallet OFAC SDN enforcement on every settle leg. The SDK resolves the API key from `process.env.AGENTSCORE_API_KEY` (or `gate.apiKey` if a partial gate config is present), reads `process.env.AGENTSCORE_BASE_URL` for staging/dev overrides, extracts the payment signer, and calls `/v1/assess` with the signer block. SDN hit → 403 with `wallet_not_trusted` + `decision_reasons: ['sanctions_flagged']`. No key → one-time `[checkout]` / `[.computeFirst]` warning + skip (dev/testnet pattern; see `src/_warnings.ts`). Stripe SPT (no extractable wallet signer) skips silently — Stripe runs its own OFAC screen at customer creation. `hasIdentityGate()` detects identity-bearing flags (`requireKyc`, `requireSanctionsClear`, `minAge`, `allowed/blockedJurisdictions`); when none are set, the 402 body omits `agent_memory` and per-rail commands strip the `-H 'X-Operator-Token: ...'` flag (gateless merchants have no operator to identify). Gate configured WITHOUT `apiKey` falls through to the wallet-OFAC-only path so the merchant gets the strict-liability floor instead of silently allowing. +Merchants who do NOT configure a `gate` on `Checkout` (or `computeFirstCheckout`) still get wallet OFAC SDN enforcement on every settle leg. The SDK resolves the API key from `process.env.AGENTSCORE_API_KEY` (or `gate.apiKey` if a partial gate config is present), reads `process.env.AGENTSCORE_BASE_URL` for staging/dev overrides, extracts the payment signer, and calls `/v1/assess` with the signer block. SDN hit → 403 with `wallet_not_trusted` + `decision_reasons: ['sanctions_flagged']`. No key → one-time `[checkout]` / `[.computeFirst]` warning + skip (dev/testnet pattern; see `src/_warnings.ts`). Stripe SPT (no extractable wallet signer) skips silently: Stripe runs its own OFAC screen at customer creation. `hasIdentityGate()` detects identity-bearing flags (`requireKyc`, `requireSanctionsClear`, `minAge`, `allowed/blockedJurisdictions`); when none are set, the 402 body omits `agent_memory` and per-rail commands strip the `-H 'X-Operator-Token: ...'` flag (gateless merchants have no operator to identify). Gate configured WITHOUT `apiKey` falls through to the wallet-OFAC-only path so the merchant gets the strict-liability floor instead of silently allowing. ### Fail-open (opt-in) diff --git a/README.md b/README.md index 747a467..c36e224 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ npm install hono mppx @x402/core @x402/evm @solana/mpp @solana/kit stripe # wh | `/stripe-multichain` | `createMultichainPaymentIntent` (returns `{ paymentIntentId, depositAddresses }`, read `depositAddresses[network]` directly), `createPayToAddressFromStripePI({request, amountCents, stripe, piCache, networks?, staticRecipients?, metadata?, orderId?, preferredNetwork?})`, per-order payTo resolver: on the settle leg, reuses the buyer's signed-against payTo from the MPP credential (after `piCache.hasAddress` check OR a `staticRecipients` match, the static address is always-accepted because the merchant owns it); on the discovery leg, mints a fresh PI for the rails NOT covered by `staticRecipients`, caches the merged map, registers static addresses with `piCache.cacheAddress` so verify-leg lookups pass. `mintMultichainRecipients({...same opts})`, structured variant that returns `{ recipients: Record, paymentIntentId?, reusedFromCredential }`; prefer this when your `mintRecipients` hook needs the full per-rail map (typical multi-rail merchant). Solana MPP requires a static recipient: pass `staticRecipients: { solana: '' }` and pre-fund its USDC ATA once from any external wallet. Rotating (per-order, Stripe-minted) Solana recipients do not settle, since the settlement transaction does not create the recipient's token account; the SDK logs a warning when one is minted. The static recipient is reused forever, each settle pays only the ~$0.001 per-tx fee, and this also sidesteps the per-call ATA rent (~$0.50 against MPP spec §13.6). `simulateCryptoDeposit`, `createMppxStripe`; `createPiCache` (TTL'd PI / deposit-address cache, Redis-backed when `redisUrl` set, in-memory otherwise), `simulateDepositIfTestMode` (gates on `sk_test_` and looks up the PI for you), `STRIPE_TEST_TX_HASH_SUCCESS` / `STRIPE_TEST_TX_HASH_FAILED` constants. Peer dep on `stripe`. | | `/api` | Everything from `@agent-score/sdk` re-exported in one place: `AgentScore` + `AgentScoreError`, `AGENTSCORE_TEST_ADDRESSES` + `isAgentScoreTestAddress`. **Don't add `@agent-score/sdk` as a separate dep**; the two can drift versions and cause subtle type mismatches. | | `/middleware/{hono,express,fastify,nextjs,web}` | Framework-specific rate-limit middleware. Hono / Express / Fastify expose middleware factories (`rateLimitHono`, `rateLimitExpress`, `rateLimitFastify`); Next.js exposes `withRateLimit(opts, handler)`; Web Fetch exposes `createRateLimit(opts) => guard(req)`. Shared options: `windowSeconds` (default 60), `maxRequests` (default 60), `keyResolver` (default first hop of `x-forwarded-for`), `redisUrl` (lazy-imports `ioredis` when set, in-memory `Map` fallback otherwise), `keyPrefix`. `ioredis` is an optional peer dep, install it only if you want distributed limiting. | -| `@agent-score/commerce` (top-level) | `Checkout` orchestrator + `CheckoutContext` + `CheckoutGateConfig` + `CheckoutValidationError` + `DiscoveryProbeConfig` + `MountUcpRoutesOptions` + `SettleOutcome` + `MppxComposeOutcome` + `PricingResult` (the 2.0 high-level surface: one config object, hooks for preValidate/computePricing/onSettled/mintRecipients/composeMppx, auto-derived x402+mppx servers, per-framework adapters `handleHono`/`handleExpress`/`handleFastify`/`handleNextjs`/`handleWeb`, signed UCP routes via `mountUcpRoutes{Hono,Express,Fastify}`); `pricingResult` (factory: cents-denominated → typed `PricingResult` with embedded `PricingBlock`); `validationResponse{Hono,Express,Fastify,Nextjs,Web}` (per-framework 4xx envelope wrappers); `loadUCPSigningKeyFromEnv` (cached env-driven loader for the UCP signing key, reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via Promise-pinned cache); `hashOperatorToken` (sha256 hex of plaintext `opc_...`, for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext); `extractOwnerScope(headers) → { walletAddress?, operatorTokenHash? }` (canonical owner-identity extractor for caller-scoped resource queries, reads `X-Wallet-Address` / `X-Operator-Token`, hashes the token so plaintext never leaves the request); `hasPaymentHeader` / `hasX402Header` / `hasMppxHeader` (request discriminators, any-credential vs x402 vs MPP); `malformedPaymentCredential` (wire-shape gate: payment headers that are not base64 JSON and not token-shaped are rejected before merchant hooks run; `Checkout` applies it by default, opt out with `credentialPreCheck: false`); `createResultCache` (neutral keyed JSON-value cache with a stable body-hash key builder; cache a probe-leg result, e.g. a paid upstream call made in `preValidate`, and replay it on the settle leg) plus the compute-first-shaped `createQuoteCache` built on it; `defaultReadOnlyOnDenied(reason)` (canonical `onDenied` for read-only resource gates: 401 + `Cache-Control: no-store` while still spreading `denialReasonToBody`); plus the cross-vendor publishers `buildA2AAgentCard` + `buildUCPProfile` (return unsigned payloads: vendor signs + publishes). | +| `@agent-score/commerce` (top-level) | `Checkout` orchestrator + `CheckoutContext` + `CheckoutGateConfig` + `CheckoutValidationError` + `DiscoveryProbeConfig` + `MountUcpRoutesOptions` + `SettleOutcome` + `MppxComposeOutcome` + `PricingResult` (one config object, hooks for preValidate/computePricing/onSettled/mintRecipients/composeMppx, auto-derived x402+mppx servers, per-framework adapters `handleHono`/`handleExpress`/`handleFastify`/`handleNextjs`/`handleWeb`, signed UCP routes via `mountUcpRoutes{Hono,Express,Fastify}`); `pricingResult` (factory: cents-denominated → typed `PricingResult` with embedded `PricingBlock`); `validationResponse{Hono,Express,Fastify,Nextjs,Web}` (per-framework 4xx envelope wrappers); `loadUCPSigningKeyFromEnv` (cached env-driven loader for the UCP signing key, reads `UCP_SIGNING_KEY_JWK_PRIVATE` JSON JWK, detects alg from shape, falls back to ephemeral when unset, sanitizes errors so key bytes never reach logs, concurrent-safe via Promise-pinned cache); `hashOperatorToken` (sha256 hex of plaintext `opc_...`, for merchants persisting `operator_token_id` to their own DB without ever storing the plaintext); `extractOwnerScope(headers) → { walletAddress?, operatorTokenHash? }` (canonical owner-identity extractor for caller-scoped resource queries, reads `X-Wallet-Address` / `X-Operator-Token`, hashes the token so plaintext never leaves the request); `hasPaymentHeader` / `hasX402Header` / `hasMppxHeader` (request discriminators, any-credential vs x402 vs MPP); `malformedPaymentCredential` (wire-shape gate: payment headers that are not base64 JSON and not token-shaped are rejected before merchant hooks run; `Checkout` applies it by default, opt out with `credentialPreCheck: false`); `createResultCache` (neutral keyed JSON-value cache with a stable body-hash key builder; cache a probe-leg result, e.g. a paid upstream call made in `preValidate`, and replay it on the settle leg) plus the compute-first-shaped `createQuoteCache` built on it; `defaultReadOnlyOnDenied(reason)` (canonical `onDenied` for read-only resource gates: 401 + `Cache-Control: no-store` while still spreading `denialReasonToBody`); plus the cross-vendor publishers `buildA2AAgentCard` + `buildUCPProfile` (return unsigned payloads: vendor signs + publishes). | ## Quick start @@ -96,7 +96,7 @@ app.post("/purchase", async (c) => { }); ``` -### Checkout orchestrator (the 2.0 high-level surface) +### Checkout orchestrator `Checkout` is the canonical merchant surface for fixed-price one-shot endpoints: one config object, hooks for the merchant-specific pieces, and the SDK handles 402 emit, identity gating, x402 verify+settle, mppx compose, $0 carve-out, and the per-framework adapter. Most fixed-price merchants reach for `Checkout` first. @@ -279,12 +279,12 @@ return new Response(JSON.stringify(responseBody), { status: 402, headers }); ```typescript import { buildA2AAgentCard, buildUCPProfile, ucpA2AExtension } from "@agent-score/commerce"; -// Google A2A v1.0 Signed Agent Card; publish at /.well-known/agent-card.json. +// A2A v1.0 Agent Card; publish at /.well-known/agent-card.json. // Per UCP §A2A binding, the card MUST declare the canonical UCP extension URI in // `capabilities.extensions[]`; pass `ucpA2AExtension()` with empty capabilities // until you bind formal UCP capabilities (dev.ucp.shopping.checkout, etc.). -// Skills are top-level AgentSkill objects; identity claims live in a separate -// AgentCardSignature (RFC 7515 JWS) wrapping the serialized card. +// Skills are top-level AgentSkill objects. The card is returned unsigned: to sign it, +// compute RFC 7515 JWS entries over the card body and pass them as `signatures`. const card = buildA2AAgentCard({ name, description, @@ -297,7 +297,7 @@ const card = buildA2AAgentCard({ // Google Universal Commerce Protocol; publish at /.well-known/ucp // Output shape: { ucp: { version, services, capabilities, payment_handlers, // name?, supported_versions? }, keys: [...], signature?: "..." } -//, services / capabilities / payment_handlers are MAPS keyed by reverse-DNS +// services / capabilities / payment_handlers are MAPS keyed by reverse-DNS // service / capability / handler name (UCP spec §3 + §6). const profile = buildUCPProfile({ name, diff --git a/SECURITY.md b/SECURITY.md index fe82b97..7e029bb 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -19,5 +19,5 @@ We will acknowledge receipt within 48 hours and aim to release a fix within 7 da | Version | Supported | |---------|-----------| -| 2.x | ✅ | -| < 2.0 | ❌ | +| 3.x | ✅ | +| < 3.0 | ❌ | diff --git a/examples/README.md b/examples/README.md index 1c405f9..d4e3c3c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,11 +7,11 @@ Runnable, copy-pasteable example integrations covering the most common merchant | [`api-provider.ts`](./api-provider.ts) | API provider (Exa-style) | Per-call billing on multiple rails: Tempo MPP + x402 Base + Solana MPP, all driven by `Checkout`. No identity gate, no compliance; pay-or-fail. Demos `Checkout(discoveryProbe: ...)` for x402-crawler auto-routing, `buildMerchantIndexJson` + `standardEndpointDescriptions({ kind: 'api' })` for `GET /` discovery, and `buildRedemptionSkillMd` with the trial-credit body shape on `GET /redemption.md`. | | [`identity-only.ts`](./identity-only.ts) | Compliance gate without payment | Wraps any endpoint with KYC + age + jurisdiction checks; vendor handles their own payment | | [`multi-rail-merchant.ts`](./multi-rail-merchant.ts) | Full agent-commerce merchant | Identity gate + Tempo MPP + x402 Base + Solana MPP + Stripe SPT, all rails accepted via `Checkout`. Demos `pricingResult` (cents → typed PricingResult), `Receipt` + `buildSuccessNextSteps` in `onSettled`, per-order Stripe-multichain deposit minting via `mintRecipients`, and `simulateDepositIfTestMode` for Stripe testnet round-trips. | -| [`stripe-multichain-merchant.ts`](./stripe-multichain-merchant.ts) | Stripe-anchored multi-chain | Stripe PaymentIntent with deposit_options for tempo/base/solana; crypto deposits flow through Stripe. Read `result.depositAddresses[network]` directly. For low-margin endpoints (sub-dollar APIs), use `createPayToAddressFromStripePI` / `mintMultichainRecipients` with `staticRecipients: { solana }` — see the `/stripe-multichain` row in the main `README.md` for the full pattern and economics. | -| [`compute-first-merchant.ts`](./compute-first-merchant.ts) | Pay-per-result variable cost (LLM, transcode, per-token/byte) | The probe leg runs the work server-side, caches the result by body content-hash, and emits a 402 at the **exact** computed price; the retry pays that price and gets the cached result. Exact-mode rails only (x402-exact Base, plus Tempo/Solana/Stripe SPT via a `composeMppx` callback) — deliberately scoped out of x402-upto (Permit2) and Settlement-Overrides. Uses `computeFirstCheckout` + `createQuoteCache`; pairs with `rateLimitHono` since the probe leg runs work pre-payment. | +| [`stripe-multichain-merchant.ts`](./stripe-multichain-merchant.ts) | Stripe-anchored multi-chain | Stripe PaymentIntent with deposit_options for tempo/base/solana; crypto deposits flow through Stripe. Read `result.depositAddresses[network]` directly. For low-margin endpoints (sub-dollar APIs), use `createPayToAddressFromStripePI` / `mintMultichainRecipients` with `staticRecipients: { solana }`: see the `/stripe-multichain` row in the main `README.md` for the full pattern and economics. | +| [`compute-first-merchant.ts`](./compute-first-merchant.ts) | Pay-per-result variable cost (LLM, transcode, per-token/byte) | The probe leg runs the work server-side, caches the result by body content-hash, and emits a 402 at the **exact** computed price; the retry pays that price and gets the cached result. Exact-mode rails only (x402-exact Base, plus Tempo/Solana/Stripe SPT via a `composeMppx` callback): deliberately scoped out of x402-upto (Permit2) and Settlement-Overrides. Uses `computeFirstCheckout` (its cache is `createQuoteCache`); pairs with `rateLimitHono` since the probe leg runs work pre-payment. | | [`compliance-merchant.ts`](./compliance-merchant.ts) | Regulated-goods merchant (wine, cannabis, etc.) | Full compliance gate (KYC + sanctions + age + jurisdiction) + custom `onDenied` composing commerce helpers: `verificationAgentInstructions`, `isFixableDenial`, `buildContactSupportNextSteps`, `denialReasonToBody`/`denialReasonStatus`, `buildSignerMismatchBody`. Shows how vendors write only the business-specific branches and let commerce handle the rest. | | [`per-product-policy-merchant.ts`](./per-product-policy-merchant.ts) | Multi-product merchant with mixed compliance needs | One product hard-gates KYC + 21 + US-state allowlist (wine), one is anonymous (merch, ships anywhere), a third uses `enforcement: 'soft'` to request KYC as a fraud signal but accept anonymous sales (stamps `identity_status: 'unverified'` on the order). Uses `PolicyBlock`, `buildGateFromPolicy`, `runGateWithEnforcement`, and the one-call `validateShippingAgainstPolicy`. | -| [`signed-ucp-merchant.ts`](./signed-ucp-merchant.ts) | Signed UCP profile + JWKS endpoint | One-call mount via `checkout.mountUcpRoutesHono(app, ...)` registers `/.well-known/ucp` + `/.well-known/jwks.json` + the OPTIONS preflights. AgentScore's `agentscore-profile+jws` is a vendor extension on top of UCP for trust-mode verifiers (regulated-commerce, AP2-aware) that opt into auditable profiles; UCP §6 itself does NOT mandate signing — production UCP merchants commonly ship unsigned. This example shows ephemeral-for-dev / env-JWK-for-prod and `bootstrapUcpSigningKey` lifespan-hook usage. | +| [`signed-ucp-merchant.ts`](./signed-ucp-merchant.ts) | Signed UCP profile + JWKS endpoint | One-call mount via `checkout.mountUcpRoutesHono(app, ...)` registers `/.well-known/ucp` + `/.well-known/jwks.json` + the OPTIONS preflights. AgentScore's `agentscore-profile+jws` is a vendor extension on top of UCP for trust-mode verifiers (regulated-commerce, AP2-aware) that opt into auditable profiles; UCP §6 itself does NOT mandate signing: production UCP merchants commonly ship unsigned. This example shows ephemeral-for-dev / env-JWK-for-prod and `bootstrapUcpSigningKey` lifespan-hook usage. | ## How to use diff --git a/examples/api-provider.ts b/examples/api-provider.ts index 8b91199..9da3841 100644 --- a/examples/api-provider.ts +++ b/examples/api-provider.ts @@ -17,7 +17,7 @@ * * Peer deps: * bun add @agent-score/commerce hono mppx @x402/core @x402/evm @solana/mpp @solana/kit - * # @coinbase/x402 optional — only if you want the Coinbase CDP facilitator + * # @coinbase/x402 optional: only if you want the Coinbase CDP facilitator * * Env vars: * TEMPO_RECIPIENT your Tempo wallet for receiving USDC.e diff --git a/examples/compliance-merchant.ts b/examples/compliance-merchant.ts index 852533c..6b0db26 100644 --- a/examples/compliance-merchant.ts +++ b/examples/compliance-merchant.ts @@ -29,7 +29,7 @@ * bun add @agent-score/commerce hono * * Env vars: - * AGENTSCORE_API_KEY — your AgentScore API key + * AGENTSCORE_API_KEY: your AgentScore API key * * Run: bun run examples/compliance-merchant.ts */ diff --git a/examples/compute-first-merchant.ts b/examples/compute-first-merchant.ts index e248ac9..a2cc500 100644 --- a/examples/compute-first-merchant.ts +++ b/examples/compute-first-merchant.ts @@ -11,7 +11,7 @@ * Why this exists (vs x402 upto / Permit2): * - upto's facilitator support is still limited (Coinbase CDP testnet rejects * upto-mode settles today; only mainnet claims support). - * - Permit2 is Ethereum-only — no Solana, no Tempo non-EIP-3009, no Stripe. + * - Permit2 is Ethereum-only: no Solana, no Tempo non-EIP-3009, no Stripe. * - Compute-first works on every exact-mode rail in the ecosystem with no * buyer setup and no facilitator extensions. * @@ -21,11 +21,11 @@ * * This example wires the x402-exact rail on Base only. To add MPP rails * (Tempo, Solana, Stripe SPT), pass a `composeMppx` callback that builds - * mppx intents at the exact cached price — see + * mppx intents at the exact cached price: see * `examples/multi-rail-merchant.ts` for the fixed-price MPP compose pattern; * the compute-first variant is structurally identical except the helper * passes the cached price + recipients into your callback. Stripe SPT - * requires the computed price to be at least $0.50 USD — below that + * requires the computed price to be at least $0.50 USD: below that * Stripe's fixed ~$0.30 fee makes the charge unprofitable, so * `buildMppxComposeRails` auto-drops the stripe rail and sub-50-cent * pay-per-result APIs ship Tempo + x402 + Solana only. @@ -71,7 +71,7 @@ const x402Server = await createX402Server({ const searchHandler = computeFirstCheckout({ name: 'search', url: `${APP_URL}/search`, - // $0.01 per result. Use `0.0001` for sub-cent / per-token pricing — the + // $0.01 per result. Use `0.0001` for sub-cent / per-token pricing: the // helper auto-derives decimal precision from the unit price. unitPriceCents: 1, rails: { diff --git a/examples/identity-only.ts b/examples/identity-only.ts index 2b00667..4ab44c4 100644 --- a/examples/identity-only.ts +++ b/examples/identity-only.ts @@ -14,7 +14,7 @@ * bun add @agent-score/commerce hono * * Env vars: - * AGENTSCORE_API_KEY — your AgentScore API key + * AGENTSCORE_API_KEY: your AgentScore API key * * Run: bun run examples/identity-only.ts */ @@ -51,7 +51,7 @@ app.post('/restricted', async (c) => { const assess = getAgentScoreData(c); // assess includes: { decision, operator, kyc_verified, age_bracket, jurisdiction, ... } - // Run your own business logic here — buy something via your existing Stripe flow, + // Run your own business logic here: buy something via your existing Stripe flow, // grant access to gated content, write to your DB, whatever. AgentScore's job ends // at "this agent is verified, here's their operator id." @@ -74,6 +74,6 @@ app.post('/restricted/capture-wallet-example', async (c) => { }); // ── Public routes (no gate) ──────────────────────────────────────────────── -app.get('/public-info', (c) => c.json({ message: 'open access — no identity required' })); +app.get('/public-info', (c) => c.json({ message: 'open access: no identity required' })); export default app; diff --git a/examples/multi-rail-merchant.ts b/examples/multi-rail-merchant.ts index aaeb8e7..b7147f0 100644 --- a/examples/multi-rail-merchant.ts +++ b/examples/multi-rail-merchant.ts @@ -97,7 +97,7 @@ async function _computePricing(ctx: CheckoutContext): Promise { async function _mintRecipients(ctx: CheckoutContext): Promise> { const totalCents = Math.round((ctx.pricing?.amountUsd ?? 0) * 100); - // `mintMultichainRecipients` returns the full per-rail map in one call — + // `mintMultichainRecipients` returns the full per-rail map in one call: // preferred over the single-string `createPayToAddressFromStripePI` for // multi-rail merchants because there's no second pi-cache lookup to glue // the other rails' addresses back together. On the settle leg it short- @@ -106,7 +106,7 @@ async function _mintRecipients(ctx: CheckoutContext): Promise { const port = Number(process.env.PORT ?? 3010); console.warn(`signed-ucp-merchant listening on :${port}`); -console.warn(' /.well-known/ucp — signed profile'); -console.warn(' /.well-known/jwks.json — public key set'); -console.warn(' /_selftest/ucp — local verify round-trip'); +console.warn(' /.well-known/ucp : signed profile'); +console.warn(' /.well-known/jwks.json : public key set'); +console.warn(' /_selftest/ucp : local verify round-trip'); Bun.serve({ port, fetch: app.fetch }); diff --git a/examples/stripe-multichain-merchant.ts b/examples/stripe-multichain-merchant.ts index b765319..2495292 100644 --- a/examples/stripe-multichain-merchant.ts +++ b/examples/stripe-multichain-merchant.ts @@ -14,7 +14,7 @@ * bun add @agent-score/commerce hono stripe * * Env vars: - * STRIPE_SECRET_KEY — sk_live_... or sk_test_... + * STRIPE_SECRET_KEY : sk_live_... or sk_test_... * * Run: bun run examples/stripe-multichain-merchant.ts */ @@ -47,7 +47,7 @@ app.post('/checkout', async (c) => { }); // 2. Return per-network deposit addresses to the agent (or 402 with addresses - // embedded — see multi-rail-merchant.ts for the full 402 builder pattern). + // embedded: see multi-rail-merchant.ts for the full 402 builder pattern). return c.json({ payment_intent_id: result.paymentIntentId, deposit_addresses: result.depositAddresses, diff --git a/scripts/regenerate-cross-lang-fixtures.ts b/scripts/regenerate-cross-lang-fixtures.ts index b951112..a0314a5 100644 --- a/scripts/regenerate-cross-lang-fixtures.ts +++ b/scripts/regenerate-cross-lang-fixtures.ts @@ -48,7 +48,7 @@ function writeFixture(name: string, env: FixtureEnvelope): void { console.warn(`wrote ${out}`); } -// Spec-compliant binding helpers — each scenario uses these (or variants) so the +// Spec-compliant binding helpers: each scenario uses these (or variants) so the // fixtures cover the full set of canonical UCP fields per binding type. function shopServiceMcp(host: string): UCPServiceBinding { return { @@ -102,7 +102,7 @@ function stripeHandler(config: Record): UCPPaymentHandlerBindin async function main(): Promise { // ------------------------------------------------------------------------- - // node-minimal — empty maps; just metadata + signing keys + // node-minimal: empty maps; just metadata + signing keys // ------------------------------------------------------------------------- { const KID = 'node-minimal-EdDSA'; @@ -123,7 +123,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-es256-rails — multi-transport service + multi-rail + ES256 signing key + // node-es256-rails: multi-transport service + multi-rail + ES256 signing key // ------------------------------------------------------------------------- { const KID = 'node-es256-rails-ES256'; @@ -153,7 +153,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-extras-int — payment_handler config with int + string fields + // node-extras-int: payment_handler config with int + string fields // ------------------------------------------------------------------------- { const KID = 'node-extras-int-EdDSA'; @@ -177,7 +177,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-capability — hand-crafted vendor capability under com.agentscore.identity + // node-capability: hand-crafted vendor capability under com.agentscore.identity // ------------------------------------------------------------------------- { const KID = 'node-capability-EdDSA'; @@ -186,7 +186,7 @@ async function main(): Promise { version: '2026-04-08', spec: 'https://www.agentscore.com/specification/identity', schema: 'https://www.agentscore.com/schemas/ucp/com-agentscore-identity-v1.json', - // `extras` flat on the binding — kyc_required is a vendor field on this binding. + // `extras` flat on the binding: kyc_required is a vendor field on this binding. kyc_required: true, }; const profile = buildUCPProfile({ @@ -209,7 +209,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-unicode — multi-byte UTF-8 in name / endpoint / config + // node-unicode: multi-byte UTF-8 in name / endpoint / config // ------------------------------------------------------------------------- { const KID = 'node-unicode-EdDSA'; @@ -233,7 +233,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-multikey — JWKS with two keys, signed by the newer one + // node-multikey: JWKS with two keys, signed by the newer one // ------------------------------------------------------------------------- { const oldKey = await generateUCPSigningKey({ kid: 'node-multikey-old' }); @@ -257,7 +257,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-emoji-keys — extras with non-ASCII object keys (BMP private use, + // node-emoji-keys: extras with non-ASCII object keys (BMP private use, // CJK compatibility, supplementary plane). Exercises codepoint-vs-UTF-16 sort. // Lives at top-level `extras` (outside the `ucp` envelope). // ------------------------------------------------------------------------- @@ -289,7 +289,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-int-boundary — exercises Number.MAX_SAFE_INTEGER round-trip via extras + // node-int-boundary: exercises Number.MAX_SAFE_INTEGER round-trip via extras // ------------------------------------------------------------------------- { const KID = 'node-int-boundary-EdDSA'; @@ -317,7 +317,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-agentscore-gate-full — exercises buildUCPProfile with a full merchant + // node-agentscore-gate-full: exercises buildUCPProfile with a full merchant // gate policy declared via `agentscore_gate`. Both implementations MUST emit // identical canonical bytes so a profile signed in one verifies in the other. // ------------------------------------------------------------------------- @@ -346,7 +346,7 @@ async function main(): Promise { } // ------------------------------------------------------------------------- - // node-agentscore-gate-blocked — exercises blocked_jurisdictions + // node-agentscore-gate-blocked: exercises blocked_jurisdictions // (mutually exclusive with allowed_jurisdictions) for cross-lang parity. // ------------------------------------------------------------------------- { diff --git a/src/_denial.ts b/src/_denial.ts index 32e5e5f..688ae61 100644 --- a/src/_denial.ts +++ b/src/_denial.ts @@ -2,15 +2,15 @@ * Universal denial helpers shared across every adapter. * * What lives here: - * - `FIXABLE_DENIAL_REASONS` / `isFixableDenial` — classifier for compliance reasons that can + * - `FIXABLE_DENIAL_REASONS` / `isFixableDenial`: classifier for compliance reasons that can * be resolved by re-completing KYC (vs sanctions / age failures which are permanent). - * - `denialReasonStatus` — picks the right HTTP status code per denial code (401 for credential + * - `denialReasonStatus`: picks the right HTTP status code per denial code (401 for credential * problems, 503 for transient API errors, 403 for everything else). - * - `buildSignerMismatchBody` — produces the standard 403 body for a non-pass signer_match + * - `buildSignerMismatchBody`: produces the standard 403 body for a non-pass signer_match * verdict (read via `getSignerVerdict`). - * - `buildContactSupportNextSteps` — standard `next_steps.action: "contact_support"` shape for + * - `buildContactSupportNextSteps`: standard `next_steps.action: "contact_support"` shape for * unfixable compliance denials. - * - `verificationAgentInstructions` — the canned `agent_instructions` block for + * - `verificationAgentInstructions`: the canned `agent_instructions` block for * identity-verification 403s. Vendors can override individual fields. * * Adapters use `denialReasonStatus` inside their default `onDenied` so vendors get the right @@ -24,10 +24,10 @@ import type { DenialReason, VerifyWalletSignerResult } from './core'; * Compliance denial reasons that can be resolved by re-completing KYC. The API emits these * when KYC is missing/pending/failed; the user can re-verify and retry. * - * `jurisdiction_restricted` is NOT in this set — the API only emits it AFTER KYC is verified, + * `jurisdiction_restricted` is NOT in this set: the API only emits it AFTER KYC is verified, * meaning the user's KYC'd country is in the merchant's blocked list (or absent from the * allowed list). Re-doing KYC won't change the country, so it's permanent. Same shape as - * `sanctions_flagged` and `age_insufficient` — surface contact_support, don't waste a + * `sanctions_flagged` and `age_insufficient`: surface contact_support, don't waste a * /v1/sessions mint. */ export const FIXABLE_DENIAL_REASONS: ReadonlySet = new Set([ @@ -40,7 +40,7 @@ export const FIXABLE_DENIAL_REASONS: ReadonlySet = new Set([ * Returns true when a `wallet_not_trusted` denial's reasons are all fixable via KYC * re-verification. False when any reason is permanent (sanctions, age, jurisdiction_restricted). * - * Empty reasons returns false — without a known reason we can't promise a fix, so default to + * Empty reasons returns false: without a known reason we can't promise a fix, so default to * the bare denial path (vendors can override via custom onDenied if they want different * behavior on empty reasons). */ @@ -161,7 +161,7 @@ export function buildContactSupportNextSteps( /** * The canonical `agent_instructions` block for identity-verification 403s. Tells the agent how to * present the verify_url, poll for the operator_token, and retry the original request. Universal - * across every AgentScore-gated merchant — overrides let vendors add merchant-specific steps + * across every AgentScore-gated merchant: overrides let vendors add merchant-specific steps * (e.g. "include order_id when retrying"). */ export function verificationAgentInstructions({ @@ -177,12 +177,12 @@ export function verificationAgentInstructions({ userAction?: string; /** Replace the generic "Retry the original merchant request..." step with a merchant-specific * one (e.g. "Retry POST /purchase with X-Operator-Token AND include order_id..."). When set, - * this REPLACES baseSteps[4] rather than appending — use it instead of `extraSteps[0]` when + * this REPLACES baseSteps[4] rather than appending: use it instead of `extraSteps[0]` when * your retry instruction is a refinement of the canonical retry, not an additional step. */ retryStep?: string; /** Append additional steps after the retry step. Use this for genuinely additional steps * (e.g. "After payment the same call returns 200 with the order"), not for re-stating the - * retry — use `retryStep` for that. */ + * retry: use `retryStep` for that. */ extraSteps?: string[]; /** Override the poll cadence. Default 5 seconds. */ pollIntervalSeconds?: number; @@ -204,10 +204,10 @@ export function verificationAgentInstructions({ [key: string]: unknown; } { const baseSteps = [ - 'Present the verify_url directly to the user — it is a complete, ready-to-open URL with the session token already embedded (e.g. https://www.agentscore.com/verify?session=sess_...). Do NOT modify or construct the URL yourself.', + 'Present the verify_url directly to the user: it is a complete, ready-to-open URL with the session token already embedded (e.g. https://www.agentscore.com/verify?session=sess_...). Do NOT modify or construct the URL yourself.', `Immediately begin polling poll_url every ${pollIntervalSeconds} seconds with header X-Poll-Secret set to poll_secret. The user will complete verification in their browser while you poll in the background.`, 'The user visits the URL, signs in, completes identity verification (photo ID + selfie via Stripe Identity), and closes the tab. They do NOT need to copy or paste anything back to you.', - 'When your poll returns status "verified", extract operator_token from the response. This is a one-time value — save it immediately. Subsequent polls return status "consumed" without the token.', + 'When your poll returns status "verified", extract operator_token from the response. This is a one-time value: save it immediately. Subsequent polls return status "consumed" without the token.', retryStep ?? 'Retry the original merchant request with header X-Operator-Token set to the operator_token value.', ]; diff --git a/src/_redis.ts b/src/_redis.ts index bf70e25..2cdd5b1 100644 --- a/src/_redis.ts +++ b/src/_redis.ts @@ -2,14 +2,14 @@ * and `stripe-multichain/pi-cache` so they don't drift on connect-timeout, * TLS handling, or error-logging posture. * - * `ioredis` is an optional peer dep — callers pass `redisUrl` (or rely on + * `ioredis` is an optional peer dep: callers pass `redisUrl` (or rely on * `process.env.REDIS_URL`); when unset or the lazy import fails, this returns * null and the caller falls back to its in-process `Map`. * * Not part of the public API. */ -/** Minimal Redis surface — each caller intersects with its own usage +/** Minimal Redis surface: each caller intersects with its own usage * (incr/expire for rate-limit, get/set/del for caches). Returning `unknown` * on commands keeps the shape narrow; cast at the call site. */ export interface MinimalRedis { diff --git a/src/_response.ts b/src/_response.ts index f44eddf..cc76676 100644 --- a/src/_response.ts +++ b/src/_response.ts @@ -1,11 +1,11 @@ /** * Shared DenialReason → response body serialization for all adapters. * - * Keeps Hono / Express / Fastify / Web / Next.js defaults aligned — a field added + * Keeps Hono / Express / Fastify / Web / Next.js defaults aligned: a field added * here shows up in every adapter's 403 body automatically, and there's one place * to test the marshaling. * - * Body shape: `{ error: { code, message }, ... }` — matches the canonical AgentScore + * Body shape: `{ error: { code, message }, ... }`: matches the canonical AgentScore * error envelope so downstream agents see one consistent `error.code` + * `error.message` pair regardless of which layer produced the denial. */ @@ -18,24 +18,24 @@ import type { DenialCode, DenialReason } from './core.js'; * `agent_instructions` so every denial carries a machine-readable next step. * * Codes covered: - * - `wallet_not_trusted` — gate never stamps instructions, fallback ensures coverage - * - `payment_required` — gate never stamps; merchant tier misconfig, contact-merchant action - * - `identity_verification_required` — fallback when API didn't return next_steps - * - `token_expired` — fallback when API didn't return next_steps - * - `api_error` — `retry_with_backoff` envelope; sole retry channel (no separate + * - `wallet_not_trusted`: gate never stamps instructions, fallback ensures coverage + * - `payment_required`: gate never stamps; merchant tier misconfig, contact-merchant action + * - `identity_verification_required`: fallback when API didn't return next_steps + * - `token_expired`: fallback when API didn't return next_steps + * - `api_error`: `retry_with_backoff` envelope; sole retry channel (no separate * next_steps block emitted) * * Codes already stamped explicitly upstream in core.ts (`missing_identity`, * `invalid_credential`) and codes that don't go through DenialReason - * (`wallet_signer_mismatch`, `wallet_auth_requires_wallet_signing` — handled by + * (`wallet_signer_mismatch`, `wallet_auth_requires_wallet_signing`: handled by * `getSignerVerdict` + `buildSignerMismatchBody`) are not in this map. */ const WALLET_NOT_TRUSTED_INSTRUCTIONS = JSON.stringify({ action: 'contact_support', steps: [ - 'The wallet\'s operator failed an UNFIXABLE compliance check (sanctions, age, or jurisdiction). `reasons` lists which: `sanctions_flagged` / `age_insufficient` / `jurisdiction_restricted`. KYC re-verification won\'t change the outcome — the policy denial is structural.', + 'The wallet\'s operator failed an UNFIXABLE compliance check (sanctions, age, or jurisdiction). `reasons` lists which: `sanctions_flagged` / `age_insufficient` / `jurisdiction_restricted`. KYC re-verification won\'t change the outcome: the policy denial is structural.', 'Surface the denial to the user with the merchant\'s support contact. Do not retry the same merchant request; do not hand the user a verify_url (verification won\'t fix this code path).', - 'Fixable compliance reasons (`kyc_required`, `kyc_pending`, `kyc_failed`) do NOT land on this code — the gate auto-mints a verification session for those and returns `identity_verification_required` with poll endpoints, same shape as `missing_identity`. `jurisdiction_restricted` IS in the unfixable bucket because the API only emits it after KYC is verified (the user\'s KYC\'d country is in the blocked list — re-doing KYC won\'t change the country).', + 'Fixable compliance reasons (`kyc_required`, `kyc_pending`, `kyc_failed`) do NOT land on this code: the gate auto-mints a verification session for those and returns `identity_verification_required` with poll endpoints, same shape as `missing_identity`. `jurisdiction_restricted` IS in the unfixable bucket because the API only emits it after KYC is verified (the user\'s KYC\'d country is in the blocked list: re-doing KYC won\'t change the country).', ], user_message: 'This purchase is denied by the merchant\'s compliance policy and cannot be resolved by re-verifying. Contact the merchant\'s support if you believe this is in error.', @@ -44,17 +44,17 @@ const WALLET_NOT_TRUSTED_INSTRUCTIONS = JSON.stringify({ const PAYMENT_REQUIRED_INSTRUCTIONS = JSON.stringify({ action: 'contact_merchant', steps: [ - 'The merchant\'s AgentScore account does not have the assess endpoint enabled, so agent identity cannot be evaluated. This is a merchant-side configuration gap — there is no agent-side recovery.', - 'Contact the merchant (their support channel — typically listed in /llms.txt or the OpenAPI servers metadata) so they can resolve the configuration on their side.', + 'The merchant\'s AgentScore account does not have the assess endpoint enabled, so agent identity cannot be evaluated. This is a merchant-side configuration gap: there is no agent-side recovery.', + 'Contact the merchant (their support channel: typically listed in /llms.txt or the OpenAPI servers metadata) so they can resolve the configuration on their side.', ], user_message: - 'This merchant\'s identity gate is misconfigured. Contact the merchant — there\'s nothing to fix on the agent side.', + 'This merchant\'s identity gate is misconfigured. Contact the merchant: there\'s nothing to fix on the agent side.', }); const IDENTITY_VERIFICATION_REQUIRED_FALLBACK_INSTRUCTIONS = JSON.stringify({ action: 'deliver_verify_url_and_poll', steps: [ - 'Share verify_url with the user — they complete identity verification on AgentScore.', + 'Share verify_url with the user: they complete identity verification on AgentScore.', 'If session_id + poll_secret are present in the body, poll poll_url every 5 seconds with header `X-Poll-Secret: ` until status=verified. The poll returns a one-time operator_token.', 'Retry the original request with header `X-Operator-Token: `.', ], @@ -66,11 +66,11 @@ const API_ERROR_INSTRUCTIONS = JSON.stringify({ action: 'retry_with_backoff', steps: [ 'Verification is temporarily unavailable. Retry the request after 5-30 seconds with exponential backoff.', - 'This is NOT a compliance denial — the user does not need to re-verify their identity. Send the same identity headers (X-Wallet-Address or X-Operator-Token) on retry.', + 'This is NOT a compliance denial: the user does not need to re-verify their identity. Send the same identity headers (X-Wallet-Address or X-Operator-Token) on retry.', 'If the request continues to fail after 3+ retries (~60 seconds total), surface the error to the user with the merchant\'s support contact.', ], user_message: - 'Verification is temporarily unavailable. Please try again in a moment — this is a transient issue, not a problem with your account.', + 'Verification is temporarily unavailable. Please try again in a moment: this is a transient issue, not a problem with your account.', }); export const QUOTA_EXCEEDED_INSTRUCTIONS = JSON.stringify({ @@ -87,12 +87,12 @@ export const QUOTA_EXCEEDED_INSTRUCTIONS = JSON.stringify({ const TOKEN_EXPIRED_FALLBACK_INSTRUCTIONS = JSON.stringify({ action: 'deliver_verify_url_and_poll', steps: [ - 'The operator token is expired or revoked. AgentScore auto-mints a fresh verification session — complete it to receive a new opc_...', + 'The operator token is expired or revoked. AgentScore auto-mints a fresh verification session: complete it to receive a new opc_...', 'Share verify_url with the user, then poll poll_url every 5 seconds with header `X-Poll-Secret: ` until status=verified. The poll returns a fresh one-time operator_token.', 'Retry the original request with header `X-Operator-Token: `.', ], user_message: - 'Operator token is expired or revoked. A new verification session has been minted — visit verify_url to refresh.', + 'Operator token is expired or revoked. A new verification session has been minted: visit verify_url to refresh.', }); const DEFAULT_AGENT_INSTRUCTIONS: Partial> = { @@ -111,7 +111,7 @@ const DEFAULT_MESSAGES: Record = { wallet_not_trusted: 'The wallet does not meet the merchant compliance policy.', api_error: - 'AgentScore is unreachable. This is transient — retry in a few seconds.', + 'AgentScore is unreachable. This is transient: retry in a few seconds.', payment_required: 'Assess endpoint not enabled for this merchant. Contact support.', wallet_signer_mismatch: @@ -119,13 +119,13 @@ const DEFAULT_MESSAGES: Record = { wallet_auth_requires_wallet_signing: 'X-Wallet-Address was sent with a rail that has no wallet signature (Stripe SPT / card). Switch to X-Operator-Token, or use a wallet-signing rail (Tempo MPP, x402).', token_expired: - 'The operator token is expired or revoked. A fresh verification session has been minted — visit verify_url to mint a new token.', + 'The operator token is expired or revoked. A fresh verification session has been minted: visit verify_url to mint a new token.', invalid_credential: 'The operator token is not recognized. Switch to a different stored token, or drop the header to bootstrap a fresh session.', }; // Field names the gate claims authority over. Merchant-provided `extra` (from the -// onBeforeSession hook) MUST NOT override these — a buggy or malicious hook could +// onBeforeSession hook) MUST NOT override these: a buggy or malicious hook could // otherwise replace `verify_url` with a phishing URL or drop agent_instructions. const RESERVED_FIELDS = new Set([ 'error', @@ -166,7 +166,7 @@ const RESERVED_FIELDS = new Set([ * * Goods merchants that surface an `order_id` (or similar) from * `createSessionOnMissing.onBeforeSession` get it for free via - * `denialReasonToBody(reason)`'s `reason.extra` passthrough — but can also + * `denialReasonToBody(reason)`'s `reason.extra` passthrough: but can also * pass `opts.extra` for fallbacks (e.g. when invoked outside the auto-mint * path and order_id needs to come from the validated context). */ @@ -216,7 +216,7 @@ export function denialReasonToBody(reason: DenialReason): Record { case 'invalid_claims': return 'insufficient_claims'; case 'key_unavailable': - // The IdP's JWKS could not be fetched/resolved — our infra couldn't reach a trusted + // The IdP's JWKS could not be fetched/resolved: our infra couldn't reach a trusted // issuer, not a client-side auth failure. Distinct code so agents back off + retry // rather than uselessly re-signing. return 'idp_unavailable'; @@ -154,8 +154,8 @@ export interface AipErrorRequirements { /** * Build an RFC 9457 problem-details body for an AIP verify failure. Adapters serialize this as * `application/problem+json` with {@link aipErrorStatus}. Optionally carries the merchant's - * requirements — `trusted_issuers` on untrusted_issuer; `required_claims` / `required_trust_level` / - * `required_amr` on insufficient_claims — so the agent learns what would satisfy the gate. + * requirements: `trusted_issuers` on untrusted_issuer; `required_claims` / `required_trust_level` / + * `required_amr` on insufficient_claims: so the agent learns what would satisfy the gate. */ export const buildAipErrorBody = (failure: VerifyAitFailure, requirements?: AipErrorRequirements): AipErrorBody => { const code = aipErrorCode(failure); @@ -185,7 +185,7 @@ export const buildAipErrorBody = (failure: VerifyAitFailure, requirements?: AipE * surfaces as one of these AgentScore codes; the spec's fixed error set expresses each as: * - `token_expired` → `expired_token` (401) * - `invalid_credential` → `invalid_signature` (401) - * - `api_error` → `idp_unavailable` (503, transient — the claims couldn't be evaluated) + * - `api_error` → `idp_unavailable` (503, transient: the claims couldn't be evaluated) * - everything else (compliance: `wallet_not_trusted` + `sanctions_flagged` / `age_insufficient` * / `jurisdiction_restricted` / `kyc_*`) → `insufficient_claims` (403): the AIT did not attest * (or attested a failing value for) the required compliance claim. @@ -207,8 +207,8 @@ const aipPolicyDenyCode = (code: string): { code: string; status: 401 | 403 | 50 * Wrap an AgentScore AIT-path denial body in the RFC 9457 + AIP-spec superset. * * Reuses {@link buildAipErrorBody}'s SHAPE convention (`type`/`title`/`status`/`detail` + - * escalation extensions) but for the *policy-deny* case — a verified AIT that `/v1/assess` then - * denied — which carries an AgentScore compliance/credential code, not a verify-failure reason. + * escalation extensions) but for the *policy-deny* case: a verified AIT that `/v1/assess` then + * denied: which carries an AgentScore compliance/credential code, not a verify-failure reason. * * The result is a SUPERSET: the canonical `{ error, agent_instructions, ... }` body is spread in * verbatim (so existing consumers keep parsing `error.code`), with the RFC 9457 envelope layered @@ -238,18 +238,18 @@ export const buildAipPolicyDenyBody = ( }; // Escalation extensions, scoped exactly as the spec mandates: `required_claims` / // `required_trust_level` / `required_amr` on insufficient_claims. `trusted_issuers` belongs to - // untrusted_issuer — a VERIFY failure that never reaches the policy-deny path — so it is not + // untrusted_issuer (a VERIFY failure that never reaches the policy-deny path) so it is not // emitted here (the edge-deny `buildAipErrorBody` owns that one). if (requirements && spec.code === 'insufficient_claims') { if (requirements.requiredClaims?.length) superset.required_claims = requirements.requiredClaims; if (requirements.requiredTrustLevel !== undefined) superset.required_trust_level = requirements.requiredTrustLevel; if (requirements.requiredAmr?.length) superset.required_amr = requirements.requiredAmr; } - // Spread the canonical body LAST so `error` / `agent_instructions` / `reasons` win verbatim — + // Spread the canonical body LAST so `error` / `agent_instructions` / `reasons` win verbatim: // the RFC 9457 fields are additive and never clobber the rich AgentScore scheme. The envelope // fields themselves (`type` / `title` / `status` / `detail`) are RESERVED the other way: the // canonical body never carries them legitimately, but a merchant `onBeforeSession` hook's - // `extra` rides through `denialReasonToBody` unfiltered — strip them so a smuggled `status` + // `extra` rides through `denialReasonToBody` unfiltered: strip them so a smuggled `status` // can't rewrite the problem+json envelope (or the HTTP status Checkout derives from it). const { type: _type, title: _title, status: _status, detail: _detail, ...rest } = body; return { ...superset, ...rest }; @@ -313,7 +313,7 @@ const requirementsFromOptions = (opts: AipGateOptions): AipErrorRequirements => }); /** - * Verify the AIP credential AND enforce the gate's trust_level / auth.amr requirement in one call — + * Verify the AIP credential AND enforce the gate's trust_level / auth.amr requirement in one call: * the standalone-adapter counterpart to {@link verifyAitRequest}. Returns the verified AIT, or an * RFC 9457 denial body (a verify failure → its wire code; trust insufficient → weak_auth) carrying * the merchant's `required_*` / `trusted_issuers` so the agent can self-correct. diff --git a/src/aip/http-signature.ts b/src/aip/http-signature.ts index d533e01..6950ce5 100644 --- a/src/aip/http-signature.ts +++ b/src/aip/http-signature.ts @@ -1,5 +1,5 @@ /** - * RFC 9421 HTTP Message Signatures — the AIP-constrained subset. + * RFC 9421 HTTP Message Signatures: the AIP-constrained subset. * * AIP (Agentic Identity Protocol) binds an Agent Identity Token (AIT) to the * agent that presents it: the agent signs each HTTP request with the private key whose @@ -29,7 +29,7 @@ const { subtle } = globalThis.crypto; /** Runtime-agnostic base64 (standard, not base64url) codecs. The verify path runs on every AIT * check, including the Fetch-native web / nextjs adapters that target Cloudflare Workers / Vercel - * Edge where the Node `Buffer` global is undefined — so decode/encode via `atob`/`btoa`, which are + * Edge where the Node `Buffer` global is undefined: so decode/encode via `atob`/`btoa`, which are * available on every standards runtime (and Node ≥16). */ const b64ToBytes = (b64: string): Uint8Array => { const bin = atob(b64); @@ -55,13 +55,13 @@ export const AIP_SIGNATURE_TAG = 'agent-identity'; const DEFAULT_MAX_SKEW_SECONDS = 60; /** Hard ceiling on the PoP signature's own declared lifetime (`expires - created`), in seconds. - * Requiring `created`+`expires` bounds replay to the declared window — but with no ceiling a + * Requiring `created`+`expires` bounds replay to the declared window: but with no ceiling a * malicious trusted-issuer agent could set `expires = created + (AIT lifetime)` and replay for the * full window. Cap it tightly so every accepted PoP is short-lived. First-party `pay` signs a 60s * window, so it passes; this only bites a signer that declares an over-long PoP. Matches the * authoritative API verifier's `MAX_POP_WINDOW_SECONDS` (the AgentScore API verifier) so the * edge (standalone `aipGate`) and the API can't drift. (Distinct from the AIT JWT's `exp - iat` - * ceiling in verify.ts — this is the HTTP-signature layer.) */ + * ceiling in verify.ts: this is the HTTP-signature layer.) */ export const MAX_POP_WINDOW_SECONDS = 120; /** Parameters parsed from (or used to build) a `Signature-Input` member. */ @@ -172,8 +172,8 @@ const serializeParams = (p: SignatureParams): string => { * `@signature-params` line. Components are joined by `\n` with no trailing newline. * Throws if a covered component has no available value. * - * On the VERIFY path, pass `rawSignatureParams` — the member value exactly as received from - * `Signature-Input` — so the base reproduces the signer's serialization byte-for-byte regardless + * On the VERIFY path, pass `rawSignatureParams`: the member value exactly as received from + * `Signature-Input`: so the base reproduces the signer's serialization byte-for-byte regardless * of the order they emitted the params in (RFC 9421 §2.3 puts no order on them). Without it the * line is re-serialized in our canonical order (the SIGN path), which would wrongly reject a * spec-legal signer that ordered params differently. @@ -207,7 +207,7 @@ class MissingComponentError extends Error { * Parse a `Signature-Input` dictionary and return the member tagged `tag`. The tag is REQUIRED * (the AIP spec mandates `tag="agent-identity"`): an untagged member is skipped like any * wrong-tagged member. `rawParams` is the member's value exactly as received (the inner list + - * its parameters, byte-for-byte, trimmed of surrounding OWS only) — the verifier echoes it into + * its parameters, byte-for-byte, trimmed of surrounding OWS only): the verifier echoes it into * the `"@signature-params"` base line so the signer's param order is preserved. * Returns null if no AIP member is found or the member is malformed. */ @@ -326,7 +326,7 @@ const parseInnerListMember = (value: string): SignatureParams | null => { * 3. REQUIRE both `created` and `expires`, reject an over-long declared window * (`expires - created` > MAX_POP_WINDOW_SECONDS → `pop_window_too_long`), then enforce them * against `now` with skew tolerance. Both are mandatory: an optional time bound is no time - * bound — without `expires` a captured `(token, Signature-Input, Signature)` triple is + * bound: without `expires` a captured `(token, Signature-Input, Signature)` triple is * replayable for the whole AIT lifetime. A signature omitting either is rejected * (`created_missing` / `expires_missing`). This matches the authoritative API verifier * (the AgentScore API verifier) so a merchant running `aipGate` STANDALONE (the @@ -334,7 +334,7 @@ const parseInnerListMember = (value: string): SignatureParams | null => { * 4. confirm `keyid` equals the RFC 7638 thumbprint of `cnf.jwk` * 5. reconstruct the signature base and verify Ed25519 over it * - * NOTE: this is a STATELESS verifier — it bounds the replay WINDOW but does not dedupe within it. + * NOTE: this is a STATELESS verifier: it bounds the replay WINDOW but does not dedupe within it. * A captured triple can still be replayed until `expires` (≤ MAX_POP_WINDOW_SECONDS + skew from * `created`). A stateful seen-signature cache (as in the authoritative API) is out of scope for the * SDK edge; the tight window bound is the meaningful mitigation here. @@ -349,7 +349,7 @@ export const verifyMessageSignature = async ( // The `alg` param is optional in RFC 9421 (the verifier derives the algorithm from the key); // when a signer does include it, the registered HTTP-sig label is `ed25519`. Accept that plus the // JWS spelling `EdDSA`, case-insensitively, so a spec-loose external signer isn't wrongly rejected - // — the actual key type is still pinned to OKP/Ed25519 below, so this only affects the label. + // the actual key type is still pinned to OKP/Ed25519 below, so this only affects the label. if (params.alg !== undefined && !['ed25519', 'eddsa'].includes(params.alg.toLowerCase())) { return { ok: false, reason: 'unsupported_alg' }; } @@ -362,17 +362,17 @@ export const verifyMessageSignature = async ( } // REQUIRE both `created` and `expires`. Treating them as optional leaves an unbounded replay - // window — a captured signature with no `expires` is valid for the AIT's full lifetime. Reject + // window: a captured signature with no `expires` is valid for the AIT's full lifetime. Reject // when either is absent so every accepted PoP carries an explicit, enforceable time bound. (Our // pay signer always emits both with a 60s window; this only rejects spec-loose external signers.) if (params.created === undefined) { return { ok: false, reason: 'created_missing' }; } if (params.expires === undefined) { return { ok: false, reason: 'expires_missing' }; } // Bound the PoP's own declared lifetime. created+expires alone only bound replay to whatever - // window the SIGNER chose — a malicious trusted-issuer agent could declare a window as wide as the + // window the SIGNER chose: a malicious trusted-issuer agent could declare a window as wide as the // AIT lifetime and replay for all of it. Reject an over-long window so every accepted PoP is // short-lived. (pay signs 60s; this only bites a signer declaring > MAX_POP_WINDOW_SECONDS.) - // A NEGATIVE window (expires before created) is equally malformed — without the explicit check + // A NEGATIVE window (expires before created) is equally malformed: without the explicit check // it would slip under the cap (negative < 120). if (params.expires < params.created || params.expires - params.created > MAX_POP_WINDOW_SECONDS) { return { ok: false, reason: 'pop_window_too_long' }; @@ -393,7 +393,7 @@ export const verifyMessageSignature = async ( // Ed25519 (OKP). Validate the key shape BEFORE thumbprinting / importing: a malformed JWK // (missing or non-string `x`) makes `calculateJwkThumbprint` throw, and a non-OKP key (e.g. a // P-256 EC cnf) makes `importJWK(... 'EdDSA')` throw JOSENotSupported. Neither call site below - // catches, so an unguarded throw would crash the gate — reject with a typed failure instead. + // catches, so an unguarded throw would crash the gate: reject with a typed failure instead. // (Note: the JWT alg allowlist permits ES256 for the IDP *issuer* signing key, a different key.) const cnf = input.cnfJwk as { kty?: unknown; crv?: unknown; x?: unknown }; if (cnf.kty !== 'OKP' || cnf.crv !== 'Ed25519' || typeof cnf.x !== 'string' || cnf.x.length === 0) { diff --git a/src/aip/jwks.ts b/src/aip/jwks.ts index aa8e4c1..260e4fc 100644 --- a/src/aip/jwks.ts +++ b/src/aip/jwks.ts @@ -4,16 +4,16 @@ * Verifiers resolve an IdP's public keys from `https://{iss}/.well-known/agent-identity/jwks.json` * (the spec's well-known path). This module owns: * - * - **Trusted-issuer enforcement** — only `iss` values on the allowlist are fetched, compared + * - **Trusted-issuer enforcement**: only `iss` values on the allowlist are fetched, compared * after URL canonicalization (lowercase scheme+host, no default port, no trailing slash) so * `https://issuer.example` and `https://issuer.example/` match. - * - **HTTPS-only** — JWKS over plain HTTP is MITM-vulnerable; we refuse it. - * - **Caching with a HARD cap** — we honor `Cache-Control: max-age` as advisory but never + * - **HTTPS-only**: JWKS over plain HTTP is MITM-vulnerable; we refuse it. + * - **Caching with a HARD cap**: we honor `Cache-Control: max-age` as advisory but never * cache longer than {@link HARD_MAX_CACHE_SECONDS}, regardless of what the IdP sends. A * compromised IdP can't pin stale keys with `max-age=31536000`. - * - **kid-miss refresh** — a lookup for a `kid` not in the cached set triggers one refetch + * - **kid-miss refresh**: a lookup for a `kid` not in the cached set triggers one refetch * (rotation may have published a new key inside the cache window). - * - **use:"sig" filtering** — only signing keys are returned. + * - **use:"sig" filtering**: only signing keys are returned. * * Pure-ish: the only I/O is `fetch`, injectable for tests. */ @@ -35,11 +35,11 @@ export const DEFAULT_CACHE_SECONDS = 300; // 5m * `cooldownDuration: 30_000` (jose `createRemoteJWKSet`, `the AgentScore API verifier`). Without * it, an unauthenticated attacker who sends a stream of tokens with unknown `kid`s (the `kid`/`iss` * are decoded BEFORE signature verification, and the canonical issuer is always trusted) forces one - * upstream JWKS GET per request — a refetch-amplification / DoS vector against the issuer. Stamping + * upstream JWKS GET per request: a refetch-amplification / DoS vector against the issuer. Stamping * the cooldown on FAILURE too (a negative cache) closes the cold/erroring-issuer variant: a stream * of tokens against an issuer whose JWKS is down would otherwise retry the GET on every request. */ -export const REFETCH_COOLDOWN_MS = 30_000; // 30s — matches the API verifier +export const REFETCH_COOLDOWN_MS = 30_000; // 30s: matches the API verifier type FetchLike = (url: string, init?: { headers?: Record }) => Promise<{ ok: boolean; @@ -49,7 +49,7 @@ type FetchLike = (url: string, init?: { headers?: Record }) => P }>; /** AgentScore's own AIT issuer. ALWAYS trusted by every {@link JwksCache} (and therefore every - * gate/adapter built on it) without the merchant listing it — this SDK is the AgentScore + * gate/adapter built on it) without the merchant listing it: this SDK is the AgentScore * verifier, so a merchant can't accidentally fail to trust AgentScore-issued AITs. `trustedIssuers` * only needs to name ADDITIONAL external issuers. */ export const AGENTSCORE_CANONICAL_ISSUER = 'https://www.agentscore.com'; @@ -81,13 +81,13 @@ export type JwksLookupResult = interface CachedKeys { keys: JWK[]; expiresAt: number; // ms - /** Until this timestamp (ms), no lookup will hit upstream — a kid-miss within a fresh cache + /** Until this timestamp (ms), no lookup will hit upstream: a kid-miss within a fresh cache * returns `key_not_found`, and a cold/expired cache returns `lastFailure` without fetching. * Stamped to `now + REFETCH_COOLDOWN_MS` on every fetch ATTEMPT (success or failure). This is * the refetch-amplification / DoS guard (see {@link REFETCH_COOLDOWN_MS}). */ cooldownUntil: number; // ms /** Why the last fetch attempt failed, when it did. Served (without refetching) to lookups that - * land within the cooldown with no usable cached keys — the negative-cache entry for a + * land within the cooldown with no usable cached keys: the negative-cache entry for a * cold/erroring issuer. Absent after a successful fetch. */ lastFailure?: JwksLookupFailure; } @@ -143,7 +143,7 @@ export class JwksCache { private readonly now: () => number; private readonly userAgent: string; private readonly cache = new Map(); - /** Per-issuer in-flight refresh promise — coalesces concurrent refreshes to ONE upstream fetch. + /** Per-issuer in-flight refresh promise: coalesces concurrent refreshes to ONE upstream fetch. * Without it, a concurrent burst of distinct-kid lookups on a cold/expired cache each call * `refresh()` before any has populated the cache → N parallel JWKS GETs (refetch amplification). * The cooldown only suppresses SEQUENTIAL refetches; single-flight suppresses CONCURRENT ones. @@ -188,7 +188,7 @@ export class JwksCache { const hit = this.select(cached.keys, kid); if (hit) { return { ok: true, key: hit }; } // kid miss within the cache window. Normally we'd force one refetch (rotation may have - // published a new key) — but only once the refetch cooldown has elapsed. WITHIN the + // published a new key): but only once the refetch cooldown has elapsed. WITHIN the // cooldown we return key_not_found WITHOUT refetching. This caps JWKS GETs at ~1 per issuer // per cooldown regardless of how many unknown-kid tokens an attacker streams (the DoS // guard). Once the cooldown passes we fall through to a single refetch, because rotation @@ -199,7 +199,7 @@ export class JwksCache { // Past the cooldown: fall through to a single forced refetch below. } else if (cached && this.now() < cached.cooldownUntil) { // No usable cached keys (cold fetch failed, or the cache expired) and still inside the - // refetch cooldown — fail WITHOUT an upstream GET. This is the negative cache for a + // refetch cooldown: fail WITHOUT an upstream GET. This is the negative cache for a // cold/erroring issuer: sequential lookups within the cooldown cost zero fetches. return { ok: false, reason: cached.lastFailure ?? 'key_not_found' }; } @@ -274,7 +274,7 @@ export class JwksCache { } /** Stamp the per-issuer refetch cooldown on a FAILED fetch attempt (the negative cache). Any - * previously cached keys + expiry are preserved — a failed kid-miss refetch must not nuke a + * previously cached keys + expiry are preserved: a failed kid-miss refetch must not nuke a * still-fresh key set; only the cooldown and failure reason are updated. */ private stampFailure(canonIssuer: string, reason: JwksLookupFailure): { ok: false; reason: JwksLookupFailure } { const now = this.now(); diff --git a/src/aip/request.ts b/src/aip/request.ts index 93dc094..7338b89 100644 --- a/src/aip/request.ts +++ b/src/aip/request.ts @@ -87,7 +87,7 @@ export const buildVerifyContextFromParts = (parts: { const host = parts.authority ?? readNodeHeader(parts.headers, 'host') ?? ''; // url may be an absolute URL or an origin-form target ("/checkout?...", possibly "//x"). Build // the URL by APPENDING the target to the origin (not resolving it as a reference) so a leading - // "//" is treated as PATH — `new URL('//x', base)` would mis-read "//x" as a protocol-relative + // "//" is treated as PATH: `new URL('//x', base)` would mis-read "//x" as a protocol-relative // authority and drop it, diverging from the signer's `URL.pathname` and failing PoP. // Always assigned in both branches below, so no initializer (avoids a dead assignment). let path: string; diff --git a/src/aip/types.ts b/src/aip/types.ts index 60bded0..6098551 100644 --- a/src/aip/types.ts +++ b/src/aip/types.ts @@ -13,7 +13,7 @@ * * Extensibility contract (per spec): the `identity` object is open. If a claim is present, * the IdP attests to it; verifiers ignore claims they don't recognize. Absence is the - * "unknown" signal — IdPs do not ship `null` for "not checked". + * "unknown" signal: IdPs do not ship `null` for "not checked". */ import type { JWK } from 'jose'; @@ -22,7 +22,7 @@ import type { JWK } from 'jose'; export type TrustLevel = 'autonomous' | 'human_present' | 'human_confirmed'; /** - * Authentication Method Reference values (RFC 8176 / IANA AMR registry). Open set — these + * Authentication Method Reference values (RFC 8176 / IANA AMR registry). Open set: these * are the values relevant to agent identity; others are valid and pass through. */ export type AmrValue = 'face' | 'fpt' | 'hwk' | 'otp' | 'pin' | 'pwd' | 'sms' | 'swk' | 'user' | 'mfa'; @@ -65,7 +65,7 @@ interface PaymentClaim { /** * Identity claims (presence == IdP attestation). Spec-defined fields plus AgentScore - * compliance extension claims. Open by contract — unknown fields are allowed and ignored. + * compliance extension claims. Open by contract: unknown fields are allowed and ignored. */ export interface IdentityClaim { email?: string; @@ -127,7 +127,7 @@ export interface AitHeader { * and well-typed, and enforces the one normative conditional in the spec: a * `human_confirmed` token MUST carry at least one `auth.amr` value. * - * This is shape/contract validation only — it does NOT verify signatures (that's the + * This is shape/contract validation only: it does NOT verify signatures (that's the * verifier pipeline) and does NOT apply trust policy (that's the gate / `/v1/assess`). */ export type AitValidationResult = diff --git a/src/aip/verify.ts b/src/aip/verify.ts index cc9b921..e0e8a50 100644 --- a/src/aip/verify.ts +++ b/src/aip/verify.ts @@ -1,11 +1,11 @@ /** - * AIP Agent Identity Token (AIT) verification pipeline — the verifier orchestrator. + * AIP Agent Identity Token (AIT) verification pipeline: the verifier orchestrator. * * This is the function a merchant gate calls. It executes the spec's verification steps over * a presented request, composing the three foundation modules: - * - {@link ./jwks} — trusted-issuer enforcement + key discovery - * - {@link ./http-signature} — RFC 9421 proof-of-possession over the request - * - {@link ./types} — AIT structural contract + * - {@link ./jwks} : trusted-issuer enforcement + key discovery + * - {@link ./http-signature}: RFC 9421 proof-of-possession over the request + * - {@link ./types} : AIT structural contract * * Steps (per spec): * 1. read the `Agent-Identity` header (one or more) @@ -77,7 +77,7 @@ export interface VerifiedAit { /** The raw JWT string that verified (the winning `Agent-Identity` header value, Bearer * prefix stripped). Lets a gate forward the exact token to `/v1/assess` as `aip_token`. */ token: string; - /** The RFC 9421 signature material for this request — forwarded to `/v1/assess` as + /** The RFC 9421 signature material for this request: forwarded to `/v1/assess` as * `aip_signature` so the API re-verifies proof-of-possession authoritatively (the edge * check here is only a fail-fast filter; the API is the source of truth). */ signatureMaterial: { @@ -109,7 +109,7 @@ export const verifyAit = async ( if (!ctx.signatureInput || !ctx.signature) { return { ok: false, reason: 'pop_signature_missing' }; } - // Captured post-guard (string, not string|null) — reused for the local fail-fast PoP check and + // Captured post-guard (string, not string|null): reused for the local fail-fast PoP check and // forwarded to /v1/assess so the API can re-verify the same proof-of-possession authoritatively. const signatureInput = ctx.signatureInput; const signature = ctx.signature; @@ -166,7 +166,7 @@ export const verifyAit = async ( try { const idpKey = await importJWK(keyLookup.key, normalizeAlg(header.alg)); await jwtVerify(token, idpKey, { - // Pin the signature algorithm allowlist (RFC 8725 §3.1) — also rejects `alg:none`. Without + // Pin the signature algorithm allowlist (RFC 8725 §3.1): also rejects `alg:none`. Without // this, jose accepts whatever alg the resolved JWK supports, so a trusted IdP publishing a // non-Ed25519 (e.g. RSA/EC) `use:sig` key would let an attacker present an RS256/ES256 // token that verifies. Matches the server-side allowlist in the AgentScore API verifier. @@ -196,14 +196,14 @@ export const verifyAit = async ( continue; } - // Step 6 + 7 + 8: PoP — verify the RFC 9421 signature against cnf.jwk. + // Step 6 + 7 + 8: PoP: verify the RFC 9421 signature against cnf.jwk. const popResult = await verifyMessageSignature({ method: ctx.method, authority: ctx.authority, path: ctx.path, // The agent-identity covered component is the BARE AIT (a Bearer prefix, if present, is // transport that `stripBearer` removed above). Verify over `token`, not `raw`, so the edge and - // the API — which verifies over the forwarded bare aip_token — reconstruct the identical base. + // the API (which verifies over the forwarded bare aip_token) reconstruct the identical base. agentIdentity: token, signatureInput, signature, diff --git a/src/api/index.ts b/src/api/index.ts index cd400b1..33359e3 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,5 @@ /** - * AgentScore SDK re-export — vendors install only `@agent-score/commerce` and reach + * AgentScore SDK re-export: vendors install only `@agent-score/commerce` and reach * everything from the underlying `@agent-score/sdk` here. Don't add `@agent-score/sdk` * as a separate dep; the two can drift versions and cause subtle type mismatches. * diff --git a/src/challenge/agent_instructions.ts b/src/challenge/agent_instructions.ts index 6e41518..bbebb68 100644 --- a/src/challenge/agent_instructions.ts +++ b/src/challenge/agent_instructions.ts @@ -2,7 +2,7 @@ import type { HowToPayBlock } from './how_to_pay'; /** Map of rail key (e.g. 'x402_base', 'tempo_mpp', 'stripe') → list of client identifiers * that have been smoke-verified by the merchant against the protocol shape they emit. - * Strings are display labels, not install commands — agents already get install commands + * Strings are display labels, not install commands: agents already get install commands * via `how_to_pay..setup`. Use these as a "what's known to work" hint. */ export type CompatibleClients = Record; @@ -53,7 +53,7 @@ function defaultWarnings(howToPay: HowToPayBlock): string[] { * * Verified state as of the SDK release. The same data is also published as a docs page * for humans (rationale, per-rail commands, why some clients don't fully work, last - * verified date) — this default keeps the merchant-side surface in sync. + * verified date): this default keeps the merchant-side surface in sync. */ /** Symbolic rail keys agent-facing surfaces use to talk about a rail without spelling out * network/scheme details. Same keys as `CompatibleClients` map keys. */ @@ -67,7 +67,7 @@ const RAIL_CLIENTS: Record = { }; /** Returns the smoke-verified client list for a set of rail keys. The single source of - * truth for "which CLIs we've verified end-to-end on each rail" — consumed both by the + * truth for "which CLIs we've verified end-to-end on each rail": consumed both by the * 402-body builder (`defaultCompatibleClients`) and by discovery surfaces (skill.md, * llms.txt, etc.). Update here, every surface inherits. */ export function compatibleClientsByRails(rails: readonly RailKey[]): CompatibleClients | undefined { @@ -122,7 +122,7 @@ export function buildAgentInstructions({ /** Recommended rail (e.g., 'tempo', 'x402_base'). Surfaced for agents to default to. */ recommended?: string; /** Per-rail list of client names the merchant has verified work end-to-end. Vendors set - * this from their own smoke matrix — defaults to none. When omitted, the field is not emitted. */ + * this from their own smoke matrix: defaults to none. When omitted, the field is not emitted. */ compatibleClients?: CompatibleClients; /** Arbitrary additional fields the vendor wants merged into the agent_instructions object. */ extra?: Record; diff --git a/src/challenge/agent_memory.ts b/src/challenge/agent_memory.ts index 7adcf98..d772b62 100644 --- a/src/challenge/agent_memory.ts +++ b/src/challenge/agent_memory.ts @@ -12,7 +12,7 @@ * before?" in their own DB * - On first encounter (no prior request from this operator/wallet/IP), include the hint * so the agent saves the pattern - * - On subsequent encounters, skip — the agent already has it (or never will) + * - On subsequent encounters, skip: the agent already has it (or never will) * * The hint contents come from `buildAgentMemoryHint` (re-exported here for convenience). * Keep it stateless: AgentScore's pattern doesn't depend on the merchant's identity, so diff --git a/src/challenge/body.ts b/src/challenge/body.ts index 9013862..40abf72 100644 --- a/src/challenge/body.ts +++ b/src/challenge/body.ts @@ -51,11 +51,11 @@ export function build402Body({ x402, extra, }: { - /** From buildAcceptedMethods — list of MPP method entries. */ + /** From buildAcceptedMethods: list of MPP method entries. */ acceptedMethods: AcceptedMethodEntry[]; - /** From buildAgentInstructions — wraps how_to_pay + warnings + recommended_tools. */ + /** From buildAgentInstructions: wraps how_to_pay + warnings + recommended_tools. */ agentInstructions?: AgentInstructions; - /** From buildIdentityMetadata — wallet-mode echoer. Spread into the body when present. */ + /** From buildIdentityMetadata: wallet-mode echoer. Spread into the body when present. */ identityMetadata?: IdentityMetadataBlock; /** Cross-merchant agent_memory hint (from gate). */ agentMemory?: unknown; @@ -67,11 +67,11 @@ export function build402Body({ currency?: string; /** Order id for retry correlation. */ orderId?: string | null; - /** Product info — surfaced on the 402 so agents can confirm what they're buying. */ + /** Product info: surfaced on the 402 so agents can confirm what they're buying. */ product?: { id: string; name: string }; /** The body the agent should retry with after payment (e.g., the original request body). */ retryBody?: unknown; - /** Recommended rail — agent's default if multiple are listed. */ + /** Recommended rail: agent's default if multiple are listed. */ recommended?: string; /** x402-compliance fields (paired with the PAYMENT-REQUIRED header from `payment/wwwauthenticate`). */ x402?: { diff --git a/src/challenge/how_to_pay.ts b/src/challenge/how_to_pay.ts index 82e39c8..9eaa6fc 100644 --- a/src/challenge/how_to_pay.ts +++ b/src/challenge/how_to_pay.ts @@ -74,15 +74,15 @@ export function buildHowToPay({ }: { /** The merchant's full URL (e.g., 'https://agents.merchant.example/api/buy'). */ url: string; - /** JSON string of the body the agent should retry with — typically the original request body. */ + /** JSON string of the body the agent should retry with: typically the original request body. */ retryBodyJson: string; /** Total amount in USD (string or number). Used to compute max-spend defaults and stripe context. */ totalUsd: string | number; - /** Per-rail config — each is optional. Pass only the rails you support. */ + /** Per-rail config: each is optional. Pass only the rails you support. */ rails: HowToPayRails; /** Placeholder text for the operator token in commands. Defaults to ''. * Pass `null` (gateless merchants) to strip the `-H 'X-Operator-Token: ...'` line entirely - * from each rail command — appropriate when the merchant doesn't run an identity gate. */ + * from each rail command: appropriate when the merchant doesn't run an identity gate. */ opTokenPlaceholder?: string | null; /** Override max-spend value used in commands. Default: `ceil(totalUsd) + 1` * (for prices ≥ $1) or `totalUsd.toFixed(decimals)` (for sub-dollar prices, @@ -96,7 +96,7 @@ export function buildHowToPay({ const d = decimals ?? 2; const defaultMaxSpend = totalNum >= 1 ? (Math.ceil(totalNum) + 1).toFixed(d) : totalNum.toFixed(d); const maxSpendStr = String(maxSpend ?? defaultMaxSpend); - // When opTokenPlaceholder is explicitly null, the merchant is gateless — strip + // When opTokenPlaceholder is explicitly null, the merchant is gateless: strip // the `-H 'X-Operator-Token: ...'` snippet from every rail command. Otherwise // fall back to '' for back-compat. const gateless = opTokenPlaceholder === null; diff --git a/src/challenge/identity.ts b/src/challenge/identity.ts index c703063..f2f0584 100644 --- a/src/challenge/identity.ts +++ b/src/challenge/identity.ts @@ -19,7 +19,7 @@ export interface IdentityMetadataBlock { /** * Build the identity-metadata block for an enriched 402 body. Echoes the agent's * identity context (wallet vs. operator-token mode) so the agent can self-correct - * before signing — specifically, on wallet-auth rails the agent MUST sign with one + * before signing: specifically, on wallet-auth rails the agent MUST sign with one * of the wallets in linked_wallets (all resolve to the same operator). */ export function buildIdentityMetadata({ diff --git a/src/challenge/pricing.ts b/src/challenge/pricing.ts index 003dac7..caa20c4 100644 --- a/src/challenge/pricing.ts +++ b/src/challenge/pricing.ts @@ -3,7 +3,7 @@ * * Composes the cents-denominated price components into the dollar-string shape that * 402 challenge bodies advertise. Standardizes the pricing block so every merchant - * — current and future commerce-platform plugins (Commerce7, WooCommerce, Shopify) — + * (including current and future commerce-platform plugins such as Commerce7, WooCommerce, Shopify) * surfaces the same shape to agents. * * Shipping is included by default because most physical-goods merchants carry it; pass @@ -22,7 +22,7 @@ export interface PricingBlock { discount?: string; /** Final total = subtotal + tax + shipping - discount, dollar-string. Floored at 0. */ total: string; - /** Tax rate as a decimal fraction (e.g. `0.0775` for 7.75%). Optional — omit for tax-free merchants. */ + /** Tax rate as a decimal fraction (e.g. `0.0775` for 7.75%). Optional: omit for tax-free merchants. */ tax_rate?: number; /** ISO-3166-2 state code or jurisdiction name used for tax calc. Optional. */ tax_state?: string; diff --git a/src/challenge/respond_402.ts b/src/challenge/respond_402.ts index 72be966..c91eb30 100644 --- a/src/challenge/respond_402.ts +++ b/src/challenge/respond_402.ts @@ -1,13 +1,13 @@ /** - * `respond402` — single-call 402 emit for merchants who use both `mppx` (for tempo + stripe + * `respond402`: single-call 402 emit for merchants who use both `mppx` (for tempo + stripe * MPP rails) AND x402 (for Base + Solana). * * The seam is fiddly enough to get wrong by hand: * - mppx's `compose()(req)` returns a 402 Response with WWW-Authenticate directives - * whose ids mppx's server-side validator REMEMBERS — they round-trip in client + * whose ids mppx's server-side validator REMEMBERS: they round-trip in client * credentials. Overwriting that header breaks the round-trip. * - x402 needs the binary-friendly `PAYMENT-REQUIRED` header (base64-encoded JSON - * of `{x402Version, accepts, resource}`) — mppx doesn't emit it. + * of `{x402Version, accepts, resource}`): mppx doesn't emit it. * - Merchants want a richer JSON body (pricing, identity metadata, agent_instructions, * agent_memory, retry_body, accepted_methods cross-reference) than the bare mppx body. * @@ -31,7 +31,7 @@ import { normalizeHeadersToLowercase } from '../_headers'; import { paymentRequiredHeader } from '../payment/wwwauthenticate'; -/** Framework-neutral 402 response shape — body + headers + status. */ +/** Framework-neutral 402 response shape: body + headers + status. */ export interface Respond402Result { body: Record; headers: Record; @@ -44,13 +44,13 @@ export function respond402({ x402, }: { /** Headers from mppx's 402 Response (`Object.fromEntries(challenge.headers)`). The - * `WWW-Authenticate` directives are preserved verbatim — mppx's server-side validator + * `WWW-Authenticate` directives are preserved verbatim: mppx's server-side validator * matches credentials to the ids it generated. */ mppxChallengeHeaders: Record; - /** The already-built 402 body — call `build402Body({...})` to construct it. */ + /** The already-built 402 body: call `build402Body({...})` to construct it. */ body: Record; /** When set, layers on the x402 PAYMENT-REQUIRED header (base64-encoded JSON). - * Omit for merchants that don't accept x402 (Base/Solana) — mppx-only setups. */ + * Omit for merchants that don't accept x402 (Base/Solana): mppx-only setups. */ x402?: Parameters[0]; }): Respond402Result { const headers = normalizeHeadersToLowercase(mppxChallengeHeaders); diff --git a/src/challenge/validation_error.ts b/src/challenge/validation_error.ts index 5c2503c..bf58651 100644 --- a/src/challenge/validation_error.ts +++ b/src/challenge/validation_error.ts @@ -4,7 +4,7 @@ * `bad_request` / `not_found` / `out_of_stock` / etc. errors converges on the * same shape: `{ error: {code, message}, ...optional_hints, next_steps? }`. * - * This builder doesn't choose the HTTP status — vendors wrap the returned + * This builder doesn't choose the HTTP status: vendors wrap the returned * body in their framework's response (`c.json(body, 400)` in Hono, * `Response.json(body, {status: 400})` for the Web Fetch path, etc.). Status * stays the merchant's call because the same shape works for 400/404/409/422. diff --git a/src/checkout.ts b/src/checkout.ts index 25c833d..cfc703b 100644 --- a/src/checkout.ts +++ b/src/checkout.ts @@ -1,5 +1,5 @@ /** - * High-level Checkout orchestrator — composes 402-emit + verify+settle. + * High-level Checkout orchestrator: composes 402-emit + verify+settle. * * The Checkout primitive collapses the agentic-commerce dance (emit 402 → * verify+settle on retry → respond) into a single `await checkout.handle(request)` @@ -12,18 +12,18 @@ * - **Self-custody-only merchants** configure chain rails (Tempo / Base / Solana) * via `X402BaseRailSpec` / `TempoRailSpec` / `SolanaMppRailSpec`. * - **Custodial-only merchants** configure `StripeRailSpec` and skip the chain - * rails — Stripe SPT settles via the same `composeMppx` hook. + * rails: Stripe SPT settles via the same `composeMppx` hook. * - **Multi-rail merchants** configure all of the above; the agent picks the rail. * - * Three flexibility axes — every combination is supported: + * Three flexibility axes: every combination is supported: * - * - **x402 only / MPP only / both** — Checkout works with `x402Server` alone, + * - **x402 only / MPP only / both**: Checkout works with `x402Server` alone, * `composeMppx` alone, or both. Whichever payment header arrives is dispatched * to the configured handler; the other path is simply absent. - * - **Self-custody / Stripe / mixed** — rails dict is the single source of truth. + * - **Self-custody / Stripe / mixed**: rails dict is the single source of truth. * Listing `StripeRailSpec` makes Stripe SPT an acceptable rail; omitting it * makes the merchant chain-only. Mixing freely is the default. - * - **Gated / ungated identity** — `CheckoutRequest.assess` is optional. Merchants + * - **Gated / ungated identity**: `CheckoutRequest.assess` is optional. Merchants * who run AgentScoreGate upstream pass its result through; merchants running * anonymous leave it `null`. * @@ -112,13 +112,13 @@ export interface CheckoutRequest { * the gate for this endpoint. */ assess?: Record | null; /** Optional escape hatch for the framework's native request object. Pass when - * your `composeMppx` hook needs to call `mppx.compose(...)(rawRequest)` — mppx's + * your `composeMppx` hook needs to call `mppx.compose(...)(rawRequest)`: mppx's * compose binds to the raw HTTP request, so the orchestrator forwards this * through unchanged. */ raw?: unknown; } -/** Output of `Checkout.computePricing` — per-request pricing. */ +/** Output of `Checkout.computePricing`: per-request pricing. */ export interface PricingResult { /** Total to charge in USD (or the upper bound, for `mode: 'upto'` rails). */ amountUsd: number; @@ -232,7 +232,7 @@ export function pricingResult(opts: { /** * Per-route discovery-probe config. When passed to {@link Checkout}, any * empty-body POST without a payment credential short-circuits with a sample - * 402 advertising the merchant's payment shape — the canonical pattern x402 + * 402 advertising the merchant's payment shape: the canonical pattern x402 * crawlers rely on. */ export interface DiscoveryProbeConfig { @@ -274,7 +274,7 @@ export interface CheckoutContext { * for this request. Set by Checkout's internal gate after a successful allow * when an operator_token is present; `undefined` for wallet-authenticated * requests (no operator_token to associate) or anonymous discovery legs. - * Fire-and-forget — invoke from `onSettled` with the recovered signer. */ + * Fire-and-forget: invoke from `onSettled` with the recovered signer. */ captureWallet?: (opts: { walletAddress: string; network: 'evm' | 'solana'; @@ -352,17 +352,17 @@ export type RunGateFn = (ctx: CheckoutContext) => Promise; * * Three customization layers, in order of precedence: * - * 1. ``runGate`` — full escape hatch. Replaces the SDK's gate flow entirely. + * 1. ``runGate``: full escape hatch. Replaces the SDK's gate flow entirely. * Merchants implement assess + denial body construction themselves. Useful * only for non-standard auth (e.g. shared signing keys, non-AgentScore IdP). - * 2. ``perRequestPolicy`` — reads `ctx.state` (populated by preValidate) and + * 2. ``perRequestPolicy``: reads `ctx.state` (populated by preValidate) and * returns a partial policy override applied per request (e.g. product-level * KYC requirement). When omitted, the static fields below apply uniformly. - * 3. ``onDenied`` — invoked AFTER the SDK builds the canonical DenialReason. + * 3. ``onDenied``: invoked AFTER the SDK builds the canonical DenialReason. * Return a `GateDenial` to override the response body shape, or null to * fall back to the canonical body from `denialReasonToBody`. * - * Static policy fields mirror `AgentScoreCoreOptions` — see that interface + * Static policy fields mirror `AgentScoreCoreOptions`: see that interface * for field semantics. */ export interface CheckoutGateConfig { @@ -416,7 +416,7 @@ export interface CheckoutGateConfig { * fields (KYC / age / sanctions / jurisdiction) without an `apiKey` fails closed * (`aip_policy_requires_api_key`) since policy can only be evaluated via `/v1/assess`. */ aip?: AipGateConfig; - /** Full escape hatch — replaces the SDK gate flow. */ + /** Full escape hatch: replaces the SDK gate flow. */ runGate?: RunGateFn; } @@ -424,7 +424,7 @@ export interface CheckoutGateConfig { export interface AipGateConfig { /** ADDITIONAL external issuers to trust beyond AgentScore's own (e.g. `['https://issuer.example']`), * matched after canonicalization. AgentScore's canonical issuer - * ({@link AGENTSCORE_CANONICAL_ISSUER}) is ALWAYS trusted and never needs listing — this SDK + * ({@link AGENTSCORE_CANONICAL_ISSUER}) is ALWAYS trusted and never needs listing: this SDK * is the AgentScore verifier, so a merchant can't accidentally fail to trust AgentScore AITs. * Omit/empty to accept only AgentScore-issued AITs. */ trustedIssuers?: string[]; @@ -432,13 +432,13 @@ export interface AipGateConfig { * the AIT JWT `exp`/`iat`). Defaults to 60s for both. */ maxSkewSeconds?: number; /** Expected `@authority` (public hostname) the RFC 9421 signature must cover. When set, the - * verifier binds the signature to this value instead of trusting the inbound `Host` header — + * verifier binds the signature to this value instead of trusting the inbound `Host` header: * pin it to your real public host (e.g. `'wine.example.com'`) when behind a proxy that does * not normalize `Host`, to prevent a captured AIT+signature from being replayed to a * different virtual host on the same origin. */ authority?: string; /** Minimum `trust_level` an AIT must assert to pass this gate (autonomous < human_present < - * human_confirmed) — the spec's human-presence gate (e.g. require `human_confirmed` for + * human_confirmed): the spec's human-presence gate (e.g. require `human_confirmed` for * checkout). Enforced at the edge from the verified token; insufficient → 403 weak_auth with * `required_trust_level`. Unset = any trust level accepted. */ requireTrustLevel?: TrustLevel; @@ -449,12 +449,12 @@ export interface AipGateConfig { /** Per-issuer compliance policy override, keyed by issuer URL (canonicalized before lookup). * When a request's AIT is verified and its `iss` matches a key here, that block REPLACES the * gate's default policy fields (`requireKyc` / `requireSanctionsClear` / `minAge` / - * `allowed/blockedJurisdictions`) for that request — letting a merchant apply different rules + * `allowed/blockedJurisdictions`) for that request: letting a merchant apply different rules * by issuer (e.g. full compliance for its own AITs, a relaxed set for a partner issuer whose * tokens carry fewer attested claims). The replacement is whole-policy, not a merge: an issuer * block of `{ requireKyc: true, minAge: 21 }` evaluates ONLY those two rules for that issuer * (sanctions / jurisdiction omitted → not enforced for that issuer). Issuers NOT listed here - * use the gate's default policy unchanged. Only the AIT path consults this — wallet / + * use the gate's default policy unchanged. Only the AIT path consults this: wallet / * operator-token requests are unaffected. * * This is a deliberate compliance posture per issuer, not a default; an empty/absent map keeps @@ -620,11 +620,11 @@ function staticRecipient(r: RecipientLike): string | null { * Used to bind the agent-supplied x402 `payTo` to the merchant's CONFIGURED recipient: the * x402 `payTo` is read from the agent's signed payload, and the only sanity check is * `isCachedAddress`. For a static-recipient merchant (one address, no per-order minting and no - * custom `isCachedAddress`), the gate must reject any `payTo` that isn't the configured address — + * custom `isCachedAddress`), the gate must reject any `payTo` that isn't the configured address: * otherwise a hostile agent points `payTo` at their own wallet and drains the settle. This set is * the allow-list the auto-supplied `isCachedAddress` checks against. * - * Empty when every x402-base recipient is a factory/empty sentinel (pure per-order minting) — in + * Empty when every x402-base recipient is a factory/empty sentinel (pure per-order minting): in * that case there's nothing static to bind, and the merchant is expected to supply `isCachedAddress` * (e.g. `piCache.hasAddress`) themselves. */ @@ -741,11 +741,11 @@ export function makeMppxComposeHook(opts: { /** * Apply per-call recipient overrides (from `mintRecipients`) to rail specs. * Returns a new dict; original rails dict is not mutated. Stripe rails are - * passed through unchanged (no on-chain recipient — they use `profileId`). + * passed through unchanged (no on-chain recipient: they use `profileId`). * * When the merchant declares rails with sentinel empty-string recipients * (per-order minting pattern) and `mintRecipients` only returns addresses - * for some rails, drop the rails that resolve to an empty recipient — those + * for some rails, drop the rails that resolve to an empty recipient: those * weren't actually minted for this request and shouldn't be advertised in * the 402. */ @@ -888,7 +888,7 @@ export class Checkout { } /** - * True when the merchant has configured an identity-bearing policy flag — + * True when the merchant has configured an identity-bearing policy flag: * `require_kyc`, `require_sanctions_clear` (name screening on the KYC * identity), `min_age`, or jurisdiction lists. Wallet OFAC SDN enforcement * (the always-on default) does NOT count as an identity gate; agents don't @@ -944,7 +944,7 @@ export class Checkout { /** Pass when the merchant mints per-order addresses so `verifyX402Request` can * confirm the `payTo` was minted by this merchant (e.g. `piCache.hasAddress`). * When omitted, Checkout auto-binds the agent-supplied `payTo` to the rail's - * configured STATIC recipient(s) — the permissive default applies ONLY when no + * configured STATIC recipient(s): the permissive default applies ONLY when no * static recipient exists (pure per-order minting). */ isCachedAddress?: IsCachedAddressFn; /** Engage the $0 carve-out when pricing resolves to $0: x402 and @@ -957,7 +957,7 @@ export class Checkout { /** Reject payment credentials that fail the cheap wire-shape check (not * base64 JSON, not a token-shaped value) BEFORE any merchant hook runs, * so junk headers never trigger `preValidate` / pricing / recipient - * minting / the gate's assess call. Shape only — signature and payTo + * minting / the gate's assess call. Shape only: signature and payTo * verification stay on the settle path. Default `true`; set `false` for * custom `composeMppx` implementations that accept non-standard * credential encodings. */ @@ -968,7 +968,7 @@ export class Checkout { gate?: CheckoutGateConfig; /** Per-endpoint x402 `extensions` block emitted on the 402 body. Merge * outputs of `createBazaarDiscovery({...})` (or other extension declarers) - * here — Checkout forwards verbatim into the 402 response body's + * here: Checkout forwards verbatim into the 402 response body's * `extensions` field so Bazaar crawlers and other spec-compliant clients * read the route's declared input/output schema. */ discoveryExtensions?: Record; @@ -1229,7 +1229,7 @@ export class Checkout { // 0.5. Credential shape gate. Runs BEFORE preValidate / the identity gate / // pricing / recipient minting so a junk payment header cannot trigger // merchant hooks (which may do paid upstream work) or burn an assess - // call. Shape only — real verification stays on the settle path, which + // call. Shape only: real verification stays on the settle path, which // needs per-request state the hooks produce. Scoped to the credential // channels this Checkout actually dispatches on, so e.g. an x402 header // at a Tempo-only merchant keeps its current discovery-leg behavior. @@ -1274,7 +1274,7 @@ export class Checkout { // - Merchants with an explicit `gate` config run the full identity // policy (KYC / age / sanctions / jurisdiction) via `runGate`. // - Merchants WITHOUT a `gate` config still get wallet OFAC SDN - // enforcement via `runWalletSanctionsOnly` — this is the always-on + // enforcement via `runWalletSanctionsOnly`: this is the always-on // strict-liability default. Falls back to `process.env.AGENTSCORE_API_KEY` // for the API call; logs a warning and skips when no key is set // (dev/testnet pattern). @@ -1301,7 +1301,7 @@ export class Checkout { // Recipients are read by every downstream dispatch (x402 verify+settle, // mppx compose, 402 emit). Resolve once here so hooks see ctx.recipients - // populated. The resolver is idempotent — subsequent calls no-op. + // populated. The resolver is idempotent: subsequent calls no-op. // // mintRecipients can throw CheckoutValidationError from cross-bundle // helpers like createPayToAddressFromStripePI (e.g. malformed @@ -1359,12 +1359,12 @@ export class Checkout { const gate = this.gate; if (gate === undefined) return null; if (gate.runGate !== undefined) { - // Escape hatch — fully owns the gate. The wallet-OFAC fallback below + // Escape hatch: fully owns the gate. The wallet-OFAC fallback below // (apiKey === undefined → runWalletSanctionsOnly) does NOT fire here; // merchants who supply runGate are taking explicit ownership of // compliance enforcement and should call /v1/assess themselves (or // accept that they're not getting SDN protection). NOTE: `runGate` also - // bypasses the `gate.aip` AIP pre-step below — a custom gate owns AIT + // bypasses the `gate.aip` AIP pre-step below: a custom gate owns AIT // verification too. `runGate` and `gate.aip` are mutually exclusive. const result = await gate.runGate(ctx); // Allow merchants to return undefined as an alias for `null` (allow). @@ -1376,7 +1376,7 @@ export class Checkout { } return result; } - // AIP pre-step — runs BEFORE the no-apiKey fallback so a present-but-invalid AIT is always + // AIP pre-step: runs BEFORE the no-apiKey fallback so a present-but-invalid AIT is always // a hard deny, and a cryptographically verified AIT is honored even on an offline-only gate. // The RFC 9421 proof-of-possession can only be checked here at the edge, where the signed // HTTP message lives. A valid AIT becomes the sole identity (wins over wallet / operator-token). @@ -1421,7 +1421,7 @@ export class Checkout { aipSignature = aipResult.ait.signatureMaterial; // Enforce the merchant's trust_level / auth.amr requirement (the spec's human-presence gate). - // Verification-derived (carried in the verified token), so enforced here at the edge — + // Verification-derived (carried in the verified token), so enforced here at the edge: // insufficient → weak_auth (403) with required_* so the agent can step up (re-mint a // higher-trust AIT) rather than guess. const weakAuthDetail = checkTrustRequirements(aipResult.ait.payload, gate.aip.requireTrustLevel, gate.aip.requireAmr); @@ -1459,7 +1459,7 @@ export class Checkout { // signature + RFC 9421 PoP). But compliance *policy* (KYC / age / sanctions / // jurisdiction) is evaluated against the token's claims by `/v1/assess`, which needs an // apiKey. If the merchant declared policy fields without an apiKey we cannot enforce - // them — fail closed rather than silently allow a verified-but-non-compliant identity + // them: fail closed rather than silently allow a verified-but-non-compliant identity // (e.g. an under-21 AIT through a `minAge: 21` gate). Identity-only gates (no policy // fields) are satisfied by the verified AIT alone. const hasPolicy = effPolicy.requireKyc || effPolicy.requireSanctionsClear || effPolicy.minAge != null @@ -1487,7 +1487,7 @@ export class Checkout { if (gate.perRequestPolicy !== undefined) { policyOverride = await gate.perRequestPolicy(ctx); // A null override means "no per-request *identity* policy for this product" - // — but it must NOT skip the always-on wallet OFAC SDN floor. Route to + // but it must NOT skip the always-on wallet OFAC SDN floor. Route to // runWalletSanctionsOnly so a NULL-enforcement product still screens its // payment signer (identical to the no-gate dispatch above). The floor is a // no-op for non-wallet flows (no apiKey, or no extractable signer on Stripe @@ -1498,7 +1498,7 @@ export class Checkout { apiKey: gate.apiKey, ...(gate.baseUrl !== undefined && { baseUrl: gate.baseUrl }), ...(gate.userAgent !== undefined && { userAgent: gate.userAgent }), - // Compliance fields come from effPolicy — the per-issuer override for the verified AIT's + // Compliance fields come from effPolicy: the per-issuer override for the verified AIT's // issuer when one is configured, else the gate defaults (see effPolicy above). A whole- // policy replacement: an issuer override of `{ requireKyc, minAge }` sends ONLY those, // so sanctions / jurisdiction are not enforced for that issuer. @@ -1568,7 +1568,7 @@ export class Checkout { // denials so the gate enforces wallet binding inline (no separate hook). // Signer-match enforcement applies only to the wallet identity path. On the AIT path the // identity is the token (PoP-bound via cnf) and assess was keyed by aip_token, so there is - // no address-keyed signer verdict to read — the wallet binding for AITs is the IdP's + // no address-keyed signer verdict to read: the wallet binding for AITs is the IdP's // `payment.signer` claim, enforced server-side. Guarding on aipToken===undefined keeps // this from being dead code that silently no-ops on a cache miss. if (aipToken === undefined && walletAddress !== undefined) { @@ -1607,7 +1607,7 @@ export class Checkout { } const body = denialReasonToBody(reason); // AIT-input denial (a verified AIT that /v1/assess then denied): emit the AgentScore body as - // an RFC 9457 + AIP-spec SUPERSET so the response is both schemes at once — the rich + // an RFC 9457 + AIP-spec SUPERSET so the response is both schemes at once: the rich // AgentScore `{ error, agent_instructions, ... }` AND the spec's `type`/`title`/`status`/ // `detail` (+ escalation). `application/problem+json` so spec consumers content-negotiate it. // The wallet / operator-token paths (aipToken undefined) keep the bare AgentScore body + @@ -1643,9 +1643,9 @@ export class Checkout { * strict-liability default). * * Env knobs: - * - `AGENTSCORE_API_KEY` — required. No key → one-time warning + skip + * - `AGENTSCORE_API_KEY`: required. No key → one-time warning + skip * (dev/testnet pattern; production should always configure a key). - * - `AGENTSCORE_BASE_URL` — optional override for staging/dev API + * - `AGENTSCORE_BASE_URL`: optional override for staging/dev API * (e.g. `https://api.staging.example` or `http://localhost:3002`). * * Stripe SPT (no extractable wallet signer) → skip silently; Stripe runs @@ -1654,12 +1654,12 @@ export class Checkout { * Calls `/v1/assess` with the signer wallet as both the primary address * and the signer block. The API enforces signer-sanctions unconditionally * when a signer is present (no policy flag needed). Denies on OFAC SDN - * hit; fail-closed on unavailable lookup (strict liability — falsely + * hit; fail-closed on unavailable lookup (strict liability: falsely * allowing a sanctioned settle is an OFAC violation, falsely denying a * clean buyer is just bad UX). */ private async runWalletSanctionsOnly(ctx: CheckoutContext): Promise { - // Prefer the gate's own apiKey when present, else the env var — symmetric with + // Prefer the gate's own apiKey when present, else the env var: symmetric with // python's `_run_wallet_sanctions_only` (gate.api_key or env). In practice this is // reached only when the gate has no apiKey, so both resolve to the env var. const apiKey = this.gate?.apiKey ?? process.env.AGENTSCORE_API_KEY; @@ -1672,7 +1672,7 @@ export class Checkout { const x402Header = headers['payment-signature'] ?? headers['x-payment']; const signer = await extractPaymentSignerFromAuth(headers['authorization'], x402Header); if (!signer) { - // Stripe SPT path — no wallet signer, no OFAC check possible. Stripe + // Stripe SPT path: no wallet signer, no OFAC check possible. Stripe // screens its own customer accounts; we have nothing to add here. return null; } @@ -1712,7 +1712,7 @@ export class Checkout { if (!this.zeroSettleCarveOut || ctx.pricing === null) return null; // Gate on the REAL amount, not cents. `Math.round(amountUsd * 100)` rounds a sub-cent // NON-zero price (e.g. $0.002 → 0 cents) to zero and would skip the on-chain settle while - // still delivering the goods — a free-goods bypass. Only a genuine $0 price takes the + // still delivering the goods: a free-goods bypass. Only a genuine $0 price takes the // carve-out. Matches python `checkout.py` (`amount_usd == 0`). if (ctx.pricing.amountUsd !== 0) return null; const headers = normalizeHeadersToLowercase(ctx.request.headers); @@ -1748,11 +1748,11 @@ export class Checkout { // Route on the credential's payload type. mppx >= 0.8 settles zero-amount // challenges natively ONLY via the wallet-bound EIP-712 `proof` credential // (full signature verify, access-key authorization, replay protection, and - // a real receipt whose method drives railKey) — an agent that saw a $0 + // a real receipt whose method drives railKey): an agent that saw a $0 // challenge signs a proof, so those delegate to handleMppx. Every other // credential at $0 CANNOT settle upstream: `hash`/`transaction` payloads // mean the agent signed against a nonzero quote the merchant re-priced to - // $0 at settle (no-match / full-discount flows — the authorization is + // $0 at settle (no-match / full-discount flows: the authorization is // never exercised), Solana has no upstream $0 contract, and token-style // credentials have no $0 semantics. Those keep the carve-out: nothing // settles, and the signer is recovered for attribution only via the full @@ -1803,7 +1803,7 @@ export class Checkout { // Per-request minted recipient (`ctx.recipients['x402_base']` from `mintRecipients`) wins over // the construction-time static set. A rail can carry BOTH a static recipient AND `mintRecipients` // (static = discovery/sentinel default; per-request mint = the real payTo). Binding to the static - // set here would reject the legit minted payTo, so the per-request recipient takes precedence — + // set here would reject the legit minted payTo, so the per-request recipient takes precedence: // exactly as the compute-first path already does (`expectedPayTo = recipients.x402_base`). const minted = ctx?.recipients['x402_base']; if (minted !== undefined && minted.length > 0) { @@ -1817,7 +1817,7 @@ export class Checkout { return this.x402StaticRecipients.has(address.toLowerCase()); } // Pure per-order minting with no `isCachedAddress` supplied: nothing static to bind against. - // Stays permissive (unchanged behavior) — such merchants are expected to pass `isCachedAddress`. + // Stays permissive (unchanged behavior): such merchants are expected to pass `isCachedAddress`. return true; } @@ -2051,7 +2051,7 @@ export class Checkout { // emitted accepted_methods + how_to_pay stay consistent with what mppx's // compose layer will actually accept (see buildMppxComposeRails). Without // this, the 402 body advertises a stripe rail that has no matching - // WWW-Authenticate challenge — agents see it offered but any SPT pay + // WWW-Authenticate challenge: agents see it offered but any SPT pay // attempt fails. The compose-time auto-drop emits the user-facing warn; // here we just strip the slot from the discovery body. if (ctx.pricing.amountUsd < STRIPE_MIN_CHARGE_USD && emitRails.stripe !== undefined) { @@ -2085,7 +2085,7 @@ export class Checkout { totalUsd: ctx.pricing.amountUsd.toFixed(pricingDecimals), rails: howToPayRails, // Merchants without an identity-bearing policy flag get clean commands - // without an X-Operator-Token header — agents don't need one to satisfy + // without an X-Operator-Token header: agents don't need one to satisfy // wallet OFAC enforcement (the always-on default). ...(this.hasIdentityGate() ? {} : { opTokenPlaceholder: null }), ...(ctx.pricing.decimals !== undefined && { decimals: ctx.pricing.decimals }), @@ -2343,7 +2343,7 @@ function stripContentType(headers: Record): Record): string | undefined { @@ -2458,7 +2458,7 @@ declare module './checkout' { raw: req, }); for (const [k, v] of Object.entries(stripContentType(result.headers))) res.setHeader(k, v); - // Honor an explicit content-type (AIP problem+json) — setting it before json() makes Express + // Honor an explicit content-type (AIP problem+json): setting it before json() makes Express // respect it instead of forcing application/json. Only the AIP path sets one, so non-AIP // responses are untouched (res.json defaults to application/json as before). const ct = explicitContentType(result.headers); @@ -2642,7 +2642,7 @@ interface FastifyLikeReply { send: (body: string) => unknown; } -/** Options for the `mountUcpRoutes` helpers — one shape used by all +/** Options for the `mountUcpRoutes` helpers: one shape used by all * three adapters. Saves merchants from copy-pasting the same 3-route block * (GET ucp + GET jwks + OPTIONS preflights) every time. */ export interface MountUcpRoutesOptions { diff --git a/src/checkout_compute_first.ts b/src/checkout_compute_first.ts index b2e7991..72c8eb6 100644 --- a/src/checkout_compute_first.ts +++ b/src/checkout_compute_first.ts @@ -1,5 +1,5 @@ /** - * `computeFirstCheckout` — variable-cost pay-per-result merchant helper using + * `computeFirstCheckout`: variable-cost pay-per-result merchant helper using * compute-first + exact-x402 (no upto, no Permit2, no Settlement-Overrides). * * Flow (per request): @@ -22,7 +22,7 @@ * * Works on every exact-mode rail today (x402-exact Base, tempo/charge, * solana/charge, Stripe SPT). The tradeoff vs. upto is that the work runs on - * the unpaid probe leg — so rate-limiting is load-bearing (use + * the unpaid probe leg: so rate-limiting is load-bearing (use * `@agent-score/commerce/middleware/hono` `rateLimitHono` or the per-framework * equivalent). */ @@ -76,7 +76,7 @@ export interface WorkOutcome { } export interface ComputeFirstWorkContext { - /** Raw Web Fetch `Request` for the probe leg — useful when the work hook + /** Raw Web Fetch `Request` for the probe leg: useful when the work hook * needs to dispatch by header (e.g. agent identity) or read query params. */ request: Request; } @@ -110,7 +110,7 @@ export interface ComputeFirstMppContext { export interface ComputeFirstSettledContext { request: Request; /** Which rail family captured the payment. `'x402'` = x402-exact on Base. - * `'mpp'` = one of Tempo / Solana / Stripe SPT via mppx — disambiguate with + * `'mpp'` = one of Tempo / Solana / Stripe SPT via mppx: disambiguate with * `mppMethod` when you need per-MPP-rail dispatch (e.g. firing the Stripe * testnet deposit simulator with the right `network`). */ rail: 'x402' | 'mpp'; @@ -164,24 +164,24 @@ export interface ComputeFirstOptions { * * - **Probe leg** (no payment header): the merchant builds per-rail intents * at the exact cached price and calls `composeMppxRequest(mppx, intents, - * request)` — mppx returns a 402 challenge whose `WWW-Authenticate` + * request)`: mppx returns a 402 challenge whose `WWW-Authenticate` * header carries the proper per-rail `request=` values * the agent needs to sign. Return `{status: 402, headers}` where - * `headers` is `mppxChallengeHeaders(result)` — the helper merges them + * `headers` is `mppxChallengeHeaders(result)`: the helper merges them * into the 402 response so pay / mppx-clients can settle on any * advertised MPP rail. * - **Settle leg** (`Authorization: Payment` header attached): mppx * verifies + composes. Return `{status: 200, ...}` with `txHash` and * signer info for the success envelope. * - * Omit to skip MPP entirely — the helper then advertises only x402-exact + * Omit to skip MPP entirely: the helper then advertises only x402-exact * in the 402 and rejects MPP-header settles with 503 `mpp_unavailable`. */ composeMppx?: (ctx: ComputeFirstMppContext) => Promise<{ status: number; raw?: unknown; headers?: Record; txHash?: string; signerAddress?: string; signerNetwork?: 'evm' | 'solana' }>; /** Optional side-effect hook fired after a successful settle on either rail * (x402 or MPP), before the response is sent. Use this for Stripe testnet * deposit simulation, audit logging, captureWallet writeback, or any other * post-settle work that shouldn't block the response. Errors are caught - * and logged but don't fail the request — the buyer still gets their data. */ + * and logged but don't fail the request: the buyer still gets their data. */ onSettled?: (ctx: ComputeFirstSettledContext) => Promise | void; /** Optional input validator. Throw a `CheckoutValidationError` for typed * 4xx envelopes; any other error becomes a 500. */ @@ -219,9 +219,9 @@ export interface SuccessBodyArgs { } export interface ComputeFirstHandler { - /** Hono adapter — pass `(c) => handler.handleHono(c)` into your route. */ + /** Hono adapter: pass `(c) => handler.handleHono(c)` into your route. */ handleHono(c: Context): Promise; - /** Web Fetch adapter — pass `handler.handleWeb` directly as a route handler. */ + /** Web Fetch adapter: pass `handler.handleWeb` directly as a route handler. */ handleWeb(req: Request): Promise; } @@ -327,7 +327,7 @@ export function computeFirstCheckout(opts: ComputeFirstOptions): ComputeFirstHan // composeMppx callback's probe-leg pass (no payment header → mppx returns // a 402 challenge with per-rail `request=` values). This // gives tempo/solana/stripe-spt the proper signing intent the agent needs - // — hand-rolling `paymentDirective` here with `request=''` is what broke + // hand-rolling `paymentDirective` here with `request=''` is what broke // the earlier smoke. x402-exact still uses PAYMENT-REQUIRED only (no // WWW-Authenticate directive needed). let mppChallengeHeaders: Record = {}; @@ -390,7 +390,7 @@ export function computeFirstCheckout(opts: ComputeFirstOptions): ComputeFirstHan } const x402Header = readX402PaymentHeader(req); const signer = await extractPaymentSigner(req, x402Header); - if (!signer) return null; // Stripe SPT — no wallet to screen + if (!signer) return null; // Stripe SPT: no wallet to screen const baseUrl = process.env.AGENTSCORE_BASE_URL; const core = createAgentScoreCore({ apiKey, @@ -426,7 +426,7 @@ export function computeFirstCheckout(opts: ComputeFirstOptions): ComputeFirstHan recipients: MintedRecipients, ): Promise { // Bind the agent-supplied `payTo` to the recipient this request actually advertised in its - // 402 (`recipients.x402_base` — static rail recipient OR per-order mint). The agent controls + // 402 (`recipients.x402_base`: static rail recipient OR per-order mint). The agent controls // `payTo` in the x402 payload, so accepting any address would let a hostile agent redirect the // USDC to their own wallet while still receiving the goods (funds drain). When no x402 recipient // was advertised (shouldn't happen on the x402 settle path), fall back to permissive so a @@ -650,7 +650,7 @@ export function computeFirstCheckout(opts: ComputeFirstOptions): ComputeFirstHan { status: 400, headers: { 'Content-Type': 'application/json' } }, ); } - // Wallet OFAC SDN enforcement (always-on default — matches Checkout's + // Wallet OFAC SDN enforcement (always-on default: matches Checkout's // `runWalletSanctionsOnly`). Strict-liability check before the rail- // specific settle so funds don't move (x402) or order doesn't fulfill // (MPP) for a sanctioned wallet. @@ -666,7 +666,7 @@ export function computeFirstCheckout(opts: ComputeFirstOptions): ComputeFirstHan try { outcome = await opts.runWork(body, { request: req }); } catch { - // Suppress the upstream exception detail in the wire response — merchant + // Suppress the upstream exception detail in the wire response: merchant // errors may carry stack traces or internal state. The merchant's own // logger surface (passed via opts.onError if present) is the right // channel for the full exception. diff --git a/src/core.ts b/src/core.ts index 55a0ed3..7f383d5 100644 --- a/src/core.ts +++ b/src/core.ts @@ -86,7 +86,7 @@ export interface CreateSessionOnMissing { | { context?: string; productName?: string }; /** Side-effect hook that runs after the session is minted. Return value is merged * into `DenialReason.extra` so custom `onDenied` handlers can include merchant-specific - * fields (e.g. `order_id`) in the 403 response. Hook errors are logged and swallowed — + * fields (e.g. `order_id`) in the 403 response. Hook errors are logged and swallowed: * a failing side effect should not block the 403 from reaching the agent. */ onBeforeSession?: (ctx: TCtx, session: SessionMetadata) => Promise> | Record; @@ -103,7 +103,7 @@ export interface AgentScoreCoreOptions { minAge?: number; /** List of blocked jurisdictions (blocklist). */ blockedJurisdictions?: string[]; - /** List of allowed jurisdictions (allowlist — only these pass). */ + /** List of allowed jurisdictions (allowlist: only these pass). */ allowedJurisdictions?: string[]; /** If true, allow the request through when the API is unreachable. Defaults to false. */ failOpen?: boolean; @@ -138,7 +138,7 @@ export type DenialCode = // - `token_expired`: revoked + TTL-expired (the API unifies these). The 401 body carries // an auto-minted session, forwarded into DenialReason for one-shot recovery. // - `invalid_credential`: the token doesn't exist at all (typo, never minted, fabricated). - // No auto-session is minted by the API for this case — agents should switch to a + // No auto-session is minted by the API for this case: agents should switch to a // different stored token or drop the header to bootstrap a fresh session. | 'token_expired' | 'invalid_credential'; @@ -187,7 +187,7 @@ export interface DenialReason { * response shape (e.g. to include a merchant-minted `order_id`). */ extra?: Record; // --------------------------------------------------------------------------- - // Wallet-signer-match fields — populated for wallet_signer_mismatch only. + // Wallet-signer-match fields: populated for wallet_signer_mismatch only. // --------------------------------------------------------------------------- /** Operator id resolved from `X-Wallet-Address`. */ claimed_operator?: string; @@ -251,7 +251,7 @@ export interface AssessResult { operator_handle?: string; verify_url?: string; policy_result?: PolicyResult | null; - /** IdP provenance, present only when `identity_method === 'aip_token'` — which issuer attested + /** IdP provenance, present only when `identity_method === 'aip_token'`: which issuer attested * the identity and the trust level it asserted. Mirrors the SDK's `AssessResponse.aip`. */ aip?: { issuer: string; @@ -268,14 +268,14 @@ export interface AssessResult { * infrastructure issues. Surfaced on `EvaluateOutcome` so merchants can log/alert when * their gate is running in degraded mode (compliance not actually enforced this request). * - * - `quota_exceeded` — AgentScore returned 429 - * - `api_error` — AgentScore returned 5xx or non-2xx that isn't 429 - * - `network_timeout` — request to /v1/assess timed out or failed at the network layer + * - `quota_exceeded`: AgentScore returned 429 + * - `api_error`: AgentScore returned 5xx or non-2xx that isn't 429 + * - `network_timeout`: request to /v1/assess timed out or failed at the network layer */ export type FailOpenInfraReason = 'quota_exceeded' | 'api_error' | 'network_timeout'; /** Per-account assess quota observability, captured from `X-Quota-*` response headers - * on the success path. Mirrors the SDK's `QuotaInfo` shape — re-exported from gate state + * on the success path. Mirrors the SDK's `QuotaInfo` shape: re-exported from gate state * so merchants can monitor approach-to-cap proactively (warn at 80%, alert at 95%). */ export interface GateQuotaInfo { limit: number | null; @@ -287,7 +287,7 @@ export interface GateQuotaInfo { /** * Outcome from `AgentScoreCore.evaluate()`. Adapters map this to framework-specific responses. * - * - `{ kind: 'allow', data }` — the request passed the policy. `data` is present on a normal + * - `{ kind: 'allow', data }`: the request passed the policy. `data` is present on a normal * allow; `undefined` when fail-open short-circuited (identity missing, API unreachable, * timeout, or 402 paid-tier required). * - When `failOpen: true` and the allow was the result of an AgentScore-side infrastructure @@ -297,11 +297,11 @@ export interface GateQuotaInfo { * `X-Quota-*` headers. Optional; absent on Enterprise / unlimited tiers. * - `signerVerdict` carries the per-request wallet-signer verdicts (signer_match + signer_sanctions) * composed by this evaluate's assess call. It rides on the RETURN VALUE (not a shared slot on the - * core) so concurrent same-wallet/different-signer requests can't read each other's verdict — + * core) so concurrent same-wallet/different-signer requests can't read each other's verdict: * the adapter stashes it on its per-request state for `getSignerVerdict(ctx)` to read back. * Present only on wallet-only identity when the response carried either signer block; `undefined` * otherwise (operator-token / AIT paths, discovery legs with no signer). - * - `{ kind: 'deny', reason }` — the request was denied. Adapters should render a 403 with the + * - `{ kind: 'deny', reason }`: the request was denied. Adapters should render a 403 with the * reason, or invoke the caller's custom denial handler. */ export type EvaluateOutcome = @@ -316,15 +316,15 @@ interface CaptureWalletOptions { operatorToken: string; /** Signer wallet recovered from the payment payload. */ walletAddress: string; - /** Key-derivation family — `"evm"` for any EVM chain, `"solana"` for Solana. */ + /** Key-derivation family: `"evm"` for any EVM chain, `"solana"` for Solana. */ network: 'evm' | 'solana'; /** Optional stable key for the logical payment (e.g., payment intent id, tx hash). When the - * same key is seen again for the same (credential, wallet, network), the server no-ops — + * same key is seen again for the same (credential, wallet, network), the server no-ops: * prevents agent retries from inflating transaction_count. */ idempotencyKey?: string; } -/** Combined wallet-signer verdict surfaced by `getSignerVerdict(c)` — both verdicts come +/** Combined wallet-signer verdict surfaced by `getSignerVerdict(c)`: both verdicts come * through the gate's primary `/v1/assess` call (single round trip). `signer_match` describes * the wallet-binding (claimed wallet's operator ≡ signer wallet's operator); `signer_sanctions` * describes the OFAC SDN wallet-address check. @@ -424,18 +424,18 @@ interface CachedAssessResult { * correct pointers. Contents describe the AgentScore identity substrate in transferable * terms; merchant-specific context lives in other `agent_instructions` fields. */ -// Canonical production AgentScore API — used as the authoritative source for endpoint pointers +// Canonical production AgentScore API: used as the authoritative source for endpoint pointers // emitted to agent memory regardless of how a given merchant configured their gate's baseUrl. const CANONICAL_AGENTSCORE_API = 'https://api.agentscore.com'; // JSON-encoded action copy emitted on wallet-signer-match denials. Spread into 403 bodies -// by merchants so agents get a concrete recovery path inside the denial response itself — +// by merchants so agents get a concrete recovery path inside the denial response itself: // no discovery-doc round trip required. const WALLET_SIGNER_MISMATCH_INSTRUCTIONS = JSON.stringify({ action: 'resign_or_switch_to_operator_token', steps: [ - 'Preferred: re-submit the payment signed by expected_signer (or any entry in linked_wallets — same-operator wallets are fungible) and retry with the same X-Wallet-Address.', - 'Alternative: drop X-Wallet-Address and retry with X-Operator-Token. Use a stored opc_... if you have one; otherwise retry this request with NO identity header — the merchant will mint a verification session in the 403 body (verify_url + poll_secret). Share verify_url with the user, poll, receive a fresh opc_...', + 'Preferred: re-submit the payment signed by expected_signer (or any entry in linked_wallets: same-operator wallets are fungible) and retry with the same X-Wallet-Address.', + 'Alternative: drop X-Wallet-Address and retry with X-Operator-Token. Use a stored opc_... if you have one; otherwise retry this request with NO identity header: the merchant will mint a verification session in the 403 body (verify_url + poll_secret). Share verify_url with the user, poll, receive a fresh opc_...', ], user_message: 'The payment signer resolves to a different operator than X-Wallet-Address. Re-sign from expected_signer or any linked_wallets entry, or switch to X-Operator-Token.', @@ -444,30 +444,30 @@ const WALLET_SIGNER_MISMATCH_INSTRUCTIONS = JSON.stringify({ const WALLET_AUTH_REQUIRES_WALLET_SIGNING_INSTRUCTIONS = JSON.stringify({ action: 'switch_to_operator_token', steps: [ - 'This payment rail (Stripe SPT, card) carries no wallet signature — X-Wallet-Address cannot be verified against the payment.', + 'This payment rail (Stripe SPT, card) carries no wallet signature: X-Wallet-Address cannot be verified against the payment.', 'Drop X-Wallet-Address and retry with X-Operator-Token. If you do not have a stored opc_..., retry with no identity header to receive a verification session.', ], user_message: 'Wallet-address identity is only supported on wallet-signing rails (Tempo MPP, x402). On Stripe or card, use X-Operator-Token instead.', }); -// `invalid_credential` is permanent — the token doesn't exist (typo, never minted, fabricated). +// `invalid_credential` is permanent: the token doesn't exist (typo, never minted, fabricated). // Distinct from `token_expired` (which carries an auto-session for one-shot recovery): the // API doesn't auto-mint a session here because the client may simply have a different valid // token to try. Agents should switch tokens or drop the header to bootstrap a session. const INVALID_CREDENTIAL_INSTRUCTIONS = JSON.stringify({ action: 'switch_token_or_restart_session', steps: [ - 'The X-Operator-Token you sent does not match any credential. This is a permanent state — retrying with the same token will keep failing.', + 'The X-Operator-Token you sent does not match any credential. This is a permanent state: retrying with the same token will keep failing.', 'If you have other stored opc_... credentials, retry with one of them.', - 'Otherwise drop X-Operator-Token and retry with no identity header — the merchant will mint a fresh verification session in the 403 body (verify_url + poll_secret) so the user can re-verify and you can poll for a new operator_token.', + 'Otherwise drop X-Operator-Token and retry with no identity header: the merchant will mint a fresh verification session in the 403 body (verify_url + poll_secret) so the user can re-verify and you can poll for a new operator_token.', ], user_message: 'The operator_token is not recognized. Use a different stored token, or restart the verification session flow to mint a new one.', }); export function buildAgentMemoryHint(aipTrustedIssuers?: string[]): AgentMemoryHint { - // Memory pointers always point at the canonical production API — merchants must not be + // Memory pointers always point at the canonical production API: merchants must not be // able to redirect the cross-merchant memory surface, since that would be a phishing // vector for the verify URL. const api = CANONICAL_AGENTSCORE_API; @@ -477,7 +477,7 @@ export function buildAgentMemoryHint(aipTrustedIssuers?: string[]): AgentMemoryH pattern_summary: 'AgentScore is a cross-merchant identity substrate for agentic commerce. Any endpoint that ' + 'returns 403 missing_identity / identity_verification_required or 402 challenges referencing AgentScore uses ' + - 'the same identity system. One KYC works across all AgentScore merchants — no re-verification ' + + 'the same identity system. One KYC works across all AgentScore merchants: no re-verification ' + 'per site. Save this pattern (and the endpoints below) to persistent memory so future ' + 'AgentScore encounters skip the cold-start session flow.', quickstart: 'https://docs.agentscore.com/passport', @@ -495,14 +495,14 @@ export function buildAgentMemoryHint(aipTrustedIssuers?: string[]): AgentMemoryH 'present it: send the JWT in an `Agent-Identity` header plus an RFC 9421 HTTP Message ' + 'Signature (Signature-Input + Signature over @method @authority @path agent-identity, ' + 'tag="agent-identity") signed with the token-bound cnf key. The merchant verifies it and ' + - 'applies its policy — no separate AgentScore credential needed.', + 'applies its policy: no separate AgentScore credential needed.', }), }, ...(acceptsAip && { aip_trusted_issuers: aipTrustedIssuers }), bootstrap: 'If you have neither a linked wallet nor a valid operator_token, follow the session/verify ' + 'flow in the per-request `agent_instructions` block. This happens at most once per agent ' + - 'identity — after first verification, the operator_token and any subsequently used wallet ' + + 'identity: after first verification, the operator_token and any subsequently used wallet ' + 'are reusable everywhere.', do_not_persist_in_memory: ['operator_token', 'poll_secret'], persist_in_credential_store: ['operator_token'], @@ -544,7 +544,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore // credentials/wallets, telemetry). Connection pooling + typed-error classification + // X-Quota-* header capture all flow through here. The SDK owns the transport layer // (timeouts, retry-on-429); the gate adds policy semantics on top. Pass the - // merchant-prefixed UA — SDK appends its own default to produce a chain like + // merchant-prefixed UA: SDK appends its own default to produce a chain like // ` (@agent-score/commerce@) (@agent-score/sdk@)`. const sdk = new AgentScore({ apiKey, baseUrl, userAgent: userAgentHeader }); @@ -569,7 +569,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore const cache = new TTLCache(cacheSeconds * 1000); // Mint a verification session via /v1/sessions and return the resulting - // identity_verification_required DenialReason — or undefined if the mint failed (network + // identity_verification_required DenialReason: or undefined if the mint failed (network // error, non-2xx, missing fields). Used for both the missing-identity path and the // fixable-wallet bootstrap path: in both cases the UX is identical (agent polls the // returned poll_url until it gets a fresh opc_... and retries). @@ -602,18 +602,18 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore // Validate required fields before trusting the response. A misbehaving (or mocked-wrong) // API could 200 without session_id/poll_secret/verify_url, which would propagate - // `undefined` into the 403 body and leave the agent stuck — treat as session-create + // `undefined` into the 403 body and leave the agent stuck: treat as session-create // failure and fall back to the caller's bare denial. if ( typeof data.session_id !== 'string' || typeof data.poll_secret !== 'string' || typeof data.verify_url !== 'string' ) { - console.warn('[gate] /v1/sessions returned 200 without required fields — falling back to bare denial'); + console.warn('[gate] /v1/sessions returned 200 without required fields: falling back to bare denial'); return undefined; } - // Run onBeforeSession side-effect hook. Errors are swallowed — a failing DB write + // Run onBeforeSession side-effect hook. Errors are swallowed: a failing DB write // (e.g. can't insert pending order) should not block the 403. let extra: Record | undefined; if (createSessionOnMissing.onBeforeSession && ctx !== undefined) { @@ -653,10 +653,10 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore }; } catch (err) { // Session-mint failed (network, /v1/sessions returned non-2xx, body parse error, - // onBeforeSession threw inside the inner try). Caller falls back to a bare denial — + // onBeforeSession threw inside the inner try). Caller falls back to a bare denial: // agents still get a 403 with a probe-strategy hint. Log loudly so a persistent // /v1/sessions outage isn't masked. - console.warn('[gate] createSessionOnMissing path failed — falling back to bare denial:', err instanceof Error ? err.message : err); + console.warn('[gate] createSessionOnMissing path failed: falling back to bare denial:', err instanceof Error ? err.message : err); return undefined; } } @@ -666,11 +666,11 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore ctx?: unknown, signer?: PaymentSigner | null, ): Promise { - // Treat "returned identity object with no usable fields" the same as "no identity at all" — + // Treat "returned identity object with no usable fields" the same as "no identity at all": // otherwise a misbehaving custom extractIdentity would send an empty body to /v1/assess. if (!identity || (!identity.address?.trim() && !identity.operatorToken?.trim() && !identity.aipToken?.trim())) { // failOpen short-circuits BEFORE the session mint. This branch isn't an infra failure - // (no AgentScore call has been made yet) so we don't mark the gate state as degraded — + // (no AgentScore call has been made yet) so we don't mark the gate state as degraded: // missing identity + failOpen is the explicit opt-in pass-through behavior, not a // graceful-degradation event. Merchants who need identity-or-deny on a failOpen gate // should add a guard at the handler that checks for X-Wallet-Address / X-Operator-Token @@ -687,16 +687,16 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore const acceptsAip = aipTrustedIssuers !== undefined && aipTrustedIssuers.length > 0; const aipStep = acceptsAip ? [ - `If you hold an AIP Agent Identity Token from a trusted issuer (${aipTrustedIssuers.join(', ')}): present it — send the JWT in an Agent-Identity header plus an RFC 9421 HTTP Message Signature (Signature-Input + Signature over @method @authority @path agent-identity, tag="agent-identity") signed with the token-bound cnf key. This satisfies identity in one round trip without an AgentScore credential.`, + `If you hold an AIP Agent Identity Token from a trusted issuer (${aipTrustedIssuers.join(', ')}): present it: send the JWT in an Agent-Identity header plus an RFC 9421 HTTP Message Signature (Signature-Input + Signature over @method @authority @path agent-identity, tag="agent-identity") signed with the token-bound cnf key. This satisfies identity in one round trip without an AgentScore credential.`, ] : []; const missingIdentityInstructions = JSON.stringify({ action: 'probe_identity_then_session', steps: [ ...aipStep, - 'If you have a wallet AND this merchant accepts a wallet-signing rail (Tempo MPP or x402 — see openapi.json x-payment-info): send X-Wallet-Address and retry. If the wallet is already linked to an AgentScore operator (via prior SIWE claim or prior captureWallet), this passes in one round trip. If the wallet is unlinked or the account has no KYC, the 403 will include a verify_url — share it with the user to claim the wallet + complete KYC, then retry.', + 'If you have a wallet AND this merchant accepts a wallet-signing rail (Tempo MPP or x402; see openapi.json x-payment-info): send X-Wallet-Address and retry. If the wallet is already linked to an AgentScore operator (via prior SIWE claim or prior captureWallet), this passes in one round trip. If the wallet is unlinked or the account has no KYC, the 403 will include a verify_url: share it with the user to claim the wallet + complete KYC, then retry.', 'If step 1 is denied or you already have a stored operator_token (valid + not expired): send X-Operator-Token: opc_... and retry.', - 'If neither applies: retry with NO identity header. Merchants that auto-create verification sessions (most AgentScore merchants do) return verify_url + session_id + poll_secret in the 403 body — share verify_url with the user, then poll poll_url every 5s with the X-Poll-Secret header until status=verified (the poll returns a one-time operator_token). If the retry returns the same bare 403, this merchant does not support self-service session bootstrapping — direct the user to https://www.agentscore.com/sign-up to create an AgentScore identity and mint an operator_token from their dashboard (https://www.agentscore.com/dashboard/verify). The user hands the opc_... to you, and you retry with X-Operator-Token.', + 'If neither applies: retry with NO identity header. Merchants that auto-create verification sessions (most AgentScore merchants do) return verify_url + session_id + poll_secret in the 403 body: share verify_url with the user, then poll poll_url every 5s with the X-Poll-Secret header until status=verified (the poll returns a one-time operator_token). If the retry returns the same bare 403, this merchant does not support self-service session bootstrapping: direct the user to https://www.agentscore.com/sign-up to create an AgentScore identity and mint an operator_token from their dashboard (https://www.agentscore.com/dashboard/verify). The user hands the opc_... to you, and you retry with X-Operator-Token.', ], user_message: 'Try X-Wallet-Address first if you have a wallet and the merchant accepts Tempo/x402; fall back to a stored X-Operator-Token, then to the session/verify flow described in agent_memory.bootstrap.', @@ -711,7 +711,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore }; } - // operator_token is opaque + ASCII-only — lowercasing is safe. Wallet addresses go + // operator_token is opaque + ASCII-only: lowercasing is safe. Wallet addresses go // through normalizeAddress because Solana base58 is case-sensitive and lowercasing // would corrupt the cache key (a Solana cache miss every time, plus collision risk // with mixed-case variants of the same operator). @@ -726,7 +726,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore // signer_sanctions). The signer is NOT part of the identity, so it must be folded into the // response cache key: otherwise a 2nd request with the SAME claimed identity but a DIFFERENT // payment signer would hit the cached decision and return the first signer's stale - // signer_match/signer_sanctions — bypassing wallet-signer binding AND the unconditional OFAC + // signer_match/signer_sanctions: bypassing wallet-signer binding AND the unconditional OFAC // signer screen (a sanctioned 2nd signer would settle on a cached `clear`). Appending the // normalized signer (network + address) makes a different signer a cache MISS, forcing a fresh // assess that re-screens it. Requests with no signer keep the identity-only key (no behavior @@ -743,7 +743,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore const cached = cache.get(cacheKey); if (cached) { // Recompute the per-request verdict from the cached response. The cache key folds in the - // signer, so a cache hit is the SAME (identity, signer) pair — the cached signer blocks are + // signer, so a cache hit is the SAME (identity, signer) pair: the cached signer blocks are // this request's verdict. Riding it on the return value (vs a shared slot) is what makes // concurrent same-wallet/different-signer reads race-free. const cachedVerdict = cached.raw @@ -769,7 +769,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore // (sanctions_flagged, age_insufficient, jurisdiction_restricted) keep the bare // wallet_not_trusted denial. `jurisdiction_restricted` is unfixable: the API // only emits it after KYC is verified (the user's KYC'd country is in the - // blocked list — re-doing KYC won't change the country). + // blocked list: re-doing KYC won't change the country). if (isFixableDenial(cached.reasons)) { const sessionReason = await tryMintSessionDenial(ctx); if (sessionReason) { @@ -807,16 +807,16 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore // Pre-extracted payment signer (by the adapter middleware). When present, the API // composes BOTH signer_match (wallet-binding) and signer_sanctions (OFAC SDN wallet // check) verdicts on the response in one round trip. Wallet-OFAC enforcement on the - // signer block is unconditional — a signer_sanctions hit flips decision -> deny + // signer block is unconditional: a signer_sanctions hit flips decision -> deny // regardless of policy.require_sanctions_clear (which gates the separate NAME screen). ...(signer && { signer: { address: signer.address, network: signer.network } }), }; - // SDK has overloads — narrow by which identity is set so TS picks the right one. + // SDK has overloads: narrow by which identity is set so TS picks the right one. // AIT takes precedence: when present it's the sole identity input. The edge already // PoP-verified as a fail-fast filter; we forward the token + signature material so the API // re-verifies the IdP signature AND the proof-of-possession authoritatively. Checkout always // sets both together; a hand-rolled evaluate() with aipToken but no aipSignature is a caller - // error — fail loudly rather than silently misrouting to the operator-token branch. + // error: fail loudly rather than silently misrouting to the operator-token branch. if (identity.aipToken !== undefined && identity.aipSignature === undefined) { throw new Error('AgentScoreCore.evaluate: aipToken requires aipSignature (RFC 9421 proof-of-possession material).'); } @@ -832,7 +832,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore return { kind: 'deny', reason: { code: 'payment_required' } }; } if (err instanceof TokenExpiredError) { - // SDK extracts the auto-minted session fields onto the error instance — no body + // SDK extracts the auto-minted session fields onto the error instance: no body // re-parsing needed here. return { kind: 'deny', @@ -849,7 +849,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore }; } if (err instanceof InvalidCredentialError) { - // Permanent — no auto-session, agent should switch tokens or restart. + // Permanent: no auto-session, agent should switch tokens or restart. return { kind: 'deny', reason: { @@ -879,7 +879,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore const status = (err as { status?: number } | null)?.status; const errName = err instanceof Error ? err.name : ''; if (status === 429) { - console.warn('[gate] /v1/assess returned 429 (untyped — defensive)'); + console.warn('[gate] /v1/assess returned 429 (untyped: defensive)'); if (failOpen) return { kind: 'allow', degraded: true, infraReason: 'quota_exceeded' }; return { kind: 'deny', @@ -892,13 +892,13 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore return { kind: 'deny', reason: { code: 'api_error' } }; } // Generic AgentScoreError (rate_limited, 5xx, network_error, body parse, unknown 4xx) - // or any non-AgentScoreError unexpected throw — surface as api_error. + // or any non-AgentScoreError unexpected throw: surface as api_error. // Include the SDK-classified error code (when available) so ops/dev see // schema-drift cases like a new 401 error.code rather than a silent 503. const errCode = (err as { code?: string } | null)?.code; const msg = err instanceof Error ? err.message : String(err); const detail = errCode ? `${errCode}: ${msg}` : msg; - console.warn(`[gate] /v1/assess call failed — surfacing as api_error: ${detail}`); + console.warn(`[gate] /v1/assess call failed: surfacing as api_error: ${detail}`); if (failOpen) return { kind: 'allow', degraded: true, infraReason: 'api_error' }; return { kind: 'deny', reason: { code: 'api_error' } }; } @@ -989,7 +989,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore // (sanctions_flagged, age_insufficient, jurisdiction_restricted) keep the bare // wallet_not_trusted denial. `jurisdiction_restricted` is unfixable: the API // only emits it after KYC is verified (the user's KYC'd country is in the - // blocked list — re-doing KYC won't change the country). + // blocked list: re-doing KYC won't change the country). if (isFixableDenial(decisionReasons)) { const sessionReason = await tryMintSessionDenial(ctx); if (sessionReason) { @@ -1035,7 +1035,7 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore }); } catch (err) { // Fire-and-forget: don't throw. Log so a persistent capture outage is visible - // to merchant ops — otherwise wallet↔operator linkage silently stops. + // to merchant ops: otherwise wallet↔operator linkage silently stops. console.warn('[agentscore-commerce] captureWallet failed:', err instanceof Error ? err.message : err); } } @@ -1083,11 +1083,11 @@ export function createAgentScoreCore(options: AgentScoreCoreOptions): AgentScore /** * Build the per-request signer verdicts (signer_match + signer_sanctions) from an assess * response. Returns `undefined` unless the wallet is the EFFECTIVE identity (no operator_token, - * no AIT — operator-token / AIT wins and signer-match is deliberately NOT enforced) AND the + * no AIT: operator-token / AIT wins and signer-match is deliberately NOT enforced) AND the * response carried at least one signer block. * * The verdict is returned on the `EvaluateOutcome` and stashed by each adapter on its - * PER-REQUEST state (Hono `c`, Express/Fastify `request`, web closure), NOT on a shared slot — + * PER-REQUEST state (Hono `c`, Express/Fastify `request`, web closure), NOT on a shared slot: * so concurrent same-wallet/different-signer requests can never read each other's verdict. */ function buildSignerVerdict( diff --git a/src/discovery/agentscore_content.ts b/src/discovery/agentscore_content.ts index 3d37496..73c1ba0 100644 --- a/src/discovery/agentscore_content.ts +++ b/src/discovery/agentscore_content.ts @@ -14,7 +14,7 @@ /** * Whether a paid surface accepts redemption codes. Applies to any merchant - * that bills per-purchase or per-call — goods (catalog rows) and API + * that bills per-purchase or per-call: goods (catalog rows) and API * (per-endpoint or per-tier billing) both use this enum. */ export type PurchaseMode = 'redemption_only' | 'coupon_applicable' | 'paid_only'; @@ -59,7 +59,7 @@ export function purchaseModeNote(mode: string): string { * Unknown rail names are passed through verbatim so future rails work without * an SDK bump. * - * Pass `vendorType: 'api'` for per-call API providers — the catalog step is + * Pass `vendorType: 'api'` for per-call API providers: the catalog step is * dropped and the final step becomes "Make the paid call" instead of "Place * the order". */ @@ -207,7 +207,7 @@ export function standardEndpointDescriptions(opts?: { /** * Build the canonical AgentScore commerce `/` root discovery body. Works for * both goods merchants (catalog + purchase + orders) and API merchants (per- - * call paid endpoints) — `endpoints` and any merchant-specific fields are + * call paid endpoints): `endpoints` and any merchant-specific fields are * passed through `extra`. * * Common fields surfaced: `name`, `description`, `docs`, `endpoints`, @@ -240,7 +240,7 @@ export function buildMerchantIndexJson(opts: { /** * Standard `next_steps` block emitted in a 200 success body. Works for both - * goods-merchant order-success and API-merchant per-call-success — the + * goods-merchant order-success and API-merchant per-call-success: the * `user_message` reinforces the cross-merchant Passport pattern (universal), * with merchant-specific copy overridable via `userMessage`. * @@ -248,7 +248,7 @@ export function buildMerchantIndexJson(opts: { * have an order-detail endpoint can either pass a usage/dashboard URL or omit * the field by passing an empty string (filtered out before emit). * - * `fulfillmentEta` is goods-specific (shipping window) — omit for API or + * `fulfillmentEta` is goods-specific (shipping window): omit for API or * digital-goods merchants. */ export function buildSuccessNextSteps(opts: { diff --git a/src/discovery/llms_txt.ts b/src/discovery/llms_txt.ts index 4152040..54ad005 100644 --- a/src/discovery/llms_txt.ts +++ b/src/discovery/llms_txt.ts @@ -56,7 +56,7 @@ AgentScore identity is reusable across every AgentScore-gated merchant: one KYC, interface LlmsTxtPaymentSectionConfig { /** Symbolic rail names supported. */ rails: ('tempo-mainnet' | 'tempo-testnet' | 'x402-base-mainnet' | 'x402-base-sepolia' | 'mpp-solana-mainnet' | 'mpp-solana-devnet' | 'stripe-spt' | string)[]; - /** Merchant URL — used in the example commands. */ + /** Merchant URL: used in the example commands. */ appUrl: string; /** * When true, emit the verbose multi-step variant: setup commands per rail, full per-rail @@ -75,7 +75,7 @@ interface LlmsTxtPaymentSectionConfig { * supported rails with concrete CLI examples (tempo request, agentscore-pay, link-cli) * per the configured rail set. * - * Pass `verbose: true` for the rich variant — multi-step setup + multi-line command examples + + * Pass `verbose: true` for the rich variant: multi-step setup + multi-line command examples + * exact-amount warnings. Default is the compact one-bullet-per-rail form. */ export function llmsTxtPaymentSection(input: LlmsTxtPaymentSectionConfig): string { diff --git a/src/discovery/openapi.ts b/src/discovery/openapi.ts index 1119295..f0b8261 100644 --- a/src/discovery/openapi.ts +++ b/src/discovery/openapi.ts @@ -3,7 +3,7 @@ * OpenAPI 3.1 document (typically /openapi.json) so MPPScan and similar agent registries * can validate the merchant's auth + denial schemas correctly. * - * Each helper returns a piece of an OpenAPI document — vendors compose them into their + * Each helper returns a piece of an OpenAPI document: vendors compose them into their * full spec. */ @@ -330,7 +330,7 @@ export function xServiceInfoExtension(opts: { /** * Derive an `x-payment-info` extension from a configured `Checkout` instance. * - * Walks `checkout.rails` and emits one entry in `protocols[]` per rail — + * Walks `checkout.rails` and emits one entry in `protocols[]` per rail: * Tempo MPP, x402 (Base), Solana MPP, Stripe SPT. Saves merchants from * enumerating protocols by hand and keeps the OpenAPI doc in sync with the * actual rails the Checkout serves. @@ -381,7 +381,7 @@ export function xPaymentInfoFromCheckout(opts: { } else if (network.startsWith('solana:')) { // Per MPP solana/charge spec (paymentauth.org/draft-solana-charge-00): // `currency` = SPL mint address (base58) for tokens, `"sol"` for native. - // No `asset` field in the spec — token symbols are not part of the + // No `asset` field in the spec: token symbols are not part of the // discovery contract. protocols.push({ mpp: { diff --git a/src/discovery/probe.ts b/src/discovery/probe.ts index ec6e962..4947f5f 100644 --- a/src/discovery/probe.ts +++ b/src/discovery/probe.ts @@ -3,7 +3,7 @@ import { networks } from '../payment/networks'; import { USDC } from '../payment/usdc'; import { paymentRequiredHeader } from '../payment/wwwauthenticate'; -/** Placeholder payTo for x402 sample accepts in the discovery probe — the probe +/** Placeholder payTo for x402 sample accepts in the discovery probe: the probe * exists for crawlers to find that we support x402, not for actual payment. The * real 402 (returned on a fully-formed request body) carries real deposit * addresses minted from a Stripe PaymentIntent. */ @@ -16,7 +16,7 @@ const ZERO_SOLANA_PAYTO = '11111111111111111111111111111111'; * the discovery probe to advertise x402 support without exposing real deposit * addresses. * - * Returns null when the network isn't in the registry — vendors with custom + * Returns null when the network isn't in the registry: vendors with custom * networks should construct accepts entries by hand and pass them via * `x402Sample.accepts` directly. */ @@ -74,7 +74,7 @@ export function sampleX402AcceptForNetwork( } interface DiscoveryProbeOptions { - /** Realm — typically the host of your merchant URL (e.g., "agents.merchant.example"). */ + /** Realm: typically the host of your merchant URL (e.g., "agents.merchant.example"). */ realm: string; /** Symbolic rail name to advertise in the sample challenge (e.g., 'tempo-mainnet'). */ sampleRail: string; @@ -92,13 +92,13 @@ interface DiscoveryProbeOptions { message?: string; /** Optional sample x402 accepts entries. When provided, the probe response also * carries the standard x402 `payment-required` header (base64 PaymentRequired) AND - * an `accepts` array in the body — so x402 crawlers (e.g. Coinbase awal's + * an `accepts` array in the body: so x402 crawlers (e.g. Coinbase awal's * `x402 details`/`x402 pay`) can discover the endpoint's x402 support without * needing to send a fully-formed business request. Entries are emitted as-is in * their declared `x402Version` shape (v2 `amount`); clients version-route on * `x402Version`. * - * Pass `networks` (shorthand) for the common case — the helper looks up USDC + * Pass `networks` (shorthand) for the common case: the helper looks up USDC * per network from the registry and uses placeholder payTo addresses. Or pass * `accepts` directly for full control over the sample shape. */ x402Sample?: { @@ -109,7 +109,7 @@ interface DiscoveryProbeOptions { * USDC registry are silently skipped. Use `accepts` for custom shapes. */ networks?: string[]; /** Sample accepts entries. Used when `networks` shorthand isn't enough. - * Supplied entries are NOT merged with `networks`-derived entries — pick + * Supplied entries are NOT merged with `networks`-derived entries: pick * one or the other. */ accepts?: unknown[]; /** Sample atomic amount used by the `networks` shorthand. Defaults to @@ -228,7 +228,7 @@ export interface RequestLike { } /** - * Returns true when the request is an empty-body POST without a payment credential — + * Returns true when the request is an empty-body POST without a payment credential: * the canonical MPP discovery probe pattern. Vendors compose this with * buildDiscoveryProbeResponse to short-circuit crawler requests before any business * logic runs. diff --git a/src/discovery/request_id.ts b/src/discovery/request_id.ts index 45438cb..2e4ecd9 100644 --- a/src/discovery/request_id.ts +++ b/src/discovery/request_id.ts @@ -2,7 +2,7 @@ * Echo the request-id middleware sets on the context as an `X-Request-ID` * response header. Agents correlate logs across 4xx retries by reading this. * - * Hono variant — reads `c.get('requestId')` populated by `hono/request-id`. + * Hono variant: reads `c.get('requestId')` populated by `hono/request-id`. * Express / Fastify / Next.js / Web Fetch variants will follow when consumers * need them. * diff --git a/src/discovery/robots_tag.ts b/src/discovery/robots_tag.ts index aa8e074..a2092ed 100644 --- a/src/discovery/robots_tag.ts +++ b/src/discovery/robots_tag.ts @@ -5,7 +5,7 @@ * They should NOT carry `X-Robots-Tag: noindex` since the whole point is for * agents (and search/discovery crawlers) to find them. * - * Everything else on an agent-only API should noindex by default — there's no + * Everything else on an agent-only API should noindex by default: there's no * human-shaped HTML to surface to general search engines, and accidental * indexing leaks transactional endpoints into noisy SERPs. */ @@ -26,7 +26,7 @@ export const defaultDiscoveryPaths: ReadonlySet = new Set([ /** * Pure predicate for "is this path a known discovery surface?". Compose this * into your own framework's middleware when you don't want the bundled Hono - * wrapper. Custom paths are the union with the defaults — pass `replace: true` + * wrapper. Custom paths are the union with the defaults: pass `replace: true` * to skip the defaults. */ export function isDiscoveryPath( @@ -52,7 +52,7 @@ interface NoindexNonDiscoveryOptions { * set (e.g. omits `/openapi.json` from a closed API). */ replacePaths?: boolean; /** Override the X-Robots-Tag value applied to non-discovery paths. Defaults to - * the standard "noindex, nofollow, noarchive, nosnippet" tuple — change only + * the standard "noindex, nofollow, noarchive, nosnippet" tuple: change only * if you have a very specific crawl-shape requirement. */ robotsTag?: string; } @@ -162,6 +162,6 @@ export function wrapNoindexResponse( * return applyNoindexHeader(res, path); * } * - * Same wrapper shape as the Web Fetch helper — exported separately for clarity + * Same wrapper shape as the Web Fetch helper: exported separately for clarity * in Next.js docs/examples. */ export const applyNoindexHeader = wrapNoindexResponse; diff --git a/src/discovery/skill_md.ts b/src/discovery/skill_md.ts index 97f0371..b64b9ed 100644 --- a/src/discovery/skill_md.ts +++ b/src/discovery/skill_md.ts @@ -41,18 +41,18 @@ export interface SkillMdLink { } interface BuildSkillMdInput { - /** Skill manifest identifier — kebab-case per agentskills.io spec: 1-64 chars, lowercase + /** Skill manifest identifier: kebab-case per agentskills.io spec: 1-64 chars, lowercase * alphanumeric + hyphens, no leading/trailing/consecutive hyphens. Validated at build * time; invalid names throw. e.g. 'example-merchant-commerce'. */ name: string; - /** Skill description — agentskills.io spec: 1-1024 chars, non-empty. Should describe both + /** Skill description: agentskills.io spec: 1-1024 chars, non-empty. Should describe both * what the skill does AND when to use it; imperative phrasing recommended ("Use when…"). * Validated at build time; over-length throws. */ description: string; /** Merchant homepage (or domain root). Emitted as `metadata.homepage` per spec * (top-level non-spec fields go under metadata). */ homepage: string; - /** Skill schema version — increment when the skill body materially changes. Emitted as + /** Skill schema version: increment when the skill body materially changes. Emitted as * a quoted string under `metadata.version` per agentskills.io spec (metadata values * must be strings). Accepts string or number; numbers are converted. Default "1". */ version?: string | number; @@ -65,7 +65,7 @@ interface BuildSkillMdInput { compatibility?: string; /** Optional space-separated string of pre-approved tools (experimental per spec). */ allowedTools?: string; - /** Additional caller-defined metadata entries — flat key/value strings nested under + /** Additional caller-defined metadata entries: flat key/value strings nested under * `metadata:`. Spec requires string values. */ metadata?: Record; @@ -77,13 +77,13 @@ interface BuildSkillMdInput { intro?: string; /** Files / well-known URLs surfaced under the "Important Files" table. The skill.md URL - * itself is added automatically — list other discovery surfaces (llms.txt, mpp.json, + * itself is added automatically: list other discovery surfaces (llms.txt, mpp.json, * openapi.json, agent-card.json). */ files?: SkillMdLink[]; /** Rails the merchant accepts. Drives the Payment + Compatible Clients sections. Order * is preserved in render. Default to the rails actually declared on the merchant's - * `respond402` config — keep these in sync. */ + * `respond402` config: keep these in sync. */ acceptedRails: RailKey[]; /** Override the per-rail compatible-clients matrix. When omitted, derives from * `acceptedRails` via the SDK's smoke-verified default. Override keys not in @@ -92,7 +92,7 @@ interface BuildSkillMdInput { /** Identity requirements as agent-observable outcomes (kyc / age / jurisdiction / * sanctions). Internal posture (`failOpen`, mount strategy, KYC vendor) is intentionally - * not part of this shape — agents act on outcomes, not implementation. */ + * not part of this shape: agents act on outcomes, not implementation. */ identity?: SkillMdIdentityRequirements; /** URL to the identity-bootstrap skill. Linked from the Identity Prerequisite section * so an agent without a Passport can follow the bootstrap before attempting purchase. */ @@ -101,7 +101,7 @@ interface BuildSkillMdInput { /** Shipping policy, for physical-goods merchants. Omit for digital merchants. */ shipping?: SkillMdShippingPolicy; - /** Agent-facing endpoints — path, method, whether auth is required, brief purpose. */ + /** Agent-facing endpoints: path, method, whether auth is required, brief purpose. */ endpoints: SkillMdEndpoint[]; /** When this skill should fire (skill loader uses for trigger matching). */ @@ -162,14 +162,14 @@ function validateInput(input: BuildSkillMdInput): void { } } -/** Quote a value as a YAML double-quoted scalar — escape `\\`, `"`, and newlines. The +/** Quote a value as a YAML double-quoted scalar: escape `\\`, `"`, and newlines. The * agentskills.io spec calls out unquoted colons in `description` as the most common * parse failure across clients; emit every user-supplied scalar quoted to be safe. */ function quoteYaml(value: string): string { return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`; } -/** Sanitize a string for inclusion in a markdown table cell — escape backslashes first +/** Sanitize a string for inclusion in a markdown table cell: escape backslashes first * (so existing `\` aren't treated as escapes), then escape pipes (which would otherwise * terminate the cell). */ function tableCell(value: string): string { @@ -326,7 +326,7 @@ function titleBlock(input: BuildSkillMdInput): string { * Output is YAML frontmatter (`name` / `description` / optional `license` / * `compatibility` / `allowed-tools` / `metadata`) followed by markdown sections * describing payment rails, identity requirements, endpoints, triggers, and support - * links — strictly the agent-facing contract, with no internal posture (no `failOpen`, + * links: strictly the agent-facing contract, with no internal posture (no `failOpen`, * no mount-strategy names, no KYC vendor, no defense parameters). * * Spec compliance: diff --git a/src/discovery/well_known.ts b/src/discovery/well_known.ts index 3ec9736..87e869b 100644 --- a/src/discovery/well_known.ts +++ b/src/discovery/well_known.ts @@ -84,7 +84,7 @@ function isTempoSession(s: CheckoutRailSpec): s is TempoSessionRailSpec { function isStripe(s: CheckoutRailSpec): s is StripeRailSpec { return !('recipient' in s); } -/** A rail spec qualifies for UCP publication when `recipient` is defined — +/** A rail spec qualifies for UCP publication when `recipient` is defined: * whether concrete (`'0xabc'`), empty-string sentinel (per-order minted by * the consumer), or a factory callable (per-order minted on demand). The * `tempoToNetworkEntry` / `x402ToNetworkEntry` builders drop the recipient diff --git a/src/errors.ts b/src/errors.ts index c34c347..1e7d6c2 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -2,7 +2,7 @@ * Cross-module typed errors. * * Lives in its own module so `payment/` and `stripe-multichain/` helpers can - * throw `CheckoutValidationError` without importing from `checkout.ts` — this + * throw `CheckoutValidationError` without importing from `checkout.ts`: this * also sidesteps tsup's per-entry class duplication. * * Re-exported from `checkout.ts` and the top-level entry to preserve the diff --git a/src/forwarded_proto.ts b/src/forwarded_proto.ts index 087b19c..e83a24a 100644 --- a/src/forwarded_proto.ts +++ b/src/forwarded_proto.ts @@ -2,8 +2,8 @@ * Rewrite a URL's scheme to the proxy's original protocol. * * Behind a TLS-terminating edge proxy (ALB / CloudFront / nginx) the inbound - * request arrives as `http://`, but x402 discovery — and the mppx client's - * resource-match check — require the public `https://`. Honor `X-Forwarded-Proto` + * request arrives as `http://`, but x402 discovery: and the mppx client's + * resource-match check: require the public `https://`. Honor `X-Forwarded-Proto` * (the scheme the client actually used) so the emitted `resource.url` matches * the URL the client fetched. * diff --git a/src/identity/a2a.ts b/src/identity/a2a.ts index ba90cef..7f6734e 100644 --- a/src/identity/a2a.ts +++ b/src/identity/a2a.ts @@ -1,12 +1,12 @@ /** - * Google A2A (Agent-to-Agent) v1.0 Agent Card builder. + * A2A (Agent-to-Agent) v1.0 Agent Card builder. * * Compose the JSON payload for an A2A v1.0 Agent Card matching the canonical - * `AgentCard` type from `@a2a-js/sdk`. Returned object is the unsigned card body — - * wrap with an `A2AAgentCardSignature` (RFC 7515 JWS) to sign vendor-side before - * publishing at /.well-known/agent-card.json. + * `AgentCard` type from `@a2a-js/sdk`. Returned object is the unsigned card body; to sign + * it vendor-side, attach `A2AAgentCardSignature` entries (RFC 7515 JWS) as `signatures` + * before publishing at /.well-known/agent-card.json. * - * Why publish: A2A is a Linux Foundation standard. Signed Agent Cards let any + * Why publish: A2A is a Linux Foundation standard. Agent Cards let any * A2A-compatible reader discover an agent's capabilities + protocol bindings without * per-platform integration. Per UCP §A2A binding, agents serving UCP via the A2A * transport MUST declare the canonical UCP extension URI in `capabilities.extensions[]` @@ -21,20 +21,19 @@ const DEFAULT_TRANSPORT = 'JSONRPC'; const DEFAULT_INPUT_MODE = 'application/json'; const DEFAULT_OUTPUT_MODE = 'application/json'; -/** Canonical UCP A2A extension URI — verifiers look for this exact URI in +/** Canonical UCP A2A extension URI: verifiers look for this exact URI in * `capabilities.extensions[]` to detect UCP support on the agent card. Pinned * to the 2026-08-25 spec snapshot. */ export const UCP_A2A_EXTENSION_URI = 'https://ucp.dev/2026-08-25/specification/reference'; -/** Canonical URI for the AIP (Agentic Identity Protocol) A2A agent-card extension — points at the +/** Canonical URI for the AIP (Agentic Identity Protocol) A2A agent-card extension: points at the * issuer-discovery well-known so a reader can resolve the protocol. */ export const AIP_A2A_EXTENSION_URI = 'https://www.agentscore.com/.well-known/agent-identity'; -/** One transport+URL combination the agent exposes. Lives in `AgentCard.additionalInterfaces[]` - * for multi-binding agents; the PRIMARY transport+URL pair lives on `AgentCard.url` + - * `AgentCard.preferredTransport`. */ +/** One transport+URL combination beyond the primary `url`, passed as `additionalInterfaces`; + * each becomes a `supportedInterfaces` entry after the primary one. */ export interface A2AAgentInterface { - /** Open string — core values are `JSONRPC`, `GRPC`, `HTTP+JSON`. */ + /** Open string: core values are `JSONRPC`, `GRPC`, `HTTP+JSON`. */ transport: string; /** Absolute URL where this transport is available. */ url: string; @@ -98,7 +97,7 @@ export function ucpA2AExtension( /** Build the AIP entry for an A2A agent card's `capabilities.extensions[]`. Advertises that the * agent accepts an Agent Identity Token (JWT) in an `Agent-Identity` header plus an RFC 9421 - * proof-of-possession signature — so an agent discovering via the agent-card learns it can present + * proof-of-possession signature: so an agent discovering via the agent-card learns it can present * an AIT (matching what the merchant's mpp.json / llms.txt / skill.md already advertise). Optional * `requiredTrustLevel` / `requiredAmr` surface a gate's human-presence requirement. */ export function aipA2AExtension( @@ -202,7 +201,7 @@ interface BuildA2AAgentCardInput { /** Primary endpoint URL, the first entry of `supportedInterfaces`, speaking * `preferredTransport` (default `HTTP+JSON`). */ url: string; - /** Top-level skill declarations — what the agent can do. REQUIRED per spec + /** Top-level skill declarations: what the agent can do. REQUIRED per spec * (proto field 12 [field_behavior=REQUIRED]); must have ≥1 entry. */ skills: A2AAgentSkill[]; /** Agent's own version, e.g. `"1.0.0"`. Distinct from the A2A `protocolVersion`. */ @@ -315,7 +314,7 @@ export function buildA2AAgentCard(input: BuildA2AAgentCardInput): A2AAgentCard { return card; } -// Make the convention explicit at the type level — `DEFAULT_TRANSPORT` is referenced for -// the canonical-default helper consumers may want to introspect. +// A2A_DEFAULT_TRANSPORT is the protocol's own default binding (JSONRPC). buildA2AAgentCard +// defaults to HTTP+JSON instead, since our merchants serve REST-shaped endpoints. export const A2A_PROTOCOL_VERSION = PROTOCOL_VERSION; export const A2A_DEFAULT_TRANSPORT = DEFAULT_TRANSPORT; diff --git a/src/identity/default_denied.ts b/src/identity/default_denied.ts index 45220ac..ca1f765 100644 --- a/src/identity/default_denied.ts +++ b/src/identity/default_denied.ts @@ -2,7 +2,7 @@ * gate config. Replaces the denial-mapping switch a merchant would otherwise * hand-write. * - * The shape is framework-neutral (`{status, body, headers?}`) — matches + * The shape is framework-neutral (`{status, body, headers?}`): matches * `Checkout`'s `onDenied` signature directly. For per-framework gate * middleware (`agentscoreGate(...)`) the merchant adapts at the call site * with `c.json(body, status, headers)` / equivalent. @@ -37,7 +37,7 @@ export interface DefaultOnDeniedResult { /** Build the canonical `onDenied(ctx, reason)` callback. Returns * framework-neutral `{status, body, headers?}` matching `Checkout`'s - * `onDenied` signature. The `ctx` arg is ignored — pass `_ctx`-style if + * `onDenied` signature. The `ctx` arg is ignored: pass `_ctx`-style if * unused. * * Branch table (matches the hand-rolled version in every consumer): diff --git a/src/identity/express.ts b/src/identity/express.ts index d2ac54b..f6c7850 100644 --- a/src/identity/express.ts +++ b/src/identity/express.ts @@ -27,9 +27,9 @@ interface GateState { operatorToken?: string; walletAddress?: string; /** Set to `true` only when the gate fail-open'd due to AgentScore-side infra failure - * (429/5xx/network timeout). Compliance was NOT enforced — log/alert in your handler. */ + * (429/5xx/network timeout). Compliance was NOT enforced: log/alert in your handler. */ degraded?: boolean; - /** Why the gate degraded — quota_exceeded / api_error / network_timeout. */ + /** Why the gate degraded: quota_exceeded / api_error / network_timeout. */ infraReason?: FailOpenInfraReason; /** Per-account assess quota observability captured from `X-Quota-*` response headers * on the success path. Absent on Enterprise / unlimited tiers, or when the gate didn't @@ -130,7 +130,7 @@ export function agentscoreGate(options: AgentScoreGateOptions) { /** * Read whether the gate fail-open'd due to AgentScore-side infrastructure failure on * this request. Returns `{ degraded: false }` for normal allows; `{ degraded: true, - * infraReason }` when bypassed (compliance NOT enforced — log/alert). + * infraReason }` when bypassed (compliance NOT enforced: log/alert). */ export function getGateDegradedState( req: Request, @@ -180,11 +180,11 @@ export async function captureWallet( /** * Synchronous read of the cached signer verdicts (`signer_match` + `signer_sanctions`). - * Both verdicts were composed by the gate's primary `/v1/assess` call on this request — + * Both verdicts were composed by the gate's primary `/v1/assess` call on this request: * single round trip, no extra API cost vs the legacy 2-call pattern. Returns `undefined` * for operator-token paths, discovery legs, or routes the gate didn't run on. * - * Wallet-OFAC SDN enforcement is unconditional whenever a signer is in the request — an + * Wallet-OFAC SDN enforcement is unconditional whenever a signer is in the request: an * SDN hit (or unavailable lookup) is already enforced by the gate (decision → deny before * the handler runs); merchant code typically only needs to consume this getter for the * `signer_match` wallet-binding verdict. @@ -213,7 +213,7 @@ export function conditionalAgentscoreGate(options: AgentScoreGateOptions) { } // --------------------------------------------------------------------------- -// AIP gate (Agentic Identity Protocol) — verifies a key-bound Agent Identity Token (AIT) +// AIP gate (Agentic Identity Protocol): verifies a key-bound Agent Identity Token (AIT) // from a trusted IdP instead of an opaque operator token. Cryptographic identity only; // merchants who want compliance enrichment feed the verified claims to /v1/assess. // --------------------------------------------------------------------------- diff --git a/src/identity/fastify.ts b/src/identity/fastify.ts index 9e6111c..4c5a205 100644 --- a/src/identity/fastify.ts +++ b/src/identity/fastify.ts @@ -27,9 +27,9 @@ interface GateState { operatorToken?: string; walletAddress?: string; /** Set to `true` only when the gate fail-open'd due to AgentScore-side infra failure - * (429/5xx/network timeout). Compliance was NOT enforced — log/alert in your handler. */ + * (429/5xx/network timeout). Compliance was NOT enforced: log/alert in your handler. */ degraded?: boolean; - /** Why the gate degraded — quota_exceeded / api_error / network_timeout. */ + /** Why the gate degraded: quota_exceeded / api_error / network_timeout. */ infraReason?: FailOpenInfraReason; /** Per-account assess quota observability captured from `X-Quota-*` response headers. */ quota?: GateQuotaInfo; @@ -149,7 +149,7 @@ export function getAgentScoreData(request: FastifyRequest): AssessResult | undef /** * Read whether the gate fail-open'd due to AgentScore-side infrastructure failure on * this request. Returns `{ degraded: false }` for normal allows; `{ degraded: true, - * infraReason }` when bypassed (compliance NOT enforced — log/alert). + * infraReason }` when bypassed (compliance NOT enforced: log/alert). */ export function getGateDegradedState( request: FastifyRequest, @@ -192,7 +192,7 @@ export async function captureWallet( * Both composed by the gate's primary /v1/assess in one round trip. Returns `undefined` * for operator-token paths, discovery legs, or routes the gate didn't run on. * - * Wallet-OFAC SDN enforcement is unconditional whenever a signer is in the request — an + * Wallet-OFAC SDN enforcement is unconditional whenever a signer is in the request: an * SDN hit (or unavailable lookup) is already enforced by the gate (decision → deny before * the handler runs); merchant code typically only needs this getter for the `signer_match` * wallet-binding verdict. @@ -262,7 +262,7 @@ const conditionalAgentscoreGatePlugin: FastifyPluginAsync export const conditionalAgentscoreGate = conditionalAgentscoreGatePlugin; // --------------------------------------------------------------------------- -// AIP gate (Agentic Identity Protocol) — verifies a key-bound Agent Identity Token (AIT) +// AIP gate (Agentic Identity Protocol): verifies a key-bound Agent Identity Token (AIT) // from a trusted IdP. Cryptographic identity only; enrich via /v1/assess from the handler. // --------------------------------------------------------------------------- @@ -306,7 +306,7 @@ const aipGatePlugin: FastifyPluginAsync = async (fastify, (aipGatePlugin as unknown as Record)[Symbol.for('skip-override')] = true; export const aipGate = aipGatePlugin; -/** Conditional AIP plugin — only verifies when an `Agent-Identity` header is present. */ +/** Conditional AIP plugin: only verifies when an `Agent-Identity` header is present. */ const conditionalAipGatePlugin: FastifyPluginAsync = async (fastify, options) => { const { onDenied = defaultAipOnDenied, ...gateOpts } = options; fastify.addHook('preHandler', async (request, reply) => { diff --git a/src/identity/hono.ts b/src/identity/hono.ts index b2cff55..2d0cba4 100644 --- a/src/identity/hono.ts +++ b/src/identity/hono.ts @@ -28,10 +28,10 @@ interface GateState { operatorToken?: string; walletAddress?: string; /** Set to `true` only when the gate fail-open'd due to AgentScore-side infra failure - * (429/5xx/network timeout). Compliance was NOT enforced for this request — log/alert + * (429/5xx/network timeout). Compliance was NOT enforced for this request: log/alert * in your handler. See {@link getGateDegradedState}. */ degraded?: boolean; - /** Why the gate degraded — quota_exceeded / api_error / network_timeout. */ + /** Why the gate degraded: quota_exceeded / api_error / network_timeout. */ infraReason?: FailOpenInfraReason; /** Per-account assess quota observability captured from `X-Quota-*` response headers * on the success path. Absent on Enterprise / unlimited tiers, or when the gate didn't @@ -146,7 +146,7 @@ export function getAgentScoreData(c: Context): AssessResult | undefined { /** * Read whether the gate fail-open'd due to AgentScore-side infrastructure failure on * this request. Returns `{ degraded: false }` for normal allows; `{ degraded: true, - * infraReason }` when the gate was bypassed (compliance NOT enforced — log/alert). + * infraReason }` when the gate was bypassed (compliance NOT enforced: log/alert). * * Only set when `failOpen: true` was configured AND the failure was an infra failure * (429 quota_exceeded, 5xx api_error, network_timeout). Real compliance denials never @@ -172,7 +172,7 @@ export function getGateQuotaInfo(c: Context): GateQuotaInfo | undefined { * Report a wallet that paid under the operator_token the gate extracted on this request. * Call this after a successful payment to build AgentScore's cross-merchant credential↔wallet * profile. No-ops silently if the gate never ran, the request was wallet-authenticated (no - * operator_token to associate), or the API call fails — capture is fire-and-forget by design. + * operator_token to associate), or the API call fails: capture is fire-and-forget by design. * * ```ts * app.post('/purchase', async (c) => { @@ -200,12 +200,12 @@ export async function captureWallet( /** * Synchronous read of the cached signer verdicts (`signer_match` wallet-binding + * `signer_sanctions` OFAC SDN wallet-address check). Both verdicts were composed by the - * gate's primary `/v1/assess` call on this request — single round trip, no extra API call. + * gate's primary `/v1/assess` call on this request: single round trip, no extra API call. * * Returns `undefined` when the gate didn't run, the request was operator-token-only, or * no payment credential was attached (discovery legs). * - * Wallet-OFAC SDN enforcement is unconditional whenever a signer is in the request — an + * Wallet-OFAC SDN enforcement is unconditional whenever a signer is in the request: an * SDN hit (or unavailable lookup) is already enforced by the gate (decision → deny before * the handler runs); merchant code typically only needs this getter for the `signer_match` * wallet-binding verdict. @@ -239,7 +239,7 @@ export function conditionalAgentscoreGate(options: AgentScoreGateOptions): Middl } // --------------------------------------------------------------------------- -// AIP gate (Agentic Identity Protocol) — verifies a key-bound Agent Identity Token (AIT) +// AIP gate (Agentic Identity Protocol): verifies a key-bound Agent Identity Token (AIT) // from a trusted IdP instead of an opaque operator token. Cryptographic identity only; // merchants who want compliance enrichment feed the verified claims to /v1/assess. Hono is // Fetch-native, so this reuses `verifyAitRequest(c.req.raw)` directly while keeping the same diff --git a/src/identity/nextjs.ts b/src/identity/nextjs.ts index 77e05c8..99e030b 100644 --- a/src/identity/nextjs.ts +++ b/src/identity/nextjs.ts @@ -47,9 +47,9 @@ export function withAgentScoreGate | null | undefin * * The vendor passes: * - `gate`: their framework's middleware (Hono `MiddlewareHandler`, Express - * `(req, res, next) => void`, etc.) — anything that resolves on accept and + * `(req, res, next) => void`, etc.): anything that resolves on accept and * throws or returns a `Response` on deny. * - `runGate`: a thin adapter that calls the middleware with the framework * context and returns either `{ ok: true }` (gate accepted) or @@ -176,7 +176,7 @@ export function isSanctionsDenial(body: Record | null | undefin * - `enforcement="soft"` + denied: swallow; status="unverified". * - accepted: status="verified". * - * **Sanctions are never swallowed.** Soft mode is a commercial knob — it lets a merchant + * **Sanctions are never swallowed.** Soft mode is a commercial knob: it lets a merchant * accept an order from an agent that didn't satisfy KYC / age / jurisdiction (stamping a * degraded `identity_status` for ops). But an OFAC SDN sanctions deny is strict-liability: * settling for a sanctioned wallet is a violation regardless of the merchant's posture. So a @@ -192,7 +192,7 @@ export async function runGateWithEnforcement( const outcome = await runGate(); if (outcome.ok) return { status: 'verified' }; - // A sanctions deny stays terminal in BOTH modes — soft only downgrades non-sanctions reasons. + // A sanctions deny stays terminal in BOTH modes: soft only downgrades non-sanctions reasons. if (enforcement === 'hard' || isSanctionsDenial(outcome.body)) { return { status: 'denied', @@ -219,7 +219,7 @@ export function shippingCountryAllowed(country: string, policy: PolicyBlock | nu /** * US-state allowlist (e.g. wine). * - * Only enforced for US shipments — non-US shipments are governed by + * Only enforced for US shipments: non-US shipments are governed by * {@link shippingCountryAllowed} independently. */ export function shippingStateAllowed( @@ -247,7 +247,7 @@ export function shippingStateAllowed( * "ship anywhere" and the function is a no-op. The reason a location is * excluded is **merchant-defined**: it might be regulatory (regulated goods * + state allowlist), operational (no fulfillment partner), or commercial - * (fragility, fraud-rate-by-region, etc.) — the helper doesn't assume. + * (fragility, fraud-rate-by-region, etc.): the helper doesn't assume. * * `productName` is the user-facing item name surfaced in the error message * ("Cannot ship 'Wine 2020' to NY ..."). Omit for a generic message. @@ -257,7 +257,7 @@ export function shippingStateAllowed( * * `countryMessage` / `stateMessage` override the default messages verbatim * (use these when the default phrasing isn't right for your consumer agents - * — e.g. you want to surface the regulatory reason explicitly, or you want + * e.g. you want to surface the regulatory reason explicitly, or you want * the message in a different language). */ export function validateShippingAgainstPolicy(opts: { diff --git a/src/identity/ucp-jwks.ts b/src/identity/ucp-jwks.ts index 43e0f19..2861af4 100644 --- a/src/identity/ucp-jwks.ts +++ b/src/identity/ucp-jwks.ts @@ -7,17 +7,17 @@ * trust-mode clients reject the profile. * * This module provides: - * - `generateUCPSigningKey()` — generate an Ed25519 keypair for signing - * - `signUCPProfile()` — sign a UCP profile body, returning a JWS-attached envelope - * - `verifyUCPProfile()` — verify a signed profile against a JWKS - * - `buildJWKSResponse()` — assemble a JWKS document for `/.well-known/jwks.json` + * - `generateUCPSigningKey()`: generate an Ed25519 keypair for signing + * - `signUCPProfile()`: sign a UCP profile body, returning a JWS-attached envelope + * - `verifyUCPProfile()`: verify a signed profile against a JWKS + * - `buildJWKSResponse()`: assemble a JWKS document for `/.well-known/jwks.json` * * Implementation rides on `jose` (peer-dep, optional). Merchants who don't sign their * profile (development) skip this module entirely; the unsigned `buildUCPProfile()` * path still works. * * Why Ed25519: smaller signatures (64 bytes vs 256+ for RSA), faster verification, no - * curve-parameter ceremony. UCP also accepts ES256 (P-256 ECDSA) — pass `alg: 'ES256'` + * curve-parameter ceremony. UCP also accepts ES256 (P-256 ECDSA): pass `alg: 'ES256'` * to `signUCPProfile()` if your existing payment signing key is P-256. */ @@ -28,13 +28,13 @@ import type { UCPProfile, UCPSigningKey } from './ucp'; * UCP profile's `keys[]`. */ export interface GeneratedUCPKey { - /** Private key (KeyLike, opaque) — pass to `signUCPProfile()`. Never publish. */ + /** Private key (KeyLike, opaque): pass to `signUCPProfile()`. Never publish. */ privateKey: unknown; /** Public key as JWK: publish at `/.well-known/jwks.json` and inline in UCP `keys[]`. */ publicJWK: UCPSigningKey; } -/** A JWKS document — `{ keys: [...] }` per RFC 7517. Serve at `/.well-known/jwks.json`. */ +/** A JWKS document: `{ keys: [...] }` per RFC 7517. Serve at `/.well-known/jwks.json`. */ export interface JWKSResponse { keys: UCPSigningKey[]; } @@ -51,7 +51,7 @@ export interface SignedUCPProfile extends UCPProfile { const JOSE_INSTALL_HINT = 'Install the optional peer dependency: `npm install jose@^6` (or `bun add jose`). Tested against jose v6.x.'; -/** UCP §6 + RFC 8725 §3.1 — restrict accepted JWS algorithms. Anything outside this +/** UCP §6 + RFC 8725 §3.1: restrict accepted JWS algorithms. Anything outside this * list (HS, RS, none, etc.) is rejected to prevent alg-confusion attacks where a * hostile JWK published in the profile's keys[] is used with an unintended * algorithm. */ @@ -105,7 +105,7 @@ async function loadJose(): Promise { * * Implementation note: UCP §6.2 specifies "the JSON-serialized profile body, with * `signature` removed and keys ordered lexicographically at every nesting level." This - * is JCS-style canonicalization without the full RFC 8785 numeric handling — UCP + * is JCS-style canonicalization without the full RFC 8785 numeric handling: UCP * profiles don't contain floats so the simpler key-sort is sufficient. */ function canonicalizeProfile(profile: UCPProfile): string { @@ -208,7 +208,7 @@ function stableStringify(value: unknown): string { /** * Generate a fresh Ed25519 (default) or ES256 keypair for signing UCP profiles. * - * The `privateKey` is an opaque KeyLike — store it server-side and pass to + * The `privateKey` is an opaque KeyLike: store it server-side and pass to * `signUCPProfile()`. Never log or transmit the private key. * * The `publicJWK` is what you publish at `/.well-known/jwks.json` and inline in the @@ -252,7 +252,7 @@ export async function generateUCPSigningKey(opts: { * canonical body, look up the key referenced by the JWS header's `kid`, and validate. * * The profile's `keys[]` MUST already include a JWK with the matching `kid` - * — otherwise verifiers can't find the public key. Add the `publicJWK` from + * otherwise verifiers can't find the public key. Add the `publicJWK` from * `generateUCPSigningKey()` to your `keys[]` before calling this. * * Example: @@ -269,11 +269,11 @@ export async function signUCPProfile( kid, alg = 'EdDSA', }: { - /** Private signing key — opaque KeyLike from `generateUCPSigningKey()` or `importJWK()`. */ + /** Private signing key: opaque KeyLike from `generateUCPSigningKey()` or `importJWK()`. */ signingKey: unknown; /** Key ID (must match a `kid` in the profile's `keys[]`). */ kid: string; - /** Signing algorithm — `EdDSA` (default) or `ES256`. */ + /** Signing algorithm: `EdDSA` (default) or `ES256`. */ alg?: 'EdDSA' | 'ES256'; }, ): Promise { @@ -398,7 +398,7 @@ export async function verifyUCPProfile( // Shape-check first so that // explicit `crit: null` / `crit: []` / `crit: "foo"` / `crit: [42]` aren't // silently accepted; only well-formed crit arrays fall through to the - // unrecognized-extension check (RFC 8725 §3.10 — UCP defines no crit headers). + // unrecognized-extension check (RFC 8725 §3.10: UCP defines no crit headers). if ('crit' in header) { const crit = (header as { crit?: unknown }).crit; if (!Array.isArray(crit) || crit.length === 0 || !crit.every((c) => typeof c === 'string')) { @@ -554,7 +554,7 @@ function detectAlgFromJwk(jwk: Record): 'EdDSA' | 'ES256' | nul } // Cache entries are keyed by the full resolved opts (so different opts get separate -// entries) and store the IN-FLIGHT Promise — concurrent first-callers with the same +// entries) and store the IN-FLIGHT Promise: concurrent first-callers with the same // opts await the same key generation rather than racing to produce different ephemeral // keys (a losing keypair signing a JWS that the published JWKS then rejects). const envLoaderCache = new Map>(); @@ -629,10 +629,10 @@ async function buildEnvSigningKey( ); } - // Derive a canonical public JWK from the public node key — drops `d` and any other + // Derive a canonical public JWK from the public node key: drops `d` and any other // private-only fields (and unknown env JWK fields like key_ops, x5c, x5t). const publicJWK = publicNodeKey.export({ format: 'jwk' }) as unknown as UCPSigningKey; - // Empty-string kid in env JWK falls through to the configured default — + // Empty-string kid in env JWK falls through to the configured default: // publishing `"kid": ""` breaks every kid-pinning verifier. publicJWK.kid = (jwkDict.kid as string | undefined) || kidDefault; publicJWK.alg = detectedAlg; @@ -641,7 +641,7 @@ async function buildEnvSigningKey( return { privateKey, publicJWK }; } - // Ephemeral fallback — generate a fresh keypair. + // Ephemeral fallback: generate a fresh keypair. return generateUCPSigningKey({ kid: kidDefault, alg: algFallback }); } @@ -701,7 +701,7 @@ export async function loadUCPSigningKeyFromEnv({ // Pin the in-flight Promise so concurrent first-callers await the same generation. cached = buildEnvSigningKey(resolved).catch((err) => { // Clear on rejection so a transient malformed env doesn't permanently poison - // every future call — the next caller retries the build. + // every future call: the next caller retries the build. envLoaderCache.delete(key); throw err; }); diff --git a/src/identity/ucp.ts b/src/identity/ucp.ts index 4899c01..c306a17 100644 --- a/src/identity/ucp.ts +++ b/src/identity/ucp.ts @@ -53,9 +53,9 @@ import { export interface UCPSigningKey { /** JWK kid (key id). */ kid: string; - /** JWK kty (key type) — `EC`, `RSA`, or `OKP`. */ + /** JWK kty (key type): `EC`, `RSA`, or `OKP`. */ kty: string; - /** JWK alg (signing algorithm) — `ES256`, `RS256`, or `EdDSA`. */ + /** JWK alg (signing algorithm): `ES256`, `RS256`, or `EdDSA`. */ alg?: string; /** JWK use, typically `sig`. */ use?: string; @@ -98,17 +98,17 @@ export const UCPSigningKey = { fromJWK: ucpSigningKeyFromJWKImpl, }; -/** Transport binding — keyed under a service name (e.g., `dev.ucp.shopping`). */ +/** Transport binding: keyed under a service name (e.g., `dev.ucp.shopping`). */ export interface UCPServiceBinding { /** Spec version, YYYY-MM-DD per UCP convention. REQUIRED. */ version: string; /** URL to human-readable specification. REQUIRED. */ spec: string; - /** Transport — `rest` / `mcp` / `a2a` / `embedded`. REQUIRED. */ + /** Transport: `rest` / `mcp` / `a2a` / `embedded`. REQUIRED. */ transport: 'rest' | 'mcp' | 'a2a' | 'embedded'; - /** Endpoint URL — required for rest/mcp; A2A points at the agent-card.json URL. */ + /** Endpoint URL: required for rest/mcp; A2A points at the agent-card.json URL. */ endpoint?: string; - /** URL to JSON Schema — required for rest/mcp/embedded per spec. */ + /** URL to JSON Schema: required for rest/mcp/embedded per spec. */ schema?: string; /** Optional id for entity-instance disambiguation. */ id?: string; @@ -118,7 +118,7 @@ export interface UCPServiceBinding { [k: string]: unknown; } -/** Capability binding — keyed under a capability name (e.g., `dev.ucp.shopping.checkout`). */ +/** Capability binding: keyed under a capability name (e.g., `dev.ucp.shopping.checkout`). */ export interface UCPCapabilityBinding { /** Capability version, YYYY-MM-DD. REQUIRED. */ version: string; @@ -130,7 +130,7 @@ export interface UCPCapabilityBinding { id?: string; /** Entity-specific config (feature flags, callback URLs, etc). */ config?: Record; - /** Parent capability(ies) extended — single string or array for multi-parent. */ + /** Parent capability(ies) extended: single string or array for multi-parent. */ extends?: string | string[]; /** Optional version requirements per UCP §6.5. */ requires?: { @@ -142,7 +142,7 @@ export interface UCPCapabilityBinding { [k: string]: unknown; } -/** Payment handler binding — keyed under a handler reverse-DNS name (e.g., `com.google.pay`). */ +/** Payment handler binding: keyed under a handler reverse-DNS name (e.g., `com.google.pay`). */ export interface UCPPaymentHandlerBinding { /** Handler instance id (short, human-readable, e.g., `gpay`, `tempo`, `x402`). REQUIRED. */ id: string; @@ -152,27 +152,27 @@ export interface UCPPaymentHandlerBinding { spec: string; /** URL to handler config schema. REQUIRED. */ schema: string; - /** Available instruments — type + per-type constraints (cards, wallets, etc.). */ + /** Available instruments: type + per-type constraints (cards, wallets, etc.). */ available_instruments?: Array<{ type: string; constraints?: Record; [k: string]: unknown }>; - /** Handler config — gateway IDs, merchant IDs, public keys, etc. */ + /** Handler config: gateway IDs, merchant IDs, public keys, etc. */ config?: Record; /** Vendor-specific extras. */ [k: string]: unknown; } -/** UCP body — nested under the `ucp` key of the published profile. */ +/** UCP body: nested under the `ucp` key of the published profile. */ export interface UCPProfileBody { /** UCP spec version (YYYY-MM-DD). */ version: string; /** Display name for the merchant / agent surface. */ name?: string; - /** Services — keyed by service name (e.g., `dev.ucp.shopping`). Each value is an + /** Services: keyed by service name (e.g., `dev.ucp.shopping`). Each value is an * array of transport bindings (one merchant typically advertises multiple transports * under one service name). */ services: Record; - /** Capabilities — keyed by capability name (e.g., `dev.ucp.shopping.checkout`). */ + /** Capabilities: keyed by capability name (e.g., `dev.ucp.shopping.checkout`). */ capabilities: Record; - /** Payment handlers — keyed by handler reverse-DNS name (e.g., `com.google.pay`). */ + /** Payment handlers: keyed by handler reverse-DNS name (e.g., `com.google.pay`). */ payment_handlers: Record; /** Optional `supported_versions` map linking historical version-specific profile URLs. * Pattern: `{ "2026-01-23": "https://merchant/.well-known/ucp/2026-01-23", ... }`. */ @@ -190,7 +190,7 @@ export interface UCPProfile { * `keys` the one canonical field and removed `signing_keys`. Verifiers fetch this profile, * match the kid from a JWS / RFC 9421 signature header against this list, and validate. */ keys: UCPSigningKey[]; - /** Set when JWS-signed via `signUCPProfile` — JWS Compact Serialization with detached + /** Set when JWS-signed via `signUCPProfile`: JWS Compact Serialization with detached * payload (header..signature; payload is the canonicalized body minus this field). */ signature?: string; /** Top-level vendor-specific extras (outside the `ucp` envelope). */ @@ -217,7 +217,7 @@ interface BuildUCPProfileInput { signing_keys?: UCPSigningKey[]; /** Merchant gate policy declaration. When provided, the SDK auto-injects an * `com.agentscore.identity` capability binding into `capabilities`, with the - * policy as the binding's `config`. Static merchant declaration only — no + * policy as the binding's `config`. Static merchant declaration only: no * per-operator data ever ends up on the public profile. Per-operator identity * attestation lives on the AP2 risk-signal endpoint, not here. */ agentscore_gate?: AgentScoreGatePolicy; @@ -245,7 +245,7 @@ const AGENTSCORE_CAPABILITY_VERSION = '2026-04-08'; /** Merchant gate policy declared on the UCP profile via `com.agentscore.identity` capability config. * All fields optional; merchant declares which AgentScore checks the gate enforces. Snake-case - * field names match the AgentScore API's `/v1/assess` policy contract verbatim — no conversion + * field names match the AgentScore API's `/v1/assess` policy contract verbatim: no conversion * layer between this declaration and what the gate actually enforces at runtime. */ export interface AgentScoreGatePolicy { /** Gate denies if the operator/account behind the agent is not Stripe-Identity-verified. */ @@ -263,7 +263,7 @@ export interface AgentScoreGatePolicy { } const AGENTSCORE_DEFAULT_SPEC_URL = 'https://www.agentscore.com/specification/identity'; const AGENTSCORE_DEFAULT_SCHEMA_URL = 'https://www.agentscore.com/schemas/ucp/com-agentscore-identity-v1.json'; -// Multi-parent extension — `com.agentscore.identity` declares merchant policy relevant at +// Multi-parent extension: `com.agentscore.identity` declares merchant policy relevant at // both checkout-build (compliance gate) and cart-build (price-gate eligibility, jurisdiction- // restricted items in cart) time, so an agent reading either parent capability picks up the // policy contract. Mirrors the multi-parent convention in the live ecosystem @@ -303,7 +303,7 @@ const RESERVED_UCP_FIELDS = new Set([ * is provided. The capability's `config` carries the merchant's static gate * policy declaration (require_kyc / require_sanctions_clear / min_age / * allowed_jurisdictions / blocked_jurisdictions). NO per-operator data is ever - * placed on the public profile — per-operator identity attestation flows through + * placed on the public profile: per-operator identity attestation flows through * the AP2 risk-signal endpoint, not here. * * Example: @@ -366,7 +366,7 @@ export function buildUCPProfile(input: BuildUCPProfileInput): UCPProfile { } // Auto-inject `com.agentscore.identity` capability when the merchant declares a gate - // policy. Static merchant-policy declaration only — no per-operator data on the public + // policy. Static merchant-policy declaration only: no per-operator data on the public // profile. Per-operator identity attestation flows through the AP2 risk-signal endpoint // or per-request 4xx response bodies, not here. if (input.agentscore_gate) { @@ -427,7 +427,7 @@ const SCHEMA_BASE = 'https://www.agentscore.com/schemas/payment-handlers'; // CAIP-2 → UCP-namespace network-name mapping. UCP payment_handler bindings publish // network strings in the UCP namespace (`base-8453`, `solana-mainnet-beta`); RailSpecs // carry the CAIP-2 form (`eip155:8453`, `solana:5eykt4...`). Unknown values pass -// through verbatim — vendors who pin a non-standard rail can override the spec's +// through verbatim: vendors who pin a non-standard rail can override the spec's // network field directly. const CAIP2_TO_UCP_NETWORK: Record = { 'eip155:8453': 'base-8453', @@ -496,7 +496,7 @@ function mppRailToNetworkEntry(spec: MppRailSpec): Record { return solanaMppToNetworkEntry(spec as SolanaMppRailSpec); } if (isTempoRailSpec(spec)) return tempoToNetworkEntry(spec); - // Default: treat as TempoRailSpec — covers the common case where the caller passes + // Default: treat as TempoRailSpec: covers the common case where the caller passes // a bare `{recipient}` with no network/chain_id override. return tempoToNetworkEntry(spec as TempoRailSpec); } diff --git a/src/identity/web.ts b/src/identity/web.ts index 386e088..f623dd4 100644 --- a/src/identity/web.ts +++ b/src/identity/web.ts @@ -59,9 +59,9 @@ export type GuardResult = * or AIT paths, or when the API has no handle salt configured. */ operatorHandle?: OperatorHandle; /** Set to `true` only when the gate fail-open'd due to AgentScore-side infra failure - * (429/5xx/network timeout). Compliance was NOT enforced this request — log/alert. */ + * (429/5xx/network timeout). Compliance was NOT enforced this request: log/alert. */ degraded?: boolean; - /** Why the gate degraded — quota_exceeded / api_error / network_timeout. */ + /** Why the gate degraded: quota_exceeded / api_error / network_timeout. */ infraReason?: FailOpenInfraReason; /** Per-account assess quota observability from X-Quota-* response headers. */ quota?: GateQuotaInfo; @@ -109,7 +109,7 @@ export function createAgentScoreGate(options: AgentScoreGateOptions): (req: Requ const identity = extractIdentity(req); // Extract the payment signer pre-evaluate. When present, the API composes // signer_match + signer_sanctions verdicts on the primary assess response in one - // round trip. Wallet-OFAC enforcement is unconditional — SDN wallet hits flip + // round trip. Wallet-OFAC enforcement is unconditional: SDN wallet hits flip // decision -> deny inline before the handler runs, regardless of policy flags. const signer = await extractPaymentSigner(req, readX402PaymentHeader(req)); const outcome = await core.evaluate(identity, req, signer); @@ -119,7 +119,7 @@ export function createAgentScoreGate(options: AgentScoreGateOptions): (req: Requ ? (opts: { walletAddress: string; network: 'evm' | 'solana'; idempotencyKey?: string }) => core.captureWallet({ operatorToken: identity.operatorToken!, ...opts }) : undefined; - // Synchronous getter — returns THIS request's signer verdicts (signer_match + + // Synchronous getter: returns THIS request's signer verdicts (signer_match + // signer_sanctions), captured on the per-request `outcome` (NOT a shared core slot) so // concurrent same-wallet/different-signer requests can't read each other's verdict. Bound // on strict wallet-auth requests (the getter itself returns `undefined` when no signer was @@ -177,9 +177,9 @@ export function withAgentScoreGate( * or AIT paths, or when the API has no handle salt configured. */ operatorHandle?: OperatorHandle; /** Set to `true` only when the gate fail-open'd due to AgentScore-side infra failure - * (429/5xx/network timeout). Compliance was NOT enforced this request — log/alert. */ + * (429/5xx/network timeout). Compliance was NOT enforced this request: log/alert. */ degraded?: boolean; - /** Why the gate degraded — quota_exceeded / api_error / network_timeout. */ + /** Why the gate degraded: quota_exceeded / api_error / network_timeout. */ infraReason?: FailOpenInfraReason; /** Per-account assess quota observability from X-Quota-* response headers. */ quota?: GateQuotaInfo; @@ -233,11 +233,11 @@ export function withConditionalAgentScoreGate( } // --------------------------------------------------------------------------- -// AIP gate (Agentic Identity Protocol) — Web Fetch +// AIP gate (Agentic Identity Protocol): Web Fetch // // `createAipGate` verifies a key-bound Agent Identity Token (AIT) from a trusted IdP and // returns a guard result; `withAipGate` wraps a handler. Fetch-native, so it reuses -// `verifyAitRequest` directly. Identity verification only — merchants enrich via /v1/assess. +// `verifyAitRequest` directly. Identity verification only: merchants enrich via /v1/assess. // --------------------------------------------------------------------------- export type AipGuardResult = diff --git a/src/index.ts b/src/index.ts index 53debc1..596ae1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -176,7 +176,7 @@ export { type MalformedPaymentCredential, } from './payment/payment_header'; export { buildIdentityBootstrap, type IdentityBootstrapBlock } from './challenge/identity'; -// AIP (Agentic Identity Protocol) — AIT verification (verifier role) + RFC 9421 signing. +// AIP (Agentic Identity Protocol): AIT verification (verifier role) + RFC 9421 signing. export { AGENT_IDENTITY_HEADER, verifyAit, diff --git a/src/payment/amounts.ts b/src/payment/amounts.ts index 549f03e..0cef80e 100644 --- a/src/payment/amounts.ts +++ b/src/payment/amounts.ts @@ -89,7 +89,7 @@ export function usdToAtomic(usd: string | number, opts: { decimals: number }): b * prevents agent-side string-comparison flakiness. * * `decimals` controls the dollar-precision of the output and defaults to `2` - * (canonical USD cents). Raise it for sub-cent unit pricing — e.g. + * (canonical USD cents). Raise it for sub-cent unit pricing: e.g. * `formatUsdCents(0.05, 4)` returns `"0.0005"` for a half-of-one-millicent * amount. The `cents` input is allowed to be fractional so per-token / * per-byte pricing models can compute `priceCents = unitPriceCents × n` diff --git a/src/payment/compose_rails.ts b/src/payment/compose_rails.ts index 00cd355..f6c9772 100644 --- a/src/payment/compose_rails.ts +++ b/src/payment/compose_rails.ts @@ -30,7 +30,7 @@ export interface BuildMppxComposeRailsOptions { /** Include the `stripe/charge` intent (Stripe SPT rail). Default `true`. * * Stripe's documented USD minimum is $0.50 because the fixed processing - * fee (~$0.30) exceeds revenue below that — sub-50-cent charges that DO + * fee (~$0.30) exceeds revenue below that: sub-50-cent charges that DO * go through still cost the merchant money (a $0.11 PI nets -$0.19 after * fees). Some Stripe accounts also reject PI creation under the floor * with `amount_too_small`. The helper auto-drops the rail (with a diff --git a/src/payment/default_rails.ts b/src/payment/default_rails.ts index 57f2178..a6a19a3 100644 --- a/src/payment/default_rails.ts +++ b/src/payment/default_rails.ts @@ -6,7 +6,7 @@ * * Per-order recipient minting (Stripe-multichain) is wired via the Checkout's * `mintRecipients` hook, so the `recipient: ''` sentinel here is the expected - * shape — `mintRecipients` overrides it at request time. + * shape: `mintRecipients` overrides it at request time. */ import { networks } from './networks'; @@ -34,13 +34,13 @@ type DefaultRails = { /** Build the canonical four-rail `rails` dict. Keys match the convention used * across consumer codebases (`tempo`, `x402_base`, `solana_mpp`, `stripe`). - * Empty-string recipients are placeholders — Checkout's `mintRecipients` hook + * Empty-string recipients are placeholders: Checkout's `mintRecipients` hook * must populate real values at request time. */ export function buildDefaultCheckoutRails(opts: BuildDefaultCheckoutRailsOptions): DefaultRails { const out: DefaultRails = {}; if (opts.tempo) { // Derive network + chainId + token from `testnet` when the caller requests it - // without pinning them — testnet must point at Tempo Moderato (chain 42431) and + // without pinning them: testnet must point at Tempo Moderato (chain 42431) and // the testnet USDC contract, not the mainnet defaults baked into RAIL_SPEC_DEFAULTS. const merged: TempoRailSpec = { recipient: '', ...RAIL_SPEC_DEFAULTS.tempo, ...opts.tempo }; if (merged.testnet) { @@ -52,7 +52,7 @@ export function buildDefaultCheckoutRails(opts: BuildDefaultCheckoutRailsOptions } if (opts.x402Base) { // Derive chainId + token from network when caller overrides network without - // pinning them — Sepolia network must point at the Sepolia USDC contract, + // pinning them: Sepolia network must point at the Sepolia USDC contract, // not the mainnet contract baked into RAIL_SPEC_DEFAULTS. const merged: X402BaseRailSpec = { recipient: '', ...RAIL_SPEC_DEFAULTS.x402Base, ...opts.x402Base }; if (merged.network === networks.base.sepolia.caip2) { diff --git a/src/payment/directive.ts b/src/payment/directive.ts index 8cf8671..4961420 100644 --- a/src/payment/directive.ts +++ b/src/payment/directive.ts @@ -17,7 +17,7 @@ export function buildPaymentRequestBlob({ chainId, networkId, }: { - /** Symbolic rail name (e.g., 'tempo-mainnet', 'x402-base-mainnet') — fills in defaults */ + /** Symbolic rail name (e.g., 'tempo-mainnet', 'x402-base-mainnet'): fills in defaults */ rail?: string; /** Amount in USD as a number or string. Converted to raw integer using `decimals`. */ amountUsd: string | number; @@ -29,7 +29,7 @@ export function buildPaymentRequestBlob({ recipient?: string; /** EVM chain ID (goes into methodDetails.chainId). Defaults from rail. */ chainId?: number; - /** Stripe profile_id or similar (goes into methodDetails.networkId — note camelCase per link-cli's mpp decode validator). */ + /** Stripe profile_id or similar (goes into methodDetails.networkId: note camelCase per link-cli's mpp decode validator). */ networkId?: string; }): string { const railDef = rail ? lookupRail(rail) : undefined; @@ -62,11 +62,11 @@ export function paymentDirective({ expires, request, }: { - /** Symbolic rail name — sets `method` automatically */ + /** Symbolic rail name: sets `method` automatically */ rail?: string; /** Challenge id (unique per request, used to correlate retries) */ id: string; - /** Realm — the host of the merchant URL (e.g., "agents.merchant.example") */ + /** Realm: the host of the merchant URL (e.g., "agents.merchant.example") */ realm: string; /** MPP method name. Defaults from rail (e.g., 'tempo', 'stripe'). */ method?: string; diff --git a/src/payment/headers.ts b/src/payment/headers.ts index ea23c1c..529a150 100644 --- a/src/payment/headers.ts +++ b/src/payment/headers.ts @@ -1,32 +1,32 @@ /** - * Multi-rail payment header bundle — one call composes both `WWW-Authenticate` (the + * Multi-rail payment header bundle: one call composes both `WWW-Authenticate` (the * `paymentauth.org` Payment directives) and the standard x402 `PAYMENT-REQUIRED` header * from a single rails declaration. Reduces ~10 lines of merchant boilerplate per 402 * response. * * Layered on top of `paymentDirective` / `wwwAuthenticateHeader` / `paymentRequiredHeader` - * — those primitives stay exposed for vendors who want full control. + * those primitives stay exposed for vendors who want full control. */ import { buildPaymentDirective } from './directive'; import { paymentRequiredHeader, wwwAuthenticateHeader } from './wwwauthenticate'; export interface PaymentHeadersRail { - /** Symbolic rail name — `tempo-mainnet`, `x402-base-mainnet`, `stripe`, etc. */ + /** Symbolic rail name: `tempo-mainnet`, `x402-base-mainnet`, `stripe`, etc. */ rail: string; /** Amount in USD as a number or string. */ amountUsd: string | number; - /** Recipient address (on-chain) — required for crypto rails. */ + /** Recipient address (on-chain): required for crypto rails. */ recipient?: string; - /** Stripe profile_id / network_id — required for `stripe` rail. */ + /** Stripe profile_id / network_id: required for `stripe` rail. */ networkId?: string; - /** EVM chain id override — usually inferred from rail. */ + /** EVM chain id override: usually inferred from rail. */ chainId?: number; - /** Token contract / currency override — usually inferred from rail. */ + /** Token contract / currency override: usually inferred from rail. */ currency?: string; - /** Decimal precision override — usually inferred from rail (USDC=6, etc.). */ + /** Decimal precision override: usually inferred from rail (USDC=6, etc.). */ decimals?: number; - /** MPP method override — usually inferred from rail. */ + /** MPP method override: usually inferred from rail. */ method?: string; /** MPP intent. Default `charge`. */ intent?: string; @@ -69,10 +69,10 @@ export function buildPaymentHeaders({ rails: PaymentHeadersRail[]; /** Order id used as the directive challenge id (per-rail it becomes `${orderId}-${rail}`). */ orderId: string; - /** Realm — the host of the merchant URL (e.g. `agents.merchant.example`). */ + /** Realm: the host of the merchant URL (e.g. `agents.merchant.example`). */ realm: string; /** - * Optional x402 `accepts` array — included as the standard PAYMENT-REQUIRED header so + * Optional x402 `accepts` array: included as the standard PAYMENT-REQUIRED header so * x402 clients (`@x402/fetch`, `@x402/core` HTTPClient, `agentscore-pay`) can parse the * base64-encoded JSON form instead of the WWW-Authenticate text directives. Pass * `undefined` (or omit) to skip the PAYMENT-REQUIRED header. diff --git a/src/payment/idempotency.ts b/src/payment/idempotency.ts index 2a23e38..1ee5856 100644 --- a/src/payment/idempotency.ts +++ b/src/payment/idempotency.ts @@ -7,7 +7,7 @@ * * Convention: * 1. Prefer the upstream payment-rail's stable identifier (Stripe PaymentIntent id, x402 - * tx hash) when one exists — those are already idempotent on their side. + * tx hash) when one exists: those are already idempotent on their side. * 2. Fall back to a synthesized `pi-{orderId}-{amountCents}` key when no upstream id is * available (e.g. pre-creation, or rails without a PI concept). * 3. Server caps idempotency keys at 200 chars; this helper warns when that boundary is @@ -20,7 +20,7 @@ const SERVER_IDEMPOTENCY_KEY_MAX = 200; * Compose a stable idempotency key for AgentScore wallet capture and other retry-safe POSTs. * * Returns `undefined` when no inputs are present (caller should treat as "no idempotency - * key — first attempt only", same shape as omitting the field entirely). + * key: first attempt only", same shape as omitting the field entirely). * * Examples: * ```ts @@ -37,11 +37,11 @@ export function buildIdempotencyKey({ amountCents, prefix, }: { - /** Upstream rail's stable payment id — Stripe PaymentIntent id, x402 tx hash, etc. Wins when present. */ + /** Upstream rail's stable payment id: Stripe PaymentIntent id, x402 tx hash, etc. Wins when present. */ paymentIntentId?: string | null; - /** Order id — used to compose a fallback key when no paymentIntentId exists. */ + /** Order id: used to compose a fallback key when no paymentIntentId exists. */ orderId?: string | null; - /** Amount in cents (or smallest currency unit) — added to the fallback for extra collision resistance. */ + /** Amount in cents (or smallest currency unit): added to the fallback for extra collision resistance. */ amountCents?: number; /** Optional extra prefix to namespace the key (e.g. `"refund"`, `"void"`). */ prefix?: string; @@ -66,10 +66,10 @@ function clampKey(key: string): string { if (key.length <= SERVER_IDEMPOTENCY_KEY_MAX) return key; // Server truncates anyway; surfacing the warning here gives callers a chance to design // shorter inputs. We still return the original key (server-side truncation is the source - // of truth) — clamping client-side would change semantics for any caller already + // of truth): clamping client-side would change semantics for any caller already // depending on the full string for their own dedup. console.warn( - `[agentscore-commerce] idempotency key longer than ${SERVER_IDEMPOTENCY_KEY_MAX} chars — server will truncate, may cause silent collisions if multiple keys share the first ${SERVER_IDEMPOTENCY_KEY_MAX} chars.`, + `[agentscore-commerce] idempotency key longer than ${SERVER_IDEMPOTENCY_KEY_MAX} chars: server will truncate, may cause silent collisions if multiple keys share the first ${SERVER_IDEMPOTENCY_KEY_MAX} chars.`, ); return key; } diff --git a/src/payment/mppx_failures.ts b/src/payment/mppx_failures.ts index 62b3c8a..5e45db4 100644 --- a/src/payment/mppx_failures.ts +++ b/src/payment/mppx_failures.ts @@ -1,8 +1,8 @@ /** Classifier for known mppx verification-failure patterns. * * mppx's `payment.failed` event fires when an mppx rail's verify() throws. - * Some failures carry actionable signals — the agent's wallet isn't - * enrolled with Tempo's keychain, for example — that we want to surface + * Some failures carry actionable signals: the agent's wallet isn't + * enrolled with Tempo's keychain, for example: that we want to surface * to the client as typed error codes instead of the generic * `payment_proof_invalid: regenerate`. * @@ -71,14 +71,14 @@ const SOLANA_CONFIRMATION_TIMEOUT: ClassifiedMppxFailure = { nextSteps: { action: 'check_settlement_before_retry', user_message: - 'Your payment was broadcast to the network but confirmation timed out, so it is unconfirmed rather than failed. Check your wallet balance and the recipient before retrying: if the balance decreased, the payment likely landed and you should NOT pay again — wait for the merchant to reconcile or contact support. Only resubmit if the funds are still in your wallet.', + 'Your payment was broadcast to the network but confirmation timed out, so it is unconfirmed rather than failed. Check your wallet balance and the recipient before retrying: if the balance decreased, the payment likely landed and you should NOT pay again: wait for the merchant to reconcile or contact support. Only resubmit if the funds are still in your wallet.', }, extra: { chain: 'solana', broadcast: true }, }; /** Classify a failure reason against known patterns. * - * Returns `null` when the reason is unrecognized — callers fall back to + * Returns `null` when the reason is unrecognized: callers fall back to * the generic `payment_proof_invalid` envelope. The reason argument may * be the raw `Error.message`, `error.shortMessage` (viem), or any * string carrying the upstream description; we substring-match. diff --git a/src/payment/mppx_server.ts b/src/payment/mppx_server.ts index 4579332..50b04eb 100644 --- a/src/payment/mppx_server.ts +++ b/src/payment/mppx_server.ts @@ -95,7 +95,7 @@ function solanaDefaultRpcUrl(network: SolanaMppNetwork): string { * Keys are rail names (`tempo` / `solana` / `tempo_session` / `stripe`); values * are the matching `*RailSpec` types every other helper also consumes. * - * `mppx` is an OPTIONAL peer dependency — install it only if you accept MPP rails. + * `mppx` is an OPTIONAL peer dependency: install it only if you accept MPP rails. */ export async function createMppxServer({ rails, @@ -109,7 +109,7 @@ export async function createMppxServer({ const mppx = await dynamicImport('mppx/server'); /* v8 ignore start -- peer-dep-absence guard; mppx is installed in the test env */ if (!mppx?.Mppx?.create) { - throw new Error('mppx not installed — `npm install mppx` to use createMppxServer.'); + throw new Error('mppx not installed: `npm install mppx` to use createMppxServer.'); } /* v8 ignore stop */ @@ -138,7 +138,7 @@ export async function createMppxServer({ function registerTempo(mppx: MppxModule, spec: TempoRailSpec, _name: string): unknown { /* v8 ignore start -- peer-dep version-mismatch guard; current mppx ships tempo.charge */ if (!mppx.tempo?.charge) { - throw new Error('mppx.tempo.charge not available — check installed mppx version.'); + throw new Error('mppx.tempo.charge not available: check installed mppx version.'); } /* v8 ignore stop */ const defaultCurrency = spec.testnet ? USDC.tempo.testnet.address : USDC.tempo.mainnet.address; @@ -158,7 +158,7 @@ async function registerTempoSession(mppx: MppxModule, spec: TempoSessionRailSpec /* v8 ignore start -- peer-dep version-mismatch guard; current mppx ships tempo.session */ if (!mppx.tempo?.session) { throw new Error( - 'mppx.tempo.session not available — your mppx version may not support sessions yet. Upgrade with `npm install mppx@latest`.', + 'mppx.tempo.session not available: your mppx version may not support sessions yet. Upgrade with `npm install mppx@latest`.', ); } /* v8 ignore stop */ @@ -177,7 +177,7 @@ async function registerSolana(spec: SolanaMppRailSpec): Promise { const solanaMpp = await dynamicImport('@solana/mpp/server'); if (!solanaMpp?.charge) { throw new Error( - '@solana/mpp not installed — `npm install @solana/mpp @solana/kit` to use the solana rail.', + '@solana/mpp not installed: `npm install @solana/mpp @solana/kit` to use the solana rail.', ); } const network = solanaNetworkFromCAIP2(spec.network); @@ -289,7 +289,7 @@ export function wrapSolanaChargeWithFinalizedBlockhash( /** * Result shape of `composeMppxRequest`. mppx's `mppx.compose(...)(request)` - * resolves to one of two variants — type-narrowed here so consumers can + * resolves to one of two variants: type-narrowed here so consumers can * `if (result.status === 200) { result.withReceipt(...) }` without an * `as any` cast. */ diff --git a/src/payment/network_kind.ts b/src/payment/network_kind.ts index 817e6ee..4f84994 100644 --- a/src/payment/network_kind.ts +++ b/src/payment/network_kind.ts @@ -24,7 +24,7 @@ export function isEvmNetwork(input: NetworkLike): boolean { /** True when the network is a CAIP-2 Solana chain (`solana:`). * Note: `'solana'` bare (no `:`) is the mppx-internal label, NOT a CAIP-2 - * network spec — this helper treats it as false. */ + * network spec: this helper treats it as false. */ export function isSolanaNetwork(input: NetworkLike): boolean { return readNetwork(input).startsWith('solana:'); } diff --git a/src/payment/payment_header.ts b/src/payment/payment_header.ts index 6854cda..09ced1a 100644 --- a/src/payment/payment_header.ts +++ b/src/payment/payment_header.ts @@ -6,9 +6,9 @@ * discovery leg flows through unauthenticated and gets a 402 with all rails. * * Three credential channels are checked: - * - `Payment-Signature` — MPP credentials (Tempo, Solana, Stripe SPT) - * - `X-Payment` — x402 v1 EIP-3009 credentials - * - `Authorization: Payment ` — x402 v2 / paymentauth.org credentials + * - `Payment-Signature`: MPP credentials (Tempo, Solana, Stripe SPT) + * - `X-Payment`: x402 v1 EIP-3009 credentials + * - `Authorization: Payment `: x402 v2 / paymentauth.org credentials */ type WebHeaders = { get(name: string): string | null }; @@ -128,7 +128,7 @@ export interface MalformedPaymentCredential { * * This is deliberately a SHAPE check only. Signature verification, payTo * binding, and challenge validation stay where they are (the x402 validator - * and the mppx settle path) — those need per-request state the hooks produce. + * and the mppx settle path): those need per-request state the hooks produce. * A well-formed-but-invalid credential still reaches the real validators and * fails there. * diff --git a/src/payment/rail_spec.ts b/src/payment/rail_spec.ts index 4e19d20..9e7ba3d 100644 --- a/src/payment/rail_spec.ts +++ b/src/payment/rail_spec.ts @@ -1,5 +1,5 @@ /** - * Canonical `*RailSpec` types — one shape per rail, consumed by every helper. + * Canonical `*RailSpec` types: one shape per rail, consumed by every helper. * * A merchant accepting Tempo + Base + Solana + Stripe declares one `*RailSpec` * per rail and passes it to every helper (`buildAcceptedMethods`, @@ -55,7 +55,7 @@ export interface X402BaseRailSpec { /** * Canonical config for the Solana MPP rail. * - * `signer` is an optional fee-payer signer for server-side fee sponsorship — + * `signer` is an optional fee-payer signer for server-side fee sponsorship: * typed as `unknown` to avoid hard-importing `@solana/kit` types here. Pass any * `TransactionPartialSigner`. */ @@ -73,7 +73,7 @@ export interface SolanaMppRailSpec { /** * Canonical config for the Stripe SPT rail. * - * `recipient` is intentionally absent — Stripe rails use `profileId` as the + * `recipient` is intentionally absent: Stripe rails use `profileId` as the * merchant-side network identifier the agent's SPT is scoped to; the * transaction recipient is the merchant's Stripe account, not an on-chain * address. @@ -91,7 +91,7 @@ export interface StripeRailSpec { * * `escrowContract` is the merchant-deployed on-chain escrow that holds channel * deposits + pays out cumulative vouchers on settlement. `store` is a - * `ChannelStore` instance — typed as `unknown` to avoid hard-importing `mppx`'s + * `ChannelStore` instance: typed as `unknown` to avoid hard-importing `mppx`'s * store interface here. */ export interface TempoSessionRailSpec { @@ -104,7 +104,7 @@ export interface TempoSessionRailSpec { } /** - * Default field values for each `*RailSpec` — callers can spread these into + * Default field values for each `*RailSpec`: callers can spread these into * their spec literal when they want defaults without typing them out. Sourced * from the USDC registry so they stay in sync with on-chain reality. */ diff --git a/src/payment/rails.ts b/src/payment/rails.ts index 3cbbe92..0b5a0b5 100644 --- a/src/payment/rails.ts +++ b/src/payment/rails.ts @@ -40,7 +40,7 @@ export const rails = { decimals: USDC.base.sepolia.decimals, asset: USDC.base.sepolia.address, }, - // Upto rails — pay UP TO a max amount (Permit2-based, vs EIP-3009 for exact). Use for + // Upto rails: pay UP TO a max amount (Permit2-based, vs EIP-3009 for exact). Use for // variable-cost APIs where the actual cost depends on output (LLM tokens, bandwidth, etc.). // Only available on EVM networks; Solana svm doesn't ship an upto scheme yet. 'x402-base-mainnet-upto': { @@ -93,7 +93,7 @@ export interface RailDefinition { /** * Lookup a rail definition by symbolic name. Returns undefined if the rail isn't in - * the registry — vendors with custom rails should pass the low-level fields directly. + * the registry: vendors with custom rails should pass the low-level fields directly. */ export function lookupRail(name: string): RailDefinition | undefined { return rails[name as RailName] as RailDefinition | undefined; diff --git a/src/payment/settlement_override.ts b/src/payment/settlement_override.ts index 2275902..5ae388e 100644 --- a/src/payment/settlement_override.ts +++ b/src/payment/settlement_override.ts @@ -1,5 +1,5 @@ /** - * x402 Settlement-Overrides header helpers — used with the `upto` scheme to specify the + * x402 Settlement-Overrides header helpers: used with the `upto` scheme to specify the * actual amount to charge after the work is done. The header is JSON-encoded and lives * on the merchant's response; the facilitator settles for that amount instead of the * advertised maximum. diff --git a/src/payment/signer.ts b/src/payment/signer.ts index cbf2f17..2c15742 100644 --- a/src/payment/signer.ts +++ b/src/payment/signer.ts @@ -2,7 +2,7 @@ * Network-aware signer extraction from MPP and x402 credentials. * * `extractPaymentSigner(request, x402Header?)` returns `{address, network: 'evm'|'solana'} | null` - * — the network field tells `captureWallet` which key family to attribute the signer to. + * the network field tells `captureWallet` which key family to attribute the signer to. * `extractPaymentSignerFromAuth(authHeader, x402Header?)` is the headers-only variant for * adapters that don't expose a Web Fetch Request (Express, Fastify, ASGI-bridged frameworks). * `readX402PaymentHeader(request)` is a small helper for pulling the x402 header out of a diff --git a/src/payment/solana.ts b/src/payment/solana.ts index 735fb7b..1708585 100644 --- a/src/payment/solana.ts +++ b/src/payment/solana.ts @@ -8,9 +8,9 @@ * `loadSolanaFeePayer({ privateKey })` accepts a Solana keypair in any of the * three forms agents commonly export it as: * - * - **base58** (Phantom export format) — 64-byte secret+public, or 32-byte + * - **base58** (Phantom export format): 64-byte secret+public, or 32-byte * secret-only - * - **hex** — 128-char string (64 bytes hex: 32-byte secret + 32-byte public) + * - **hex**: 128-char string (64 bytes hex: 32-byte secret + 32-byte public) * * Returns a `KeyPairSigner` from `@solana/kit` ready to pass as the `signer` * field on a `SolanaMppRailSpec`. Returns `undefined` when `privateKey` is @@ -31,7 +31,7 @@ export async function loadSolanaFeePayer(opts: { } | null; if (!kit?.createKeyPairSignerFromPrivateKeyBytes || !kit.getBase58Codec) { throw new Error( - '@solana/kit not installed — `npm install @solana/kit` for loadSolanaFeePayer.', + '@solana/kit not installed: `npm install @solana/kit` for loadSolanaFeePayer.', ); } let bytes: Uint8Array; diff --git a/src/payment/wwwauthenticate.ts b/src/payment/wwwauthenticate.ts index 0b56fe1..66109ae 100644 --- a/src/payment/wwwauthenticate.ts +++ b/src/payment/wwwauthenticate.ts @@ -12,7 +12,7 @@ export function wwwAuthenticateHeader(directives: string[]): string { * Opt-in helper: the 402 emitters (`paymentRequiredHeader` / `build402Body`) do NOT * call this. Strict x402 v2 settlement matches the agent's echoed requirement against * the server's rebuilt one by exact comparison, so an extra `maxAmountRequired` the - * rebuild lacks silently fails settle — keep emitted `accepts` as `buildPaymentRequirements` + * rebuild lacks silently fails settle: keep emitted `accepts` as `buildPaymentRequirements` * produced them. Call this only for a client hardcoded to read `maxAmountRequired` * regardless of `x402Version`. */ @@ -35,7 +35,7 @@ export function aliasAmountFields(accepts: unknown[]): unknown[] { * * Note: do NOT add a v1↔v2 amount-field alias here. `@x402/core`'s * `findMatchingRequirements` uses `deepEqual` against the agent's signed - * `accepted` payload — any field present on one side and missing on the other + * `accepted` payload: any field present on one side and missing on the other * (e.g. `maxAmountRequired` on the wire body but not in `buildPaymentRequirements`'s * output) makes the match silently fail at settle time. Keep `accepts` shape * identical to whatever `buildPaymentRequirements` produces server-side. diff --git a/src/payment/x402_server.ts b/src/payment/x402_server.ts index 7df6368..1aeca3a 100644 --- a/src/payment/x402_server.ts +++ b/src/payment/x402_server.ts @@ -67,9 +67,9 @@ interface BazaarModule { /** * One-call x402 server setup. Resolves facilitator, constructs the server, registers * schemes per network with v1+v2 dual-register, optionally adds the Bazaar extension, - * and initializes — replaces ~15 lines of boilerplate with a single config call. + * and initializes: replaces ~15 lines of boilerplate with a single config call. * - * x402 packages are peer dependencies — vendors install only the schemes they use. + * x402 packages are peer dependencies: vendors install only the schemes they use. * Throws a guiding error if a required peer is missing. * * const server = await createX402Server({ @@ -83,7 +83,7 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom /* v8 ignore start -- peer-dep-absence guard; @x402/core is installed in test env */ if (!x402Core) { throw new Error( - '@x402/core not installed — `npm install @x402/core` to use createX402Server.', + '@x402/core not installed: `npm install @x402/core` to use createX402Server.', ); } /* v8 ignore stop */ @@ -100,7 +100,7 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom /* v8 ignore start -- peer-dep-absence guard; @coinbase/x402 is installed in test env */ if (!cb?.facilitator) { throw new Error( - '@coinbase/x402 not installed — `npm install @coinbase/x402` for facilitator: "coinbase".', + '@coinbase/x402 not installed: `npm install @coinbase/x402` for facilitator: "coinbase".', ); } /* v8 ignore stop */ @@ -125,7 +125,7 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom evmUptoModule ??= await dynamicImport('@x402/evm/upto/server'); /* v8 ignore start -- peer-dep-absence guard; @x402/evm is installed in test env */ if (!evmUptoModule?.UptoEvmScheme) { - throw new Error('@x402/evm not installed — `npm install @x402/evm` for x402 base upto rails.'); + throw new Error('@x402/evm not installed: `npm install @x402/evm` for x402 base upto rails.'); } /* v8 ignore stop */ registerX402SchemesV1V2(server, network, new evmUptoModule.UptoEvmScheme()); @@ -133,7 +133,7 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom evmExactModule ??= await dynamicImport('@x402/evm/exact/server'); /* v8 ignore start -- peer-dep-absence guard; @x402/evm is installed in test env */ if (!evmExactModule?.ExactEvmScheme) { - throw new Error('@x402/evm not installed — `npm install @x402/evm` for x402 base rails.'); + throw new Error('@x402/evm not installed: `npm install @x402/evm` for x402 base rails.'); } /* v8 ignore stop */ registerX402SchemesV1V2(server, network, new evmExactModule.ExactEvmScheme()); @@ -150,7 +150,7 @@ export async function createX402Server(opts: CreateX402ServerOptions = {}): Prom /* v8 ignore start -- peer-dep-absence guard; @x402/extensions is installed in test env */ if (!bazaar?.bazaarResourceServerExtension) { throw new Error( - '@x402/extensions not installed — `npm install @x402/extensions` for bazaar discovery.', + '@x402/extensions not installed: `npm install @x402/extensions` for bazaar discovery.', ); } /* v8 ignore stop */ @@ -180,12 +180,12 @@ export interface BuildX402AcceptsForOptions { * 1. Construct the resource-config object themselves * 2. Remember to serialize each Pydantic-equivalent requirement back to a * plain object before stitching it into the 402 body - * 3. Hardcode `extra` (which differs by the actual on-chain contract — base + * 3. Hardcode `extra` (which differs by the actual on-chain contract: base * mainnet USDC has `name: "USD Coin"`, base sepolia USDC has `name: "USDC"`; * EIP-712 domain hashes differ, so getting this wrong silently breaks every * signature verify at the facilitator) * - * Returns a list of plain objects in the shape that x402 expects on the wire — + * Returns a list of plain objects in the shape that x402 expects on the wire: * drop them straight into the `accepts` field of the 402 challenge body. */ export async function buildX402AcceptsFor402( diff --git a/src/payment/x402_settle.ts b/src/payment/x402_settle.ts index f5c3506..afa8e9e 100644 --- a/src/payment/x402_settle.ts +++ b/src/payment/x402_settle.ts @@ -173,7 +173,7 @@ export function classifyX402SettleResult( * `try/catch` around the full settle flow). Returns a `ClassifiedX402Error` * when the error message matches a known pattern; `null` otherwise. * - * Callers should rethrow on `null` — this helper never swallows unknown errors. + * Callers should rethrow on `null`: this helper never swallows unknown errors. * * Pattern matching is case-insensitive substring on the error message: * - `"x402version"` / `"invalid payment"` / `"unsupported x402"` → diff --git a/src/payment/x402_validation.ts b/src/payment/x402_validation.ts index 69a2698..605a874 100644 --- a/src/payment/x402_validation.ts +++ b/src/payment/x402_validation.ts @@ -11,7 +11,7 @@ * extract the signed network + payTo, validate against the merchant's accepted * network, validate the payTo address shape, and check that the payTo was minted by * THIS merchant (cache hit). Each step has its own denial code and `next_steps` - * shape — getting the message right by hand across 4 conditions is fiddly. + * shape: getting the message right by hand across 4 conditions is fiddly. */ import { networks } from './networks'; @@ -27,7 +27,7 @@ export const X402_SUPPORTED_BASE_NETWORKS = new Set([ * init / server boot. * * Throws `Error` with a message that names the unsupported value AND lists the valid - * options — agents tracking down a misconfigured deploy don't need to grep for the + * options: agents tracking down a misconfigured deploy don't need to grep for the * supported list. */ export function validateX402NetworkConfig({ baseNetwork }: { baseNetwork: string }): void { @@ -69,7 +69,7 @@ export type VerifyX402RequestResult = }; const REGENERATE_WARNING = - 'Use `agentscore-pay pay --chain base` (or `tempo request` for Tempo USDC) so the credential is signed and submitted via the protocol handshake. Do NOT use `tempo wallet transfer` — that sends USDC on-chain but does not complete the handshake.'; + 'Use `agentscore-pay pay --chain base` (or `tempo request` for Tempo USDC) so the credential is signed and submitted via the protocol handshake. Do NOT use `tempo wallet transfer`: that sends USDC on-chain but does not complete the handshake.'; function regenerateBody(message: string, userMessage: string) { return { @@ -90,7 +90,7 @@ function regenerateBody(message: string, userMessage: string) { * Returns `{ok: true, payload, signedNetwork, signedPayTo}` when valid; the caller * passes `payload` straight into `processX402Settle`. * - * Returns `{ok: false, body, status}` when invalid — the merchant just does + * Returns `{ok: false, body, status}` when invalid: the merchant just does * `return c.json(body, status)` (or framework equivalent). * * Reads the header from `payment-signature` first, falling back to `x-payment` (both @@ -101,7 +101,7 @@ export async function verifyX402Request({ isCachedAddress, acceptedNetwork, }: { - /** The incoming Request — `verifyX402Request` reads the X-Payment / payment-signature header. */ + /** The incoming Request: `verifyX402Request` reads the X-Payment / payment-signature header. */ request: Request; /** Async lookup that returns true when the address was minted by this merchant * (typically `piCache.hasAddress`). The check validates that the credential's diff --git a/src/payment/zero-settle.ts b/src/payment/zero-settle.ts index 38b52d8..e4db8fd 100644 --- a/src/payment/zero-settle.ts +++ b/src/payment/zero-settle.ts @@ -16,7 +16,7 @@ * - **Every other MPP credential at $0** (`hash` / `transaction` payloads, * token-style credentials, Solana): carved out. These arise when the agent * signed against a NONZERO quote that the merchant re-priced to $0 at - * settle (no-match / full-discount flows — the authorization is simply + * settle (no-match / full-discount flows: the authorization is simply * never exercised), or on rails with no upstream $0 contract * (`@solana/mpp` has no proof-credential surface). Upstream would reject * all of them at $0, so the carve-out lifts the signer for wallet-capture diff --git a/src/quote_cache.ts b/src/quote_cache.ts index 6b8a2e2..2989b4a 100644 --- a/src/quote_cache.ts +++ b/src/quote_cache.ts @@ -3,15 +3,15 @@ * * `createResultCache` is the neutral primitive: a keyed JSON-value cache with * a stable content-hash key builder. Use it to cache any per-request result a - * merchant computes on the probe leg and replays on the settle leg — e.g. the + * merchant computes on the probe leg and replays on the settle leg: e.g. the * output of a paid upstream call made in a `Checkout.preValidate` hook, so a * payment retry (or a junk payment header) never pays upstream twice. * * `createQuoteCache` is the compute-first-flavored wrapper used by * {@link computeFirstCheckout}: the cached value is a `CachedQuote` * (`{body, priceCents, recipients}`). Standard x402-fetch retry semantics - * resign the buyer's ORIGINAL request body — there's no `result_id` echo - * channel through the protocol — so both caches key by a stable content-hash + * resign the buyer's ORIGINAL request body: there's no `result_id` echo + * channel through the protocol: so both caches key by a stable content-hash * of the request body. Same body → same hash → same cache slot. * * Default in-memory `Map`; optional `redisUrl` lazy-imports `ioredis` for diff --git a/src/signer.ts b/src/signer.ts index 84f177d..f2c24c8 100644 --- a/src/signer.ts +++ b/src/signer.ts @@ -3,13 +3,13 @@ * * Shared between merchants and the gate. Three paths recover a wallet signer: * - * - **Tempo MPP** — `Authorization: Payment `; credential `source` is a DID of the + * - **Tempo MPP**: `Authorization: Payment `; credential `source` is a DID of the * form `did:pkh:eip155::
`. - * - **Solana MPP `solana/charge`** — `Authorization: Payment `; recovery via either + * - **Solana MPP `solana/charge`**: `Authorization: Payment `; recovery via either * a `did:pkh:solana::
` source (when set by the client) or by decoding * the credential's signed-tx payload and reading the SPL `TransferChecked` authority - * (pull mode only — `payload.type === 'transaction'`). - * - **x402 EIP-3009 (EVM, e.g. Base/Sepolia)** — `payment-signature` / `x-payment`; + * (pull mode only: `payload.type === 'transaction'`). + * - **x402 EIP-3009 (EVM, e.g. Base/Sepolia)**: `payment-signature` / `x-payment`; * decoded payload carries `payload.authorization.from`. * * Optional peer deps: `mppx` for MPP credentials, `@solana/kit` for the Solana tx-decode @@ -41,7 +41,7 @@ interface SolanaKitMinimal { /** * Decode a Solana MPP `solana/charge` credential's `payload.transaction` (base64-encoded - * signed Solana tx) and return the SPL `TransferChecked` authority — the source-ATA owner, + * signed Solana tx) and return the SPL `TransferChecked` authority: the source-ATA owner, * which is the buyer's wallet. Pull mode only (`payload.type === 'transaction'`); push mode * (`payload.type === 'signature'`) returns null because recovery would require an RPC fetch. */ @@ -97,7 +97,7 @@ async function extractSolanaSignerFromCredential(credential: unknown): Promise { - // x402 — base64 JSON, EIP-3009 only. EVM `payload.authorization.from` is the signer. + // x402: base64 JSON, EIP-3009 only. EVM `payload.authorization.from` is the signer. // Tried before the MPP path so a request carrying both header families resolves the // x402 signer first, consistent with `extractSignerForPrecheck`. if (x402PaymentHeader) { @@ -132,7 +132,7 @@ export async function extractPaymentSigner( } } - // MPP — Authorization: Payment + // MPP: Authorization: Payment const authHeader = request.headers.get('authorization'); if (authHeader) { try { @@ -170,7 +170,7 @@ export async function extractPaymentSigner( * (Express, Fastify, ASGI-bridged frameworks). Constructs a synthetic Request carrying * only the `authorization` header and delegates to {@link extractPaymentSigner}. Works * because the MPP and x402 paths only read `request.headers.get('authorization')` and - * the explicit `x402PaymentHeader` arg — no body, query, or method semantics needed. + * the explicit `x402PaymentHeader` arg: no body, query, or method semantics needed. */ export async function extractPaymentSignerFromAuth( authHeader: string | null | undefined, diff --git a/src/stripe-multichain/mppx_stripe.ts b/src/stripe-multichain/mppx_stripe.ts index 866e109..a31f5a2 100644 --- a/src/stripe-multichain/mppx_stripe.ts +++ b/src/stripe-multichain/mppx_stripe.ts @@ -1,6 +1,6 @@ /** * Wraps the `mppStripe.charge(...)` boilerplate from `mppx/server`. Returns the value - * vendors pass into `Mppx.create({ methods: [...] })`. mppx is an OPTIONAL peer dependency — + * vendors pass into `Mppx.create({ methods: [...] })`. mppx is an OPTIONAL peer dependency: * vendors who don't use Stripe SPT don't need to install it. * * Example: @@ -27,7 +27,7 @@ export async function createMppxStripe({ }: { /** Stripe profile_id / network_id (the value advertised in your `stripe/charge` accepted_methods entry). */ profileId: string; - /** Stripe secret key — mppx uses it to validate inbound SharedPaymentTokens. */ + /** Stripe secret key: mppx uses it to validate inbound SharedPaymentTokens. */ secretKey: string; /** Payment method types this stripe rail accepts. Default ['card', 'link']. */ paymentMethodTypes?: string[]; @@ -45,7 +45,7 @@ export async function createMppxStripe({ /* v8 ignore start -- peer-dep-absence guard; mppx is installed in the test env so this branch can't be exercised without mocking the dynamic import */ if (!mppx?.stripe?.charge) { throw new Error( - 'mppx not installed — install with `npm install mppx` to use createMppxStripe.', + 'mppx not installed: install with `npm install mppx` to use createMppxStripe.', ); } /* v8 ignore stop */ diff --git a/src/stripe-multichain/pay_to_address.ts b/src/stripe-multichain/pay_to_address.ts index 292a697..6f7b2d8 100644 --- a/src/stripe-multichain/pay_to_address.ts +++ b/src/stripe-multichain/pay_to_address.ts @@ -8,7 +8,7 @@ * the 402 advertises a stable per-order deposit address. * - **Settle leg** (MPP credential attached): reuse the buyer's * signed-against payTo from the credential (after verifying it's in the - * local cache OR matches a configured `staticRecipients` entry) — + * local cache OR matches a configured `staticRecipients` entry): * otherwise the verify leg would compare against a freshly-rotated * address and reject the credential. * @@ -20,7 +20,7 @@ * `preferredNetwork`'s address). Convenient when the merchant only needs * one rail's payTo back. * - `mintMultichainRecipients` returns the full per-rail map plus the PI - * id. Preferred for multi-rail merchants — avoids the second pi-cache + * id. Preferred for multi-rail merchants: avoids the second pi-cache * lookup to stitch sibling addresses back together. */ @@ -29,7 +29,7 @@ import { createMultichainPaymentIntent, type StripeClientLike } from './payment_ import type { PiCache } from './pi-cache'; export interface CreatePayToAddressFromStripePIOptions { - /** Inbound HTTP request — header is read for an Authorization Payment credential. */ + /** Inbound HTTP request: header is read for an Authorization Payment credential. */ request: Request; /** Order amount in cents. */ amountCents: number; @@ -39,12 +39,12 @@ export interface CreatePayToAddressFromStripePIOptions { piCache: PiCache; /** Networks to advertise to Stripe `deposit_options`. Default ['tempo', 'base', 'solana']. * Networks present as a key in `staticRecipients` are removed from this list - * automatically — Stripe is not asked to mint a per-PI address for them. */ + * automatically: Stripe is not asked to mint a per-PI address for them. */ networks?: string[]; /** Merchant-owned static deposit addresses, keyed by network. Use this to bypass * Stripe per-PI rotation on chains where a rotating recipient is expensive * (Solana: each new recipient address costs ~0.002 SOL of ATA rent locked on - * an account the merchant can't close — see MPP spec §13.6 "ATA Rent Drain"). + * an account the merchant can't close: see MPP spec §13.6 "ATA Rent Drain"). * * The SDK handles everything: (a) excludes these networks from the Stripe mint, * (b) registers them with `piCache.cacheAddress` on every call (so settle-leg @@ -67,7 +67,7 @@ export interface CreatePayToAddressFromStripePIOptions { preferredNetwork?: string; } -/** Structured result for `mintMultichainRecipients` — exposes the full per-network +/** Structured result for `mintMultichainRecipients`: exposes the full per-network * deposit map plus the PI id, so merchants can stop guessing "is the returned * string the tempo address or the solana static". */ export interface MintMultichainRecipientsResult { @@ -101,7 +101,7 @@ let warnedRotatingSolanaMint = false; * `createMultichainPaymentIntent` and caches the addresses + PI mapping. * * When `staticRecipients` is configured, prefer `mintMultichainRecipients` - * instead — its structured return avoids the "is this string the tempo or + * instead: its structured return avoids the "is this string the tempo or * the solana static" ambiguity on the settle leg. */ export async function createPayToAddressFromStripePI( opts: CreatePayToAddressFromStripePIOptions, @@ -115,7 +115,7 @@ export async function createPayToAddressFromStripePI( /** Structured variant of `createPayToAddressFromStripePI`: returns the full * per-rail map plus the PI id. Preferred when the merchant's `mintRecipients` - * hook needs all rail addresses (typical multi-rail merchant) — saves the + * hook needs all rail addresses (typical multi-rail merchant): saves the * pi-cache lookups + sidesteps the "returned-string-is-ambiguous" trap on * the settle leg when `staticRecipients` is configured. */ export async function mintMultichainRecipients( @@ -174,7 +174,7 @@ async function tryResolveFromCredential( }); } - // Merchant-owned static recipient is always valid — bypasses the piCache TTL + // Merchant-owned static recipient is always valid: bypasses the piCache TTL // window since the merchant owns the address. const staticForMethod = opts.staticRecipients?.[method]; if (staticForMethod && staticForMethod === toAddress) return toAddress; diff --git a/src/stripe-multichain/payment_intent.ts b/src/stripe-multichain/payment_intent.ts index 551d98c..fc79312 100644 --- a/src/stripe-multichain/payment_intent.ts +++ b/src/stripe-multichain/payment_intent.ts @@ -1,7 +1,7 @@ import { CheckoutValidationError } from '../errors'; /** - * Minimal Stripe client surface — only the methods we use. Vendors pass their actual + * Minimal Stripe client surface: only the methods we use. Vendors pass their actual * `Stripe` instance (peer dep on the `stripe` package); this interface keeps the SDK * decoupled from any specific Stripe version. */ @@ -70,7 +70,7 @@ async function createMultichainPaymentIntentOnce({ }: { /** A configured Stripe SDK instance. */ stripe: StripeClientLike; - /** Amount in cents (Stripe convention — $1.00 = 100). */ + /** Amount in cents (Stripe convention: $1.00 = 100). */ amount: number; /** Currency code. Default 'usd'. */ currency?: string; @@ -78,7 +78,7 @@ async function createMultichainPaymentIntentOnce({ networks?: string[]; /** Metadata to attach to the PI (visible in Stripe dashboard). */ metadata?: Record; - /** Idempotency key — agent retries of the same purchase won't create duplicate PIs. */ + /** Idempotency key: agent retries of the same purchase won't create duplicate PIs. */ idempotencyKey?: string; }): Promise { const pi = await stripe.paymentIntents.create( diff --git a/src/stripe-multichain/pi-cache.ts b/src/stripe-multichain/pi-cache.ts index f5ed45c..ae090f2 100644 --- a/src/stripe-multichain/pi-cache.ts +++ b/src/stripe-multichain/pi-cache.ts @@ -3,20 +3,20 @@ * * Stripe-multichain merchants need three lookups during a request lifecycle: * - * 1. **Is this on-chain `pay_to` address one we minted?** — when an MPP credential + * 1. **Is this on-chain `pay_to` address one we minted?**: when an MPP credential * arrives with a `recipient`, verify it matches a recently-minted Stripe deposit * address. Validates the credential's deposit address against the addresses the * merchant has actually minted. * - * 2. **Which PaymentIntent owns this deposit address?** — when settling, the + * 2. **Which PaymentIntent owns this deposit address?**: when settling, the * `simulate_crypto_deposit` test_helpers call needs the PaymentIntent id for the * deposit address that was paid to. * - * 3. **Which sibling deposit addresses belong to the same PaymentIntent?** — when + * 3. **Which sibling deposit addresses belong to the same PaymentIntent?**: when * enriching a 402 with x402 entries, the merchant needs the Base + Solana addresses * Stripe minted alongside the original Tempo address (one PI carries up to three). * - * All three are TTL-bounded (default 300s — long enough for an agent to retry, short + * All three are TTL-bounded (default 300s: long enough for an agent to retry, short * enough to bound memory). Backed by Redis when `redisUrl` is set, falls back to * in-process Map otherwise. Single-instance servers can use the in-memory cache; * multi-instance deployments need a shared cache (Redis) so a deposit lands on @@ -25,7 +25,7 @@ import { memoizedRedis, type MinimalRedis } from '../_redis'; -// ioredis is an optional peer dep — typed structurally to avoid pulling its types into +// ioredis is an optional peer dep: typed structurally to avoid pulling its types into // the build for merchants that run in-process without Redis. The structural type covers // only the methods we call (set with EX/get); merchants using Redis install ioredis // themselves. diff --git a/src/stripe-multichain/simulate_deposit.ts b/src/stripe-multichain/simulate_deposit.ts index 6b2e633..51cd970 100644 --- a/src/stripe-multichain/simulate_deposit.ts +++ b/src/stripe-multichain/simulate_deposit.ts @@ -1,6 +1,6 @@ /** * Stripe's documented magic test_helpers transaction hash that resolves the - * PaymentIntent to `succeeded` within 15 seconds. Same value across all networks — + * PaymentIntent to `succeeded` within 15 seconds. Same value across all networks: * Stripe normalizes the format internally. Anything else (including network-shaped * placeholder bytes) is rejected with "not a valid testmode transaction hash". * @@ -46,9 +46,9 @@ export async function simulateCryptoDeposit({ network: 'tempo' | 'base' | 'solana'; /** Optional simulated buyer wallet address. Defaults to a sensible placeholder per network. */ buyerWallet?: string; - /** Token currency (e.g., 'usdc'). Optional — passed as a form param if set. */ + /** Token currency (e.g., 'usdc'). Optional: passed as a form param if set. */ tokenCurrency?: string; - /** Simulated transaction hash. Optional — passed as a form param if set. */ + /** Simulated transaction hash. Optional: passed as a form param if set. */ transactionHash?: string; /** Stripe secret key (for the test-helpers Authorization header). Must be a `sk_test_...` key. */ stripeSecretKey: string; @@ -84,7 +84,7 @@ export async function simulateCryptoDeposit({ * Higher-level wrapper around {@link simulateCryptoDeposit} for the testnet/dev path. * Bundles the three steps every Stripe-multichain merchant repeats: * - * 1. Gate on `sk_test_` key prefix — production keys reject the test_helpers endpoint + * 1. Gate on `sk_test_` key prefix: production keys reject the test_helpers endpoint * with 400; live deposits reach Stripe's real crypto-deposit watcher instead. * 2. Resolve the PaymentIntent id from the deposit address (cache lookup). * 3. Call `simulate_crypto_deposit` with Stripe's documented success magic hash. @@ -93,7 +93,7 @@ export async function simulateCryptoDeposit({ * `[stripe] ✗ Failed to simulate deposit for PI : ` on failure. * Errors are caught + logged (never thrown) so a sim hiccup doesn't fail the order. * - * Use case is exclusively dev/testnet end-to-end — production servers (sk_live_) no-op. + * Use case is exclusively dev/testnet end-to-end: production servers (sk_live_) no-op. */ export async function simulateDepositIfTestMode({ getPaymentIntentId, @@ -104,7 +104,7 @@ export async function simulateDepositIfTestMode({ stripeSecretKey, stripeVersion, }: { - /** Stripe PaymentIntent id resolver — given a deposit address, return the PI id (or undefined + /** Stripe PaymentIntent id resolver: given a deposit address, return the PI id (or undefined * if the cache TTL expired between 402 emit and settlement). Typically `cache.getPaymentIntentId`. */ getPaymentIntentId: (depositAddress: string) => string | undefined; /** The deposit address that was paid to (recipient). */ @@ -124,7 +124,7 @@ export async function simulateDepositIfTestMode({ const piId = getPaymentIntentId(depositAddress); if (!piId) { console.warn( - `[stripe] Skipping deposit simulation — no PI cached for deposit address ${depositAddress.slice(0, 10)}… (network=${network}). The PI cache TTL may have expired between 402 emission and settlement.`, + `[stripe] Skipping deposit simulation: no PI cached for deposit address ${depositAddress.slice(0, 10)}… (network=${network}). The PI cache TTL may have expired between 402 emission and settlement.`, ); return; } diff --git a/tests/_mppx_receipt.test.ts b/tests/_mppx_receipt.test.ts index bad3125..b5c774f 100644 --- a/tests/_mppx_receipt.test.ts +++ b/tests/_mppx_receipt.test.ts @@ -49,7 +49,7 @@ describe('deriveMppxReceiptMethod', () => { it('falls back to extractMppxReceiptMethod(header) when no direct receipt.method', async () => { // raw has withReceipt → header found → method extraction attempted. // mppx import will fail (peer dep not installed in tests for this module), - // so the result is undefined — but the line is exercised. + // so the result is undefined: but the line is exercised. const raw = { withReceipt: (_res: Response): Response => new Response(null, { headers: { 'Payment-Receipt': 'fake-receipt-base64' } }), diff --git a/tests/_redis.test.ts b/tests/_redis.test.ts index 7b86b01..b92ec6d 100644 --- a/tests/_redis.test.ts +++ b/tests/_redis.test.ts @@ -20,7 +20,7 @@ describe('memoizedRedis', () => { expect(await get()).toBeNull(); }); - it('memoizes — second call returns the same value without re-attempting', async () => { + it('memoizes: second call returns the same value without re-attempting', async () => { const get = memoizedRedis({ url: undefined, label: 'test' }); const a = await get(); const b = await get(); @@ -39,7 +39,7 @@ describe('memoizedRedis', () => { // either way the helper returns null cleanly when construction fails. const get = memoizedRedis({ url: 'redis://invalid:0', label: 'test' }); const result = await get(); - // We don't assert null specifically — only that memoization works (no throw). + // We don't assert null specifically: only that memoization works (no throw). const result2 = await get(); expect(result2).toBe(result); }); diff --git a/tests/_response.test.ts b/tests/_response.test.ts index 80d78d4..2106e2a 100644 --- a/tests/_response.test.ts +++ b/tests/_response.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { denialReasonToBody } from '../src/_response'; import type { DenialReason } from '../src/core'; -// Minimal DenialReason factory — every field starts undefined so each test sets +// Minimal DenialReason factory: every field starts undefined so each test sets // only what it's asserting. const reason = (fields: Partial = {}): DenialReason => ({ code: 'missing_identity', @@ -87,7 +87,7 @@ describe('denialReasonToBody', () => { }); it('marshals actual_signer_operator: null explicitly (distinct from undefined)', () => { - // null means "signer is a valid wallet but not linked to any operator" — the + // null means "signer is a valid wallet but not linked to any operator": the // gate must surface null rather than omit the field so agents can distinguish // "not linked" from "never checked". const body = denialReasonToBody(reason({ diff --git a/tests/address.test.ts b/tests/address.test.ts index 9da2f17..f8958f6 100644 --- a/tests/address.test.ts +++ b/tests/address.test.ts @@ -24,7 +24,7 @@ describe('isSolanaAddress', () => { }); it('rejects EVM addresses (0x prefix collides with valid base58 chars)', () => { - // 0x... would otherwise match the base58 alphabet — the explicit !startsWith('0x') + // 0x... would otherwise match the base58 alphabet: the explicit !startsWith('0x') // disambiguation guards against routing an EVM address into the Solana code path. expect(isSolanaAddress('0x690BF056DA820EF2e74f8943B3Fe5ca4ADEe7a3e')).toBe(false); }); @@ -65,7 +65,7 @@ describe('normalizeAddress', () => { it('falls through to lowercase for unrecognized inputs (consistent with EVM-historical default)', () => { // Garbage input still returns SOMETHING (lowercased) so callers don't need an - // is-valid guard before normalizing — DB writes are guarded separately. + // is-valid guard before normalizing: DB writes are guarded separately. expect(normalizeAddress('NotAnAddress')).toBe('notanaddress'); }); }); diff --git a/tests/agent_memory_aip.test.ts b/tests/agent_memory_aip.test.ts index 1c7b58a..20bab21 100644 --- a/tests/agent_memory_aip.test.ts +++ b/tests/agent_memory_aip.test.ts @@ -3,7 +3,7 @@ * * The cross-merchant memory hint advertises the AIT identity path ONLY when the merchant opted * into AIP (passed a non-empty trusted-issuer list). Merchants that don't accept AITs must not - * tell agents to present one — that would be wrong guidance and leak a capability the route + * tell agents to present one: that would be wrong guidance and leak a capability the route * doesn't honor. Verifies both branches. */ import { describe, expect, it } from 'vitest'; diff --git a/tests/aip_adapters.test.ts b/tests/aip_adapters.test.ts index e183e1a..4c41826 100644 --- a/tests/aip_adapters.test.ts +++ b/tests/aip_adapters.test.ts @@ -159,7 +159,7 @@ describe('authority pinning (AipGateOptions.authority)', () => { }); }); -// Express middleware tested by direct invocation with mock req/res/next — deterministic, +// Express middleware tested by direct invocation with mock req/res/next: deterministic, // no HTTP stack (supertest isn't a dep). Express is referenced only for its types here. interface MockRes { statusCode: number; diff --git a/tests/aip_gate.test.ts b/tests/aip_gate.test.ts index d9f3fc9..eff8f45 100644 --- a/tests/aip_gate.test.ts +++ b/tests/aip_gate.test.ts @@ -122,7 +122,7 @@ describe('aipErrorStatus', () => { } }); - it('returns 503 for key_unavailable (IdP JWKS unreachable — retryable, not a client auth failure)', () => { + it('returns 503 for key_unavailable (IdP JWKS unreachable: retryable, not a client auth failure)', () => { expect(aipErrorStatus('key_unavailable')).toBe(503); }); }); diff --git a/tests/aip_hono_gate.test.ts b/tests/aip_hono_gate.test.ts index c43c42f..8f6d5cc 100644 --- a/tests/aip_hono_gate.test.ts +++ b/tests/aip_hono_gate.test.ts @@ -1,5 +1,5 @@ /** - * AIP gate wiring for the Hono adapter — the context-getter sibling of express/fastify. + * AIP gate wiring for the Hono adapter: the context-getter sibling of express/fastify. * * `aip_adapters.test.ts` covers express/fastify/web/nextjs; this is Hono's dedicated suite, * exercising `aipGate` / `conditionalAipGate` / `getVerifiedAit` end-to-end with a real @@ -132,7 +132,7 @@ describe('hono conditionalAipGate', () => { }); }); -describe('hono aipGate — trust_level / auth.amr enforcement (standalone gate)', () => { +describe('hono aipGate: trust_level / auth.amr enforcement (standalone gate)', () => { const trustApp = (aipOpts: Record) => { const a = new Hono(); a.post('/checkout', aipGate({ jwks: jwks(), now: NOW, ...aipOpts }), (c) => c.json({ ok: true })); diff --git a/tests/aip_http_signature.test.ts b/tests/aip_http_signature.test.ts index 0620b5b..321e238 100644 --- a/tests/aip_http_signature.test.ts +++ b/tests/aip_http_signature.test.ts @@ -33,7 +33,7 @@ const baseReq = { const roundTrip = async (overrides: Partial = {}) => { // The verifier now REQUIRES `expires` (replay-window hardening), so default to a 60s window // (matching pay's signer) unless a test overrides it. `signMessage` itself omits `expires` by - // default — that's only the serialization-format default, exercised explicitly below. + // default: that's only the serialization-format default, exercised explicitly below. const created = overrides.created ?? Math.floor(Date.now() / 1000); const { signatureInput, signature } = await signMessage({ ...baseReq, @@ -113,7 +113,7 @@ describe('parseSignatureInput', () => { it('preserves the raw member value (rawParams) byte-for-byte', () => { // The verifier echoes this into the "@signature-params" base line, so it must be the member - // value exactly as received — including a non-canonical param order. + // value exactly as received: including a non-canonical param order. const raw = '("@method" "@authority" "@path" "agent-identity");keyid="abc";created=1715400000;expires=1715400060;tag="agent-identity"'; const parsed = parseSignatureInput(`ait=${raw}`); expect(parsed?.rawParams).toBe(raw); @@ -241,7 +241,7 @@ describe('verifyMessageSignature failure modes', () => { const otherPriv = await exportJWK(privateKey); const otherPub = await exportJWK(publicKey); const s = await signMessage({ ...baseReq, privateJwk: otherPriv, publicJwk: otherPub, created: 1715400000, expires: 1715400060 }); - // present the wrong cnf (our original key) — keyid in the sig won't match its thumbprint + // present the wrong cnf (our original key): keyid in the sig won't match its thumbprint const r = await verifyMessageSignature({ ...baseReq, signatureInput: s.signatureInput, @@ -286,7 +286,7 @@ describe('verifyMessageSignature failure modes', () => { }); it('rejects a signature missing expires (replayable for the full AIT lifetime)', async () => { - // signMessage omits `expires` by default — exactly the spec-loose shape the hardening rejects. + // signMessage omits `expires` by default: exactly the spec-loose shape the hardening rejects. const s = await signMessage({ ...baseReq, privateJwk, publicJwk, created: 1715400000 }); const r = await verify({}, s); expect(r).toEqual({ ok: false, reason: 'expires_missing' }); @@ -309,7 +309,7 @@ describe('verifyMessageSignature failure modes', () => { it('rejects a NEGATIVE window (expires before created)', async () => { // A negative window would slip under the 120s cap (negative < 120) and, within skew, pass the - // created/expires clock checks — it must be rejected as a window violation. + // created/expires clock checks: it must be rejected as a window violation. const s = await roundTrip({ created: 1715400000, expires: 1715399990 }); const r = await verify({ now: 1715400005 }, s); expect(r).toEqual({ ok: false, reason: 'pop_window_too_long' }); diff --git a/tests/aip_jwks.test.ts b/tests/aip_jwks.test.ts index 6af348b..caee2b3 100644 --- a/tests/aip_jwks.test.ts +++ b/tests/aip_jwks.test.ts @@ -94,7 +94,7 @@ describe('JwksCache.isTrusted', () => { }); it('always trusts AgentScore’s canonical issuer, even with no/empty trustedIssuers', () => { - // The invariant must hold for EVERY consumer (raw adapters included), not just Checkout — + // The invariant must hold for EVERY consumer (raw adapters included), not just Checkout: // a merchant can't accidentally fail to trust AgentScore-issued AITs. const noList = new JwksCache({ fetchImpl: makeFetch([keyA]) }); expect(noList.isTrusted('https://www.agentscore.com')).toBe(true); @@ -175,7 +175,7 @@ describe('JwksCache.getKey', () => { // --- refetch-amplification / DoS guard (REFETCH_COOLDOWN_MS) --------------------------------- - it('does NOT refetch on a kid miss within the cooldown — a flood of unknown kids costs 1 fetch', async () => { + it('does NOT refetch on a kid miss within the cooldown: a flood of unknown kids costs 1 fetch', async () => { // Canonical issuer is always trusted and kid/iss are read pre-verify, so an unauthenticated // attacker could otherwise force one JWKS GET per unknown-kid token. The cooldown caps it. let nowMs = 1_000_000; @@ -184,7 +184,7 @@ describe('JwksCache.getKey', () => { await c.getKey('https://issuer.example', 'key-A'); // populates cache + stamps cooldown (call 1) // Stream many distinct unknown kids well inside the cooldown window. for (let i = 0; i < 50; i++) { - nowMs += 100; // 5s total elapsed — still < 30s cooldown + nowMs += 100; // 5s total elapsed: still < 30s cooldown const r = await c.getKey('https://issuer.example', `attacker-kid-${i}`); expect(r).toEqual({ ok: false, reason: 'key_not_found' }); } @@ -305,13 +305,13 @@ describe('JwksCache.getKey', () => { it('sequential failures within the cooldown perform exactly ONE upstream fetch', async () => { // An erroring issuer must not be re-fetched per request: the failed attempt stamps the same // per-issuer cooldown a success does, so a stream of tokens against a down/cold issuer costs - // one GET per cooldown window — not one per token. + // one GET per cooldown window: not one per token. let nowMs = 1_000_000; const fetchImpl = vi.fn(async () => { throw new Error('network'); }); const c = new JwksCache({ trustedIssuers: TRUSTED, fetchImpl, now: () => nowMs }); expect(await c.getKey('https://issuer.example', 'key-A')).toEqual({ ok: false, reason: 'fetch_failed' }); for (let i = 0; i < 50; i++) { - nowMs += 100; // 5s total elapsed — still < 30s cooldown + nowMs += 100; // 5s total elapsed: still < 30s cooldown const r = await c.getKey('https://issuer.example', `kid-${i}`); expect(r).toEqual({ ok: false, reason: 'fetch_failed' }); } @@ -415,7 +415,7 @@ describe('JwksCache.getKey', () => { expect(fetchImpl).toHaveBeenCalledTimes(1); // single upstream fetch for the whole burst // Each caller still selects independently from the shared key set: the two known kids resolve, - // the rest report key_not_found — proving coalescing didn't cross-contaminate verdicts. + // the rest report key_not_found: proving coalescing didn't cross-contaminate verdicts. expect(results[0]).toMatchObject({ ok: true }); expect(results[1]).toMatchObject({ ok: true }); expect(results[2]).toEqual({ ok: false, reason: 'key_not_found' }); diff --git a/tests/aip_request.test.ts b/tests/aip_request.test.ts index 9f6c1a8..5659aff 100644 --- a/tests/aip_request.test.ts +++ b/tests/aip_request.test.ts @@ -59,7 +59,7 @@ describe('hasAgentIdentityHeader', () => { }); }); -describe('buildVerifyContextFromParts — @path derivation matches the signer', () => { +describe('buildVerifyContextFromParts: @path derivation matches the signer', () => { const parts = (url: string, host = 'wine.example') => buildVerifyContextFromParts({ method: 'POST', url, headers: { host, 'agent-identity': 'a.b.c', 'signature-input': 'si', signature: 'sig' }, }); diff --git a/tests/aip_types.test.ts b/tests/aip_types.test.ts index 796a7f0..977c7f4 100644 --- a/tests/aip_types.test.ts +++ b/tests/aip_types.test.ts @@ -36,7 +36,7 @@ describe('isAitShape', () => { }); }); -describe('validateAitPayload — required claims', () => { +describe('validateAitPayload: required claims', () => { it('accepts a well-formed payload', () => { const r = validateAitPayload(validPayload); expect(r.ok).toBe(true); @@ -78,7 +78,7 @@ describe('validateAitPayload — required claims', () => { }); }); -describe('validateAitPayload — human_confirmed requires auth.amr', () => { +describe('validateAitPayload: human_confirmed requires auth.amr', () => { it('rejects human_confirmed with no auth at all', () => { const r = validateAitPayload({ ...validPayload, trust_level: 'human_confirmed' }); expect(r).toEqual({ ok: false, reason: 'human_confirmed_without_amr' }); @@ -100,7 +100,7 @@ describe('validateAitPayload — human_confirmed requires auth.amr', () => { }); }); -describe('validateAitPayload — extension claims pass through', () => { +describe('validateAitPayload: extension claims pass through', () => { it('preserves identity + payment extension claims on success', () => { const withExtensions: AitPayload = { ...validPayload, diff --git a/tests/aip_verify.test.ts b/tests/aip_verify.test.ts index 946ddec..aaa2d9d 100644 --- a/tests/aip_verify.test.ts +++ b/tests/aip_verify.test.ts @@ -82,7 +82,7 @@ const signedCtx = async (token: string, signWith = agentPrivateJwk, signPub = ag const NOW = 1715400020; -describe('verifyAit — happy path', () => { +describe('verifyAit: happy path', () => { it('verifies a well-formed, signed AIT end to end', async () => { const token = await mintAit(); const ctx = await signedCtx(token); @@ -102,7 +102,7 @@ describe('verifyAit — happy path', () => { }); }); -describe('verifyAit — token presence', () => { +describe('verifyAit: token presence', () => { it('returns no_token when no Agent-Identity header', async () => { const r = await verifyAit( { ...REQ, agentIdentityHeaders: [], signatureInput: 'x', signature: 'y' }, @@ -120,7 +120,7 @@ describe('verifyAit — token presence', () => { expect(r).toEqual({ ok: false, reason: 'pop_signature_missing' }); }); - it('accepts a Bearer-prefixed Agent-Identity header — the signed component is the bare JWT', async () => { + it('accepts a Bearer-prefixed Agent-Identity header: the signed component is the bare JWT', async () => { const token = await mintAit(); // Sign over the BARE AIT (Bearer is transport the verifier strips before the crypto), then // present it WITH a Bearer prefix; edge and API both reconstruct over the bare JWT. @@ -130,7 +130,7 @@ describe('verifyAit — token presence', () => { }); }); -describe('verifyAit — issuer + key', () => { +describe('verifyAit: issuer + key', () => { it('rejects an untrusted issuer', async () => { const token = await mintAit({ iss: 'https://evil.com' }); const ctx = await signedCtx(token); @@ -146,7 +146,7 @@ describe('verifyAit — issuer + key', () => { }); }); -describe('verifyAit — signature + expiry', () => { +describe('verifyAit: signature + expiry', () => { it('rejects an AIT signed by a different IdP key (idp_signature_invalid)', async () => { // JWKS serves an unrelated key under the same kid → IdP sig fails. const other = await generateKeyPair('EdDSA', { crv: 'Ed25519', extractable: true }); @@ -182,7 +182,7 @@ describe('verifyAit — signature + expiry', () => { it('rejects an AIT whose lifetime exceeds the 300s edge ceiling (lowered from 3600)', async () => { // A 600s-lifetime AIT (under the old 3600 default, over the new 300) is now rejected at the edge, - // matching the authoritative API verifier. The PoP is fresh and exp is ahead of now — only the + // matching the authoritative API verifier. The PoP is fresh and exp is ahead of now: only the // exp-iat span is the problem. const token = await mintAit({ iat: NOW - 10, exp: NOW + 590 }); // 600s lifetime > 300s const ctx = await signedCtx(token); @@ -217,7 +217,7 @@ describe('verifyAit — signature + expiry', () => { }); }); -describe('verifyAit — claim contract', () => { +describe('verifyAit: claim contract', () => { it('rejects a token that is not AIT-shaped (no agent claim)', async () => { const token = await mintAit({ omitAgent: true }); const ctx = await signedCtx(token); @@ -233,7 +233,7 @@ describe('verifyAit — claim contract', () => { }); }); -describe('verifyAit — proof of possession', () => { +describe('verifyAit: proof of possession', () => { it('rejects when the request is signed by a key other than cnf.jwk (pop_signature_invalid)', async () => { // AIT binds agentPublicJwk, but we sign the request with a different key. const other = await generateKeyPair('EdDSA', { crv: 'Ed25519', extractable: true }); @@ -256,7 +256,7 @@ describe('verifyAit — proof of possession', () => { it('rejects (does not throw on) an AIT bound to a P-256 cnf key', async () => { // The PoP verifier is Ed25519-only. A structurally-valid, JWT-valid AIT whose cnf is a P-256 // EC key must return a typed failure (caught by the cnf-key-type guard), NOT crash the gate - // with an uncaught importJWK throw. Sign the request with the normal Ed25519 agent key — the + // with an uncaught importJWK throw. Sign the request with the normal Ed25519 agent key: the // request signature is irrelevant because the verifier rejects on the cnf key type first. const ec = await generateKeyPair('ES256', { extractable: true }); const ecPub = await exportJWK(ec.publicKey); @@ -276,7 +276,7 @@ describe('verifyAit — proof of possession', () => { }); }); -describe('verifyAit — multiple AITs', () => { +describe('verifyAit: multiple AITs', () => { it('verifies when one of several Agent-Identity headers is valid and matches the request signature', async () => { const good = await mintAit(); const badIssuer = await mintAit({ iss: 'https://evil.com' }); @@ -292,7 +292,7 @@ describe('verifyAit — multiple AITs', () => { }); }); -describe('verifyAit — defense sanity (importJWK round-trip)', () => { +describe('verifyAit: defense sanity (importJWK round-trip)', () => { it('the cnf key returned can be imported (well-formed JWK)', async () => { const token = await mintAit(); const ctx = await signedCtx(token); diff --git a/tests/build_verification_required_body.test.ts b/tests/build_verification_required_body.test.ts index 3779141..f723950 100644 --- a/tests/build_verification_required_body.test.ts +++ b/tests/build_verification_required_body.test.ts @@ -1,5 +1,5 @@ /** - * Tests for `buildVerificationRequiredBody` — the canonical body builder for + * Tests for `buildVerificationRequiredBody`: the canonical body builder for * `identity_verification_required` denials. Collapses the per-merchant body * mapping into one call (verify_url / session_id / poll_secret / poll_url / * agent_instructions spread from the reason; merchant supplies message + diff --git a/tests/cache.test.ts b/tests/cache.test.ts index 176dfc0..2bddc93 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -94,7 +94,7 @@ describe('TTLCache', () => { vi.advanceTimersByTime(800); cache.set('key', 'second'); vi.advanceTimersByTime(800); - // 1600 ms total — original would have expired, but we reset at 800 + // 1600 ms total: original would have expired, but we reset at 800 expect(cache.get('key')).toBe('second'); }); }); diff --git a/tests/challenge/how_to_pay.test.ts b/tests/challenge/how_to_pay.test.ts index 6e4b9b2..4b3130d 100644 --- a/tests/challenge/how_to_pay.test.ts +++ b/tests/challenge/how_to_pay.test.ts @@ -63,7 +63,7 @@ describe('buildHowToPay', () => { it('builds stripe entry with link-cli commands when profileId set + amount under cap', () => { const block = buildHowToPay({ ...baseInput, - totalUsd: 100, // 10000 cents — well under $500 cap + totalUsd: 100, // 10000 cents: well under $500 cap rails: { stripe: { profileId: 'acct_test_123', productName: 'Cabernet 2021' } }, }); expect(block.stripe!.command_link_cli).toBeDefined(); @@ -75,7 +75,7 @@ describe('buildHowToPay', () => { it('emits stripe note (not link-cli commands) when amount over $500 cap', () => { const block = buildHowToPay({ ...baseInput, - totalUsd: 1000, // 100000 cents — over cap + totalUsd: 1000, // 100000 cents: over cap rails: { stripe: { profileId: 'acct_test_123' } }, }); expect(block.stripe!.command_link_cli).toBeUndefined(); diff --git a/tests/checkout.test.ts b/tests/checkout.test.ts index f83c662..47e4849 100644 --- a/tests/checkout.test.ts +++ b/tests/checkout.test.ts @@ -29,10 +29,10 @@ function req(overrides: Partial = {}): CheckoutRequest { } // ───────────────────────────────────────────────────────────────────────────── -// 402 emit — every rail combination +// 402 emit: every rail combination // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — 402 emit by rail combination', () => { +describe('Checkout: 402 emit by rail combination', () => { it('x402-only, no MPP, no identity (API seller pattern)', async () => { const checkout = new Checkout({ rails: { x402_base: { recipient: '0xTREASURY' } as X402BaseRailSpec }, @@ -76,7 +76,7 @@ describe('Checkout — 402 emit by rail combination', () => { // MPP compose path // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — composeMppx hook', () => { +describe('Checkout: composeMppx hook', () => { it('200 from compose runs onSettled and returns success', async () => { const onSettled = vi.fn().mockResolvedValue(null); const composeMppx = vi.fn( @@ -307,7 +307,7 @@ describe('Checkout — composeMppx hook', () => { // Custom hooks // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — custom hooks', () => { +describe('Checkout: custom hooks', () => { it('computePricing can branch on identity', async () => { const price = (ctx: CheckoutContext): PricingResult => { if (ctx.request.assess?.identity_status === 'verified') { @@ -359,7 +359,7 @@ describe('Checkout — custom hooks', () => { // Stripe SPT $0.50 minimum auto-drop on emit_402 // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — stripe rail auto-drop on emit_402', () => { +describe('Checkout: stripe rail auto-drop on emit_402', () => { it('drops stripe from accepted_methods when amountUsd < $0.50', async () => { const checkout = new Checkout({ rails: { @@ -395,7 +395,7 @@ describe('Checkout — stripe rail auto-drop on emit_402', () => { // Init guards // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — init guards', () => { +describe('Checkout: init guards', () => { it('x402Server requires an X402BaseRailSpec in rails["x402_base"]', () => { expect( () => @@ -414,7 +414,7 @@ describe('Checkout — init guards', () => { // Solana rail dispatch (smoke test that Solana specs survive _emit_402 path) // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — Solana rail compatibility', () => { +describe('Checkout: Solana rail compatibility', () => { it('emits 402 with Solana MPP rail in accepted_methods', async () => { const checkout = new Checkout({ rails: { @@ -434,10 +434,10 @@ describe('Checkout — Solana rail compatibility', () => { }); // ───────────────────────────────────────────────────────────────────────────── -// railsKeyForMppxMethod — maps mppx credential method → merchant rails-dict key +// railsKeyForMppxMethod: maps mppx credential method → merchant rails-dict key // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — railsKeyForMppxMethod', () => { +describe('Checkout: railsKeyForMppxMethod', () => { const buildCheckout = (rails: Record) => new Checkout({ rails: rails as Parameters[0]['rails'], @@ -515,10 +515,10 @@ describe('Checkout — railsKeyForMppxMethod', () => { }); // ───────────────────────────────────────────────────────────────────────────── -// handleMppx — railKey is derived from receipt method when available +// handleMppx: railKey is derived from receipt method when available // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — MPP railKey end-to-end derivation', () => { +describe('Checkout: MPP railKey end-to-end derivation', () => { const buildCheckout = ( composeMppx: (ctx: CheckoutContext) => Promise, onSettled: (ctx: CheckoutContext, outcome: { railKey?: string }) => Promise>, @@ -630,12 +630,12 @@ describe('Checkout — MPP railKey end-to-end derivation', () => { }); // ───────────────────────────────────────────────────────────────────────────── -// resolveRecipients error handling — mintRecipients throwing +// resolveRecipients error handling: mintRecipients throwing // CheckoutValidationError lands as a 4xx envelope; other errors rethrow // (covers the cross-bundle name-based catch added cross-bundle compat). // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — mintRecipients error handling', () => { +describe('Checkout: mintRecipients error handling', () => { it('converts CheckoutValidationError from mintRecipients into a 4xx envelope', async () => { const { CheckoutValidationError } = await import('../src/errors'); const checkout = new Checkout({ @@ -734,7 +734,7 @@ function x402Header(payTo: string, network = BIND_NETWORK): string { return Buffer.from(JSON.stringify(payload)).toString('base64'); } -describe('Checkout — x402 v2 resource metadata + extensions on the 402', () => { +describe('Checkout: x402 v2 resource metadata + extensions on the 402', () => { beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -800,7 +800,7 @@ describe('Checkout — x402 v2 resource metadata + extensions on the 402', () => }); }); -describe('Checkout — x402 payTo binding to the configured static recipient', () => { +describe('Checkout: x402 payTo binding to the configured static recipient', () => { beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -817,7 +817,7 @@ describe('Checkout — x402 payTo binding to the configured static recipient', ( it('REJECTS a settle whose payTo is NOT the configured recipient (hostile redirect)', async () => { const server = makeFakeX402Server(); const checkout = buildCheckout(server); - // Agent forges payTo = their own wallet (a VALID EVM address, so it clears the shape check — + // Agent forges payTo = their own wallet (a VALID EVM address, so it clears the shape check: // the rejection is specifically the recipient bind, not address validation). The permissive // default must NOT apply to a static-recipient merchant: rejected before the facilitator runs. const attacker = '0xbadbadbadbadbadbadbadbadbadbadbadbadbad0'; @@ -852,7 +852,7 @@ describe('Checkout — x402 payTo binding to the configured static recipient', ( url: 'https://api.example/purchase', computePricing: () => ({ amountUsd: 0.01 }), x402Server: server as never, - // Custom lookup: only the per-order minted address is valid — NOT the static rail recipient. + // Custom lookup: only the per-order minted address is valid: NOT the static rail recipient. isCachedAddress: (addr) => addr.toLowerCase() === seen.toLowerCase(), }); // The static rail recipient is now rejected (custom lookup overrides the static-set bind)... @@ -889,7 +889,7 @@ describe('Checkout — x402 payTo binding to the configured static recipient', ( }); }); -describe('Checkout — zero-settle carve-out gates on the real amount (sub-cent guard)', () => { +describe('Checkout: zero-settle carve-out gates on the real amount (sub-cent guard)', () => { beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -908,7 +908,7 @@ describe('Checkout — zero-settle carve-out gates on the real amount (sub-cent const result = await checkout.handle(req({ headers: { 'x-payment': x402Header(BIND_RECIPIENT) } })); expect(result.status).toBe(200); expect(result.settled).toBe(true); - // Real settle ran — NOT the zero-settle carve-out (which never calls the facilitator). + // Real settle ran: NOT the zero-settle carve-out (which never calls the facilitator). expect(server.settlePayment).toHaveBeenCalled(); }); @@ -929,12 +929,12 @@ describe('Checkout — zero-settle carve-out gates on the real amount (sub-cent }); // ───────────────────────────────────────────────────────────────────────────── -// Zero-settle carve-out — railKey resolves from the bound credential (no receipt +// Zero-settle carve-out: railKey resolves from the bound credential (no receipt // exists on the $0 path, so the receipt-method derivation can't run) and the // x402 branch verifies the credential before honoring the carve-out. // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — zero-settle railKey resolves from the bound credential', () => { +describe('Checkout: zero-settle railKey resolves from the bound credential', () => { beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -984,8 +984,8 @@ describe('Checkout — zero-settle railKey resolves from the bound credential', it('a Tempo credential resolves railKey to the tempo rail even when solana is declared first', async () => { // A hash/transaction credential at $0 means the agent signed against a // NONZERO quote that re-priced to $0 at settle (no-match flows). Upstream - // cannot settle those at $0, so the carve-out absorbs them — no compose - // call, no charge — and railKey derives from the recovered signer network, + // cannot settle those at $0, so the carve-out absorbs them: no compose + // call, no charge: and railKey derives from the recovered signer network, // order-independent of rail declaration. let observed: { railKey?: string; signerNetwork?: string | null } | undefined; const composeMppx = vi.fn(async (): Promise => ({ status: 200, raw: {} })); @@ -1060,7 +1060,7 @@ describe('Checkout — zero-settle railKey resolves from the bound credential', it('a token-style (JWT-shaped) credential at $0 keeps the carve-out', async () => { // Stripe SPT and other token credentials pass the shape gate but have no - // $0 settle semantics upstream — carve-out, null signer, no compose call. + // $0 settle semantics upstream: carve-out, null signer, no compose call. let observed: { railKey?: string; signerNetwork?: string | null } | undefined; const composeMppx = vi.fn(async (): Promise => ({ status: 200, raw: {} })); const checkout = buildZeroCheckout( @@ -1101,7 +1101,7 @@ describe('Checkout — zero-settle railKey resolves from the bound credential', }); }); -describe('Checkout — zero-settle x402 branch verifies the credential first', () => { +describe('Checkout: zero-settle x402 branch verifies the credential first', () => { beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); }); @@ -1150,7 +1150,7 @@ describe('Checkout — zero-settle x402 branch verifies the credential first', ( // stripping); the junk credential is never settled. // ───────────────────────────────────────────────────────────────────────────── -describe('Checkout — credential shape gate (pre-hook)', () => { +describe('Checkout: credential shape gate (pre-hook)', () => { beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); afterEach(() => { vi.unstubAllEnvs(); vi.restoreAllMocks(); }); diff --git a/tests/checkout_aip.test.ts b/tests/checkout_aip.test.ts index b6d9370..f01f6aa 100644 --- a/tests/checkout_aip.test.ts +++ b/tests/checkout_aip.test.ts @@ -11,7 +11,7 @@ * invalid cases, which fail before any JWKS fetch (so no real crypto / network is needed). * * The gate runs only on the settle leg (a payment credential attached), so each request carries - * an `x-payment` header — otherwise `handle` treats it as anonymous discovery and emits a 402. + * an `x-payment` header: otherwise `handle` treats it as anonymous discovery and emits a 402. */ import { SignJWT, exportJWK, generateKeyPair, type JWK } from 'jose'; import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; @@ -88,7 +88,7 @@ describe('Checkout gate × AIP', () => { it('does not engage AIP when gate.aip is unset, even if an Agent-Identity header is sent', async () => { // No aip config → the Agent-Identity header is ignored. The gate proceeds to the // normal assess path; with a fake API key the SDK call fails and surfaces as a - // non-problem+json denial — the key assertion is that it is NOT the AIP problem body. + // non-problem+json denial: the key assertion is that it is NOT the AIP problem body. const res = await makeCheckout().handle(req({ 'agent-identity': 'eyJhbGciOiJFZERTQSJ9.e30.sig' })); expect(res.headers['content-type']).not.toBe('application/problem+json'); }); @@ -130,7 +130,7 @@ describe('Checkout gate × AIP', () => { // block stubs global fetch to serve the issuer's JWKS. It exercises the no-apiKey offline path: // a verified AIT through a gate that declares compliance policy but has no apiKey cannot be // policy-evaluated (no /v1/assess), so it must FAIL CLOSED rather than allow a non-compliant id. -describe('Checkout gate × AIP — offline (no apiKey) policy enforcement', () => { +describe('Checkout gate × AIP: offline (no apiKey) policy enforcement', () => { const ISS = 'https://issuer.example'; const KID = 'partner-key'; let idpPrivate: CryptoKey; @@ -209,7 +209,7 @@ describe('Checkout gate × AIP — offline (no apiKey) policy enforcement', () = rails: { x402_base: { recipient: '0xT', network: 'eip155:8453' } as X402BaseRailSpec }, url: 'https://wine.example/purchase', computePricing: () => ({ amountUsd: 50 }), - gate: { aip: { trustedIssuers: [ISS], ...aipExtra } }, // NO apiKey — trust check runs pre-policy + gate: { aip: { trustedIssuers: [ISS], ...aipExtra } }, // NO apiKey: trust check runs pre-policy }); it('denies weak_auth (403 + required_trust_level) when trust_level is below the gate requirement', async () => { @@ -243,7 +243,7 @@ describe('Checkout gate × AIP — offline (no apiKey) policy enforcement', () = // FAILS CLOSED (`aip_policy_requires_api_key`); a request whose effective policy is EMPTY passes // the gate (returns null → reaches x402 settle → facilitator-init throws on the JWKS-only stub). // So "fails closed" = policy was applied to this issuer; "throws at settle" = policy was empty. -describe('Checkout gate × AIP — issuer-conditional policy', () => { +describe('Checkout gate × AIP: issuer-conditional policy', () => { const PARTNER = 'https://issuer.example'; const OURS = 'https://www.agentscore.com'; const keys: Record = {}; @@ -310,7 +310,7 @@ describe('Checkout gate × AIP — issuer-conditional policy', () => { expect((res.body as { error?: { code?: string } }).error?.code).toBe('aip_policy_requires_api_key'); }); - it('applies the relaxed override to the named partner issuer — still policy-bearing → fails closed (not silently allowed)', async () => { + it('applies the relaxed override to the named partner issuer: still policy-bearing → fails closed (not silently allowed)', async () => { // Relaxed ≠ empty: The partner still requires KYC + 21, so a no-apiKey gate still can't evaluate it // and fails closed. This proves the override is policy-BEARING, not a bypass. const res = await gate().handle(await signedReqFrom(PARTNER, { id_verified: true, age_over_21: true })); @@ -341,7 +341,7 @@ describe('Checkout gate × AIP — issuer-conditional policy', () => { computePricing: () => ({ amountUsd: 50 }), gate: { requireKyc: true, requireSanctionsClear: true, minAge: 21, - // key has a trailing slash; verified iss is 'https://issuer.example' — must still match. + // key has a trailing slash; verified iss is 'https://issuer.example': must still match. aip: { trustedIssuers: [PARTNER, OURS], issuerPolicies: { 'https://issuer.example/': {} } }, }, }); diff --git a/tests/checkout_aip_forward.test.ts b/tests/checkout_aip_forward.test.ts index 753e2a9..4201c20 100644 --- a/tests/checkout_aip_forward.test.ts +++ b/tests/checkout_aip_forward.test.ts @@ -1,5 +1,5 @@ /** - * Checkout × AIP — assess forwarding contract. + * Checkout × AIP: assess forwarding contract. * * After the edge fail-fast verify passes, the gate must forward BOTH the `aip_token` AND the * RFC 9421 signature material to `/v1/assess`, so the API (the authoritative verifier) re-checks @@ -105,7 +105,7 @@ async function signedReq(): Promise { }; } -describe('Checkout × AIP — assess forwarding', () => { +describe('Checkout × AIP: assess forwarding', () => { it('forwards aip_token + the RFC 9421 signature material to /v1/assess', async () => { const checkout = new Checkout({ rails: { x402_base: { recipient: '0xT', network: 'eip155:8453' } as X402BaseRailSpec }, @@ -127,11 +127,11 @@ describe('Checkout × AIP — assess forwarding', () => { }); }); -describe('Checkout × AIP — policy-deny superset body', () => { +describe('Checkout × AIP: policy-deny superset body', () => { it('emits the RFC 9457 + AgentScore superset (problem+json) when /v1/assess denies a verified AIT', async () => { // A verified AIT that /v1/assess then DENIES on compliance (e.g. sanctions). The body must be // BOTH schemes: the AgentScore `{ error.code, agent_instructions, reasons }` AND the spec's - // `type: urn:aip:error:insufficient_claims` / status 403 — content-negotiated as problem+json. + // `type: urn:aip:error:insufficient_claims` / status 403: content-negotiated as problem+json. assessState.response = { decision: 'deny', decision_reasons: ['sanctions_flagged'] }; const checkout = new Checkout({ rails: { x402_base: { recipient: '0xT', network: 'eip155:8453' } as X402BaseRailSpec }, @@ -150,7 +150,7 @@ describe('Checkout × AIP — policy-deny superset body', () => { expect((res.body as { detail?: string }).detail).toContain('sanctions_flagged'); // Escalation hint derived from the gate's effective policy. expect((res.body as { required_claims?: string[] }).required_claims).toEqual(['sanctions_clear', 'age_over_21']); - // Rich AgentScore scheme preserved verbatim — still the source of truth for the agent. + // Rich AgentScore scheme preserved verbatim: still the source of truth for the agent. expect((res.body as { error?: { code?: string } }).error?.code).toBe('wallet_not_trusted'); expect((res.body as { reasons?: string[] }).reasons).toEqual(['sanctions_flagged']); expect((res.body as { agent_instructions?: string }).agent_instructions).toBeTruthy(); diff --git a/tests/checkout_compute_first_settle.test.ts b/tests/checkout_compute_first_settle.test.ts index f2fc18c..ff92faa 100644 --- a/tests/checkout_compute_first_settle.test.ts +++ b/tests/checkout_compute_first_settle.test.ts @@ -63,7 +63,7 @@ function makeX402PaymentHeader(network = X402_NETWORK, payTo = X402_PAY_TO): str return Buffer.from(JSON.stringify(payload)).toString('base64'); } -describe('computeFirstCheckout — x402 settle path', () => { +describe('computeFirstCheckout: x402 settle path', () => { // computeFirstCheckout enforces wallet OFAC SDN before settle (matches // Checkout's `runWalletSanctionsOnly`). Stub the env to opt these tests into // the "no API key → log+skip" path so the focus stays on x402 settle, not on @@ -227,7 +227,7 @@ describe('computeFirstCheckout — x402 settle path', () => { }); it('ACCEPTS an x402 settle whose payTo equals the advertised recipient', async () => { - // Control case: the same flow with the honest payTo settles normally — the bind is exact-match, + // Control case: the same flow with the honest payTo settles normally: the bind is exact-match, // not a blanket rejection. const cache = createQuoteCache(); const fakeServer = makeFakeX402Server(); @@ -258,7 +258,7 @@ describe('computeFirstCheckout — x402 settle path', () => { }); }); -describe('computeFirstCheckout — MPP settle path', () => { +describe('computeFirstCheckout: MPP settle path', () => { // Same env-stub pattern as x402 settle (wallet OFAC path needs an API key // or a mocked SDK; we cover the actual OFAC path in seamless-helpers). beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); @@ -335,7 +335,7 @@ describe('computeFirstCheckout — MPP settle path', () => { }, onSettled: async () => { onSettledFired = true; - throw new Error('intentional onSettled error — should be caught + logged'); + throw new Error('intentional onSettled error: should be caught + logged'); }, }); @@ -353,7 +353,7 @@ describe('computeFirstCheckout — MPP settle path', () => { }, body: JSON.stringify(body), })); - // Should succeed (200) even though onSettled threw — error is caught + logged. + // Should succeed (200) even though onSettled threw: error is caught + logged. expect(settleRes.status).toBe(200); expect(onSettledFired).toBe(true); }); @@ -530,7 +530,7 @@ describe('computeFirstCheckout — MPP settle path', () => { runWork: async () => ({ resultCount: 1, body: {} }), composeMppx: async (ctx) => { if (!ctx.request.headers.get('authorization')) return { status: 402, headers: {} }; - // No signerAddress in result — helper falls back to extractPaymentSigner. + // No signerAddress in result: helper falls back to extractPaymentSigner. return { status: 200, raw: { receipt: { method: 'tempo' } } }; }, }); @@ -548,7 +548,7 @@ describe('computeFirstCheckout — MPP settle path', () => { it('MPP signer resolves from the Authorization DID when composeMppx omits signerAddress (onSettled gets signer + txHash)', async () => { // Mock mppx so extractPaymentSigner's MPP-credential path returns a real - // EVM signer from the `did:pkh:eip155` source — exercising the line-559 + // EVM signer from the `did:pkh:eip155` source: exercising the line-559 // `.then(s => s ? {...} : undefined)` truthy branch + the onSettled // signer/txHash spreads (572-573) that the signerAddress-supplied tests skip. vi.doMock('mppx', () => ({ @@ -571,7 +571,7 @@ describe('computeFirstCheckout — MPP settle path', () => { runWork: async () => ({ resultCount: 1, body: { matches: ['a'] } }), composeMppx: async (ctx) => { if (!ctx.request.headers.get('authorization')) return { status: 402, headers: {} }; - // No signerAddress — forces the extractPaymentSigner fallback. + // No signerAddress: forces the extractPaymentSigner fallback. return { status: 200, raw: { receipt: { method: 'tempo' } }, txHash: 'pi_did_999' }; }, onSettled: async (ctx) => { @@ -660,7 +660,7 @@ describe('computeFirstCheckout — MPP settle path', () => { }); }); -describe('computeFirstCheckout — wallet OFAC enforcement (always-on default)', () => { +describe('computeFirstCheckout: wallet OFAC enforcement (always-on default)', () => { it('denies on signer-sanctions outcome before the rail handler fires', async () => { vi.doMock('../src/core', async () => { const real = await vi.importActual('../src/core'); @@ -712,7 +712,7 @@ describe('computeFirstCheckout — wallet OFAC enforcement (always-on default)', it('no AGENTSCORE_API_KEY: logs warn once, skips OFAC, x402 settle proceeds', async () => { vi.stubEnv('AGENTSCORE_API_KEY', ''); const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); - // Reset the shared warn-once flag — earlier tests in this run may have + // Reset the shared warn-once flag: earlier tests in this run may have // tripped it (see src/_warnings.ts). const { _resetWarnedNoApiKey } = await import('../src/_warnings'); _resetWarnedNoApiKey(); diff --git a/tests/core_signer_cache.test.ts b/tests/core_signer_cache.test.ts index eea39ae..12ee460 100644 --- a/tests/core_signer_cache.test.ts +++ b/tests/core_signer_cache.test.ts @@ -4,14 +4,14 @@ * The assess response cache was keyed by IDENTITY only (claimed wallet / operator_token / aip * hash); the payment SIGNER was not part of the key. So a 2nd request inside `cacheSeconds` that * claimed the same wallet but signed with a DIFFERENT wallet hit the cached decision and returned - * the first signer's stale `signer_match: pass` / `signer_sanctions: clear` — the API never + * the first signer's stale `signer_match: pass` / `signer_sanctions: clear`: the API never * re-screened the new signer. Variant: a sanctioned 2nd signer would settle on the cached `clear`. * * Fix: fold the normalized signer (address + network) into the response cache key whenever a * signer is present, so a different signer is a cache MISS and gets re-screened. These tests pin * that contract by stubbing `global.fetch` (the real SDK + real core run on top) and driving two * different signers for one claimed identity. The verdict is read off each `evaluate` call's - * RETURN VALUE (`outcome.signerVerdict`) — request-scoped, so one request can't read another's. + * RETURN VALUE (`outcome.signerVerdict`): request-scoped, so one request can't read another's. */ import { afterEach, describe, expect, it, vi } from 'vitest'; import { createAgentScoreCore } from '../src/core'; @@ -23,7 +23,7 @@ const CLAIMED = '0xAAAaaAAaaAAAaaAAAaaAAAaaAAAaaAAAaaAAAaaA'; // Two distinct EVM signers for the same claimed wallet. const SIGNER_OK: PaymentSigner = { address: '0xB111111111111111111111111111111111111111', network: 'evm' }; const SIGNER_MISMATCH: PaymentSigner = { address: '0xC222222222222222222222222222222222222222', network: 'evm' }; -// The repo's canonical OFAC test SDN shape (ofac_label 'ETH', sdn_uid 19011) — see hono.test.ts. +// The repo's canonical OFAC test SDN shape (ofac_label 'ETH', sdn_uid 19011): see hono.test.ts. const SIGNER_SDN: PaymentSigner = { address: '0xD333333333333333333333333333333333333333', network: 'evm' }; interface AssessBody { @@ -93,7 +93,7 @@ afterEach(() => { vi.restoreAllMocks(); }); -describe('core — signer is part of the assess cache key (anti cache-poisoning)', () => { +describe('core: signer is part of the assess cache key (anti cache-poisoning)', () => { it('re-screens a DIFFERENT signer within the cache window (does NOT return the first signer’s verdict)', async () => { const { calls } = stubAssessFetch(); const core = createAgentScoreCore({ apiKey: API_KEY, cacheSeconds: 300 }); @@ -117,11 +117,11 @@ describe('core — signer is part of the assess cache key (anti cache-poisoning) if (v2?.signer_match?.kind === 'wallet_signer_mismatch') { expect(v2.signer_match.actualSigner).toBe(SIGNER_MISMATCH.address.toLowerCase()); } - // The FIRST request's outcome still independently holds SIGNER_OK's `pass` — verdicts are + // The FIRST request's outcome still independently holds SIGNER_OK's `pass`: verdicts are // per-request (ride the return value), so r2's re-screen can't retroactively mutate r1. expect(r1.signerVerdict?.signer_match?.kind).toBe('pass'); // The decision still surfaces as an allow from /v1/assess here (mismatch is enforced by the - // adapter via the verdict, not by flipping the decision) — the load-bearing assertion is that + // adapter via the verdict, not by flipping the decision): the load-bearing assertion is that // the verdict is the new signer's, not the cached one. expect(r2.kind).toBe('allow'); }); @@ -152,7 +152,7 @@ describe('core — signer is part of the assess cache key (anti cache-poisoning) const core = createAgentScoreCore({ apiKey: API_KEY, cacheSeconds: 300 }); await core.evaluate({ address: CLAIMED }, undefined, SIGNER_OK); await core.evaluate({ address: CLAIMED }, undefined, SIGNER_OK); - expect(calls).toHaveLength(1); // identical signer is a hit — no spurious re-screen + expect(calls).toHaveLength(1); // identical signer is a hit: no spurious re-screen }); it('same identity, NO signer, twice → cache HIT (identity-only key unchanged)', async () => { diff --git a/tests/default_rails_network_derivation.test.ts b/tests/default_rails_network_derivation.test.ts index 0300b81..69be0fd 100644 --- a/tests/default_rails_network_derivation.test.ts +++ b/tests/default_rails_network_derivation.test.ts @@ -11,7 +11,7 @@ import { buildDefaultCheckoutRails } from '../src/payment/default_rails'; import { networks } from '../src/payment/networks'; import { USDC } from '../src/payment/usdc'; -describe('buildDefaultCheckoutRails — x402Base network/token derivation', () => { +describe('buildDefaultCheckoutRails: x402Base network/token derivation', () => { it('Sepolia override flips chainId + token to Sepolia USDC even when token not pinned', () => { const rails = buildDefaultCheckoutRails({ x402Base: { network: networks.base.sepolia.caip2 }, @@ -68,7 +68,7 @@ describe('buildDefaultCheckoutRails — x402Base network/token derivation', () = }); }); -describe('buildDefaultCheckoutRails — tempo testnet field-pinned branches', () => { +describe('buildDefaultCheckoutRails: tempo testnet field-pinned branches', () => { it('testnet: true with explicit token/chainId/network keeps the overrides (defined branches)', () => { const rails = buildDefaultCheckoutRails({ tempo: { @@ -91,7 +91,7 @@ describe('buildDefaultCheckoutRails — tempo testnet field-pinned branches', () }); }); -describe('buildDefaultCheckoutRails — solanaMpp network/mint derivation', () => { +describe('buildDefaultCheckoutRails: solanaMpp network/mint derivation', () => { it('devnet CAIP-2 flips mint to devnet USDC', () => { const rails = buildDefaultCheckoutRails({ solanaMpp: { network: networks.solana.devnet.caip2 }, diff --git a/tests/denial.test.ts b/tests/denial.test.ts index edeb22c..840d8dd 100644 --- a/tests/denial.test.ts +++ b/tests/denial.test.ts @@ -34,7 +34,7 @@ describe('FIXABLE_DENIAL_REASONS / isFixableDenial', () => { }); it('jurisdiction_restricted is UNFIXABLE', () => { - // The API only emits jurisdiction_restricted AFTER KYC is verified — meaning the + // The API only emits jurisdiction_restricted AFTER KYC is verified: meaning the // user's KYC'd country is in the merchant's blocked list. Re-doing KYC won't change // the country, same shape as sanctions_flagged / age_insufficient. expect(FIXABLE_DENIAL_REASONS.has('jurisdiction_restricted')).toBe(false); diff --git a/tests/discovery/probe.test.ts b/tests/discovery/probe.test.ts index 703587c..f7ff049 100644 --- a/tests/discovery/probe.test.ts +++ b/tests/discovery/probe.test.ts @@ -137,7 +137,7 @@ describe('sampleX402AcceptForNetwork', () => { }); }); -describe('buildDiscoveryProbeResponse — x402Sample branch', () => { +describe('buildDiscoveryProbeResponse: x402Sample branch', () => { it('emits payment-required header + body accepts when networks shorthand is supplied', () => { const probe = buildDiscoveryProbeResponse({ realm: 'merchant.example', diff --git a/tests/edge-cases.test.ts b/tests/edge-cases.test.ts index 4ab9580..0a6728c 100644 --- a/tests/edge-cases.test.ts +++ b/tests/edge-cases.test.ts @@ -284,7 +284,7 @@ describe('untyped SDK error fallbacks (defensive paths)', () => { async function gateWithAssessThrow(thrown: unknown, opts?: { failOpen?: boolean }) { // Mock the SDK so `assess` throws a raw error the SDK normally would have - // wrapped — exercising core's defensive untyped fallbacks. Keep the real + // wrapped: exercising core's defensive untyped fallbacks. Keep the real // error classes so the typed `instanceof` checks above still behave. vi.doMock('@agent-score/sdk', async () => { const real = await vi.importActual('@agent-score/sdk'); @@ -366,8 +366,8 @@ describe('invalid wallet header edge cases', () => { }); it('missing_identity body carries probe_identity_then_session instructions', async () => { - // Bare bootstrap denial describes the full probe strategy — wallet-first on - // signing rails, fall back to stored opc_..., fall back to session flow — so + // Bare bootstrap denial describes the full probe strategy: wallet-first on + // signing rails, fall back to stored opc_..., fall back to session flow: so // agents without durable memory recover in at most one extra round trip. const mw = agentscoreGate({ apiKey: API_KEY }); const req = makeReq(); @@ -771,7 +771,7 @@ describe('compliance options edge cases', () => { }); }); -describe('evaluate() — 401 passthrough edge cases', () => { +describe('evaluate(): 401 passthrough edge cases', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -854,7 +854,7 @@ describe('evaluate() — 401 passthrough edge cases', () => { expect(status).toHaveBeenCalledWith(503); expect(json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.objectContaining({ code: 'api_error' }) })); - // Used to be silent — schema drift would mask itself for hours. Now logs the + // Used to be silent: schema drift would mask itself for hours. Now logs the // unknown code so ops notice without crashing the request. expect(warn).toHaveBeenCalledWith(expect.stringContaining('something_unknown')); warn.mockRestore(); @@ -863,7 +863,7 @@ describe('evaluate() — 401 passthrough edge cases', () => { it('emits invalid_credential 403 (not retry-suggesting api_error) for permanent token failures', async () => { // The API returns 401 invalid_credential when the token doesn't exist at all // (typo, never minted). Distinct from token_expired (which carries an auto-session) - // — invalid_credential has no recovery payload, the agent must switch tokens or + // invalid_credential has no recovery payload, the agent must switch tokens or // restart the session flow. Used to fall through to api_error → 503 retry which // looped forever on a permanent state. const apiBody = { error: { code: 'invalid_credential', message: 'Operator credential not found' } }; @@ -883,7 +883,7 @@ describe('evaluate() — 401 passthrough edge cases', () => { expect(status).toHaveBeenCalledWith(401); const body = json.mock.calls[0]![0] as Record; expect(body.error.code).toBe('invalid_credential'); - // No session fields — API didn't mint one for this case. Agent gets actionable copy. + // No session fields: API didn't mint one for this case. Agent gets actionable copy. expect(body).not.toHaveProperty('verify_url'); expect(body).not.toHaveProperty('session_id'); expect(body.agent_instructions).toBeDefined(); diff --git a/tests/express.test.ts b/tests/express.test.ts index b9d1cd8..29ae16a 100644 --- a/tests/express.test.ts +++ b/tests/express.test.ts @@ -50,7 +50,7 @@ function mockFetchOk(body: unknown): void { } function mockFetchStatus(status: number, errorCode?: string): void { - // SDK retries once on 429 — use mockResolvedValue so both attempts get the same body. + // SDK retries once on 429: use mockResolvedValue so both attempts get the same body. // Include retry-after: 0 so the SDK retry path doesn't wait. // Include error.code in the body when provided so SDK maps to a typed error subclass. const body = errorCode ? { error: { code: errorCode, message: 'mock' } } : {}; @@ -81,7 +81,7 @@ describe('agentscoreGate factory', () => { }); }); -describe('agentscoreGate middleware — missing wallet address', () => { +describe('agentscoreGate middleware: missing wallet address', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -111,7 +111,7 @@ describe('agentscoreGate middleware — missing wallet address', () => { }); }); -describe('agentscoreGate middleware — successful assessment', () => { +describe('agentscoreGate middleware: successful assessment', () => { beforeEach(() => { mockFetchOk(ALLOW_RESPONSE); }); @@ -133,7 +133,7 @@ describe('agentscoreGate middleware — successful assessment', () => { }); }); -describe('agentscoreGate middleware — denied assessment', () => { +describe('agentscoreGate middleware: denied assessment', () => { beforeEach(() => { mockFetchOk(DENY_RESPONSE); }); @@ -160,7 +160,7 @@ describe('agentscoreGate middleware — denied assessment', () => { }); }); -describe('agentscoreGate middleware — API error', () => { +describe('agentscoreGate middleware: API error', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -293,7 +293,7 @@ describe('agentscoreGate middleware — API error', () => { }); }); -describe('agentscoreGate middleware — 402 payment required', () => { +describe('agentscoreGate middleware: 402 payment required', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -325,7 +325,7 @@ describe('agentscoreGate middleware — 402 payment required', () => { }); }); -describe('agentscoreGate middleware — cache', () => { +describe('agentscoreGate middleware: cache', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -342,7 +342,7 @@ describe('agentscoreGate middleware — cache', () => { // fetch should have been called once expect(global.fetch).toHaveBeenCalledTimes(1); - // Second request — same wallet, no new fetch stub needed + // Second request: same wallet, no new fetch stub needed const req2 = makeReq(WALLET); const { res: res2 } = makeRes(); const next2 = makeNext(); @@ -377,7 +377,7 @@ describe('agentscoreGate middleware — cache', () => { }); -describe('agentscoreGate middleware — a response with no decision fails closed', () => { +describe('agentscoreGate middleware: a response with no decision fails closed', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -457,7 +457,7 @@ describe('agentscoreGate middleware — a response with no decision fails closed }); }); -describe('agentscoreGate middleware — a sent policy must come back evaluated', () => { +describe('agentscoreGate middleware: a sent policy must come back evaluated', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -543,7 +543,7 @@ describe('agentscoreGate middleware — a sent policy must come back evaluated', }); }); -describe('agentscoreGate middleware — chain option', () => { +describe('agentscoreGate middleware: chain option', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -580,7 +580,7 @@ describe('agentscoreGate middleware — chain option', () => { }); }); -describe('agentscoreGate middleware — policy fields in request body', () => { +describe('agentscoreGate middleware: policy fields in request body', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -603,7 +603,7 @@ describe('agentscoreGate middleware — policy fields in request body', () => { }); }); -describe('agentscoreGate middleware — custom onDenied', () => { +describe('agentscoreGate middleware: custom onDenied', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -681,7 +681,7 @@ describe('agentscoreGate middleware — custom onDenied', () => { }); }); -describe('agentscoreGate middleware — edge cases', () => { +describe('agentscoreGate middleware: edge cases', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -910,7 +910,7 @@ describe('agentscoreGate middleware — edge cases', () => { }); }); -describe('agentscoreGate middleware — verify_url and operator_verification in response', () => { +describe('agentscoreGate middleware: verify_url and operator_verification in response', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -1170,7 +1170,7 @@ describe('agentscoreGate middleware — verify_url and operator_verification in // Identity model: extractIdentity, operator token, header aliases // --------------------------------------------------------------------------- -describe('agentscoreGate — identity model', () => { +describe('agentscoreGate: identity model', () => { afterEach(() => { vi.restoreAllMocks(); }); @@ -1316,7 +1316,7 @@ describe('agentscoreGate — identity model', () => { // createSessionOnMissing // --------------------------------------------------------------------------- -describe('agentscoreGate middleware — createSessionOnMissing', () => { +describe('agentscoreGate middleware: createSessionOnMissing', () => { const SESSION_RESPONSE = { session_id: 'sess_abc123', verify_url: 'https://www.agentscore.com/verify/sess_abc123', @@ -1538,7 +1538,7 @@ describe('agentscoreGate middleware — createSessionOnMissing', () => { }); }); -describe('Express adapter — captureWallet', () => { +describe('Express adapter: captureWallet', () => { afterEach(() => vi.restoreAllMocks()); it('posts to /v1/credentials/wallets with operator_token + signer + network after gate ran', async () => { @@ -1595,7 +1595,7 @@ describe('Express adapter — captureWallet', () => { expect(body.idempotency_key).toBe('pi_abc'); }); - it('swallows capture failures silently — does not throw to the caller', async () => { + it('swallows capture failures silently: does not throw to the caller', async () => { global.fetch = vi.fn() .mockResolvedValueOnce({ ok: true, status: 200, json: vi.fn().mockResolvedValueOnce(ALLOW_RESPONSE) } as unknown as Response) .mockRejectedValueOnce(new Error('network down')); diff --git a/tests/extract_owner_scope.test.ts b/tests/extract_owner_scope.test.ts index 4b4dc04..c5e1af3 100644 --- a/tests/extract_owner_scope.test.ts +++ b/tests/extract_owner_scope.test.ts @@ -3,7 +3,7 @@ import { extractOwnerScope, hashOperatorToken } from '../src/identity/tokens'; // A real EIP-55 checksummed EVM address + its lowercase form. The stored `orders.wallet_address` // column persists the lowercased signer, so extractOwnerScope MUST lowercase the inbound -// X-Wallet-Address — otherwise a checksummed header misses its own order rows (404). +// X-Wallet-Address: otherwise a checksummed header misses its own order rows (404). const CHECKSUMMED = '0xeb2Ca790F72787c7e61bC6c861353a1e4ACDFCa5'; const LOWERCASED = CHECKSUMMED.toLowerCase(); @@ -24,7 +24,7 @@ describe('extractOwnerScope', () => { }); it('preserves a Solana base58 address verbatim (case-sensitive)', () => { - // Solana addresses are base58 and case-sensitive — normalization MUST NOT lowercase them. + // Solana addresses are base58 and case-sensitive: normalization MUST NOT lowercase them. const sol = 'DQyrAcCrDXQ7iiRTHtPhHkjFmh1mVGwXqUL9F4FUe9YN'; const scope = extractOwnerScope({ 'x-wallet-address': sol }); expect(scope.walletAddress).toBe(sol); diff --git a/tests/fastify.test.ts b/tests/fastify.test.ts index 9dcb555..523f492 100644 --- a/tests/fastify.test.ts +++ b/tests/fastify.test.ts @@ -47,7 +47,7 @@ function mockFetchOkWithQuota(body: unknown): void { } as unknown as Response); } -describe('Fastify adapter — identity extraction', () => { +describe('Fastify adapter: identity extraction', () => { afterEach(() => { vi.restoreAllMocks(); }); it('extracts wallet address from X-Wallet-Address header and attaches assess data to request', async () => { @@ -96,7 +96,7 @@ describe('Fastify adapter — identity extraction', () => { }); }); -describe('Fastify adapter — deny behavior', () => { +describe('Fastify adapter: deny behavior', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns 403 wallet_not_trusted with verify_url on policy deny', async () => { @@ -158,7 +158,7 @@ describe('Fastify adapter — deny behavior', () => { }); }); -describe('Fastify adapter — User-Agent', () => { +describe('Fastify adapter: User-Agent', () => { afterEach(() => { vi.restoreAllMocks(); }); it('sends canonical User-Agent by default', async () => { @@ -192,7 +192,7 @@ describe('Fastify adapter — User-Agent', () => { }); }); -describe('Fastify adapter — fail-open + session creation paths', () => { +describe('Fastify adapter: fail-open + session creation paths', () => { afterEach(() => { vi.restoreAllMocks(); }); it('allows through on 402 when failOpen is true', async () => { @@ -237,7 +237,7 @@ describe('Fastify adapter — fail-open + session creation paths', () => { }); }); -describe('Fastify adapter — error paths', () => { +describe('Fastify adapter: error paths', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns 403 payment_required on 402 assess response', async () => { @@ -339,7 +339,7 @@ describe('Fastify adapter — error paths', () => { }); }); -describe('Fastify conditional gate — settle-leg allow paths', () => { +describe('Fastify conditional gate: settle-leg allow paths', () => { afterEach(() => { vi.restoreAllMocks(); }); it('flows discovery legs (no payment header) straight through without calling assess', async () => { @@ -433,7 +433,7 @@ describe('Fastify conditional gate — settle-leg allow paths', () => { }); }); -describe('Fastify adapter — chain option', () => { +describe('Fastify adapter: chain option', () => { afterEach(() => { vi.restoreAllMocks(); }); it('forwards constructor chain to /v1/assess body', async () => { @@ -450,7 +450,7 @@ describe('Fastify adapter — chain option', () => { }); }); -describe('Fastify adapter — captureWallet', () => { +describe('Fastify adapter: captureWallet', () => { afterEach(() => vi.restoreAllMocks()); it('posts to /v1/credentials/wallets after the gate ran on an operator_token request', async () => { @@ -523,7 +523,7 @@ describe('Fastify adapter — captureWallet', () => { expect(body.idempotency_key).toBe('pi_abc'); }); - it('swallows capture failures silently — handler response unaffected', async () => { + it('swallows capture failures silently: handler response unaffected', async () => { global.fetch = vi.fn() .mockResolvedValueOnce({ ok: true, status: 200, json: vi.fn().mockResolvedValueOnce(ALLOW_RESPONSE) } as unknown as Response) .mockRejectedValueOnce(new Error('network down')); diff --git a/tests/forwarded_proto.test.ts b/tests/forwarded_proto.test.ts index 4217b40..d6dba84 100644 --- a/tests/forwarded_proto.test.ts +++ b/tests/forwarded_proto.test.ts @@ -39,7 +39,7 @@ describe('readForwardedProto', () => { }); }); -describe('computeFirstCheckout — resource.url honors X-Forwarded-Proto', () => { +describe('computeFirstCheckout: resource.url honors X-Forwarded-Proto', () => { const rails = { rails: { tempo: { diff --git a/tests/getSignerVerdict.test.ts b/tests/getSignerVerdict.test.ts index 9876663..f1e9ae7 100644 --- a/tests/getSignerVerdict.test.ts +++ b/tests/getSignerVerdict.test.ts @@ -1,5 +1,5 @@ /** - * Per-adapter coverage for `getSignerVerdict`: the verdict is REQUEST-SCOPED — it's stashed on + * Per-adapter coverage for `getSignerVerdict`: the verdict is REQUEST-SCOPED: it's stashed on * the per-request gate state (Hono `c`, Express `req`, Fastify `request`), NOT on the shared core. * `getSignerVerdict(ctx)` reads `state.signerVerdict` back. Returns `undefined` when the gate didn't * run (no state) or when the request carried no verdict (operator-token / discovery legs). The @@ -101,7 +101,7 @@ describe('fastify getSignerVerdict', () => { }); // --------------------------------------------------------------------------- -// Deny path — the verdict must be stashed BEFORE onDenied so a custom denial +// Deny path: the verdict must be stashed BEFORE onDenied so a custom denial // handler can read it via getSignerVerdict (e.g. to render the mismatch body). // --------------------------------------------------------------------------- diff --git a/tests/getSignerVerdict_race.test.ts b/tests/getSignerVerdict_race.test.ts index 6aeb473..8fa80c9 100644 --- a/tests/getSignerVerdict_race.test.ts +++ b/tests/getSignerVerdict_race.test.ts @@ -4,10 +4,10 @@ * The gate adapters share ONE `core` (and thus one assess client) across all requests. The signer * verdict used to live on a shared map keyed by claimed-ADDRESS only. Two concurrent requests that * claim the SAME wallet but sign with DIFFERENT signers both wrote that one slot; whichever assess - * resolved last won, so the other request's `getSignerVerdict()` read a sibling's verdict — e.g. a + * resolved last won, so the other request's `getSignerVerdict()` read a sibling's verdict: e.g. a * mismatched signer could read the honest signer's `pass`, defeating wallet-signer binding. * - * Fix: the verdict is request-scoped — it rides the `evaluate` return value and the adapter stashes + * Fix: the verdict is request-scoped: it rides the `evaluate` return value and the adapter stashes * it on the PER-REQUEST context (`c`), which `getSignerVerdict(c)` reads back. This test interleaves * two same-wallet/different-signer requests through one shared Hono gate and asserts each handler * reads its OWN verdict. A barrier holds both handlers until BOTH assess calls have resolved (so a @@ -76,7 +76,7 @@ function stubInterleavedAssess(gate: Promise): void { afterEach(() => { vi.restoreAllMocks(); }); -describe('signer-verdict slot race — concurrent same-wallet/different-signer requests', () => { +describe('signer-verdict slot race: concurrent same-wallet/different-signer requests', () => { it('each interleaved request reads its OWN signer verdict (not a sibling’s)', async () => { let releaseAssess: (() => void) | undefined; const assessGate = new Promise((res) => { releaseAssess = res; }); @@ -84,7 +84,7 @@ describe('signer-verdict slot race — concurrent same-wallet/different-signer r // Barrier: both handlers park here until BOTH have entered. Since a handler runs only AFTER its // gate evaluate (and thus its verdict stash) completes, the barrier guarantees both stashes have - // happened before either reads — exactly the window where a shared last-writer slot would leak. + // happened before either reads: exactly the window where a shared last-writer slot would leak. let entered = 0; let releaseBarrier: (() => void) | undefined; const barrier = new Promise((res) => { releaseBarrier = res; }); @@ -94,7 +94,7 @@ describe('signer-verdict slot race — concurrent same-wallet/different-signer r return barrier; }; - // ONE gate (one shared core) mounted on the app — the production sharing model. + // ONE gate (one shared core) mounted on the app: the production sharing model. const app = new Hono(); app.use('*', agentscoreGate({ apiKey: API_KEY })); app.get('/buy', async (c) => { @@ -116,7 +116,7 @@ describe('signer-verdict slot race — concurrent same-wallet/different-signer r const badBody = await badRes.json() as { kind: string; actual: string | null }; // The honest-signer request must read `pass`; the mismatched-signer request must read its OWN - // mismatch verdict (with ITS signer baked in) — never the sibling's. + // mismatch verdict (with ITS signer baked in): never the sibling's. expect(okBody.kind).toBe('pass'); expect(badBody.kind).toBe('wallet_signer_mismatch'); expect(badBody.actual).toBe(SIGNER_BAD.toLowerCase()); diff --git a/tests/hono.test.ts b/tests/hono.test.ts index 5f3b448..d3dc1b6 100644 --- a/tests/hono.test.ts +++ b/tests/hono.test.ts @@ -61,7 +61,7 @@ function mockFetchStatus(status: number, errorCode?: string): void { // Identity extraction // --------------------------------------------------------------------------- -describe('Hono adapter — identity extraction', () => { +describe('Hono adapter: identity extraction', () => { afterEach(() => { vi.restoreAllMocks(); }); it('extracts wallet address from X-Wallet-Address header', async () => { @@ -132,7 +132,7 @@ describe('Hono adapter — identity extraction', () => { // Context attachment + getAgentScoreData helper // --------------------------------------------------------------------------- -describe('Hono adapter — context attachment', () => { +describe('Hono adapter: context attachment', () => { afterEach(() => { vi.restoreAllMocks(); }); it('attaches assess data via c.set, retrievable by getAgentScoreData', async () => { @@ -165,7 +165,7 @@ describe('Hono adapter — context attachment', () => { // Deny behavior // --------------------------------------------------------------------------- -describe('Hono adapter — deny behavior', () => { +describe('Hono adapter: deny behavior', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns 403 wallet_not_trusted with verify_url on policy deny', async () => { @@ -240,7 +240,7 @@ describe('Hono adapter — deny behavior', () => { // createSessionOnMissing // --------------------------------------------------------------------------- -describe('Hono adapter — createSessionOnMissing', () => { +describe('Hono adapter: createSessionOnMissing', () => { afterEach(() => { vi.restoreAllMocks(); }); it('creates session and returns 403 identity_verification_required when identity missing', async () => { @@ -522,7 +522,7 @@ describe('Hono adapter — createSessionOnMissing', () => { // Fail-open // --------------------------------------------------------------------------- -describe('Hono adapter — fail-open', () => { +describe('Hono adapter: fail-open', () => { afterEach(() => { vi.restoreAllMocks(); }); it('passes through on 402 when failOpen is true', async () => { @@ -552,7 +552,7 @@ describe('Hono adapter — fail-open', () => { // User-Agent header // --------------------------------------------------------------------------- -describe('Hono adapter — User-Agent header', () => { +describe('Hono adapter: User-Agent header', () => { afterEach(() => { vi.restoreAllMocks(); }); it('sends User-Agent matching canonical package identifier', async () => { @@ -590,7 +590,7 @@ describe('Hono adapter — User-Agent header', () => { // Error paths: 402 payment_required + 500 api_error // --------------------------------------------------------------------------- -describe('Hono adapter — error paths', () => { +describe('Hono adapter: error paths', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns 403 payment_required on 402 assess response', async () => { @@ -709,7 +709,7 @@ describe('Hono adapter — error paths', () => { // chain= constructor option // --------------------------------------------------------------------------- -describe('Hono adapter — chain option', () => { +describe('Hono adapter: chain option', () => { afterEach(() => { vi.restoreAllMocks(); }); it('forwards constructor chain to /v1/assess body', async () => { @@ -743,7 +743,7 @@ describe('Hono adapter — chain option', () => { // captureWallet // --------------------------------------------------------------------------- -describe('Hono adapter — captureWallet', () => { +describe('Hono adapter: captureWallet', () => { afterEach(() => { vi.restoreAllMocks(); }); it('forwards idempotencyKey as snake_case idempotency_key in the body', async () => { @@ -840,10 +840,10 @@ describe('Hono adapter — captureWallet', () => { }); // --------------------------------------------------------------------------- -// getSignerVerdict — exercise core's projectSignerMatch + cache-read paths +// getSignerVerdict: exercise core's projectSignerMatch + cache-read paths // --------------------------------------------------------------------------- -describe('Hono adapter — getSignerVerdict projection', () => { +describe('Hono adapter: getSignerVerdict projection', () => { afterEach(() => { vi.restoreAllMocks(); }); it('projects signer_match.kind=pass with claimed/signer operators', async () => { diff --git a/tests/identity-helpers.test.ts b/tests/identity-helpers.test.ts index 448a76a..8d589c9 100644 --- a/tests/identity-helpers.test.ts +++ b/tests/identity-helpers.test.ts @@ -24,7 +24,7 @@ import type { FastifyRequest } from 'fastify'; import type { Context } from 'hono'; -// Couples to the internal GATE_STATE_KEY string in each adapter; intentional — +// Couples to the internal GATE_STATE_KEY string in each adapter; intentional: // the read helpers are thin wrappers over that key, and the test exercises the // extraction logic directly. const GATE_STATE_KEY = '__agentscoreGate'; diff --git a/tests/identity/cross-lang.test.ts b/tests/identity/cross-lang.test.ts index 4ad8ec2..60b2ab3 100644 --- a/tests/identity/cross-lang.test.ts +++ b/tests/identity/cross-lang.test.ts @@ -30,7 +30,7 @@ const fixtures: { name: string; data: Fixture }[] = readdirSync(FIXTURE_DIR) data: JSON.parse(readFileSync(join(FIXTURE_DIR, name), 'utf-8')) as Fixture, })); -describe('UCP signing — cross-language fixture corpus', () => { +describe('UCP signing: cross-language fixture corpus', () => { for (const { name, data } of fixtures) { it(`verifies ${name} (${data.alg}, generated by ${data.generator})`, async () => { const ok = await verifyUCPProfile(data.profile, data.jwks); diff --git a/tests/identity/load-ucp-signing-key-from-env.test.ts b/tests/identity/load-ucp-signing-key-from-env.test.ts index 799782f..ea2f2ab 100644 --- a/tests/identity/load-ucp-signing-key-from-env.test.ts +++ b/tests/identity/load-ucp-signing-key-from-env.test.ts @@ -1,5 +1,5 @@ /** - * Tests for `loadUCPSigningKeyFromEnv` — env-driven UCP signing-key loader. + * Tests for `loadUCPSigningKeyFromEnv`: env-driven UCP signing-key loader. * * Locked behavior contract (shared with the Python sibling at * `python-commerce/tests/test_load_ucp_signing_key_from_env.py`): @@ -51,7 +51,7 @@ async function buildP256JWK(): Promise> { // ─── env JWK present: happy paths ───────────────────────────────────────── -describe('loadUCPSigningKeyFromEnv — env JWK present', () => { +describe('loadUCPSigningKeyFromEnv: env JWK present', () => { it('loads an Ed25519 JWK', async () => { const jwk = await buildEd25519JWK(); jwk.kid = 'test-ed25519-key'; @@ -99,7 +99,7 @@ describe('loadUCPSigningKeyFromEnv — env JWK present', () => { // ─── kid precedence ────────────────────────────────────────────────────── -describe('loadUCPSigningKeyFromEnv — kid precedence', () => { +describe('loadUCPSigningKeyFromEnv: kid precedence', () => { it('embedded JWK kid wins over env kid', async () => { const jwk = await buildEd25519JWK(); jwk.kid = 'embedded-kid'; @@ -132,7 +132,7 @@ describe('loadUCPSigningKeyFromEnv — kid precedence', () => { // ─── ephemeral fallback ────────────────────────────────────────────────── -describe('loadUCPSigningKeyFromEnv — ephemeral fallback', () => { +describe('loadUCPSigningKeyFromEnv: ephemeral fallback', () => { it('generates an ephemeral Ed25519 key when env JWK is missing', async () => { const result = await loadUCPSigningKeyFromEnv(); expect(result.publicJWK.alg).toBe('EdDSA'); @@ -160,7 +160,7 @@ describe('loadUCPSigningKeyFromEnv — ephemeral fallback', () => { // ─── whitespace handling ───────────────────────────────────────────────── -describe('loadUCPSigningKeyFromEnv — whitespace handling', () => { +describe('loadUCPSigningKeyFromEnv: whitespace handling', () => { it('whitespace-only env JWK is treated as absent', async () => { process.env.UCP_SIGNING_KEY_JWK_PRIVATE = ' \n\t '; const result = await loadUCPSigningKeyFromEnv(); @@ -177,7 +177,7 @@ describe('loadUCPSigningKeyFromEnv — whitespace handling', () => { // ─── error paths ───────────────────────────────────────────────────────── -describe('loadUCPSigningKeyFromEnv — error paths', () => { +describe('loadUCPSigningKeyFromEnv: error paths', () => { it('throws for malformed JSON, naming the env var', async () => { process.env.UCP_SIGNING_KEY_JWK_PRIVATE = '{not valid json'; await expect(loadUCPSigningKeyFromEnv()).rejects.toThrow( @@ -221,7 +221,7 @@ describe('loadUCPSigningKeyFromEnv — error paths', () => { // ─── caching + concurrency ─────────────────────────────────────────────── -describe('loadUCPSigningKeyFromEnv — caching', () => { +describe('loadUCPSigningKeyFromEnv: caching', () => { it('repeated calls return the cached key (same object identity)', async () => { const first = await loadUCPSigningKeyFromEnv(); const second = await loadUCPSigningKeyFromEnv(); @@ -240,7 +240,7 @@ describe('loadUCPSigningKeyFromEnv — caching', () => { const results = await Promise.all( Array.from({ length: 8 }, () => loadUCPSigningKeyFromEnv()), ); - // All 8 callers received the same key object — no two-key race. + // All 8 callers received the same key object: no two-key race. for (const r of results) expect(r).toBe(results[0]); }); @@ -254,7 +254,7 @@ describe('loadUCPSigningKeyFromEnv — caching', () => { it('rejection clears cache so next call retries (no permanent poison)', async () => { process.env.UCP_SIGNING_KEY_JWK_PRIVATE = '{not valid'; await expect(loadUCPSigningKeyFromEnv()).rejects.toThrow(); - // Fix the env, retry — should succeed. + // Fix the env, retry: should succeed. delete process.env.UCP_SIGNING_KEY_JWK_PRIVATE; const result = await loadUCPSigningKeyFromEnv(); expect(result.publicJWK.alg).toBe('EdDSA'); diff --git a/tests/identity/payment_handlers.test.ts b/tests/identity/payment_handlers.test.ts index 7cb5048..111824c 100644 --- a/tests/identity/payment_handlers.test.ts +++ b/tests/identity/payment_handlers.test.ts @@ -23,7 +23,7 @@ const x402Config = (out: ReturnType): { networks: Rec return binding!.config as { networks: Record[] }; }; -describe('mppPaymentHandler — TempoRailSpec', () => { +describe('mppPaymentHandler: TempoRailSpec', () => { it('static recipient is emitted verbatim under tempo-mainnet', () => { const out = mppPaymentHandler({ networks: [{ recipient: '0xfeedface' } as TempoRailSpec] }); expect(mppConfig(out).networks).toEqual([ @@ -56,7 +56,7 @@ describe('mppPaymentHandler — TempoRailSpec', () => { }); }); -describe('mppPaymentHandler — SolanaMppRailSpec', () => { +describe('mppPaymentHandler: SolanaMppRailSpec', () => { it('mainnet CAIP-2 maps to solana-mainnet-beta UCP namespace', () => { const spec: SolanaMppRailSpec = { recipient: 'solanaaddr', @@ -78,7 +78,7 @@ describe('mppPaymentHandler — SolanaMppRailSpec', () => { }); }); -describe('mppPaymentHandler — mixed rails', () => { +describe('mppPaymentHandler: mixed rails', () => { it('one call mixes Tempo, Solana MPP, and Tempo session entries', () => { const out = mppPaymentHandler({ networks: [ diff --git a/tests/identity/policy.test.ts b/tests/identity/policy.test.ts index 0980aa7..b3c5b4e 100644 --- a/tests/identity/policy.test.ts +++ b/tests/identity/policy.test.ts @@ -183,7 +183,7 @@ describe('runGateWithEnforcement', () => { it('soft mode swallows a wallet_not_trusted deny carrying only fixable reasons', async () => { // KYC/age/jurisdiction misses downgrade to unverified under soft so the order completes - // with a degraded identity_status. (Sanctions are the sole exception — see below.) + // with a degraded identity_status. (Sanctions are the sole exception: see below.) const body = { error: { code: 'wallet_not_trusted' }, reasons: ['kyc_required'] }; const result = await runGateWithEnforcement('soft', async () => ({ ok: false, status: 403, body })); expect(result.status).toBe('unverified'); @@ -191,7 +191,7 @@ describe('runGateWithEnforcement', () => { expect(result.denialBody).toEqual(body); }); - it('soft mode does NOT swallow a sanctions deny (reasons: sanctions_flagged) — stays terminal', async () => { + it('soft mode does NOT swallow a sanctions deny (reasons: sanctions_flagged): stays terminal', async () => { // CRITICAL strict-liability floor: soft enforcement must NEVER swallow an OFAC SDN deny. A // wallet_not_trusted deny whose reasons carry `sanctions_flagged` stays denied even under // soft, so a sanctioned wallet is never settled. diff --git a/tests/identity/tokens.test.ts b/tests/identity/tokens.test.ts index 177a91f..125494c 100644 --- a/tests/identity/tokens.test.ts +++ b/tests/identity/tokens.test.ts @@ -1,8 +1,8 @@ /** - * Tests for `hashOperatorToken` — canonical sha256 hex digest used to persist + * Tests for `hashOperatorToken`: canonical sha256 hex digest used to persist * operator tokens without storing plaintext on disk. * - * The expected digests below are hardcoded — locked as the cross-language + * The expected digests below are hardcoded: locked as the cross-language * contract with the Python sibling at `python-commerce/tests/test_tokens.py`. * Both files reference the same fixture inputs and the same expected output * bytes. A drift in either language (algorithm swap, encoding change, accidental @@ -24,7 +24,7 @@ const FIXTURES: [string, string][] = [ ['opc_cross_lang_fixture', '96690dd2659bc1e33227e943d5f8a526c7c95a0ede5775a1573abab6578ca8ec'], ['opc_anything', 'e6ba517ac96ee39190c4d703b2d968fec96e87827374e56095a2f443d870730d'], ['opc_42', '731985dd676ea0702b3e6f6cbb107eaf467319e2801e6f953f08cbcc7dd71684'], - // Non-ASCII fixture — UTF-8 encoding of "é" is 0xC3 0xA9; locks the encoding + // Non-ASCII fixture: UTF-8 encoding of "é" is 0xC3 0xA9; locks the encoding // contract so a future implementation that swaps Node's default encoding // still produces the same bytes. ['opc_é', 'c1dba11d60cbfc1264d115e07a74a0355b6a66ded4ee3f930024a1733ba6942f'], diff --git a/tests/identity/ucp-signing.test.ts b/tests/identity/ucp-signing.test.ts index 85aa4f7..46d6e26 100644 --- a/tests/identity/ucp-signing.test.ts +++ b/tests/identity/ucp-signing.test.ts @@ -34,7 +34,7 @@ const baseInput = { }, }; -describe('UCP signing — generateUCPSigningKey', () => { +describe('UCP signing: generateUCPSigningKey', () => { it('generates an Ed25519 keypair by default', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'test-key-1' }); expect(privateKey).toBeDefined(); @@ -64,7 +64,7 @@ describe('UCP signing — generateUCPSigningKey', () => { }); }); -describe('UCP signing — signUCPProfile / verifyUCPProfile round-trip', () => { +describe('UCP signing: signUCPProfile / verifyUCPProfile round-trip', () => { it('signs an Ed25519-keyed profile and verifies against the matching JWKS', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'merchant-2026-05' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -124,7 +124,7 @@ describe('UCP signing — signUCPProfile / verifyUCPProfile round-trip', () => { }); }); -describe('UCP signing — canonicalization', () => { +describe('UCP signing: canonicalization', () => { it('signs the profile such that key-order in the JSON does not affect verification', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'k' }); const profileA = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -132,7 +132,7 @@ describe('UCP signing — canonicalization', () => { // Hand-construct the same profile with keys in REVERSE insertion order so // canonicalization actually has work to do. JSON.parse(JSON.stringify(x)) - // preserves the source order, which is a vacuous round-trip — this version + // preserves the source order, which is a vacuous round-trip: this version // genuinely re-orders. const reordered: Record = {}; const sortedKeys = Object.keys(signed).sort().reverse(); @@ -143,7 +143,7 @@ describe('UCP signing — canonicalization', () => { }); }); -describe('UCP signing — buildJWKSResponse', () => { +describe('UCP signing: buildJWKSResponse', () => { it('wraps keys in a `{ keys: [...] }` document', () => { const k1 = { kid: 'a', kty: 'OKP', crv: 'Ed25519', x: 'xxx', use: 'sig', alg: 'EdDSA' }; const k2 = { kid: 'b', kty: 'EC', crv: 'P-256', x: 'xxx', y: 'yyy', use: 'sig', alg: 'ES256' }; @@ -156,10 +156,10 @@ describe('UCP signing — buildJWKSResponse', () => { }); }); -describe('UCP signing — security: alg-confusion + typ + dup-kid', () => { +describe('UCP signing: security: alg-confusion + typ + dup-kid', () => { // RFC 8725 §3.1: a verifier MUST restrict accepted JWS algorithms to the // set the application expects. A naive implementation that calls importJWK(jwk, header.alg) - // can be coerced into using HS256 (symmetric) with the public key as the secret — + // can be coerced into using HS256 (symmetric) with the public key as the secret: // a hostile signing_keys[] entry then mints valid-looking signatures. it('rejects HS256 signatures even when the JWKS contains an HS256 oct key', async () => { const jose = await import('jose'); @@ -296,7 +296,7 @@ describe('UCP signing — security: alg-confusion + typ + dup-kid', () => { }); }); -describe('UCP signing — float canonicalization defense', () => { +describe('UCP signing: float canonicalization defense', () => { it('throws when signing a profile that contains a non-integer Number anywhere', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'k' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -322,7 +322,7 @@ describe('UCP signing — float canonicalization defense', () => { }); }); -describe('UCP signing — additional hardening', () => { +describe('UCP signing: additional hardening', () => { it('signUCPProfile throws when kid is not in profile.keys[]', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'real' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -389,12 +389,12 @@ describe('UCP signing — additional hardening', () => { const { base64url } = await import('jose'); // Valid protected header (passes the typ/alg/kid pre-checks), but the JWS has - // only two segments after it — jose's compactVerify rejects with JWSInvalid, + // only two segments after it: jose's compactVerify rejects with JWSInvalid, // which the inner catch wraps to malformed_jws (distinct from the pre-decode // header check that fires for an unparseable header segment). const headerJson = JSON.stringify({ alg: 'EdDSA', kid: 'k', typ: 'agentscore-profile+jws' }); const headerB64 = base64url.encode(new TextEncoder().encode(headerJson)); - // Two segments only (header.payload) — not a valid compact JWS (needs three). + // Two segments only (header.payload): not a valid compact JWS (needs three). const jws = `${headerB64}.${base64url.encode(new TextEncoder().encode('{}'))}`; const signed = { ...profile, signature: jws }; @@ -494,7 +494,7 @@ describe('UCPSigningKey.fromJWK', () => { }); }); -describe('UCP signing — JCS-incompatible value rejection', () => { +describe('UCP signing: JCS-incompatible value rejection', () => { // Probe the internal stableStringify by signing a profile that holds the // offending value. The signer canonicalizes via stableStringify, so any // rejection there bubbles up through signUCPProfile. @@ -554,7 +554,7 @@ describe('UCP signing — JCS-incompatible value rejection', () => { }); }); -describe('UCP signing — integer overflow defense', () => { +describe('UCP signing: integer overflow defense', () => { async function signWith(extras: unknown): Promise>> { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'k' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -587,7 +587,7 @@ describe('UCP signing — integer overflow defense', () => { }); }); -describe('UCP signing — JWK alg / header alg consistency', () => { +describe('UCP signing: JWK alg / header alg consistency', () => { it('rejects when matched JWK alg does not match JWS header alg', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'mismatch', alg: 'EdDSA' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -599,7 +599,7 @@ describe('UCP signing — JWK alg / header alg consistency', () => { }); }); -describe('UCP signing — round-4 hardening', () => { +describe('UCP signing: round-4 hardening', () => { it('rejects a JWK with use=enc as unusable_key', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'enc-key', alg: 'EdDSA' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); @@ -665,7 +665,7 @@ describe('UCP signing — round-4 hardening', () => { }); }); -describe('UCP signing — verifier-side canonicalize must not leak raw Error', () => { +describe('UCP signing: verifier-side canonicalize must not leak raw Error', () => { async function makeSigned(): Promise<{ signed: Awaited>; jwks: ReturnType; @@ -712,7 +712,7 @@ describe('UCP signing — verifier-side canonicalize must not leak raw Error', ( }); }); -describe('UCP signing — error precedence parity (profile-first)', () => { +describe('UCP signing: error precedence parity (profile-first)', () => { it('null profile + malformed JWKS returns no_signature (profile-first)', async () => { await expect(verifyUCPProfile(null as never, 'not a jwks' as never)) .rejects.toMatchObject({ name: 'UCPVerificationError', code: 'no_signature' }); @@ -812,7 +812,7 @@ describe('UCP signing — error precedence parity (profile-first)', () => { }); }); -describe('UCP signing — U+2028 / U+2029 rejection', () => { +describe('UCP signing: U+2028 / U+2029 rejection', () => { // Modern V8 emits U+2028 / U+2029 raw from JSON.stringify, so on today's Node // the divergence with Python json.dumps(ensure_ascii=False) is theoretical. // The rejection mirrors core/api/src/lib/canonicalize.ts so the contract @@ -855,7 +855,7 @@ describe('UCP signing — U+2028 / U+2029 rejection', () => { .rejects.toThrow(/U\+2029/); }); - it('accepts U+2027 (HYPHENATION POINT) as sanity case — different codepoint, not a target', async () => { + it('accepts U+2027 (HYPHENATION POINT) as sanity case: different codepoint, not a target', async () => { const { privateKey, publicJWK } = await generateUCPSigningKey({ kid: 'k' }); const profile = buildUCPProfile({ ...baseInput, signing_keys: [publicJWK] }); (profile as unknown as Record).extras = { note: 'before‧after' }; @@ -886,7 +886,7 @@ describe('UCP signing — U+2028 / U+2029 rejection', () => { }); }); -describe('UCP signing — JWK use/alg null treated as absent', () => { +describe('UCP signing: JWK use/alg null treated as absent', () => { // RFC 7517 lists `use` and `alg` as optional. JSON null for these fields is // out-of-spec but harmless; treat null as absent so the Node verifier // matches Python's `is not None` semantics and a JWK with explicit nulls diff --git a/tests/identity/ucp.test.ts b/tests/identity/ucp.test.ts index 4e0df3a..65c8428 100644 --- a/tests/identity/ucp.test.ts +++ b/tests/identity/ucp.test.ts @@ -36,7 +36,7 @@ describe('buildUCPProfile (spec-compliant shape)', () => { expect(profile.ucp.services).toEqual(baseInput.services); expect(profile.ucp.capabilities).toEqual({}); expect(profile.ucp.payment_handlers).toEqual({}); - // No top-level `spec` field per UCP spec — spec lives per-binding. + // No top-level `spec` field per UCP spec: spec lives per-binding. expect((profile as Record).spec).toBeUndefined(); // No `version` at top level either; lives under `ucp`. expect((profile as Record).version).toBeUndefined(); @@ -62,7 +62,7 @@ describe('buildUCPProfile (spec-compliant shape)', () => { expect(cap?.version).toBe('2026-04-08'); expect(cap?.spec).toContain('agentscore.com'); expect(cap?.schema).toContain('com-agentscore-identity-v1.json'); - // Multi-parent extends — matches Shopify's dev.shopify.catalog.storefront pattern + // Multi-parent extends: matches Shopify's dev.shopify.catalog.storefront pattern // and UCP-canonical dev.ucp.shopping.discount (extends [checkout, cart]). expect(cap?.extends).toEqual(['dev.ucp.shopping.checkout', 'dev.ucp.shopping.cart']); // Config is the merchant's policy declaration, NOT per-operator data. Public diff --git a/tests/middleware/rate_limit_adapters.test.ts b/tests/middleware/rate_limit_adapters.test.ts index 6a4e19e..d5e5492 100644 --- a/tests/middleware/rate_limit_adapters.test.ts +++ b/tests/middleware/rate_limit_adapters.test.ts @@ -176,7 +176,7 @@ describe('rateLimitFastify', () => { }; const req = { headers: {}, ip: undefined }; await hook(req as never, reply as never); - // No assertion — just exercises the fallback branch + // No assertion: just exercises the fallback branch }); it('respects custom keyResolver', async () => { diff --git a/tests/nextjs.test.ts b/tests/nextjs.test.ts index aa41126..ed0a954 100644 --- a/tests/nextjs.test.ts +++ b/tests/nextjs.test.ts @@ -34,7 +34,7 @@ function mockFetchStatus(status: number, errorCode?: string): void { } as unknown as Response); } -describe('Next.js adapter — withAgentScoreGate (route handler wrapper)', () => { +describe('Next.js adapter: withAgentScoreGate (route handler wrapper)', () => { afterEach(() => { vi.restoreAllMocks(); }); it('reaches handler on allow with assess data', async () => { @@ -347,7 +347,7 @@ describe('Next.js adapter — withAgentScoreGate (route handler wrapper)', () => expect(handler).toHaveBeenCalled(); }); - it('captureWallet swallows failures silently — handler response unaffected', async () => { + it('captureWallet swallows failures silently: handler response unaffected', async () => { global.fetch = vi.fn() .mockResolvedValueOnce({ ok: true, status: 200, json: vi.fn().mockResolvedValueOnce(ALLOW_RESPONSE) } as unknown as Response) .mockRejectedValueOnce(new Error('network down')); @@ -389,7 +389,7 @@ describe('Next.js adapter — withAgentScoreGate (route handler wrapper)', () => }); }); -describe('Next.js adapter — agentscoreMiddleware', () => { +describe('Next.js adapter: agentscoreMiddleware', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns undefined on allow (request should continue)', async () => { @@ -429,7 +429,7 @@ describe('Next.js adapter — agentscoreMiddleware', () => { }); }); -describe('Next.js adapter — error paths + chain', () => { +describe('Next.js adapter: error paths + chain', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns 403 payment_required on 402', async () => { @@ -472,7 +472,7 @@ describe('Next.js adapter — error paths + chain', () => { }); }); -describe('Next.js adapter — withConditionalAgentScoreGate', () => { +describe('Next.js adapter: withConditionalAgentScoreGate', () => { it('discovery leg (no payment header): handler runs without invoking gate; sync return wrapped in Promise', async () => { const POST = withConditionalAgentScoreGate( { apiKey: API_KEY }, @@ -499,7 +499,7 @@ describe('Next.js adapter — withConditionalAgentScoreGate', () => { }); }); -describe('Next.js adapter — conditionalAgentscoreMiddleware', () => { +describe('Next.js adapter: conditionalAgentscoreMiddleware', () => { it('discovery leg returns undefined (chain continues)', async () => { const mw = conditionalAgentscoreMiddleware({ apiKey: API_KEY }); const res = await mw(new Request('https://example.com/', { method: 'POST' })); diff --git a/tests/pay_to_address.test.ts b/tests/pay_to_address.test.ts index cce8493..507e0c8 100644 --- a/tests/pay_to_address.test.ts +++ b/tests/pay_to_address.test.ts @@ -348,7 +348,7 @@ describe('createPayToAddressFromStripePI', () => { it('settle leg still rejects when the credential signs against an unknown recipient even with staticRecipients configured', async () => { // The static-recipient bypass MUST only apply when the signed-against recipient - // exactly matches the configured static address — otherwise it'd let any + // exactly matches the configured static address: otherwise it'd let any // attacker-chosen recipient through. const { cache } = makeFakeCache({ hasAddress: false }); const STATIC_SOLANA = 'FR96wd96urHJdMnYayFrPYmDeAjKvwi3rQ2wkgXXTSP8'; diff --git a/tests/payment/amounts.test.ts b/tests/payment/amounts.test.ts index aaef51c..c170645 100644 --- a/tests/payment/amounts.test.ts +++ b/tests/payment/amounts.test.ts @@ -1,5 +1,5 @@ /** - * Tests for `usdToAtomic` — converts USD amounts to bigint atomic units. + * Tests for `usdToAtomic`: converts USD amounts to bigint atomic units. * * The fixture corpus below is locked as the cross-language contract with the * Python sibling at `python-commerce/tests/test_amounts.py`. Both files reference diff --git a/tests/payment/classify_orchestration_error.test.ts b/tests/payment/classify_orchestration_error.test.ts index 99f46c0..aed7e1d 100644 --- a/tests/payment/classify_orchestration_error.test.ts +++ b/tests/payment/classify_orchestration_error.test.ts @@ -1,5 +1,5 @@ /** - * Tests for `classifyOrchestrationError` — string-match classification of + * Tests for `classifyOrchestrationError`: string-match classification of * arbitrary thrown errors during the 402 orchestration. * * Locked cross-language fixtures shared with the Python sibling at @@ -27,7 +27,7 @@ const FIXTURES: [string, string, ClassifiedX402Error['code'] | null][] = [ ['facilitator_lowercase', 'Facilitator unreachable', 'payment_provider_unavailable'], ['cdp_lowercase', 'CDP JWT expired', 'payment_provider_unavailable'], ['stripe_uppercase', 'STRIPE timeout', 'payment_provider_unavailable'], - // Unknown — caller rethrows + // Unknown: caller rethrows ['database_error', 'duplicate key value violates unique constraint', null], ['network_error', 'ECONNREFUSED', null], ['empty_string', '', null], diff --git a/tests/payment/detect_rail_from_headers.test.ts b/tests/payment/detect_rail_from_headers.test.ts index 78bb881..0bbd0d4 100644 --- a/tests/payment/detect_rail_from_headers.test.ts +++ b/tests/payment/detect_rail_from_headers.test.ts @@ -1,5 +1,5 @@ /** - * Tests for `detectRailFromHeaders` — reports which payment-protocol family + * Tests for `detectRailFromHeaders`: reports which payment-protocol family * the inbound request carries. * * The fixture corpus below is locked as the cross-language contract with the diff --git a/tests/payment/directive-edge-cases.test.ts b/tests/payment/directive-edge-cases.test.ts index b920eb7..1423800 100644 --- a/tests/payment/directive-edge-cases.test.ts +++ b/tests/payment/directive-edge-cases.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { buildPaymentRequestBlob, paymentDirective, buildPaymentDirective } from '../../src/payment/directive'; -describe('buildPaymentRequestBlob — defaults + edge cases', () => { +describe('buildPaymentRequestBlob: defaults + edge cases', () => { it('defaults to 6 decimals + usd currency when no rail and no overrides', async () => { const blob = buildPaymentRequestBlob({ amountUsd: 1 }); const decoded = JSON.parse(Buffer.from(blob, 'base64url').toString()); @@ -10,7 +10,7 @@ describe('buildPaymentRequestBlob — defaults + edge cases', () => { expect(decoded.methodDetails).toBeUndefined(); }); - it('uses unknown rail without crashing — falls back to defaults', async () => { + it('uses unknown rail without crashing: falls back to defaults', async () => { const blob = buildPaymentRequestBlob({ rail: 'not-a-real-rail', amountUsd: 1 }); const decoded = JSON.parse(Buffer.from(blob, 'base64url').toString()); expect(decoded.amount).toBe('1000000'); @@ -30,7 +30,7 @@ describe('buildPaymentRequestBlob — defaults + edge cases', () => { }); }); -describe('paymentDirective — defaults', () => { +describe('paymentDirective: defaults', () => { it('defaults method to "unknown" when no rail is provided', async () => { const directive = paymentDirective({ id: 'chg', realm: 'ex.com', request: 'abc' }); expect(directive).toContain('method="unknown"'); @@ -47,13 +47,13 @@ describe('paymentDirective — defaults', () => { expect(expires).toBeLessThanOrEqual(after + 5 * 60 * 1000 + 100); }); - it('uses an unknown rail without crashing — defaults method to "unknown"', async () => { + it('uses an unknown rail without crashing: defaults method to "unknown"', async () => { const directive = paymentDirective({ rail: 'not-a-real-rail', id: 'chg', realm: 'ex.com', request: 'abc' }); expect(directive).toContain('method="unknown"'); }); }); -describe('buildPaymentDirective — convenience wrapper', () => { +describe('buildPaymentDirective: convenience wrapper', () => { it('chains buildPaymentRequestBlob + paymentDirective with a rail', async () => { const directive = buildPaymentDirective({ rail: 'tempo-mainnet', diff --git a/tests/payment/extract_signer_for_precheck.test.ts b/tests/payment/extract_signer_for_precheck.test.ts index 778f747..ca02465 100644 --- a/tests/payment/extract_signer_for_precheck.test.ts +++ b/tests/payment/extract_signer_for_precheck.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { extractSignerForPrecheck } from '../../src/signer'; -describe('extractSignerForPrecheck — additional branches', () => { +describe('extractSignerForPrecheck: additional branches', () => { it('returns null when no payment header at all', async () => { expect(await extractSignerForPrecheck({})).toBeNull(); expect(await extractSignerForPrecheck({ 'user-agent': 'test' })).toBeNull(); diff --git a/tests/payment/idempotency.test.ts b/tests/payment/idempotency.test.ts index e353e01..edcf58c 100644 --- a/tests/payment/idempotency.test.ts +++ b/tests/payment/idempotency.test.ts @@ -54,7 +54,7 @@ describe('buildIdempotencyKey', () => { const result = buildIdempotencyKey({ paymentIntentId: key }); expect(warnSpy).toHaveBeenCalledOnce(); expect(warnSpy.mock.calls[0]?.[0]).toContain('idempotency key longer than 200 chars'); - // Returns the original key unchanged — server is the source of truth for truncation. + // Returns the original key unchanged: server is the source of truth for truncation. expect(result).toBe(key); }); }); diff --git a/tests/payment/lazy.test.ts b/tests/payment/lazy.test.ts index fc74a09..6fd748d 100644 --- a/tests/payment/lazy.test.ts +++ b/tests/payment/lazy.test.ts @@ -51,7 +51,7 @@ describe('lazyMppxServer', () => { expect(typeof getter).toBe('function'); }); - it('memoizes — concurrent first-calls share the same promise', async () => { + it('memoizes: concurrent first-calls share the same promise', async () => { // Spy on createMppxServer's lazy import. Without ioredis-style mocks for // mppx peer dep, we just confirm the getter is callable and returns the // same shape (memoization is internal). @@ -61,7 +61,7 @@ describe('lazyMppxServer', () => { }); const a = getter(); const b = getter(); - // Both calls should share the in-flight promise — settle them either way. + // Both calls should share the in-flight promise: settle them either way. await Promise.allSettled([a, b]); }); @@ -73,7 +73,7 @@ describe('lazyMppxServer', () => { }); }); -describe('lazyX402Server — memoized getter invocation (stub facilitator)', () => { +describe('lazyX402Server: memoized getter invocation (stub facilitator)', () => { afterEach(() => { vi.restoreAllMocks(); }); it('constructs the x402 server on first getter call and caches it for subsequent calls', async () => { diff --git a/tests/payment/mppx_server_extras.test.ts b/tests/payment/mppx_server_extras.test.ts index ae6acd0..4ed1705 100644 --- a/tests/payment/mppx_server_extras.test.ts +++ b/tests/payment/mppx_server_extras.test.ts @@ -12,7 +12,7 @@ import type { TempoSessionRailSpec, } from '../../src/payment/rail_spec'; -describe('createMppxServer — additional rail branches', () => { +describe('createMppxServer: additional rail branches', () => { it('routes tempo_session config through mppx.tempo.session', async () => { // mppx >=0.7.0 builds a session without a construction-time account // (the account is required later for channel close / settlement; the old diff --git a/tests/payment/rail_spec.test.ts b/tests/payment/rail_spec.test.ts index 84431fc..2a1b2a0 100644 --- a/tests/payment/rail_spec.test.ts +++ b/tests/payment/rail_spec.test.ts @@ -58,7 +58,7 @@ describe('SolanaMppRailSpec', () => { expect(spec.tokenProgram).toBeUndefined(); }); - it('fee-payer signer roundtrips through the spec — opaque object', () => { + it('fee-payer signer roundtrips through the spec: opaque object', () => { const sentinel = { __sentinel: true }; const spec: SolanaMppRailSpec = { recipient: 'GEQg2TM4VL315Bd4LLkGrhBjdNfoatKjCJYHBDPM3D74', @@ -120,7 +120,7 @@ describe('resolveRecipient', () => { expect(calls).toBe(1); }); - it('calls the factory exactly once per resolution — caching is caller-side', async () => { + it('calls the factory exactly once per resolution: caching is caller-side', async () => { let calls = 0; const factory = async () => { calls += 1; diff --git a/tests/payment/signer-from-auth.test.ts b/tests/payment/signer-from-auth.test.ts index 1e2b0f5..e80df07 100644 --- a/tests/payment/signer-from-auth.test.ts +++ b/tests/payment/signer-from-auth.test.ts @@ -8,7 +8,7 @@ * (which is fully inline and identical to the Python sibling). The MPP * path on Node delegates to `mppx.Credential.fromRequest`, so direct * byte-for-byte cross-language fixture parity for MPP is a non-goal here - * — Python's MPP path uses inline base64+JSON parsing, and exact-match + * Python's MPP path uses inline base64+JSON parsing, and exact-match * parity would require a parallel inline path on Node (out of scope for * the re-export PR; tracked separately). */ @@ -46,7 +46,7 @@ describe('extractPaymentSignerFromAuth re-export from @agent-score/commerce/paym }); it('returns null when the x402 payload has no authorization.from', async () => { - // base64 of `{"payload":{}}` — well-formed JSON, no signer to recover. + // base64 of `{"payload":{}}`: well-formed JSON, no signer to recover. const empty = 'eyJwYXlsb2FkIjp7fX0='; expect(await extractPaymentSignerFromAuth(null, empty)).toBeNull(); }); diff --git a/tests/payment/signer.test.ts b/tests/payment/signer.test.ts index a6f6c3d..3fba433 100644 --- a/tests/payment/signer.test.ts +++ b/tests/payment/signer.test.ts @@ -8,7 +8,7 @@ const encodeX402 = (payload: unknown): string => Buffer.from(JSON.stringify(payl const makeRequest = (headers: Record = {}): Request => new Request('https://example.com/purchase', { headers }); -describe('extractPaymentSigner — returns {address, network} for x402 EVM', () => { +describe('extractPaymentSigner: returns {address, network} for x402 EVM', () => { it('returns evm network for an EIP-3009 payload with eip155 network', async () => { const header = encodeX402({ accepted: { network: 'eip155:8453' }, @@ -33,7 +33,7 @@ describe('extractPaymentSigner — returns {address, network} for x402 EVM', () }); }); -describe('extractPaymentSigner — Solana SVM path', () => { +describe('extractPaymentSigner: Solana SVM path', () => { it('returns null when the SVM payload has no transaction (no payer recoverable)', async () => { const header = encodeX402({ accepted: { network: 'solana:abc' }, payload: {} }); expect(await extractPaymentSigner(makeRequest(), header)).toBeNull(); diff --git a/tests/payment/solana.test.ts b/tests/payment/solana.test.ts index 7cc2c9b..04d93d6 100644 --- a/tests/payment/solana.test.ts +++ b/tests/payment/solana.test.ts @@ -8,9 +8,9 @@ describe('loadSolanaFeePayer', () => { }); it('attempts to construct a signer with valid hex privateKey (loads @solana/kit)', async () => { - // 128-char hex string — exercises the hex branch (lines 38-39) + // 128-char hex string: exercises the hex branch (lines 38-39) const hex = 'a'.repeat(128); - // Either succeeds (kit installed) or throws (kit missing) — either path is + // Either succeeds (kit installed) or throws (kit missing): either path is // valid. We just need to exercise the branch. try { await loadSolanaFeePayer({ privateKey: hex }); @@ -29,7 +29,7 @@ describe('loadSolanaFeePayer', () => { }); }); -describe('loadSolanaFeePayer — peer-dep guard', () => { +describe('loadSolanaFeePayer: peer-dep guard', () => { afterEach(() => { vi.doUnmock('@solana/kit'); }); it('throws a guiding error when @solana/kit lacks the required exports', async () => { diff --git a/tests/payment/wwwauthenticate.test.ts b/tests/payment/wwwauthenticate.test.ts index 8440a6f..61596ae 100644 --- a/tests/payment/wwwauthenticate.test.ts +++ b/tests/payment/wwwauthenticate.test.ts @@ -58,7 +58,7 @@ describe('paymentRequiredHeader', () => { it('emits accepts entries verbatim (no amount-field aliasing) so deepEqual matchers pass', () => { // @x402/core's findMatchingRequirements deep-equals the agent's signed `accepted` - // payload against the merchant's accepts array — any extra alias field on the wire + // payload against the merchant's accepts array: any extra alias field on the wire // breaks the match silently. paymentRequiredHeader emits exactly what the merchant // supplied. Vendors targeting v1-only clients can call aliasAmountFields explicitly // and pass the result through, accepting the matcher trade-off. diff --git a/tests/payment/x402_server_upto.test.ts b/tests/payment/x402_server_upto.test.ts index 041e571..544b9a3 100644 --- a/tests/payment/x402_server_upto.test.ts +++ b/tests/payment/x402_server_upto.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import { createX402Server } from '../../src/payment/x402_server'; -describe('createX402Server — upto scheme support', () => { +describe('createX402Server: upto scheme support', () => { it('successfully registers an UptoEvmScheme on x402 base rails', async () => { const server = await createX402Server({ rails: ['x402-base-mainnet-upto'], initialize: false }); expect(server).toBeDefined(); diff --git a/tests/payment/x402_settle.test.ts b/tests/payment/x402_settle.test.ts index 42b7872..2bc481d 100644 --- a/tests/payment/x402_settle.test.ts +++ b/tests/payment/x402_settle.test.ts @@ -120,7 +120,7 @@ describe('processX402Settle', () => { } }); - it('does NOT swallow settle errors as facilitator_error — settle_failed stays its own phase', async () => { + it('does NOT swallow settle errors as facilitator_error: settle_failed stays its own phase', async () => { const server = makeServer({ settlePayment: vi.fn().mockRejectedValue(new Error('on-chain rejection')) }); const result = await processX402Settle({ x402Server: server, ...baseInput }); expect(result.success).toBe(false); diff --git a/tests/payment/zero-settle.test.ts b/tests/payment/zero-settle.test.ts index da7febe..8c33315 100644 --- a/tests/payment/zero-settle.test.ts +++ b/tests/payment/zero-settle.test.ts @@ -1,5 +1,5 @@ /** - * Tests for `zeroAmountCarveOut` — skips upstream verify+settle for $0 orders + * Tests for `zeroAmountCarveOut`: skips upstream verify+settle for $0 orders * and recovers the signer for wallet-capture attribution. * * Locked cross-language fixtures shared with the Python sibling at diff --git a/tests/public-surface.test.ts b/tests/public-surface.test.ts index 6baa4c5..b1429f4 100644 --- a/tests/public-surface.test.ts +++ b/tests/public-surface.test.ts @@ -17,7 +17,7 @@ import { describe, expect, it } from 'vitest'; -describe('public-surface — top-level @agent-score/commerce barrel', () => { +describe('public-surface: top-level @agent-score/commerce barrel', () => { it('exports the UCP env-loader (loadUCPSigningKeyFromEnv) from the top-level barrel', async () => { const topLevel = await import('../src/index.js'); const module = await import('../src/identity/ucp-jwks.js'); @@ -34,7 +34,7 @@ describe('public-surface — top-level @agent-score/commerce barrel', () => { }); }); -describe('public-surface — @agent-score/commerce/payment subpath', () => { +describe('public-surface: @agent-score/commerce/payment subpath', () => { it('exports detectRailFromHeaders + zeroAmountCarveOut + usdToAtomic', async () => { const subpath = await import('../src/payment/index.js'); expect(typeof subpath.detectRailFromHeaders).toBe('function'); diff --git a/tests/seamless-helpers.test.ts b/tests/seamless-helpers.test.ts index 2466a0e..8e8c82d 100644 --- a/tests/seamless-helpers.test.ts +++ b/tests/seamless-helpers.test.ts @@ -1079,7 +1079,7 @@ describe('loadSolanaFeePayer', () => { | { getBase58Codec?: () => { decode: (b: Uint8Array) => string } } | null; if (!kit?.getBase58Codec) { - return; // peer dep missing — covered by the hex-input test + return; // peer dep missing: covered by the hex-input test } const { loadSolanaFeePayer } = await import('../src/payment/solana'); const seed = Uint8Array.from({ length: 32 }, (_, i) => i + 1); @@ -1432,7 +1432,7 @@ describe('Checkout gate hooks', () => { ).rejects.toThrow(); }); it('perRequestPolicy returning null routes to the wallet-OFAC floor (no-op with no extractable signer → settle proceeds)', async () => { - // null override no longer fully skips the gate — it falls through to the + // null override no longer fully skips the gate: it falls through to the // always-on wallet OFAC floor. With an opaque MPP auth header (no // extractable signer), the floor is a no-op and settle proceeds. The // SDN-deny / clean-allow floor verdicts are covered in the core-mocked @@ -1464,7 +1464,7 @@ describe('Checkout auto-derives composeMppx from mppxSecretKey', () => { mppxSecretKey: 'X'.repeat(32), }); // Discovery leg should emit 402 with the auto-derived composeMppx producing - // a www-authenticate challenge — but our mock real `lazyMppxServer` will + // a www-authenticate challenge: but our mock real `lazyMppxServer` will // need mppx peer dep. So we only assert the constructor accepted the // auto-derive config (no throw). expect(checkout).toBeDefined(); @@ -1772,7 +1772,7 @@ describe('Checkout SDK gate path', () => { }); }); -describe('Checkout SDK gate path — fully-populated gate config', () => { +describe('Checkout SDK gate path: fully-populated gate config', () => { it('forwards every optional gate field + perRequestPolicy override into the core options', async () => { // A maximal gate config exercises the optional-spread branches that build // `coreOpts` (baseUrl / userAgent / requireKyc / requireSanctionsClear / @@ -1900,7 +1900,7 @@ describe('Checkout SDK gate path — fully-populated gate config', () => { }); // ───────────────────────────────────────────────────────────────────────────── -// Wallet OFAC sanctions default (TEC-311) — gateless merchants +// Wallet OFAC sanctions default (TEC-311): gateless merchants // ───────────────────────────────────────────────────────────────────────────── describe('Checkout wallet OFAC default (no gate config)', () => { @@ -2103,7 +2103,7 @@ describe('Checkout wallet OFAC default (no gate config)', () => { x402Server: _mockX402Server() as never, isCachedAddress: () => true, onSettled: async () => ({}), - // gate set, but apiKey NOT — pre-fix this was silent-allow + // gate set, but apiKey NOT: pre-fix this was silent-allow gate: { requireKyc: true }, }); const result = await checkout.handle({ @@ -2214,7 +2214,7 @@ function _x402PaymentHeader(payerAddress: string): string { describe('Checkout handleX402 happy path', () => { // These tests construct a gateless Checkout (no `gate` config) and exercise // the x402 settle path. Under the always-on wallet OFAC default, that path - // would call /v1/assess — but these tests don't mock the AgentScore API. + // would call /v1/assess: but these tests don't mock the AgentScore API. // Stub the env to opt these tests into the "no API key → log+skip" path so // the focus stays on the x402 mock surface, not on OFAC enforcement. beforeEach(() => { vi.stubEnv('AGENTSCORE_API_KEY', ''); }); @@ -2626,7 +2626,7 @@ describe('Checkout mintRecipients - branch coverage', () => { computePricing: async () => ({ amountUsd: 1.0 }), }); // Discovery leg should still emit 402 even with an empty-recipient rail - // (per-order mint pattern — the rail is dropped from accepts). + // (per-order mint pattern: the rail is dropped from accepts). const result = await checkout.handle({ method: 'POST', url: 'https://api.example/purchase', @@ -2960,7 +2960,7 @@ describe('Checkout zero-settle MPP carve-out', () => { it('zeroSettleCarveOut=true + $0 + hash credential keeps the carve-out (repriced-to-zero flow)', async () => { // A hash credential at $0 means the agent signed against a nonzero quote // that re-priced to $0 at settle (no-match / full-discount flows). Upstream - // cannot settle it at $0 — the carve-out absorbs it: 200, no compose call, + // cannot settle it at $0: the carve-out absorbs it: 200, no compose call, // nothing charged. const { Checkout } = await import('../src/checkout'); const composeMppx = vi.fn(async () => ({ @@ -2991,7 +2991,7 @@ describe('Checkout zero-settle MPP carve-out', () => { }); it('zeroSettleCarveOut=true + $0 + PROOF credential delegates to composeMppx', async () => { - // An agent that saw a $0 challenge signs a proof credential — that path + // An agent that saw a $0 challenge signs a proof credential: that path // delegates so mppx's native zero-amount verification runs; upstream // rejections surface as payment failures instead of a 200 with an // unverified signer. @@ -3125,7 +3125,7 @@ describe('Checkout.mountUcpRoutes', () => { ); expect(captured2.status).toBe(204); - // GET /.well-known/jwks.json — covers the second mounted Express GET. + // GET /.well-known/jwks.json: covers the second mounted Express GET. const captured3: Record = {}; const res3 = { status: (c: number) => { captured3.status = c; return undefined; }, @@ -3138,7 +3138,7 @@ describe('Checkout.mountUcpRoutes', () => { const jwksBody = JSON.parse(captured3.body) as { keys: { kid: string }[] }; expect(jwksBody.keys[0].kid).toBe('mount-test'); - // OPTIONS /.well-known/jwks.json — preflight on the jwks mount. + // OPTIONS /.well-known/jwks.json: preflight on the jwks mount. const captured4: Record = {}; const res4 = { status: (c: number) => { captured4.status = c; return undefined; }, @@ -3190,7 +3190,7 @@ describe('Checkout.mountUcpRoutes', () => { await routes['OPTIONS /.well-known/ucp']({ headers: {} }, reply2); expect(captured2.status).toBe(204); - // GET /.well-known/jwks.json — same fastify reply chain as ucp. + // GET /.well-known/jwks.json: same fastify reply chain as ucp. const captured3: Record = {}; const reply3: any = { code: (c: number) => { captured3.status = c; return reply3; }, @@ -3203,7 +3203,7 @@ describe('Checkout.mountUcpRoutes', () => { const jwksBody = JSON.parse(captured3.body) as { keys: { kid: string }[] }; expect(jwksBody.keys[0].kid).toBe('mount-test'); - // OPTIONS /.well-known/jwks.json — preflight on the second mount. + // OPTIONS /.well-known/jwks.json: preflight on the second mount. const captured4: Record = {}; const reply4: any = { code: (c: number) => { captured4.status = c; return reply4; }, diff --git a/tests/signer.test.ts b/tests/signer.test.ts index 4feee47..ced7d1f 100644 --- a/tests/signer.test.ts +++ b/tests/signer.test.ts @@ -9,7 +9,7 @@ const SIGNER_MIXED = '0xABCDEF0123456789ABCDEF0123456789ABCDEF01'; // Monotonic cache-bust for dynamic imports. Using `Date.now()` would collide when // two tests run within the same millisecond (likely on fast CPUs or under CI load), -// which would share a cached module and break the vi.doMock state — flake. +// which would share a cached module and break the vi.doMock state: flake. let _importCounter = 0; const freshImportKey = () => `${Date.now()}-${++_importCounter}`; @@ -44,7 +44,7 @@ describe('readX402PaymentHeader', () => { }); }); -describe('extractPaymentSigner — x402 path', () => { +describe('extractPaymentSigner: x402 path', () => { it('returns the lowercased `from` address from a valid x402 payload', async () => { const req = makeRequest(); const header = encodeX402({ payload: { authorization: { from: SIGNER_MIXED } } }); @@ -75,7 +75,7 @@ describe('extractPaymentSigner — x402 path', () => { }); }); -describe('extractPaymentSigner — MPP path', () => { +describe('extractPaymentSigner: MPP path', () => { // mppx is an optional peer dep and is not installed in the gate's test env. The dynamic // import resolves to null and the helper falls through, leaving MPP extraction a no-op // for merchants who don't opt in to MPP. @@ -366,7 +366,7 @@ describe('extractPaymentSigner — MPP path', () => { }); }); -describe('extractPaymentSigner — Solana credentials are no longer extracted', () => { +describe('extractPaymentSigner: Solana credentials are no longer extracted', () => { it('returns null for a credential carrying a Solana network (Solana goes through MPP solana/charge; gate signer-extraction skipped)', async () => { const header = encodeX402({ accepted: { network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp' }, diff --git a/tests/solana_network_resolver.test.ts b/tests/solana_network_resolver.test.ts index 8770d81..8682e20 100644 --- a/tests/solana_network_resolver.test.ts +++ b/tests/solana_network_resolver.test.ts @@ -14,7 +14,7 @@ vi.mock('@solana/mpp/server', () => ({ })), })); -// pympp peer dep stub — the helper calls dynamic import('mppx') only on the +// pympp peer dep stub: the helper calls dynamic import('mppx') only on the // server build path; we stub @solana/mpp/server above for the Solana branch. vi.mock('mppx', () => ({ default: {} })); diff --git a/tests/web.test.ts b/tests/web.test.ts index 6c0fef4..dffeb0c 100644 --- a/tests/web.test.ts +++ b/tests/web.test.ts @@ -44,7 +44,7 @@ function mockFetchStatus(status: number, errorCode?: string): void { } as unknown as Response); } -describe('Web Fetch adapter — createAgentScoreGate', () => { +describe('Web Fetch adapter: createAgentScoreGate', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns { allowed: true, data } on allow', async () => { @@ -58,7 +58,7 @@ describe('Web Fetch adapter — createAgentScoreGate', () => { if (result.allowed) { expect(result.data).toMatchObject({ decision: 'allow' }); // Wallet-authenticated allow exposes a bound getSignerVerdict; invoking it - // delegates to the cached core verdict (undefined here — no signer in the mock). + // delegates to the cached core verdict (undefined here: no signer in the mock). expect(typeof result.getSignerVerdict).toBe('function'); expect(result.getSignerVerdict?.()).toBeUndefined(); } @@ -208,7 +208,7 @@ describe('Web Fetch adapter — createAgentScoreGate', () => { ); }); - it('fails open on 402 when failOpen is true — returns allowed with no data', async () => { + it('fails open on 402 when failOpen is true: returns allowed with no data', async () => { mockFetchStatus(402); const guard = createAgentScoreGate({ apiKey: API_KEY, failOpen: true }); const req = new Request('https://example.com/', { headers: { 'x-wallet-address': WALLET } }); @@ -275,7 +275,7 @@ describe('Web Fetch adapter — createAgentScoreGate', () => { }); }); -describe('Web Fetch adapter — withAgentScoreGate', () => { +describe('Web Fetch adapter: withAgentScoreGate', () => { afterEach(() => { vi.restoreAllMocks(); }); it('invokes handler with assess data on allow', async () => { @@ -372,7 +372,7 @@ describe('Web Fetch adapter — withAgentScoreGate', () => { }); }); -describe('Web Fetch adapter — error paths + chain', () => { +describe('Web Fetch adapter: error paths + chain', () => { afterEach(() => { vi.restoreAllMocks(); }); it('returns { allowed: false } with payment_required deny reason on 402', async () => {