@astrasyncai/verification-gateway 5.10.0 → 5.12.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 (106) 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 +359 -30
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/express.mjs +359 -30
  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 +168 -41
  11. package/dist/adapters/mcp.js.map +1 -1
  12. package/dist/adapters/mcp.mjs +168 -41
  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 +352 -13
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nextjs.mjs +352 -13
  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 +100 -8
  23. package/dist/adapters/sdk.js.map +1 -1
  24. package/dist/adapters/sdk.mjs +100 -8
  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 +805 -16
  31. package/dist/bin/astrasync-codex-hook.js +805 -16
  32. package/dist/bin/astrasync-guard.js +894 -71
  33. package/dist/bin/astrasync.js +951 -74
  34. package/dist/browser/background.js +284 -7
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +284 -7
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/cli/index.d.mts +8 -8
  39. package/dist/cli/index.d.ts +8 -8
  40. package/dist/cli/index.js +22 -64
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/cli/index.mjs +22 -64
  43. package/dist/cli/index.mjs.map +1 -1
  44. package/dist/codex/index.d.mts +5 -0
  45. package/dist/codex/index.d.ts +5 -0
  46. package/dist/codex/index.js +821 -24
  47. package/dist/codex/index.js.map +1 -1
  48. package/dist/codex/index.mjs +823 -26
  49. package/dist/codex/index.mjs.map +1 -1
  50. package/dist/cursor/extension.d.mts +9 -2
  51. package/dist/cursor/extension.d.ts +9 -2
  52. package/dist/cursor/extension.js +787 -11
  53. package/dist/cursor/extension.js.map +1 -1
  54. package/dist/cursor/extension.mjs +789 -11
  55. package/dist/cursor/extension.mjs.map +1 -1
  56. package/dist/edge-config.d.mts +1 -1
  57. package/dist/edge-config.d.ts +1 -1
  58. package/dist/edge-config.js +1 -1
  59. package/dist/edge-config.js.map +1 -1
  60. package/dist/edge-config.mjs +1 -1
  61. package/dist/edge-config.mjs.map +1 -1
  62. package/dist/edge-core/index.d.mts +128 -13
  63. package/dist/edge-core/index.d.ts +128 -13
  64. package/dist/edge-core/index.js +337 -55
  65. package/dist/edge-core/index.js.map +1 -1
  66. package/dist/edge-core/index.mjs +334 -55
  67. package/dist/edge-core/index.mjs.map +1 -1
  68. package/dist/enrolment-store-8DyaIOLe.d.mts +26 -0
  69. package/dist/enrolment-store-8DyaIOLe.d.ts +26 -0
  70. package/dist/gateway/gateway.js +284 -7
  71. package/dist/gateway/gateway.js.map +1 -1
  72. package/dist/gateway/gateway.mjs +284 -7
  73. package/dist/gateway/gateway.mjs.map +1 -1
  74. package/dist/git-trigger/git-hooks.d.mts +1 -1
  75. package/dist/git-trigger/git-hooks.d.ts +1 -1
  76. package/dist/index.d.mts +215 -5
  77. package/dist/index.d.ts +215 -5
  78. package/dist/index.js +3756 -80
  79. package/dist/index.js.map +1 -1
  80. package/dist/index.mjs +3748 -80
  81. package/dist/index.mjs.map +1 -1
  82. package/dist/platform-signatures.js +4 -1
  83. package/dist/platform-signatures.js.map +1 -1
  84. package/dist/platform-signatures.mjs +4 -1
  85. package/dist/platform-signatures.mjs.map +1 -1
  86. package/dist/registration/index.js +1 -1
  87. package/dist/registration/index.js.map +1 -1
  88. package/dist/registration/index.mjs +1 -1
  89. package/dist/registration/index.mjs.map +1 -1
  90. package/dist/transport/index.js +1 -1
  91. package/dist/transport/index.js.map +1 -1
  92. package/dist/transport/index.mjs +1 -1
  93. package/dist/transport/index.mjs.map +1 -1
  94. package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
  95. package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
  96. package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
  97. package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
  98. package/dist/ui/index.d.mts +1 -1
  99. package/dist/ui/index.d.ts +1 -1
  100. package/dist/verify.d.mts +35 -4
  101. package/dist/verify.d.ts +35 -4
  102. package/dist/verify.js +130 -16
  103. package/dist/verify.js.map +1 -1
  104. package/dist/verify.mjs +129 -16
  105. package/dist/verify.mjs.map +1 -1
  106. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var SDK_VERSION = "5.10.0";
