@astrasyncai/verification-gateway 5.11.0 → 5.13.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 (98) hide show
  1. package/README.md +4 -0
  2. package/dist/adapters/express.d.mts +1 -1
  3. package/dist/adapters/express.d.ts +1 -1
  4. package/dist/adapters/express.js +394 -39
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/express.mjs +394 -39
  7. package/dist/adapters/express.mjs.map +1 -1
  8. package/dist/adapters/mcp.d.mts +5 -1
  9. package/dist/adapters/mcp.d.ts +5 -1
  10. package/dist/adapters/mcp.js +193 -49
  11. package/dist/adapters/mcp.js.map +1 -1
  12. package/dist/adapters/mcp.mjs +193 -49
  13. package/dist/adapters/mcp.mjs.map +1 -1
  14. package/dist/adapters/nextjs.d.mts +1 -1
  15. package/dist/adapters/nextjs.d.ts +1 -1
  16. package/dist/adapters/nextjs.js +387 -22
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nextjs.mjs +387 -22
  19. package/dist/adapters/nextjs.mjs.map +1 -1
  20. package/dist/adapters/sdk.d.mts +9 -1
  21. package/dist/adapters/sdk.d.ts +9 -1
  22. package/dist/adapters/sdk.js +125 -16
  23. package/dist/adapters/sdk.js.map +1 -1
  24. package/dist/adapters/sdk.mjs +125 -16
  25. package/dist/adapters/sdk.mjs.map +1 -1
  26. package/dist/agent/index.js +1 -1
  27. package/dist/agent/index.js.map +1 -1
  28. package/dist/agent/index.mjs +1 -1
  29. package/dist/agent/index.mjs.map +1 -1
  30. package/dist/bin/astrasync-claude-hook.js +309 -15
  31. package/dist/bin/astrasync-codex-hook.js +309 -15
  32. package/dist/bin/astrasync-guard.js +309 -15
  33. package/dist/bin/astrasync.js +322 -17
  34. package/dist/browser/background.js +309 -15
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +309 -15
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/cli/index.js +1 -1
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/index.mjs +1 -1
  41. package/dist/cli/index.mjs.map +1 -1
  42. package/dist/codex/index.js +309 -15
  43. package/dist/codex/index.js.map +1 -1
  44. package/dist/codex/index.mjs +309 -15
  45. package/dist/codex/index.mjs.map +1 -1
  46. package/dist/cursor/extension.js +309 -15
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +309 -15
  49. package/dist/cursor/extension.mjs.map +1 -1
  50. package/dist/edge-config.d.mts +1 -1
  51. package/dist/edge-config.d.ts +1 -1
  52. package/dist/edge-config.js +1 -1
  53. package/dist/edge-config.js.map +1 -1
  54. package/dist/edge-config.mjs +1 -1
  55. package/dist/edge-config.mjs.map +1 -1
  56. package/dist/edge-core/index.d.mts +128 -13
  57. package/dist/edge-core/index.d.ts +128 -13
  58. package/dist/edge-core/index.js +362 -63
  59. package/dist/edge-core/index.js.map +1 -1
  60. package/dist/edge-core/index.mjs +359 -63
  61. package/dist/edge-core/index.mjs.map +1 -1
  62. package/dist/gateway/gateway.js +309 -15
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/gateway.mjs +309 -15
  65. package/dist/gateway/gateway.mjs.map +1 -1
  66. package/dist/git-trigger/git-hooks.d.mts +1 -1
  67. package/dist/git-trigger/git-hooks.d.ts +1 -1
  68. package/dist/index.d.mts +118 -15
  69. package/dist/index.d.ts +118 -15
  70. package/dist/index.js +521 -72
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +515 -72
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/platform-signatures.js +4 -1
  75. package/dist/platform-signatures.js.map +1 -1
  76. package/dist/platform-signatures.mjs +4 -1
  77. package/dist/platform-signatures.mjs.map +1 -1
  78. package/dist/registration/index.js +1 -1
  79. package/dist/registration/index.js.map +1 -1
  80. package/dist/registration/index.mjs +1 -1
  81. package/dist/registration/index.mjs.map +1 -1
  82. package/dist/transport/index.js +1 -1
  83. package/dist/transport/index.js.map +1 -1
  84. package/dist/transport/index.mjs +1 -1
  85. package/dist/transport/index.mjs.map +1 -1
  86. package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
  87. package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
  88. package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
  89. package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
  90. package/dist/ui/index.d.mts +1 -1
  91. package/dist/ui/index.d.ts +1 -1
  92. package/dist/verify.d.mts +35 -14
  93. package/dist/verify.d.ts +35 -14
  94. package/dist/verify.js +165 -25
  95. package/dist/verify.js.map +1 -1
  96. package/dist/verify.mjs +164 -25
  97. package/dist/verify.mjs.map +1 -1
  98. package/package.json +3 -2
