@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
@@ -3752,7 +3752,7 @@ function getTrustLevel(score) {
3752
3752
  }
3753
3753
 
3754
3754
  // src/version.ts
3755
- var SDK_VERSION = "5.11.0";
3755
+ var SDK_VERSION = "5.13.0";
3756
3756
 
3757
3757
  // src/http.ts
3758
3758
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -3779,6 +3779,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
3779
3779
  return cache.get(apiBaseUrl)?.data;
3780
3780
  }
3781
3781
 
3782
+ // src/failure-posture.ts
3783
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
3784
+ "identity.invalid_agent_id",
3785
+ "identity.unknown_agent",
3786
+ "agent.lookup"
3787
+ ]);
3788
+ 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.";
3789
+
3782
3790
  // src/verify.ts
3783
3791
  var DEFAULT_CONFIG = {
3784
3792
  apiBaseUrl: "https://astrasync.ai/api",
@@ -3791,7 +3799,7 @@ var DEFAULT_CONFIG = {
3791
3799
  };
3792
3800
  var initCheckPerformed = false;
3793
3801
  var deprecationWarningShown = false;
3794
- async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
3802
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
3795
3803
  initCheckPerformed = true;
3796
3804
  try {
3797
3805
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -3809,7 +3817,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
3809
3817
  );
3810
3818
  }
3811
3819
  if (apiKey) {
3812
- const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
3820
+ const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
3821
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
3813
3822
  headers: { Authorization: `Bearer ${apiKey}` }
3814
3823
  });