2
+ var SDK_VERSION = "5.12.0";
3
3
 
4
4
  // src/http.ts
5
5
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -144,6 +144,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
144
144
  return cache.get(apiBaseUrl)?.data;
145
145
  }
146
146
 
147
+ // src/failure-posture.ts
148
+ var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
149
+ var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
150
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
151
+ "identity.invalid_agent_id",
152
+ "identity.unknown_agent",
153
+ "agent.lookup"
154
+ ]);
155
+ 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.";
156
+ var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
157
+ "verify_access.api_error",
158
+ "middleware.internal_error"
159
+ ]);
160
+ function isVerificationUnavailable(result) {
161
+ if (result.misconfigured) return false;
162
+ return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
163
+ }
164
+ function unavailableRetryAfterSeconds(result) {
165
+ if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
166
+ return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
167
+ }
168
+ return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
169
+ }
170
+ function isAnonymousRead(method, credentials) {
171
+ const m = (method ?? "").toUpperCase();
172
+ if (m !== "GET" && m !== "HEAD") return false;
173
+ return !credentials.astraId && !credentials.jwt;
174
+ }
175
+ function buildVerificationUnavailableBody(result) {
176
+ return {
177
+ success: false,
178
+ error: {
179
+ code: "VERIFICATION_UNAVAILABLE",
180
+ message: "Agent verification is temporarily unavailable and this path enforces verification. Your identity was not evaluated and no denial was recorded \u2014 retry after the Retry-After interval.",
181
+ retryable: true,
182
+ retryAfterSeconds: unavailableRetryAfterSeconds(result),
183
+ ...result.failures && { failures: result.failures },
184
+ ...result.correlationId && { correlationId: result.correlationId },
185
+ guidance: {
186
+ message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
187
+ }
188
+ }
189
+ };
190
+ }
191
+ function isRejectedIdentityClaim(result) {
192
+ return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
193
+ }
194
+ function denyStatusFor(result, policyDeniedCode) {
195
+ if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
196
+ if (result.identityVerified) return { status: 403, code: policyDeniedCode };
197
+ return { status: 401, code: "UNAUTHORIZED" };
198
+ }
199
+
147
200
  // src/verify.ts
148
201
  var DEFAULT_CONFIG = {
149
202
  apiBaseUrl: "https://astrasync.ai/api",
@@ -389,11 +442,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
389
442
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
390
443
  // deterministic and intentionally NOT marked retryable.
391
444
  ...isApiError && { retryable: true },
445
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
446
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
392
447
  correlationId: options.correlationId,
393
448
  attemptId: options.attemptId,
394
449
  verifiedAt: /* @__PURE__ */ new Date()
395
450
  };
396
451
  }
