@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.mjs CHANGED
@@ -30,7 +30,7 @@ function getTrustLevel(score) {
30
30
  }
31
31
 
32
32
  // src/version.ts
33
- var SDK_VERSION = "5.19.0";
33
+ var SDK_VERSION = "5.20.0";
34
34
 
35
35
  // src/http.ts
36
36
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -312,6 +312,7 @@ function cacheResult(request, result, configuredTtl, counterpartyId) {
312
312
  }
313
313
  function clearCache() {
314
314
  verificationCache.clear();
315
+ inflightVerifies.clear();
315
316
  }
316
317
  function extractCredentials(headers, query) {
317
318
  const credentials = {};
@@ -485,6 +486,9 @@ async function callVerifyAccessAPI(config, request) {
485
486
  if (requestData.enableRuntimeChallenge)
486
487
  body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
487
488
  if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
489
+ if (config.actingProof?.connectorToken) {
490
+ body.actingProof = { connectorToken: config.actingProof.connectorToken };
491
+ }
488
492
  if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
489
493
  if (requestData.createSession) body.createSession = requestData.createSession;
490
494
  if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
@@ -652,22 +656,46 @@ async function verify(config, request, options) {
652
656
  "[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."
653
657
  );
654
658
  }
655
- const uncacheable = Boolean(request.commerceArtifacts);
659
+ const uncacheable = Boolean(request.commerceArtifacts) || Boolean(mergedConfig.actingProof);
656
660
  if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
657
661
  const cached = getCachedResult(request, mergedConfig.counterpartyId);
658
662
  if (cached) {
659
663
  if (mergedConfig.debug) {
660
664
  console.log("[VerificationGateway] Returning cached result");
661
665
  }
662
- const hit = { ...cached };
663
- if (request.attemptId) {
664
- hit.attemptId = request.attemptId;
665
- } else {
666
- delete hit.attemptId;
667
- }
668
- return hit;
666
+ return sharedVerdict(cached, request);
667
+ }
668
+ }
669
+ if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
670
+ const key = getCacheKey(request, mergedConfig.counterpartyId);
671
+ const pending = inflightVerifies.get(key);
672
+ if (pending) return sharedVerdict(await pending, request);
673
+ const flight = verifyViaApi(mergedConfig, request, urls, uncacheable);
674
+ inflightVerifies.set(key, flight);
675
+ try {
676
+ return await flight;
677
+ } finally {
678
+ inflightVerifies.delete(key);
669
679
  }
670
680
  }
681
+ return verifyViaApi(mergedConfig, request, urls, uncacheable);
682
+ }
683
+ var inflightVerifies = /* @__PURE__ */ new Map();
684
+ var sharedVerdicts = /* @__PURE__ */ new WeakSet();
685
+ function isSharedVerdict(result) {
686
+ return sharedVerdicts.has(result);
687
+ }
688
+ function sharedVerdict(source, request) {
689
+ const hit = { ...source };
690
+ if (request.attemptId) {
691
+ hit.attemptId = request.attemptId;
692
+ } else {
693
+ delete hit.attemptId;
694
+ }
695
+ sharedVerdicts.add(hit);
696
+ return hit;
697
+ }
698
+ async function verifyViaApi(mergedConfig, request, urls, uncacheable) {
671
699
  const enrichedRequest = { ...request };
672
700
  if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
673
701
  enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
@@ -746,7 +774,9 @@ async function verify(config, request, options) {
746
774
  fulfilment: apiResponse.fulfilment,
747
775
  mandates: apiResponse.mandates,
748
776
  kyaToken: apiResponse.kyaToken,
749
- aep: apiResponse.aep
777
+ aep: apiResponse.aep,
778
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
779
+ warnings: apiResponse.warnings
750
780
  };
751
781
  return result2;
752
782
  }
@@ -814,7 +844,9 @@ async function verify(config, request, options) {
814
844
  // 5.15.0: merchant-lane AP2 mandates + KYAPay identity token.
815
845
  mandates: apiResponse.mandates,
816
846
  kyaToken: apiResponse.kyaToken,
817
- aep: apiResponse.aep
847
+ aep: apiResponse.aep,
848
+ // 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
849
+ warnings: apiResponse.warnings
818
850
  };
