@astrasyncai/verification-gateway 3.12.0 → 4.1.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 (134) hide show
  1. package/README.md +106 -29
  2. package/dist/adapter-interface/interface.d.mts +2 -2
  3. package/dist/adapter-interface/interface.d.ts +2 -2
  4. package/dist/adapters/express.d.mts +2 -2
  5. package/dist/adapters/express.d.ts +2 -2
  6. package/dist/adapters/express.js +482 -22
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +482 -22
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/http-pdlss.d.mts +3 -3
  11. package/dist/adapters/http-pdlss.d.ts +3 -3
  12. package/dist/adapters/http-pdlss.js.map +1 -1
  13. package/dist/adapters/http-pdlss.mjs.map +1 -1
  14. package/dist/adapters/mcp.d.mts +56 -36
  15. package/dist/adapters/mcp.d.ts +56 -36
  16. package/dist/adapters/mcp.js +309 -10
  17. package/dist/adapters/mcp.js.map +1 -1
  18. package/dist/adapters/mcp.mjs +309 -10
  19. package/dist/adapters/mcp.mjs.map +1 -1
  20. package/dist/adapters/nextjs.d.mts +2 -2
  21. package/dist/adapters/nextjs.d.ts +2 -2
  22. package/dist/adapters/nextjs.js +518 -25
  23. package/dist/adapters/nextjs.js.map +1 -1
  24. package/dist/adapters/nextjs.mjs +518 -25
  25. package/dist/adapters/nextjs.mjs.map +1 -1
  26. package/dist/adapters/sdk.d.mts +2 -2
  27. package/dist/adapters/sdk.d.ts +2 -2
  28. package/dist/adapters/sdk.js +72 -9
  29. package/dist/adapters/sdk.js.map +1 -1
  30. package/dist/adapters/sdk.mjs +72 -9
  31. package/dist/adapters/sdk.mjs.map +1 -1
  32. package/dist/agent/index.d.mts +2 -2
  33. package/dist/agent/index.d.ts +2 -2
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/index.mjs.map +1 -1
  36. package/dist/bin/astrasync-claude-hook.js +83 -20
  37. package/dist/bin/astrasync-codex-hook.js +83 -20
  38. package/dist/bin/astrasync-guard.js +83 -20
  39. package/dist/bin/astrasync.js +150 -33
  40. package/dist/browser/background.js +83 -20
  41. package/dist/browser/background.js.map +1 -1
  42. package/dist/browser/background.mjs +83 -20
  43. package/dist/browser/background.mjs.map +1 -1
  44. package/dist/browser/browser-adapter.d.mts +2 -2
  45. package/dist/browser/browser-adapter.d.ts +2 -2
  46. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  47. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  48. package/dist/cli/index.d.mts +2 -2
  49. package/dist/cli/index.d.ts +2 -2
  50. package/dist/codex/index.d.mts +3 -3
  51. package/dist/codex/index.d.ts +3 -3
  52. package/dist/codex/index.js +83 -20
  53. package/dist/codex/index.js.map +1 -1
  54. package/dist/codex/index.mjs +83 -20
  55. package/dist/codex/index.mjs.map +1 -1
  56. package/dist/cursor/cursor-adapter.d.mts +2 -2
  57. package/dist/cursor/cursor-adapter.d.ts +2 -2
  58. package/dist/cursor/extension.d.mts +2 -2
  59. package/dist/cursor/extension.d.ts +2 -2
  60. package/dist/cursor/extension.js +83 -20
  61. package/dist/cursor/extension.js.map +1 -1
  62. package/dist/cursor/extension.mjs +83 -20
  63. package/dist/cursor/extension.mjs.map +1 -1
  64. package/dist/edge-config.d.mts +22 -7
  65. package/dist/edge-config.d.ts +22 -7
  66. package/dist/edge-config.js.map +1 -1
  67. package/dist/edge-config.mjs.map +1 -1
  68. package/dist/edge-core/index.d.mts +425 -0
  69. package/dist/edge-core/index.d.ts +425 -0
  70. package/dist/edge-core/index.js +1482 -0
  71. package/dist/edge-core/index.js.map +1 -0
  72. package/dist/edge-core/index.mjs +1437 -0
  73. package/dist/edge-core/index.mjs.map +1 -0
  74. package/dist/{express-B39o89gf.d.mts → express-BVd1_3FE.d.ts} +9 -7
  75. package/dist/{express-BuxKNO_q.d.ts → express-D_4hTn5Z.d.mts} +9 -7
  76. package/dist/gateway/gateway.d.mts +2 -2
  77. package/dist/gateway/gateway.d.ts +2 -2
  78. package/dist/gateway/gateway.js +83 -20
  79. package/dist/gateway/gateway.js.map +1 -1
  80. package/dist/gateway/gateway.mjs +83 -20
  81. package/dist/gateway/gateway.mjs.map +1 -1
  82. package/dist/git-trigger/git-hooks.d.mts +2 -2
  83. package/dist/git-trigger/git-hooks.d.ts +2 -2
  84. package/dist/{index-CLHdqs2M.d.ts → index-B0YHu_SP.d.ts} +30 -27
  85. package/dist/{index-DME3w3lf.d.mts → index-BPEBlOsE.d.mts} +30 -27
  86. package/dist/{index-CqydB4ks.d.mts → index-DQb5-_1X.d.mts} +1 -1
  87. package/dist/{index-BG62SXso.d.ts → index-T1aBoUcc.d.ts} +1 -1
  88. package/dist/index.d.mts +11 -11
  89. package/dist/index.d.ts +11 -11
  90. package/dist/index.js +595 -272
  91. package/dist/index.js.map +1 -1
  92. package/dist/index.mjs +592 -272
  93. package/dist/index.mjs.map +1 -1
  94. package/dist/local-evaluator/evaluator.d.mts +2 -2
  95. package/dist/local-evaluator/evaluator.d.ts +2 -2
  96. package/dist/metadata-capture.d.mts +63 -4
  97. package/dist/metadata-capture.d.ts +63 -4
  98. package/dist/metadata-capture.js +67 -0
  99. package/dist/metadata-capture.js.map +1 -1
  100. package/dist/metadata-capture.mjs +64 -0
  101. package/dist/metadata-capture.mjs.map +1 -1
  102. package/dist/{nextjs-D74W7wt-.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
  103. package/dist/{nextjs-Ddlw_J1J.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
  104. package/dist/platform-signatures.d.mts +5 -6
  105. package/dist/platform-signatures.d.ts +5 -6
  106. package/dist/platform-signatures.js.map +1 -1
  107. package/dist/platform-signatures.mjs.map +1 -1
  108. package/dist/registration/index.d.mts +10 -11
  109. package/dist/registration/index.d.ts +10 -11
  110. package/dist/registration/index.js.map +1 -1
  111. package/dist/registration/index.mjs.map +1 -1
  112. package/dist/{sdk-CT7gdkfR.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
  113. package/dist/{sdk-Bn6bj7kt.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
  114. package/dist/transport/index.d.mts +2 -2
  115. package/dist/transport/index.d.ts +2 -2
  116. package/dist/transport/index.js.map +1 -1
  117. package/dist/transport/index.mjs.map +1 -1
  118. package/dist/{types-DHu7m9HI.d.ts → types-BLUx92FJ.d.ts} +1 -1
  119. package/dist/{types-Djw4GLtz.d.mts → types-DqfPU5Bl.d.mts} +1 -1
  120. package/dist/{types-BntuzEEn.d.mts → types-r850cOt0.d.mts} +121 -88
  121. package/dist/{types-SDu4Llbe.d.ts → types-sMxBT-nM.d.ts} +121 -88
  122. package/dist/ui/index.d.mts +11 -7
  123. package/dist/ui/index.d.ts +11 -7
  124. package/dist/ui/index.js +13 -9
  125. package/dist/ui/index.js.map +1 -1
  126. package/dist/ui/index.mjs +10 -8
  127. package/dist/ui/index.mjs.map +1 -1
  128. package/dist/verify.d.mts +11 -8
  129. package/dist/verify.d.ts +11 -8
  130. package/dist/verify.js +76 -8
  131. package/dist/verify.js.map +1 -1
  132. package/dist/verify.mjs +75 -8
  133. package/dist/verify.mjs.map +1 -1
  134. package/package.json +8 -2
package/dist/index.mjs CHANGED
@@ -126,7 +126,7 @@ function getCapabilities(accessLevel) {
126
126
  }
127
127
 
128
128
  // src/version.ts
129
- var SDK_VERSION = "3.12.0";
129
+ var SDK_VERSION = "4.1.0";
130
130
 
131
131
  // src/well-known.ts
132
132
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -204,7 +204,7 @@ var DEFAULT_CONFIG = {
204
204
  };
205
205
  var initCheckPerformed = false;
206
206
  var deprecationWarningShown = false;
207
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
207
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
208
208
  initCheckPerformed = true;
209
209
  try {
210
210
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -221,6 +221,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
221
221
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
222
222
  );
223
223
  }
224
+ if (apiKey) {
225
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
226
+ headers: { Authorization: `Bearer ${apiKey}` }
227
+ });
228
+ if (selfCheck.status === 401) {
229
+ const body = await selfCheck.json().catch(() => ({}));
230
+ const message = `[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${body.code ?? "api_key_invalid"}: ${body.message ?? "invalid key"}). Every inbound agent will be turned away until the key is rotated` + (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : "") + `.`;
231
+ if (strictInit) {
232
+ throw new Error(`${message} (strictInit=true)`);
233
+ }
234
+ console.error(message);
235
+ } else if (selfCheck.ok) {
236
+ const body = await selfCheck.json().catch(() => ({}));
237
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
238
+ console.warn(
239
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
240
+ );
241
+ } else if (debug) {
242
+ console.log("[VerificationGateway] API key self-check passed");
243
+ }
244
+ }
245
+ }
224
246
  } catch (err) {
225
247
  if (strictInit) {
226
248
  throw err;
@@ -338,8 +360,17 @@ function hasCredentials(credentials) {
338
360
  function createGuidanceResponse(_config, reason, options = {}) {
339
361
  const source = options.source ?? "no_credentials";
340
362
  const isApiError = source === "api_error";
363
+ const isMisconfig = source === "merchant_misconfig";
341
364
  const urls = options.urls;
342
- const guidance = isApiError ? {
365
+ const guidance = isMisconfig ? {
366
+ message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
367
+ registrationUrl: urls?.registrationUrl ?? "",
368
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
369
+ steps: [
370
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
371
+ "Do not retry: the failure is deterministic until the operator updates their key"
372
+ ]
373
+ } : isApiError ? {
343
374
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
344
375
  registrationUrl: urls?.registrationUrl ?? "",
345
376
  documentationUrl: urls?.documentationUrl ?? "",
@@ -377,13 +408,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
377
408
  // partners (and their custom onDenied handlers) can branch on
378
409
  // dimension. Without this, the synthesised stub was indistinguishable
379
410
  // from a real policy deny.
380
- failures: isApiError ? [
411
+ failures: isMisconfig ? [
412
+ {
413
+ dimension: "verify_access.merchant_misconfigured",
414
+ message: reason ?? "The integration API key was rejected by AstraSync",
415
+ guidance: guidance.message
416
+ }
417
+ ] : isApiError ? [
381
418
  {
382
419
  dimension: "verify_access.api_error",
383
420
  message: reason ?? "Verification temporarily unavailable",
384
421
  guidance: guidance.message
385
422
  }
386
423
  ] : void 0,
424
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
425
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
426
+ ...isMisconfig && { misconfigured: true },
387
427
  correlationId: options.correlationId,
388
428
  attemptId: options.attemptId,
389
429
  verifiedAt: /* @__PURE__ */ new Date()
@@ -438,7 +478,8 @@ async function callVerifyAccessAPI(config, request) {
438
478
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
439
479
  method: "POST",
440
480
  headers,
441
- body: JSON.stringify(body)
481
+ body: JSON.stringify(body),
482
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
442
483
  });
443
484
  const data = await response.json();
444
485
  if (response.status === 410) {
@@ -462,6 +503,17 @@ async function callVerifyAccessAPI(config, request) {
462
503
  };
463
504
  }
464
505
  if (!response.ok) {
506
+ if (response.status === 401) {
507
+ return {
508
+ success: false,
509
+ error: data.message || data.error || "Integration API key rejected",
510
+ misconfig: true,
511
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
512
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
513
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
514
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
515
+ };
516
+ }
465
517
  return {
466
518
  success: false,
467
519
  error: data.message || data.error || `API returned ${response.status}`,
@@ -483,9 +535,19 @@ async function verify(config, request, options) {
483
535
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
484
536
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
485
537
  if (mergedConfig.strictInit) {
486
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
538
+ await performInitCheck(
539
+ mergedConfig.apiBaseUrl,
540
+ mergedConfig.debug,
541
+ true,
542
+ mergedConfig.apiKey
543
+ );
487
544
  } else {
488
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
545
+ void performInitCheck(
546
+ mergedConfig.apiBaseUrl,
547
+ mergedConfig.debug,
548
+ false,
549
+ mergedConfig.apiKey
550
+ );
489
551
  }
490
552
  }
491
553
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -524,9 +586,10 @@ async function verify(config, request, options) {
524
586
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
525
587
  if (!apiResponse.success) {
526
588
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
527
- source: "api_error",
589
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
528
590
  correlationId: apiResponse.correlationId,
529
591
  attemptId: apiResponse.attemptId,
592
+ remediation: apiResponse.remediation,
530
593
  urls
531
594
  });
532
595
  }
@@ -1061,6 +1124,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
1061
1124
  }
1062
1125
 
1063
1126
  // src/metadata-capture.ts
1127
+ var CAPTURE_SCHEMA_VERSION = 1;
1064
1128
  var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
1065
1129
  "cookie",
1066
1130
  "set-cookie",
@@ -1205,6 +1269,373 @@ function extractPlatformHeaders(sanitized) {
1205
1269
  }
1206
1270
  return out;
1207
1271
  }
1272
+ var CONNECTION_HEADER_SOURCES = [
1273
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
1274
+ [
1275
+ "country",
1276
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
1277
+ ],
1278
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
1279
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
1280
+ ["asn", ["cloudfront-viewer-asn"]],
1281
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
1282
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
1283
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
1284
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
1285
+ // connection-layer truth — normalising them here is the permanent design.
1286
+ ["tlsFingerprint", ["cf-ja3-hash"]],
1287
+ ["ja4", ["cf-ja4"]]
1288
+ ];
1289
+ function deriveConnectionFromHeaders(headers) {
1290
+ if (!headers) return void 0;
1291
+ const lower = {};
1292
+ for (const name of Object.keys(headers)) {
1293
+ const value = headers[name];
1294
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
1295
+ }
1296
+ const connection = {};
1297
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
1298
+ for (const source of sources) {
1299
+ const value = lower[source]?.trim();
1300
+ if (value) {
1301
+ connection[key] = value;
1302
+ break;
1303
+ }
1304
+ }
1305
+ }
1306
+ if (!connection.ip) {
1307
+ const xff = lower["x-forwarded-for"];
1308
+ const client = xff?.split(",")[0]?.trim();
1309
+ if (client) connection.ip = client;
1310
+ }
1311
+ return Object.keys(connection).length > 0 ? connection : void 0;
1312
+ }
1313
+ function buildSdkObservedMetadata(rawHeaders) {
1314
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1315
+ const flat = {};
1316
+ if (rawHeaders) {
1317
+ for (const name of Object.keys(rawHeaders)) {
1318
+ const value = rawHeaders[name];
1319
+ if (value === void 0) continue;
1320
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
1321
+ }
1322
+ }
1323
+ const connection = deriveConnectionFromHeaders(flat);
1324
+ return {
1325
+ headers,
1326
+ ...apiKeyFormat && { apiKeyFormat },
1327
+ ...platformHeaders && { platformHeaders },
1328
+ ...connection && { connection },
1329
+ schemaVersion: CAPTURE_SCHEMA_VERSION
1330
+ };
1331
+ }
1332
+
1333
+ // src/edge-config.ts
1334
+ var DEFAULT_EDGE_CONFIG = Object.freeze({
1335
+ version: 1,
1336
+ mode: "observe",
1337
+ depth: "classify",
1338
+ pathRules: [],
1339
+ sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
1340
+ failurePosture: "open"
1341
+ });
1342
+ var EDGE_MODES = ["observe", "enforce"];
1343
+ var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
1344
+ function isEdgeConfig(value) {
1345
+ if (typeof value !== "object" || value === null) return false;
1346
+ const v = value;
1347
+ if (v.version !== 1) return false;
1348
+ if (!EDGE_MODES.includes(v.mode)) return false;
1349
+ if (!EDGE_DEPTHS.includes(v.depth)) return false;
1350
+ if (!Array.isArray(v.pathRules)) return false;
1351
+ for (const rule of v.pathRules) {
1352
+ if (typeof rule !== "object" || rule === null) return false;
1353
+ const r = rule;
1354
+ if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
1355
+ if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
1356
+ if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
1357
+ }
1358
+ const sampling = v.sampling;
1359
+ if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
1360
+ return false;
1361
+ }
1362
+ return v.failurePosture === "open";
1363
+ }
1364
+ async function fetchEdgeConfig(config, counterpartyId, opts) {
1365
+ if (!counterpartyId) return null;
1366
+ const headers = { Accept: "application/json" };
1367
+ if (config.apiKey) {
1368
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
1369
+ headers["X-API-Key"] = config.apiKey;
1370
+ }
1371
+ if (opts?.etag) headers["If-None-Match"] = opts.etag;
1372
+ try {
1373
+ const response = await fetch(
1374
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1375
+ { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1376
+ );
1377
+ if (response.status === 304) return { notModified: true };
1378
+ if (!response.ok) return null;
1379
+ const body = await response.json();
1380
+ const edgeConfig = body.data?.edgeConfig;
1381
+ if (!isEdgeConfig(edgeConfig)) return null;
1382
+ return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
1383
+ } catch {
1384
+ return null;
1385
+ }
1386
+ }
1387
+ var DEFAULT_TTL_MS = 60 * 1e3;
1388
+ var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
1389
+ var cache2 = /* @__PURE__ */ new Map();
1390
+ var inflight2 = /* @__PURE__ */ new Map();
1391
+ function cacheKey(apiBaseUrl, counterpartyId) {
1392
+ return `${apiBaseUrl}|${counterpartyId}`;
1393
+ }
1394
+ function degradeToObserve(config) {
1395
+ if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
1396
+ return config;
1397
+ }
1398
+ return {
1399
+ ...config,
1400
+ mode: "observe",
1401
+ pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
1402
+ };
1403
+ }
1404
+ async function revalidate(config, counterpartyId) {
1405
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1406
+ const existing = inflight2.get(key);
1407
+ if (existing) return existing;
1408
+ const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
1409
+ if (result === null) return;
1410
+ const now = Date.now();
1411
+ if ("notModified" in result) {
1412
+ const entry = cache2.get(key);
1413
+ if (entry) entry.lastSuccessAt = now;
1414
+ return;
1415
+ }
1416
+ cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
1417
+ }).finally(() => {
1418
+ inflight2.delete(key);
1419
+ });
1420
+ inflight2.set(key, promise);
1421
+ return promise;
1422
+ }
1423
+ async function getEdgeConfig(config, counterpartyId, opts) {
1424
+ const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
1425
+ const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
1426
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1427
+ const entry = cache2.get(key);
1428
+ if (entry) {
1429
+ const age = Date.now() - entry.lastSuccessAt;
1430
+ if (age > ttlMs) {
1431
+ revalidate(config, counterpartyId).catch(() => {
1432
+ });
1433
+ }
1434
+ return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
1435
+ }
1436
+ await revalidate(config, counterpartyId).catch(() => {
1437
+ });
1438
+ return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
1439
+ }
1440
+ function matchEdgePathRule(rules, path) {
1441
+ for (const rule of rules) {
1442
+ if (globToRegExp(rule.pattern).test(path)) return rule;
1443
+ }
1444
+ return void 0;
1445
+ }
1446
+ function globToRegExp(pattern) {
1447
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
1448
+ return new RegExp(`^${escaped}$`, "i");
1449
+ }
1450
+ function _resetEdgeConfigCache() {
1451
+ cache2.clear();
1452
+ inflight2.clear();
1453
+ }
1454
+
1455
+ // src/platform-signatures.ts
1456
+ var PLATFORM_AGENT_SIGNATURES = [
1457
+ {
1458
+ vendor: "claude",
1459
+ displayName: "Anthropic Claude",
1460
+ uaPatterns: [/claude/i, /anthropic/i],
1461
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
1462
+ apiKeyFormatPatterns: [/^sk-ant/i],
1463
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
1464
+ },
1465
+ {
1466
+ vendor: "chatgpt",
1467
+ displayName: "OpenAI ChatGPT",
1468
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
1469
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
1470
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
1471
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
1472
+ },
1473
+ {
1474
+ vendor: "gemini",
1475
+ displayName: "Google Gemini",
1476
+ uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
1477
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
1478
+ apiKeyFormatPatterns: [/^aiza/i],
1479
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
1480
+ },
1481
+ {
1482
+ vendor: "cursor",
1483
+ displayName: "Cursor",
1484
+ uaPatterns: [/cursor/i],
1485
+ agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
1486
+ },
1487
+ {
1488
+ vendor: "goose",
1489
+ displayName: "Block Goose",
1490
+ uaPatterns: [/goose/i, /block-goose/i],
1491
+ agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
1492
+ },
1493
+ {
1494
+ vendor: "perplexity",
1495
+ displayName: "Perplexity",
1496
+ uaPatterns: [/perplexity/i],
1497
+ agentCardPatterns: [/perplexity\.ai/i],
1498
+ apiKeyFormatPatterns: [/^pplx-/i]
1499
+ }
1500
+ ];
1501
+ function matchPlatformSignature(signatures, input) {
1502
+ const ua = input.userAgent ?? "";
1503
+ const cardUrl = input.agentCardUrl ?? "";
1504
+ const apiKeyFormat = input.apiKeyFormat ?? "";
1505
+ const headerLines = [];
1506
+ if (input.platformHeaders) {
1507
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
1508
+ headerLines.push(name, `${name}: ${value}`);
1509
+ }
1510
+ }
1511
+ for (const sig of signatures) {
1512
+ const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
1513
+ const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1514
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1515
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1516
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
1517
+ const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1518
+ const cardHost = (() => {
1519
+ try {
1520
+ return cardUrl ? new URL(cardUrl).host : "";
1521
+ } catch {
1522
+ return "";
1523
+ }
1524
+ })();
1525
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1526
+ return {
1527
+ vendor: sig.vendor,
1528
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
1529
+ displayName: sig.displayName
1530
+ };
1531
+ }
1532
+ }
1533
+ return void 0;
1534
+ }
1535
+ function detectPlatformFingerprint(input) {
1536
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
1537
+ }
1538
+
1539
+ // src/edge-core/classify.ts
1540
+ var STATIC_EXTENSIONS = /\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i;
1541
+ var BOT_UA_PATTERN = /curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i;
1542
+ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1543
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1544
+
1545
+ // src/edge-core/events.ts
1546
+ function sampled(rate, random = Math.random) {
1547
+ if (rate <= 0) return false;
1548
+ if (rate >= 1) return true;
1549
+ return random() < rate;
1550
+ }
1551
+
1552
+ // src/adapters/anonymous-beacon.ts
1553
+ function readHeader(raw, name) {
1554
+ const value = raw[name];
1555
+ if (typeof value === "string") return value;
1556
+ if (Array.isArray(value)) return value[0];
1557
+ return void 0;
1558
+ }
1559
+ function classifyPassThroughTraffic(path, rawHeaders) {
1560
+ if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
1561
+ const credentials = extractCredentials(rawHeaders, {});
1562
+ if (credentials.astraId || credentials.apiKey || credentials.jwt) {
1563
+ return { kind: "never" };
1564
+ }
1565
+ const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
1566
+ const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1567
+ const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
1568
+ if (fingerprint) {
1569
+ return {
1570
+ kind: "platform",
1571
+ protocol: "platform-ua",
1572
+ evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
1573
+ };
1574
+ }
1575
+ if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
1576
+ return {
1577
+ kind: "anonymous-bot",
1578
+ protocol: "ua-heuristic",
1579
+ evidence: { userAgent: userAgent || "(none)" }
1580
+ };
1581
+ }
1582
+ if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
1583
+ return {
1584
+ kind: "anonymous-bot",
1585
+ protocol: "ua-heuristic",
1586
+ evidence: { userAgent, spoofedBrowser: true }
1587
+ };
1588
+ }
1589
+ return { kind: "never" };
1590
+ }
1591
+ function createAnonymousBeacon(config, options) {
1592
+ const counterpartyId = config.counterpartyId;
1593
+ if (!counterpartyId) return () => {
1594
+ };
1595
+ const random = options?.random ?? Math.random;
1596
+ let edgeConfig;
1597
+ let refreshing = false;
1598
+ const refreshConfig = () => {
1599
+ if (refreshing) return;
1600
+ refreshing = true;
1601
+ getEdgeConfig(config, counterpartyId).then((cfg) => {
1602
+ edgeConfig = cfg;
1603
+ }).catch(() => {
1604
+ }).finally(() => {
1605
+ refreshing = false;
1606
+ });
1607
+ };
1608
+ refreshConfig();
1609
+ return (input) => {
1610
+ try {
1611
+ refreshConfig();
1612
+ const cfg = edgeConfig;
1613
+ if (!cfg) return;
1614
+ const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
1615
+ if (traffic.kind === "never") return;
1616
+ if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
1617
+ return;
1618
+ }
1619
+ const userAgent = readHeader(input.rawHeaders, "user-agent");
1620
+ const xff = input.rawHeaders["x-forwarded-for"];
1621
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1622
+ const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
1623
+ reportUnregisteredAttempt(config, {
1624
+ ...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
1625
+ counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
1626
+ ...sourceIp && { sourceIp },
1627
+ ...userAgent && { userAgent },
1628
+ requestPath: input.requestPath,
1629
+ requestMethod: input.requestMethod,
1630
+ ...traffic.protocol && { protocol: traffic.protocol },
1631
+ ...traffic.evidence && { evidence: traffic.evidence },
1632
+ observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
1633
+ }).catch(() => {
1634
+ });
1635
+ } catch {
1636
+ }
1637
+ };
1638
+ }
1208
1639
 