452
+ async function readJsonSafe(response) {
453
+ try {
454
+ const parsed = await response.json();
455
+ return parsed && typeof parsed === "object" ? parsed : {};
456
+ } catch {
457
+ return {};
458
+ }
459
+ }
460
+ function rejectedIdentityGuidance(failures) {
461
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
462
+ if (!f) return void 0;
463
+ if (f.dimension === "agent.lookup") {
464
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
465
+ }
466
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
467
+ }
468
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
469
+ function parseRetryAfterMs(response) {
470
+ const raw = response.headers?.get?.("retry-after");
471
+ if (!raw) return void 0;
472
+ const trimmed = raw.trim();
473
+ let ms;
474
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
475
+ ms = Number(trimmed) * 1e3;
476
+ } else {
477
+ const at = Date.parse(trimmed);
478
+ if (Number.isNaN(at)) return void 0;
479
+ ms = at - Date.now();
480
+ }
481
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
482
+ }
397
483
  async function callVerifyAccessAPI(config, request) {
398
484
  const { credentials, ...requestData } = request;
399
485
  const body = {
@@ -450,7 +536,8 @@ async function callVerifyAccessAPI(config, request) {
450
536
  body: JSON.stringify(body),
451
537
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
452
538
  });
453
- const data = await response.json();
539
+ const data = await readJsonSafe(response);
540
+ const retryAfterMs = parseRetryAfterMs(response);
454
541
  if (response.status === 410) {
455
542
  return {
456
543
  success: true,
@@ -488,6 +575,23 @@ async function callVerifyAccessAPI(config, request) {
488
575
  }
489
576
  };
490
577
  }
578
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
579
+ return {
580
+ success: true,
581
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
582
+ access: {
583
+ allowed: false,
584
+ reason: "unknown_agent",
585
+ failures: [
586
+ {
587
+ dimension: "identity.unknown_agent",
588
+ message: "The presented agent identifier is not registered with AstraSync.",
589
+ guidance: UNKNOWN_AGENT_GUIDANCE
590
+ }
591
+ ]
592
+ }
593
+ };
594
+ }
491
595
  if (!response.ok) {
492
596
  if (response.status === 401) {
493
597
  return {
@@ -502,9 +606,11 @@ async function callVerifyAccessAPI(config, request) {
502
606
  }
503
607
  return {
504
608
  success: false,
505
- error: data.message || data.error || `API returned ${response.status}`,
609
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
506
610
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
507
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
611
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
612
+ httpStatus: response.status,
613
+ ...retryAfterMs !== void 0 && { retryAfterMs }
508
614
  };
509
615
  }
510
616
  return data;
@@ -576,12 +682,14 @@ async function verify(config, request, options) {
576
682
  correlationId: apiResponse.correlationId,
577
683
  attemptId: apiResponse.attemptId,
578
684
  remediation: apiResponse.remediation,
685
+ httpStatus: apiResponse.httpStatus,
686
+ retryAfterMs: apiResponse.retryAfterMs,
579
687
  urls
580
688
  });
581
689
  }
