@astrasyncai/verification-gateway 5.18.1 → 5.20.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 (129) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/README.md +217 -4
  3. package/dist/adapter-interface/interface.d.mts +2 -2
  4. package/dist/adapter-interface/interface.d.ts +2 -2
  5. package/dist/adapters/express.d.mts +9 -1
  6. package/dist/adapters/express.d.ts +9 -1
  7. package/dist/adapters/express.js +160 -52
  8. package/dist/adapters/express.js.map +1 -1
  9. package/dist/adapters/express.mjs +160 -52
  10. package/dist/adapters/express.mjs.map +1 -1
  11. package/dist/adapters/http-pdlss.d.mts +7 -1
  12. package/dist/adapters/http-pdlss.d.ts +7 -1
  13. package/dist/adapters/http-pdlss.js +18 -2
  14. package/dist/adapters/http-pdlss.js.map +1 -1
  15. package/dist/adapters/http-pdlss.mjs +16 -1
  16. package/dist/adapters/http-pdlss.mjs.map +1 -1
  17. package/dist/adapters/kit-express.d.mts +93 -1
  18. package/dist/adapters/kit-express.d.ts +93 -1
  19. package/dist/adapters/kit-express.js +181 -2
  20. package/dist/adapters/kit-express.js.map +1 -1
  21. package/dist/adapters/kit-express.mjs +180 -2
  22. package/dist/adapters/kit-express.mjs.map +1 -1
  23. package/dist/adapters/mcp.d.mts +102 -9
  24. package/dist/adapters/mcp.d.ts +102 -9
  25. package/dist/adapters/mcp.js +242 -19
  26. package/dist/adapters/mcp.js.map +1 -1
  27. package/dist/adapters/mcp.mjs +240 -19
  28. package/dist/adapters/mcp.mjs.map +1 -1
  29. package/dist/adapters/nextjs.d.mts +1 -1
  30. package/dist/adapters/nextjs.d.ts +1 -1
  31. package/dist/adapters/nextjs.js +97 -14
  32. package/dist/adapters/nextjs.js.map +1 -1
  33. package/dist/adapters/nextjs.mjs +97 -14
  34. package/dist/adapters/nextjs.mjs.map +1 -1
  35. package/dist/adapters/sdk.d.mts +101 -2
  36. package/dist/adapters/sdk.d.ts +101 -2
  37. package/dist/adapters/sdk.js +199 -12
  38. package/dist/adapters/sdk.js.map +1 -1
  39. package/dist/adapters/sdk.mjs +199 -12
  40. package/dist/adapters/sdk.mjs.map +1 -1
  41. package/dist/agent/index.d.mts +1 -1
  42. package/dist/agent/index.d.ts +1 -1
  43. package/dist/agent/index.js +1 -1
  44. package/dist/agent/index.js.map +1 -1
  45. package/dist/agent/index.mjs +1 -1
  46. package/dist/agent/index.mjs.map +1 -1
  47. package/dist/bin/astrasync-claude-hook.js +94 -16
  48. package/dist/bin/astrasync-codex-hook.js +94 -16
  49. package/dist/bin/astrasync-guard.js +94 -16
  50. package/dist/bin/astrasync.js +127 -16
  51. package/dist/browser/background.js +94 -16
  52. package/dist/browser/background.js.map +1 -1
  53. package/dist/browser/background.mjs +94 -16
  54. package/dist/browser/background.mjs.map +1 -1
  55. package/dist/cli/index.js +1 -1
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/index.mjs +1 -1
  58. package/dist/cli/index.mjs.map +1 -1
  59. package/dist/codex/index.js +94 -16
  60. package/dist/codex/index.js.map +1 -1
  61. package/dist/codex/index.mjs +94 -16
  62. package/dist/codex/index.mjs.map +1 -1
  63. package/dist/cursor/extension.js +94 -16
  64. package/dist/cursor/extension.js.map +1 -1
  65. package/dist/cursor/extension.mjs +94 -16
  66. package/dist/cursor/extension.mjs.map +1 -1
  67. package/dist/edge-config.d.mts +1 -1
  68. package/dist/edge-config.d.ts +1 -1
  69. package/dist/edge-config.js +1 -1
  70. package/dist/edge-config.js.map +1 -1
  71. package/dist/edge-config.mjs +1 -1
  72. package/dist/edge-config.mjs.map +1 -1
  73. package/dist/edge-core/index.d.mts +1 -1
  74. package/dist/edge-core/index.d.ts +1 -1
  75. package/dist/edge-core/index.js +69 -12
  76. package/dist/edge-core/index.js.map +1 -1
  77. package/dist/edge-core/index.mjs +69 -12
  78. package/dist/edge-core/index.mjs.map +1 -1
  79. package/dist/gateway/gateway.d.mts +2 -2
  80. package/dist/gateway/gateway.d.ts +2 -2
  81. package/dist/gateway/gateway.js +94 -16
  82. package/dist/gateway/gateway.js.map +1 -1
  83. package/dist/gateway/gateway.mjs +94 -16
  84. package/dist/gateway/gateway.mjs.map +1 -1
  85. package/dist/git-trigger/git-hooks.d.mts +1 -1
  86. package/dist/git-trigger/git-hooks.d.ts +1 -1
  87. package/dist/index.d.mts +706 -34
  88. package/dist/index.d.ts +706 -34
  89. package/dist/index.js +1391 -205
  90. package/dist/index.js.map +1 -1
  91. package/dist/index.mjs +1374 -195
  92. package/dist/index.mjs.map +1 -1
  93. package/dist/local-evaluator/evaluator.d.mts +2 -2
  94. package/dist/local-evaluator/evaluator.d.ts +2 -2
  95. package/dist/registration/index.d.mts +11 -2
  96. package/dist/registration/index.d.ts +11 -2
  97. package/dist/registration/index.js +1 -1
  98. package/dist/registration/index.js.map +1 -1
  99. package/dist/registration/index.mjs +1 -1
  100. package/dist/registration/index.mjs.map +1 -1
  101. package/dist/transport/index.d.mts +1 -1
  102. package/dist/transport/index.d.ts +1 -1
  103. package/dist/transport/index.js +3 -2
  104. package/dist/transport/index.js.map +1 -1
  105. package/dist/transport/index.mjs +3 -2
  106. package/dist/transport/index.mjs.map +1 -1
  107. package/dist/{types-BRz2U0Pn.d.ts → types-B_HLhZ9n.d.ts} +1 -1
  108. package/dist/{types-BfILnheI.d.mts → types-CU17R4Nr.d.mts} +11 -0
  109. package/dist/{types-BfILnheI.d.ts → types-CU17R4Nr.d.ts} +11 -0
  110. package/dist/{types-Cns4l8gX.d.mts → types-CxjqmPl-.d.mts} +115 -13
  111. package/dist/{types-Cns4l8gX.d.ts → types-CxjqmPl-.d.ts} +115 -13
  112. package/dist/{types-3ObBpsnf.d.mts → types-D37aSQ9c.d.mts} +106 -3
  113. package/dist/{types-CfBpm3w5.d.mts → types-Djpz-L0w.d.mts} +1 -1
  114. package/dist/{types-BoxZEYPS.d.ts → types-Dn8tXnei.d.ts} +106 -3
  115. package/dist/ui/index.d.mts +1 -1
  116. package/dist/ui/index.d.ts +1 -1
  117. package/dist/verify.d.mts +50 -4
  118. package/dist/verify.d.ts +50 -4
  119. package/dist/verify.js +93 -14
  120. package/dist/verify.js.map +1 -1
  121. package/dist/verify.mjs +91 -14
  122. package/dist/verify.mjs.map +1 -1
  123. package/dist/webhooks.d.mts +16 -3
  124. package/dist/webhooks.d.ts +16 -3
  125. package/dist/webhooks.js +24 -13
  126. package/dist/webhooks.js.map +1 -1
  127. package/dist/webhooks.mjs +24 -13
  128. package/dist/webhooks.mjs.map +1 -1
  129. package/package.json +1 -1