3815
3824
  if (selfCheck.status === 401) {
@@ -3821,6 +3830,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
3821
3830
  console.error(message);
3822
3831
  } else if (selfCheck.ok) {
3823
3832
  const body = await selfCheck.json().catch(() => ({}));
3833
+ if (body.counterparty && body.counterparty.canVerify === false) {
3834
+ 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.`;
3835
+ if (strictInit) {
3836
+ throw new Error(`${message} (strictInit=true)`);
3837
+ }
3838
+ console.error(message);
3839
+ }
3824
3840
  if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
3825
3841
  console.warn(
3826
3842
  `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
@@ -3925,11 +3941,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
3925
3941
  const isMisconfig = source === "merchant_misconfig";
3926
3942
  const urls = options.urls;
3927
3943
  const guidance = isMisconfig ? {
3928
- 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.",
3944
+ 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.",
3929
3945
  registrationUrl: urls?.registrationUrl ?? "",
3930
3946
  documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
3931
3947
  steps: [
3932
- "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
3948
+ "Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
3933
3949
  "Do not retry: the failure is deterministic until the operator updates their key"
3934
3950
  ]
3935
3951
  } : isApiError ? {
@@ -3990,11 +4006,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
3990
4006
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
3991
4007
  // deterministic and intentionally NOT marked retryable.
3992
4008
  ...isApiError && { retryable: true },
4009
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
4010
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
3993
4011
  correlationId: options.correlationId,
3994
4012
  attemptId: options.attemptId,
3995
4013
  verifiedAt: /* @__PURE__ */ new Date()
3996
4014
  };
3997
4015
  }
4016
+ async function readJsonSafe(response) {
4017
+ try {
4018
+ const parsed = await response.json();
4019
+ return parsed && typeof parsed === "object" ? parsed : {};
4020
+ } catch {
4021
+ return {};
4022
+ }
4023
+ }
4024
+ function rejectedIdentityGuidance(failures) {
4025
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
4026
+ if (!f) return void 0;
4027
+ if (f.dimension === "agent.lookup") {
4028
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
4029
+ }
4030
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
4031
+ }
4032
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
4033
+ function parseRetryAfterMs(response) {
4034
+ const raw = response.headers?.get?.("retry-after");
4035
+ if (!raw) return void 0;
4036
+ const trimmed = raw.trim();
4037
+ let ms;
4038
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
4039
+ ms = Number(trimmed) * 1e3;
4040
+ } else {
4041
+ const at = Date.parse(trimmed);
4042
+ if (Number.isNaN(at)) return void 0;
4043
+ ms = at - Date.now();
4044
+ }
4045
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
4046
+ }
3998
4047
  async function callVerifyAccessAPI(config, request) {
3999
4048
  const { credentials, ...requestData } = request;
4000
4049
  const body = {
@@ -4051,7 +4100,8 @@ async function callVerifyAccessAPI(config, request) {
4051
4100
  body: JSON.stringify(body),
4052
4101
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
4053
4102
  });
4054
- const data = await response.json();
4103
+ const data = await readJsonSafe(response);
4104
+ const retryAfterMs = parseRetryAfterMs(response);
4055
4105
  if (response.status === 410) {
4056
4106
  return {
4057
4107
  success: true,
@@ -4089,13 +4139,31 @@ async function callVerifyAccessAPI(config, request) {
4089
4139
  }
4090
4140
  };
4091
4141
  }
4142
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
4143
+ return {
4144
+ success: true,
4145
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4146
+ access: {
4147
+ allowed: false,
4148
+ reason: "unknown_agent",
4149
+ failures: [
4150
+ {
4151
+ dimension: "identity.unknown_agent",
4152
+ message: "The presented agent identifier is not registered with AstraSync.",
4153
+ guidance: UNKNOWN_AGENT_GUIDANCE
4154
+ }
4155
+ ]
4156
+ }
4157
+ };
4158
+ }
4092
4159
  if (!response.ok) {
4093
- if (response.status === 401) {
4160
+ const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
4161
+ if (response.status === 401 || keyMismatch) {
4094
4162
  return {
4095
4163
  success: false,
4096
4164
  error: data.message || data.error || "Integration API key rejected",
4097
4165
  misconfig: true,
4098
- misconfigCode: typeof data?.code === "string" ? data.code : void 0,
4166
+ misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
4099
4167
  remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
4100
4168
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4101
4169
  attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
@@ -4103,9 +4171,11 @@ async function callVerifyAccessAPI(config, request) {
4103
4171
  }
4104
4172
  return {
4105
4173
  success: false,
4106
- error: data.message || data.error || `API returned ${response.status}`,
4174
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
4107
4175
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
4108
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
4176
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4177
+ httpStatus: response.status,
4178
+ ...retryAfterMs !== void 0 && { retryAfterMs }
4109
4179
  };
4110
4180
  }
4111
4181
  return data;
@@ -4126,14 +4196,22 @@ async function verify(config, request, options) {
4126
4196
  mergedConfig.apiBaseUrl,
4127
4197
  mergedConfig.debug,
4128
4198
  true,
4129
- mergedConfig.apiKey
4199
+ mergedConfig.apiKey,
4200
+ {
4201
+ id: mergedConfig.counterpartyId,
4202
+ url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
4203
+ }
4130
4204
  );
4131
4205
  } else {
4132
4206
  void performInitCheck(
4133
4207
  mergedConfig.apiBaseUrl,
4134
4208
  mergedConfig.debug,
4135
4209
  false,
4136
- mergedConfig.apiKey
4210
+ mergedConfig.apiKey,
4211
+ {
4212
+ id: mergedConfig.counterpartyId,
4213
+ url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
4214
+ }
4137
4215
  );
4138
4216
  }
4139
4217
  }
@@ -4177,12 +4255,14 @@ async function verify(config, request, options) {
4177
4255
  correlationId: apiResponse.correlationId,
4178
4256
  attemptId: apiResponse.attemptId,
4179
4257
  remediation: apiResponse.remediation,
4258
+ httpStatus: apiResponse.httpStatus,
4259
+ retryAfterMs: apiResponse.retryAfterMs,
4180
4260
  urls
4181
4261
  });
4182
4262
  }