582
690
  if (!apiResponse.access?.allowed) {
583
691
  const aggregatedFailures = apiResponse.access?.failures;
584
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
692
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
585
693
  const result2 = {
586
694
  identityVerified: idVerifiedFromBackend,
587
695
  policyAllowed: false,
@@ -593,7 +701,10 @@ async function verify(config, request, options) {
593
701
  requiresStepUp: apiResponse.access?.requiresStepUp,
594
702
  requiresApproval: apiResponse.access?.requiresApproval,
595
703
  guidance: {
596
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
704
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
705
+ // actionable registration guidance on its failure — surface it
706
+ // instead of the bare machine reason ('unknown_agent').
707
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
597
708
  registrationUrl: urls?.registrationUrl ?? "",
598
709
  documentationUrl: urls?.documentationUrl ?? "",
599
710
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -693,7 +804,7 @@ async function verify(config, request, options) {
693
804
  registrationUrl: urls?.registrationUrl ?? "",
694
805
  documentationUrl: urls?.documentationUrl ?? ""
695
806
  };
696
- } else if (result.recommendation === "step_up_required") {
807
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
697
808
  result.requiresStepUp = true;
698
809
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
699
810
  }
@@ -745,7 +856,10 @@ async function reportUnregisteredAttempt(config, data) {
745
856
  };
746
857
  await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
747
858
  method: "POST",
748
- headers: { "Content-Type": "application/json" },
859
+ headers: {
860
+ "Content-Type": "application/json",
861
+ ...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
862
+ },
749
863
  body: JSON.stringify(body)
750
864
  }).catch(() => {
751
865
  });
@@ -1335,7 +1449,10 @@ var PLATFORM_AGENT_SIGNATURES = [
1335
1449
  {
1336
1450
  vendor: "chatgpt",
1337
1451
  displayName: "OpenAI ChatGPT",
1338
- uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
1452
+ // GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
1453
+ // OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
1454
+ // classified as unattributed crawlers (round-27.4 W5).
1455
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
1339
1456
  agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
1340
1457
  apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
1341
1458
  headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
@@ -1470,6 +1587,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1470
1587
  var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
1471
1588
 
1472
1589
  // src/edge-core/events.ts
1590
+ async function withBudget(promise, ms) {
1591
+ let timer;
1592
+ try {
1593
+ return await Promise.race([
1594
+ promise,
1595
+ new Promise((resolve) => {
1596
+ timer = setTimeout(() => resolve(void 0), ms);
1597
+ })
1598
+ ]);
1599
+ } catch {
1600
+ return void 0;
1601
+ } finally {
1602
+ if (timer) clearTimeout(timer);
1603
+ }
1604
+ }
1605
+ var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
1606
+ var BeaconBuffer = class {
1607
+ constructor(options = {}) {
1608
+ this.queue = [];
1609
+ this.droppedSinceLast = 0;
1610
+ this.droppedTotal = 0;
1611
+ this.deliveredTotal = 0;
1612
+ this.backoffUntil = 0;
1613
+ this.collapse = /* @__PURE__ */ new Map();
1614
+ /** Keys the backend answered 401 for — later batches go unauthenticated. */
1615
+ this.rejectedKeys = /* @__PURE__ */ new Set();
1616
+ this.opts = {
1617
+ maxItems: options.maxItems ?? 1e3,
1618
+ maxBatch: options.maxBatch ?? 100,
1619
+ maxAttempts: options.maxAttempts ?? 3,
1620
+ collapseWindowMs: options.collapseWindowMs ?? 6e4,
1621
+ collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
1622
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
1623
+ retryBackoffMs: options.retryBackoffMs ?? 1e3,
1624
+ now: options.now ?? Date.now
1625
+ };
1626
+ }
1627
+ /**
1628
+ * Queue one beacon. With a `collapseKey`, a repeat inside the window is
1629
+ * merged into a held aggregate (sent when the window closes) instead of
1630
+ * becoming its own item.
1631
+ */
1632
+ enqueue(dest, item, collapseKey) {
1633
+ const now = this.opts.now();
1634
+ if (collapseKey) {
1635
+ const entry = this.collapse.get(collapseKey);
1636
+ if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
1637
+ for (const target of [entry.first, entry.held]) {
1638
+ if (target && !target.inFlight && this.queue.includes(target)) {
1639
+ target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
1640
+ return;
1641
+ }
1642
+ }
1643
+ const held = this.push(
1644
+ dest,
1645
+ { ...item, repeatCount: 1 },
1646
+ entry.firstSeenAt + this.opts.collapseWindowMs
1647
+ );
1648
+ entry.held = held;
1649
+ return;
1650
+ }
1651
+ this.collapse.delete(collapseKey);
1652
+ if (this.collapse.size >= this.opts.collapseMaxKeys) {
1653
+ const oldest = this.collapse.keys().next().value;
1654
+ if (oldest !== void 0) this.collapse.delete(oldest);
1655
+ }
1656
+ const fresh = { firstSeenAt: now };
1657
+ this.collapse.set(collapseKey, fresh);
1658
+ fresh.first = this.push(dest, item, now);
1659
+ return;
1660
+ }
1661
+ this.push(dest, item, now);
1662
+ }
1663
+ /**
1664
+ * Deliver every eligible queued item. Concurrent callers share ONE
1665
+ * in-flight drain (it survives the caller's budget and a Lambda freeze),
1666
+ * so nothing in flight is ever sent twice; items queued while it runs are
1667
+ * picked up by the same drain.
1668
+ */
1669
+ flush() {
1670
+ if (!this.inFlight) {
1671
+ this.inFlight = this.drain().finally(() => {
1672
+ this.inFlight = void 0;
1673
+ });
1674
+ }
1675
+ return this.inFlight;
1676
+ }
1677
+ /** Counters for tests and diagnostics. */
1678
+ stats() {
1679
+ return {
1680
+ queueDepth: this.queue.length,
1681
+ droppedSinceLast: this.droppedSinceLast,
1682
+ droppedTotal: this.droppedTotal,
1683
+ deliveredTotal: this.deliveredTotal,
1684
+ flushing: this.inFlight !== void 0
1685
+ };
1686
+ }
1687
+ push(dest, item, eligibleAt) {
1688
+ while (this.queue.length >= this.opts.maxItems) this.dropOldest();
1689
+ const entry = {
1690
+ dest,
1691
+ destKey: destKeyOf(dest),
1692
+ item,
1693
+ attempts: 0,
1694
+ eligibleAt,
1695
+ inFlight: false
1696
+ };
1697
+ this.queue.push(entry);
1698
+ return entry;
1699
+ }
1700
+ dropOldest() {
1701
+ if (this.queue.shift()) this.countDropped(1);
1702
+ }
1703
+ countDropped(n) {
1704
+ this.droppedSinceLast += n;
1705
+ this.droppedTotal += n;
1706
+ }
1707
+ async drain() {
1708
+ for (; ; ) {
1709
+ const now = this.opts.now();
1710
+ if (now < this.backoffUntil) return;
1711
+ const batch = this.takeBatch(now);
1712
+ if (batch.length === 0) return;
1713
+ const ok = await this.send(batch);
1714
+ if (!ok) return;
1715
+ }
1716
+ }
1717
+ /** Oldest eligible item + up to maxBatch-1 more for the same destination. */
1718
+ takeBatch(now) {
1719
+ const first = this.queue.find((q) => q.eligibleAt <= now);
1720
+ if (!first) return [];
1721
+ const batch = [];
1722
+ const rest = [];
1723
+ for (const q of this.queue) {
1724
+ if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
1725
+ q.inFlight = true;
1726
+ batch.push(q);
1727
+ } else {
1728
+ rest.push(q);
1729
+ }
1730
+ }
1731
+ this.queue = rest;
1732
+ return batch;
1733
+ }
1734
+ /** POST one batch. Returns false when the drain should pause (backoff). */
1735
+ async send(batch) {
1736
+ const dest = batch[0].dest;
1737
+ const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
1738
+ const reportedDropped = this.droppedSinceLast;
1739
+ const meta = {
1740
+ droppedSinceLast: reportedDropped,
1741
+ queueDepth: this.queue.length,
1742
+ adapter: dest.adapter ?? "edge-core",
1743
+ version: SDK_VERSION
1744
+ };
1745
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
1746
+ const response = await withBudget(
1747
+ sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
1748
+ method: "POST",
1749
+ headers: {
1750
+ "Content-Type": "application/json",
1751
+ ...useKey && { Authorization: `Bearer ${useKey}` }
1752
+ },
1753
+ body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
1754
+ ...controller && { signal: controller.signal }
1755
+ }),
1756
+ this.opts.requestTimeoutMs
1757
+ );
1758
+ if (!response) controller?.abort();
1759
+ const status = response?.status;
1760
+ for (const q of batch) q.inFlight = false;
1761
+ if (status !== void 0 && status >= 200 && status < 300) {
1762
+ this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
1763
+ this.deliveredTotal += batch.length;
1764
+ return true;
1765
+ }
1766
+ if (status === 401 && useKey) {
1767
+ this.rejectedKeys.add(useKey);
1768
+ this.requeue(batch, false);
1769
+ return true;
1770
+ }
1771
+ if (status === 429) {
1772
+ const retryAfter = Number(response?.headers.get("retry-after"));
1773
+ const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
1774
+ this.backoffUntil = this.opts.now() + waitMs;
1775
+ this.requeue(batch, false);
1776
+ return false;
1777
+ }
1778
+ if (status !== void 0 && status >= 400 && status < 500) {
1779
+ this.countDropped(batch.length);
1780
+ return true;
1781
+ }
1782
+ this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
1783
+ this.requeue(batch, true);
1784
+ return false;
1785
+ }
1786
+ /** Put a failed batch back at the head of the queue (order preserved). */
1787
+ requeue(batch, countAttempt) {
1788
+ const keep = [];
1789
+ for (const q of batch) {
1790
+ if (countAttempt) q.attempts += 1;
1791
+ if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
1792
+ else keep.push(q);
1793
+ }
1794
+ this.queue = [...keep, ...this.queue];
1795
+ while (this.queue.length > this.opts.maxItems) this.dropOldest();
1796
+ }
1797
+ };
1798
+ var beaconBuffer = new BeaconBuffer();
1473
1799
  function sampled(rate, random = Math.random) {
1474
1800
  if (rate <= 0) return false;
1475
1801
  if (rate >= 1) return true;
@@ -1646,12 +1972,19 @@ function defaultOnDenied(result, _req, res) {
1646
1972
  });
