@astrasyncai/verification-gateway 5.19.0 → 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 (111) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +51 -2
  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 +1 -1
  6. package/dist/adapters/express.d.ts +1 -1
  7. package/dist/adapters/express.js +65 -16
  8. package/dist/adapters/express.js.map +1 -1
  9. package/dist/adapters/express.mjs +65 -16
  10. package/dist/adapters/express.mjs.map +1 -1
  11. package/dist/adapters/mcp.d.mts +23 -1
  12. package/dist/adapters/mcp.d.ts +23 -1
  13. package/dist/adapters/mcp.js +91 -16
  14. package/dist/adapters/mcp.js.map +1 -1
  15. package/dist/adapters/mcp.mjs +91 -16
  16. package/dist/adapters/mcp.mjs.map +1 -1
  17. package/dist/adapters/nextjs.d.mts +1 -1
  18. package/dist/adapters/nextjs.d.ts +1 -1
  19. package/dist/adapters/nextjs.js +57 -11
  20. package/dist/adapters/nextjs.js.map +1 -1
  21. package/dist/adapters/nextjs.mjs +57 -11
  22. package/dist/adapters/nextjs.mjs.map +1 -1
  23. package/dist/adapters/sdk.d.mts +58 -2
  24. package/dist/adapters/sdk.d.ts +58 -2
  25. package/dist/adapters/sdk.js +124 -11
  26. package/dist/adapters/sdk.js.map +1 -1
  27. package/dist/adapters/sdk.mjs +124 -11
  28. package/dist/adapters/sdk.mjs.map +1 -1
  29. package/dist/agent/index.d.mts +1 -1
  30. package/dist/agent/index.d.ts +1 -1
  31. package/dist/agent/index.js +1 -1
  32. package/dist/agent/index.js.map +1 -1
  33. package/dist/agent/index.mjs +1 -1
  34. package/dist/agent/index.mjs.map +1 -1
  35. package/dist/bin/astrasync-claude-hook.js +49 -11
  36. package/dist/bin/astrasync-codex-hook.js +49 -11
  37. package/dist/bin/astrasync-guard.js +49 -11
  38. package/dist/bin/astrasync.js +51 -12
  39. package/dist/browser/background.js +49 -11
  40. package/dist/browser/background.js.map +1 -1
  41. package/dist/browser/background.mjs +49 -11
  42. package/dist/browser/background.mjs.map +1 -1
  43. package/dist/cli/index.js +1 -1
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/cli/index.mjs +1 -1
  46. package/dist/cli/index.mjs.map +1 -1
  47. package/dist/codex/index.js +49 -11
  48. package/dist/codex/index.js.map +1 -1
  49. package/dist/codex/index.mjs +49 -11
  50. package/dist/codex/index.mjs.map +1 -1
  51. package/dist/cursor/extension.js +49 -11
  52. package/dist/cursor/extension.js.map +1 -1
  53. package/dist/cursor/extension.mjs +49 -11
  54. package/dist/cursor/extension.mjs.map +1 -1
  55. package/dist/edge-config.d.mts +1 -1
  56. package/dist/edge-config.d.ts +1 -1
  57. package/dist/edge-config.js +1 -1
  58. package/dist/edge-config.js.map +1 -1
  59. package/dist/edge-config.mjs +1 -1
  60. package/dist/edge-config.mjs.map +1 -1
  61. package/dist/edge-core/index.d.mts +1 -1
  62. package/dist/edge-core/index.d.ts +1 -1
  63. package/dist/edge-core/index.js +49 -11
  64. package/dist/edge-core/index.js.map +1 -1
  65. package/dist/edge-core/index.mjs +49 -11
  66. package/dist/edge-core/index.mjs.map +1 -1
  67. package/dist/gateway/gateway.d.mts +2 -2
  68. package/dist/gateway/gateway.d.ts +2 -2
  69. package/dist/gateway/gateway.js +49 -11
  70. package/dist/gateway/gateway.js.map +1 -1
  71. package/dist/gateway/gateway.mjs +49 -11
  72. package/dist/gateway/gateway.mjs.map +1 -1
  73. package/dist/git-trigger/git-hooks.d.mts +1 -1
  74. package/dist/git-trigger/git-hooks.d.ts +1 -1
  75. package/dist/index.d.mts +168 -8
  76. package/dist/index.d.ts +168 -8
  77. package/dist/index.js +229 -31
  78. package/dist/index.js.map +1 -1
  79. package/dist/index.mjs +226 -31
  80. package/dist/index.mjs.map +1 -1
  81. package/dist/local-evaluator/evaluator.d.mts +2 -2
  82. package/dist/local-evaluator/evaluator.d.ts +2 -2
  83. package/dist/registration/index.d.mts +11 -2
  84. package/dist/registration/index.d.ts +11 -2
  85. package/dist/registration/index.js +1 -1
  86. package/dist/registration/index.js.map +1 -1
  87. package/dist/registration/index.mjs +1 -1
  88. package/dist/registration/index.mjs.map +1 -1
  89. package/dist/transport/index.d.mts +1 -1
  90. package/dist/transport/index.d.ts +1 -1
  91. package/dist/transport/index.js +1 -1
  92. package/dist/transport/index.js.map +1 -1
  93. package/dist/transport/index.mjs +1 -1
  94. package/dist/transport/index.mjs.map +1 -1
  95. package/dist/{types-BRz2U0Pn.d.ts → types-B_HLhZ9n.d.ts} +1 -1
  96. package/dist/{types-BfILnheI.d.mts → types-CU17R4Nr.d.mts} +11 -0
  97. package/dist/{types-BfILnheI.d.ts → types-CU17R4Nr.d.ts} +11 -0
  98. package/dist/{types-DqWPkiYl.d.mts → types-CxjqmPl-.d.mts} +25 -1
  99. package/dist/{types-DqWPkiYl.d.ts → types-CxjqmPl-.d.ts} +25 -1
  100. package/dist/{types-BoGjyon0.d.mts → types-D37aSQ9c.d.mts} +25 -1
  101. package/dist/{types-CfBpm3w5.d.mts → types-Djpz-L0w.d.mts} +1 -1
  102. package/dist/{types-eWyZuz-3.d.ts → types-Dn8tXnei.d.ts} +25 -1
  103. package/dist/ui/index.d.mts +1 -1
  104. package/dist/ui/index.d.ts +1 -1
  105. package/dist/verify.d.mts +28 -3
  106. package/dist/verify.d.ts +28 -3
  107. package/dist/verify.js +71 -11
  108. package/dist/verify.js.map +1 -1
  109. package/dist/verify.mjs +69 -11
  110. package/dist/verify.mjs.map +1 -1
  111. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -133,6 +133,7 @@ __export(src_exports, {
133
133
  isEdgeConfig: () => isEdgeConfig,
134
134
  isRejectedIdentityClaim: () => isRejectedIdentityClaim,
135
135
  isRetryableSettlementError: () => isRetryableSettlementError,
136
+ isSharedVerdict: () => isSharedVerdict,
136
137
  isSpecialUseIp: () => isSpecialUseIp,
137
138
  isVerificationUnavailable: () => isVerificationUnavailable,
138
139
  isVerifiedHopValidFor: () => isVerifiedHopValidFor,
@@ -144,6 +145,7 @@ __export(src_exports, {
144
145
  negotiateUcp: () => negotiateUcp,
145
146
  nextjs: () => nextjs_exports,
146
147
  normalizeDeclaredSource: () => normalizeDeclaredSource,
148
+ onVerificationResult: () => onVerificationResult,
147
149
  openCheckoutMandate: () => openCheckoutMandate,
148
150
  openPaymentMandate: () => openPaymentMandate,
149
151
  parseLocalPolicyYaml: () => parseYaml,
@@ -177,6 +179,7 @@ __export(src_exports, {
177
179
  ucpEscalation: () => ucpEscalation,
178
180
  ucpFailure: () => ucpFailure,
179
181
  ucpProfileHandler: () => ucpProfileHandler,
182
+ ucpProfileKeys: () => ucpProfileKeys,
180
183
  ucpRequiredComponents: () => ucpRequiredComponents,
181
184
  unavailableRetryAfterSeconds: () => unavailableRetryAfterSeconds,
182
185
  validateLocalPolicy: () => validatePolicy,
@@ -218,7 +221,7 @@ function getTrustLevel(score) {
218
221
  }
219
222
 
220
223
  // src/version.ts
221
- var SDK_VERSION = "5.19.0";
224
+ var SDK_VERSION = "5.20.0";
222
225
 
223
226
  // src/http.ts
224
227
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -500,6 +503,7 @@ function cacheResult(request, result, configuredTtl, counterpartyId) {
500
503
  }
501
504
  function clearCache() {
502
505
  verificationCache.clear();
506
+ inflightVerifies.clear();
503
507
  }
504
508
  function extractCredentials(headers, query) {
505
509
  const credentials = {};
@@ -673,6 +677,9 @@ async function callVerifyAccessAPI(config, request) {
673
677
  if (requestData.enableRuntimeChallenge)
674
678
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
675
679
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
680
+ if (config.actingProof?.connectorToken) {
681
+ body.actingProof = { connectorToken: config.actingProof.connectorToken };
682
+ }
676
683
  if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
677
684
  if (requestData.createSession) body.createSession = requestData.createSession;
678
685
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
@@ -840,22 +847,46 @@ async function verify(config, request, options) {
840
847
  "[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."
841
848
  );
842
849
  }
843
- const uncacheable = Boolean(request.commerceArtifacts);
850
+ const uncacheable = Boolean(request.commerceArtifacts) || Boolean(mergedConfig.actingProof);
844
851
  if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
845
852
  const cached = getCachedResult(request, mergedConfig.counterpartyId);
846
853
  if (cached) {
847
854
  if (mergedConfig.debug) {
848
855
  console.log("[VerificationGateway] Returning cached result");
849
856
  }
850
- const hit = { ...cached };
851
- if (request.attemptId) {
852
- hit.attemptId = request.attemptId;
853
- } else {
854
- delete hit.attemptId;
855
- }
856
- return hit;
857
+ return sharedVerdict(cached, request);
858
+ }
859
+ }
860
+ if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
861
+ const key = getCacheKey(request, mergedConfig.counterpartyId);
862
+ const pending = inflightVerifies.get(key);
863
+ if (pending) return sharedVerdict(await pending, request);
864
+ const flight = verifyViaApi(mergedConfig, request, urls, uncacheable);
865
+ inflightVerifies.set(key, flight);
866
+ try {
867
+ return await flight;
868
+ } finally {
869
+ inflightVerifies.delete(key);
857
870
  }
858
871
  }
872
+ return verifyViaApi(mergedConfig, request, urls, uncacheable);
873
+ }
874
+ var inflightVerifies = /* @__PURE__ */ new Map();
875
+ var sharedVerdicts = /* @__PURE__ */ new WeakSet();
876
+ function isSharedVerdict(result) {
877
+ return sharedVerdicts.has(result);
878
+ }
879
+ function sharedVerdict(source, request) {
880
+ const hit = { ...source };
881
+ if (request.attemptId) {
882
+ hit.attemptId = request.attemptId;
883
+ } else {
884
+ delete hit.attemptId;
885
+ }
886
+ sharedVerdicts.add(hit);
887
+ return hit;
888
+ }
889
+ async function verifyViaApi(mergedConfig, request, urls, uncacheable) {
859
890
  const enrichedRequest = { ...request };
860
891
  if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
861
892
  enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
@@ -934,7 +965,9 @@ async function verify(config, request, options) {
934
965
  fulfilment: apiResponse.fulfilment,
935
966
  mandates: apiResponse.mandates,
936
967
  kyaToken: apiResponse.kyaToken,
937
- aep: apiResponse.aep
968
+ aep: apiResponse.aep,
969
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
970
+ warnings: apiResponse.warnings
938
971
  };
939
972
  return result2;
940
973
  }
@@ -1002,7 +1035,9 @@ async function verify(config, request, options) {
1002
1035
  // 5.15.0: merchant-lane AP2 mandates + KYAPay identity token.
1003
1036
  mandates: apiResponse.mandates,
1004
1037
  kyaToken: apiResponse.kyaToken,
1005
- aep: apiResponse.aep
1038
+ aep: apiResponse.aep,
1039
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
1040
+ warnings: apiResponse.warnings
1006
1041
  };
1007
1042
  if (result.recommendation === "deny") {
1008
1043
  result.policyAllowed = false;
@@ -1028,8 +1063,24 @@ async function verify(config, request, options) {
1028
1063
  cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
1029
1064
  }
1030
1065
  }
1066
+ notifyVerificationListeners(request, result);
1031
1067
  return result;
1032
1068
  }
1069
+ var verificationListeners = /* @__PURE__ */ new Set();
1070
+ function onVerificationResult(listener) {
1071
+ verificationListeners.add(listener);
1072
+ return () => {
1073
+ verificationListeners.delete(listener);
1074
+ };
1075
+ }
1076
+ function notifyVerificationListeners(request, result) {
1077
+ for (const listener of verificationListeners) {
1078
+ try {
1079
+ listener(result, request);
1080
+ } catch {
1081
+ }
1082
+ }
1083
+ }
1033
1084
  async function recordDecision(config, sessionId, decision, reason) {
1034
1085
  const headers = { "Content-Type": "application/json" };
1035
1086
  if (config.apiKey) {
@@ -1094,6 +1145,7 @@ async function fetchRoutes(config, counterpartyId, options = {}) {
1094
1145
  headers["X-API-Key"] = config.apiKey;
1095
1146
  }
1096
1147
  try {
1148
+ headers["X-AstraSync-SDK-Version"] = SDK_VERSION;
1097
1149
  const response = await sdkFetch(
1098
1150
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes` + (options.codeGates?.length ? `?codeGates=${options.codeGates.map(encodeURIComponent).join(",")}` : ""),
1099
1151
  { method: "GET", headers }
@@ -1108,6 +1160,13 @@ async function fetchRoutes(config, counterpartyId, options = {}) {
1108
1160
  return null;
1109
1161
  }
1110
1162
  const body = await response.json();
1163
+ const type2 = body.data?.counterpartyType;
1164
+ if (typeof type2 === "string" && options.onEndpointType) {
1165
+ try {
1166
+ options.onEndpointType(type2);
1167
+ } catch {
1168
+ }
1169
+ }
1111
1170
  return body.data?.routes ?? [];
1112
1171
  } catch {
1113
1172
  return null;
@@ -3031,14 +3090,14 @@ function createMiddleware(options) {
3031
3090
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
3032
3091
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
3033
3092
  }
3034
- if (shouldRecordDecisions && sessionId) {
3093
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
3035
3094
  recordDecision(config, sessionId, "granted").catch(() => {
3036
3095
  });
3037
3096
  }
3038
3097
  return next();
3039
3098
  }
3040
3099
  if (!result.policyAllowed) {
3041
- if (shouldRecordDecisions && sessionId) {
3100
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
3042
3101
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
3043
3102
  });
3044
3103
  }
@@ -3051,7 +3110,7 @@ function createMiddleware(options) {
3051
3110
  }
3052
3111
  if (requiresHumanApproval(result)) {
3053
3112
  annotateApprovalRequired(result);
3054
- if (shouldRecordDecisions && sessionId) {
3113
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
3055
3114
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
3056
3115
  });
3057
3116
  }
@@ -3076,7 +3135,7 @@ function createMiddleware(options) {
3076
3135
  documentationUrl: wellKnownUrls.documentationUrl
3077
3136
  };
3078
3137
  }
3079
- if (shouldRecordDecisions && sessionId) {
3138
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
3080
3139
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
3081
3140
  });
3082
3141
  }
@@ -3085,7 +3144,7 @@ function createMiddleware(options) {
3085
3144
  return;
3086
3145
  }
3087
3146
  }
3088
- if (shouldRecordDecisions && sessionId) {
3147
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
3089
3148
  recordDecision(config, sessionId, "granted").catch(() => {
3090
3149
  });
3091
3150
  }
@@ -3933,6 +3992,80 @@ var VerificationGatewayClient = class {
3933
3992
  }
3934
3993
  return json4.data;
3935
3994
  }
3995
+ /**
3996
+ * 5.20.0 (round 29.1): change an endpoint's OPERATIONAL URLs with this
3997
+ * client's API key — `PATCH /api/endpoints/:id`. Accepts the key of the
3998
+ * endpoint's owner, or a personal key of an individual with an active seat
3999
+ * in the owning organisation. Policy (rules, trust floor, challenge, edge
4000
+ * config) stays dashboard-only. `null` clears a field. The owner is alerted
4001
+ * when `eventsUrl` changes.
4002
+ *
4003
+ * @param endpointId the ASTRAE id (defaults to the client's `counterpartyId`)
4004
+ * @returns the endpoint as stored, plus `changed` (the fields that changed)
4005
+ * @throws AstraSyncError — 400 `UNKNOWN_FIELDS` / validation, 401/403, 404
4006
+ */
4007
+ async updateEndpoint(endpointId, patch) {
4008
+ return this.endpointCall("PATCH", endpointId, "", patch);
4009
+ }
4010
+ /**
4011
+ * 5.20.0 (round 29.1): ask AstraSync to send ONE signed step-up event to the
4012
+ * endpoint's Events URL now — `POST /api/endpoints/:id/events/test`.
4013
+ * Delivered synchronously, outside the event outbox (random `eventId`, no
4014
+ * retries), signed with the endpoint's webhook secret like a real event.
4015
+ * Without `approvalId` the payload is synthetic (a kit acknowledges and
4016
+ * ignores it). With the id of a held kit approval at this endpoint the
4017
+ * payload is the real one, so your resume path runs: `approved` makes the
4018
+ * kit re-confirm (held again — nothing is charged); `denied` / `expired` /
4019
+ * `lapsed` release the session.
4020
+ *
4021
+ * Resolves even when your endpoint refused the event (`delivered: false`,
4022
+ * with `responseCode` / `error`).
4023
+ */
4024
+ async sendTestEvent(endpointId, options) {
4025
+ return this.endpointCall("POST", endpointId, "/events/test", options);
4026
+ }
4027
+ async endpointCall(method, endpointId, suffix, body) {
4028
+ const id = endpointId ?? this.config.counterpartyId;
4029
+ if (!id) {
4030
+ throw new AstraSyncError(
4031
+ "An endpoint id (ASTRAE-\u2026) is required: pass it, or set counterpartyId on the client",
4032
+ 400,
4033
+ "VALIDATION"
4034
+ );
4035
+ }
4036
+ if (!this.config.apiKey) {
4037
+ throw new AstraSyncError("This call needs the client apiKey", 401, "AUTH_REQUIRED");
4038
+ }
4039
+ const base = this.config.apiBaseUrl.replace(/\/+$/, "");
4040
+ const url = `${base}/endpoints/${encodeURIComponent(id)}${suffix}`;
4041
+ let res;
4042
+ try {
4043
+ res = await sdkFetch(url, {
4044
+ method,
4045
+ headers: {
4046
+ "Content-Type": "application/json",
4047
+ Authorization: `Bearer ${this.config.apiKey}`,
4048
+ "X-API-Key": this.config.apiKey
4049
+ },
4050
+ body: JSON.stringify(body),
4051
+ signal: AbortSignal.timeout(Math.max(this.timeout, 15e3))
4052
+ });
4053
+ } catch (err) {
4054
+ throw new AstraSyncError(
4055
+ `${method} ${suffix || "/endpoints/:id"} failed: ${err instanceof Error ? err.message : String(err)}`,
4056
+ 0,
4057
+ "network_error"
4058
+ );
4059
+ }
4060
+ const json4 = await res.json().catch(() => ({}));
4061
+ if (!res.ok || json4.success === false || json4.data === void 0) {
4062
+ const errObj = typeof json4.error === "object" ? json4.error : void 0;
4063
+ const code = json4.code ?? errObj?.code ?? (typeof json4.error === "string" ? json4.error : "unknown_error");
4064
+ const message = json4.message ?? errObj?.message ?? `HTTP ${res.status}`;
4065
+ throw new AstraSyncError(`${message} (${code})`, res.status, code);
4066
+ }
4067
+ return json4.data;
4068
+ }
3936
4069
  /**
3937
4070
  * Quick verification — checks credentials and policy in one call.
3938
4071
  *
@@ -6894,16 +7027,30 @@ function createMcpMiddleware(options) {
6894
7027
  failOnError = "closed",
6895
7028
  toolGatePrecedence = "code",
6896
7029
  routesRefreshMs,
7030
+ skipEvaluation,
6897
7031
  ...config
6898
7032
  } = options;
7033
+ const skipTools = new Set(skipEvaluation?.tools ?? []);
7034
+ const skipMethods = new Set(skipEvaluation?.methods ?? []);
6899
7035
  if (config.apiBaseUrl) {
6900
7036
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
6901
7037
  });
6902
7038
  }
6903
7039
  const codeGatedTools = Object.entries(toolGates ?? {}).filter(([, g]) => g !== "observe").map(([name]) => name);
7040
+ let warnedEndpointType = false;
7041
+ const onEndpointType = (counterpartyType) => {
7042
+ if (counterpartyType === "mcp_server" || warnedEndpointType) return;
7043
+ warnedEndpointType = true;
7044
+ console.warn(
7045
+ `[VerificationGateway/MCP] counterpartyId ${config.counterpartyId} is a '${counterpartyType}' endpoint, not an 'mcp_server' endpoint. Dashboard tool rules are read from an mcp_server endpoint only: register a separate MCP endpoint for this server (same owner) and use its id here. See https://astrasync.ai/docs/mcp-integration#tool-rules.`
7046
+ );
7047
+ };
6904
7048
  const rulesCache = config.apiKey && config.counterpartyId ? createRoutesCache(config, {
6905
7049
  ...routesRefreshMs !== void 0 && { refreshMs: routesRefreshMs },
6906
- ...codeGatedTools.length && { fetchOptions: { codeGates: codeGatedTools } }
7050
+ fetchOptions: {
7051
+ ...codeGatedTools.length && { codeGates: codeGatedTools },
7052
+ onEndpointType
7053
+ }
6907
7054
  }) : void 0;
6908
7055
  return async (req, res, next) => {
6909
7056
  try {
@@ -6953,6 +7100,9 @@ function createMcpMiddleware(options) {
6953
7100
  return next();
6954
7101
  }
6955
7102
  }
7103
+ if (!parsed.toolName && skipMethods.has(parsed.method) && methodGates?.[parsed.method] === void 0) {
7104
+ return next();
7105
+ }
6956
7106
  const dashboardRules = rulesCache ? dashboardToolRules(await rulesCache.current()) : void 0;
6957
7107
  const gateDecision = resolveGate(parsed, {
6958
7108
  toolGates,
@@ -6960,6 +7110,9 @@ function createMcpMiddleware(options) {
6960
7110
  dashboardRules,
6961
7111
  toolGatePrecedence
6962
7112
  });
7113
+ if (!gateDecision.enforce && (parsed.toolName && skipTools.has(parsed.toolName) || !parsed.toolName && skipMethods.has(parsed.method))) {
7114
+ return next();
7115
+ }
6963
7116
  const credentials = extractCredentials(
6964
7117
  req.headers,
6965
7118
  req.query
@@ -7016,8 +7169,14 @@ function createMcpMiddleware(options) {
7016
7169
  const forwardedFor = req.headers["x-forwarded-for"];
7017
7170
  const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
7018
7171
  const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
7172
+ const attemptRaw = readSingleHeader(req.headers["x-astra-attempt-id"]);
7173
+ const metaAttempt = req.body?.params?._meta?.astrasync?.attemptId;
7174
+ const attemptId = [attemptRaw, metaAttempt].find(
7175
+ (v) => typeof v === "string" && /^att_[0-9a-f]{32}$/.test(v)
7176
+ );
7019
7177
  const verificationRequest = {
7020
7178
  credentials,
7179
+ ...attemptId && { attemptId },
7021
7180
  purpose: pdlss.purpose,
7022
7181
  action: pdlss.action,
7023
7182
  resource: pdlss.resource,
@@ -7079,14 +7238,14 @@ function createMcpMiddleware(options) {
7079
7238
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
7080
7239
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
7081
7240
  }
7082
- if (shouldRecordDecisions && sessionId) {
7241
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
7083
7242
  recordDecision(config, sessionId, "granted").catch(() => {
7084
7243
  });
7085
7244
  }
7086
7245
  return next();
7087
7246
  }
7088
7247
  if (!result.policyAllowed) {
7089
- if (shouldRecordDecisions && sessionId) {
7248
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
7090
7249
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
7091
7250
  });
7092
7251
  }
@@ -7096,7 +7255,7 @@ function createMcpMiddleware(options) {
7096
7255
  }
7097
7256
  if (requiresHumanApproval(result)) {
7098
7257
  annotateApprovalRequired(result);
7099
- if (shouldRecordDecisions && sessionId) {
7258
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
7100
7259
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
7101
7260
  });
7102
7261
  }
@@ -7114,7 +7273,7 @@ function createMcpMiddleware(options) {
7114
7273
  })
7115
7274
  );
7116
7275
  }
7117
- if (shouldRecordDecisions && sessionId) {
7276
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
7118
7277
  recordDecision(config, sessionId, "granted").catch(() => {
7119
7278
  });
7120
7279
  }
@@ -11123,6 +11282,16 @@ function checkUcpAgentBinding(header, identities) {
11123
11282
 
11124
11283
  // src/ucp/profile.ts
11125
11284
  var UCP_VERSION = "2026-08-25";
11285
+ function ucpProfileKeys(profile) {
11286
+ const out = [];
11287
+ for (const list of [profile?.keys, profile?.signing_keys]) {
11288
+ if (!Array.isArray(list)) continue;
11289
+ for (const k of list) {
11290
+ if (k && typeof k === "object" && k.use !== "enc") out.push(k);
11291
+ }
11292
+ }
11293
+ return out;
11294
+ }
11126
11295
  var SPEC = `https://ucp.dev/${UCP_VERSION}/specification`;
11127
11296
  var SCHEMAS = `https://ucp.dev/${UCP_VERSION}/schemas`;
11128
11297
  function astrasyncPaymentHandlers(handlers, specBase = "https://astrasync.ai/ucp/handlers") {
@@ -12282,6 +12451,19 @@ var resumeResult = (outcome, session) => ({
12282
12451
  });
12283
12452
 
12284
12453
  // src/ucp/checkout-kit.ts
12454
+ function withValidImageUrl(item) {
12455
+ if (item.image_url === void 0) return item;
12456
+ let ok = false;
12457
+ try {
12458
+ const u = new URL(item.image_url);
12459
+ ok = u.protocol === "https:" || u.protocol === "http:";
12460
+ } catch {
12461
+ ok = false;
12462
+ }
12463
+ if (ok) return item;
12464
+ const { image_url: _drop, ...rest } = item;
12465
+ return rest;
12466
+ }
12285
12467
  var MemoryCheckoutStore = class {
12286
12468
  constructor() {
12287
12469
  this.m = /* @__PURE__ */ new Map();
@@ -12565,7 +12747,8 @@ function createUcpCheckoutKit(opts) {
12565
12747
  const messages = [];
12566
12748
  for (const [i, l] of lines.entries()) {
12567
12749
  const q = Math.max(1, Math.floor(l.quantity ?? 1));
12568
- const item = l.item?.id ? await opts.catalog(l.item.id, q) : null;
12750
+ const found = l.item?.id ? await opts.catalog(l.item.id, q) : null;
12751
+ const item = found && withValidImageUrl(found);
12569
12752
  if (!item) {
12570
12753
  messages.push(
12571
12754
  ucpError(
@@ -12654,7 +12837,7 @@ function createUcpCheckoutKit(opts) {
12654
12837
  { method: req.method, url: req.url, headers, body: body || void 0 },
12655
12838
  async (fresh) => {
12656
12839
  const pp = fresh ? await fetchProfile(profileUrl, true) : plat;
12657
- return "keys" in pp && pp.keys ? pp.keys : [];
12840
+ return "ucp" in pp ? ucpProfileKeys(pp) : [];
12658
12841
  }
12659
12842
  );
12660
12843
  if (!sig.ok) return { error: json2(sig.status, { code: sig.code, content: sig.content }) };
@@ -12950,7 +13133,7 @@ function createUcpCheckoutKit(opts) {
12950
13133
  ucpError(
12951
13134
  `approval_${reason}`,
12952
13135
  reason === "denied" ? "The buyer declined this purchase." : "The approval for this purchase is no longer valid. Complete the checkout again to request a new one.",
12953
- "requires_buyer_review"
13136
+ "recoverable"
12954
13137
  )
12955
13138
  ];
12956
13139
  await store.put(s);
@@ -12985,7 +13168,7 @@ function createUcpCheckoutKit(opts) {
12985
13168
  });
12986
13169
  if (status === "approved" || status === "consumed") {
12987
13170
  out.push({ sessionId: s.id, outcome: (await resumeApproved(s.id)).outcome });
12988
- } else if (status === "denied" || status === "expired") {
13171
+ } else if (status === "denied" || status === "expired" || status === "lapsed") {
12989
13172
  out.push({ sessionId: s.id, outcome: (await resumeDeclined(s.id, status)).outcome });
12990
13173
  } else {
12991
13174
  out.push({ sessionId: s.id, outcome: "pending" });
@@ -13028,7 +13211,7 @@ function createUcpCheckoutKit(opts) {
13028
13211
  _caller: caller,
13029
13212
  _platformProfile: ctx.profileUrl,
13030
13213
  _ap2: ctx.ap2,
13031
- _platformKeys: ctx.plat.keys ?? [],
13214
+ _platformKeys: ucpProfileKeys(ctx.plat),
13032
13215
  ...typeof webhookUrl === "string" && /^https:\/\//.test(webhookUrl) && { _webhookUrl: webhookUrl },
13033
13216
  ...req.headers.get("x-astra-checkout-session") && {
13034
13217
  _astraCheckoutSessionId: req.headers.get("x-astra-checkout-session").slice(0, 64)
@@ -13709,7 +13892,12 @@ var ACP_CODES = /* @__PURE__ */ new Set([
13709
13892
  "out_of_stock",
13710
13893
  "payment_declined",
13711
13894
  "requires_sign_in",
13712
- "requires_3ds"
13895
+ "requires_3ds",
13896
+ // 5.20.0: the owner-approval codes the kit itself emits.
13897
+ "approval_required",
13898
+ "approval_denied",
13899
+ "approval_expired",
13900
+ "approval_lapsed"
13713
13901
  ]);
13714
13902
  function heldBody2(text) {
13715
13903
  try {
@@ -13776,7 +13964,8 @@ function createAcpCheckoutKit(opts) {
13776
13964
  const messages = [];
13777
13965
  for (const [i, it] of items.entries()) {
13778
13966
  const q = Math.max(1, Math.floor(it.quantity ?? 1));
13779
- const c = it.id ? await opts.catalog(it.id, q) : null;
13967
+ const found = it.id ? await opts.catalog(it.id, q) : null;
13968
+ const c = found && withValidImageUrl(found);
13780
13969
  if (!c) {
13781
13970
  messages.push({
13782
13971
  type: "error",
@@ -13790,7 +13979,13 @@ function createAcpCheckoutKit(opts) {
13790
13979
  const amount = c.price * q;
13791
13980
  lines.push({
13792
13981
  id: `li_${i + 1}`,
13793
- item: { id: c.id, name: c.title, unit_amount: c.price },
13982
+ item: {
13983
+ id: c.id,
13984
+ name: c.title,
13985
+ unit_amount: c.price,
13986
+ ...c.image_url && { image_url: c.image_url },
13987
+ ...typeof c.description === "string" && { description: c.description }
13988
+ },
13794
13989
  quantity: q,
13795
13990
  name: c.title,
13796
13991
  unit_amount: c.price,
@@ -14137,7 +14332,7 @@ function createAcpCheckoutKit(opts) {
14137
14332
  code: `approval_${reason}`,
14138
14333
  content: reason === "denied" ? "The buyer declined this purchase." : "The approval for this purchase is no longer valid. Complete the session again to request a new one.",
14139
14334
  severity: "medium",
14140
- resolution: "requires_buyer_review"
14335
+ resolution: "recoverable"
14141
14336
  }
14142
14337
  ];
14143
14338
  await store.put(s);
@@ -14172,7 +14367,7 @@ function createAcpCheckoutKit(opts) {
14172
14367
  });
14173
14368
  if (status === "approved" || status === "consumed") {
14174
14369
  out.push({ sessionId: s.id, outcome: (await resumeApproved(s.id)).outcome });
14175
- } else if (status === "denied" || status === "expired") {
14370
+ } else if (status === "denied" || status === "expired" || status === "lapsed") {
14176
14371
  out.push({ sessionId: s.id, outcome: (await resumeDeclined(s.id, status)).outcome });
14177
14372
  } else {
14178
14373
  out.push({ sessionId: s.id, outcome: "pending" });
@@ -14490,6 +14685,7 @@ function expressHandler(kit, options = {}) {
14490
14685
  isEdgeConfig,
14491
14686
  isRejectedIdentityClaim,
14492
14687
  isRetryableSettlementError,
14688
+ isSharedVerdict,
14493
14689
  isSpecialUseIp,
14494
14690
  isVerificationUnavailable,
14495
14691
  isVerifiedHopValidFor,
@@ -14501,6 +14697,7 @@ function expressHandler(kit, options = {}) {
14501
14697
  negotiateUcp,
14502
14698
  nextjs,
14503
14699
  normalizeDeclaredSource,
14700
+ onVerificationResult,
14504
14701
  openCheckoutMandate,
14505
14702
  openPaymentMandate,
14506
14703
  parseLocalPolicyYaml,
@@ -14534,6 +14731,7 @@ function expressHandler(kit, options = {}) {
14534
14731
  ucpEscalation,
14535
14732
  ucpFailure,
14536
14733
  ucpProfileHandler,
14734
+ ucpProfileKeys,
14537
14735
  ucpRequiredComponents,
14538
14736
  unavailableRetryAfterSeconds,
14539
14737
  validateLocalPolicy,