@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
@@ -3986,7 +3986,7 @@ function getTrustLevel(score) {
3986
3986
  }
3987
3987
 
3988
3988
  // src/version.ts
3989
- var SDK_VERSION = "5.11.0";
3989
+ var SDK_VERSION = "5.13.0";
3990
3990
 
3991
3991
  // src/http.ts
3992
3992
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -4013,6 +4013,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
4013
4013
  return cache.get(apiBaseUrl)?.data;
4014
4014
  }
4015
4015
 
4016
+ // src/failure-posture.ts
4017
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
4018
+ "identity.invalid_agent_id",
4019
+ "identity.unknown_agent",
4020
+ "agent.lookup"
4021
+ ]);
4022
+ 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.";
4023
+
4016
4024
  // src/verify.ts
4017
4025
  var DEFAULT_CONFIG = {
4018
4026
  apiBaseUrl: "https://astrasync.ai/api",
@@ -4025,7 +4033,7 @@ var DEFAULT_CONFIG = {
4025
4033
  };
4026
4034
  var initCheckPerformed = false;
4027
4035
  var deprecationWarningShown = false;
4028
- async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4036
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
4029
4037
  initCheckPerformed = true;
4030
4038
  try {
4031
4039
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -4043,7 +4051,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4043
4051
  );
4044
4052
  }
4045
4053
  if (apiKey) {
4046
- const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
4054
+ const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
4055
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
4047
4056
  headers: { Authorization: `Bearer ${apiKey}` }
4048
4057
  });
