@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.mjs CHANGED
@@ -126,7 +126,7 @@ function getCapabilities(accessLevel) {
126
126
  }
127
127
 
128
128
  // src/version.ts
129
- var SDK_VERSION = "3.0.0";
129
+ var SDK_VERSION = "3.2.0";
130
130
 
131
131
  // src/well-known.ts
132
132
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -231,7 +231,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
231
231
  }
232
232
  }
233
233
  var verificationCache = /* @__PURE__ */ new Map();
234
- function getCacheKey(request) {
234
+ function getCacheKey(request, counterpartyId) {
235
235
  const c = request.credentials;
236
236
  return [
237
237
  c.astraId || "",
@@ -244,6 +244,14 @@ function getCacheKey(request) {
244
244
  request.jurisdiction || "",
245
245
  request.transactionValue ?? "",
246
246
  request.currency || "",
247
+ // SECURITY (cross-merchant cache leak): the merchant identity is sent via
248
+ // `config.counterpartyId`, NOT on the request, so it was previously absent
249
+ // from the key — two verifies for the SAME agent/purpose/action/value but
250
+ // DIFFERENT merchants collided, and a grant at a permissive merchant (low
251
+ // trust floor) was served for a stricter one. Same bug class as the
252
+ // duration omission (F-A1-07). counterpartyId affects the backend verdict
253
+ // (trust floor / per-route policy), so it MUST key the cache.
254
+ counterpartyId || "",
247
255
  request.counterpartyUrl || "",
248
256
  request.counterpartyType || "",
249
257
  request.isSubAgentRequest ? "1" : "0",
@@ -267,8 +275,8 @@ function getCacheKey(request) {
267
275
  request.callerMetadata?.agentCardUrl || ""
268
276
  ].join("|");
269
277
  }
270
- function getCachedResult(request) {
271
- const key = getCacheKey(request);
278
+ function getCachedResult(request, counterpartyId) {
279
+ const key = getCacheKey(request, counterpartyId);
272
280
  const cached = verificationCache.get(key);
273
281
  if (cached && cached.expiresAt > Date.now()) {
274
282
  return cached.result;
@@ -280,9 +288,9 @@ function getCachedResult(request) {
280
288
  }
281
289
  var DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;
282
290
  var DEFAULT_STEP_UP_TTL_SECONDS = 300;
283
- function cacheResult(request, result, configuredTtl) {
291
+ function cacheResult(request, result, configuredTtl, counterpartyId) {
284
292
  const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : result.requiresStepUp ? DEFAULT_STEP_UP_TTL_SECONDS : DEFAULT_AUTONOMOUS_TTL_SECONDS;
285
- const key = getCacheKey(request);
293
+ const key = getCacheKey(request, counterpartyId);
286
294
  verificationCache.set(key, {
287
295
  result,
288
296
  expiresAt: Date.now() + ttlSeconds * 1e3
@@ -480,7 +488,7 @@ async function verify(config, request) {
480
488
  );
481
489
  }
482
490
  if (mergedConfig.cacheTtl !== 0) {
483
- const cached = getCachedResult(request);
491
+ const cached = getCachedResult(request, mergedConfig.counterpartyId);
484
492
  if (cached) {
485
493
  if (mergedConfig.debug) {
486
494
  console.log("[VerificationGateway] Returning cached result");
@@ -532,8 +540,8 @@ async function verify(config, request) {
532
540
  verifiedAt: /* @__PURE__ */ new Date(),
533
541
  // Extract sessionId so decisions can be recorded for denials too
534
542
  sessionId: apiResponse.sessionId,
535
- // v2.3.10 (defect #34, round-4): anonymous traffic has no session →
536
- // correlationId is the linking key for paired local_override events.
543
+ // Anonymous traffic has no session → correlationId is the per-attempt
544
+ // linking key (the sessionId-equivalent for anonymous callers).
537
545
  correlationId: apiResponse.correlationId,
538
546
  recommendation: apiResponse.recommendation,
539
547
  recommendationReasons: apiResponse.recommendationReasons
@@ -607,17 +615,14 @@ async function verify(config, request) {
607
615
  };
608
616
  } else if (result.recommendation === "step_up_required") {
609
617
  result.requiresStepUp = true;
610
- if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
611
- result.accessLevel = "read-only";
612
- }
613
618
  result.denialReasons = result.recommendationReasons || ["Step-up verification required"];
614
619
  }
615
620
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny") {
616
- cacheResult(request, result, mergedConfig.cacheTtl);
621
+ cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
617
622
  }
618
623
  return result;
619
624
  }
620
- async function recordDecision(config, sessionId, decision, reason, override) {
625
+ async function recordDecision(config, sessionId, decision, reason) {
621
626
  const headers = { "Content-Type": "application/json" };
622
627
  if (config.apiKey) {
623
628
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -626,36 +631,7 @@ async function recordDecision(config, sessionId, decision, reason, override) {
626
631
  await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
627
632
  method: "POST",
628
633
  headers,
629
- body: JSON.stringify({
630
- decision,
631
- reason,
632
- ...override && {
633
- overriddenBy: override.overriddenBy,
634
- toolName: override.toolName,
635
- requestedLevel: override.requestedLevel,
636
- grantedLevel: override.grantedLevel
637
- }
638
- })
639
- }).catch(() => {
640
- });
641
- }
642
- async function recordAnonymousLocalOverride(config, correlationId, override, reason) {
643
- const headers = { "Content-Type": "application/json" };
644
- if (config.apiKey) {
645
- headers["Authorization"] = `Bearer ${config.apiKey}`;
646
- headers["X-API-Key"] = config.apiKey;
647
- }
648
- await fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {
649
- method: "POST",
650
- headers,
651
- body: JSON.stringify({
652
- correlationId,
653
- reason,
654
- overriddenBy: override.overriddenBy,
655
- toolName: override.toolName,
656
- requestedLevel: override.requestedLevel,
657
- grantedLevel: override.grantedLevel
658
- })
634
+ body: JSON.stringify({ decision, reason })
659
635
  }).catch(() => {
660
636
  });
661
637
  }
@@ -721,6 +697,9 @@ function setHttpHeaders(headers, credentials) {
721
697
  if (credentials.pdlss?.purpose) {
722
698
  const purposeValue = credentials.pdlss.purpose.action ? `${credentials.pdlss.purpose.category}:${credentials.pdlss.purpose.action}` : credentials.pdlss.purpose.category;
723
699
  result[`${HEADER_PREFIX}Purpose`] = purposeValue;
700
+ if (credentials.pdlss.purpose.action) {
701
+ result[`${HEADER_PREFIX}Action`] = credentials.pdlss.purpose.action;
702
+ }
724
703
  }
725
704
  if (credentials.pdlss?.duration?.maxSessionDuration) {
726
705
  result[`${HEADER_PREFIX}Duration`] = String(credentials.pdlss.duration.maxSessionDuration);
@@ -750,6 +729,13 @@ function extractHttpCredentials(headers) {
750
729
  purpose: { category, action }
751
730
  };
752
731
  }
732
+ const astraAction = getValue(`${HEADER_PREFIX}Action`) ?? getValue("x-astra-action");
733
+ if (astraAction) {
734
+ credentials.pdlss = {
735
+ ...credentials.pdlss,
736
+ purpose: { category: credentials.pdlss?.purpose?.category ?? "", action: astraAction }
737
+ };
738
+ }
753
739
  const duration = getValue(`${HEADER_PREFIX}Duration`) ?? getValue("x-astra-duration");
754
740
  if (duration) {
755
741
  credentials.pdlss = {
@@ -767,6 +753,85 @@ function extractHttpCredentials(headers) {
767
753
  return credentials;
768
754
  }
769
755
 
756
+ // src/adapters/http-pdlss.ts
757
+ var HTTP_METHOD_ACTION_TABLE = {
758
+ GET: "data.read",
759
+ HEAD: "data.read",
760
+ OPTIONS: "data.read",
761
+ POST: "data.write",
762
+ PUT: "data.write",
763
+ PATCH: "data.write",
764
+ DELETE: "data.delete"
765
+ };
766
+ var DEFAULT_HTTP_ACTION = "data.write";
767
+ var DEFAULT_HTTP_PURPOSE = "data";
768
+ function actionForHttpMethod(method) {
769
+ return HTTP_METHOD_ACTION_TABLE[method.toUpperCase()] ?? DEFAULT_HTTP_ACTION;
770
+ }
771
+ function normalizePurposeHeader(value) {
772
+ const colon = value.indexOf(":");
773
+ if (colon >= 0) {
774
+ return { purpose: value.slice(0, colon) };
775
+ }
776
+ const dot = value.indexOf(".");
777
+ if (dot > 0 && dot < value.length - 1) {
778
+ return { purpose: value.slice(0, dot), actionCandidate: value };
779
+ }
780
+ return { purpose: value };
781
+ }
782
+ function resolveHttpPdlss(input) {
783
+ const fromHeader = input.astraPurpose ? normalizePurposeHeader(input.astraPurpose) : void 0;
784
+ let action;
785
+ let actionSource;
786
+ if (input.routeAction) {
787
+ action = input.routeAction;
788
+ actionSource = "route_config";
789
+ } else if (input.hasCustomActionExtractor && input.customAction) {
790
+ action = input.customAction;
791
+ actionSource = "custom_extractor";
792
+ } else if (!input.hasCustomActionExtractor && input.astraAction) {
793
+ action = input.astraAction;
794
+ actionSource = "header";
795
+ } else if (!input.hasCustomActionExtractor && fromHeader?.actionCandidate) {
796
+ action = fromHeader.actionCandidate;
797
+ actionSource = "purpose_header_derived";
798
+ } else {
799
+ action = actionForHttpMethod(input.method);
800
+ actionSource = "method_table";
801
+ }
802
+ let purpose;
803
+ let purposeSource;
804
+ if (input.routePurpose) {
805
+ purpose = input.routePurpose;
806
+ purposeSource = "route_config";
807
+ } else if (input.hasCustomPurposeExtractor) {
808
+ if (input.customPurpose) {
809
+ purpose = input.customPurpose;
810
+ purposeSource = "custom_extractor";
811
+ }
812
+ } else if (fromHeader) {
813
+ purpose = fromHeader.purpose;
814
+ purposeSource = "header";
815
+ } else if (input.legacyPurpose) {
816
+ purpose = input.legacyPurpose;
817
+ purposeSource = "legacy_header";
818
+ } else if (input.queryPurpose) {
819
+ purpose = input.queryPurpose;
820
+ purposeSource = "query";
821
+ }
822
+ if (!purpose) {
823
+ const dot = action.indexOf(".");
824
+ if (dot > 0) {
825
+ purpose = action.slice(0, dot);
826
+ purposeSource = "action_derived";
827
+ } else {
828
+ purpose = DEFAULT_HTTP_PURPOSE;
829
+ purposeSource = "transport_default";
830
+ }
831
+ }
832
+ return { purpose, action, purposeSource, actionSource };
833
+ }
834
+
770
835
  // src/pdlss-pre-check.ts
771
836
  function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
772
837
  const failures = [];
@@ -825,33 +890,25 @@ function defaultExtractCredentials(req) {
825
890
  function extractAstraSyncCredentials(req) {
826
891
  return extractHttpCredentials(req.headers);
827
892
  }
828
- function defaultExtractPurpose(req) {
829
- const astraPurpose = req.headers["x-astra-purpose"];
830
- if (astraPurpose) {
831
- const value = Array.isArray(astraPurpose) ? astraPurpose[0] : astraPurpose;
832
- const category = value.split(":")[0];
833
- return category;
834
- }
835
- const purposeHeader = req.headers["x-purpose"] || req.headers["X-Purpose"];
836
- if (purposeHeader) {
837
- return Array.isArray(purposeHeader) ? purposeHeader[0] : purposeHeader;
838
- }
839
- if (req.query.purpose && typeof req.query.purpose === "string") {
840
- return req.query.purpose;
841
- }
842
- switch (req.method) {
843
- case "GET":
844
- return "read_data";
845
- case "POST":
846
- return "write_data";
847
- case "PUT":
848
- case "PATCH":
849
- return "write_data";
850
- case "DELETE":
851
- return "delete_data";
852
- default:
853
- return "general";
854
- }
893
+ function headerValue(value) {
894
+ if (typeof value === "string") return value;
895
+ if (Array.isArray(value)) return value[0];
896
+ return void 0;
897
+ }
898
+ function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
899
+ return resolveHttpPdlss({
900
+ method: req.method,
901
+ astraPurpose: headerValue(req.headers["x-astra-purpose"]),
902
+ astraAction: headerValue(req.headers["x-astra-action"]),
903
+ legacyPurpose: headerValue(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
904
+ queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
905
+ routePurpose: routeConfig?.purpose,
906
+ routeAction: routeConfig?.action,
907
+ hasCustomPurposeExtractor: !!customExtractPurpose,
908
+ customPurpose: customExtractPurpose?.(req),
909
+ hasCustomActionExtractor: !!customExtractAction,
910
+ customAction: customExtractAction?.(req)
911
+ });
855
912
  }
856
913
  function matchRoute(pattern, path, opts) {
857
914
  const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
@@ -911,6 +968,7 @@ function createMiddleware(options) {
911
968
  const {
912
969
  extractCredentials: customExtractCredentials,
913
970
  extractPurpose: customExtractPurpose,
971
+ extractAction: customExtractAction,
914
972
  skipPaths = [],
915
973
  onDenied = defaultOnDenied,
916
974
  recordDecisions,
@@ -996,7 +1054,21 @@ function createMiddleware(options) {
996
1054
  }
997
1055
  return next();
998
1056
  }
999
- const purpose = customExtractPurpose ? customExtractPurpose(req) : defaultExtractPurpose(req);
1057
+ const pdlssPair = resolveRequestPdlss(
1058
+ req,
1059
+ routeConfig,
1060
+ customExtractPurpose,
1061
+ customExtractAction
1062
+ );
1063
+ const purpose = pdlssPair.purpose;
1064
+ if (config.debug) {
1065
+ console.debug("[express-middleware] pdlss resolved", {
1066
+ purpose_source: pdlssPair.purposeSource,
1067
+ resolved_purpose: pdlssPair.purpose,
1068
+ action_source: pdlssPair.actionSource,
1069
+ resolved_action: pdlssPair.action
1070
+ });
1071
+ }
1000
1072
  const astraCreds = extractAstraSyncCredentials(req);
1001
1073
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}`;
1002
1074
  const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
@@ -1040,10 +1112,7 @@ function createMiddleware(options) {
1040
1112
  const result = await verify(config, {
1041
1113
  credentials,
1042
1114
  purpose,
1043
- // RFC 7230 § 3.1.1 — HTTP method tokens uppercase by IANA convention.
1044
- // Backend evaluator tolerates either case as defense-in-depth
1045
- // (round-18.6 batch 2); SDK emits canonical form.
1046
- action: req.method.toUpperCase(),
1115
+ action: pdlssPair.action,
1047
1116
  resource: req.path,
1048
1117
  createSession: shouldRecordDecisions,
1049
1118
  counterpartyUrl,
@@ -1081,35 +1150,12 @@ function createMiddleware(options) {
1081
1150
  }
1082
1151
  return next();
1083
1152
  }
1084
- if (!hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
1085
- const insufficientFailure = {
1086
- dimension: "access_level.insufficient",
1087
- message: `Endpoint requires accessLevel '${routeConfig.minAccessLevel}'; agent has '${result.accessLevel}'.`,
1088
- 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."
1089
- };
1090
- result.failures = [...result.failures ?? [], insufficientFailure];
1091
- result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
1092
- if (!result.guidance && wellKnownUrls) {
1093
- result.guidance = {
1094
- message: insufficientFailure.message,
1095
- registrationUrl: wellKnownUrls.registrationUrl,
1096
- documentationUrl: wellKnownUrls.documentationUrl
1097
- };
1098
- }
1099
- if (shouldRecordDecisions && sessionId) {
1100
- recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
1101
- });
1102
- }
1103
- dedupeFailures(result);
1104
- onDenied(result, req, res);
1105
- return;
1106
- }
1107
1153
  if (routeConfig.minTrustScore && result.agent) {
1108
1154
  if (result.agent.trustScore < routeConfig.minTrustScore) {
1109
1155
  const trustFailure = {
1110
- dimension: "access_level.insufficient",
1111
- message: `Trust score ${result.agent.trustScore} is below required ${routeConfig.minTrustScore} for this route.`,
1112
- 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."
1156
+ dimension: "endpoint.trust",
1157
+ message: "Trust below the route requirement for this endpoint.",
1158
+ 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."
1113
1159
  };
1114
1160
  result.failures = [...result.failures ?? [], trustFailure];
1115
1161
  result.denialReasons = [trustFailure.message];
@@ -1241,28 +1287,15 @@ function extractAstraSyncCredentialsFromNextRequest(request) {
1241
1287
  });
1242
1288
  return extractHttpCredentials(headers);
1243
1289
  }
1244
- function extractPurpose(request) {
1245
- const astraPurpose = request.headers.get("x-astra-purpose");
1246
- if (astraPurpose) {
1247
- return astraPurpose.split(":")[0];
1248
- }
1249
- const purposeHeader = request.headers.get("x-purpose");
1250
- if (purposeHeader) {
1251
- return purposeHeader;
1252
- }
1253
- switch (request.method.toUpperCase()) {
1254
- case "GET":
1255
- return "read_data";
1256
- case "POST":
1257
- return "write_data";
1258
- case "PUT":
1259
- case "PATCH":
1260
- return "write_data";
1261
- case "DELETE":
1262
- return "delete_data";
1263
- default:
1264
- return "general";
1265
- }
1290
+ function resolveNextPdlss(request, routeConfig) {
1291
+ return resolveHttpPdlss({
1292
+ method: request.method,
1293
+ astraPurpose: request.headers.get("x-astra-purpose") ?? void 0,
1294
+ astraAction: request.headers.get("x-astra-action") ?? void 0,
1295
+ legacyPurpose: request.headers.get("x-purpose") ?? void 0,
1296
+ routePurpose: routeConfig?.purpose,
1297
+ routeAction: routeConfig?.action
1298
+ });
1266
1299
  }
1267
1300
  function generateCommerceShieldHtml(result, options) {
1268
1301
  const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
@@ -1475,7 +1508,16 @@ function createMiddleware2(options) {
1475
1508
  }
1476
1509
  const credentials = extractCredentialsFromNextRequest(request);
1477
1510
  const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
1478
- const purpose = extractPurpose(request);
1511
+ const pdlssPair = resolveNextPdlss(request, routeConfig);
1512
+ const purpose = pdlssPair.purpose;
1513
+ if (config.debug) {
1514
+ console.debug("[nextjs-middleware] pdlss resolved", {
1515
+ purpose_source: pdlssPair.purposeSource,
1516
+ resolved_purpose: pdlssPair.purpose,
1517
+ action_source: pdlssPair.actionSource,
1518
+ resolved_action: pdlssPair.action
1519
+ });
1520
+ }
1479
1521
  const astraCreds = extractAstraSyncCredentialsFromNextRequest(request);
1480
1522
  const preCheckFailures = performCounterpartyPreCheck(routeConfig, astraCreds, purpose);
1481
1523
  if (preCheckFailures.length > 0) {
@@ -1529,10 +1571,7 @@ function createMiddleware2(options) {
1529
1571
  const result = await verify(config, {
1530
1572
  credentials,
1531
1573
  purpose,
1532
- // RFC 7230 § 3.1.1 — HTTP method tokens uppercase by IANA convention.
1533
- // Backend evaluator tolerates either case as defense-in-depth
1534
- // (round-18.6 batch 2); SDK emits canonical form.
1535
- action: request.method.toUpperCase(),
1574
+ action: pdlssPair.action,
1536
1575
  resource: pathname,
1537
1576
  counterpartyUrl,
1538
1577
  counterpartyType: config.counterpartyType || "website",
@@ -1547,7 +1586,7 @@ function createMiddleware2(options) {
1547
1586
  agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0
1548
1587
  }
1549
1588
  });
1550
- if (!result.identityVerified || !result.policyAllowed || !hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
1589
+ if (!result.identityVerified || !result.policyAllowed) {
1551
1590
  if (pathname.startsWith("/api/")) {
1552
1591
  return NextResponse.json(
1553
1592
  {
@@ -1555,10 +1594,8 @@ function createMiddleware2(options) {
1555
1594
  error: {
1556
1595
  // Round-18 G4: 401 → identity missing (re-auth); 403 → identity
1557
1596
  // OK, policy denied (update PDLSS / step up).
1558
- code: !result.identityVerified ? "UNAUTHORIZED" : "INSUFFICIENT_ACCESS",
1597
+ code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
1559
1598
  message: result.denialReasons?.[0] || "Access denied",
1560
- accessLevel: result.accessLevel,
1561
- required: routeConfig.minAccessLevel,
1562
1599
  guidance: result.guidance
1563
1600
  }
1564
1601
  },
@@ -1586,7 +1623,6 @@ function createMiddleware2(options) {
1586
1623
  response.headers.set("X-AstraSync-Access-Level", result.accessLevel);
1587
1624
  if (result.agent) {
1588
1625
  response.headers.set("X-AstraSync-Agent-Id", result.agent.astraId);
1589
- response.headers.set("X-AstraSync-Trust-Score", result.agent.trustScore.toString());
1590
1626
  }
1591
1627
  return response;
1592
1628
  };
@@ -1660,7 +1696,13 @@ var VerificationGatewayClient = class {
1660
1696
  return this.executeWithRetry(() => quickVerify(this.config, credentials));
1661
1697
  }
1662
1698
  /**
1663
- * Check if an agent has a specific access level
1699
+ * Check if an agent has a specific access level.
1700
+ *
1701
+ * @deprecated 3.2.0 — the access-level band is informational only; it no
1702
+ * longer gates in the middleware adapters (post-3.1.0 feedback #1). This
1703
+ * explicit opt-in query still works, but prefer gating on the server-side
1704
+ * policy decision (identity + policy + trust) or per-route `minTrustScore`.
1705
+ * Explicit per-route condition→constraint rules are the Phase-2 successor.
1664
1706
  */
1665
1707
  async hasAccess(credentials, requiredLevel) {
1666
1708
  const result = await this.quickVerify(credentials);
@@ -3287,9 +3329,9 @@ function toBuf(bytes) {
3287
3329
  new Uint8Array(out).set(bytes);
3288
3330
  return out;
3289
3331
  }
3290
- function checkTimestamp(headerValue, toleranceSec, nowFn) {
3291
- if (!headerValue) return { ok: false, error: "missing Timestamp header" };
3292
- const ts = parseTimestamp(headerValue);
3332
+ function checkTimestamp(headerValue2, toleranceSec, nowFn) {
3333
+ if (!headerValue2) return { ok: false, error: "missing Timestamp header" };
3334
+ const ts = parseTimestamp(headerValue2);
3293
3335
  if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
3294
3336
  const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
3295
3337
  if (Math.abs(now - ts) > toleranceSec) {
@@ -3524,14 +3566,14 @@ import {
3524
3566
  } from "@x402/core/schemas";
3525
3567
  import { safeBase64Decode } from "@x402/core/utils";
3526
3568
  function extractX402FromRequest(request) {
3527
- const headerValue = readHeader4(request.headers, "x-payment");
3569
+ const headerValue2 = readHeader4(request.headers, "x-payment");
3528
3570
  if (request.body && typeof request.body === "object") {
3529
3571
  const parsed = tryParsePayload(request.body);
3530
3572
  if (parsed) return buildPayloadContext(parsed, "body");
3531
3573
  }
3532
- if (headerValue) {
3574
+ if (headerValue2) {
3533
3575
  try {
3534
- const decoded = safeBase64Decode(headerValue);
3576
+ const decoded = safeBase64Decode(headerValue2);
3535
3577
  if (decoded) {
3536
3578
  const json = JSON.parse(decoded);
3537
3579
  const parsed = tryParsePayload(json);
@@ -3554,10 +3596,10 @@ function extractX402FromResponse(response) {
3554
3596
  const parsed = tryParseRequired(response.body);
3555
3597
  if (parsed) return buildRequiredContext(parsed, "body");
3556
3598
  }
3557
- const headerValue = readHeader4(response.headers, "x-payment-required");
3558
- if (headerValue) {
3599
+ const headerValue2 = readHeader4(response.headers, "x-payment-required");
3600
+ if (headerValue2) {
3559
3601
  try {
3560
- const decoded = safeBase64Decode(headerValue);
3602
+ const decoded = safeBase64Decode(headerValue2);
3561
3603
  if (decoded) {
3562
3604
  const json = JSON.parse(decoded);
3563
3605
  const parsed = tryParseRequired(json);
@@ -4393,7 +4435,10 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
4393
4435
  }
4394
4436
  let action;
4395
4437
  let actionSource;
4396
- if (headerAction) {
4438
+ if (toolGate?.action !== void 0) {
4439
+ action = toolGate.action;
4440
+ actionSource = "tool_gate";
4441
+ } else if (headerAction) {
4397
4442
  action = headerAction;
4398
4443
  actionSource = "header";
4399
4444
  } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
@@ -4504,7 +4549,6 @@ function createMcpMiddleware(options) {
4504
4549
  return next();
4505
4550
  }
4506
4551
  req.mcpRequest = parsed;
4507
- const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
4508
4552
  const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
4509
4553
  const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
4510
4554
  const bodyAstraId = parsed.agentIdFromBody;
@@ -4541,7 +4585,7 @@ function createMcpMiddleware(options) {
4541
4585
  return next();
4542
4586
  }
4543
4587
  }
4544
- const { level: minAccessLevel, source: gateSource } = resolveMinAccessLevel(parsed, {
4588
+ const { level: minAccessLevel } = resolveMinAccessLevel(parsed, {
4545
4589
  toolGates,
4546
4590
  methodGates
4547
4591
  });
@@ -4567,7 +4611,7 @@ function createMcpMiddleware(options) {
4567
4611
  req.path,
4568
4612
  headerPurpose,
4569
4613
  headerAction,
4570
- gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
4614
+ gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : void 0
4571
4615
  );
4572
4616
  if (config.debug) {
4573
4617
  console.debug("[mcp-middleware] pdlss resolved", {
@@ -4577,6 +4621,23 @@ function createMcpMiddleware(options) {
4577
4621
  resolved_action: pdlss.action
4578
4622
  });
4579
4623
  }
4624
+ if (!pdlss.purpose) {
4625
+ const id = req.body?.id ?? null;
4626
+ res.status(400).json({
4627
+ jsonrpc: "2.0",
4628
+ id,
4629
+ error: {
4630
+ code: -32602,
4631
+ message: "PDLSS_PURPOSE_REQUIRED",
4632
+ data: {
4633
+ dimension: "pdlss.purpose",
4634
+ 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.",
4635
+ resolvedAction: pdlss.action
4636
+ }
4637
+ }
4638
+ });
4639
+ return;
4640
+ }
4580
4641
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
4581
4642
  const shouldRecordDecisions = recordDecisions !== false;
4582
4643
  const result = await verify(config, {
@@ -4600,7 +4661,6 @@ function createMcpMiddleware(options) {
4600
4661
  });
4601
4662
  req.agentVerification = result;
4602
4663
  const sessionId = result.sessionId;
4603
- const correlationId = result.correlationId;
4604
4664
  if (!result.identityVerified || !result.policyAllowed) {
4605
4665
  if (shouldRecordDecisions && sessionId) {
4606
4666
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -4621,48 +4681,6 @@ function createMcpMiddleware(options) {
4621
4681
  }
4622
4682
  return next();
4623
4683
  }
4624
- if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {
4625
- const insufficientFailure = {
4626
- dimension: "access_level.insufficient",
4627
- message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,
4628
- 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."
4629
- };
4630
- result.failures = [...result.failures ?? [], insufficientFailure];
4631
- result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
4632
- if (!result.guidance && wellKnownUrls) {
4633
- result.guidance = {
4634
- message: insufficientFailure.message,
4635
- registrationUrl: wellKnownUrls.registrationUrl,
4636
- documentationUrl: wellKnownUrls.documentationUrl
4637
- };
4638
- }
4639
- if (shouldRecordDecisions) {
4640
- const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
4641
- const override = {
4642
- overriddenBy: overrideKind,
4643
- ...parsed.toolName && { toolName: parsed.toolName },
4644
- requestedLevel: minAccessLevel,
4645
- grantedLevel: result.accessLevel
4646
- };
4647
- if (sessionId) {
4648
- recordDecision(config, sessionId, "denied", result.denialReasons?.[0], override).catch(
4649
- () => {
4650
- }
4651
- );
4652
- } else if (correlationId) {
4653
- recordAnonymousLocalOverride(
4654
- config,
4655
- correlationId,
4656
- override,
4657
- result.denialReasons?.[0]
4658
- ).catch(() => {
4659
- });
4660
- }
4661
- }
4662
- dedupeFailures2(result);
4663
- onDenied(result, req, res);
4664
- return;
4665
- }
4666
4684
  if (effectiveAstraId) {
4667
4685
  res.setHeader(
4668
4686
  MCP_VERIFIED_HOP_HEADER,