@astrasyncai/verification-gateway 5.17.2 → 5.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +1849 -0
  2. package/README.md +141 -7
  3. package/dist/adapters/express.d.mts +1 -1
  4. package/dist/adapters/express.d.ts +1 -1
  5. package/dist/adapters/express.js +30 -12
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +30 -12
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/kit-express.d.mts +63 -0
  10. package/dist/adapters/kit-express.d.ts +63 -0
  11. package/dist/adapters/kit-express.js +88 -0
  12. package/dist/adapters/kit-express.js.map +1 -0
  13. package/dist/adapters/kit-express.mjs +61 -0
  14. package/dist/adapters/kit-express.mjs.map +1 -0
  15. package/dist/adapters/mcp.d.mts +1 -1
  16. package/dist/adapters/mcp.d.ts +1 -1
  17. package/dist/adapters/mcp.js +1 -1
  18. package/dist/adapters/mcp.js.map +1 -1
  19. package/dist/adapters/mcp.mjs +1 -1
  20. package/dist/adapters/mcp.mjs.map +1 -1
  21. package/dist/adapters/nextjs.d.mts +1 -1
  22. package/dist/adapters/nextjs.d.ts +1 -1
  23. package/dist/adapters/nextjs.js +18 -8
  24. package/dist/adapters/nextjs.js.map +1 -1
  25. package/dist/adapters/nextjs.mjs +18 -8
  26. package/dist/adapters/nextjs.mjs.map +1 -1
  27. package/dist/adapters/sdk.d.mts +1 -1
  28. package/dist/adapters/sdk.d.ts +1 -1
  29. package/dist/adapters/sdk.js +1 -1
  30. package/dist/adapters/sdk.js.map +1 -1
  31. package/dist/adapters/sdk.mjs +1 -1
  32. package/dist/adapters/sdk.mjs.map +1 -1
  33. package/dist/agent/index.js +1 -1
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/index.mjs +1 -1
  36. package/dist/agent/index.mjs.map +1 -1
  37. package/dist/bin/astrasync-claude-hook.js +1 -1
  38. package/dist/bin/astrasync-codex-hook.js +1 -1
  39. package/dist/bin/astrasync-guard.js +1 -1
  40. package/dist/bin/astrasync.js +19 -1
  41. package/dist/browser/background.js +1 -1
  42. package/dist/browser/background.js.map +1 -1
  43. package/dist/browser/background.mjs +1 -1
  44. package/dist/browser/background.mjs.map +1 -1
  45. package/dist/cli/index.js +1 -1
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/cli/index.mjs +1 -1
  48. package/dist/cli/index.mjs.map +1 -1
  49. package/dist/codex/index.js +1 -1
  50. package/dist/codex/index.js.map +1 -1
  51. package/dist/codex/index.mjs +1 -1
  52. package/dist/codex/index.mjs.map +1 -1
  53. package/dist/cursor/extension.js +1 -1
  54. package/dist/cursor/extension.js.map +1 -1
  55. package/dist/cursor/extension.mjs +1 -1
  56. package/dist/cursor/extension.mjs.map +1 -1
  57. package/dist/edge-config.d.mts +1 -1
  58. package/dist/edge-config.d.ts +1 -1
  59. package/dist/edge-config.js +1 -1
  60. package/dist/edge-config.js.map +1 -1
  61. package/dist/edge-config.mjs +1 -1
  62. package/dist/edge-config.mjs.map +1 -1
  63. package/dist/edge-core/index.d.mts +5 -4
  64. package/dist/edge-core/index.d.ts +5 -4
  65. package/dist/edge-core/index.js +18 -8
  66. package/dist/edge-core/index.js.map +1 -1
  67. package/dist/edge-core/index.mjs +18 -8
  68. package/dist/edge-core/index.mjs.map +1 -1
  69. package/dist/gateway/gateway.js +1 -1
  70. package/dist/gateway/gateway.js.map +1 -1
  71. package/dist/gateway/gateway.mjs +1 -1
  72. package/dist/gateway/gateway.mjs.map +1 -1
  73. package/dist/git-trigger/git-hooks.d.mts +1 -1
  74. package/dist/git-trigger/git-hooks.d.ts +1 -1
  75. package/dist/index.d.mts +390 -12
  76. package/dist/index.d.ts +390 -12
  77. package/dist/index.js +766 -230
  78. package/dist/index.js.map +1 -1
  79. package/dist/index.mjs +749 -222
  80. package/dist/index.mjs.map +1 -1
  81. package/dist/registration/index.js +1 -1
  82. package/dist/registration/index.js.map +1 -1
  83. package/dist/registration/index.mjs +1 -1
  84. package/dist/registration/index.mjs.map +1 -1
  85. package/dist/transport/index.d.mts +4 -3
  86. package/dist/transport/index.d.ts +4 -3
  87. package/dist/transport/index.js +1 -1
  88. package/dist/transport/index.js.map +1 -1
  89. package/dist/transport/index.mjs +1 -1
  90. package/dist/transport/index.mjs.map +1 -1
  91. package/dist/{types-vNBvYD7Z.d.mts → types-3ObBpsnf.d.mts} +7 -0
  92. package/dist/{types-DA8LzAOB.d.ts → types-BoxZEYPS.d.ts} +7 -0
  93. package/dist/{types-DRb_plOh.d.mts → types-Cns4l8gX.d.mts} +17 -0
  94. package/dist/{types-DRb_plOh.d.ts → types-Cns4l8gX.d.ts} +17 -0
  95. package/dist/ui/index.d.mts +1 -1
  96. package/dist/ui/index.d.ts +1 -1
  97. package/dist/verify.d.mts +1 -1
  98. package/dist/verify.d.ts +1 -1
  99. package/dist/verify.js +1 -1
  100. package/dist/verify.js.map +1 -1
  101. package/dist/verify.mjs +1 -1
  102. package/dist/verify.mjs.map +1 -1
  103. package/package.json +8 -2