4049
4058
  if (selfCheck.status === 401) {
@@ -4055,6 +4064,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
4055
4064
  console.error(message);
4056
4065
  } else if (selfCheck.ok) {
4057
4066
  const body = await selfCheck.json().catch(() => ({}));
4067
+ if (body.counterparty && body.counterparty.canVerify === false) {
4068
+ 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.`;
4069
+ if (strictInit) {
4070
+ throw new Error(`${message} (strictInit=true)`);
4071
+ }
4072
+ console.error(message);
4073
+ }
4058
4074
  if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
4059
4075
  console.warn(
4060
4076
  `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
@@ -4159,11 +4175,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
4159
4175
  const isMisconfig = source === "merchant_misconfig";
4160
4176
  const urls = options.urls;
4161
4177
  const guidance = isMisconfig ? {
4162
- 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.",
4178
+ 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.",
4163
4179
  registrationUrl: urls?.registrationUrl ?? "",
4164
4180
  documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
4165
4181
  steps: [
4166
- "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
4182
+ "Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
4167
4183
  "Do not retry: the failure is deterministic until the operator updates their key"
4168
4184
  ]
4169
4185
  } : isApiError ? {
@@ -4224,11 +4240,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
4224
4240
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
4225
4241
  // deterministic and intentionally NOT marked retryable.
4226
4242
  ...isApiError && { retryable: true },
4243
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
4244
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
4227
4245
  correlationId: options.correlationId,
4228
4246
  attemptId: options.attemptId,
4229
4247
  verifiedAt: /* @__PURE__ */ new Date()
4230
4248
  };
4231
4249
  }
4250
+ async function readJsonSafe(response) {
4251
+ try {
4252
+ const parsed = await response.json();
4253
+ return parsed && typeof parsed === "object" ? parsed : {};
4254
+ } catch {
4255
+ return {};
4256
+ }
4257
+ }
4258
+ function rejectedIdentityGuidance(failures) {
4259
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
4260
+ if (!f) return void 0;
4261
+ if (f.dimension === "agent.lookup") {
4262
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
4263
+ }
4264
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
4265
+ }
4266
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
4267
+ function parseRetryAfterMs(response) {
4268
+ const raw = response.headers?.get?.("retry-after");
4269
+ if (!raw) return void 0;
4270
+ const trimmed = raw.trim();
4271
+ let ms;
4272
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
4273
+ ms = Number(trimmed) * 1e3;
4274
+ } else {
4275
+ const at = Date.parse(trimmed);
4276
+ if (Number.isNaN(at)) return void 0;
4277
+ ms = at - Date.now();
4278
+ }
4279
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
4280
+ }
4232
4281
  async function callVerifyAccessAPI(config, request) {
4233
4282
  const { credentials, ...requestData } = request;
4234
4283
  const body = {
@@ -4285,7 +4334,8 @@ async function callVerifyAccessAPI(config, request) {
4285
4334
  body: JSON.stringify(body),
4286
4335
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
4287
4336
  });
4288
- const data = await response.json();
4337
+ const data = await readJsonSafe(response);
4338
+ const retryAfterMs = parseRetryAfterMs(response);
4289
4339
  if (response.status === 410) {
4290
4340
  return {
4291
4341
  success: true,
@@ -4323,13 +4373,31 @@ async function callVerifyAccessAPI(config, request) {
4323
4373
  }
4324
4374
  };
4325
4375
  }
4376
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
4377
+ return {
4378
+ success: true,
4379
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4380
+ access: {
4381
+ allowed: false,
4382
+ reason: "unknown_agent",
4383
+ failures: [
4384
+ {
4385
+ dimension: "identity.unknown_agent",
4386
+ message: "The presented agent identifier is not registered with AstraSync.",
4387
+ guidance: UNKNOWN_AGENT_GUIDANCE
4388
+ }
4389
+ ]
4390
+ }
4391
+ };
4392
+ }
4326
4393
  if (!response.ok) {
4327
- if (response.status === 401) {
4394
+ const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
4395
+ if (response.status === 401 || keyMismatch) {
4328
4396
  return {
4329
4397
  success: false,
4330
4398
  error: data.message || data.error || "Integration API key rejected",
4331
4399
  misconfig: true,
4332
- misconfigCode: typeof data?.code === "string" ? data.code : void 0,
4400
+ misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
4333
4401
  remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
4334
4402
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4335
4403
  attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
@@ -4337,9 +4405,11 @@ async function callVerifyAccessAPI(config, request) {
4337
4405
  }
4338
4406
  return {
4339
4407
  success: false,
4340
- error: data.message || data.error || `API returned ${response.status}`,
4408
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
4341
4409
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4342
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
4410
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4411
+ httpStatus: response.status,
4412
+ ...retryAfterMs !== void 0 && { retryAfterMs }
4343
4413
  };
4344
4414
  }
4345
4415
  return data;
@@ -4360,14 +4430,22 @@ async function verify(config, request, options) {
4360
4430
  mergedConfig.apiBaseUrl,
4361
4431
  mergedConfig.debug,
4362
4432
  true,
4363
- mergedConfig.apiKey
4433
+ mergedConfig.apiKey,
4434
+ {
4435
+ id: mergedConfig.counterpartyId,
4436
+ url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
4437
+ }
4364
4438
  );
4365
4439
  } else {
4366
4440
  void performInitCheck(
4367
4441
  mergedConfig.apiBaseUrl,
4368
4442
  mergedConfig.debug,
4369
4443
  false,
4370
- mergedConfig.apiKey
4444
+ mergedConfig.apiKey,
4445
+ {
4446
+ id: mergedConfig.counterpartyId,
4447
+ url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
4448
+ }
4371
4449
  );
4372
4450
  }
4373
4451
  }
@@ -4411,12 +4489,14 @@ async function verify(config, request, options) {
4411
4489
  correlationId: apiResponse.correlationId,
4412
4490
  attemptId: apiResponse.attemptId,
4413
4491
  remediation: apiResponse.remediation,
4492
+ httpStatus: apiResponse.httpStatus,
4493
+ retryAfterMs: apiResponse.retryAfterMs,
4414
4494
  urls
4415
4495
  });
4416
4496
  }
