@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
@@ -4006,7 +4006,7 @@ function getTrustLevel(score) {
4006
4006
  }
4007
4007
 
4008
4008
  // src/version.ts
4009
- var SDK_VERSION = "5.11.0";
4009
+ var SDK_VERSION = "5.13.0";
4010
4010
 
4011
4011
  // src/http.ts
4012
4012
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -4033,6 +4033,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
4033
4033
  return cache.get(apiBaseUrl)?.data;
4034
4034
  }
4035
4035
 
4036
+ // src/failure-posture.ts
4037
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
4038
+ "identity.invalid_agent_id",
4039
+ "identity.unknown_agent",
4040
+ "agent.lookup"
4041
+ ]);
4042
+ 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.";
4043
+
4036
4044
  // src/verify.ts
4037
4045
  var DEFAULT_CONFIG = {
4038
4046
  apiBaseUrl: "https://astrasync.ai/api",
@@ -4045,7 +4053,7 @@ var DEFAULT_CONFIG = {
4045
4053
  };
4046
4054
  var initCheckPerformed = false;
4047
4055
  var deprecationWarningShown = false;
4048
- async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4056
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
4049
4057
  initCheckPerformed = true;
4050
4058
  try {
4051
4059
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -4063,7 +4071,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4063
4071
  );
4064
4072
  }
4065
4073
  if (apiKey) {
4066
- const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
4074
+ const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
4075
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
4067
4076
  headers: { Authorization: `Bearer ${apiKey}` }
4068
4077
  });
