@astrasyncai/verification-gateway 5.11.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 (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 +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 +284 -7
  31. package/dist/bin/astrasync-codex-hook.js +284 -7
  32. package/dist/bin/astrasync-guard.js +284 -7
  33. package/dist/bin/astrasync.js +297 -9
  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.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 +284 -7
  43. package/dist/codex/index.js.map +1 -1
  44. package/dist/codex/index.mjs +284 -7
  45. package/dist/codex/index.mjs.map +1 -1
  46. package/dist/cursor/extension.js +284 -7
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +284 -7
  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 +337 -55
  59. package/dist/edge-core/index.js.map +1 -1
  60. package/dist/edge-core/index.mjs +334 -55
  61. package/dist/edge-core/index.mjs.map +1 -1
  62. package/dist/gateway/gateway.js +284 -7
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/gateway.mjs +284 -7
  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 -5
  69. package/dist/index.d.ts +118 -5
  70. package/dist/index.js +486 -63
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +480 -63
  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 -4
  93. package/dist/verify.d.ts +35 -4
  94. package/dist/verify.js +130 -16
  95. package/dist/verify.js.map +1 -1
  96. package/dist/verify.mjs +129 -16
  97. package/dist/verify.mjs.map +1 -1
  98. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var SDK_VERSION = "5.11.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}`;
@@ -212,7 +212,10 @@ var PLATFORM_AGENT_SIGNATURES = [
212
212
  {
213
213
  vendor: "chatgpt",
214
214
  displayName: "OpenAI ChatGPT",
215
- uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
215
+ // GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
216
+ // OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
217
+ // classified as unattributed crawlers (round-27.4 W5).
218
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
216
219
  agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
217
220
  apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
218
221
  headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
@@ -354,6 +357,14 @@ function getCachedWellKnownUrls(apiBaseUrl) {
354
357
  return cache.get(apiBaseUrl)?.data;
355
358
  }
356
359
 
360
+ // src/failure-posture.ts
361
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
362
+ "identity.invalid_agent_id",
363
+ "identity.unknown_agent",
364
+ "agent.lookup"
365
+ ]);
366
+ 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.";
367
+
357
368
  // src/verify.ts
358
369
  var DEFAULT_CONFIG = {
359
370
  apiBaseUrl: "https://astrasync.ai/api",
@@ -599,11 +610,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
599
610
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
600
611
  // deterministic and intentionally NOT marked retryable.
601
612
  ...isApiError && { retryable: true },
613
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
614
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
602
615
  correlationId: options.correlationId,
603
616
  attemptId: options.attemptId,
604
617
  verifiedAt: /* @__PURE__ */ new Date()
605
618
  };
606
619
  }
620
+ async function readJsonSafe(response) {
621
+ try {
622
+ const parsed = await response.json();
623
+ return parsed && typeof parsed === "object" ? parsed : {};
624
+ } catch {
625
+ return {};
626
+ }
627
+ }
628
+ function rejectedIdentityGuidance(failures) {
629
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
630
+ if (!f) return void 0;
631
+ if (f.dimension === "agent.lookup") {
632
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
633
+ }
634
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
635
+ }
636
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
637
+ function parseRetryAfterMs(response) {
638
+ const raw = response.headers?.get?.("retry-after");
639
+ if (!raw) return void 0;
640
+ const trimmed = raw.trim();
641
+ let ms;
642
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
643
+ ms = Number(trimmed) * 1e3;
644
+ } else {
645
+ const at = Date.parse(trimmed);
646
+ if (Number.isNaN(at)) return void 0;
647
+ ms = at - Date.now();
648
+ }
649
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
650
+ }
607
651
  async function callVerifyAccessAPI(config, request) {
608
652
  const { credentials, ...requestData } = request;
609
653
  const body = {
@@ -660,7 +704,8 @@ async function callVerifyAccessAPI(config, request) {
660
704
  body: JSON.stringify(body),
661
705
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
662
706
  });
663
- const data = await response.json();
707
+ const data = await readJsonSafe(response);
708
+ const retryAfterMs = parseRetryAfterMs(response);
664
709
  if (response.status === 410) {
665
710
  return {
666
711
  success: true,
@@ -698,6 +743,23 @@ async function callVerifyAccessAPI(config, request) {
698
743
  }
699
744
  };
700
745
  }
746
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
747
+ return {
748
+ success: true,
749
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
750
+ access: {
751
+ allowed: false,
752
+ reason: "unknown_agent",
753
+ failures: [
754
+ {
755
+ dimension: "identity.unknown_agent",
756
+ message: "The presented agent identifier is not registered with AstraSync.",
757
+ guidance: UNKNOWN_AGENT_GUIDANCE
758
+ }
759
+ ]
760
+ }
761
+ };
762
+ }
701
763
  if (!response.ok) {
702
764
  if (response.status === 401) {
703
765
  return {
@@ -712,9 +774,11 @@ async function callVerifyAccessAPI(config, request) {
712
774
  }
713
775
  return {
714
776
  success: false,
715
- error: data.message || data.error || `API returned ${response.status}`,
777
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
716
778
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
717
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
779
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
780
+ httpStatus: response.status,
781
+ ...retryAfterMs !== void 0 && { retryAfterMs }
718
782
  };
719
783
  }
720
784
  return data;
@@ -786,12 +850,14 @@ async function verify(config, request, options) {
786
850
  correlationId: apiResponse.correlationId,
787
851
  attemptId: apiResponse.attemptId,
788
852
  remediation: apiResponse.remediation,
853
+ httpStatus: apiResponse.httpStatus,
854
+ retryAfterMs: apiResponse.retryAfterMs,
789
855
  urls
790
856
  });
791
857
  }