4417
4497
  if (!apiResponse.access?.allowed) {
4418
4498
  const aggregatedFailures = apiResponse.access?.failures;
4419
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
4499
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
4420
4500
  const result2 = {
4421
4501
  identityVerified: idVerifiedFromBackend,
4422
4502
  policyAllowed: false,
@@ -4428,7 +4508,10 @@ async function verify(config, request, options) {
4428
4508
  requiresStepUp: apiResponse.access?.requiresStepUp,
4429
4509
  requiresApproval: apiResponse.access?.requiresApproval,
4430
4510
  guidance: {
4431
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
4511
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
4512
+ // actionable registration guidance on its failure — surface it
4513
+ // instead of the bare machine reason ('unknown_agent').
4514
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
4432
4515
  registrationUrl: urls?.registrationUrl ?? "",
4433
4516
  documentationUrl: urls?.documentationUrl ?? "",
4434
4517
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -4528,7 +4611,7 @@ async function verify(config, request, options) {
4528
4611
  registrationUrl: urls?.registrationUrl ?? "",
4529
4612
  documentationUrl: urls?.documentationUrl ?? ""
4530
4613
  };
4531
- } else if (result.recommendation === "step_up_required") {
4614
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
4532
4615
  result.requiresStepUp = true;
4533
4616
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
4534
4617
  }
@@ -4557,6 +4640,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
4557
4640
  var DEFAULT_TTL_MS = 60 * 1e3;
4558
4641
  var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
4559
4642
 
4643
+ // src/edge-core/events.ts
4644
+ async function withBudget(promise, ms) {
4645
+ let timer;
4646
+ try {
4647
+ return await Promise.race([
4648
+ promise,
4649
+ new Promise((resolve) => {
4650
+ timer = setTimeout(() => resolve(void 0), ms);
4651
+ })
4652
+ ]);
4653
+ } catch {
4654
+ return void 0;
4655
+ } finally {
4656
+ if (timer) clearTimeout(timer);
4657
+ }
4658
+ }
4659
+ var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
4660
+ var BeaconBuffer = class {
4661
+ constructor(options = {}) {
4662
+ this.queue = [];
4663
+ this.droppedSinceLast = 0;
4664
+ this.droppedTotal = 0;
4665
+ this.deliveredTotal = 0;
4666
+ this.backoffUntil = 0;
4667
+ this.collapse = /* @__PURE__ */ new Map();
4668
+ /** Keys the backend answered 401 for — later batches go unauthenticated. */
4669
+ this.rejectedKeys = /* @__PURE__ */ new Set();
4670
+ this.opts = {
4671
+ maxItems: options.maxItems ?? 1e3,
4672
+ maxBatch: options.maxBatch ?? 100,
4673
+ maxAttempts: options.maxAttempts ?? 3,
4674
+ collapseWindowMs: options.collapseWindowMs ?? 6e4,
4675
+ collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
4676
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
4677
+ retryBackoffMs: options.retryBackoffMs ?? 1e3,
4678
+ now: options.now ?? Date.now
4679
+ };
4680
+ }
4681
+ /**
4682
+ * Queue one beacon. With a `collapseKey`, a repeat inside the window is
4683
+ * merged into a held aggregate (sent when the window closes) instead of
4684
+ * becoming its own item.
4685
+ */
4686
+ enqueue(dest, item, collapseKey) {
4687
+ const now = this.opts.now();
4688
+ if (collapseKey) {
4689
+ const entry = this.collapse.get(collapseKey);
4690
+ if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
4691
+ for (const target of [entry.first, entry.held]) {
4692
+ if (target && !target.inFlight && this.queue.includes(target)) {
4693
+ target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
4694
+ return;
4695
+ }
4696
+ }
4697
+ const held = this.push(
4698
+ dest,
4699
+ { ...item, repeatCount: 1 },
4700
+ entry.firstSeenAt + this.opts.collapseWindowMs
4701
+ );
4702
+ entry.held = held;
4703
+ return;
4704
+ }
4705
+ this.collapse.delete(collapseKey);
4706
+ if (this.collapse.size >= this.opts.collapseMaxKeys) {
4707
+ const oldest = this.collapse.keys().next().value;
4708
+ if (oldest !== void 0) this.collapse.delete(oldest);
4709
+ }
4710
+ const fresh = { firstSeenAt: now };
4711
+ this.collapse.set(collapseKey, fresh);
4712
+ fresh.first = this.push(dest, item, now);
4713
+ return;
4714
+ }
4715
+ this.push(dest, item, now);
4716
+ }
4717
+ /**
4718
+ * Deliver every eligible queued item. Concurrent callers share ONE
4719
+ * in-flight drain (it survives the caller's budget and a Lambda freeze),
4720
+ * so nothing in flight is ever sent twice; items queued while it runs are
4721
+ * picked up by the same drain.
4722
+ */
4723
+ flush() {
4724
+ if (!this.inFlight) {
4725
+ this.inFlight = this.drain().finally(() => {
4726
+ this.inFlight = void 0;
4727
+ });
4728
+ }
4729
+ return this.inFlight;
4730
+ }
4731
+ /** Counters for tests and diagnostics. */
4732
+ stats() {
4733
+ return {
4734
+ queueDepth: this.queue.length,
4735
+ droppedSinceLast: this.droppedSinceLast,
4736
+ droppedTotal: this.droppedTotal,
4737
+ deliveredTotal: this.deliveredTotal,
4738
+ flushing: this.inFlight !== void 0
4739
+ };
4740
+ }
4741
+ push(dest, item, eligibleAt) {
4742
+ while (this.queue.length >= this.opts.maxItems) this.dropOldest();
4743
+ const entry = {
4744
+ dest,
4745
+ destKey: destKeyOf(dest),
4746
+ item,
4747
+ attempts: 0,
4748
+ eligibleAt,
4749
+ inFlight: false
4750
+ };
4751
+ this.queue.push(entry);
4752
+ return entry;
4753
+ }
4754
+ dropOldest() {
4755
+ if (this.queue.shift()) this.countDropped(1);
4756
+ }
4757
+ countDropped(n) {
4758
+ this.droppedSinceLast += n;
4759
+ this.droppedTotal += n;
4760
+ }
4761
+ async drain() {
4762
+ for (; ; ) {
4763
+ const now = this.opts.now();
4764
+ if (now < this.backoffUntil) return;
4765
+ const batch = this.takeBatch(now);
4766
+ if (batch.length === 0) return;
4767
+ const ok = await this.send(batch);
4768
+ if (!ok) return;
4769
+ }
4770
+ }
4771
+ /** Oldest eligible item + up to maxBatch-1 more for the same destination. */
4772
+ takeBatch(now) {
4773
+ const first = this.queue.find((q) => q.eligibleAt <= now);
4774
+ if (!first) return [];
4775
+ const batch = [];
4776
+ const rest = [];
4777
+ for (const q of this.queue) {
4778
+ if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
4779
+ q.inFlight = true;
4780
+ batch.push(q);
4781
+ } else {
4782
+ rest.push(q);
4783
+ }
4784
+ }
4785
+ this.queue = rest;
4786
+ return batch;
4787
+ }
4788
+ /** POST one batch. Returns false when the drain should pause (backoff). */
4789
+ async send(batch) {
4790
+ const dest = batch[0].dest;
4791
+ const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
4792
+ const reportedDropped = this.droppedSinceLast;
4793
+ const meta = {
4794
+ droppedSinceLast: reportedDropped,
4795
+ queueDepth: this.queue.length,
4796
+ adapter: dest.adapter ?? "edge-core",
4797
+ version: SDK_VERSION
4798
+ };
4799
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
4800
+ const response = await withBudget(
4801
+ sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
4802
+ method: "POST",
4803
+ headers: {
4804
+ "Content-Type": "application/json",
4805
+ ...useKey && { Authorization: `Bearer ${useKey}` }
4806
+ },
4807
+ body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
4808
+ ...controller && { signal: controller.signal }
4809
+ }),
4810
+ this.opts.requestTimeoutMs
4811
+ );
4812
+ if (!response) controller?.abort();
4813
+ const status = response?.status;
4814
+ for (const q of batch) q.inFlight = false;
4815
+ if (status !== void 0 && status >= 200 && status < 300) {
4816
+ this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
4817
+ this.deliveredTotal += batch.length;
4818
+ return true;
4819
+ }
4820
+ if (status === 401 && useKey) {
4821
+ this.rejectedKeys.add(useKey);
4822
+ this.requeue(batch, false);
4823
+ return true;
4824
+ }
4825
+ if (status === 429) {
4826
+ const retryAfter = Number(response?.headers.get("retry-after"));
4827
+ const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
4828
+ this.backoffUntil = this.opts.now() + waitMs;
4829
+ this.requeue(batch, false);
4830
+ return false;
4831
+ }
4832
+ if (status !== void 0 && status >= 400 && status < 500) {
4833
+ this.countDropped(batch.length);
4834
+ return true;
4835
+ }
4836
+ this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
4837
+ this.requeue(batch, true);
4838
+ return false;
4839
+ }
4840
+ /** Put a failed batch back at the head of the queue (order preserved). */
4841
+ requeue(batch, countAttempt) {
4842
+ const keep = [];
4843
+ for (const q of batch) {
4844
+ if (countAttempt) q.attempts += 1;
4845
+ if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
4846
+ else keep.push(q);
4847
+ }
4848
+ this.queue = [...keep, ...this.queue];
4849
+ while (this.queue.length > this.opts.maxItems) this.dropOldest();
4850
+ }
4851
+ };
4852
+ var beaconBuffer = new BeaconBuffer();
4853
+
4560
4854
  // src/adapters/express.ts
4561
4855
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
4562
4856