@astrasyncai/verification-gateway 3.0.0 → 3.2.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 (87) hide show
  1. package/dist/adapter-interface/interface.d.mts +2 -2
  2. package/dist/adapter-interface/interface.d.ts +2 -2
  3. package/dist/adapters/express.d.mts +2 -2
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.js +145 -93
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +145 -93
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +29 -11
  10. package/dist/adapters/mcp.d.ts +29 -11
  11. package/dist/adapters/mcp.js +43 -102
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +43 -102
  14. package/dist/adapters/mcp.mjs.map +1 -1
  15. package/dist/adapters/nextjs.d.mts +2 -2
  16. package/dist/adapters/nextjs.d.ts +2 -2
  17. package/dist/adapters/nextjs.js +126 -56
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +126 -56
  20. package/dist/adapters/nextjs.mjs.map +1 -1
  21. package/dist/adapters/sdk.d.mts +2 -2
  22. package/dist/adapters/sdk.d.ts +2 -2
  23. package/dist/adapters/sdk.js +25 -14
  24. package/dist/adapters/sdk.js.map +1 -1
  25. package/dist/adapters/sdk.mjs +25 -14
  26. package/dist/adapters/sdk.mjs.map +1 -1
  27. package/dist/agent/index.d.mts +2 -2
  28. package/dist/agent/index.d.ts +2 -2
  29. package/dist/agent/index.js +3 -0
  30. package/dist/agent/index.js.map +1 -1
  31. package/dist/agent/index.mjs +3 -0
  32. package/dist/agent/index.mjs.map +1 -1
  33. package/dist/browser/background.js +18 -21
  34. package/dist/browser/background.js.map +1 -1
  35. package/dist/browser/background.mjs +18 -21
  36. package/dist/browser/background.mjs.map +1 -1
  37. package/dist/browser/browser-adapter.d.mts +2 -2
  38. package/dist/browser/browser-adapter.d.ts +2 -2
  39. package/dist/cli/index.d.mts +2 -2
  40. package/dist/cli/index.d.ts +2 -2
  41. package/dist/cursor/cursor-adapter.d.mts +2 -2
  42. package/dist/cursor/cursor-adapter.d.ts +2 -2
  43. package/dist/cursor/extension.d.mts +2 -2
  44. package/dist/cursor/extension.d.ts +2 -2
  45. package/dist/cursor/extension.js +18 -21
  46. package/dist/cursor/extension.js.map +1 -1
  47. package/dist/cursor/extension.mjs +18 -21
  48. package/dist/cursor/extension.mjs.map +1 -1
  49. package/dist/{express-CrfwoNAR.d.ts → express-BowlMHQF.d.ts} +1 -1
  50. package/dist/{express-ienhAXps.d.mts → express-CeoSdOAZ.d.mts} +1 -1
  51. package/dist/gateway/gateway.d.mts +2 -2
  52. package/dist/gateway/gateway.d.ts +2 -2
  53. package/dist/gateway/gateway.js +18 -21
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +18 -21
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +2 -2
  58. package/dist/git-trigger/git-hooks.d.ts +2 -2
  59. package/dist/{index-CEg_WG6y.d.mts → index-B51W8gn8.d.mts} +1 -1
  60. package/dist/{index-DC5f8eoQ.d.ts → index-DBmlycVm.d.ts} +1 -1
  61. package/dist/{index-B5e2IDWU.d.mts → index-DtGziFEm.d.mts} +1 -1
  62. package/dist/{index-CCdZxvAr.d.ts → index-DzXXBuLm.d.ts} +1 -1
  63. package/dist/index.d.mts +7 -7
  64. package/dist/index.d.ts +7 -7
  65. package/dist/index.js +209 -191
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +209 -191
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/local-evaluator/evaluator.d.mts +2 -2
  70. package/dist/local-evaluator/evaluator.d.ts +2 -2
  71. package/dist/{nextjs-DSpisQst.d.mts → nextjs-BW1rzr1I.d.mts} +1 -1
  72. package/dist/{nextjs-66R1KW8e.d.ts → nextjs-V_K0qlAQ.d.ts} +1 -1
  73. package/dist/{sdk-5U_CBRpr.d.mts → sdk-ZYgI7G9f.d.ts} +14 -3
  74. package/dist/{sdk-Bm8np66n.d.ts → sdk-e5jg7sqW.d.mts} +14 -3
  75. package/dist/transport/index.d.mts +2 -2
  76. package/dist/transport/index.d.ts +2 -2
  77. package/dist/transport/index.js +10 -0
  78. package/dist/transport/index.js.map +1 -1
  79. package/dist/transport/index.mjs +10 -0
  80. package/dist/transport/index.mjs.map +1 -1
  81. package/dist/{types-CgDCUfo8.d.mts → types-BNiLZY0i.d.mts} +1 -1
  82. package/dist/{types-R5N4ET6x.d.ts → types-DJi-u3fz.d.ts} +1 -1
  83. package/dist/{types-B3USs-Kx.d.mts → types-rFh4VMH4.d.mts} +30 -2
  84. package/dist/{types-B3USs-Kx.d.ts → types-rFh4VMH4.d.ts} +30 -2
  85. package/dist/ui/index.d.mts +1 -1
  86. package/dist/ui/index.d.ts +1 -1
  87. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -192,7 +192,7 @@ function getCapabilities(accessLevel) {
192
192
  }