package/README.md CHANGED
@@ -575,7 +575,7 @@ Read `result.settlementOutcome.status` — never `success` alone:
575
575
 
576
576
  | status | Meaning | Your action |
577
577
  | ------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
578
- | `pending_merchant` | 5.14.1: order recorded — YOU settle it | Charge the `settlementToken` (card) or redeem the `settlement` voucher (stablecoin), then `reportSettlement()` |
578
+ | `pending_merchant` | Order recorded — YOU settle it | Charge the `settlementToken` (card) or redeem the `settlement` voucher (stablecoin), then `reportSettlement()` |
579
579
  | `settled` | Money moved (reported / webhook) | Fulfil / mark paid |
580
580
  | `requires_approval` | Held for human step-up — not a failure | Record as pending; a re-drive follows approval |
581
581
  | `failed` | Charge declined (`failureCode`) | Mark failed |
@@ -583,7 +583,7 @@ Read `result.settlementOutcome.status` — never `success` alone:
583
583
  | `no_instrument` | Policy passed, no chargeable card on file | Terminal on autonomous; NON-terminal after approval — the human's approval survives; re-drive the same session once a card exists |
584
584
  | _anything else_ | A status newer than your SDK | Treat as PENDING, never failed (open union, 5.5.0) |
585
585
 
586
- **Every merchant settles (5.14.1 — AstraSync never charges):** a purchase
586
+ **Every merchant settles (AstraSync never charges):** a purchase
587
587
  grant records the order and returns `settlementOutcome: { status:
588
588
  'pending_merchant', orderId }` with what you settle:
589
589
 
@@ -619,6 +619,124 @@ material (the bridge strips it from the agent plane). Shipping addresses
619
619
  never transit AstraSync: take them agent → your fulfilment endpoint
620
620
  directly, joined by `sessionId`.
621
621
 