4069
4078
  if (selfCheck.status === 401) {
@@ -4075,6 +4084,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4075
4084
  console.error(message);
4076
4085
  } else if (selfCheck.ok) {
4077
4086
  const body = await selfCheck.json().catch(() => ({}));
4087
+ if (body.counterparty && body.counterparty.canVerify === false) {
4088
+ 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.`;
4089
+ if (strictInit) {
4090
+ throw new Error(`${message} (strictInit=true)`);
4091
+ }
4092
+ console.error(message);
4093
+ }
4078
4094
  if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
4079
4095
  console.warn(
4080
4096
  `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
@@ -4179,11 +4195,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
4179
4195
  const isMisconfig = source === "merchant_misconfig";
4180
4196
  const urls = options.urls;
4181
4197
  const guidance = isMisconfig ? {
4182
- 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.",
4198
+ 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.",
4183
4199
  registrationUrl: urls?.registrationUrl ?? "",
4184
4200
  documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
4185
4201
  steps: [
4186
- "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
4202
+ "Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
4187
4203
  "Do not retry: the failure is deterministic until the operator updates their key"
4188
4204
  ]
4189
4205
  } : isApiError ? {
@@ -4244,11 +4260,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
4244
4260
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
4245
4261
  // deterministic and intentionally NOT marked retryable.
4246
4262
  ...isApiError && { retryable: true },
4263
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
4264
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
4247
4265
  correlationId: options.correlationId,
4248
4266
  attemptId: options.attemptId,
4249
4267
  verifiedAt: /* @__PURE__ */ new Date()
4250
4268
  };
4251
4269
  }
4270
+ async function readJsonSafe(response) {
4271
+ try {
4272
+ const parsed = await response.json();
4273
+ return parsed && typeof parsed === "object" ? parsed : {};
4274
+ } catch {
4275
+ return {};
4276
+ }
4277
+ }
4278
+ function rejectedIdentityGuidance(failures) {
4279
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
4280
+ if (!f) return void 0;
4281
+ if (f.dimension === "agent.lookup") {
4282
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
4283
+ }
4284
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
4285
+ }
4286
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
4287
+ function parseRetryAfterMs(response) {
4288
+ const raw = response.headers?.get?.("retry-after");
4289
+ if (!raw) return void 0;
4290
+ const trimmed = raw.trim();
4291
+ let ms;
4292
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
4293
+ ms = Number(trimmed) * 1e3;
4294
+ } else {
4295
+ const at = Date.parse(trimmed);
4296
+ if (Number.isNaN(at)) return void 0;
4297
+ ms = at - Date.now();
4298
+ }
4299
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
4300
+ }
4252
4301
  async function callVerifyAccessAPI(config, request) {
4253
4302
  const { credentials, ...requestData } = request;
4254
4303
  const body = {
@@ -4305,7 +4354,8 @@ async function callVerifyAccessAPI(config, request) {
4305
4354
  body: JSON.stringify(body),
4306
4355
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
4307
4356
  });
4308
- const data = await response.json();
4357
+ const data = await readJsonSafe(response);
4358
+ const retryAfterMs = parseRetryAfterMs(response);
4309
4359
  if (response.status === 410) {
4310
4360
  return {
4311
4361
  success: true,
@@ -4343,13 +4393,31 @@ async function callVerifyAccessAPI(config, request) {
4343
4393
  }
4344
4394
  };
4345
4395
  }
4396
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
4397
+ return {
4398
+ success: true,
4399
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4400
+ access: {
4401
+ allowed: false,
4402
+ reason: "unknown_agent",
4403
+ failures: [
4404
+ {
4405
+ dimension: "identity.unknown_agent",
4406
+ message: "The presented agent identifier is not registered with AstraSync.",
4407
+ guidance: UNKNOWN_AGENT_GUIDANCE
4408
+ }
4409
+ ]
4410
+ }
4411
+ };
4412
+ }
4346
4413
  if (!response.ok) {
4347
- if (response.status === 401) {
4414
+ const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
4415
+ if (response.status === 401 || keyMismatch) {
4348
4416
  return {
4349
4417
  success: false,
4350
4418
  error: data.message || data.error || "Integration API key rejected",
4351
4419
  misconfig: true,
4352
- misconfigCode: typeof data?.code === "string" ? data.code : void 0,
4420
+ misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
4353
4421
  remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
4354
4422
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4355
4423
  attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
@@ -4357,9 +4425,11 @@ async function callVerifyAccessAPI(config, request) {
4357
4425
  }
4358
4426
  return {
4359
4427
  success: false,
4360
- error: data.message || data.error || `API returned ${response.status}`,
4428
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
4361
4429
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4362
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
4430
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4431
+ httpStatus: response.status,
4432
+ ...retryAfterMs !== void 0 && { retryAfterMs }
4363
4433
  };
4364
4434
  }
4365
4435
  return data;
@@ -4380,14 +4450,22 @@ async function verify(config, request, options) {
4380
4450
  mergedConfig.apiBaseUrl,
4381
4451
  mergedConfig.debug,
4382
4452
  true,
4383
- mergedConfig.apiKey
4453
+ mergedConfig.apiKey,
4454
+ {
4455
+ id: mergedConfig.counterpartyId,
4456
+ url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
4457
+ }
4384
4458
  );
4385
4459
  } else {
4386
4460
  void performInitCheck(
4387
4461
  mergedConfig.apiBaseUrl,
4388
4462
  mergedConfig.debug,
4389
4463
  false,
4390
- mergedConfig.apiKey
4464
+ mergedConfig.apiKey,
4465
+ {
4466
+ id: mergedConfig.counterpartyId,
4467
+ url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
4468
+ }
4391
4469
  );
4392
4470
  }
4393
4471
  }
@@ -4431,12 +4509,14 @@ async function verify(config, request, options) {
4431
4509
  correlationId: apiResponse.correlationId,
4432
4510
  attemptId: apiResponse.attemptId,
4433
4511
  remediation: apiResponse.remediation,
4512
+ httpStatus: apiResponse.httpStatus,
4513
+ retryAfterMs: apiResponse.retryAfterMs,
4434
4514
  urls
4435
4515
  });
4436
4516
  }