193
193
 
194
194
  // src/version.ts
195
- var SDK_VERSION = "3.0.0";
195
+ var SDK_VERSION = "3.2.0";
196
196
 
197
197
  // src/well-known.ts
198
198
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -297,7 +297,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
297
297
  }
298
298
  }
299
299
  var verificationCache = /* @__PURE__ */ new Map();
300
- function getCacheKey(request) {
300
+ function getCacheKey(request, counterpartyId) {
301
301
  const c = request.credentials;
302
302
  return [
303
303
  c.astraId || "",
@@ -310,6 +310,14 @@ function getCacheKey(request) {
310
310
  request.jurisdiction || "",
311
311
  request.transactionValue ?? "",
312
312
  request.currency || "",
313
+ // SECURITY (cross-merchant cache leak): the merchant identity is sent via
314
+ // `config.counterpartyId`, NOT on the request, so it was previously absent
315
+ // from the key — two verifies for the SAME agent/purpose/action/value but
316
+ // DIFFERENT merchants collided, and a grant at a permissive merchant (low
317
+ // trust floor) was served for a stricter one. Same bug class as the
318
+ // duration omission (F-A1-07). counterpartyId affects the backend verdict
319
+ // (trust floor / per-route policy), so it MUST key the cache.
320
+ counterpartyId || "",
313
321
  request.counterpartyUrl || "",
314
322
  request.counterpartyType || "",
315
323
  request.isSubAgentRequest ? "1" : "0",
@@ -333,8 +341,8 @@ function getCacheKey(request) {
333
341
  request.callerMetadata?.agentCardUrl || ""
334
342
  ].join("|");
335
343
  }
336
- function getCachedResult(request) {
337
- const key = getCacheKey(request);
344
+ function getCachedResult(request, counterpartyId) {
345
+ const key = getCacheKey(request, counterpartyId);
338
346
  const cached = verificationCache.get(key);
339
347
  if (cached && cached.expiresAt > Date.now()) {
340
348
  return cached.result;
@@ -346,9 +354,9 @@ function getCachedResult(request) {
346
354
  }
347
355
  var DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;
348
356
  var DEFAULT_STEP_UP_TTL_SECONDS = 300;
349
- function cacheResult(request, result, configuredTtl) {
357
+ function cacheResult(request, result, configuredTtl, counterpartyId) {
350
358
  const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : result.requiresStepUp ? DEFAULT_STEP_UP_TTL_SECONDS : DEFAULT_AUTONOMOUS_TTL_SECONDS;
351
- const key = getCacheKey(request);
359
+ const key = getCacheKey(request, counterpartyId);
352
360
  verificationCache.set(key, {
353
361
  result,
354
362
  expiresAt: Date.now() + ttlSeconds * 1e3
@@ -546,7 +554,7 @@ async function verify(config, request) {
546
554
  );
547
555
  }
548
556
  if (mergedConfig.cacheTtl !== 0) {
549
- const cached = getCachedResult(request);
557
+ const cached = getCachedResult(request, mergedConfig.counterpartyId);
550
558
  if (cached) {
551
559
  if (mergedConfig.debug) {
552
560
  console.log("[VerificationGateway] Returning cached result");
@@ -598,8 +606,8 @@ async function verify(config, request) {
598
606
  verifiedAt: /* @__PURE__ */ new Date(),
599
607
  // Extract sessionId so decisions can be recorded for denials too
600
608
  sessionId: apiResponse.sessionId,
601
- // v2.3.10 (defect #34, round-4): anonymous traffic has no session →
602
- // correlationId is the linking key for paired local_override events.
609
+ // Anonymous traffic has no session → correlationId is the per-attempt
610
+ // linking key (the sessionId-equivalent for anonymous callers).
603
611
  correlationId: apiResponse.correlationId,
604
612
  recommendation: apiResponse.recommendation,
605
613
  recommendationReasons: apiResponse.recommendationReasons
@@ -673,17 +681,14 @@ async function verify(config, request) {
673
681
  };
674
682
  } else if (result.recommendation === "step_up_required") {
675
683
  result.requiresStepUp = true;
676
- if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
677
- result.accessLevel = "read-only";
678
- }
679
684
  result.denialReasons = result.recommendationReasons || ["Step-up verification required"];
680
685
  }
681
686
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny") {
682
- cacheResult(request, result, mergedConfig.cacheTtl);
687
+ cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
683
688
  }
684
689
  return result;
685
690
  }
686
- async function recordDecision(config, sessionId, decision, reason, override) {
691
+ async function recordDecision(config, sessionId, decision, reason) {
687
692
  const headers = { "Content-Type": "application/json" };
688
693
  if (config.apiKey) {
689
694
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -692,36 +697,7 @@ async function recordDecision(config, sessionId, decision, reason, override) {
692
697
  await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
693
698
  method: "POST",
694
699
  headers,
695
- body: JSON.stringify({
696
- decision,
697
- reason,
698
- ...override && {
699
- overriddenBy: override.overriddenBy,
700
- toolName: override.toolName,
701
- requestedLevel: override.requestedLevel,
702
- grantedLevel: override.grantedLevel
703
- }
704
- })
705
- }).catch(() => {
706
- });
707
- }
708
- async function recordAnonymousLocalOverride(config, correlationId, override, reason) {
709
- const headers = { "Content-Type": "application/json" };
710
- if (config.apiKey) {
711
- headers["Authorization"] = `Bearer ${config.apiKey}`;
712
- headers["X-API-Key"] = config.apiKey;
713
- }
714
- await fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {
715
- method: "POST",
716
- headers,
717
- body: JSON.stringify({
718
- correlationId,
719
- reason,
720
- overriddenBy: override.overriddenBy,
721
- toolName: override.toolName,
722
- requestedLevel: override.requestedLevel,
723
- grantedLevel: override.grantedLevel
724
- })
700
+ body: JSON.stringify({ decision, reason })
725
701
  }).catch(() => {
726
702
  });
727
703
  }
@@ -787,6 +763,9 @@ function setHttpHeaders(headers, credentials) {
787
763
  if (credentials.pdlss?.purpose) {
788
764
  const purposeValue = credentials.pdlss.purpose.action ? `${credentials.pdlss.purpose.category}:${credentials.pdlss.purpose.action}` : credentials.pdlss.purpose.category;
789
765
  result[`${HEADER_PREFIX}Purpose`] = purposeValue;
766
+ if (credentials.pdlss.purpose.action) {
767
+ result[`${HEADER_PREFIX}Action`] = credentials.pdlss.purpose.action;
768
+ }
790
769
  }
791
770
  if (credentials.pdlss?.duration?.maxSessionDuration) {
792
771
  result[`${HEADER_PREFIX}Duration`] = String(credentials.pdlss.duration.maxSessionDuration);
@@ -816,6 +795,13 @@ function extractHttpCredentials(headers) {
816
795
  purpose: { category, action }
817
796
  };
818
797
  }
798
+ const astraAction = getValue(`${HEADER_PREFIX}Action`) ?? getValue("x-astra-action");
799
+ if (astraAction) {
800
+ credentials.pdlss = {
801
+ ...credentials.pdlss,
802
+ purpose: { category: credentials.pdlss?.purpose?.category ?? "", action: astraAction }
803
+ };
804
+ }
819
805
  const duration = getValue(`${HEADER_PREFIX}Duration`) ?? getValue("x-astra-duration");
820
806
  if (duration) {
821
807
  credentials.pdlss = {
@@ -833,6 +819,85 @@ function extractHttpCredentials(headers) {
833
819
  return credentials;
834
820
  }
835
821
 
822
+ // src/adapters/http-pdlss.ts
823
+ var HTTP_METHOD_ACTION_TABLE = {
824
+ GET: "data.read",
825
+ HEAD: "data.read",
826
+ OPTIONS: "data.read",
827
+ POST: "data.write",
828
+ PUT: "data.write",
829
+ PATCH: "data.write",
830
+ DELETE: "data.delete"
831
+ };
832
+ var DEFAULT_HTTP_ACTION = "data.write";
833
+ var DEFAULT_HTTP_PURPOSE = "data";
834
+ function actionForHttpMethod(method) {
835
+ return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;
836
+ }
837
+ function normalizePurposeHeader(value) {
838
+ const colon = value.indexOf(":");
839
+ if (colon >= 0) {
840
+ return { purpose: value.slice(0, colon) };
841
+ }
842
+ const dot = value.indexOf(".");
843
+ if (dot > 0 && dot < value.length - 1) {
844
+ return { purpose: value.slice(0, dot), actionCandidate: value };
845
+ }
846
+ return { purpose: value };
847
+ }
848
+ function resolveHttpPdlss(input) {
849
+ const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : void 0;
850
+ let action;
851
+ let actionSource;
852
+ if (input.routeAction) {
853
+ action = input.routeAction;
854
+ actionSource = "route_config";
855
+ } else if (input.hasCustomActionExtractor && input.customAction) {
856
+ action = input.customAction;
857
+ actionSource = "custom_extractor";
858
+ } else if (!input.hasCustomActionExtractor && input.astraAction) {
859
+ action = input.astraAction;
860
+ actionSource = "header";
861
+ } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {
862
+ action = fromHeader.actionCandidate;
863
+ actionSource = "purpose_header_derived";
864
+ } else {
865
+ action = actionForHttpMethod(input.method);
866
+ actionSource = "method_table";
867
+ }
868
+ let purpose;
869
+ let purposeSource;
870
+ if (input.routePurpose) {
871
+ purpose = input.routePurpose;
872
+ purposeSource = "route_config";
873
+ } else if (input.hasCustomPurposeExtractor) {
874
+ if (input.customPurpose) {
875
+ purpose = input.customPurpose;
876
+ purposeSource = "custom_extractor";
877
+ }
878
+ } else if (fromHeader) {
879
+ purpose = fromHeader.purpose;
880
+ purposeSource = "header";
881
+ } else if (input.legacyPurpose) {
882
+ purpose = input.legacyPurpose;
883
+ purposeSource = "legacy_header";
884
+ } else if (input.queryPurpose) {
885
+ purpose = input.queryPurpose;
886
+ purposeSource = "query";
887
+ }
888
+ if (!purpose) {
889
+ const dot = action.indexOf(".");
890
+ if (dot > 0) {
891
+ purpose = action.slice(0, dot);
892
+ purposeSource = "action_derived";
893
+ } else {
894
+ purpose = DEFAULT_HTTP_PURPOSE;
895
+ purposeSource = "transport_default";
896
+ }
897
+ }
898
+ return { purpose, action, purposeSource, actionSource };
899
+ }
900
+
836
901
  // src/pdlss-pre-check.ts
837
902
  function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
838
903
  const failures = [];
@@ -891,33 +956,25 @@ function defaultExtractCredentials(req) {
891
956
  function extractAstraSyncCredentials(req) {
892
957
  return extractHttpCredentials(req.headers);
893
958
  }
894
- function defaultExtractPurpose(req) {
895
- const astraPurpose = req.headers["x-astra-purpose"];
896
- if (astraPurpose) {
897
- const value = Array.isArray(astraPurpose) ? astraPurpose[0] : astraPurpose;
898
- const category = value.split(":")[0];
899
- return category;
900
- }
901
- const purposeHeader = req.headers["x-purpose"] || req.headers["X-Purpose"];
902
- if (purposeHeader) {
903
- return Array.isArray(purposeHeader) ? purposeHeader[0] : purposeHeader;
904
- }
905
- if (req.query.purpose && typeof req.query.purpose === "string") {
906
- return req.query.purpose;
907
- }
908
- switch (req.method) {
909
- case "GET":
910
- return "read_data";
911
- case "POST":
912
- return "write_data";
913
- case "PUT":
914
- case "PATCH":
915
- return "write_data";
916
- case "DELETE":
917
- return "delete_data";
918
- default:
919
- return "general";
920
- }
959
+ function headerValue(value) {
960
+ if (typeof value === "string") return value;
961
+ if (Array.isArray(value)) return value[0];
962
+ return void 0;
963
+ }
964
+ function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
965
+ return resolveHttpPdlss({
966
+ method: req.method,
967
+ astraPurpose: headerValue(req.headers["x-astra-purpose"]),
968
+ astraAction: headerValue(req.headers["x-astra-action"]),
969
+ legacyPurpose: headerValue(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
970
+ queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
971
+ routePurpose: routeConfig?.purpose,
972
+ routeAction: routeConfig?.action,
973
+ hasCustomPurposeExtractor: !!customExtractPurpose,
974
+ customPurpose: customExtractPurpose?.(req),
975
+ hasCustomActionExtractor: !!customExtractAction,
976
+ customAction: customExtractAction?.(req)
977
+ });
921
978
  }
922
979
  function matchRoute(pattern, path, opts) {
923
980
  const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
@@ -977,6 +1034,7 @@ function createMiddleware(options) {
977
1034
  const {
978
1035
  extractCredentials: customExtractCredentials,
979
1036
  extractPurpose: customExtractPurpose,
1037
+ extractAction: customExtractAction,
980
1038
  skipPaths = [],
981
1039
  onDenied = defaultOnDenied,
982
1040
  recordDecisions,
@@ -1062,7 +1120,21 @@ function createMiddleware(options) {
1062
1120
  }
1063
1121
  return next();
1064
1122
  }
1065
- const purpose = customExtractPurpose ? customExtractPurpose(req) : defaultExtractPurpose(req);
1123
+ const pdlssPair = resolveRequestPdlss(
1124
+ req,
1125
+ routeConfig,
1126
+ customExtractPurpose,
1127
+ customExtractAction
1128
+ );
1129
+ const purpose = pdlssPair.purpose;
1130
+ if (config.debug) {
1131
+ console.debug("[express-middleware] pdlss resolved", {
1132
+ purpose_source: pdlssPair.purposeSource,
1133
+ resolved_purpose: pdlssPair.purpose,
1134
+ action_source: pdlssPair.actionSource,
1135
+ resolved_action: pdlssPair.action
1136
+ });
1137
+ }
1066
1138
  const astraCreds = extractAstraSyncCredentials(req);
1067
1139
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}`;
1068
1140
  const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
@@ -1106,10 +1178,7 @@ function createMiddleware(options) {
1106
1178
  const result = await verify(config, {
1107
1179
  credentials,
1108
1180
  purpose,
1109
- // RFC 7230 § 3.1.1 — HTTP method tokens uppercase by IANA convention.
1110
- // Backend evaluator tolerates either case as defense-in-depth
1111
- // (round-18.6 batch 2); SDK emits canonical form.
1112
- action: req.method.toUpperCase(),
1181
+ action: pdlssPair.action,
1113
1182
  resource: req.path,
1114
1183
  createSession: shouldRecordDecisions,
1115
1184
  counterpartyUrl,
@@ -1147,35 +1216,12 @@ function createMiddleware(options) {
1147
1216
  }
1148
1217
  return next();
1149
1218
  }
1150
- if (!hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
1151
- const insufficientFailure = {
1152
- dimension: "access_level.insufficient",
1153
- message: `Endpoint requires accessLevel '${routeConfig.minAccessLevel}'; agent has '${result.accessLevel}'.`,
1154
- guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
1155
- };
1156
- result.failures = [...result.failures ?? [], insufficientFailure];
1157
- result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
1158
- if (!result.guidance && wellKnownUrls) {
1159
- result.guidance = {
1160
- message: insufficientFailure.message,
1161
- registrationUrl: wellKnownUrls.registrationUrl,
1162
- documentationUrl: wellKnownUrls.documentationUrl
1163
- };
1164
- }
1165
- if (shouldRecordDecisions && sessionId) {
1166
- recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
1167
- });
1168
- }
1169
- dedupeFailures(result);
1170
- onDenied(result, req, res);
1171
- return;
1172
- }
1173
1219
  if (routeConfig.minTrustScore && result.agent) {
1174
1220
  if (result.agent.trustScore < routeConfig.minTrustScore) {
1175
1221
  const trustFailure = {
1176
- dimension: "access_level.insufficient",
1177
- message: `Trust score ${result.agent.trustScore} is below required ${routeConfig.minTrustScore} for this route.`,
1178
- guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
1222
+ dimension: "endpoint.trust",
1223
+ message: "Trust below the route requirement for this endpoint.",
1224
+ guidance: "Trust is below this route's floor. Trust is not overridable \u2014 the agent either meets the endpoint's trust policy or it doesn't. Raise the agent's trust via real signals (KYD, blockchain registration, agent-card), or have the operator lower the route's minTrustScore."
1179
1225
  };
1180
1226
  result.failures = [...result.failures ?? [], trustFailure];
1181
1227
  result.denialReasons = [trustFailure.message];
@@ -1307,28 +1353,15 @@ function extractAstraSyncCredentialsFromNextRequest(request) {
1307
1353
  });
1308
1354
  return extractHttpCredentials(headers);
1309
1355
  }
1310
- function extractPurpose(request) {
1311
- const astraPurpose = request.headers.get("x-astra-purpose");
1312
- if (astraPurpose) {
1313
- return astraPurpose.split(":")[0];
1314
- }
1315
- const purposeHeader = request.headers.get("x-purpose");
1316
- if (purposeHeader) {
1317
- return purposeHeader;
1318
- }
1319
- switch (request.method.toUpperCase()) {
1320
- case "GET":
1321
- return "read_data";
1322
- case "POST":
1323
- return "write_data";
1324
- case "PUT":
1325
- case "PATCH":
1326
- return "write_data";
1327
- case "DELETE":
1328
- return "delete_data";
1329
- default:
1330
- return "general";
1331
- }
1356
+ function resolveNextPdlss(request, routeConfig) {
1357
+ return resolveHttpPdlss({
1358
+ method: request.method,
1359
+ astraPurpose: request.headers.get("x-astra-purpose") ?? void 0,
1360
+ astraAction: request.headers.get("x-astra-action") ?? void 0,
1361
+ legacyPurpose: request.headers.get("x-purpose") ?? void 0,
1362
+ routePurpose: routeConfig?.purpose,
1363
+ routeAction: routeConfig?.action
1364
+ });
1332
1365
  }
1333
1366
  function generateCommerceShieldHtml(result, options) {
1334
1367
  const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
@@ -1541,7 +1574,16 @@ function createMiddleware2(options) {
1541
1574
  }
1542
1575
  const credentials = extractCredentialsFromNextRequest(request);
1543
1576
  const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
1544
- const purpose = extractPurpose(request);
1577
+ const pdlssPair = resolveNextPdlss(request, routeConfig);
1578
+ const purpose = pdlssPair.purpose;
1579
+ if (config.debug) {
1580
+ console.debug("[nextjs-middleware] pdlss resolved", {
1581
+ purpose_source: pdlssPair.purposeSource,
1582
+ resolved_purpose: pdlssPair.purpose,
1583
+ action_source: pdlssPair.actionSource,
1584
+ resolved_action: pdlssPair.action
1585
+ });
1586
+ }
1545
1587
  const astraCreds = extractAstraSyncCredentialsFromNextRequest(request);
1546
1588
  const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
1547
1589
  if (preCheckFailures.length > 0) {
@@ -1595,10 +1637,7 @@ function createMiddleware2(options) {
1595
1637
  const result = await verify(config, {
1596
1638
  credentials,
1597
1639
  purpose,
1598
- // RFC 7230 § 3.1.1 — HTTP method tokens uppercase by IANA convention.
1599
- // Backend evaluator tolerates either case as defense-in-depth
1600
- // (round-18.6 batch 2); SDK emits canonical form.
1601
- action: request.method.toUpperCase(),
1640
+ action: pdlssPair.action,
1602
1641
  resource: pathname,
1603
1642
  counterpartyUrl,
1604
1643
  counterpartyType: config.counterpartyType || "website",
@@ -1613,7 +1652,7 @@ function createMiddleware2(options) {
1613
1652
  agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0
1614
1653
  }
1615
1654
  });
1616
- if (!result.identityVerified || !result.policyAllowed || !hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
1655
+ if (!result.identityVerified || !result.policyAllowed) {
1617
1656
  if (pathname.startsWith("/api/")) {
1618
1657
  return NextResponse.json(
1619
1658
  {
@@ -1621,10 +1660,8 @@ function createMiddleware2(options) {
1621
1660
  error: {
1622
1661
  // Round-18 G4: 401 → identity missing (re-auth); 403 → identity
1623
1662
  // OK, policy denied (update PDLSS / step up).
1624
- code: !result.identityVerified ? "UNAUTHORIZED" : "INSUFFICIENT_ACCESS",
1663
+ code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
1625
1664
  message: result.denialReasons?.[0] || "Access denied",
1626
- accessLevel: result.accessLevel,
1627
- required: routeConfig.minAccessLevel,
1628
1665
  guidance: result.guidance
1629
1666
  }
1630
1667
  },
@@ -1652,7 +1689,6 @@ function createMiddleware2(options) {
1652
1689
  response.headers.set("X-AstraSync-Access-Level", result.accessLevel);
1653
1690
  if (result.agent) {
1654
1691
  response.headers.set("X-AstraSync-Agent-Id", result.agent.astraId);
1655
- response.headers.set("X-AstraSync-Trust-Score", result.agent.trustScore.toString());
1656
1692
  }
1657
1693
  return response;
1658
1694
  };
@@ -1726,7 +1762,13 @@ var VerificationGatewayClient = class {
1726
1762
  return this.executeWithRetry(() => quickVerify(this.config, credentials));
1727
1763
  }
1728
1764
  /**
1729
- * Check if an agent has a specific access level
1765
+ * Check if an agent has a specific access level.
1766
+ *
1767
+ * @deprecated 3.2.0 — the access-level band is informational only; it no
1768
+ * longer gates in the middleware adapters (post-3.1.0 feedback #1). This
1769
+ * explicit opt-in query still works, but prefer gating on the server-side
1770
+ * policy decision (identity + policy + trust) or per-route `minTrustScore`.
1771
+ * Explicit per-route condition→constraint rules are the Phase-2 successor.
1730
1772
  */
1731
1773
  async hasAccess(credentials, requiredLevel) {
1732
1774
  const result = await this.quickVerify(credentials);
@@ -3353,9 +3395,9 @@ function toBuf(bytes) {
3353
3395
  new Uint8Array(out).set(bytes);
3354
3396
  return out;
3355
3397
  }
3356
- function checkTimestamp(headerValue, toleranceSec, nowFn) {
3357
- if (!headerValue) return { ok: false, error: "missing Timestamp header" };
3358
- const ts = parseTimestamp(headerValue);
3398
+ function checkTimestamp(headerValue2, toleranceSec, nowFn) {
3399
+ if (!headerValue2) return { ok: false, error: "missing Timestamp header" };
3400
+ const ts = parseTimestamp(headerValue2);
3359
3401
  if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
3360
3402
  const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
3361
3403
  if (Math.abs(now - ts) > toleranceSec) {
@@ -3587,14 +3629,14 @@ function verifyMPP(input) {
3587
3629
  var import_schemas = require("@x402/core/schemas");
3588
3630
  var import_utils = require("@x402/core/utils");
3589
3631
  function extractX402FromRequest(request) {
3590
- const headerValue = readHeader4(request.headers, "x-payment");
3632
+ const headerValue2 = readHeader4(request.headers, "x-payment");
3591
3633
  if (request.body && typeof request.body === "object") {
3592
3634
  const parsed = tryParsePayload(request.body);
3593
3635
  if (parsed) return buildPayloadContext(parsed, "body");
3594
3636
  }
3595
- if (headerValue) {
3637
+ if (headerValue2) {
3596
3638
  try {
3597
- const decoded = (0, import_utils.safeBase64Decode)(headerValue);
3639
+ const decoded = (0, import_utils.safeBase64Decode)(headerValue2);
3598
3640
  if (decoded) {
3599
3641
  const json = JSON.parse(decoded);
3600
3642
  const parsed = tryParsePayload(json);
@@ -3617,10 +3659,10 @@ function extractX402FromResponse(response) {
3617
3659
  const parsed = tryParseRequired(response.body);
3618
3660
  if (parsed) return buildRequiredContext(parsed, "body");
3619
3661
  }
3620
- const headerValue = readHeader4(response.headers, "x-payment-required");
3621
- if (headerValue) {
3662
+ const headerValue2 = readHeader4(response.headers, "x-payment-required");
3663
+ if (headerValue2) {
3622
3664
  try {
3623
- const decoded = (0, import_utils.safeBase64Decode)(headerValue);
3665
+ const decoded = (0, import_utils.safeBase64Decode)(headerValue2);
3624
3666
  if (decoded) {
3625
3667
  const json = JSON.parse(decoded);
3626
3668
  const parsed = tryParseRequired(json);
@@ -4456,7 +4498,10 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
4456
4498
  }
4457
4499
  let action;
4458
4500
  let actionSource;
4459
- if (headerAction) {
4501
+ if (toolGate?.action !== void 0) {
4502
+ action = toolGate.action;
4503
+ actionSource = "tool_gate";
4504
+ } else if (headerAction) {
4460
4505
  action = headerAction;
4461
4506
  actionSource = "header";
4462
4507
  } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
@@ -4567,7 +4612,6 @@ function createMcpMiddleware(options) {
4567
4612
  return next();
4568
4613
  }
4569
4614
  req.mcpRequest = parsed;
4570
- const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
4571
4615
  const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
4572
4616
  const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
4573
4617
  const bodyAstraId = parsed.agentIdFromBody;
@@ -4604,7 +4648,7 @@ function createMcpMiddleware(options) {
4604
4648
  return next();
4605
4649
  }
4606
4650
  }
4607
- const { level: minAccessLevel, source: gateSource } = resolveMinAccessLevel(parsed, {
4651
+ const { level: minAccessLevel } = resolveMinAccessLevel(parsed, {
4608
4652
  toolGates,
4609
4653
  methodGates
4610
4654
  });
@@ -4630,7 +4674,7 @@ function createMcpMiddleware(options) {
4630
4674
  req.path,
4631
4675
  headerPurpose,
4632
4676
  headerAction,
4633
- gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
4677
+ gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : void 0
4634
4678
  );
4635
4679
  if (config.debug) {
4636
4680
  console.debug("[mcp-middleware] pdlss resolved", {
@@ -4640,6 +4684,23 @@ function createMcpMiddleware(options) {
4640
4684
  resolved_action: pdlss.action
4641
4685
  });
4642
4686
  }
4687
+ if (!pdlss.purpose) {
4688
+ const id = req.body?.id ?? null;
4689
+ res.status(400).json({
4690
+ jsonrpc: "2.0",
4691
+ id,
4692
+ error: {
4693
+ code: -32602,
4694
+ message: "PDLSS_PURPOSE_REQUIRED",
4695
+ data: {
4696
+ dimension: "pdlss.purpose",
4697
+ detail: "This tool is access-gated but the call declared no PDLSS purpose. Supply a bare-category purpose via the X-Astra-Purpose header or params._meta.astrasync.purpose, or have the merchant set the tool\u2019s purpose in its toolGate config.",
4698
+ resolvedAction: pdlss.action
4699
+ }
4700
+ }
4701
+ });
4702
+ return;
4703
+ }
4643
4704
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
4644
4705
  const shouldRecordDecisions = recordDecisions !== false;
4645
4706
  const result = await verify(config, {
@@ -4663,7 +4724,6 @@ function createMcpMiddleware(options) {
4663
4724
  });
4664
4725
  req.agentVerification = result;
4665
4726
  const sessionId = result.sessionId;
4666
- const correlationId = result.correlationId;
4667
4727
  if (!result.identityVerified || !result.policyAllowed) {
4668
4728
  if (shouldRecordDecisions && sessionId) {
4669
4729
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -4684,48 +4744,6 @@ function createMcpMiddleware(options) {
4684
4744
  }
4685
4745
  return next();
4686
4746
  }
4687
- if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {
4688
- const insufficientFailure = {
4689
- dimension: "access_level.insufficient",
4690
- message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,
4691
- guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
4692
- };
4693
- result.failures = [...result.failures ?? [], insufficientFailure];
4694
- result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
4695
- if (!result.guidance && wellKnownUrls) {
4696
- result.guidance = {
4697
- message: insufficientFailure.message,
4698
- registrationUrl: wellKnownUrls.registrationUrl,
4699
- documentationUrl: wellKnownUrls.documentationUrl
4700
- };
4701
- }
4702
- if (shouldRecordDecisions) {
4703
- const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
4704
- const override = {
4705
- overriddenBy: overrideKind,
4706
- ...parsed.toolName && { toolName: parsed.toolName },
4707
- requestedLevel: minAccessLevel,
4708
- grantedLevel: result.accessLevel
4709
- };
4710
- if (sessionId) {
4711
- recordDecision(config, sessionId, "denied", result.denialReasons?.[0], override).catch(
4712
- () => {
4713
- }
4714
- );
4715
- } else if (correlationId) {
4716
- recordAnonymousLocalOverride(
4717
- config,
4718
- correlationId,
4719
- override,
4720
- result.denialReasons?.[0]
4721
- ).catch(() => {
4722
- });
4723
- }
4724
- }
4725
- dedupeFailures2(result);
4726
- onDenied(result, req, res);
4727
- return;
4728
- }
4729
4747
  if (effectiveAstraId) {
4730
4748
  res.setHeader(
4731
4749
  MCP_VERIFIED_HOP_HEADER,