622
+ ## UCP & ACP checkout kits
623
+
624
+ `createUcpCheckoutKit()` (UCP 2026-08-25 REST) and `createAcpCheckoutKit()`
625
+ (ACP 2026-04-17) do the protocol work of a checkout endpoint; you supply the
626
+ catalog, settlement and fulfilment. Both are fetch-style
627
+ (`kit.handle(request) → Response`).
628
+
629
+ ```ts
630
+ import {
631
+ createUcpCheckoutKit,
632
+ expressHandler,
633
+ RedisIdempotencyStore,
634
+ } from '@astrasyncai/verification-gateway';
635
+
636
+ const ucp = createUcpCheckoutKit({
637
+ profile: {
638
+ endpoint: 'https://shop.example/ucp/v1',
639
+ paymentHandlers: ['ai.astrasync.card_token'],
640
+ ap2Mandates: true,
641
+ orders: true, // dev.ucp.shopping.order: GET /orders/{id} + order webhooks
642
+ },
643
+ currency: 'USD',
644
+ catalog: async (id) => db.items.find(id),
645
+ verifyPurchase: (ctx) =>
646
+ client.confirmCheckout({
647
+ astraId: ctx.astraId,
648
+ attemptId: ctx.attemptId,
649
+ checkoutSessionId: ctx.checkoutSessionId,
650
+ checkoutJwt: ctx.checkoutJwt,
651
+ transactionValue: ctx.valueMajor,
652
+ currency: 'USD',
653
+ }),
654
+ settle: async ({ result }) => chargeWithYourProcessor(result), // settled | pending | failed
655
+ reportSettlement: (r) => client.reportSettlement(r),
656
+ fulfil: async ({ session }) => ({ id: await createOrder(session) }),
657
+ signingKey: { privateKey, kid: 'merchant-1' },
658
+ store: myCheckoutStore, // get / put / claim (+ findByOrderId)
659
+ idempotencyStore: new RedisIdempotencyStore(redis),
660
+ links: [
661
+ { type: 'terms_of_service', url: 'https://shop.example/terms' },
662
+ { type: 'privacy_policy', url: 'https://shop.example/privacy' },
663
+ ],
664
+ permalinkBase: 'https://shop.example/orders',
665
+ webhook: {}, // URL from the platform profile; signed with signingKey
666
+ });
667
+
668
+ app.get('/.well-known/ucp', (_req, res) => res.json(ucp.profile));
669
+ app.use('/ucp/v1', express.raw({ type: '*/*' }), expressHandler(ucp));
670
+ ```
671
+
672
+ - **Stores.** The defaults are in-memory (one process, lost on restart); the
673
+ kits warn when they run on them outside `NODE_ENV=test`. In production pass
674
+ `store` and `idempotencyStore` (`RedisIdempotencyStore`).
675
+ - **`claim(id, from, to)`** — an atomic compare-and-set of the session status
676
+ on your store (e.g. `UPDATE … SET status = $to WHERE id = $id AND status =
677
+ $from`). `complete` claims `complete_in_progress` before it verifies, so two
678
+ concurrent completes never both charge: the loser gets the session back with
679
+ a recoverable `checkout_in_progress` message (UCP) or `409
680
+ checkout_in_progress` (ACP). Without `claim` the kit warns once and the lock
681
+ is best-effort. `MemoryCheckoutStore` implements it.
682
+ - **Holds never settle.** Any step-up signal (`requiresStepUp`,
683
+ `requiresApproval`, `recommendation: 'step_up_required'`, a
684
+ `requires_approval` outcome, or a `stepUpApproval` that isn't `consumed`)
685
+ returns UCP `requires_escalation` with `continue_url` = the buyer's approval
686
+ page, or ACP `pending_approval`. After approval the platform completes again;
687
+ the redeemed approval comes back `status: 'consumed'` and the purchase
688
+ proceeds.
689
+ - **`kit.resolvePending(sessionId, { status, processorRef?, txHash?, chainId?, failureCode? })`**
690
+ — when `settle` returns `pending`, the session stays `complete_in_progress`
691
+ with no order. Call `resolvePending` from your processor's webhook:
692
+ `'settled'` reports the settlement, runs `fulfil` and completes the session
693
+ (sending the UCP order webhook); `'failed'` reports it and returns the
694
+ session to `ready_for_complete` / `ready_for_payment`. Calling it again is a
695
+ no-op. The stored result never contains the settlement token, voucher or
696
+ mandates.
697
+ - **One instrument (UCP).** `complete` needs exactly one
698
+ `payment.instruments[]` entry whose `handler_id` is one of your
699
+ `payment_handlers`; anything else is `payment_failed`.
700
+ - **`links`** (UCP) — `terms_of_service`, `privacy_policy`, `refund_policy`,
701
+ `shipping_policy`, `faq` (`{ type, url, title? }`), returned on every
702
+ checkout and covered by `ap2.merchant_authorization`. A GET returns the
703
+ stored authorization; it never re-signs or writes.
704
+ - **Agent binding (UCP).** After `verifyPurchase`, the `UCP-Agent` profile is
705
+ checked against the verified agent's `protocolIdentities.ucpProfile`
706
+ (override with `resolveIdentities(req, result)`); a mismatch is
707
+ `ucp_agent_mismatch` and nothing settles. `ucpAgentBinding: 'registered'`
708
+ (default) lets agents with no registered profile through (e.g. the AstraSync
709
+ bridge's own profile); `'strict'` refuses them; `'off'` skips the check.
710
+ - **Caller scope.** Sessions and idempotency keys belong to the caller: the
711
+ verified ASTRA-id (`ctx.callerId` from `expressHandler`, or your
712
+ `resolveCallerId(req)`), else the `Authorization` header, else `X-Astra-Id`.
713
+ UCP falls back to the platform profile; ACP refuses a request with none
714
+ (401). Another caller gets 404, and never another caller's replay.
715
+ - **`expressHandler(kit, { callerId? })`** (also `/kit-express`) — mounts a
716
+ kit on Express. Give it the raw body (`express.raw()` on the kit's routes,
717
+ or `req.rawBody` from your JSON parser's `verify` hook): signatures and
718
+ idempotency fingerprints are computed over the exact bytes. When the
719
+ AstraSync middleware ran first, the verified ASTRA-id scopes the session.
720
+ - **UCP orders.** `buildUcpOrder(session)` is the `dev.ucp.shopping.order`
721
+ entity; `GET /orders/{id}` serves it; `webhook` sends it, signed, when the
722
+ order is created, and `kit.sendOrderWebhook(sessionId)` sends it again after
723
+ you update it.
724
+ - **ACP.** An `API-Version` other than `2026-04-17` is `400
725
+ unsupported_api_version` with `supported_versions`; `Idempotency-Key` and
726
+ `Request-Id` are echoed; `order.line_items` mirrors the session;
727
+ `permalinkBase` fills `permalink_url`. `buildAcpWellKnown({ endpoint,
728
+ paymentHandlers })` is a discovery document to serve at
729
+ `/.well-known/acp.json`.
730
+ - Canceling a completed or canceled checkout is `405` on both.
731
+
732
+ ### Unmatched routes (Express)
733
+
734
+ A request no configured route matches passes through by default
735
+ (`defaultRoutePolicy: 'pass'`). With `evaluateAlwaysIfCredentialed` and an
736
+ `X-Astra-Id`, or with `defaultRoutePolicy: 'observe'` and any AstraSync
737
+ credential, it's evaluated in observe mode: `req.agentVerification` is set and
738
+ the call is recorded, but the request always reaches your handler.
739
+
622
740
  ## Settlement Artifacts
623
741
 
624
742
  On a clean merchant-mediated grant where the owner has a verified payment instrument, verify-access returns a `settlement` object (wire format **v2**, versioned via the `ver: 2` claim — verify/redeem reject other versions):
@@ -660,11 +778,11 @@ interface FiatSettlementBinding {
660
778
 
661
779
  ### Voucher lifecycle (server-to-server)
662
780
 
663
- | Endpoint | Purpose |
664
- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
665
- | `POST /api/wallets/voucher/verify` | Verify signature/issuer and return the decoded payload — proves authenticity only, no spend |
666
- | `POST /api/wallets/voucher/redeem` | Atomic single-use spend: first caller gets `200 redeemed` (plus `order: { status: 'redeemed' }` when the voucher funds a recorded order — 5.14.1, no charge happens here), replays get `409 already_redeemed` (with first-redeemer forensics), revoked vouchers get `409 revoked` |
667
- | `GET /api/wallets/voucher/.well-known/jwks.json` | Public JWKS for offline signature verification |
781
+ | Endpoint | Purpose |
782
+ | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
783
+ | `POST /api/wallets/voucher/verify` | Verify signature/issuer and return the decoded payload — proves authenticity only, no spend |
784
+ | `POST /api/wallets/voucher/redeem` | Atomic single-use spend: first caller gets `200 redeemed` (plus `order: { status: 'redeemed' }` when the voucher funds a recorded order — no charge happens here) , replays get `409 already_redeemed` (with first-redeemer forensics), revoked vouchers get `409 revoked` |
785
+ | `GET /api/wallets/voucher/.well-known/jwks.json` | Public JWKS for offline signature verification |
668
786
 
669
787
  Redeem accepts optional `expectedAmountMinor` / `expectedCurrency` — mismatches are rejected (`400 amount_mismatch` / `currency_mismatch`) **before** the voucher is spent, so a mismatch never burns the voucher. Vouchers carry `aud` = your merchant id; declare `merchantId` on verify/redeem to have the audience check enforced (standard JWT libraries give you the same check free on offline verification).
670
788
 
@@ -889,6 +1007,18 @@ answered by the checkpoint; the agent needs no `apiEndpoint` of its own.
889
1007
 
890
1008
  Newest first. Full detail for every release is in [CHANGELOG.md](./CHANGELOG.md).
891
1009
 
1010
+ ### v5.18.0 — checkout kit fixes, orders, Express mounting, MPP interop
1011
+
1012
+ - UCP / ACP kits: holds never settle; `resolvePending()` finishes a pending settlement; `complete` claims the session (store `claim`) so concurrent completes can't both charge; sessions and idempotency keys are scoped to the caller.
1013
+ - UCP: exactly one payment instrument; `UCP-Agent` bound to the verified agent; `links` in the signed checkout; GETs don't re-sign; order capability (`GET /orders/{id}`, `buildUcpOrder`, signed order webhooks).
1014
+ - ACP: `unsupported_api_version`, echoed `Idempotency-Key` / `Request-Id`, 405 on canceling a finished session, order line items and `permalink_url`, `buildAcpWellKnown()`.
1015
+ - `expressHandler(kit)` mounts a kit on Express with the raw body kept; Express `defaultRoutePolicy` and observe-mode evaluation of unmatched routes for credentialed callers.
1016
+ - MPP challenge ids now match mppx; `buildMpp402Response()`; `verifyMppChallenge` checks the body digest and can accept each challenge once.
1017
+
1018
+ ### v5.17.2 — guard browse says where to approve
1019
+
1020
+ - `astrasync guard browse` prints the approval link while it waits for the owner's passkey.
1021
+
892
1022
  ### v5.17.1 — one approval for a route that holds its own
893
1023
 
894
1024
  - `approvalHeldByRoute` (Express): a route that holds its own owner approval receives an approval-only result instead of a refusal, and verify-access creates no approval — the owner approves once.
@@ -909,6 +1039,10 @@ Newest first. Full detail for every release is in [CHANGELOG.md](./CHANGELOG.md)
909
1039
  registers each call while it is in flight; otherwise call
910
1040
  `registerPending()` / `removePending()` around each interaction.
911
1041
 
1042
+ ### v5.15.0 — agentic commerce protocols
1043
+
1044
+ - AP2 v0.2 mandates, UCP (profile, negotiation, signatures, webhooks) and ACP checkout kits, KYAPay token verification, RFC 9421 request signing, request idempotency, and merchant helpers for x402 v2 and MPP.
1045
+
912
1046
  ### v4.1.0 — Trusted Agent Gateway naming + MCP metadata capture
913
1047
 
914
1048
  - The Next.js unverified-agent overlay is now the **verification interstitial**: `showInterstitial` option, `VerificationInterstitial` / `useVerificationInterstitial` / `VerificationInterstitialProps`. The old `showCommerceShield` / `CommerceShield` / `useCommerceShield` / `CommerceShieldProps` names keep working as deprecated aliases until the next major (`showInterstitial` wins when both options are set).
@@ -1,5 +1,5 @@
1
1
  import { RequestHandler, Request } from 'express';
2
- import { d as VerificationResult, E as ExpressMiddlewareOptions, a as AstraSyncCredentials } from '../types-DRb_plOh.mjs';
2
+ import { d as VerificationResult, E as ExpressMiddlewareOptions, a as AstraSyncCredentials } from '../types-Cns4l8gX.mjs';
3
3
 
4
4
  /**
5
5
  * AstraSync Universal Verification Gateway - Express Middleware
@@ -1,5 +1,5 @@
1
1
  import { RequestHandler, Request } from 'express';
2
- import { d as VerificationResult, E as ExpressMiddlewareOptions, a as AstraSyncCredentials } from '../types-DRb_plOh.js';
2
+ import { d as VerificationResult, E as ExpressMiddlewareOptions, a as AstraSyncCredentials } from '../types-Cns4l8gX.js';
3
3
 
4
4
  /**
5
5
  * AstraSync Universal Verification Gateway - Express Middleware
@@ -316,6 +316,23 @@ function detectCommerce(req, host) {
316
316
  }
317
317
  const signature = headerValue(req, "signature");
318
318
  const signatureInput = headerValue(req, "signature-input");
319
+ if (/\/checkout[-_]sessions(\/|$)/.test(req.uri) && headerValue(req, "ucp-agent")) {
320
+ const signed = Boolean(signature && signatureInput);
321
+ return {
322
+ protocol: "ucp",
323
+ artifacts: {
324
+ ucpRequest: { method, url, ...jsonBody && { body: jsonBody } },
325
+ ...signed && {
326
+ rfc9421: { headers, method, url, ...req.bodyText && { body: req.bodyText } }
327
+ }
328
+ },
329
+ evidence: {
330
+ path: req.uri,
331
+ ...signed && { keyid: signatureInput.match(/keyid="([^"]+)"/)?.[1] },
332
+ ...truncated
333
+ }
334
+ };
335
+ }
319
336
  if (signature && signatureInput) {
320
337
  const signatureAgent = headerValue(req, "signature-agent");
321
338
  const registryHint = headerValue(req, "x-agent-registry");
@@ -350,13 +367,6 @@ function detectCommerce(req, host) {
350
367
  evidence: { hasTimestamp: Boolean(acpTimestamp), ...truncated }
351
368
  };
352
369
  }
353
- if (/\/checkout[-_]sessions(\/|$)/.test(req.uri) && headerValue(req, "ucp-agent")) {
354
- return {
355
- protocol: "ucp",
356
- artifacts: { ucpRequest: { method, url, ...jsonBody && { body: jsonBody } } },
357
- evidence: { path: req.uri, ...truncated }
358
- };
359
- }
360
370
  if (jsonBody) {
361
371
  const intent = jsonBody.intent_mandate ?? jsonBody.intentMandate;
362
372
  const cart = jsonBody.cart_mandate ?? jsonBody.cartMandate;
@@ -469,7 +479,7 @@ function routePolicyOf(route) {
469
479
  }
470
480
 
471
481
  // src/version.ts
472
- var SDK_VERSION = "5.17.2";
482
+ var SDK_VERSION = "5.18.0";
473
483
 
474
484
  // src/http.ts
475
485
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -2361,6 +2371,7 @@ function createMiddleware(options) {
2361
2371
  anonymousBeacon = true,
2362
2372
  trustVerifiedHop = false,
2363
2373
  verifiedHopMaxAgeMs,
2374
+ defaultRoutePolicy = "pass",
2364
2375
  ...config
2365
2376
  } = options;
2366
2377
  if (config.apiBaseUrl) {
@@ -2426,12 +2437,15 @@ function createMiddleware(options) {
2426
2437
  });
2427
2438
  }
2428
2439
  const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
2429
- const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
2440
+ const matchedRoute = findRouteConfig(cachedRoutes, req.path, req.method, {
2430
2441
  caseInsensitive: caseInsensitiveRouteMatch,
2431
2442
  logShadowDivergence: true,
2432
2443
  correlationId
2433
2444
  });
2434
- if (!routeConfig) {
2445
+ const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
2446
+ const credentialed = Boolean(credentials.astraId || credentials.apiKey || credentials.jwt);
2447
+ const observeUnmatched = !matchedRoute && (config.evaluateAlwaysIfCredentialed === true && Boolean(credentials.astraId) || defaultRoutePolicy === "observe" && credentialed);
2448
+ if (!matchedRoute && !observeUnmatched) {
2435
2449
  if (config.setPassThroughHeader) {
2436
2450
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
2437
2451
  res.setHeader(
@@ -2442,10 +2456,14 @@ function createMiddleware(options) {
2442
2456
  beaconPassThrough?.(req);
2443
2457
  return next();
2444
2458
  }
2459
+ const routeConfig = matchedRoute ?? {
2460
+ pattern: req.path,
2461
+ method: req.method,
2462
+ observe: true
2463
+ };
2445
2464
  const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
2446
- const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
2447
2465
  const shouldEnforce = !routeConfig.observe;
2448
- if (routeConfig.observe && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
2466
+ if (routeConfig.observe && !observeUnmatched && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
2449
2467
  if (config.setPassThroughHeader) {
2450
2468
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
2451
2469
  res.setHeader("X-Astra-Gateway-Reason", "route-observe");