@@ -3713,7 +3713,7 @@ Policy saved to ${outputPath}`);
3713
3713
  };
3714
3714
 
3715
3715
  // src/version.ts
3716
- var SDK_VERSION = "5.11.0";
3716
+ var SDK_VERSION = "5.13.0";
3717
3717
 
3718
3718
  // src/http.ts
3719
3719
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -4644,6 +4644,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
4644
4644
  return cache.get(apiBaseUrl)?.data;
4645
4645
  }
4646
4646
 
4647
+ // src/failure-posture.ts
4648
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
4649
+ "identity.invalid_agent_id",
4650
+ "identity.unknown_agent",
4651
+ "agent.lookup"
4652
+ ]);
4653
+ var UNKNOWN_AGENT_GUIDANCE = "Register your agent with AstraSync (or, inside an MCP host, add the AstraSync MCP connector for keyless, human-approved registration), then present the canonical ASTRA-* id issued after owner approval.";
4654
+
4647
4655
  // src/verify.ts
4648
4656
  var DEFAULT_CONFIG = {
4649
4657
  apiBaseUrl: "https://astrasync.ai/api",
@@ -4656,7 +4664,7 @@ var DEFAULT_CONFIG = {
4656
4664
  };
4657
4665
  var initCheckPerformed = false;
4658
4666
  var deprecationWarningShown = false;
4659
- async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4667
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
4660
4668
  initCheckPerformed = true;
4661
4669
  try {
4662
4670
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -4674,7 +4682,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4674
4682
  );
4675
4683
  }
4676
4684
  if (apiKey) {
4677
- const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
4685
+ const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
4686
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
4678
4687
  headers: { Authorization: `Bearer ${apiKey}` }
4679
4688
  });
4680
4689
  if (selfCheck.status === 401) {
@@ -4686,6 +4695,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4686
4695
  console.error(message);
4687
4696
  } else if (selfCheck.ok) {
4688
4697
  const body = await selfCheck.json().catch(() => ({}));
4698
+ if (body.counterparty && body.counterparty.canVerify === false) {
4699
+ const message = `[VerificationGateway] the configured AstraSync API key cannot verify for this endpoint (${body.counterparty.message ?? "endpoint not found or owned by another account"}). Every inbound agent will be turned away until the key or counterparty is corrected.`;
4700
+ if (strictInit) {
4701
+ throw new Error(`${message} (strictInit=true)`);
4702
+ }
4703
+ console.error(message);
4704
+ }
4689
4705
  if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
4690
4706
  console.warn(
4691
4707
  `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
@@ -4790,11 +4806,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
4790
4806
  const isMisconfig = source === "merchant_misconfig";
4791
4807
  const urls = options.urls;
4792
4808
  const guidance = isMisconfig ? {
4793
- 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.",
4809
+ message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, rotated, or belongs to another account). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
4794
4810
  registrationUrl: urls?.registrationUrl ?? "",
4795
4811
  documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
4796
4812
  steps: [
4797
- "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
4813
+ "Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
4798
4814
  "Do not retry: the failure is deterministic until the operator updates their key"
4799
4815
  ]
4800
4816
  } : isApiError ? {
@@ -4855,11 +4871,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
4855
4871
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
4856
4872
  // deterministic and intentionally NOT marked retryable.
4857
4873
  ...isApiError && { retryable: true },
4874
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
4875
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
4858
4876
  correlationId: options.correlationId,
4859
4877
  attemptId: options.attemptId,
4860
4878
  verifiedAt: /* @__PURE__ */ new Date()
4861
4879
  };
4862
4880
  }
