@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
@@ -479,7 +479,7 @@ function routePolicyOf(route) {
479
479
  }
480
480
 
481
481
  // src/version.ts
482
- var SDK_VERSION = "5.18.1";
482
+ var SDK_VERSION = "5.20.0";
483
483
 
484
484
  // src/http.ts
485
485
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -797,7 +797,12 @@ function getCacheKey(request, counterpartyId) {
797
797
  // its start_checkout quote was served the cached quote-leg grant (no
798
798
  // settlementOutcome) and never reached the backend. Same F-A1-07 bug class.
799
799
  request.commercePhase || "",
800
- request.checkoutSessionId || ""
800
+ request.checkoutSessionId || "",
801
+ // 5.19.0: a route_config purpose claim is checked server-side against
802
+ // the stored rule for this method + path, so it changes the verdict.
803
+ request.purposeSource || "",
804
+ request.requestMethod || "",
805
+ request.requestPath || ""
801
806
  ].join("|");
802
807
  }
803
808
  function getCachedResult(request, counterpartyId) {
@@ -989,6 +994,9 @@ async function callVerifyAccessAPI(config, request) {
989
994
  if (requestData.enableRuntimeChallenge)
990
995
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
991
996
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
997
+ if (config.actingProof?.connectorToken) {
998
+ body.actingProof = { connectorToken: config.actingProof.connectorToken };
999
+ }
992
1000
  if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
993
1001
  if (requestData.createSession) body.createSession = requestData.createSession;
994
1002
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
@@ -1005,6 +1013,11 @@ async function callVerifyAccessAPI(config, request) {
1005
1013
  if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;
1006
1014
  if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
1007
1015
  if (requestData.attemptId) body.attemptId = requestData.attemptId;
1016
+ if (requestData.purposeSource) body.purposeSource = requestData.purposeSource;
1017
+ if (requestData.agentArtifacts) body.agentArtifacts = requestData.agentArtifacts;
1018
+ if (requestData.metadata) body.metadata = requestData.metadata;
1019
+ if (requestData.requestMethod) body.requestMethod = requestData.requestMethod;
1020
+ if (requestData.requestPath) body.requestPath = requestData.requestPath;
1008
1021
  if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
1009
1022
  body.sdkVersion = SDK_VERSION;
1010
1023
  if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
@@ -1151,22 +1164,46 @@ async function verify(config, request, options) {
1151
1164
  "[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."
1152
1165
  );
1153
1166
  }
1154
- const uncacheable = Boolean(request.commerceArtifacts);
1167
+ const uncacheable = Boolean(request.commerceArtifacts) || Boolean(mergedConfig.actingProof);
1155
1168
  if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
1156
1169
  const cached = getCachedResult(request, mergedConfig.counterpartyId);
1157
1170
  if (cached) {
1158
1171
  if (mergedConfig.debug) {
1159
1172
  console.log("[VerificationGateway] Returning cached result");
1160
1173
  }
1161
- const hit = { ...cached };
1162
- if (request.attemptId) {
1163
- hit.attemptId = request.attemptId;
1164
- } else {
1165
- delete hit.attemptId;
1166
- }
1167
- return hit;
1174
+ return sharedVerdict(cached, request);
1175
+ }
1176
+ }
1177
+ if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
1178
+ const key = getCacheKey(request, mergedConfig.counterpartyId);
1179
+ const pending = inflightVerifies.get(key);
1180
+ if (pending) return sharedVerdict(await pending, request);
1181
+ const flight = verifyViaApi(mergedConfig, request, urls, uncacheable);
1182
+ inflightVerifies.set(key, flight);
1183
+ try {
1184
+ return await flight;
1185
+ } finally {
1186
+ inflightVerifies.delete(key);
1168
1187
  }
1169
1188
  }
