@astrasyncai/verification-gateway 5.19.0 → 5.21.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 +130 -0
  2. package/README.md +53 -4
  3. package/dist/adapter-interface/interface.d.mts +2 -2
  4. package/dist/adapter-interface/interface.d.ts +2 -2
  5. package/dist/adapters/express.d.mts +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 +35 -2
  24. package/dist/adapters/sdk.d.ts +35 -2
  25. package/dist/adapters/sdk.js +110 -11
  26. package/dist/adapters/sdk.js.map +1 -1
  27. package/dist/adapters/sdk.mjs +110 -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 +50 -12
  36. package/dist/bin/astrasync-codex-hook.js +50 -12
  37. package/dist/bin/astrasync-guard.js +50 -12
  38. package/dist/bin/astrasync.js +65 -13
  39. package/dist/browser/background.js +50 -12
  40. package/dist/browser/background.js.map +1 -1
  41. package/dist/browser/background.mjs +50 -12
  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 +50 -12
  48. package/dist/codex/index.js.map +1 -1
  49. package/dist/codex/index.mjs +50 -12
  50. package/dist/codex/index.mjs.map +1 -1
  51. package/dist/cursor/extension.js +50 -12
  52. package/dist/cursor/extension.js.map +1 -1
  53. package/dist/cursor/extension.mjs +50 -12
  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 +50 -12
  70. package/dist/gateway/gateway.js.map +1 -1
  71. package/dist/gateway/gateway.mjs +50 -12
  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 +286 -8
  76. package/dist/index.d.ts +286 -8
  77. package/dist/index.js +532 -49
  78. package/dist/index.js.map +1 -1
  79. package/dist/index.mjs +522 -49
  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-BoGjyon0.d.mts → types-B4zhOAlq.d.mts} +30 -1
  96. package/dist/{types-BRz2U0Pn.d.ts → types-B_HLhZ9n.d.ts} +1 -1
  97. package/dist/{types-BfILnheI.d.mts → types-CU17R4Nr.d.mts} +11 -0
  98. package/dist/{types-BfILnheI.d.ts → types-CU17R4Nr.d.ts} +11 -0
  99. package/dist/{types-eWyZuz-3.d.ts → types-CtBXnWuV.d.ts} +30 -1
  100. package/dist/{types-DqWPkiYl.d.mts → types-DHHyXtuZ.d.mts} +30 -1
  101. package/dist/{types-DqWPkiYl.d.ts → types-DHHyXtuZ.d.ts} +30 -1
  102. package/dist/{types-CfBpm3w5.d.mts → types-Djpz-L0w.d.mts} +1 -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.21.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,66 @@ var VerificationGatewayClient = class {
3745
3801
  }
3746
3802
  return json4.data;
3747
3803
  }