792
858
  if (!apiResponse.access?.allowed) {
793
859
  const aggregatedFailures = apiResponse.access?.failures;
794
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
860
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
795
861
  const result2 = {
796
862
  identityVerified: idVerifiedFromBackend,
797
863
  policyAllowed: false,
@@ -803,7 +869,10 @@ async function verify(config, request, options) {
803
869
  requiresStepUp: apiResponse.access?.requiresStepUp,
804
870
  requiresApproval: apiResponse.access?.requiresApproval,
805
871
  guidance: {
806
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
872
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
873
+ // actionable registration guidance on its failure — surface it
874
+ // instead of the bare machine reason ('unknown_agent').
875
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
807
876
  registrationUrl: urls?.registrationUrl ?? "",
808
877
  documentationUrl: urls?.documentationUrl ?? "",
809
878
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -903,7 +972,7 @@ async function verify(config, request, options) {
903
972
  registrationUrl: urls?.registrationUrl ?? "",
904
973
  documentationUrl: urls?.documentationUrl ?? ""
905
974
  };
906
- } else if (result.recommendation === "step_up_required") {
975
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
907
976
  result.requiresStepUp = true;
908
977
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
909
978
  }
@@ -913,21 +982,6 @@ async function verify(config, request, options) {
913
982
  }
914
983
  return result;
915
984
  }
916
- async function reportUnregisteredAttempt(config, data) {
917
- const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
918
- const { counterpartyUrl, ...rest } = data;
919
- const body = {
920
- ...rest,
921
- ...counterpartyUrl && { counterpartyUrl },
922
- ...config.counterpartyId && { counterpartyId: config.counterpartyId }
923
- };
924
- await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
925
- method: "POST",
926
- headers: { "Content-Type": "application/json" },
927
- body: JSON.stringify(body)
928
- }).catch(() => {
929
- });
930
- }
931
985
 
932
986
  // src/edge-core/request.ts
933
987
  function headerValue(req, name) {
@@ -1463,42 +1517,263 @@ async function withBudget(promise, ms) {
1463
1517
  if (timer) clearTimeout(timer);
1464
1518
  }
1465
1519
  }