1189
+ return verifyViaApi(mergedConfig, request, urls, uncacheable);
1190
+ }
1191
+ var inflightVerifies = /* @__PURE__ */ new Map();
1192
+ var sharedVerdicts = /* @__PURE__ */ new WeakSet();
1193
+ function isSharedVerdict(result) {
1194
+ return sharedVerdicts.has(result);
1195
+ }
1196
+ function sharedVerdict(source, request) {
1197
+ const hit = { ...source };
1198
+ if (request.attemptId) {
1199
+ hit.attemptId = request.attemptId;
1200
+ } else {
1201
+ delete hit.attemptId;
1202
+ }
1203
+ sharedVerdicts.add(hit);
1204
+ return hit;
1205
+ }
1206
+ async function verifyViaApi(mergedConfig, request, urls, uncacheable) {
1170
1207
  const enrichedRequest = { ...request };
1171
1208
  if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
1172
1209
  enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
@@ -1194,9 +1231,18 @@ async function verify(config, request, options) {
1194
1231
  if (!apiResponse.access?.allowed) {
1195
1232
  const aggregatedFailures = apiResponse.access?.failures;
1196
1233
  const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
1234
+ const denyAgent = idVerifiedFromBackend && apiResponse.agent ? {
1235
+ astraId: apiResponse.agent.astraId,
1236
+ name: apiResponse.agent.name,
1237
+ trustScore: apiResponse.agent.trustScore,
1238
+ trustLevel: getTrustLevel(apiResponse.agent.trustScore),
1239
+ blockchainAnchored: apiResponse.agent.blockchainStatus === "anchored",
1240
+ status: apiResponse.agent.agentStatus
1241
+ } : void 0;
1197
1242
  const result2 = {
1198
1243
  identityVerified: idVerifiedFromBackend,
1199
1244
  policyAllowed: false,
1245
+ ...denyAgent && { agent: denyAgent },
1200
1246
  // 3.6.0: the server now emits top-level denialReasons on every deny
1201
1247
  // path — prefer it verbatim; the failure-message reconstruction remains
1202
1248
  // as the fallback for older backends.
@@ -1236,7 +1282,9 @@ async function verify(config, request, options) {
1236
1282
  fulfilment: apiResponse.fulfilment,
1237
1283
  mandates: apiResponse.mandates,
1238
1284
  kyaToken: apiResponse.kyaToken,
1239
- aep: apiResponse.aep
1285
+ aep: apiResponse.aep,
1286
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
1287
+ warnings: apiResponse.warnings
1240
1288
  };
1241
1289
  return result2;
1242
1290
  }
@@ -1304,7 +1352,9 @@ async function verify(config, request, options) {
1304
1352
  // 5.15.0: merchant-lane AP2 mandates + KYAPay identity token.
1305
1353
  mandates: apiResponse.mandates,
1306
1354
  kyaToken: apiResponse.kyaToken,
1307
- aep: apiResponse.aep
1355
+ aep: apiResponse.aep,
1356
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
1357
+ warnings: apiResponse.warnings
1308
1358
  };
1309
1359
  if (result.recommendation === "deny") {
1310
1360
  result.policyAllowed = false;
@@ -1330,8 +1380,18 @@ async function verify(config, request, options) {
1330
1380
  cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
1331
1381
  }
1332
1382
  }
1383
+ notifyVerificationListeners(request, result);
1333
1384
  return result;
1334
1385
  }
1386
+ var verificationListeners = /* @__PURE__ */ new Set();
1387
+ function notifyVerificationListeners(request, result) {
1388
+ for (const listener of verificationListeners) {
1389
+ try {
1390
+ listener(result, request);
1391
+ } catch {
1392
+ }
1393
+ }
1394
+ }
1335
1395
  async function recordDecision(config, sessionId, decision, reason) {
1336
1396
  const headers = { "Content-Type": "application/json" };
1337
1397
  if (config.apiKey) {
@@ -1346,7 +1406,7 @@ async function recordDecision(config, sessionId, decision, reason) {
1346
1406
  });
1347
1407
  }
1348
1408
  var refusedPolicyReads = /* @__PURE__ */ new Set();
1349
- async function fetchRoutes(config, counterpartyId) {
1409
+ async function fetchRoutes(config, counterpartyId, options = {}) {
1350
1410
  if (!counterpartyId) return null;
1351
1411
  const headers = { "Content-Type": "application/json" };
1352
1412
  if (config.apiKey) {
@@ -1354,8 +1414,9 @@ async function fetchRoutes(config, counterpartyId) {
1354
1414
  headers["X-API-Key"] = config.apiKey;
1355
1415
  }
1356
1416
  try {
1417
+ headers["X-AstraSync-SDK-Version"] = SDK_VERSION;
1357
1418
  const response = await sdkFetch(
1358
- `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
1419
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes` + (options.codeGates?.length ? `?codeGates=${options.codeGates.map(encodeURIComponent).join(",")}` : ""),
1359
1420
  { method: "GET", headers }
1360
1421
  );
1361
1422
  if (!response.ok) {
@@ -1368,6 +1429,13 @@ async function fetchRoutes(config, counterpartyId) {
1368
1429
  return null;
1369
1430
  }
1370
1431
  const body = await response.json();
1432
+ const type = body.data?.counterpartyType;
1433
+ if (typeof type === "string" && options.onEndpointType) {
1434
+ try {
1435
+ options.onEndpointType(type);
1436
+ } catch {
1437
+ }
1438
+ }
1371
1439
  return body.data?.routes ?? [];
1372
1440
  } catch {
1373
1441
  return null;
@@ -1401,6 +1469,42 @@ async function reportCounterpartyPreCheckFailure(config, data) {
1401
1469
  });
1402
1470
  }
1403
1471
 
1472
+ // src/adapters/routes-cache.ts
1473
+ var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1474
+ function createRoutesCache(config, opts = {}) {
1475
+ const refreshMs = opts.refreshMs ?? DEFAULT_ROUTES_REFRESH_MS;
1476
+ let cached = [];
1477
+ let lastFetchAt = 0;
1478
+ let refreshing = null;
1479
+ async function refresh() {
1480
+ if (!config.counterpartyId) {
1481
+ opts.onNoCounterparty?.();
1482
+ return;
1483
+ }
1484
+ const fetched = await fetchRoutes(config, config.counterpartyId, opts.fetchOptions);
1485
+ if (fetched) {
1486
+ cached = fetched;
1487
+ lastFetchAt = Date.now();
1488
+ opts.onFetched?.(fetched);
1489
+ }
1490
+ }
1491
+ function start() {
1492
+ refreshing = refresh().catch(() => void 0).finally(() => {
1493
+ refreshing = null;
1494
+ });
1495
+ }
1496
+ start();
1497
+ return {
1498
+ async current() {
1499
+ if (refreshing) await refreshing.catch(() => {
1500
+ });
1501
+ if (config.counterpartyId && Date.now() - lastFetchAt > refreshMs) start();
1502
+ return cached;
1503
+ },
1504
+ peek: () => cached
1505
+ };
1506
+ }
1507
+
1404
1508
  // src/transport/http.ts
1405
1509
  var HEADER_PREFIX = "X-Astra-";
1406
1510
  function extractHttpCredentials(headers) {
@@ -1550,6 +1654,20 @@ function resolveHttpPdlss(input) {
1550
1654
  }
1551
1655
  return { purpose, action, purposeSource, actionSource };
1552
1656
  }
1657
+ function wirePurposeSource(source) {
1658
+ switch (source) {
1659
+ case "route_config":
1660
+ return "route_config";
1661
+ case "header":
1662
+ case "legacy_header":
1663
+ case "query":
1664
+ return "header";
1665
+ case "custom_extractor":
1666
+ return "body";
1667
+ default:
1668
+ return "default";
1669
+ }
1670
+ }
1553
1671
 
1554
1672
  // src/adapters/approval-gate.ts
1555
1673
  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.";
@@ -2350,7 +2468,6 @@ function defaultOnDenied(result, _req, res) {
2350
2468
  }
2351
2469
  });
2352
2470
  }
2353
- var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
2354
2471
  function createMiddleware(options) {
2355
2472
  if ("routes" in options) {
2356
2473
  console.warn(
@@ -2366,7 +2483,7 @@ function createMiddleware(options) {
2366
2483
  recordDecisions,
2367
2484
  enableRuntimeChallenge = true,
2368
2485
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
2369
- failOnError = "open",
2486
+ failOnError = "closed",
2370
2487
  caseInsensitiveRouteMatch = false,
2371
2488
  anonymousBeacon = true,
2372
2489
  trustVerifiedHop = false,
@@ -2390,26 +2507,19 @@ function createMiddleware(options) {
2390
2507
  rawHeaders: req.headers
2391
2508
  });
2392
2509
  } : void 0;
2393
- let cachedRoutes = [];
2394
- let lastFetchAt = 0;
2395
- let refreshing = null;
2396
2510
  let warnedNoCounterparty = false;
2397
2511
  let warnedEmptyRoutes = false;
2398
- async function refreshRoutes() {
2399
- if (!config.counterpartyId) {
2400
- if (!warnedNoCounterparty) {
2401
- console.warn(
2402
- "[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."
2403
- );
2404
- warnedNoCounterparty = true;
2405
- }
2406
- return;
2407
- }
2408
- const fetched = await fetchRoutes(config, config.counterpartyId);
2409
- if (fetched) {
2410
- cachedRoutes = fetched;
2411
- lastFetchAt = Date.now();
2412
- if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
2512
+ const routesCache = createRoutesCache(config, {
2513
+ refreshMs: routesRefreshMs,
2514
+ onNoCounterparty: () => {
2515
+ if (warnedNoCounterparty) return;
2516
+ console.warn(
2517
+ "[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."
2518
+ );
2519
+ warnedNoCounterparty = true;
2520
+ },
2521
+ onFetched: (routes) => {
2522
+ if (routes.length === 0 && !warnedEmptyRoutes) {
2413
2523
  const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
2414
2524
  console.warn(
2415
2525
  `[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`
@@ -2417,9 +2527,6 @@ function createMiddleware(options) {
2417
2527
  warnedEmptyRoutes = true;
2418
2528
  }
2419
2529
  }
2420
- }
2421
- refreshing = refreshRoutes().finally(() => {
2422
- refreshing = null;
2423
2530
  });
2424
2531
  return async (req, res, next) => {
2425
2532
  try {
@@ -2427,15 +2534,9 @@ function createMiddleware(options) {
2427
2534
  if (shouldSkip) {
2428
2535
  return next();
2429
2536
  }
2430
- if (refreshing) {
2431
- await refreshing.catch(() => {
2432
- });
2433
- }
2434
- if (config.counterpartyId && Date.now() - lastFetchAt > routesRefreshMs) {
2435
- refreshing = refreshRoutes().finally(() => {
2436
- refreshing = null;
2437
- });
2438
- }
2537
+ const cachedRoutes = (await routesCache.current()).filter(
2538
+ (r) => typeof r.pattern === "string" && !r.tool
2539
+ );
2439
2540
  const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
2440
2541
  const matchedRoute = findRouteConfig(cachedRoutes, req.path, req.method, {
2441
2542
  caseInsensitive: caseInsensitiveRouteMatch,
@@ -2559,6 +2660,12 @@ function createMiddleware(options) {
2559
2660
  ...commerceArtifacts && { commerceArtifacts },
2560
2661
  purpose,
2561
2662
  action: pdlssPair.action,
2663
+ // 5.19.0: the source + the request line let verify-access check a
2664
+ // route purpose against the stored rule (item 20: an MPP paid leg
2665
+ // keeps `shopping.purchase` instead of `commerce.payment.execute`).
2666
+ purposeSource: wirePurposeSource(pdlssPair.purposeSource),
2667
+ requestMethod: req.method,
2668
+ requestPath: req.path,
2562
2669
  resource: req.path,
2563
2670
  createSession: shouldRecordDecisions,
2564
2671
  counterpartyUrl,
@@ -2607,14 +2714,14 @@ function createMiddleware(options) {
2607
2714
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
2608
2715
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
2609
2716
  }
2610
- if (shouldRecordDecisions && sessionId) {
2717
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2611
2718
  recordDecision(config, sessionId, "granted").catch(() => {
2612
2719
  });
2613
2720
  }
2614
2721
  return next();
2615
2722
  }
2616
2723
  if (!result.policyAllowed) {
2617
- if (shouldRecordDecisions && sessionId) {
2724
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2618
2725
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
2619
2726
  });
2620
2727
  }
@@ -2627,7 +2734,7 @@ function createMiddleware(options) {
2627
2734
  }
2628
2735
  if (requiresHumanApproval(result)) {
2629
2736
  annotateApprovalRequired(result);
2630
- if (shouldRecordDecisions && sessionId) {
2737
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2631
2738
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
2632
2739
  });
2633
2740
  }
@@ -2636,7 +2743,8 @@ function createMiddleware(options) {
2636
2743
  return;
2637
2744
  }
2638
2745
  if (routeConfig.minTrustScore && result.agent) {
2639
- if (result.agent.trustScore < routeConfig.minTrustScore) {
2746
+ const score = result.agent.trustScore;
2747
+ if (typeof score !== "number" || score < routeConfig.minTrustScore) {
2640
2748
  const trustFailure = {
2641
2749
  dimension: "endpoint.trust",
2642
2750
  message: "Trust below the route requirement for this endpoint.",
@@ -2651,7 +2759,7 @@ function createMiddleware(options) {
2651
2759
  documentationUrl: wellKnownUrls.documentationUrl
2652
2760
  };
2653
2761
  }
2654
- if (shouldRecordDecisions && sessionId) {
2762
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2655
2763
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
2656
2764
  });
2657
2765
  }
@@ -2660,7 +2768,7 @@ function createMiddleware(options) {
2660
2768
  return;
2661
2769
  }
2662
2770
  }
2663
- if (shouldRecordDecisions && sessionId) {
2771
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2664
2772
  recordDecision(config, sessionId, "granted").catch(() => {
2665
2773
  });
2666
2774
  }