4881
+ async function readJsonSafe(response) {
4882
+ try {
4883
+ const parsed = await response.json();
4884
+ return parsed && typeof parsed === "object" ? parsed : {};
4885
+ } catch {
4886
+ return {};
4887
+ }
4888
+ }
4889
+ function rejectedIdentityGuidance(failures) {
4890
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
4891
+ if (!f) return void 0;
4892
+ if (f.dimension === "agent.lookup") {
4893
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
4894
+ }
4895
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
4896
+ }
4897
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
4898
+ function parseRetryAfterMs(response) {
4899
+ const raw = response.headers?.get?.("retry-after");
4900
+ if (!raw) return void 0;
4901
+ const trimmed = raw.trim();
4902
+ let ms;
4903
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
4904
+ ms = Number(trimmed) * 1e3;
4905
+ } else {
4906
+ const at = Date.parse(trimmed);
4907
+ if (Number.isNaN(at)) return void 0;
4908
+ ms = at - Date.now();
4909
+ }
4910
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
4911
+ }
4863
4912
  async function callVerifyAccessAPI(config, request2) {
4864
4913
  const { credentials, ...requestData } = request2;
4865
4914
  const body = {
@@ -4916,7 +4965,8 @@ async function callVerifyAccessAPI(config, request2) {
4916
4965
  body: JSON.stringify(body),
4917
4966
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
4918
4967
  });
4919
- const data = await response.json();
4968
+ const data = await readJsonSafe(response);
4969
+ const retryAfterMs = parseRetryAfterMs(response);
4920
4970
  if (response.status === 410) {
4921
4971
  return {
4922
4972
  success: true,
@@ -4954,13 +5004,31 @@ async function callVerifyAccessAPI(config, request2) {
4954
5004
  }
4955
5005
  };
4956
5006
  }
5007
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
5008
+ return {
5009
+ success: true,
5010
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
5011
+ access: {
5012
+ allowed: false,
5013
+ reason: "unknown_agent",
5014
+ failures: [
5015
+ {
5016
+ dimension: "identity.unknown_agent",
5017
+ message: "The presented agent identifier is not registered with AstraSync.",
5018
+ guidance: UNKNOWN_AGENT_GUIDANCE
5019
+ }
5020
+ ]
5021
+ }
5022
+ };
5023
+ }
4957
5024
  if (!response.ok) {
4958
- if (response.status === 401) {
5025
+ const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
5026
+ if (response.status === 401 || keyMismatch) {
4959
5027
  return {
4960
5028
  success: false,
4961
5029
  error: data.message || data.error || "Integration API key rejected",
4962
5030
  misconfig: true,
4963
- misconfigCode: typeof data?.code === "string" ? data.code : void 0,
5031
+ misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
4964
5032
  remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
4965
5033
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4966
5034
  attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
@@ -4968,9 +5036,11 @@ async function callVerifyAccessAPI(config, request2) {
4968
5036
  }
4969
5037
  return {
4970
5038
  success: false,
4971
- error: data.message || data.error || `API returned ${response.status}`,
5039
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
4972
5040
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4973
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
5041
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
5042
+ httpStatus: response.status,
5043
+ ...retryAfterMs !== void 0 && { retryAfterMs }
4974
5044
  };
4975
5045
  }
4976
5046
  return data;
@@ -4991,14 +5061,22 @@ async function verify(config, request2, options) {
4991
5061
  mergedConfig.apiBaseUrl,
4992
5062
  mergedConfig.debug,
4993
5063
  true,
4994
- mergedConfig.apiKey
5064
+ mergedConfig.apiKey,
5065
+ {
5066
+ id: mergedConfig.counterpartyId,
5067
+ url: mergedConfig.counterpartyUrl ?? request2.counterpartyUrl
5068
+ }
4995
5069
  );
4996
5070
  } else {
4997
5071
  void performInitCheck(
4998
5072
  mergedConfig.apiBaseUrl,
4999
5073
  mergedConfig.debug,
5000
5074
  false,
5001
- mergedConfig.apiKey
5075
+ mergedConfig.apiKey,
5076
+ {
5077
+ id: mergedConfig.counterpartyId,
5078
+ url: mergedConfig.counterpartyUrl ?? request2.counterpartyUrl
5079
+ }
5002
5080
  );
5003
5081
  }
5004
5082
  }
@@ -5042,12 +5120,14 @@ async function verify(config, request2, options) {
5042
5120
  correlationId: apiResponse.correlationId,
5043
5121
  attemptId: apiResponse.attemptId,
5044
5122
  remediation: apiResponse.remediation,
5123
+ httpStatus: apiResponse.httpStatus,
5124
+ retryAfterMs: apiResponse.retryAfterMs,
5045
5125
  urls
5046
5126
  });
5047
5127
  }