@@ -452,7 +452,7 @@ function routePolicyOf(route) {
452
452
  }
453
453
 
454
454
  // src/version.ts
455
- var SDK_VERSION = "5.18.1";
455
+ var SDK_VERSION = "5.20.0";
456
456
 
457
457
  // src/http.ts
458
458
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -770,7 +770,12 @@ function getCacheKey(request, counterpartyId) {
770
770
  // its start_checkout quote was served the cached quote-leg grant (no
771
771
  // settlementOutcome) and never reached the backend. Same F-A1-07 bug class.
772
772
  request.commercePhase || "",
773
- request.checkoutSessionId || ""
773
+ request.checkoutSessionId || "",
774
+ // 5.19.0: a route_config purpose claim is checked server-side against
775
+ // the stored rule for this method + path, so it changes the verdict.
776
+ request.purposeSource || "",
777
+ request.requestMethod || "",
778
+ request.requestPath || ""
774
779
  ].join("|");
775
780
  }
776
781
  function getCachedResult(request, counterpartyId) {
@@ -962,6 +967,9 @@ async function callVerifyAccessAPI(config, request) {
962
967
  if (requestData.enableRuntimeChallenge)
963
968
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
964
969
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
970
+ if (config.actingProof?.connectorToken) {
971
+ body.actingProof = { connectorToken: config.actingProof.connectorToken };
972
+ }
965
973
  if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
966
974
  if (requestData.createSession) body.createSession = requestData.createSession;
967
975
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
@@ -978,6 +986,11 @@ async function callVerifyAccessAPI(config, request) {
978
986
  if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;
979
987
  if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
980
988
  if (requestData.attemptId) body.attemptId = requestData.attemptId;
989
+ if (requestData.purposeSource) body.purposeSource = requestData.purposeSource;
990
+ if (requestData.agentArtifacts) body.agentArtifacts = requestData.agentArtifacts;
991
+ if (requestData.metadata) body.metadata = requestData.metadata;
992
+ if (requestData.requestMethod) body.requestMethod = requestData.requestMethod;
993
+ if (requestData.requestPath) body.requestPath = requestData.requestPath;
981
994
  if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
982
995
  body.sdkVersion = SDK_VERSION;
983
996
  if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
@@ -1124,22 +1137,46 @@ async function verify(config, request, options) {
1124
1137
  "[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access decisions (verified ID + runtime challenge + PDLSS + trust score). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
1125
1138
  );
1126
1139
  }
1127
- const uncacheable = Boolean(request.commerceArtifacts);
1140
+ const uncacheable = Boolean(request.commerceArtifacts) || Boolean(mergedConfig.actingProof);
1128
1141
  if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
1129
1142
  const cached = getCachedResult(request, mergedConfig.counterpartyId);
1130
1143
  if (cached) {
1131
1144
  if (mergedConfig.debug) {
1132
1145
  console.log("[VerificationGateway] Returning cached result");
1133
1146
  }
1134
- const hit = { ...cached };
1135
- if (request.attemptId) {
1136
- hit.attemptId = request.attemptId;
1137
- } else {
1138
- delete hit.attemptId;
1139
- }
1140
- return hit;
1147
+ return sharedVerdict(cached, request);
1148
+ }
1149
+ }
1150
+ if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
1151
+ const key = getCacheKey(request, mergedConfig.counterpartyId);
1152
+ const pending = inflightVerifies.get(key);
1153
+ if (pending) return sharedVerdict(await pending, request);
1154
+ const flight = verifyViaApi(mergedConfig, request, urls, uncacheable);
1155
+ inflightVerifies.set(key, flight);
1156
+ try {
1157
+ return await flight;
1158
+ } finally {
1159
+ inflightVerifies.delete(key);
1141
1160
  }
1142
1161
  }