1647
1973
  return;
1648
1974
  }
1649
- const statusCode = !result.identityVerified ? 401 : 403;
1975
+ if (isVerificationUnavailable(result)) {
1976
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1977
+ res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
1978
+ res.setHeader("Retry-After", String(unavailableRetryAfterSeconds(result)));
1979
+ res.status(503).json(buildVerificationUnavailableBody(result));
1980
+ return;
1981
+ }
1982
+ const { status: statusCode, code } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
1650
1983
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
1651
1984
  res.status(statusCode).json({
1652
1985
  success: false,
1653
1986
  error: {
1654
- code: !result.identityVerified ? "UNAUTHORIZED" : "INSUFFICIENT_ACCESS",
1987
+ code,
1655
1988
  message: result.denialReasons?.[0] || "Access denied",
1656
1989
  guidance: result.guidance,
1657
1990
  // Round-10: aggregated per-dimension detail + correlation handle.
@@ -1899,6 +2232,21 @@ function createMiddleware(options) {
1899
2232
  result.stepUpOutcome = outcome;
1900
2233
  }
1901
2234
  }
2235
+ if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isAnonymousRead(req.method, credentials))) {
2236
+ res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
2237
+ return next();
2238
+ }
2239
+ if (!shouldEnforce) {
2240
+ if (config.setPassThroughHeader) {
2241
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
2242
+ res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
2243
+ }
2244
+ if (shouldRecordDecisions && sessionId) {
2245
+ recordDecision(config, sessionId, "granted").catch(() => {
2246
+ });
2247
+ }
2248
+ return next();
2249
+ }
1902
2250
  if (!result.policyAllowed) {
1903
2251
  if (shouldRecordDecisions && sessionId) {
1904
2252
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -1918,17 +2266,6 @@ function createMiddleware(options) {
1918
2266
  onDenied(result, req, res);
1919
2267
  return;
1920
2268
  }
1921
- if (!shouldEnforce) {
1922
- if (config.setPassThroughHeader) {
1923
- res.setHeader("X-Astra-Gateway-Mode", "enforced");
1924
- res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
1925
- }
1926
- if (shouldRecordDecisions && sessionId) {
1927
- recordDecision(config, sessionId, "granted").catch(() => {
1928
- });
1929
- }
1930
- return next();
1931
- }
1932
2269
  if (routeConfig.minTrustScore && result.agent) {
1933
2270
  if (result.agent.trustScore < routeConfig.minTrustScore) {
1934
2271
  const trustFailure = {
@@ -1984,14 +2321,6 @@ function createMiddleware(options) {
1984
2321
  verifiedAt: /* @__PURE__ */ new Date(),
1985
2322
  correlationId
1986
2323
  };
1987
- const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
1988
- if (catchUrls) {
1989
- result.guidance = {
1990
- message: `Middleware threw ${errorClass} \u2014 failing closed`,
1991
- registrationUrl: catchUrls.registrationUrl,
1992
- documentationUrl: catchUrls.documentationUrl
1993
- };
1994
- }
1995
2324
  dedupeFailures(result);
1996
2325
  return onDenied(result, req, res);
1997
2326
  }