5048
5128
  if (!apiResponse.access?.allowed) {
5049
5129
  const aggregatedFailures = apiResponse.access?.failures;
5050
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
5130
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
5051
5131
  const result2 = {
5052
5132
  identityVerified: idVerifiedFromBackend,
5053
5133
  policyAllowed: false,
@@ -5059,7 +5139,10 @@ async function verify(config, request2, options) {
5059
5139
  requiresStepUp: apiResponse.access?.requiresStepUp,
5060
5140
  requiresApproval: apiResponse.access?.requiresApproval,
5061
5141
  guidance: {
5062
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
5142
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
5143
+ // actionable registration guidance on its failure — surface it
5144
+ // instead of the bare machine reason ('unknown_agent').
5145
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
5063
5146
  registrationUrl: urls?.registrationUrl ?? "",
5064
5147
  documentationUrl: urls?.documentationUrl ?? "",
5065
5148
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -5159,7 +5242,7 @@ async function verify(config, request2, options) {
5159
5242
  registrationUrl: urls?.registrationUrl ?? "",
5160
5243
  documentationUrl: urls?.documentationUrl ?? ""
5161
5244
  };
5162
- } else if (result.recommendation === "step_up_required") {
5245
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
5163
5246
  result.requiresStepUp = true;
5164
5247
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
5165
5248
  }
@@ -5188,6 +5271,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
5188
5271
  var DEFAULT_TTL_MS = 60 * 1e3;
5189
5272
  var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
5190
5273
 
5274
+ // src/edge-core/events.ts
5275
+ async function withBudget(promise, ms) {
5276
+ let timer;
5277
+ try {
5278
+ return await Promise.race([
5279
+ promise,
5280
+ new Promise((resolve) => {
5281
+ timer = setTimeout(() => resolve(void 0), ms);
5282
+ })
5283
+ ]);
5284
+ } catch {
5285
+ return void 0;
5286
+ } finally {
5287
+ if (timer) clearTimeout(timer);
5288
+ }
5289
+ }
5290
+ var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
5291
+ var BeaconBuffer = class {
5292
+ constructor(options = {}) {
5293
+ this.queue = [];
5294
+ this.droppedSinceLast = 0;
5295
+ this.droppedTotal = 0;
5296
+ this.deliveredTotal = 0;
5297
+ this.backoffUntil = 0;
5298
+ this.collapse = /* @__PURE__ */ new Map();
5299
+ /** Keys the backend answered 401 for — later batches go unauthenticated. */
5300
+ this.rejectedKeys = /* @__PURE__ */ new Set();
5301
+ this.opts = {
5302
+ maxItems: options.maxItems ?? 1e3,
5303
+ maxBatch: options.maxBatch ?? 100,
5304
+ maxAttempts: options.maxAttempts ?? 3,
5305
+ collapseWindowMs: options.collapseWindowMs ?? 6e4,
5306
+ collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
5307
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
5308
+ retryBackoffMs: options.retryBackoffMs ?? 1e3,
5309
+ now: options.now ?? Date.now
5310
+ };
5311
+ }
5312
+ /**
5313
+ * Queue one beacon. With a `collapseKey`, a repeat inside the window is
5314
+ * merged into a held aggregate (sent when the window closes) instead of
5315
+ * becoming its own item.
5316
+ */
5317
+ enqueue(dest, item, collapseKey) {
5318
+ const now = this.opts.now();
5319
+ if (collapseKey) {
5320
+ const entry = this.collapse.get(collapseKey);
5321
+ if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
5322
+ for (const target of [entry.first, entry.held]) {
5323
+ if (target && !target.inFlight && this.queue.includes(target)) {
5324
+ target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
5325
+ return;
5326
+ }
5327
+ }
5328
+ const held = this.push(
5329
+ dest,
5330
+ { ...item, repeatCount: 1 },
5331
+ entry.firstSeenAt + this.opts.collapseWindowMs
5332
+ );
5333
+ entry.held = held;
5334
+ return;
5335
+ }
5336
+ this.collapse.delete(collapseKey);
5337
+ if (this.collapse.size >= this.opts.collapseMaxKeys) {
5338
+ const oldest = this.collapse.keys().next().value;
5339
+ if (oldest !== void 0) this.collapse.delete(oldest);
5340
+ }
5341
+ const fresh = { firstSeenAt: now };
5342
+ this.collapse.set(collapseKey, fresh);
5343
+ fresh.first = this.push(dest, item, now);
5344
+ return;
5345
+ }
5346
+ this.push(dest, item, now);
5347
+ }
5348
+ /**
5349
+ * Deliver every eligible queued item. Concurrent callers share ONE
5350
+ * in-flight drain (it survives the caller's budget and a Lambda freeze),
5351
+ * so nothing in flight is ever sent twice; items queued while it runs are
5352
+ * picked up by the same drain.
5353
+ */
5354
+ flush() {
5355
+ if (!this.inFlight) {
5356
+ this.inFlight = this.drain().finally(() => {
5357
+ this.inFlight = void 0;
5358
+ });
5359
+ }
5360
+ return this.inFlight;
5361
+ }
5362
+ /** Counters for tests and diagnostics. */
5363
+ stats() {
5364
+ return {
5365
+ queueDepth: this.queue.length,
5366
+ droppedSinceLast: this.droppedSinceLast,
5367
+ droppedTotal: this.droppedTotal,
5368
+ deliveredTotal: this.deliveredTotal,
5369
+ flushing: this.inFlight !== void 0
5370
+ };
5371
+ }
5372
+ push(dest, item, eligibleAt) {
5373
+ while (this.queue.length >= this.opts.maxItems) this.dropOldest();
5374
+ const entry = {
5375
+ dest,
5376
+ destKey: destKeyOf(dest),
5377
+ item,
5378
+ attempts: 0,
5379
+ eligibleAt,
5380
+ inFlight: false
5381
+ };
5382
+ this.queue.push(entry);
5383
+ return entry;
5384
+ }
5385
+ dropOldest() {
5386
+ if (this.queue.shift()) this.countDropped(1);
5387
+ }
5388
+ countDropped(n) {
5389
+ this.droppedSinceLast += n;
5390
+ this.droppedTotal += n;
5391
+ }
5392
+ async drain() {
5393
+ for (; ; ) {
5394
+ const now = this.opts.now();
5395
+ if (now < this.backoffUntil) return;
5396
+ const batch = this.takeBatch(now);
5397
+ if (batch.length === 0) return;
5398
+ const ok = await this.send(batch);
5399
+ if (!ok) return;
5400
+ }
5401
+ }
5402
+ /** Oldest eligible item + up to maxBatch-1 more for the same destination. */
5403
+ takeBatch(now) {
5404
+ const first = this.queue.find((q) => q.eligibleAt <= now);
5405
+ if (!first) return [];
5406
+ const batch = [];
5407
+ const rest = [];
5408
+ for (const q of this.queue) {
5409
+ if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
5410
+ q.inFlight = true;
5411
+ batch.push(q);
5412
+ } else {
5413
+ rest.push(q);
5414
+ }
5415
+ }
5416
+ this.queue = rest;
5417
+ return batch;
5418
+ }
5419
+ /** POST one batch. Returns false when the drain should pause (backoff). */
5420
+ async send(batch) {
5421
+ const dest = batch[0].dest;
5422
+ const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
5423
+ const reportedDropped = this.droppedSinceLast;
5424
+ const meta = {
5425
+ droppedSinceLast: reportedDropped,
5426
+ queueDepth: this.queue.length,
5427
+ adapter: dest.adapter ?? "edge-core",
5428
+ version: SDK_VERSION
5429
+ };
5430
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
5431
+ const response = await withBudget(
5432
+ sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
5433
+ method: "POST",
5434
+ headers: {
5435
+ "Content-Type": "application/json",
5436
+ ...useKey && { Authorization: `Bearer ${useKey}` }
5437
+ },
5438
+ body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
5439
+ ...controller && { signal: controller.signal }
5440
+ }),
5441
+ this.opts.requestTimeoutMs
5442
+ );
5443
+ if (!response) controller?.abort();
5444
+ const status = response?.status;
5445
+ for (const q of batch) q.inFlight = false;
5446
+ if (status !== void 0 && status >= 200 && status < 300) {
5447
+ this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
5448
+ this.deliveredTotal += batch.length;
5449
+ return true;
5450
+ }
5451
+ if (status === 401 && useKey) {
5452
+ this.rejectedKeys.add(useKey);
5453
+ this.requeue(batch, false);
5454
+ return true;
5455
+ }
5456
+ if (status === 429) {
5457
+ const retryAfter = Number(response?.headers.get("retry-after"));
5458
+ const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
5459
+ this.backoffUntil = this.opts.now() + waitMs;
5460
+ this.requeue(batch, false);
5461
+ return false;
5462
+ }
5463
+ if (status !== void 0 && status >= 400 && status < 500) {
5464
+ this.countDropped(batch.length);
5465
+ return true;
5466
+ }
5467
+ this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
5468
+ this.requeue(batch, true);
5469
+ return false;
5470
+ }
5471
+ /** Put a failed batch back at the head of the queue (order preserved). */
5472
+ requeue(batch, countAttempt) {
5473
+ const keep = [];
5474
+ for (const q of batch) {
5475
+ if (countAttempt) q.attempts += 1;
5476
+ if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
5477
+ else keep.push(q);
5478
+ }
5479
+ this.queue = [...keep, ...this.queue];
5480
+ while (this.queue.length > this.opts.maxItems) this.dropOldest();
5481
+ }
5482
+ };
5483
+ var beaconBuffer = new BeaconBuffer();
5484
+
5191
5485
  // src/adapters/express.ts
5192
5486
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
5193
5487