4183
4263
  if (!apiResponse.access?.allowed) {
4184
4264
  const aggregatedFailures = apiResponse.access?.failures;
4185
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
4265
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
4186
4266
  const result2 = {
4187
4267
  identityVerified: idVerifiedFromBackend,
4188
4268
  policyAllowed: false,
@@ -4194,7 +4274,10 @@ async function verify(config, request, options) {
4194
4274
  requiresStepUp: apiResponse.access?.requiresStepUp,
4195
4275
  requiresApproval: apiResponse.access?.requiresApproval,
4196
4276
  guidance: {
4197
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
4277
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
4278
+ // actionable registration guidance on its failure — surface it
4279
+ // instead of the bare machine reason ('unknown_agent').
4280
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
4198
4281
  registrationUrl: urls?.registrationUrl ?? "",
4199
4282
  documentationUrl: urls?.documentationUrl ?? "",
4200
4283
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -4294,7 +4377,7 @@ async function verify(config, request, options) {
4294
4377
  registrationUrl: urls?.registrationUrl ?? "",
4295
4378
  documentationUrl: urls?.documentationUrl ?? ""
4296
4379
  };
4297
- } else if (result.recommendation === "step_up_required") {
4380
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
4298
4381
  result.requiresStepUp = true;
4299
4382
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
4300
4383
  }
@@ -4323,6 +4406,217 @@ var DEFAULT_EDGE_CONFIG = Object.freeze({
4323
4406
  var DEFAULT_TTL_MS = 60 * 1e3;
4324
4407
  var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
4325
4408
 
4409
+ // src/edge-core/events.ts
4410
+ async function withBudget(promise, ms) {
4411
+ let timer;
4412
+ try {
4413
+ return await Promise.race([
4414
+ promise,
4415
+ new Promise((resolve) => {
4416
+ timer = setTimeout(() => resolve(void 0), ms);
4417
+ })
4418
+ ]);
4419
+ } catch {
4420
+ return void 0;
4421
+ } finally {
4422
+ if (timer) clearTimeout(timer);
4423
+ }
4424
+ }
4425
+ var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
4426
+ var BeaconBuffer = class {
4427
+ constructor(options = {}) {
4428
+ this.queue = [];
4429
+ this.droppedSinceLast = 0;
4430
+ this.droppedTotal = 0;
4431
+ this.deliveredTotal = 0;
4432
+ this.backoffUntil = 0;
4433
+ this.collapse = /* @__PURE__ */ new Map();
4434
+ /** Keys the backend answered 401 for — later batches go unauthenticated. */
4435
+ this.rejectedKeys = /* @__PURE__ */ new Set();
4436
+ this.opts = {
4437
+ maxItems: options.maxItems ?? 1e3,
4438
+ maxBatch: options.maxBatch ?? 100,
4439
+ maxAttempts: options.maxAttempts ?? 3,
4440
+ collapseWindowMs: options.collapseWindowMs ?? 6e4,
4441
+ collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
4442
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
4443
+ retryBackoffMs: options.retryBackoffMs ?? 1e3,
4444
+ now: options.now ?? Date.now
4445
+ };
4446
+ }
4447
+ /**
4448
+ * Queue one beacon. With a `collapseKey`, a repeat inside the window is
4449
+ * merged into a held aggregate (sent when the window closes) instead of
4450
+ * becoming its own item.
4451
+ */
4452
+ enqueue(dest, item, collapseKey) {
4453
+ const now = this.opts.now();
4454
+ if (collapseKey) {
4455
+ const entry = this.collapse.get(collapseKey);
4456
+ if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
4457
+ for (const target of [entry.first, entry.held]) {
4458
+ if (target && !target.inFlight && this.queue.includes(target)) {
4459
+ target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
4460
+ return;
4461
+ }
4462
+ }
4463
+ const held = this.push(
4464
+ dest,
4465
+ { ...item, repeatCount: 1 },
4466
+ entry.firstSeenAt + this.opts.collapseWindowMs
4467
+ );
4468
+ entry.held = held;
4469
+ return;
4470
+ }
4471
+ this.collapse.delete(collapseKey);
4472
+ if (this.collapse.size >= this.opts.collapseMaxKeys) {
4473
+ const oldest = this.collapse.keys().next().value;
4474
+ if (oldest !== void 0) this.collapse.delete(oldest);
4475
+ }
4476
+ const fresh = { firstSeenAt: now };
4477
+ this.collapse.set(collapseKey, fresh);
4478
+ fresh.first = this.push(dest, item, now);
4479
+ return;
4480
+ }
4481
+ this.push(dest, item, now);
4482
+ }
4483
+ /**
4484
+ * Deliver every eligible queued item. Concurrent callers share ONE
4485
+ * in-flight drain (it survives the caller's budget and a Lambda freeze),
4486
+ * so nothing in flight is ever sent twice; items queued while it runs are
4487
+ * picked up by the same drain.
4488
+ */
4489
+ flush() {
4490
+ if (!this.inFlight) {
4491
+ this.inFlight = this.drain().finally(() => {
4492
+ this.inFlight = void 0;
4493
+ });
4494
+ }
4495
+ return this.inFlight;
4496
+ }
4497
+ /** Counters for tests and diagnostics. */
4498
+ stats() {
4499
+ return {
4500
+ queueDepth: this.queue.length,
4501
+ droppedSinceLast: this.droppedSinceLast,
4502
+ droppedTotal: this.droppedTotal,
4503
+ deliveredTotal: this.deliveredTotal,
4504
+ flushing: this.inFlight !== void 0
4505
+ };
4506
+ }
4507
+ push(dest, item, eligibleAt) {
4508
+ while (this.queue.length >= this.opts.maxItems) this.dropOldest();
4509
+ const entry = {
4510
+ dest,
4511
+ destKey: destKeyOf(dest),
4512
+ item,
4513
+ attempts: 0,
4514
+ eligibleAt,
4515
+ inFlight: false
4516
+ };
4517
+ this.queue.push(entry);
4518
+ return entry;
4519
+ }
4520
+ dropOldest() {
4521
+ if (this.queue.shift()) this.countDropped(1);
4522
+ }
4523
+ countDropped(n) {
4524
+ this.droppedSinceLast += n;
4525
+ this.droppedTotal += n;
4526
+ }
4527
+ async drain() {
4528
+ for (; ; ) {
4529
+ const now = this.opts.now();
4530
+ if (now < this.backoffUntil) return;
4531
+ const batch = this.takeBatch(now);
4532
+ if (batch.length === 0) return;
4533
+ const ok = await this.send(batch);
4534
+ if (!ok) return;
4535
+ }
4536
+ }
4537
+ /** Oldest eligible item + up to maxBatch-1 more for the same destination. */
4538
+ takeBatch(now) {
4539
+ const first = this.queue.find((q) => q.eligibleAt <= now);
4540
+ if (!first) return [];
4541
+ const batch = [];
4542
+ const rest = [];
4543
+ for (const q of this.queue) {
4544
+ if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
4545
+ q.inFlight = true;
4546
+ batch.push(q);
4547
+ } else {
4548
+ rest.push(q);
4549
+ }
4550
+ }
4551
+ this.queue = rest;
4552
+ return batch;
4553
+ }
4554
+ /** POST one batch. Returns false when the drain should pause (backoff). */
4555
+ async send(batch) {
4556
+ const dest = batch[0].dest;
4557
+ const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
4558
+ const reportedDropped = this.droppedSinceLast;
4559
+ const meta = {
4560
+ droppedSinceLast: reportedDropped,
4561
+ queueDepth: this.queue.length,
4562
+ adapter: dest.adapter ?? "edge-core",
4563
+ version: SDK_VERSION
4564
+ };
4565
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
4566
+ const response = await withBudget(
4567
+ sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
4568
+ method: "POST",
4569
+ headers: {
4570
+ "Content-Type": "application/json",
4571
+ ...useKey && { Authorization: `Bearer ${useKey}` }
4572
+ },
4573
+ body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
4574
+ ...controller && { signal: controller.signal }
4575
+ }),
4576
+ this.opts.requestTimeoutMs
4577
+ );
4578
+ if (!response) controller?.abort();
4579
+ const status = response?.status;
4580
+ for (const q of batch) q.inFlight = false;
4581
+ if (status !== void 0 && status >= 200 && status < 300) {
4582
+ this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
4583
+ this.deliveredTotal += batch.length;
4584
+ return true;
4585
+ }
4586
+ if (status === 401 && useKey) {
4587
+ this.rejectedKeys.add(useKey);
4588
+ this.requeue(batch, false);
4589
+ return true;
4590
+ }
4591
+ if (status === 429) {
4592
+ const retryAfter = Number(response?.headers.get("retry-after"));
4593
+ const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
4594
+ this.backoffUntil = this.opts.now() + waitMs;
4595
+ this.requeue(batch, false);
4596
+ return false;
4597
+ }
4598
+ if (status !== void 0 && status >= 400 && status < 500) {
4599
+ this.countDropped(batch.length);
4600
+ return true;
4601
+ }
4602
+ this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
4603
+ this.requeue(batch, true);
4604
+ return false;
4605
+ }
4606
+ /** Put a failed batch back at the head of the queue (order preserved). */
4607
+ requeue(batch, countAttempt) {
4608
+ const keep = [];
4609
+ for (const q of batch) {
4610
+ if (countAttempt) q.attempts += 1;
4611
+ if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
4612
+ else keep.push(q);
4613
+ }
4614
+ this.queue = [...keep, ...this.queue];
4615
+ while (this.queue.length > this.opts.maxItems) this.dropOldest();
4616
+ }
4617
+ };
4618
+ var beaconBuffer = new BeaconBuffer();
4619
+
4326
4620
  // src/adapters/express.ts
4327
4621
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
4328
4622