1209
1640
  // src/adapters/express.ts
1210
1641
  function defaultExtractCredentials(req) {
@@ -1216,7 +1647,7 @@ function defaultExtractCredentials(req) {
1216
1647
  function extractAstraSyncCredentials(req) {
1217
1648
  return extractHttpCredentials(req.headers);
1218
1649
  }
1219
- function headerValue(value) {
1650
+ function headerValue2(value) {
1220
1651
  if (typeof value === "string") return value;
1221
1652
  if (Array.isArray(value)) return value[0];
1222
1653
  return void 0;
@@ -1224,9 +1655,9 @@ function headerValue(value) {
1224
1655
  function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
1225
1656
  return resolveHttpPdlss({
1226
1657
  method: req.method,
1227
- astraPurpose: headerValue(req.headers["x-astra-purpose"]),
1228
- astraAction: headerValue(req.headers["x-astra-action"]),
1229
- legacyPurpose: headerValue(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
1658
+ astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
1659
+ astraAction: headerValue2(req.headers["x-astra-action"]),
1660
+ legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
1230
1661
  queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
1231
1662
  routePurpose: routeConfig?.purpose,
1232
1663
  routeAction: routeConfig?.action,
@@ -1274,6 +1705,20 @@ function dedupeFailures(result) {
1274
1705
  }
1275
1706
  }
1276
1707
  function defaultOnDenied(result, _req, res) {
1708
+ if (result.misconfigured) {
1709
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1710
+ res.status(503).json({
1711
+ success: false,
1712
+ error: {
1713
+ code: "MERCHANT_VERIFICATION_MISCONFIGURED",
1714
+ message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
1715
+ guidance: result.guidance,
1716
+ failures: result.failures,
1717
+ correlationId: result.correlationId
1718
+ }
1719
+ });
1720
+ return;
1721
+ }
1277
1722
  const statusCode = !result.identityVerified ? 401 : 403;
1278
1723
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
1279
1724
  res.status(statusCode).json({
@@ -1295,6 +1740,11 @@ function defaultOnDenied(result, _req, res) {
1295
1740
  }
1296
1741
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1297
1742
  function createMiddleware(options) {
1743
+ if ("routes" in options) {
1744
+ console.warn(
1745
+ "[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
1746
+ );
1747
+ }
1298
1748
  const {
1299
1749
  extractCredentials: customExtractCredentials,
1300
1750
  extractPurpose: customExtractPurpose,
@@ -1306,12 +1756,25 @@ function createMiddleware(options) {
1306
1756
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1307
1757
  failOnError = "open",
1308
1758
  caseInsensitiveRouteMatch = false,
1759
+ anonymousBeacon = true,
1309
1760
  ...config
1310
1761
  } = options;
1311
1762
  if (config.apiBaseUrl) {
1312
1763
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
1313
1764
  });
1314
1765
  }
1766
+ const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
1767
+ const beaconPassThrough = emitAnonymousBeacon ? (req) => {
1768
+ const xff = req.headers["x-forwarded-for"];
1769
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1770
+ emitAnonymousBeacon({
1771
+ requestPath: req.path,
1772
+ requestMethod: req.method,
1773
+ counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
1774
+ sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
1775
+ rawHeaders: req.headers
1776
+ });
1777
+ } : void 0;
1315
1778
  let cachedRoutes = [];
1316
1779
  let lastFetchAt = 0;
1317
1780
  let refreshing = null;
@@ -1372,6 +1835,7 @@ function createMiddleware(options) {
1372
1835
  cachedRoutes.length === 0 ? "no-policy" : "no-match"
1373
1836
  );
1374
1837
  }
1838
+ beaconPassThrough?.(req);
1375
1839
  return next();
1376
1840
  }
1377
1841
  const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
@@ -1382,6 +1846,7 @@ function createMiddleware(options) {
1382
1846
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
1383
1847
  res.setHeader("X-Astra-Gateway-Reason", "route-none");
1384
1848
  }
1849
+ beaconPassThrough?.(req);
1385
1850
  return next();
1386
1851
  }
1387
1852
  const pdlssPair = resolveRequestPdlss(
@@ -1474,16 +1939,10 @@ function createMiddleware(options) {
1474
1939
  forwardedFor: forwardedForStr,
1475
1940
  agentCardUrl,
1476
1941
  // Workstream D — capture EVERY inbound header (SDK-only merchants
1477
- // have no edge to do it upstream). Local string work, secrets
1478
- // stripped; out of the verify cache key so it doesn't defeat caching.
1479
- observedMetadata: (() => {
1480
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
1481
- return {
1482
- headers,
1483
- ...apiKeyFormat && { apiKeyFormat },
1484
- ...platformHeaders && { platformHeaders }
1485
- };
1486
- })()
1942
+ // have no edge to do it upstream) plus the CDN-derived connection
1943
+ // block. Local string work, secrets stripped; out of the verify
1944
+ // cache key so it doesn't defeat caching.
1945
+ observedMetadata: buildSdkObservedMetadata(req.headers)
1487
1946
  }
1488
1947
  };
1489
1948
  let result = await verify(config, verificationRequest);
@@ -1677,7 +2136,7 @@ function resolveNextPdlss(request, routeConfig) {
1677
2136
  routeAction: routeConfig?.action
1678
2137
  });
1679
2138
  }
1680
- function generateCommerceShieldHtml(result, options) {
2139
+ function generateInterstitialHtml(result, options) {
1681
2140
  const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
1682
2141
  const message = escapeHtml(
1683
2142
  options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
@@ -1833,13 +2292,31 @@ function generateCommerceShieldHtml(result, options) {
1833
2292
  }
1834
2293
  var DEFAULT_ROUTES_REFRESH_MS2 = 5 * 60 * 1e3;
1835
2294
  function createMiddleware2(options) {
2295
+ if ("routes" in options) {
2296
+ console.warn(
2297
+ "[VerificationGateway/Next.js] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
2298
+ );
2299
+ }
1836
2300
  const {
1837
2301
  skipPaths = [],
1838
- showCommerceShield = true,
2302
+ showInterstitial,
2303
+ showCommerceShield,
1839
2304
  enableRuntimeChallenge = true,
1840
2305
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS2,
2306
+ anonymousBeacon = true,
1841
2307
  ...config
1842
2308
  } = options;
2309
+ const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
2310
+ const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "website" }) : void 0;
2311
+ const beaconPassThrough = emitAnonymousBeacon ? (request, pathname) => {
2312
+ emitAnonymousBeacon({
2313
+ requestPath: pathname,
2314
+ requestMethod: request.method,
2315
+ counterpartyUrl: config.counterpartyUrl || request.nextUrl.origin,
2316
+ sourceIp: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || void 0,
2317
+ rawHeaders: Object.fromEntries(request.headers.entries())
2318
+ });
2319
+ } : void 0;
1843
2320
  let cachedRoutes = [];
1844
2321
  let lastFetchAt = 0;
1845
2322
  let refreshing = null;
@@ -1881,9 +2358,11 @@ function createMiddleware2(options) {
1881
2358
  }
1882
2359
  const routeConfig = findRouteConfig2(cachedRoutes, pathname, request.method);
1883
2360
  if (!routeConfig) {
2361
+ beaconPassThrough?.(request, pathname);
1884
2362
  return NextResponse.next();
1885
2363
  }
1886
2364
  if (routeConfig.minAccessLevel === "none") {
2365
+ beaconPassThrough?.(request, pathname);
1887
2366
  return NextResponse.next();
1888
2367
  }
1889
2368
  const credentials = extractCredentialsFromNextRequest(request);
@@ -1935,8 +2414,8 @@ function createMiddleware2(options) {
1935
2414
  { status: 403 }
1936
2415
  );
1937
2416
  }
1938
- if (showCommerceShield) {
1939
- return new NextResponse(generateCommerceShieldHtml(preCheckResult, options), {
2417
+ if (interstitialEnabled) {
2418
+ return new NextResponse(generateInterstitialHtml(preCheckResult, options), {
1940
2419
  status: 200,
1941
2420
  headers: {
1942
2421
  "Content-Type": "text/html",
@@ -1964,17 +2443,9 @@ function createMiddleware2(options) {
1964
2443
  host: request.headers.get("host") || void 0,
1965
2444
  forwardedFor,
1966
2445
  agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0,
1967
- // Workstream D — full sanitised header capture (Web Headers → plain map).
1968
- observedMetadata: (() => {
1969
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(
1970
- Object.fromEntries(request.headers.entries())
1971
- );
1972
- return {
1973
- headers,
1974
- ...apiKeyFormat && { apiKeyFormat },
1975
- ...platformHeaders && { platformHeaders }
1976
- };
1977
- })()
2446
+ // Workstream D — full sanitised header capture + CDN-derived
2447
+ // connection block (Web Headers → plain map).
2448
+ observedMetadata: buildSdkObservedMetadata(Object.fromEntries(request.headers.entries()))
1978
2449
  }
1979
2450
  };
1980
2451
  let result = await verify(config, verificationRequest);
@@ -1990,6 +2461,21 @@ function createMiddleware2(options) {
1990
2461
  const approvalRequired = result.identityVerified && result.policyAllowed && requiresHumanApproval(result);
1991
2462
  if (approvalRequired) annotateApprovalRequired(result);
1992
2463
  if (!result.identityVerified || !result.policyAllowed || approvalRequired) {
2464
+ if (result.misconfigured && pathname.startsWith("/api/")) {
2465
+ return NextResponse.json(
2466
+ {
2467
+ success: false,
2468
+ error: {
2469
+ code: "MERCHANT_VERIFICATION_MISCONFIGURED",
2470
+ message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
2471
+ guidance: result.guidance,
2472
+ failures: result.failures,
2473
+ correlationId: result.correlationId
2474
+ }
2475
+ },
2476
+ { status: 503 }
2477
+ );
2478
+ }
1993
2479
  if (pathname.startsWith("/api/")) {
1994
2480
  return NextResponse.json(
1995
2481
  {
@@ -2014,8 +2500,8 @@ function createMiddleware2(options) {
2014
2500
  }
2015
2501
  );
2016
2502
  }
2017
- if (showCommerceShield) {
2018
- return new NextResponse(generateCommerceShieldHtml(result, options), {
2503
+ if (interstitialEnabled) {
2504
+ return new NextResponse(generateInterstitialHtml(result, options), {
2019
2505
  status: 200,
2020
2506
  headers: {
2021
2507
  "Content-Type": "text/html",
@@ -2103,7 +2589,7 @@ var VerificationGatewayClient = class {
2103
2589
  /**
2104
2590
  * Quick verification — checks credentials and policy in one call.
2105
2591
  *
2106
- * Round-18 G4: return shape mirrors `VerificationResult`'s identity/policy
2592
+ * The return shape mirrors `VerificationResult`'s identity/policy
2107
2593
  * split. Map to HTTP status the same way: `!identityVerified` → 401,
2108
2594
  * `identityVerified && !policyAllowed` → 403.
2109
2595
  */
@@ -2533,8 +3019,8 @@ function extractX402TransactionValue(req) {
2533
3019
  // src/transport/rfc9421.ts
2534
3020
  import { parseDictionary } from "structured-headers";
2535
3021
  function parseRFC9421(headers) {
2536
- const sigInput = readHeader(headers, "signature-input");
2537
- const sig = readHeader(headers, "signature");
3022
+ const sigInput = readHeader2(headers, "signature-input");
3023
+ const sig = readHeader2(headers, "signature");
2538
3024
  if (!sigInput || !sig) return null;
2539
3025
  let inputDict;
2540
3026
  let sigDict;
@@ -2578,7 +3064,7 @@ function parseRFC9421(headers) {
2578
3064
  if (signatures.length === 0) return null;
2579
3065
  return { signatures };
2580
3066
  }
2581
- function readHeader(headers, name) {
3067
+ function readHeader2(headers, name) {
2582
3068
  for (const key of Object.keys(headers)) {
2583
3069
  if (key.toLowerCase() === name) {
2584
3070
  const raw = headers[key];
@@ -2896,11 +3382,11 @@ function extractACPContext(request) {
2896
3382
  const purpose = mapACPRequestToPurpose(method, path);
2897
3383
  const sessionId = extractSessionId2(path);
2898
3384
  const headers = request.headers ?? {};
2899
- const signatureHeader = readHeader2(headers, "signature");
2900
- const timestampHeader = readHeader2(headers, "timestamp");
2901
- const idempotencyKey = readHeader2(headers, "idempotency-key");
2902
- const apiVersion = readHeader2(headers, "api-version");
2903
- const bearer = extractBearer(readHeader2(headers, "authorization"));
3385
+ const signatureHeader = readHeader3(headers, "signature");
3386
+ const timestampHeader = readHeader3(headers, "timestamp");
3387
+ const idempotencyKey = readHeader3(headers, "idempotency-key");
3388
+ const apiVersion = readHeader3(headers, "api-version");
3389
+ const bearer = extractBearer(readHeader3(headers, "authorization"));
2904
3390
  const body = request.body ?? {};
2905
3391
  const merchantId = coerceString3(body.merchant_id ?? body.merchantId);
2906
3392
  const totals = Array.isArray(body.totals) ? body.totals : void 0;
@@ -2968,7 +3454,7 @@ function extractFulfillmentOption(body) {
2968
3454
  }
2969
3455
  return void 0;
2970
3456
  }
2971
- function readHeader2(headers, name) {
3457
+ function readHeader3(headers, name) {
2972
3458
  for (const key of Object.keys(headers)) {
2973
3459
  if (key.toLowerCase() === name) {
2974
3460
  const raw = headers[key];
@@ -3762,9 +4248,9 @@ function toBuf(bytes) {
3762
4248
  new Uint8Array(out).set(bytes);
3763
4249
  return out;
3764
4250
  }
3765
- function checkTimestamp(headerValue2, toleranceSec, nowFn) {
3766
- if (!headerValue2) return { ok: false, error: "missing Timestamp header" };
3767
- const ts = parseTimestamp(headerValue2);
4251
+ function checkTimestamp(headerValue3, toleranceSec, nowFn) {
4252
+ if (!headerValue3) return { ok: false, error: "missing Timestamp header" };
4253
+ const ts = parseTimestamp(headerValue3);
3768
4254
  if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
3769
4255
  const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
3770
4256
  if (Math.abs(now - ts) > toleranceSec) {
@@ -3808,7 +4294,7 @@ async function getSubtle() {
3808
4294
  // src/transport/mpp.ts
3809
4295
  import { Challenge, Credential, Receipt } from "mppx";
3810
4296
  function extractMPPFromRequest(request) {
3811
- const auth = readHeader3(request.headers, "authorization");
4297
+ const auth = readHeader4(request.headers, "authorization");
3812
4298
  if (!auth || !/^\s*Payment\s+/i.test(auth)) return null;
3813
4299
  try {
3814
4300
  const credential = Credential.deserialize(auth);
@@ -3836,7 +4322,7 @@ function extractMPPFromResponse(response) {
3836
4322
  offeredMethods: methods
3837
4323
  };
3838
4324
  }
3839
- const receiptHeader = readHeader3(response.headers, "payment-receipt");
4325
+ const receiptHeader = readHeader4(response.headers, "payment-receipt");
3840
4326
  if (receiptHeader) {
3841
4327
  try {
3842
4328
  const parsed = Receipt.deserialize(receiptHeader);
@@ -3856,7 +4342,7 @@ function extractMPPFromResponse(response) {
3856
4342
  return { kind: "error", error: { type: "invalid-receipt-encoding" } };
3857
4343
  }
3858
4344
  }
3859
- const contentType = readHeader3(response.headers, "content-type");
4345
+ const contentType = readHeader4(response.headers, "content-type");
3860
4346
  if (contentType && /application\/problem\+json/i.test(contentType)) {
3861
4347
  const body = typeof response.body === "object" && response.body !== null ? response.body : {};
3862
4348
  return {
@@ -3879,7 +4365,7 @@ function extractMPPContext(message) {
3879
4365
  return extractMPPFromRequest(message);
3880
4366
  }
3881
4367
  function collectChallenges(response) {
3882
- const wwwAuth = readHeader3(response.headers, "www-authenticate");
4368
+ const wwwAuth = readHeader4(response.headers, "www-authenticate");
3883
4369
  if (!wwwAuth) return [];
3884
4370
  const headers = new Headers();
3885
4371
  headers.set("www-authenticate", wwwAuth);
@@ -3907,7 +4393,7 @@ function summarizeChallenge(ch) {
3907
4393
  opaque: raw.opaque
3908
4394
  };
3909
4395
  }
3910
- function readHeader3(headers, name) {
4396
+ function readHeader4(headers, name) {
3911
4397
  for (const key of Object.keys(headers)) {
3912
4398
  if (key.toLowerCase() === name) {
3913
4399
  const raw = headers[key];
@@ -3999,14 +4485,14 @@ import {
3999
4485
  } from "@x402/core/schemas";
4000
4486
  import { safeBase64Decode } from "@x402/core/utils";
4001
4487
  function extractX402FromRequest(request) {
4002
- const headerValue2 = readHeader4(request.headers, "x-payment");
4488
+ const headerValue3 = readHeader5(request.headers, "x-payment");
4003
4489
  if (request.body && typeof request.body === "object") {
4004
4490
  const parsed = tryParsePayload(request.body);
4005
4491
  if (parsed) return buildPayloadContext(parsed, "body");
4006
4492
  }
4007
- if (headerValue2) {
4493
+ if (headerValue3) {
4008
4494
  try {
4009
- const decoded = safeBase64Decode(headerValue2);
4495
+ const decoded = safeBase64Decode(headerValue3);
4010
4496
  if (decoded) {
4011
4497
  const json = JSON.parse(decoded);
4012
4498
  const parsed = tryParsePayload(json);
@@ -4029,10 +4515,10 @@ function extractX402FromResponse(response) {
4029
4515
  const parsed = tryParseRequired(response.body);
4030
4516
  if (parsed) return buildRequiredContext(parsed, "body");
4031
4517
  }
4032
- const headerValue2 = readHeader4(response.headers, "x-payment-required");
4033
- if (headerValue2) {
4518
+ const headerValue3 = readHeader5(response.headers, "x-payment-required");
4519
+ if (headerValue3) {
4034
4520
  try {
4035
- const decoded = safeBase64Decode(headerValue2);
4521
+ const decoded = safeBase64Decode(headerValue3);
4036
4522
  if (decoded) {
4037
4523
  const json = JSON.parse(decoded);
4038
4524
  const parsed = tryParseRequired(json);
@@ -4129,7 +4615,7 @@ function coerceVersion(v) {
4129
4615
  if (v === 1 || v === 2) return v;
4130
4616
  return null;
4131
4617
  }
4132
- function readHeader4(headers, name) {
4618
+ function readHeader5(headers, name) {
4133
4619
  if (!headers) return void 0;
4134
4620
  for (const key of Object.keys(headers)) {
4135
4621
  if (key.toLowerCase() === name) {
@@ -4877,8 +5363,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
4877
5363
  } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
4878
5364
  action = parsed.actionFromBody;
4879
5365
  actionSource = parsed.actionSourceFromBody;
5366
+ } else if (parsed.method === "tools/call") {
5367
+ action = void 0;
5368
+ actionSource = void 0;
4880
5369
  } else {
4881
- action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
5370
+ action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
4882
5371
  actionSource = "transport_layer";
4883
5372
  }
4884
5373
  return { purpose, action, resource, purposeSource, actionSource };
@@ -4916,6 +5405,24 @@ function dedupeFailures2(result) {
4916
5405
  }
4917
5406
  function defaultMcpDenied(result, req, res) {
4918
5407
  const id = req.body?.id ?? null;
5408
+ if (result.misconfigured) {
5409
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
5410
+ res.status(503).json({
5411
+ jsonrpc: "2.0",
5412
+ id,
5413
+ error: {
5414
+ // -32002 → merchant verification misconfigured (see -32000/-32001 below)
5415
+ code: -32002,
5416
+ message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
5417
+ data: {
5418
+ guidance: result.guidance,
5419
+ failures: result.failures,
5420
+ correlationId: result.correlationId
5421
+ }
5422
+ }
5423
+ });
5424
+ return;
5425
+ }
4919
5426
  const status = !result.identityVerified ? 401 : 403;
4920
5427
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
4921
5428
  res.status(status).json({
@@ -4925,6 +5432,7 @@ function defaultMcpDenied(result, req, res) {
4925
5432
  // JSON-RPC error codes:
4926
5433
  // -32000 → unauthorized (no identity resolved)
4927
5434
  // -32001 → insufficient access (identity OK, policy denied)
5435
+ // -32002 → merchant verification misconfigured (handled above)
4928
5436
  code: !result.identityVerified ? -32e3 : -32001,
4929
5437
  message: result.denialReasons?.[0] ?? "Access denied",
4930
5438
  data: {
@@ -4974,6 +5482,15 @@ function createMcpMiddleware(options) {
4974
5482
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
4975
5483
  });
4976
5484
  }
5485
+ if (toolGates) {
5486
+ for (const [toolName, gate] of Object.entries(toolGates)) {
5487
+ if (typeof gate === "string") {
5488
+ console.warn(
5489
+ `[VerificationGateway/MCP] toolGates['${toolName}'] uses the bare-string shorthand ('${gate}'). Gated tools need a PDLSS purpose (else PDLSS_PURPOSE_REQUIRED 400) and evaluate purpose-only without a declared action. Use the object form instead: { minAccessLevel: '${gate}', purpose: '...', action: '...' }.`
5490
+ );
5491
+ }
5492
+ }
5493
+ }
4977
5494
  return async (req, res, next) => {
4978
5495
  try {
4979
5496
  if (skip) return next();
@@ -5093,7 +5610,13 @@ function createMcpMiddleware(options) {
5093
5610
  callerMetadata: {
5094
5611
  sourceIp: req.ip,
5095
5612
  userAgent: req.headers["user-agent"],
5096
- host: req.headers.host
5613
+ host: req.headers.host,
5614
+ // Capture EVERY inbound header (parity with the express adapter —
5615
+ // MCP-only merchants have no edge to do it upstream) plus the
5616
+ // CDN-derived connection block. Local string work, secrets
5617
+ // stripped; out of the verify cache key so it doesn't defeat
5618
+ // caching.
5619
+ observedMetadata: buildSdkObservedMetadata(req.headers)
5097
5620
  }
5098
5621
  };
5099
5622
  let result = await verify(config, verificationRequest);
@@ -5878,212 +6401,6 @@ async function recordDecision2(config, params) {
5878
6401
  blockchainTxHash: result.blockchainTxHash
5879
6402
  };
5880
6403
  }
5881
-
5882
- // src/edge-config.ts
5883
- var DEFAULT_EDGE_CONFIG = Object.freeze({
5884
- version: 1,
5885
- mode: "observe",
5886
- depth: "classify",
5887
- pathRules: [],
5888
- sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
5889
- failurePosture: "open"
5890
- });
5891
- var EDGE_MODES = ["observe", "enforce"];
5892
- var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
5893
- function isEdgeConfig(value) {
5894
- if (typeof value !== "object" || value === null) return false;
5895
- const v = value;
5896
- if (v.version !== 1) return false;
5897
- if (!EDGE_MODES.includes(v.mode)) return false;
5898
- if (!EDGE_DEPTHS.includes(v.depth)) return false;
5899
- if (!Array.isArray(v.pathRules)) return false;
5900
- for (const rule of v.pathRules) {
5901
- if (typeof rule !== "object" || rule === null) return false;
5902
- const r = rule;
5903
- if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
5904
- if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
5905
- if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
5906
- }
5907
- const sampling = v.sampling;
5908
- if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
5909
- return false;
5910
- }
5911
- return v.failurePosture === "open";
5912
- }
5913
- async function fetchEdgeConfig(config, counterpartyId, opts) {
5914
- if (!counterpartyId) return null;
5915
- const headers = { Accept: "application/json" };
5916
- if (config.apiKey) {
5917
- headers["Authorization"] = `Bearer ${config.apiKey}`;
5918
- headers["X-API-Key"] = config.apiKey;
5919
- }
5920
- if (opts?.etag) headers["If-None-Match"] = opts.etag;
5921
- try {
5922
- const response = await fetch(
5923
- `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
5924
- { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
5925
- );
5926
- if (response.status === 304) return { notModified: true };
5927
- if (!response.ok) return null;
5928
- const body = await response.json();
5929
- const edgeConfig = body.data?.edgeConfig;
5930
- if (!isEdgeConfig(edgeConfig)) return null;
5931
- return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
5932
- } catch {
5933
- return null;
5934
- }
5935
- }
5936
- var DEFAULT_TTL_MS = 60 * 1e3;
5937
- var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
5938
- var cache2 = /* @__PURE__ */ new Map();
5939
- var inflight2 = /* @__PURE__ */ new Map();
5940
- function cacheKey(apiBaseUrl, counterpartyId) {
5941
- return `${apiBaseUrl}|${counterpartyId}`;
5942
- }
5943
- function degradeToObserve(config) {
5944
- if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
5945
- return config;
5946
- }
5947
- return {
5948
- ...config,
5949
- mode: "observe",
5950
- pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
5951
- };
5952
- }
5953
- async function revalidate(config, counterpartyId) {
5954
- const key = cacheKey(config.apiBaseUrl, counterpartyId);
5955
- const existing = inflight2.get(key);
5956
- if (existing) return existing;
5957
- const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
5958
- if (result === null) return;
5959
- const now = Date.now();
5960
- if ("notModified" in result) {
5961
- const entry = cache2.get(key);
5962
- if (entry) entry.lastSuccessAt = now;
5963
- return;
5964
- }
5965
- cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
5966
- }).finally(() => {
5967
- inflight2.delete(key);
5968
- });
5969
- inflight2.set(key, promise);
5970
- return promise;
5971
- }
5972
- async function getEdgeConfig(config, counterpartyId, opts) {
5973
- const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
5974
- const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
5975
- const key = cacheKey(config.apiBaseUrl, counterpartyId);
5976
- const entry = cache2.get(key);
5977
- if (entry) {
5978
- const age = Date.now() - entry.lastSuccessAt;
5979
- if (age > ttlMs) {
5980
- revalidate(config, counterpartyId).catch(() => {
5981
- });
5982
- }
5983
- return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
5984
- }
5985
- await revalidate(config, counterpartyId).catch(() => {
5986
- });
5987
- return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
5988
- }
5989
- function matchEdgePathRule(rules, path) {
5990
- for (const rule of rules) {
5991
- if (globToRegExp(rule.pattern).test(path)) return rule;
5992
- }
5993
- return void 0;
5994
- }
5995
- function globToRegExp(pattern) {
5996
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
5997
- return new RegExp(`^${escaped}$`, "i");
5998
- }
5999
- function _resetEdgeConfigCache() {
6000
- cache2.clear();
6001
- inflight2.clear();
6002
- }
6003
-
6004
- // src/platform-signatures.ts
6005
- var PLATFORM_AGENT_SIGNATURES = [
6006
- {
6007
- vendor: "claude",
6008
- displayName: "Anthropic Claude",
6009
- uaPatterns: [/claude/i, /anthropic/i],
6010
- agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
6011
- apiKeyFormatPatterns: [/^sk-ant/i],
6012
- headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
6013
- },
6014
- {
6015
- vendor: "chatgpt",
6016
- displayName: "OpenAI ChatGPT",
6017
- uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
6018
- agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
6019
- apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
6020
- headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
6021
- },
6022
- {
6023
- vendor: "gemini",
6024
- displayName: "Google Gemini",
6025
- uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
6026
- agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
6027
- apiKeyFormatPatterns: [/^aiza/i],
6028
- headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
6029
- },
6030
- {
6031
- vendor: "cursor",
6032
- displayName: "Cursor",
6033
- uaPatterns: [/cursor/i],
6034
- agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
6035
- },
6036
- {
6037
- vendor: "goose",
6038
- displayName: "Block Goose",
6039
- uaPatterns: [/goose/i, /block-goose/i],
6040
- agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
6041
- },
6042
- {
6043
- vendor: "perplexity",
6044
- displayName: "Perplexity",
6045
- uaPatterns: [/perplexity/i],
6046
- agentCardPatterns: [/perplexity\.ai/i],
6047
- apiKeyFormatPatterns: [/^pplx-/i]
6048
- }
6049
- ];
6050
- function matchPlatformSignature(signatures, input) {
6051
- const ua = input.userAgent ?? "";
6052
- const cardUrl = input.agentCardUrl ?? "";
6053
- const apiKeyFormat = input.apiKeyFormat ?? "";
6054
- const headerLines = [];
6055
- if (input.platformHeaders) {
6056
- for (const [name, value] of Object.entries(input.platformHeaders)) {
6057
- headerLines.push(name, `${name}: ${value}`);
6058
- }
6059
- }
6060
- for (const sig of signatures) {
6061
- const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
6062
- const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
6063
- const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
6064
- const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
6065
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
6066
- const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
6067
- const cardHost = (() => {
6068
- try {
6069
- return cardUrl ? new URL(cardUrl).host : "";
6070
- } catch {
6071
- return "";
6072
- }
6073
- })();
6074
- const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
6075
- return {
6076
- vendor: sig.vendor,
6077
- fingerprintKey: `${sig.vendor}:${keyBasis}`,
6078
- displayName: sig.displayName
6079
- };
6080
- }
6081
- }
6082
- return void 0;
6083
- }
6084
- function detectPlatformFingerprint(input) {
6085
- return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
6086
- }
6087
6404
  export {
6088
6405
  ACCESS_LEVEL_DESCRIPTIONS,
6089
6406
  ACCESS_LEVEL_HIERARCHY,
@@ -6091,6 +6408,7 @@ export {
6091
6408
  AstraSync,
6092
6409
  AstraSyncError,
6093
6410
  AuthenticationError,
6411
+ CAPTURE_SCHEMA_VERSION,
6094
6412
  ChallengeHandler,
6095
6413
  DEFAULT_EDGE_CONFIG,
6096
6414
  DEFAULT_TRUST_THRESHOLDS,
@@ -6109,9 +6427,11 @@ export {
6109
6427
  authorizeSettlement,
6110
6428
  awaitStepUpApproval,
6111
6429
  buildGuidance,
6430
+ buildSdkObservedMetadata,
6112
6431
  clearCache,
6113
6432
  createMcpMiddleware,
6114
6433
  degradeToObserve,
6434
+ deriveConnectionFromHeaders,
6115
6435
  detectPlatformFingerprint,
6116
6436
  determineAccessLevel,
6117
6437
  express_exports as express,