1162
+ return verifyViaApi(mergedConfig, request, urls, uncacheable);
1163
+ }
1164
+ var inflightVerifies = /* @__PURE__ */ new Map();
1165
+ var sharedVerdicts = /* @__PURE__ */ new WeakSet();
1166
+ function isSharedVerdict(result) {
1167
+ return sharedVerdicts.has(result);
1168
+ }
1169
+ function sharedVerdict(source, request) {
1170
+ const hit = { ...source };
1171
+ if (request.attemptId) {
1172
+ hit.attemptId = request.attemptId;
1173
+ } else {
1174
+ delete hit.attemptId;
1175
+ }
1176
+ sharedVerdicts.add(hit);
1177
+ return hit;
1178
+ }
1179
+ async function verifyViaApi(mergedConfig, request, urls, uncacheable) {
1143
1180
  const enrichedRequest = { ...request };
1144
1181
  if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
1145
1182
  enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
@@ -1167,9 +1204,18 @@ async function verify(config, request, options) {
1167
1204
  if (!apiResponse.access?.allowed) {
1168
1205
  const aggregatedFailures = apiResponse.access?.failures;
1169
1206
  const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
1207
+ const denyAgent = idVerifiedFromBackend && apiResponse.agent ? {
1208
+ astraId: apiResponse.agent.astraId,
1209
+ name: apiResponse.agent.name,
1210
+ trustScore: apiResponse.agent.trustScore,
1211
+ trustLevel: getTrustLevel(apiResponse.agent.trustScore),
1212
+ blockchainAnchored: apiResponse.agent.blockchainStatus === "anchored",
1213
+ status: apiResponse.agent.agentStatus
1214
+ } : void 0;
1170
1215
  const result2 = {
1171
1216
  identityVerified: idVerifiedFromBackend,
1172
1217
  policyAllowed: false,
1218
+ ...denyAgent && { agent: denyAgent },
1173
1219
  // 3.6.0: the server now emits top-level denialReasons on every deny
1174
1220
  // path — prefer it verbatim; the failure-message reconstruction remains
1175
1221
  // as the fallback for older backends.
@@ -1209,7 +1255,9 @@ async function verify(config, request, options) {
1209
1255
  fulfilment: apiResponse.fulfilment,
1210
1256
  mandates: apiResponse.mandates,
1211
1257
  kyaToken: apiResponse.kyaToken,
1212
- aep: apiResponse.aep
1258
+ aep: apiResponse.aep,
1259
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
1260
+ warnings: apiResponse.warnings
1213
1261
  };
1214
1262
  return result2;
1215
1263
  }
@@ -1277,7 +1325,9 @@ async function verify(config, request, options) {
1277
1325
  // 5.15.0: merchant-lane AP2 mandates + KYAPay identity token.
1278
1326
  mandates: apiResponse.mandates,
1279
1327
  kyaToken: apiResponse.kyaToken,
1280
- aep: apiResponse.aep
1328
+ aep: apiResponse.aep,
1329
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
1330
+ warnings: apiResponse.warnings
1281
1331
  };
1282
1332
  if (result.recommendation === "deny") {
1283
1333
  result.policyAllowed = false;
@@ -1303,8 +1353,18 @@ async function verify(config, request, options) {
1303
1353
  cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
1304
1354
  }
1305
1355
  }
1356
+ notifyVerificationListeners(request, result);
1306
1357
  return result;
1307
1358
  }
1359
+ var verificationListeners = /* @__PURE__ */ new Set();
1360
+ function notifyVerificationListeners(request, result) {
1361
+ for (const listener of verificationListeners) {
1362
+ try {
1363
+ listener(result, request);
1364
+ } catch {
1365
+ }
1366
+ }
1367
+ }
1308
1368
  async function recordDecision(config, sessionId, decision, reason) {
1309
1369
  const headers = { "Content-Type": "application/json" };
1310
1370
  if (config.apiKey) {
@@ -1319,7 +1379,7 @@ async function recordDecision(config, sessionId, decision, reason) {
1319
1379
  });
1320
1380
  }
1321
1381
  var refusedPolicyReads = /* @__PURE__ */ new Set();
1322
- async function fetchRoutes(config, counterpartyId) {
1382
+ async function fetchRoutes(config, counterpartyId, options = {}) {
1323
1383
  if (!counterpartyId) return null;
1324
1384
  const headers = { "Content-Type": "application/json" };
1325
1385
  if (config.apiKey) {
@@ -1327,8 +1387,9 @@ async function fetchRoutes(config, counterpartyId) {
1327
1387
  headers["X-API-Key"] = config.apiKey;
1328
1388
  }
1329
1389
  try {
1390
+ headers["X-AstraSync-SDK-Version"] = SDK_VERSION;
1330
1391
  const response = await sdkFetch(
1331
- `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
1392
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes` + (options.codeGates?.length ? `?codeGates=${options.codeGates.map(encodeURIComponent).join(",")}` : ""),
1332
1393
  { method: "GET", headers }
1333
1394
  );
1334
1395
  if (!response.ok) {
@@ -1341,6 +1402,13 @@ async function fetchRoutes(config, counterpartyId) {
1341
1402
  return null;
1342
1403
  }
1343
1404
  const body = await response.json();
1405
+ const type = body.data?.counterpartyType;
1406
+ if (typeof type === "string" && options.onEndpointType) {
1407
+ try {
1408
+ options.onEndpointType(type);
1409
+ } catch {
1410
+ }
1411
+ }
1344
1412
  return body.data?.routes ?? [];
1345
1413
  } catch {
1346
1414
  return null;
@@ -1374,6 +1442,42 @@ async function reportCounterpartyPreCheckFailure(config, data) {
1374
1442
  });
1375
1443
  }
1376
1444
 
1445
+ // src/adapters/routes-cache.ts
1446
+ var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1447
+ function createRoutesCache(config, opts = {}) {
1448
+ const refreshMs = opts.refreshMs ?? DEFAULT_ROUTES_REFRESH_MS;
1449
+ let cached = [];
1450
+ let lastFetchAt = 0;
1451
+ let refreshing = null;
1452
+ async function refresh() {
1453
+ if (!config.counterpartyId) {
1454
+ opts.onNoCounterparty?.();
1455
+ return;
1456
+ }
1457
+ const fetched = await fetchRoutes(config, config.counterpartyId, opts.fetchOptions);
1458
+ if (fetched) {
1459
+ cached = fetched;
1460
+ lastFetchAt = Date.now();
1461
+ opts.onFetched?.(fetched);
1462
+ }
1463
+ }
1464
+ function start() {
1465
+ refreshing = refresh().catch(() => void 0).finally(() => {
1466
+ refreshing = null;
1467
+ });
1468
+ }
1469
+ start();
1470
+ return {
1471
+ async current() {
1472
+ if (refreshing) await refreshing.catch(() => {
1473
+ });
1474
+ if (config.counterpartyId && Date.now() - lastFetchAt > refreshMs) start();
1475
+ return cached;
1476
+ },
1477
+ peek: () => cached
1478
+ };
1479
+ }
1480
+
1377
1481
  // src/transport/http.ts
1378
1482
  var HEADER_PREFIX = "X-Astra-";
1379
1483
  function extractHttpCredentials(headers) {
@@ -1523,6 +1627,20 @@ function resolveHttpPdlss(input) {
1523
1627
  }
1524
1628
  return { purpose, action, purposeSource, actionSource };
1525
1629
  }
1630
+ function wirePurposeSource(source) {
1631
+ switch (source) {
1632
+ case "route_config":
1633
+ return "route_config";
1634
+ case "header":
1635
+ case "legacy_header":
1636
+ case "query":
1637
+ return "header";
1638
+ case "custom_extractor":
1639
+ return "body";
1640
+ default:
1641
+ return "default";
1642
+ }
1643
+ }
1526
1644
 
1527
1645
  // src/adapters/approval-gate.ts
1528
1646
  var APPROVAL_REASON = "Transaction is above the autonomous limit and requires human approval. The agent owner can approve or deny this transaction in the AstraSync dashboard.";
@@ -2323,7 +2441,6 @@ function defaultOnDenied(result, _req, res) {
2323
2441
  }
2324
2442
  });
2325
2443
  }
2326
- var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
2327
2444
  function createMiddleware(options) {
2328
2445
  if ("routes" in options) {
2329
2446
  console.warn(
@@ -2339,7 +2456,7 @@ function createMiddleware(options) {
2339
2456
  recordDecisions,
2340
2457
  enableRuntimeChallenge = true,
2341
2458
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
2342
- failOnError = "open",
2459
+ failOnError = "closed",
2343
2460
  caseInsensitiveRouteMatch = false,
2344
2461
  anonymousBeacon = true,
2345
2462
  trustVerifiedHop = false,
@@ -2363,26 +2480,19 @@ function createMiddleware(options) {
2363
2480
  rawHeaders: req.headers
2364
2481
  });
2365
2482
  } : void 0;
2366
- let cachedRoutes = [];
2367
- let lastFetchAt = 0;
2368
- let refreshing = null;
2369
2483
  let warnedNoCounterparty = false;
2370
2484
  let warnedEmptyRoutes = false;
2371
- async function refreshRoutes() {
2372
- if (!config.counterpartyId) {
2373
- if (!warnedNoCounterparty) {
2374
- console.warn(
2375
- "[VerificationGateway] No counterpartyId configured \u2014 falling through (allow all). Per-route policy lives in the AstraSync dashboard now; register the endpoint and set counterpartyId in your middleware config to enforce policy."
2376
- );
2377
- warnedNoCounterparty = true;
2378
- }
2379
- return;
2380
- }
2381
- const fetched = await fetchRoutes(config, config.counterpartyId);
2382
- if (fetched) {
2383
- cachedRoutes = fetched;
2384
- lastFetchAt = Date.now();
2385
- if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
2485
+ const routesCache = createRoutesCache(config, {
2486
+ refreshMs: routesRefreshMs,
2487
+ onNoCounterparty: () => {
2488
+ if (warnedNoCounterparty) return;
2489
+ console.warn(
2490
+ "[VerificationGateway] No counterpartyId configured \u2014 falling through (allow all). Per-route policy lives in the AstraSync dashboard now; register the endpoint and set counterpartyId in your middleware config to enforce policy."
2491
+ );
2492
+ warnedNoCounterparty = true;
2493
+ },
2494
+ onFetched: (routes) => {
2495
+ if (routes.length === 0 && !warnedEmptyRoutes) {
2386
2496
  const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
2387
2497
  console.warn(
2388
2498
  `[VerificationGateway] No route policy configured for ${config.counterpartyId}. Gateway is in pass-through mode for ALL traffic until you add at least one route. Configure at ${dashboard}/dashboard/endpoints/${config.counterpartyId}/routes`
@@ -2390,9 +2500,6 @@ function createMiddleware(options) {
2390
2500
  warnedEmptyRoutes = true;
2391
2501
  }
2392
2502
  }
2393
- }
2394
- refreshing = refreshRoutes().finally(() => {
2395
- refreshing = null;
2396
2503
  });
2397
2504
  return async (req, res, next) => {
2398
2505
  try {
@@ -2400,15 +2507,9 @@ function createMiddleware(options) {
2400
2507
  if (shouldSkip) {
2401
2508
  return next();
2402
2509
  }
2403
- if (refreshing) {
2404
- await refreshing.catch(() => {
2405
- });
2406
- }
2407
- if (config.counterpartyId && Date.now() - lastFetchAt > routesRefreshMs) {
2408
- refreshing = refreshRoutes().finally(() => {
2409
- refreshing = null;
2410
- });
2411
- }
2510
+ const cachedRoutes = (await routesCache.current()).filter(
2511
+ (r) => typeof r.pattern === "string" && !r.tool
2512
+ );
2412
2513
  const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
2413
2514
  const matchedRoute = findRouteConfig(cachedRoutes, req.path, req.method, {
2414
2515
  caseInsensitive: caseInsensitiveRouteMatch,
@@ -2532,6 +2633,12 @@ function createMiddleware(options) {
2532
2633
  ...commerceArtifacts && { commerceArtifacts },
2533
2634
  purpose,
2534
2635
  action: pdlssPair.action,
2636
+ // 5.19.0: the source + the request line let verify-access check a
2637
+ // route purpose against the stored rule (item 20: an MPP paid leg
2638
+ // keeps `shopping.purchase` instead of `commerce.payment.execute`).
2639
+ purposeSource: wirePurposeSource(pdlssPair.purposeSource),
2640
+ requestMethod: req.method,
2641
+ requestPath: req.path,
2535
2642
  resource: req.path,
2536
2643
  createSession: shouldRecordDecisions,
2537
2644
  counterpartyUrl,
@@ -2580,14 +2687,14 @@ function createMiddleware(options) {
2580
2687
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
2581
2688
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
2582
2689
  }
2583
- if (shouldRecordDecisions && sessionId) {
2690
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2584
2691
  recordDecision(config, sessionId, "granted").catch(() => {
2585
2692
  });
2586
2693
  }
2587
2694
  return next();
2588
2695
  }
2589
2696
  if (!result.policyAllowed) {
2590
- if (shouldRecordDecisions && sessionId) {
2697
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2591
2698
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
2592
2699
  });
2593
2700
  }
@@ -2600,7 +2707,7 @@ function createMiddleware(options) {
2600
2707
  }
2601
2708
  if (requiresHumanApproval(result)) {
2602
2709
  annotateApprovalRequired(result);
2603
- if (shouldRecordDecisions && sessionId) {
2710
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2604
2711
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
2605
2712
  });
2606
2713
  }
@@ -2609,7 +2716,8 @@ function createMiddleware(options) {
2609
2716
  return;
2610
2717
  }
2611
2718
  if (routeConfig.minTrustScore && result.agent) {
2612
- if (result.agent.trustScore < routeConfig.minTrustScore) {
2719
+ const score = result.agent.trustScore;
2720
+ if (typeof score !== "number" || score < routeConfig.minTrustScore) {
2613
2721
  const trustFailure = {
2614
2722
  dimension: "endpoint.trust",
2615
2723
  message: "Trust below the route requirement for this endpoint.",
@@ -2624,7 +2732,7 @@ function createMiddleware(options) {
2624
2732
  documentationUrl: wellKnownUrls.documentationUrl
2625
2733
  };
2626
2734
  }
2627
- if (shouldRecordDecisions && sessionId) {
2735
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2628
2736
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
2629
2737
  });
2630
2738
  }
@@ -2633,7 +2741,7 @@ function createMiddleware(options) {
2633
2741
  return;
2634
2742
  }
2635
2743
  }
2636
- if (shouldRecordDecisions && sessionId) {
2744
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2637
2745
  recordDecision(config, sessionId, "granted").catch(() => {
2638
2746
  });
2639
2747
  }