819
851
  if (result.recommendation === "deny") {
820
852
  result.policyAllowed = false;
@@ -840,8 +872,24 @@ async function verify(config, request, options) {
840
872
  cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
841
873
  }
842
874
  }
875
+ notifyVerificationListeners(request, result);
843
876
  return result;
844
877
  }
878
+ var verificationListeners = /* @__PURE__ */ new Set();
879
+ function onVerificationResult(listener) {
880
+ verificationListeners.add(listener);
881
+ return () => {
882
+ verificationListeners.delete(listener);
883
+ };
884
+ }
885
+ function notifyVerificationListeners(request, result) {
886
+ for (const listener of verificationListeners) {
887
+ try {
888
+ listener(result, request);
889
+ } catch {
890
+ }
891
+ }
892
+ }
845
893
  async function recordDecision(config, sessionId, decision, reason) {
846
894
  const headers = { "Content-Type": "application/json" };
847
895
  if (config.apiKey) {
@@ -906,6 +954,7 @@ async function fetchRoutes(config, counterpartyId, options = {}) {
906
954
  headers["X-API-Key"] = config.apiKey;
907
955
  }
908
956
  try {
957
+ headers["X-AstraSync-SDK-Version"] = SDK_VERSION;
909
958
  const response = await sdkFetch(
910
959
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes` + (options.codeGates?.length ? `?codeGates=${options.codeGates.map(encodeURIComponent).join(",")}` : ""),
911
960
  { method: "GET", headers }
@@ -920,6 +969,13 @@ async function fetchRoutes(config, counterpartyId, options = {}) {
920
969
  return null;
921
970
  }
922
971
  const body = await response.json();
972
+ const type2 = body.data?.counterpartyType;
973
+ if (typeof type2 === "string" && options.onEndpointType) {
974
+ try {
975
+ options.onEndpointType(type2);
976
+ } catch {
977
+ }
978
+ }
923
979
  return body.data?.routes ?? [];
924
980
  } catch {
925
981
  return null;
@@ -2843,14 +2899,14 @@ function createMiddleware(options) {
2843
2899
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
2844
2900
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
2845
2901
  }
2846
- if (shouldRecordDecisions && sessionId) {
2902
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2847
2903
  recordDecision(config, sessionId, "granted").catch(() => {
2848
2904
  });
2849
2905
  }
2850
2906
  return next();
2851
2907
  }
2852
2908
  if (!result.policyAllowed) {
2853
- if (shouldRecordDecisions && sessionId) {
2909
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2854
2910
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
2855
2911
  });
2856
2912
  }
@@ -2863,7 +2919,7 @@ function createMiddleware(options) {
2863
2919
  }
2864
2920
  if (requiresHumanApproval(result)) {
2865
2921
  annotateApprovalRequired(result);
2866
- if (shouldRecordDecisions && sessionId) {
2922
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2867
2923
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
2868
2924
  });
2869
2925
  }
@@ -2888,7 +2944,7 @@ function createMiddleware(options) {
2888
2944
  documentationUrl: wellKnownUrls.documentationUrl
2889
2945
  };
2890
2946
  }
2891
- if (shouldRecordDecisions && sessionId) {
2947
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2892
2948
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
2893
2949
  });
2894
2950
  }
@@ -2897,7 +2953,7 @@ function createMiddleware(options) {
2897
2953
  return;
2898
2954
  }
2899
2955
  }
2900
- if (shouldRecordDecisions && sessionId) {
2956
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
2901
2957
  recordDecision(config, sessionId, "granted").catch(() => {
2902
2958
  });
2903
2959
  }
@@ -3745,6 +3801,80 @@ var VerificationGatewayClient = class {
3745
3801
  }
3746
3802
  return json4.data;
3747
3803
  }
3804
+ /**
3805
+ * 5.20.0 (round 29.1): change an endpoint's OPERATIONAL URLs with this
3806
+ * client's API key — `PATCH /api/endpoints/:id`. Accepts the key of the
3807
+ * endpoint's owner, or a personal key of an individual with an active seat
3808
+ * in the owning organisation. Policy (rules, trust floor, challenge, edge
3809
+ * config) stays dashboard-only. `null` clears a field. The owner is alerted
3810
+ * when `eventsUrl` changes.
3811
+ *
3812
+ * @param endpointId the ASTRAE id (defaults to the client's `counterpartyId`)
3813
+ * @returns the endpoint as stored, plus `changed` (the fields that changed)
3814
+ * @throws AstraSyncError — 400 `UNKNOWN_FIELDS` / validation, 401/403, 404
3815
+ */
3816
+ async updateEndpoint(endpointId, patch) {
3817
+ return this.endpointCall("PATCH", endpointId, "", patch);
3818
+ }
3819
+ /**
3820
+ * 5.20.0 (round 29.1): ask AstraSync to send ONE signed step-up event to the
3821
+ * endpoint's Events URL now — `POST /api/endpoints/:id/events/test`.
3822
+ * Delivered synchronously, outside the event outbox (random `eventId`, no
3823
+ * retries), signed with the endpoint's webhook secret like a real event.
3824
+ * Without `approvalId` the payload is synthetic (a kit acknowledges and
3825
+ * ignores it). With the id of a held kit approval at this endpoint the
3826
+ * payload is the real one, so your resume path runs: `approved` makes the
3827
+ * kit re-confirm (held again — nothing is charged); `denied` / `expired` /
3828
+ * `lapsed` release the session.
3829
+ *
3830
+ * Resolves even when your endpoint refused the event (`delivered: false`,
3831
+ * with `responseCode` / `error`).
3832
+ */
3833
+ async sendTestEvent(endpointId, options) {
3834
+ return this.endpointCall("POST", endpointId, "/events/test", options);
3835
+ }
3836
+ async endpointCall(method, endpointId, suffix, body) {
3837
+ const id = endpointId ?? this.config.counterpartyId;
3838
+ if (!id) {
3839
+ throw new AstraSyncError(
3840
+ "An endpoint id (ASTRAE-\u2026) is required: pass it, or set counterpartyId on the client",
3841
+ 400,
3842
+ "VALIDATION"
3843
+ );
3844
+ }
3845
+ if (!this.config.apiKey) {
3846
+ throw new AstraSyncError("This call needs the client apiKey", 401, "AUTH_REQUIRED");
3847
+ }
3848
+ const base = this.config.apiBaseUrl.replace(/\/+$/, "");
3849
+ const url = `${base}/endpoints/${encodeURIComponent(id)}${suffix}`;
3850
+ let res;
3851
+ try {
3852
+ res = await sdkFetch(url, {
3853
+ method,
3854
+ headers: {
3855
+ "Content-Type": "application/json",
3856
+ Authorization: `Bearer ${this.config.apiKey}`,
3857
+ "X-API-Key": this.config.apiKey
3858
+ },
3859
+ body: JSON.stringify(body),
3860
+ signal: AbortSignal.timeout(Math.max(this.timeout, 15e3))
3861
+ });
3862
+ } catch (err) {
3863
+ throw new AstraSyncError(
3864
+ `${method} ${suffix || "/endpoints/:id"} failed: ${err instanceof Error ? err.message : String(err)}`,
3865
+ 0,
3866
+ "network_error"
3867
+ );
3868
+ }
3869
+ const json4 = await res.json().catch(() => ({}));
3870
+ if (!res.ok || json4.success === false || json4.data === void 0) {
3871
+ const errObj = typeof json4.error === "object" ? json4.error : void 0;
3872
+ const code = json4.code ?? errObj?.code ?? (typeof json4.error === "string" ? json4.error : "unknown_error");
3873
+ const message = json4.message ?? errObj?.message ?? `HTTP ${res.status}`;
3874
+ throw new AstraSyncError(`${message} (${code})`, res.status, code);
3875
+ }
3876
+ return json4.data;
3877
+ }
3748
3878
  /**
3749
3879
  * Quick verification — checks credentials and policy in one call.
3750
3880
  *
@@ -6709,16 +6839,30 @@ function createMcpMiddleware(options) {
6709
6839
  failOnError = "closed",
6710
6840
  toolGatePrecedence = "code",
6711
6841
  routesRefreshMs,
6842
+ skipEvaluation,
6712
6843
  ...config
6713
6844
  } = options;
6845
+ const skipTools = new Set(skipEvaluation?.tools ?? []);
6846
+ const skipMethods = new Set(skipEvaluation?.methods ?? []);
6714
6847
  if (config.apiBaseUrl) {
6715
6848
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
6716
6849
  });
6717
6850
  }
6718
6851
  const codeGatedTools = Object.entries(toolGates ?? {}).filter(([, g]) => g !== "observe").map(([name]) => name);
6852
+ let warnedEndpointType = false;
6853
+ const onEndpointType = (counterpartyType) => {
6854
+ if (counterpartyType === "mcp_server" || warnedEndpointType) return;
6855
+ warnedEndpointType = true;
6856
+ console.warn(
6857
+ `[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.`
6858
+ );
6859
+ };
6719
6860
  const rulesCache = config.apiKey && config.counterpartyId ? createRoutesCache(config, {
6720
6861
  ...routesRefreshMs !== void 0 && { refreshMs: routesRefreshMs },
6721
- ...codeGatedTools.length && { fetchOptions: { codeGates: codeGatedTools } }
6862
+ fetchOptions: {
6863
+ ...codeGatedTools.length && { codeGates: codeGatedTools },
6864
+ onEndpointType
6865
+ }
6722
6866
  }) : void 0;
6723
6867
  return async (req, res, next) => {
6724
6868
  try {
@@ -6768,6 +6912,9 @@ function createMcpMiddleware(options) {
6768
6912
  return next();
6769
6913
  }
6770
6914
  }
6915
+ if (!parsed.toolName && skipMethods.has(parsed.method) && methodGates?.[parsed.method] === void 0) {
6916
+ return next();
6917
+ }
6771
6918
  const dashboardRules = rulesCache ? dashboardToolRules(await rulesCache.current()) : void 0;
6772
6919
  const gateDecision = resolveGate(parsed, {
6773
6920
  toolGates,
@@ -6775,6 +6922,9 @@ function createMcpMiddleware(options) {
6775
6922
  dashboardRules,
6776
6923
  toolGatePrecedence
6777
6924
  });
6925
+ if (!gateDecision.enforce && (parsed.toolName && skipTools.has(parsed.toolName) || !parsed.toolName && skipMethods.has(parsed.method))) {
6926
+ return next();
6927
+ }
6778
6928
  const credentials = extractCredentials(
6779
6929
  req.headers,
6780
6930
  req.query
@@ -6831,8 +6981,14 @@ function createMcpMiddleware(options) {
6831
6981
  const forwardedFor = req.headers["x-forwarded-for"];
6832
6982
  const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
6833
6983
  const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
6984
+ const attemptRaw = readSingleHeader(req.headers["x-astra-attempt-id"]);
6985
+ const metaAttempt = req.body?.params?._meta?.astrasync?.attemptId;
6986
+ const attemptId = [attemptRaw, metaAttempt].find(
6987
+ (v) => typeof v === "string" && /^att_[0-9a-f]{32}$/.test(v)
6988
+ );
6834
6989
  const verificationRequest = {
6835
6990
  credentials,
6991
+ ...attemptId && { attemptId },
6836
6992
  purpose: pdlss.purpose,
6837
6993
  action: pdlss.action,
6838
6994
  resource: pdlss.resource,
@@ -6894,14 +7050,14 @@ function createMcpMiddleware(options) {
6894
7050
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
6895
7051
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
6896
7052
  }
6897
- if (shouldRecordDecisions && sessionId) {
7053
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6898
7054
  recordDecision(config, sessionId, "granted").catch(() => {
6899
7055
  });
6900
7056
  }
6901
7057
  return next();
6902
7058
  }
6903
7059
  if (!result.policyAllowed) {
6904
- if (shouldRecordDecisions && sessionId) {
7060
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6905
7061
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
6906
7062
  });
6907
7063
  }
@@ -6911,7 +7067,7 @@ function createMcpMiddleware(options) {
6911
7067
  }
6912
7068
  if (requiresHumanApproval(result)) {
6913
7069
  annotateApprovalRequired(result);
6914
- if (shouldRecordDecisions && sessionId) {
7070
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6915
7071
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
6916
7072
  });
6917
7073
  }
@@ -6929,7 +7085,7 @@ function createMcpMiddleware(options) {
6929
7085
  })
6930
7086
  );
6931
7087
  }
6932
- if (shouldRecordDecisions && sessionId) {
7088
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6933
7089
  recordDecision(config, sessionId, "granted").catch(() => {
6934
7090
  });
6935
7091
  }
@@ -10938,6 +11094,16 @@ function checkUcpAgentBinding(header, identities) {
10938
11094
 
10939
11095
  // src/ucp/profile.ts
10940
11096
  var UCP_VERSION = "2026-08-25";
11097
+ function ucpProfileKeys(profile) {
11098
+ const out = [];
11099
+ for (const list of [profile?.keys, profile?.signing_keys]) {
11100
+ if (!Array.isArray(list)) continue;
11101
+ for (const k of list) {
11102
+ if (k && typeof k === "object" && k.use !== "enc") out.push(k);
11103
+ }
11104
+ }
11105
+ return out;
11106
+ }
10941
11107
  var SPEC = `https://ucp.dev/${UCP_VERSION}/specification`;
10942
11108
  var SCHEMAS = `https://ucp.dev/${UCP_VERSION}/schemas`;
10943
11109
  function astrasyncPaymentHandlers(handlers, specBase = "https://astrasync.ai/ucp/handlers") {
@@ -12102,6 +12268,19 @@ var resumeResult = (outcome, session) => ({
12102
12268
  });
12103
12269
 
12104
12270
  // src/ucp/checkout-kit.ts
12271
+ function withValidImageUrl(item) {
12272
+ if (item.image_url === void 0) return item;
12273
+ let ok = false;
12274
+ try {
12275
+ const u = new URL(item.image_url);
12276
+ ok = u.protocol === "https:" || u.protocol === "http:";
12277
+ } catch {
12278
+ ok = false;
12279
+ }
12280
+ if (ok) return item;
12281
+ const { image_url: _drop, ...rest } = item;
12282
+ return rest;
12283
+ }
12105
12284
  var MemoryCheckoutStore = class {
12106
12285
  constructor() {
12107
12286
  this.m = /* @__PURE__ */ new Map();
@@ -12385,7 +12564,8 @@ function createUcpCheckoutKit(opts) {
12385
12564
  const messages = [];
12386
12565
  for (const [i, l] of lines.entries()) {
12387
12566
  const q = Math.max(1, Math.floor(l.quantity ?? 1));
12388
- const item = l.item?.id ? await opts.catalog(l.item.id, q) : null;
12567
+ const found = l.item?.id ? await opts.catalog(l.item.id, q) : null;
12568
+ const item = found && withValidImageUrl(found);
12389
12569
  if (!item) {
12390
12570
  messages.push(
12391
12571
  ucpError(
@@ -12474,7 +12654,7 @@ function createUcpCheckoutKit(opts) {
12474
12654
  { method: req.method, url: req.url, headers, body: body || void 0 },
12475
12655
  async (fresh) => {
12476
12656
  const pp = fresh ? await fetchProfile(profileUrl, true) : plat;
12477
- return "keys" in pp && pp.keys ? pp.keys : [];
12657
+ return "ucp" in pp ? ucpProfileKeys(pp) : [];
12478
12658
  }
12479
12659
  );
12480
12660
  if (!sig.ok) return { error: json2(sig.status, { code: sig.code, content: sig.content }) };
@@ -12770,7 +12950,7 @@ function createUcpCheckoutKit(opts) {
12770
12950
  ucpError(
12771
12951
  `approval_${reason}`,
12772
12952
  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.",
12773
- "requires_buyer_review"
12953
+ "recoverable"
12774
12954
  )
12775
12955
  ];
12776
12956
  await store.put(s);
@@ -12805,7 +12985,7 @@ function createUcpCheckoutKit(opts) {
12805
12985
  });
12806
12986
  if (status === "approved" || status === "consumed") {
12807
12987
  out.push({ sessionId: s.id, outcome: (await resumeApproved(s.id)).outcome });
12808
- } else if (status === "denied" || status === "expired") {
12988
+ } else if (status === "denied" || status === "expired" || status === "lapsed") {
12809
12989
  out.push({ sessionId: s.id, outcome: (await resumeDeclined(s.id, status)).outcome });
12810
12990
  } else {
12811
12991
  out.push({ sessionId: s.id, outcome: "pending" });
@@ -12848,7 +13028,7 @@ function createUcpCheckoutKit(opts) {
12848
13028
  _caller: caller,
12849
13029
  _platformProfile: ctx.profileUrl,
12850
13030
  _ap2: ctx.ap2,
12851
- _platformKeys: ctx.plat.keys ?? [],
13031
+ _platformKeys: ucpProfileKeys(ctx.plat),
12852
13032
  ...typeof webhookUrl === "string" && /^https:\/\//.test(webhookUrl) && { _webhookUrl: webhookUrl },
12853
13033
  ...req.headers.get("x-astra-checkout-session") && {
12854
13034
  _astraCheckoutSessionId: req.headers.get("x-astra-checkout-session").slice(0, 64)
@@ -13540,7 +13720,12 @@ var ACP_CODES = /* @__PURE__ */ new Set([
13540
13720
  "out_of_stock",
13541
13721
  "payment_declined",
13542
13722
  "requires_sign_in",
13543
- "requires_3ds"
13723
+ "requires_3ds",
13724
+ // 5.20.0: the owner-approval codes the kit itself emits.
13725
+ "approval_required",
13726
+ "approval_denied",
13727
+ "approval_expired",
13728
+ "approval_lapsed"
13544
13729
  ]);
13545
13730
  function heldBody2(text) {
13546
13731
  try {
@@ -13607,7 +13792,8 @@ function createAcpCheckoutKit(opts) {
13607
13792
  const messages = [];
13608
13793
  for (const [i, it] of items.entries()) {
13609
13794
  const q = Math.max(1, Math.floor(it.quantity ?? 1));
13610
- const c = it.id ? await opts.catalog(it.id, q) : null;
13795
+ const found = it.id ? await opts.catalog(it.id, q) : null;
13796
+ const c = found && withValidImageUrl(found);
13611
13797
  if (!c) {
13612
13798
  messages.push({
13613
13799
  type: "error",
@@ -13621,7 +13807,13 @@ function createAcpCheckoutKit(opts) {
13621
13807
  const amount = c.price * q;
13622
13808
  lines.push({
13623
13809
  id: `li_${i + 1}`,
13624
- item: { id: c.id, name: c.title, unit_amount: c.price },
13810
+ item: {
13811
+ id: c.id,
13812
+ name: c.title,
13813
+ unit_amount: c.price,
13814
+ ...c.image_url && { image_url: c.image_url },
13815
+ ...typeof c.description === "string" && { description: c.description }
13816
+ },
13625
13817
  quantity: q,
13626
13818
  name: c.title,
13627
13819
  unit_amount: c.price,
@@ -13968,7 +14160,7 @@ function createAcpCheckoutKit(opts) {
13968
14160
  code: `approval_${reason}`,
13969
14161
  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.",
13970
14162
  severity: "medium",
13971
- resolution: "requires_buyer_review"
14163
+ resolution: "recoverable"
13972
14164
  }
13973
14165
  ];
13974
14166
  await store.put(s);
@@ -14003,7 +14195,7 @@ function createAcpCheckoutKit(opts) {
14003
14195
  });
14004
14196
  if (status === "approved" || status === "consumed") {
14005
14197
  out.push({ sessionId: s.id, outcome: (await resumeApproved(s.id)).outcome });
14006
- } else if (status === "denied" || status === "expired") {
14198
+ } else if (status === "denied" || status === "expired" || status === "lapsed") {
14007
14199
  out.push({ sessionId: s.id, outcome: (await resumeDeclined(s.id, status)).outcome });
14008
14200
  } else {
14009
14201
  out.push({ sessionId: s.id, outcome: "pending" });
@@ -14320,6 +14512,7 @@ export {
14320
14512
  isEdgeConfig,
14321
14513
  isRejectedIdentityClaim,
14322
14514
  isRetryableSettlementError,
14515
+ isSharedVerdict,
14323
14516
  isSpecialUseIp,
14324
14517
  isVerificationUnavailable,
14325
14518
  isVerifiedHopValidFor,
@@ -14331,6 +14524,7 @@ export {
14331
14524
  negotiateUcp,
14332
14525
  nextjs_exports as nextjs,
14333
14526
  normalizeDeclaredSource,
14527
+ onVerificationResult,
14334
14528
  openCheckoutMandate,
14335
14529
  openPaymentMandate,
14336
14530
  parseYaml as parseLocalPolicyYaml,
@@ -14364,6 +14558,7 @@ export {
14364
14558
  ucpEscalation,
14365
14559
  ucpFailure,
14366
14560
  ucpProfileHandler,
14561
+ ucpProfileKeys,
14367
14562
  ucpRequiredComponents,
14368
14563
  unavailableRetryAfterSeconds,
14369
14564
  validatePolicy as validateLocalPolicy,