3804
+ /**
3805
+ * 5.20.0 (round 29.1): ask AstraSync to send ONE signed step-up event to the
3806
+ * endpoint's Events URL now — `POST /api/endpoints/:id/events/test`.
3807
+ * Delivered synchronously, outside the event outbox (random `eventId`, no
3808
+ * retries), signed with the endpoint's webhook secret like a real event.
3809
+ * The payload is synthetic (`approvalId: "test_…"`); a kit acknowledges and
3810
+ * ignores it.
3811
+ *
3812
+ * 5.21.0 (shops' item 48): synthetic only. Sending a real held approval's
3813
+ * payload (which releases or re-drives that checkout) is dashboard-only, so
3814
+ * a leaked API key can't do it.
3815
+ *
3816
+ * Resolves even when your endpoint refused the event (`delivered: false`,
3817
+ * with `responseCode` / `error`).
3818
+ */
3819
+ async sendTestEvent(endpointId, options) {
3820
+ return this.endpointCall("POST", endpointId, "/events/test", { type: options.type });
3821
+ }
3822
+ async endpointCall(method, endpointId, suffix, body) {
3823
+ const id = endpointId ?? this.config.counterpartyId;
3824
+ if (!id) {
3825
+ throw new AstraSyncError(
3826
+ "An endpoint id (ASTRAE-\u2026) is required: pass it, or set counterpartyId on the client",
3827
+ 400,
3828
+ "VALIDATION"
3829
+ );
3830
+ }
3831
+ if (!this.config.apiKey) {
3832
+ throw new AstraSyncError("This call needs the client apiKey", 401, "AUTH_REQUIRED");
3833
+ }
3834
+ const base = this.config.apiBaseUrl.replace(/\/+$/, "");
3835
+ const url = `${base}/endpoints/${encodeURIComponent(id)}${suffix}`;
3836
+ let res;
3837
+ try {
3838
+ res = await sdkFetch(url, {
3839
+ method,
3840
+ headers: {
3841
+ "Content-Type": "application/json",
3842
+ Authorization: `Bearer ${this.config.apiKey}`,
3843
+ "X-API-Key": this.config.apiKey
3844
+ },
3845
+ body: JSON.stringify(body),
3846
+ signal: AbortSignal.timeout(Math.max(this.timeout, 15e3))
3847
+ });
3848
+ } catch (err) {
3849
+ throw new AstraSyncError(
3850
+ `${method} ${suffix || "/endpoints/:id"} failed: ${err instanceof Error ? err.message : String(err)}`,
3851
+ 0,
3852
+ "network_error"
3853
+ );
3854
+ }
3855
+ const json4 = await res.json().catch(() => ({}));
3856
+ if (!res.ok || json4.success === false || json4.data === void 0) {
3857
+ const errObj = typeof json4.error === "object" ? json4.error : void 0;
3858
+ const code = json4.code ?? errObj?.code ?? (typeof json4.error === "string" ? json4.error : "unknown_error");
3859
+ const message = json4.message ?? errObj?.message ?? `HTTP ${res.status}`;
3860
+ throw new AstraSyncError(`${message} (${code})`, res.status, code);
3861
+ }
3862
+ return json4.data;
3863
+ }
3748
3864
  /**
3749
3865
  * Quick verification — checks credentials and policy in one call.
3750
3866
  *
@@ -6709,16 +6825,30 @@ function createMcpMiddleware(options) {
6709
6825
  failOnError = "closed",
6710
6826
  toolGatePrecedence = "code",
6711
6827
  routesRefreshMs,
6828
+ skipEvaluation,
6712
6829
  ...config
6713
6830
  } = options;
6831
+ const skipTools = new Set(skipEvaluation?.tools ?? []);
6832
+ const skipMethods = new Set(skipEvaluation?.methods ?? []);
6714
6833
  if (config.apiBaseUrl) {
6715
6834
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
6716
6835
  });
6717
6836
  }
6718
6837
  const codeGatedTools = Object.entries(toolGates ?? {}).filter(([, g]) => g !== "observe").map(([name]) => name);
6838
+ let warnedEndpointType = false;
6839
+ const onEndpointType = (counterpartyType) => {
6840
+ if (counterpartyType === "mcp_server" || warnedEndpointType) return;
6841
+ warnedEndpointType = true;
6842
+ console.warn(
6843
+ `[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.`
6844
+ );
6845
+ };
6719
6846
  const rulesCache = config.apiKey && config.counterpartyId ? createRoutesCache(config, {
6720
6847
  ...routesRefreshMs !== void 0 && { refreshMs: routesRefreshMs },
6721
- ...codeGatedTools.length && { fetchOptions: { codeGates: codeGatedTools } }
6848
+ fetchOptions: {
6849
+ ...codeGatedTools.length && { codeGates: codeGatedTools },
6850
+ onEndpointType
6851
+ }
6722
6852
  }) : void 0;
6723
6853
  return async (req, res, next) => {
6724
6854
  try {
@@ -6768,6 +6898,9 @@ function createMcpMiddleware(options) {
6768
6898
  return next();
6769
6899
  }
6770
6900
  }
6901
+ if (!parsed.toolName && skipMethods.has(parsed.method) && methodGates?.[parsed.method] === void 0) {
6902
+ return next();
6903
+ }
6771
6904
  const dashboardRules = rulesCache ? dashboardToolRules(await rulesCache.current()) : void 0;
6772
6905
  const gateDecision = resolveGate(parsed, {
6773
6906
  toolGates,
@@ -6775,6 +6908,9 @@ function createMcpMiddleware(options) {
6775
6908
  dashboardRules,
6776
6909
  toolGatePrecedence
6777
6910
  });
6911
+ if (!gateDecision.enforce && (parsed.toolName && skipTools.has(parsed.toolName) || !parsed.toolName && skipMethods.has(parsed.method))) {
6912
+ return next();
6913
+ }
6778
6914
  const credentials = extractCredentials(
6779
6915
  req.headers,
6780
6916
  req.query
@@ -6831,8 +6967,14 @@ function createMcpMiddleware(options) {
6831
6967
  const forwardedFor = req.headers["x-forwarded-for"];
6832
6968
  const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
6833
6969
  const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
6970
+ const attemptRaw = readSingleHeader(req.headers["x-astra-attempt-id"]);
6971
+ const metaAttempt = req.body?.params?._meta?.astrasync?.attemptId;
6972
+ const attemptId = [attemptRaw, metaAttempt].find(
6973
+ (v) => typeof v === "string" && /^att_[0-9a-f]{32}$/.test(v)
6974
+ );
6834
6975
  const verificationRequest = {
6835
6976
  credentials,
6977
+ ...attemptId && { attemptId },
6836
6978
  purpose: pdlss.purpose,
6837
6979
  action: pdlss.action,
6838
6980
  resource: pdlss.resource,
@@ -6894,14 +7036,14 @@ function createMcpMiddleware(options) {
6894
7036
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
6895
7037
  res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
6896
7038
  }
6897
- if (shouldRecordDecisions && sessionId) {
7039
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6898
7040
  recordDecision(config, sessionId, "granted").catch(() => {
6899
7041
  });
6900
7042
  }
6901
7043
  return next();
6902
7044
  }
6903
7045
  if (!result.policyAllowed) {
6904
- if (shouldRecordDecisions && sessionId) {
7046
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6905
7047
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
6906
7048
  });
6907
7049
  }
@@ -6911,7 +7053,7 @@ function createMcpMiddleware(options) {
6911
7053
  }
6912
7054
  if (requiresHumanApproval(result)) {
6913
7055
  annotateApprovalRequired(result);
6914
- if (shouldRecordDecisions && sessionId) {
7056
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6915
7057
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
6916
7058
  });
6917
7059
  }
@@ -6929,7 +7071,7 @@ function createMcpMiddleware(options) {
6929
7071
  })
6930
7072
  );
6931
7073
  }
6932
- if (shouldRecordDecisions && sessionId) {
7074
+ if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
6933
7075
  recordDecision(config, sessionId, "granted").catch(() => {
6934
7076
  });
6935
7077
  }
@@ -8869,13 +9011,13 @@ function requireLoader() {
8869
9011
  throwError(state, "ill-formed argument of the YAML directive");
8870
9012
  }
8871
9013
  const major = parseInt(match[1], 10);
8872
- const minor = parseInt(match[2], 10);
9014
+ const minor2 = parseInt(match[2], 10);
8873
9015
  if (major !== 1) {
8874
9016
  throwError(state, "unacceptable YAML version of the document");
8875
9017
  }
8876
9018
  state.version = args[0];
8877
- state.checkLineBreaks = minor < 2;
8878
- if (minor !== 1 && minor !== 2) {
9019
+ state.checkLineBreaks = minor2 < 2;
9020
+ if (minor2 !== 1 && minor2 !== 2) {
8879
9021
  throwWarning(state, "unsupported YAML version of the document");
8880
9022
  }
8881
9023
  },
@@ -10938,6 +11080,16 @@ function checkUcpAgentBinding(header, identities) {
10938
11080
 
10939
11081
  // src/ucp/profile.ts
10940
11082
  var UCP_VERSION = "2026-08-25";
11083
+ function ucpProfileKeys(profile) {
11084
+ const out = [];
11085
+ for (const list of [profile?.keys, profile?.signing_keys]) {
11086
+ if (!Array.isArray(list)) continue;
11087
+ for (const k of list) {
11088
+ if (k && typeof k === "object" && k.use !== "enc") out.push(k);
11089
+ }
11090
+ }
11091
+ return out;
11092
+ }
10941
11093
  var SPEC = `https://ucp.dev/${UCP_VERSION}/specification`;
10942
11094
  var SCHEMAS = `https://ucp.dev/${UCP_VERSION}/schemas`;
10943
11095
  function astrasyncPaymentHandlers(handlers, specBase = "https://astrasync.ai/ucp/handlers") {
@@ -11361,6 +11513,228 @@ async function sendUcpWebhook(opts) {
11361
11513
  return { ok: false, status: last, attempts };
11362
11514
  }
11363
11515
 
11516
+ // src/ucp/agent-commerce.ts
11517
+ var ISSUER_PROGRAM_HEADER = "X-Astra-Issuer-Program";
11518
+ function issuerOfferPrice(priceMinor, percentOff) {
11519
+ if (!percentOff) return priceMinor;
11520
+ const pct = Math.min(100, Math.max(0, percentOff));
11521
+ return Math.max(0, Math.round(priceMinor * (1 - pct / 100)));
11522
+ }
11523
+ function priceForAgent(rules, sku, basePriceMinor, ctx) {
11524
+ const rule = rules?.products?.[sku];
11525
+ let price = rule?.priceMinor ?? basePriceMinor;
11526
+ if (ctx.verified && rules?.verifiedPercentOff) {
11527
+ price = issuerOfferPrice(price, rules.verifiedPercentOff);
11528
+ }
11529
+ const offer = ctx.verified && ctx.issuerProgram && rule?.issuerEligible !== false ? rules?.offers?.find((o) => o.program === ctx.issuerProgram) : void 0;
11530
+ if (offer?.pricePercentOff) {
11531
+ return {
11532
+ priceMinor: issuerOfferPrice(price, offer.pricePercentOff),
11533
+ regularMinor: price,
11534
+ issuerProgram: offer.program
11535
+ };
11536
+ }
11537
+ return { priceMinor: price };
11538
+ }
11539
+ function visibleToAgent(rules, sku, ctx) {
11540
+ if (!ctx.verified && rules?.hideFromUnverified) return false;
11541
+ return rules?.products?.[sku]?.visible !== false;
11542
+ }
11543
+ function createAgentCommerce(opts) {
11544
+ const f = opts.fetch ?? sdkFetch;
11545
+ let current = null;
11546
+ let kitReport;
11547
+ const refreshMs = opts.refreshMs ?? 5 * 6e4;
11548
+ async function refresh() {
11549
+ try {
11550
+ const headers = {
11551
+ Accept: "application/json",
11552
+ Authorization: `Bearer ${opts.apiKey}`,
11553
+ "X-AstraSync-SDK-Version": SDK_VERSION
11554
+ };
11555
+ if (kitReport) {
11556
+ headers["X-AstraSync-Kit-Report"] = Buffer.from(JSON.stringify(kitReport)).toString(
11557
+ "base64url"
11558
+ );
11559
+ }
11560
+ const res = await f(
11561
+ `${opts.apiBaseUrl.replace(/\/+$/, "")}/endpoints/${encodeURIComponent(
11562
+ opts.counterpartyId
11563
+ )}/agent-commerce`,
11564
+ { method: "GET", headers }
11565
+ );
11566
+ if (!res.ok) return;
11567
+ const body = await res.json();
11568
+ if (body.data && typeof body.data.version === "number") current = body.data;
11569
+ } catch {
11570
+ }
11571
+ }
11572
+ const timer = refreshMs > 0 ? setInterval(() => {
11573
+ void refresh();
11574
+ }, refreshMs) : null;
11575
+ timer?.unref?.();
11576
+ void refresh();
11577
+ return {
11578
+ rules: () => current,
11579
+ refresh,
11580
+ price: (sku, base, ctx) => priceForAgent(current, sku, base, ctx),
11581
+ visible: (sku, ctx) => visibleToAgent(current, sku, ctx),
11582
+ report: (r) => {
11583
+ kitReport = { ...kitReport, ...r };
11584
+ },
11585
+ stop: () => {
11586
+ if (timer) clearInterval(timer);
11587
+ }
11588
+ };
11589
+ }
11590
+ function pricingContextFrom(headers) {
11591
+ const astraId = headers.get("x-astra-id");
11592
+ const hop = parseVerifiedHop(headers.get(MCP_VERIFIED_HOP_HEADER.toLowerCase()));
11593
+ const verified = !!astraId && (!hop || hop.astraId === astraId && Date.now() - hop.checkedAt <= MCP_VERIFIED_HOP_MAX_AGE_MS);
11594
+ const program = headers.get(ISSUER_PROGRAM_HEADER.toLowerCase());
11595
+ return {
11596
+ verified,
11597
+ ...verified && program && /^[a-z0-9-]{2,40}$/.test(program) && { issuerProgram: program }
11598
+ };
11599
+ }
11600
+ function minor(value) {
11601
+ const n = typeof value === "number" ? value : Number(String(value ?? "").replace(/[^\d.]/g, ""));
11602
+ return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) : null;
11603
+ }
11604
+ function fromFeed(url, options = {}) {
11605
+ const f = options.fetch ?? fetch;
11606
+ let cache4 = null;
11607
+ return async () => {
11608
+ if (cache4 && Date.now() - cache4.at < 6e4) return cache4.products;
11609
+ const res = await f(url, {
11610
+ headers: { Accept: "application/json, application/xml, text/csv" }
11611
+ });
11612
+ if (!res.ok) throw new Error(`Product feed ${url} answered ${res.status}`);
11613
+ const text = await res.text();
11614
+ const products = parseFeed(text, options.currency ?? "AUD");
11615
+ cache4 = { at: Date.now(), products };
11616
+ return products;
11617
+ };
11618
+ }
11619
+ function parseFeed(text, currency) {
11620
+ const trimmed = text.trimStart();
11621
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
11622
+ const json4 = JSON.parse(trimmed);
11623
+ const list = Array.isArray(json4) ? json4 : json4.products ?? json4.items ?? [];
11624
+ return list.flatMap((p) => {
11625
+ const variant = p.variants?.[0];
11626
+ const price = minor(variant?.price ?? p.price);
11627
+ const sku = String(variant?.sku || p.sku || p.id || "");
11628
+ if (!sku || price === null) return [];
11629
+ const images = Array.isArray(p.images) ? p.images.map(
11630
+ (i) => typeof i === "string" ? i : i.src ?? ""
11631
+ ) : [p.image_url, p.image].filter((x) => typeof x === "string");
11632
+ return [
11633
+ {
11634
+ sku,
11635
+ name: String(p.title ?? p.name ?? sku),
11636
+ priceMinor: price,
11637
+ currency: String(p.currency ?? currency),
11638
+ ...typeof p.description === "string" && { description: p.description },
11639
+ imageUrls: images.filter((u) => /^https:\/\//.test(u)),
11640
+ inStock: variant ? variant.available !== false : p.in_stock !== false
11641
+ }
11642
+ ];
11643
+ });
11644
+ }
11645
+ if (trimmed.startsWith("<")) {
11646
+ const items = trimmed.split(/<item[\s>]|<entry[\s>]/).slice(1);
11647
+ return items.flatMap((chunk) => {
11648
+ const tag = (name) => {
11649
+ const m = new RegExp(`<(?:g:)?${name}[^>]*>([\\s\\S]*?)</(?:g:)?${name}>`).exec(chunk);
11650
+ return m ? m[1].replace(/<!\[CDATA\[|\]\]>/g, "").trim() : void 0;
11651
+ };
11652
+ const sku = tag("id");
11653
+ const price = minor(tag("price"));
11654
+ if (!sku || price === null) return [];
11655
+ const extra = [...chunk.matchAll(/<g:additional_image_link>([^<]+)</g)].map(
11656
+ (m) => m[1].trim()
11657
+ );
11658
+ return [
11659
+ {
11660
+ sku,
11661
+ name: tag("title") ?? sku,
11662
+ priceMinor: price,
11663
+ currency: /[A-Z]{3}/.exec(tag("price") ?? "")?.[0] ?? currency,
11664
+ imageUrls: [tag("image_link"), ...extra].filter(
11665
+ (u) => !!u && /^https:\/\//.test(u)
11666
+ ),
11667
+ inStock: (tag("availability") ?? "in stock").replace(/_/g, " ") !== "out of stock"
11668
+ }
11669
+ ];
11670
+ });
11671
+ }
11672
+ const [head, ...rows] = trimmed.split(/\r?\n/).filter(Boolean);
11673
+ const cols = head.split(",").map((c) => c.trim().toLowerCase());
11674
+ const at = (r, ...names) => {
11675
+ const i = cols.findIndex((c) => names.includes(c));
11676
+ return i >= 0 ? r[i]?.trim() : void 0;
11677
+ };
11678
+ return rows.flatMap((line) => {
11679
+ const r = line.split(",");
11680
+ const sku = at(r, "id", "sku");
11681
+ const price = minor(at(r, "price"));
11682
+ if (!sku || price === null) return [];
11683
+ const image = at(r, "image_link", "image_url", "image");
11684
+ return [
11685
+ {
11686
+ sku,
11687
+ name: at(r, "title", "name") ?? sku,
11688
+ priceMinor: price,
11689
+ currency,
11690
+ imageUrls: image && /^https:\/\//.test(image) ? [image] : [],
11691
+ inStock: (at(r, "availability", "in_stock") ?? "in stock") !== "out of stock"
11692
+ }
11693
+ ];
11694
+ });
11695
+ }
11696
+ function serveAgentCatalog(opts) {
11697
+ return async (req, res) => {
11698
+ const ctx = pricingContextFrom({
11699
+ get: (name) => {
11700
+ const v = req.headers[name.toLowerCase()];
11701
+ return Array.isArray(v) ? v[0] ?? null : v ?? null;
11702
+ }
11703
+ });
11704
+ let products;
11705
+ try {
11706
+ products = await opts.products();
11707
+ } catch {
11708
+ res.status(503).json({ error: "catalog_unavailable" });
11709
+ return;
11710
+ }
11711
+ const rules = opts.commerce.rules();
11712
+ res.setHeader?.("Cache-Control", ctx.verified ? "private, no-store" : "public, max-age=60");
11713
+ res.json({
11714
+ products: products.filter((p) => opts.commerce.visible(p.sku, ctx)).map((p) => {
11715
+ const priced = opts.commerce.price(p.sku, p.priceMinor, ctx);
11716
+ const image = rules?.products?.[p.sku]?.imageUrl ?? p.imageUrls?.[0];
11717
+ return {
11718
+ sku: p.sku,
11719
+ name: p.name,
11720
+ ...p.description && { description: p.description },
11721
+ price: priced.priceMinor / 100,
11722
+ currency: p.currency,
11723
+ ...image && { image_url: image },
11724
+ in_stock: p.inStock !== false,
11725
+ ...p.url && { url: p.url },
11726
+ ...priced.issuerProgram && {
11727
+ issuer_offer: {
11728
+ program: priced.issuerProgram,
11729
+ regular_price: (priced.regularMinor ?? p.priceMinor) / 100
11730
+ }
11731
+ }
11732
+ };
11733
+ })
11734
+ });
11735
+ };
11736
+ }
11737
+
11364
11738
  // src/ucp/checkout-kit.ts
11365
11739
  import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
11366
11740
  import { CompactSign as CompactSign2 } from "jose";
@@ -12102,6 +12476,19 @@ var resumeResult = (outcome, session) => ({
12102
12476
  });
12103
12477
 
12104
12478
  // src/ucp/checkout-kit.ts
12479
+ function withValidImageUrl(item) {
12480
+ if (item.image_url === void 0) return item;
12481
+ let ok = false;
12482
+ try {
12483
+ const u = new URL(item.image_url);
12484
+ ok = u.protocol === "https:" || u.protocol === "http:";
12485
+ } catch {
12486
+ ok = false;
12487
+ }
12488
+ if (ok) return item;
12489
+ const { image_url: _drop, ...rest } = item;
12490
+ return rest;
12491
+ }
12105
12492
  var MemoryCheckoutStore = class {
12106
12493
  constructor() {
12107
12494
  this.m = /* @__PURE__ */ new Map();