4437
4517
  if (!apiResponse.access?.allowed) {
4438
4518
  const aggregatedFailures = apiResponse.access?.failures;
4439
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
4519
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
4440
4520
  const result2 = {
4441
4521
  identityVerified: idVerifiedFromBackend,
4442
4522
  policyAllowed: false,
@@ -4448,7 +4528,10 @@ async function verify(config, request, options) {
4448
4528
  requiresStepUp: apiResponse.access?.requiresStepUp,
4449
4529
  requiresApproval: apiResponse.access?.requiresApproval,
4450
4530
  guidance: {
4451
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
4531
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
4532
+ // actionable registration guidance on its failure — surface it
4533
+ // instead of the bare machine reason ('unknown_agent').
4534
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
4452
4535
  registrationUrl: urls?.registrationUrl ?? "",
4453
4536
  documentationUrl: urls?.documentationUrl ?? "",
4454
4537
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -4548,7 +4631,7 @@ async function verify(config, request, options) {
4548
4631
  registrationUrl: urls?.registrationUrl ?? "",
4549
4632
  documentationUrl: urls?.documentationUrl ?? ""
4550
4633
  };
4551
- } else if (result.recommendation === "step_up_required") {
4634
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
4552
4635
  result.requiresStepUp = true;
4553
4636
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
4554
4637
  }
@@ -4577,6 +4660,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
4577
4660
  var DEFAULT_TTL_MS = 60 * 1e3;
4578
4661
  var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
4579
4662
 
4663
+ // src/edge-core/events.ts
4664
+ async function withBudget(promise, ms) {
4665
+ let timer;
4666
+ try {
4667
+ return await Promise.race([
4668
+ promise,
4669
+ new Promise((resolve) => {
4670
+ timer = setTimeout(() => resolve(void 0), ms);
4671
+ })
4672
+ ]);
4673
+ } catch {
4674
+ return void 0;
4675
+ } finally {
4676
+ if (timer) clearTimeout(timer);
4677
+ }
4678
+ }
4679
+ var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
4680
+ var BeaconBuffer = class {
4681
+ constructor(options = {}) {
4682
+ this.queue = [];
4683
+ this.droppedSinceLast = 0;
4684
+ this.droppedTotal = 0;
4685
+ this.deliveredTotal = 0;
4686
+ this.backoffUntil = 0;
4687
+ this.collapse = /* @__PURE__ */ new Map();
4688
+ /** Keys the backend answered 401 for — later batches go unauthenticated. */
4689
+ this.rejectedKeys = /* @__PURE__ */ new Set();
4690
+ this.opts = {
4691
+ maxItems: options.maxItems ?? 1e3,
4692
+ maxBatch: options.maxBatch ?? 100,
4693
+ maxAttempts: options.maxAttempts ?? 3,
4694
+ collapseWindowMs: options.collapseWindowMs ?? 6e4,
4695
+ collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
4696
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
4697
+ retryBackoffMs: options.retryBackoffMs ?? 1e3,
4698
+ now: options.now ?? Date.now
4699
+ };
4700
+ }
4701
+ /**
4702
+ * Queue one beacon. With a `collapseKey`, a repeat inside the window is
4703
+ * merged into a held aggregate (sent when the window closes) instead of
4704
+ * becoming its own item.
4705
+ */
4706
+ enqueue(dest, item, collapseKey) {
4707
+ const now = this.opts.now();
4708
+ if (collapseKey) {
4709
+ const entry = this.collapse.get(collapseKey);
4710
+ if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
4711
+ for (const target of [entry.first, entry.held]) {
4712
+ if (target && !target.inFlight && this.queue.includes(target)) {
4713
+ target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
4714
+ return;
4715
+ }
4716
+ }
4717
+ const held = this.push(
4718
+ dest,
4719
+ { ...item, repeatCount: 1 },
4720
+ entry.firstSeenAt + this.opts.collapseWindowMs
4721
+ );
4722
+ entry.held = held;
4723
+ return;
4724
+ }
4725
+ this.collapse.delete(collapseKey);
4726
+ if (this.collapse.size >= this.opts.collapseMaxKeys) {
4727
+ const oldest = this.collapse.keys().next().value;
4728
+ if (oldest !== void 0) this.collapse.delete(oldest);
4729
+ }
4730
+ const fresh = { firstSeenAt: now };
4731
+ this.collapse.set(collapseKey, fresh);
4732
+ fresh.first = this.push(dest, item, now);
4733
+ return;
4734
+ }
4735
+ this.push(dest, item, now);
4736
+ }
4737
+ /**
4738
+ * Deliver every eligible queued item. Concurrent callers share ONE
4739
+ * in-flight drain (it survives the caller's budget and a Lambda freeze),
4740
+ * so nothing in flight is ever sent twice; items queued while it runs are
4741
+ * picked up by the same drain.
4742
+ */
4743
+ flush() {
4744
+ if (!this.inFlight) {
4745
+ this.inFlight = this.drain().finally(() => {
4746
+ this.inFlight = void 0;
4747
+ });
4748
+ }
4749
+ return this.inFlight;
4750
+ }
4751
+ /** Counters for tests and diagnostics. */
4752
+ stats() {
4753
+ return {
4754
+ queueDepth: this.queue.length,
4755
+ droppedSinceLast: this.droppedSinceLast,
4756
+ droppedTotal: this.droppedTotal,
4757
+ deliveredTotal: this.deliveredTotal,
4758
+ flushing: this.inFlight !== void 0
4759
+ };
4760
+ }
4761
+ push(dest, item, eligibleAt) {
4762
+ while (this.queue.length >= this.opts.maxItems) this.dropOldest();
4763
+ const entry = {
4764
+ dest,
4765
+ destKey: destKeyOf(dest),
4766
+ item,
4767
+ attempts: 0,
4768
+ eligibleAt,
4769
+ inFlight: false
4770
+ };
4771
+ this.queue.push(entry);
4772
+ return entry;
4773
+ }
4774
+ dropOldest() {
4775
+ if (this.queue.shift()) this.countDropped(1);
4776
+ }
4777
+ countDropped(n) {
4778
+ this.droppedSinceLast += n;
4779
+ this.droppedTotal += n;
4780
+ }
4781
+ async drain() {
4782
+ for (; ; ) {
4783
+ const now = this.opts.now();
4784
+ if (now < this.backoffUntil) return;
4785
+ const batch = this.takeBatch(now);
4786
+ if (batch.length === 0) return;
4787
+ const ok = await this.send(batch);
4788
+ if (!ok) return;
4789
+ }
4790
+ }
4791
+ /** Oldest eligible item + up to maxBatch-1 more for the same destination. */
4792
+ takeBatch(now) {
4793
+ const first = this.queue.find((q) => q.eligibleAt <= now);
4794
+ if (!first) return [];
4795
+ const batch = [];
4796
+ const rest = [];
4797
+ for (const q of this.queue) {
4798
+ if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
4799
+ q.inFlight = true;
4800
+ batch.push(q);
4801
+ } else {
4802
+ rest.push(q);
4803
+ }
4804
+ }
4805
+ this.queue = rest;
4806
+ return batch;
4807
+ }
4808
+ /** POST one batch. Returns false when the drain should pause (backoff). */
4809
+ async send(batch) {
4810
+ const dest = batch[0].dest;
4811
+ const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
4812
+ const reportedDropped = this.droppedSinceLast;
4813
+ const meta = {
4814
+ droppedSinceLast: reportedDropped,
4815
+ queueDepth: this.queue.length,
4816
+ adapter: dest.adapter ?? "edge-core",
4817
+ version: SDK_VERSION
4818
+ };
4819
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
4820
+ const response = await withBudget(
4821
+ sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
4822
+ method: "POST",
4823
+ headers: {
4824
+ "Content-Type": "application/json",
4825
+ ...useKey && { Authorization: `Bearer ${useKey}` }
4826
+ },
4827
+ body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
4828
+ ...controller && { signal: controller.signal }
4829
+ }),
4830
+ this.opts.requestTimeoutMs
4831
+ );
4832
+ if (!response) controller?.abort();
4833
+ const status = response?.status;
4834
+ for (const q of batch) q.inFlight = false;
4835
+ if (status !== void 0 && status >= 200 && status < 300) {
4836
+ this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
4837
+ this.deliveredTotal += batch.length;
4838
+ return true;
4839
+ }
4840
+ if (status === 401 && useKey) {
4841
+ this.rejectedKeys.add(useKey);
4842
+ this.requeue(batch, false);
4843
+ return true;
4844
+ }
4845
+ if (status === 429) {
4846
+ const retryAfter = Number(response?.headers.get("retry-after"));
4847
+ const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
4848
+ this.backoffUntil = this.opts.now() + waitMs;
4849
+ this.requeue(batch, false);
4850
+ return false;
4851
+ }
4852
+ if (status !== void 0 && status >= 400 && status < 500) {
4853
+ this.countDropped(batch.length);
4854
+ return true;
4855
+ }
4856
+ this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
4857
+ this.requeue(batch, true);
4858
+ return false;
4859
+ }
4860
+ /** Put a failed batch back at the head of the queue (order preserved). */
4861
+ requeue(batch, countAttempt) {
4862
+ const keep = [];
4863
+ for (const q of batch) {
4864
+ if (countAttempt) q.attempts += 1;
4865
+ if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
4866
+ else keep.push(q);
4867
+ }
4868
+ this.queue = [...keep, ...this.queue];
4869
+ while (this.queue.length > this.opts.maxItems) this.dropOldest();
4870
+ }
4871
+ };
4872
+ var beaconBuffer = new BeaconBuffer();
4873
+
4580
4874
  // src/adapters/express.ts
4581
4875
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
4582
4876