1466
- async function emitBeacon(options, req, classification, budgetMs, queryValueAllowlist) {
1520
+ var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
1521
+ var BeaconBuffer = class {
1522
+ constructor(options = {}) {
1523
+ this.queue = [];
1524
+ this.droppedSinceLast = 0;
1525
+ this.droppedTotal = 0;
1526
+ this.deliveredTotal = 0;
1527
+ this.backoffUntil = 0;
1528
+ this.collapse = /* @__PURE__ */ new Map();
1529
+ /** Keys the backend answered 401 for — later batches go unauthenticated. */
1530
+ this.rejectedKeys = /* @__PURE__ */ new Set();
1531
+ this.opts = {
1532
+ maxItems: options.maxItems ?? 1e3,
1533
+ maxBatch: options.maxBatch ?? 100,
1534
+ maxAttempts: options.maxAttempts ?? 3,
1535
+ collapseWindowMs: options.collapseWindowMs ?? 6e4,
1536
+ collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
1537
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
1538
+ retryBackoffMs: options.retryBackoffMs ?? 1e3,
1539
+ now: options.now ?? Date.now
1540
+ };
1541
+ }
1542
+ /**
1543
+ * Queue one beacon. With a `collapseKey`, a repeat inside the window is
1544
+ * merged into a held aggregate (sent when the window closes) instead of
1545
+ * becoming its own item.
1546
+ */
1547
+ enqueue(dest, item, collapseKey) {
1548
+ const now = this.opts.now();
1549
+ if (collapseKey) {
1550
+ const entry = this.collapse.get(collapseKey);
1551
+ if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
1552
+ for (const target of [entry.first, entry.held]) {
1553
+ if (target && !target.inFlight && this.queue.includes(target)) {
1554
+ target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
1555
+ return;
1556
+ }
1557
+ }
1558
+ const held = this.push(
1559
+ dest,
1560
+ { ...item, repeatCount: 1 },
1561
+ entry.firstSeenAt + this.opts.collapseWindowMs
1562
+ );
1563
+ entry.held = held;
1564
+ return;
1565
+ }
1566
+ this.collapse.delete(collapseKey);
1567
+ if (this.collapse.size >= this.opts.collapseMaxKeys) {
1568
+ const oldest = this.collapse.keys().next().value;
1569
+ if (oldest !== void 0) this.collapse.delete(oldest);
1570
+ }
1571
+ const fresh = { firstSeenAt: now };
1572
+ this.collapse.set(collapseKey, fresh);
1573
+ fresh.first = this.push(dest, item, now);
1574
+ return;
1575
+ }
1576
+ this.push(dest, item, now);
1577
+ }
1578
+ /**
1579
+ * Deliver every eligible queued item. Concurrent callers share ONE
1580
+ * in-flight drain (it survives the caller's budget and a Lambda freeze),
1581
+ * so nothing in flight is ever sent twice; items queued while it runs are
1582
+ * picked up by the same drain.
1583
+ */
1584
+ flush() {
1585
+ if (!this.inFlight) {
1586
+ this.inFlight = this.drain().finally(() => {
1587
+ this.inFlight = void 0;
1588
+ });
1589
+ }
1590
+ return this.inFlight;
1591
+ }
1592
+ /** Counters for tests and diagnostics. */
1593
+ stats() {
1594
+ return {
1595
+ queueDepth: this.queue.length,
1596
+ droppedSinceLast: this.droppedSinceLast,
1597
+ droppedTotal: this.droppedTotal,
1598
+ deliveredTotal: this.deliveredTotal,
1599
+ flushing: this.inFlight !== void 0
1600
+ };
1601
+ }
1602
+ push(dest, item, eligibleAt) {
1603
+ while (this.queue.length >= this.opts.maxItems) this.dropOldest();
1604
+ const entry = {
1605
+ dest,
1606
+ destKey: destKeyOf(dest),
1607
+ item,
1608
+ attempts: 0,
1609
+ eligibleAt,
1610
+ inFlight: false
1611
+ };
1612
+ this.queue.push(entry);
1613
+ return entry;
1614
+ }
1615
+ dropOldest() {
1616
+ if (this.queue.shift()) this.countDropped(1);
1617
+ }
1618
+ countDropped(n) {
1619
+ this.droppedSinceLast += n;
1620
+ this.droppedTotal += n;
1621
+ }
1622
+ async drain() {
1623
+ for (; ; ) {
1624
+ const now = this.opts.now();
1625
+ if (now < this.backoffUntil) return;
1626
+ const batch = this.takeBatch(now);
1627
+ if (batch.length === 0) return;
1628
+ const ok = await this.send(batch);
1629
+ if (!ok) return;
1630
+ }
1631
+ }
1632
+ /** Oldest eligible item + up to maxBatch-1 more for the same destination. */
1633
+ takeBatch(now) {
1634
+ const first = this.queue.find((q) => q.eligibleAt <= now);
1635
+ if (!first) return [];
1636
+ const batch = [];
1637
+ const rest = [];
1638
+ for (const q of this.queue) {
1639
+ if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
1640
+ q.inFlight = true;
1641
+ batch.push(q);
1642
+ } else {
1643
+ rest.push(q);
1644
+ }
1645
+ }
1646
+ this.queue = rest;
1647
+ return batch;
1648
+ }
1649
+ /** POST one batch. Returns false when the drain should pause (backoff). */
1650
+ async send(batch) {
1651
+ const dest = batch[0].dest;
1652
+ const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
1653
+ const reportedDropped = this.droppedSinceLast;
1654
+ const meta = {
1655
+ droppedSinceLast: reportedDropped,
1656
+ queueDepth: this.queue.length,
1657
+ adapter: dest.adapter ?? "edge-core",
1658
+ version: SDK_VERSION
1659
+ };
1660
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
1661
+ const response = await withBudget(
1662
+ sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
1663
+ method: "POST",
1664
+ headers: {
1665
+ "Content-Type": "application/json",
1666
+ ...useKey && { Authorization: `Bearer ${useKey}` }
1667
+ },
1668
+ body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
1669
+ ...controller && { signal: controller.signal }
1670
+ }),
1671
+ this.opts.requestTimeoutMs
1672
+ );
1673
+ if (!response) controller?.abort();
1674
+ const status = response?.status;
1675
+ for (const q of batch) q.inFlight = false;
1676
+ if (status !== void 0 && status >= 200 && status < 300) {
1677
+ this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
1678
+ this.deliveredTotal += batch.length;
1679
+ return true;
1680
+ }
1681
+ if (status === 401 && useKey) {
1682
+ this.rejectedKeys.add(useKey);
1683
+ this.requeue(batch, false);
1684
+ return true;
1685
+ }
1686
+ if (status === 429) {
1687
+ const retryAfter = Number(response?.headers.get("retry-after"));
1688
+ const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
1689
+ this.backoffUntil = this.opts.now() + waitMs;
1690
+ this.requeue(batch, false);
1691
+ return false;
1692
+ }
1693
+ if (status !== void 0 && status >= 400 && status < 500) {
1694
+ this.countDropped(batch.length);
1695
+ return true;
1696
+ }
1697
+ this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
1698
+ this.requeue(batch, true);
1699
+ return false;
1700
+ }
1701
+ /** Put a failed batch back at the head of the queue (order preserved). */
1702
+ requeue(batch, countAttempt) {
1703
+ const keep = [];
1704
+ for (const q of batch) {
1705
+ if (countAttempt) q.attempts += 1;
1706
+ if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
1707
+ else keep.push(q);
1708
+ }
1709
+ this.queue = [...keep, ...this.queue];
1710
+ while (this.queue.length > this.opts.maxItems) this.dropOldest();
1711
+ }
1712
+ };
1713
+ var beaconBuffer = new BeaconBuffer();
1714
+ function getBeaconBuffer() {
1715
+ return beaconBuffer;
1716
+ }
1717
+ function resetBeaconBuffer(options) {
1718
+ beaconBuffer = new BeaconBuffer(options);
1719
+ return beaconBuffer;
1720
+ }
1721
+ async function emitBeacon(options, req, classification, budgetMs, queryValueAllowlist, apiKey) {
1467
1722
  const attemptId = extractAttemptId(req);
1468
1723
  const declaredSource = normalizeDeclaredSource(headerValue(req, "x-astra-source"));
1469
- const budgeted = withBudget(
1470
- reportUnregisteredAttempt(
1471
- { apiBaseUrl: options.apiBaseUrl, counterpartyId: options.counterpartyId },
1472
- {
1473
- counterpartyUrl: options.counterpartyUrl,
1474
- counterpartyType: "website",
1475
- sourceIp: req.clientIp,
1476
- userAgent: headerValue(req, "user-agent"),
1477
- requestPath: req.uri,
1478
- requestMethod: req.method,
1479
- // Attempt-chain handle (header or ?astra_attempt= deep link) — joins
1480
- // this beacon into the agent's research chain via pe_attempt_idx.
1481
- ...attemptId && { attemptId },
1482
- // First-class referer + accept: traffic-source and surface-classifier
1483
- // inputs (previously buried inside observedMetadata.headers).
1484
- referer: headerValue(req, "referer"),
1485
- accept: headerValue(req, "accept"),
1486
- ...declaredSource && { declaredSource },
1487
- // Evidence fields the backend persists into the event record.
1488
- protocol: classification.protocol,
1489
- evidence: classification.evidence,
1490
- // The full sanitized header + connection capture for the
1491
- // unregistered caller (fire-and-forget, so free).
1492
- observedMetadata: buildObservedMetadata(req, queryValueAllowlist)
1493
- }
1494
- ),
1495
- budgetMs
1724
+ const userAgent = headerValue(req, "user-agent");
1725
+ const item = {
1726
+ ...options.counterpartyId && { counterpartyId: options.counterpartyId },
1727
+ ...options.counterpartyUrl && { counterpartyUrl: options.counterpartyUrl },
1728
+ counterpartyType: "website",
1729
+ sourceIp: req.clientIp,
1730
+ userAgent,
1731
+ requestPath: req.uri,
1732
+ requestMethod: req.method,
1733
+ // Attempt-chain handle (header or ?astra_attempt= deep link) — joins
1734
+ // this beacon into the agent's research chain via pe_attempt_idx.
1735
+ ...attemptId && { attemptId },
1736
+ // First-class referer + accept: traffic-source and surface-classifier
1737
+ // inputs (previously buried inside observedMetadata.headers).
1738
+ referer: headerValue(req, "referer"),
1739
+ accept: headerValue(req, "accept"),
1740
+ ...declaredSource && { declaredSource },
1741
+ // Evidence fields the backend persists into the event record.
1742
+ protocol: classification.protocol,
1743
+ evidence: classification.evidence,
1744
+ // The full sanitized header + connection capture for the unregistered
1745
+ // caller.
1746
+ observedMetadata: buildObservedMetadata(req, queryValueAllowlist),
1747
+ // Capture time — batched delivery skews createdAt by the flush delay.
1748
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
1749
+ };
1750
+ for (const k of Object.keys(item)) if (item[k] === void 0) delete item[k];
1751
+ const buffer = getBeaconBuffer();
1752
+ buffer.enqueue(
1753
+ {
1754
+ apiBaseUrl: options.apiBaseUrl,
1755
+ ...apiKey && { apiKey },
1756
+ counterparty: options.counterpartyId || options.counterpartyUrl,
1757
+ ...options.adapter && { adapter: options.adapter }
1758
+ },
1759
+ item,
1760
+ // Repeat collapse needs a client identity; without an IP, distinct
1761
+ // callers sharing a UA would merge, so don't collapse.
1762
+ req.clientIp ? [
1763
+ options.counterpartyId || options.counterpartyUrl,
1764
+ req.clientIp,
1765
+ userAgent ?? "",
1766
+ req.method,
1767
+ req.uri,
1768
+ attemptId ?? ""
1769
+ ].join("\0") : void 0
1496
1770
  );
1771
+ const flushing = buffer.flush();
1497
1772
  if (options.waitUntil) {
1498
- options.waitUntil(budgeted);
1773
+ options.waitUntil(flushing);
1499
1774
  return;
1500
1775
  }
1501
- await budgeted;
1776
+ await withBudget(flushing, budgetMs);
1502
1777
  }
1503
1778
  function sampled(rate, random = Math.random) {
1504
1779
  if (rate <= 0) return false;
@@ -1663,7 +1938,7 @@ async function runTier(options, req, classification, policy, apiKey, capture) {
1663
1938
  }
1664
1939
  return outcomeFromVerifyResult(classification, result);
1665
1940
  }
1666
- await emitBeacon(options, req, classification, beaconMs, queryValueAllowlist);
1941
+ await emitBeacon(options, req, classification, beaconMs, queryValueAllowlist, apiKey);
1667
1942
  const outcome = baseOutcome(classification);
1668
1943
  if (policy.mode === "enforce" && policy.depth !== "classify") {
1669
1944
  outcome.decision = "deny";
@@ -1678,7 +1953,8 @@ async function runTier(options, req, classification, policy, apiKey, capture) {
1678
1953
  req,
1679
1954
  { ...classification, protocol: classification.protocol ?? "ua-heuristic" },
1680
1955
  beaconMs,
1681
- queryValueAllowlist
1956
+ queryValueAllowlist,
1957
+ apiKey
1682
1958
  );
1683
1959
  }
1684
1960
  return baseOutcome(classification);
@@ -1731,6 +2007,7 @@ async function evaluateEdgeRequest(options, req, config, apiKey) {
1731
2007
  }
1732
2008
  export {
1733
2009
  ASAT_HEADERS,
2010
+ BeaconBuffer,
1734
2011
  _patterns,
1735
2012
  applyOutcome,
1736
2013
  buildDenyBody,
@@ -1741,10 +2018,12 @@ export {
1741
2018
  detectCommerce,
1742
2019
  emitBeacon,
1743
2020
  evaluateEdgeRequest,
2021
+ getBeaconBuffer,
1744
2022
  headerValue,
1745
2023
  outcomeFromVerifyResult,
1746
2024
  passUntouched,
1747
2025
  requestUrl,
2026
+ resetBeaconBuffer,
1748
2027
  resolvePolicy,
1749
2028
  runTier,
1750
2029
  sampled,