@@ -12171,6 +12558,7 @@ var clone = (v) => JSON.parse(JSON.stringify(v));
12171
12558
  var inFlight = (st) => st === "complete_in_progress" || st === "finishing";
12172
12559
  function createUcpCheckoutKit(opts) {
12173
12560
  const profile = buildUcpProfile(opts.profile);
12561
+ opts.agentCommerce?.report({ checkoutUrl: opts.profile.endpoint, protocols: ["ucp"] });
12174
12562
  const store = opts.store ?? new MemoryCheckoutStore();
12175
12563
  const idem = {
12176
12564
  store: opts.idempotencyStore ?? new MemoryIdempotencyStore(),
@@ -12344,9 +12732,7 @@ function createUcpCheckoutKit(opts) {
12344
12732
  delete s._approvalId;
12345
12733
  if (settled.status === "failed") {
12346
12734
  s.status = "ready_for_complete";
12347
- s.messages = [
12348
- ucpError("payment_failed", "The payment did not go through.", "recoverable")
12349
- ];
12735
+ s.messages = [ucpError("payment_failed", "The payment did not go through.", "recoverable")];
12350
12736
  await save(s);
12351
12737
  return;
12352
12738
  }
@@ -12380,12 +12766,14 @@ function createUcpCheckoutKit(opts) {
12380
12766
  const b = checkUcpAgentBinding(req.headers.get("ucp-agent"), identities);
12381
12767
  return b.ok ? null : b;
12382
12768
  }
12383
- async function priceLines(lines) {
12769
+ async function priceLines(lines, pricing = { verified: false }) {
12384
12770
  const out = [];
12385
12771
  const messages = [];
12772
+ let issuerProgram;
12386
12773
  for (const [i, l] of lines.entries()) {
12387
12774
  const q = Math.max(1, Math.floor(l.quantity ?? 1));
12388
- const item = l.item?.id ? await opts.catalog(l.item.id, q) : null;
12775
+ const found = l.item?.id ? await opts.catalog(l.item.id, q) : null;
12776
+ const item = found && withValidImageUrl(found);
12389
12777
  if (!item) {
12390
12778
  messages.push(
12391
12779
  ucpError(
@@ -12397,6 +12785,22 @@ function createUcpCheckoutKit(opts) {
12397
12785
  );
12398
12786
  continue;
12399
12787
  }
12788
+ if (opts.agentCommerce) {
12789
+ if (!opts.agentCommerce.visible(item.id, pricing)) {
12790
+ messages.push(
12791
+ ucpError(
12792
+ "item_unavailable",
12793
+ `Item ${item.id} is unavailable.`,
12794
+ "recoverable",
12795
+ `$.line_items[${i}]`
12796
+ )
12797
+ );
12798
+ continue;
12799
+ }
12800
+ const priced = opts.agentCommerce.price(item.id, item.price, pricing);
12801
+ item.price = priced.priceMinor;
12802
+ if (priced.issuerProgram) issuerProgram = priced.issuerProgram;
12803
+ }
12400
12804
  const amount = item.price * q;
12401
12805
  out.push({
12402
12806
  id: `li_${i + 1}`,
@@ -12412,6 +12816,7 @@ function createUcpCheckoutKit(opts) {
12412
12816
  return {
12413
12817
  lines: out,
12414
12818
  messages,
12819
+ issuerProgram,
12415
12820
  totals: [
12416
12821
  { type: "subtotal", amount: sum },
12417
12822
  { type: "total", amount: sum }
@@ -12474,7 +12879,7 @@ function createUcpCheckoutKit(opts) {
12474
12879
  { method: req.method, url: req.url, headers, body: body || void 0 },
12475
12880
  async (fresh) => {
12476
12881
  const pp = fresh ? await fetchProfile(profileUrl, true) : plat;
12477
- return "keys" in pp && pp.keys ? pp.keys : [];
12882
+ return "ucp" in pp ? ucpProfileKeys(pp) : [];
12478
12883
  }
12479
12884
  );
12480
12885
  if (!sig.ok) return { error: json2(sig.status, { code: sig.code, content: sig.content }) };
@@ -12581,7 +12986,11 @@ function createUcpCheckoutKit(opts) {
12581
12986
  ...checkoutJwt && { checkoutJwt },
12582
12987
  valueMajor: total(s) / 100,
12583
12988
  agentArtifacts,
12584
- metadata: { kitSessionId: s.id, protocol: "ucp" }
12989
+ metadata: {
12990
+ kitSessionId: s.id,
12991
+ protocol: "ucp",
12992
+ ...s._issuerProgram && { issuerProgram: s._issuerProgram }
12993
+ }
12585
12994
  });
12586
12995
  const binding2 = await bindAgent(req, result);
12587
12996
  if (binding2) {
@@ -12720,7 +13129,11 @@ function createUcpCheckoutKit(opts) {
12720
13129
  attemptId: s._attemptId,
12721
13130
  ...checkoutJwt && { checkoutJwt },
12722
13131
  valueMajor: total(s) / 100,
12723
- metadata: { kitSessionId: s.id, protocol: "ucp" },
13132
+ metadata: {
13133
+ kitSessionId: s.id,
13134
+ protocol: "ucp",
13135
+ ...s._issuerProgram && { issuerProgram: s._issuerProgram }
13136
+ },
12724
13137
  resumed: { ...s._approvalId && { approvalId: s._approvalId } }
12725
13138
  });
12726
13139
  if (isHeld(result)) {
@@ -12770,7 +13183,7 @@ function createUcpCheckoutKit(opts) {
12770
13183
  ucpError(
12771
13184
  `approval_${reason}`,
12772
13185
  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"
13186
+ "recoverable"
12774
13187
  )
12775
13188
  ];
12776
13189
  await store.put(s);
@@ -12805,7 +13218,7 @@ function createUcpCheckoutKit(opts) {
12805
13218
  });
12806
13219
  if (status === "approved" || status === "consumed") {
12807
13220
  out.push({ sessionId: s.id, outcome: (await resumeApproved(s.id)).outcome });
12808
- } else if (status === "denied" || status === "expired") {
13221
+ } else if (status === "denied" || status === "expired" || status === "lapsed") {
12809
13222
  out.push({ sessionId: s.id, outcome: (await resumeDeclined(s.id, status)).outcome });
12810
13223
  } else {
12811
13224
  out.push({ sessionId: s.id, outcome: "pending" });
@@ -12835,7 +13248,10 @@ function createUcpCheckoutKit(opts) {
12835
13248
  const caller = await resolveCallerScope(req, reqCtx, opts.resolveCallerId) ?? fallbackScope("ucp-profile", ctx.profileUrl);
12836
13249
  if (req.method === "POST" && path === "/checkout-sessions") {
12837
13250
  return withIdempotency2(req, body, path, caller, async () => {
12838
- const priced = await priceLines(parsed.line_items ?? []);
13251
+ const priced = await priceLines(
13252
+ parsed.line_items ?? [],
13253
+ pricingContextFrom(req.headers)
13254
+ );
12839
13255
  const webhookUrl = ctx.plat.ucp.capabilities?.[UCP_ORDER_CAPABILITY]?.[0]?.config?.webhook_url;
12840
13256
  const s2 = {
12841
13257
  id: `chk_${randomUUID2().replace(/-/g, "")}`,
@@ -12848,11 +13264,12 @@ function createUcpCheckoutKit(opts) {
12848
13264
  _caller: caller,
12849
13265
  _platformProfile: ctx.profileUrl,
12850
13266
  _ap2: ctx.ap2,
12851
- _platformKeys: ctx.plat.keys ?? [],
13267
+ _platformKeys: ucpProfileKeys(ctx.plat),
12852
13268
  ...typeof webhookUrl === "string" && /^https:\/\//.test(webhookUrl) && { _webhookUrl: webhookUrl },
12853
13269
  ...req.headers.get("x-astra-checkout-session") && {
12854
13270
  _astraCheckoutSessionId: req.headers.get("x-astra-checkout-session").slice(0, 64)
12855
- }
13271
+ },
13272
+ ...priced.issuerProgram && { _issuerProgram: priced.issuerProgram }
12856
13273
  };
12857
13274
  if (!priced.lines.length) {
12858
13275
  return json2(
@@ -12897,8 +13314,13 @@ function createUcpCheckoutKit(opts) {
12897
13314
  if (inFlight(s.status)) return busy(s);
12898
13315
  if (s.status === "requires_escalation") return heldNoEdit(s);
12899
13316
  if (parsed.line_items) {
12900
- const priced = await priceLines(parsed.line_items);
13317
+ const priced = await priceLines(
13318
+ parsed.line_items,
13319
+ pricingContextFrom(req.headers)
13320
+ );
12901
13321
  s.line_items = priced.lines;
13322
+ if (priced.issuerProgram) s._issuerProgram = priced.issuerProgram;
13323
+ else delete s._issuerProgram;
12902
13324
  s.totals = priced.totals;
12903
13325
  s.messages = priced.messages;
12904
13326
  s.status = priced.lines.length && !priced.messages.length ? "ready_for_complete" : "incomplete";
@@ -13540,7 +13962,12 @@ var ACP_CODES = /* @__PURE__ */ new Set([
13540
13962
  "out_of_stock",
13541
13963
  "payment_declined",
13542
13964
  "requires_sign_in",
13543
- "requires_3ds"
13965
+ "requires_3ds",
13966
+ // 5.20.0: the owner-approval codes the kit itself emits.
13967
+ "approval_required",
13968
+ "approval_denied",
13969
+ "approval_expired",
13970
+ "approval_lapsed"
13544
13971
  ]);
13545
13972
  function heldBody2(text) {
13546
13973
  try {
@@ -13550,6 +13977,7 @@ function heldBody2(text) {
13550
13977
  }
13551
13978
  }
13552
13979
  function createAcpCheckoutKit(opts) {
13980
+ opts.agentCommerce?.report({ protocols: ["acp"] });
13553
13981
  const mem = /* @__PURE__ */ new Map();
13554
13982
  const store = opts.store ?? {
13555
13983
  get: async (id) => mem.get(id) ?? null,
@@ -13602,12 +14030,14 @@ function createAcpCheckoutKit(opts) {
13602
14030
  ...s.order && { order: s.order }
13603
14031
  };
13604
14032
  }
13605
- async function price(items) {
14033
+ async function price(items, pricing = { verified: false }) {
13606
14034
  const lines = [];
13607
14035
  const messages = [];
14036
+ let issuerProgram;
13608
14037
  for (const [i, it] of items.entries()) {
13609
14038
  const q = Math.max(1, Math.floor(it.quantity ?? 1));
13610
- const c = it.id ? await opts.catalog(it.id, q) : null;
14039
+ const found = it.id ? await opts.catalog(it.id, q) : null;
14040
+ const c = found && withValidImageUrl(found);
13611
14041
  if (!c) {
13612
14042
  messages.push({
13613
14043
  type: "error",
@@ -13618,10 +14048,31 @@ function createAcpCheckoutKit(opts) {
13618
14048
  });
13619
14049
  continue;
13620
14050
  }
14051
+ if (opts.agentCommerce) {
14052
+ if (!opts.agentCommerce.visible(c.id, pricing)) {
14053
+ messages.push({
14054
+ type: "error",
14055
+ code: "not_found",
14056
+ content: `Item ${c.id} not found.`,
14057
+ resolution: "recoverable",
14058
+ param: `$.line_items[${i}]`
14059
+ });
14060
+ continue;
14061
+ }
14062
+ const priced = opts.agentCommerce.price(c.id, c.price, pricing);
14063
+ c.price = priced.priceMinor;
14064
+ if (priced.issuerProgram) issuerProgram = priced.issuerProgram;
14065
+ }
13621
14066
  const amount = c.price * q;
13622
14067
  lines.push({
13623
14068
  id: `li_${i + 1}`,
13624
- item: { id: c.id, name: c.title, unit_amount: c.price },
14069
+ item: {
14070
+ id: c.id,
14071
+ name: c.title,
14072
+ unit_amount: c.price,
14073
+ ...c.image_url && { image_url: c.image_url },
14074
+ ...typeof c.description === "string" && { description: c.description }
14075
+ },
13625
14076
  quantity: q,
13626
14077
  name: c.title,
13627
14078
  unit_amount: c.price,
@@ -13635,6 +14086,7 @@ function createAcpCheckoutKit(opts) {
13635
14086
  return {
13636
14087
  lines,
13637
14088
  messages,
14089
+ issuerProgram,
13638
14090
  totals: [
13639
14091
  { type: "subtotal", amount: sum },
13640
14092
  { type: "total", amount: sum }
@@ -13786,7 +14238,11 @@ function createAcpCheckoutKit(opts) {
13786
14238
  attemptId,
13787
14239
  valueMajor: total2(s) / 100,
13788
14240
  agentArtifacts,
13789
- metadata: { kitSessionId: s.id, protocol: "acp" }
14241
+ metadata: {
14242
+ kitSessionId: s.id,
14243
+ protocol: "acp",
14244
+ ...s._issuerProgram && { issuerProgram: s._issuerProgram }
14245
+ }
13790
14246
  });
13791
14247
  if (isHeld(result)) {
13792
14248
  s.status = "pending_approval";
@@ -13923,7 +14379,11 @@ function createAcpCheckoutKit(opts) {
13923
14379
  astraId: s._astraId,
13924
14380
  attemptId: s._attemptId,
13925
14381
  valueMajor: total2(s) / 100,
13926
- metadata: { kitSessionId: s.id, protocol: "acp" },
14382
+ metadata: {
14383
+ kitSessionId: s.id,
14384
+ protocol: "acp",
14385
+ ...s._issuerProgram && { issuerProgram: s._issuerProgram }
14386
+ },
13927
14387
  resumed: { ...s._approvalId && { approvalId: s._approvalId } }
13928
14388
  });
13929
14389
  if (isHeld(result)) {
@@ -13968,7 +14428,7 @@ function createAcpCheckoutKit(opts) {
13968
14428
  code: `approval_${reason}`,
13969
14429
  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
14430
  severity: "medium",
13971
- resolution: "requires_buyer_review"
14431
+ resolution: "recoverable"
13972
14432
  }
13973
14433
  ];
13974
14434
  await store.put(s);
@@ -14003,7 +14463,7 @@ function createAcpCheckoutKit(opts) {
14003
14463
  });
14004
14464
  if (status === "approved" || status === "consumed") {
14005
14465
  out.push({ sessionId: s.id, outcome: (await resumeApproved(s.id)).outcome });
14006
- } else if (status === "denied" || status === "expired") {
14466
+ } else if (status === "denied" || status === "expired" || status === "lapsed") {
14007
14467
  out.push({ sessionId: s.id, outcome: (await resumeDeclined(s.id, status)).outcome });
14008
14468
  } else {
14009
14469
  out.push({ sessionId: s.id, outcome: "pending" });
@@ -14041,7 +14501,7 @@ function createAcpCheckoutKit(opts) {
14041
14501
  const items = parsed.line_items ?? parsed.items;
14042
14502
  if (!items?.length)
14043
14503
  return apiError(400, "missing", "line_items is required.", "$.line_items");
14044
- const p = await price(items);
14504
+ const p = await price(items, pricingContextFrom(req.headers));
14045
14505
  const s2 = {
14046
14506
  id: `cs_${randomUUID3().replace(/-/g, "")}`,
14047
14507
  status: p.lines.length && !p.messages.length ? "ready_for_payment" : "not_ready_for_payment",
@@ -14052,7 +14512,8 @@ function createAcpCheckoutKit(opts) {
14052
14512
  _caller: caller,
14053
14513
  ...req.headers.get("x-astra-checkout-session") && {
14054
14514
  _astraCheckoutSessionId: req.headers.get("x-astra-checkout-session").slice(0, 64)
14055
- }
14515
+ },
14516
+ ...p.issuerProgram && { _issuerProgram: p.issuerProgram }
14056
14517
  };
14057
14518
  if (s2.status === "ready_for_payment") {
14058
14519
  const v = await validate(s2);
@@ -14081,7 +14542,9 @@ function createAcpCheckoutKit(opts) {
14081
14542
  );
14082
14543
  const items = parsed.line_items ?? parsed.items;
14083
14544
  if (items) {
14084
- const p = await price(items);
14545
+ const p = await price(items, pricingContextFrom(req.headers));
14546
+ if (p.issuerProgram) s._issuerProgram = p.issuerProgram;
14547
+ else delete s._issuerProgram;
14085
14548
  Object.assign(s, {
14086
14549
  line_items: p.lines,
14087
14550
  totals: p.totals,
@@ -14231,6 +14694,7 @@ export {
14231
14694
  DECLARED_SOURCE_MAX,
14232
14695
  DEFAULT_EDGE_CONFIG,
14233
14696
  DEFAULT_IDEMPOTENCY_TTL_MS,
14697
+ ISSUER_PROGRAM_HEADER,
14234
14698
  KYDRequiredError,
14235
14699
  MAX_HEADERS,
14236
14700
  MAX_HEADER_VALUE_BYTES,
@@ -14289,6 +14753,7 @@ export {
14289
14753
  closedPaymentMandate,
14290
14754
  contentDigest,
14291
14755
  createAcpCheckoutKit,
14756
+ createAgentCommerce,
14292
14757
  createKbHop,
14293
14758
  createMcpMiddleware,
14294
14759
  createUcpCheckoutKit,
@@ -14310,6 +14775,7 @@ export {
14310
14775
  fetchRoutes,
14311
14776
  fetchUcpProfile,
14312
14777
  finishIdempotent,
14778
+ fromFeed,
14313
14779
  getCachedWellKnownUrls,
14314
14780
  getEdgeConfig,
14315
14781
  getTrustLevel,
@@ -14320,10 +14786,12 @@ export {
14320
14786
  isEdgeConfig,
14321
14787
  isRejectedIdentityClaim,
14322
14788
  isRetryableSettlementError,
14789
+ isSharedVerdict,
14323
14790
  isSpecialUseIp,
14324
14791
  isVerificationUnavailable,
14325
14792
  isVerifiedHopValidFor,
14326
14793
  issueDelegateSdJwt,
14794
+ issuerOfferPrice,
14327
14795
  matchEdgePathRule,
14328
14796
  matchPlatformSignature,
14329
14797
  matchRoutePattern,
@@ -14331,8 +14799,10 @@ export {
14331
14799
  negotiateUcp,
14332
14800
  nextjs_exports as nextjs,
14333
14801
  normalizeDeclaredSource,
14802
+ onVerificationResult,
14334
14803
  openCheckoutMandate,
14335
14804
  openPaymentMandate,
14805
+ parseFeed,
14336
14806
  parseYaml as parseLocalPolicyYaml,
14337
14807
  parseMppCredential,
14338
14808
  parseUcpAgentHeader,
@@ -14341,6 +14811,7 @@ export {
14341
14811
  parseX402Payment,
14342
14812
  pollStepUpStatus,
14343
14813
  prefetchWellKnown,
14814
+ priceForAgent,
14344
14815
  quickVerify,
14345
14816
  recordDecision2 as recordDecision,
14346
14817
  reportAttempt,
@@ -14354,6 +14825,7 @@ export {
14354
14825
  sdkFetch,
14355
14826
  sendUcpWebhook,
14356
14827
  serializeVerifiedHop,
14828
+ serveAgentCatalog,
14357
14829
  setMcpMeta,
14358
14830
  signHttpRequest,
14359
14831
  signRootMandate,
@@ -14364,6 +14836,7 @@ export {
14364
14836
  ucpEscalation,
14365
14837
  ucpFailure,
14366
14838
  ucpProfileHandler,
14839
+ ucpProfileKeys,
14367
14840
  ucpRequiredComponents,
14368
14841
  unavailableRetryAfterSeconds,
14369
14842
  validatePolicy as validateLocalPolicy,