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