@ainyc/canonry 4.51.1 → 4.51.3

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 (25) hide show
  1. package/assets/assets/{BacklinksPage-DIZCcqsP.js → BacklinksPage-9TlM08Wf.js} +1 -1
  2. package/assets/assets/ProjectPage-CD591qDz.js +6 -0
  3. package/assets/assets/{RunRow-DqezNIUy.js → RunRow-D7qdWWRl.js} +1 -1
  4. package/assets/assets/{RunsPage-CfvTJ9Ny.js → RunsPage-CvewepfU.js} +1 -1
  5. package/assets/assets/{SettingsPage-HfMGIa5v.js → SettingsPage-C7BvAhiB.js} +1 -1
  6. package/assets/assets/{TrafficPage-DV_Dvpl3.js → TrafficPage-DC3NhFOh.js} +1 -1
  7. package/assets/assets/TrafficSourceDetailPage-BvtTA6rs.js +1 -0
  8. package/assets/assets/{arrow-left-DpxpMUNt.js → arrow-left-Agb02DMK.js} +1 -1
  9. package/assets/assets/index-DTCZ93Ne.js +210 -0
  10. package/assets/assets/server-traffic-C-0Ndjpw.js +1 -0
  11. package/assets/assets/{trash-2-CnBiLbiZ.js → trash-2-lkrXVRRm.js} +1 -1
  12. package/assets/index.html +1 -1
  13. package/dist/{chunk-2ARCCG5E.js → chunk-FYGBW3SM.js} +4 -0
  14. package/dist/{chunk-GGXU5VKI.js → chunk-HMZKIOLG.js} +1 -1
  15. package/dist/{chunk-DLDLDWH4.js → chunk-QZ5XSM6C.js} +34 -1
  16. package/dist/{chunk-JOKPYAEL.js → chunk-WBO5S3IX.js} +3131 -277
  17. package/dist/cli.js +40 -8
  18. package/dist/index.js +4 -4
  19. package/dist/{intelligence-service-XMZEWLCW.js → intelligence-service-2XL2M7QP.js} +2 -2
  20. package/dist/mcp.js +2 -2
  21. package/package.json +11 -11
  22. package/assets/assets/ProjectPage-R2cxJb5Y.js +0 -6
  23. package/assets/assets/TrafficSourceDetailPage-lefreKBO.js +0 -1
  24. package/assets/assets/index-DKBPD33e.js +0 -210
  25. package/assets/assets/server-traffic-Bm8iKtXK.js +0 -1
@@ -2,10 +2,11 @@ import {
2
2
  ApiClient,
3
3
  canonryMcpTools,
4
4
  configExists,
5
+ getConfigPath,
5
6
  loadConfig,
6
7
  loadConfigRaw,
7
8
  saveConfigPatch
8
- } from "./chunk-GGXU5VKI.js";
9
+ } from "./chunk-HMZKIOLG.js";
9
10
  import {
10
11
  DEFAULT_RUN_HISTORY_LIMIT,
11
12
  IntelligenceService,
@@ -82,7 +83,7 @@ import {
82
83
  smoothedRunDelta,
83
84
  trafficSources,
84
85
  usageCounters
85
- } from "./chunk-DLDLDWH4.js";
86
+ } from "./chunk-QZ5XSM6C.js";
86
87
  import {
87
88
  AGENT_MEMORY_VALUE_MAX_BYTES,
88
89
  AGENT_PROVIDER_IDS,
@@ -247,6 +248,7 @@ import {
247
248
  runInProgress,
248
249
  runNotCancellable,
249
250
  runTriggerRequestSchema,
251
+ runtimeStateMissing,
250
252
  schedulableRunKindSchema,
251
253
  scheduleDtoSchema,
252
254
  scheduleUpsertRequestSchema,
@@ -282,7 +284,7 @@ import {
282
284
  wordpressSchemaDeployResultDtoSchema,
283
285
  wordpressSchemaStatusResultDtoSchema,
284
286
  wordpressStatusDtoSchema
285
- } from "./chunk-2ARCCG5E.js";
287
+ } from "./chunk-FYGBW3SM.js";
286
288
 
287
289
  // src/telemetry.ts
288
290
  import crypto from "crypto";
@@ -571,12 +573,15 @@ function checkLatestVersionForServer(opts) {
571
573
  // src/server.ts
572
574
  import { createRequire as createRequire4 } from "module";
573
575
  import crypto35 from "crypto";
574
- import fs13 from "fs";
576
+ import fs15 from "fs";
575
577
  import path15 from "path";
576
578
  import { fileURLToPath as fileURLToPath2 } from "url";
577
579
  import { eq as eq42 } from "drizzle-orm";
578
580
  import Fastify from "fastify";
579
581
 
582
+ // ../api-routes/src/index.ts
583
+ import fs8 from "fs";
584
+
580
585
  // ../api-routes/src/auth.ts
581
586
  import crypto2 from "crypto";
582
587
  import { eq } from "drizzle-orm";
@@ -669,9 +674,22 @@ function writeAuditLog(db, entry) {
669
674
  entityType: entry.entityType,
670
675
  entityId: entry.entityId ?? null,
671
676
  diff: entry.diff != null ? JSON.stringify(entry.diff) : null,
677
+ userAgent: entry.userAgent ?? null,
678
+ actorSession: entry.actorSession ?? null,
672
679
  createdAt: now
673
680
  }).run();
674
681
  }
682
+ function auditFromRequest(request, entry) {
683
+ const ua = request.headers["user-agent"];
684
+ const userAgent = Array.isArray(ua) ? ua.join(", ") : ua ?? null;
685
+ const sess = request.headers["x-canonry-actor-session"];
686
+ const actorSession = Array.isArray(sess) ? sess.join(", ") : sess ?? null;
687
+ return {
688
+ ...entry,
689
+ userAgent: entry.userAgent ?? userAgent,
690
+ actorSession: entry.actorSession ?? actorSession
691
+ };
692
+ }
675
693
  function resolveSnapshotMentionResult(snapshot, project) {
676
694
  if (snapshot.answerText) {
677
695
  const domains = effectiveDomains({
@@ -1023,6 +1041,12 @@ function aliasArraysEqual(a, b) {
1023
1041
  // ../api-routes/src/queries.ts
1024
1042
  import crypto5 from "crypto";
1025
1043
  import { eq as eq4, inArray, sql as sql3 } from "drizzle-orm";
1044
+ function preserveSnapshotQueryText(tx, projectId, queryIds) {
1045
+ const candidates = queryIds && queryIds.length > 0 ? tx.select({ id: queries.id, text: queries.query }).from(queries).where(inArray(queries.id, queryIds)).all() : tx.select({ id: queries.id, text: queries.query }).from(queries).where(eq4(queries.projectId, projectId)).all();
1046
+ for (const q of candidates) {
1047
+ tx.update(querySnapshots).set({ queryText: q.text }).where(eq4(querySnapshots.queryId, q.id)).run();
1048
+ }
1049
+ }
1026
1050
  async function queryRoutes(app, opts) {
1027
1051
  app.get("/projects/:name/queries", async (request, reply) => {
1028
1052
  const project = resolveProject(app.db, request.params.name);
@@ -1037,6 +1061,7 @@ async function queryRoutes(app, opts) {
1037
1061
  }
1038
1062
  const now = (/* @__PURE__ */ new Date()).toISOString();
1039
1063
  app.db.transaction((tx) => {
1064
+ preserveSnapshotQueryText(tx, project.id);
1040
1065
  tx.delete(queries).where(eq4(queries.projectId, project.id)).run();
1041
1066
  for (const q of body.queries) {
1042
1067
  tx.insert(queries).values({
@@ -1047,13 +1072,13 @@ async function queryRoutes(app, opts) {
1047
1072
  createdAt: now
1048
1073
  }).run();
1049
1074
  }
1050
- writeAuditLog(tx, {
1075
+ writeAuditLog(tx, auditFromRequest(request, {
1051
1076
  projectId: project.id,
1052
1077
  actor: "api",
1053
1078
  action: "queries.replaced",
1054
1079
  entityType: "query",
1055
1080
  diff: { queries: body.queries }
1056
- });
1081
+ }));
1057
1082
  });
1058
1083
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
1059
1084
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
@@ -1099,16 +1124,17 @@ async function queryRoutes(app, opts) {
1099
1124
  const idsToDelete = existing.filter((q) => toDelete.has(q.query)).map((q) => q.id);
1100
1125
  if (idsToDelete.length > 0) {
1101
1126
  app.db.transaction((tx) => {
1127
+ preserveSnapshotQueryText(tx, project.id, idsToDelete);
1102
1128
  for (const id of idsToDelete) {
1103
1129
  tx.delete(queries).where(eq4(queries.id, id)).run();
1104
1130
  }
1105
- writeAuditLog(tx, {
1131
+ writeAuditLog(tx, auditFromRequest(request, {
1106
1132
  projectId: project.id,
1107
1133
  actor: "api",
1108
1134
  action: "queries.deleted",
1109
1135
  entityType: "query",
1110
1136
  diff: { deleted: body.queries.filter((q) => existing.some((e) => e.query === q)) }
1111
- });
1137
+ }));
1112
1138
  });
1113
1139
  }
1114
1140
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
@@ -1138,13 +1164,13 @@ async function queryRoutes(app, opts) {
1138
1164
  }
1139
1165
  }
1140
1166
  if (added.length > 0) {
1141
- writeAuditLog(app.db, {
1167
+ writeAuditLog(app.db, auditFromRequest(request, {
1142
1168
  projectId: project.id,
1143
1169
  actor: "api",
1144
1170
  action: "queries.appended",
1145
1171
  entityType: "query",
1146
1172
  diff: { added }
1147
- });
1173
+ }));
1148
1174
  }
1149
1175
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
1150
1176
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
@@ -1202,6 +1228,7 @@ async function queryRoutes(app, opts) {
1202
1228
  }
1203
1229
  const now = (/* @__PURE__ */ new Date()).toISOString();
1204
1230
  app.db.transaction((tx) => {
1231
+ preserveSnapshotQueryText(tx, project.id);
1205
1232
  tx.delete(queries).where(eq4(queries.projectId, project.id)).run();
1206
1233
  for (const keyword of body.keywords) {
1207
1234
  tx.insert(queries).values({
@@ -1212,13 +1239,13 @@ async function queryRoutes(app, opts) {
1212
1239
  createdAt: now
1213
1240
  }).run();
1214
1241
  }
1215
- writeAuditLog(tx, {
1242
+ writeAuditLog(tx, auditFromRequest(request, {
1216
1243
  projectId: project.id,
1217
1244
  actor: "api",
1218
1245
  action: "queries.replaced",
1219
1246
  entityType: "query",
1220
1247
  diff: { queries: body.keywords }
1221
- });
1248
+ }));
1222
1249
  });
1223
1250
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
1224
1251
  return reply.send(rows.map((r) => ({ id: r.id, keyword: r.query, createdAt: r.createdAt })));
@@ -1234,16 +1261,17 @@ async function queryRoutes(app, opts) {
1234
1261
  const idsToDelete = existing.filter((q) => toDelete.has(q.query)).map((q) => q.id);
1235
1262
  if (idsToDelete.length > 0) {
1236
1263
  app.db.transaction((tx) => {
1264
+ preserveSnapshotQueryText(tx, project.id, idsToDelete);
1237
1265
  for (const id of idsToDelete) {
1238
1266
  tx.delete(queries).where(eq4(queries.id, id)).run();
1239
1267
  }
1240
- writeAuditLog(tx, {
1268
+ writeAuditLog(tx, auditFromRequest(request, {
1241
1269
  projectId: project.id,
1242
1270
  actor: "api",
1243
1271
  action: "queries.deleted",
1244
1272
  entityType: "query",
1245
1273
  diff: { deleted: body.keywords.filter((keyword) => existing.some((e) => e.query === keyword)) }
1246
- });
1274
+ }));
1247
1275
  });
1248
1276
  }
1249
1277
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
@@ -1273,13 +1301,13 @@ async function queryRoutes(app, opts) {
1273
1301
  }
1274
1302
  }
1275
1303
  if (added.length > 0) {
1276
- writeAuditLog(app.db, {
1304
+ writeAuditLog(app.db, auditFromRequest(request, {
1277
1305
  projectId: project.id,
1278
1306
  actor: "api",
1279
1307
  action: "queries.appended",
1280
1308
  entityType: "query",
1281
1309
  diff: { added }
1282
- });
1310
+ }));
1283
1311
  }
1284
1312
  const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
1285
1313
  return reply.send(rows.map((r) => ({ id: r.id, keyword: r.query, createdAt: r.createdAt })));
@@ -1639,8 +1667,10 @@ async function runRoutes(app, opts) {
1639
1667
  const limit = parseListLimit(request.query.limit, 500, 5e3);
1640
1668
  const since = parseListSince(request.query.since);
1641
1669
  const includeProbe = request.query.includeProbe === "1" || request.query.includeProbe === "true";
1670
+ const kind = parseListKind(request.query.kind);
1642
1671
  const filters = [gte(runs.createdAt, since)];
1643
1672
  if (!includeProbe) filters.push(notProbeRun());
1673
+ if (kind) filters.push(eq7(runs.kind, kind));
1644
1674
  const rows = app.db.select().from(runs).where(and2(...filters)).orderBy(desc(runs.createdAt), desc(runs.id)).limit(limit).all();
1645
1675
  return reply.send(rows.map(formatRun));
1646
1676
  });
@@ -1749,6 +1779,14 @@ function parseListLimit(raw, defaultValue, max) {
1749
1779
  }
1750
1780
  return Math.min(parsed, max);
1751
1781
  }
1782
+ function parseListKind(raw) {
1783
+ if (raw === void 0 || raw === "") return null;
1784
+ const validKinds = Object.values(RunKinds);
1785
+ if (!validKinds.includes(raw)) {
1786
+ throw validationError(`"kind" must be one of: ${validKinds.join(", ")}`);
1787
+ }
1788
+ return raw;
1789
+ }
1752
1790
  function parseListSince(raw) {
1753
1791
  if (raw === void 0) {
1754
1792
  const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
@@ -2495,9 +2533,24 @@ async function historyRoutes(app) {
2495
2533
  ...snapshot,
2496
2534
  answerMentioned: resolveSnapshotAnswerMentioned(snapshot, project)
2497
2535
  }));
2536
+ const queryByText = /* @__PURE__ */ new Map();
2537
+ for (const q of projectQueries) queryByText.set(q.query, q);
2538
+ function resolveSnapQueryId(snap) {
2539
+ if (snap.queryId) return snap.queryId;
2540
+ if (snap.queryText) {
2541
+ const match = queryByText.get(snap.queryText);
2542
+ if (match) return match.id;
2543
+ }
2544
+ return null;
2545
+ }
2546
+ const allSnapshotsResolved = allSnapshots.map((s) => ({
2547
+ ...s,
2548
+ resolvedQueryId: resolveSnapQueryId(s)
2549
+ }));
2498
2550
  const deduped = /* @__PURE__ */ new Map();
2499
- for (const snap of allSnapshots) {
2500
- const key = `${snap.runId}:${snap.queryId}`;
2551
+ for (const snap of allSnapshotsResolved) {
2552
+ if (!snap.resolvedQueryId) continue;
2553
+ const key = `${snap.runId}:${snap.resolvedQueryId}`;
2501
2554
  const existing = deduped.get(key);
2502
2555
  if (!existing || !existing.answerMentioned && snap.answerMentioned || existing.answerMentioned === snap.answerMentioned && snap.citationState === CitationStates.cited) {
2503
2556
  deduped.set(key, snap);
@@ -2505,15 +2558,17 @@ async function historyRoutes(app) {
2505
2558
  }
2506
2559
  const dedupedSnapshots = [...deduped.values()];
2507
2560
  const rawByQueryProvider = /* @__PURE__ */ new Map();
2508
- for (const snap of allSnapshots) {
2509
- const key = `${snap.queryId}::${snap.provider}`;
2561
+ for (const snap of allSnapshotsResolved) {
2562
+ if (!snap.resolvedQueryId) continue;
2563
+ const key = `${snap.resolvedQueryId}::${snap.provider}`;
2510
2564
  const arr = rawByQueryProvider.get(key);
2511
2565
  if (arr) arr.push(snap);
2512
2566
  else rawByQueryProvider.set(key, [snap]);
2513
2567
  }
2514
2568
  const rawByQueryModel = /* @__PURE__ */ new Map();
2515
- for (const snap of allSnapshots) {
2516
- const key = `${snap.queryId}::${snap.provider}:${snap.model ?? "unknown"}`;
2569
+ for (const snap of allSnapshotsResolved) {
2570
+ if (!snap.resolvedQueryId) continue;
2571
+ const key = `${snap.resolvedQueryId}::${snap.provider}:${snap.model ?? "unknown"}`;
2517
2572
  const arr = rawByQueryModel.get(key);
2518
2573
  if (arr) arr.push(snap);
2519
2574
  else rawByQueryModel.set(key, [snap]);
@@ -2560,7 +2615,7 @@ async function historyRoutes(app) {
2560
2615
  });
2561
2616
  }
2562
2617
  const timeline = projectQueries.map((q) => {
2563
- const qSnapshots = dedupedSnapshots.filter((s) => s.queryId === q.id).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2618
+ const qSnapshots = dedupedSnapshots.filter((s) => s.resolvedQueryId === q.id).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2564
2619
  const runEntries = computeTransitions(qSnapshots);
2565
2620
  const providerRuns = {};
2566
2621
  const providerKeys = [...rawByQueryProvider.keys()].filter((k) => k.startsWith(`${q.id}::`));
@@ -8387,6 +8442,39 @@ var scheduleKindQueryParameter = {
8387
8442
  description: 'Schedulable run kind. Defaults to "answer-visibility" for backward compatibility.',
8388
8443
  schema: { type: "string", enum: ["answer-visibility", "traffic-sync"] }
8389
8444
  };
8445
+ var runsListKindQueryParameter = {
8446
+ name: "kind",
8447
+ in: "query",
8448
+ description: "Restrict results to a single run kind. Without this filter, integration syncs (bing-inspect, gsc-sync, ga-sync) can fill the default 500-row cap within minutes on busy projects and push answer-visibility runs out of the response.",
8449
+ schema: {
8450
+ type: "string",
8451
+ enum: [
8452
+ "answer-visibility",
8453
+ "site-audit",
8454
+ "gsc-sync",
8455
+ "inspect-sitemap",
8456
+ "ga-sync",
8457
+ "bing-inspect",
8458
+ "bing-inspect-sitemap",
8459
+ "backlink-extract",
8460
+ "traffic-sync",
8461
+ "aeo-discover-seed",
8462
+ "aeo-discover-probe"
8463
+ ]
8464
+ }
8465
+ };
8466
+ var runsListSinceQueryParameter = {
8467
+ name: "since",
8468
+ in: "query",
8469
+ description: "Only return runs with created_at >= this ISO 8601 timestamp. Defaults to 30 days ago.",
8470
+ schema: stringSchema
8471
+ };
8472
+ var runsListIncludeProbeQueryParameter = {
8473
+ name: "includeProbe",
8474
+ in: "query",
8475
+ description: 'Set to "1" or "true" to include probe runs. Probes are excluded by default because they are operator/agent test runs and must not pollute dashboard aggregates.',
8476
+ schema: stringSchema
8477
+ };
8390
8478
  var reportAudienceQueryParameter = {
8391
8479
  name: "audience",
8392
8480
  in: "query",
@@ -8970,6 +9058,12 @@ var routeCatalog = [
8970
9058
  path: "/api/v1/runs",
8971
9059
  summary: "List all runs",
8972
9060
  tags: ["runs"],
9061
+ parameters: [
9062
+ limitQueryParameter,
9063
+ runsListSinceQueryParameter,
9064
+ runsListIncludeProbeQueryParameter,
9065
+ runsListKindQueryParameter
9066
+ ],
8973
9067
  responses: {
8974
9068
  200: jsonArrayResponse("Runs returned.", "RunDto")
8975
9069
  }
@@ -17504,7 +17598,7 @@ async function queryBacklinks(opts) {
17504
17598
  const reversed = opts.targets.map(reverseDomain);
17505
17599
  const targetList = reversed.map(quote).join(", ");
17506
17600
  const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
17507
- const sql16 = `
17601
+ const sql17 = `
17508
17602
  WITH vertices AS (
17509
17603
  SELECT * FROM read_csv(
17510
17604
  ${quote(opts.vertexPath)},
@@ -17540,7 +17634,7 @@ async function queryBacklinks(opts) {
17540
17634
  const conn = await instance.connect();
17541
17635
  let rows;
17542
17636
  try {
17543
- const reader = await conn.runAndReadAll(sql16);
17637
+ const reader = await conn.runAndReadAll(sql17);
17544
17638
  rows = reader.getRowObjects();
17545
17639
  } finally {
17546
17640
  conn.disconnectSync?.();
@@ -18212,217 +18306,2472 @@ async function listCloudRunTrafficEvents(accessToken, options) {
18212
18306
  };
18213
18307
  }
18214
18308
 
18215
- // ../integration-traffic/src/rules.ts
18216
- var LEGACY_CHATGPT_DOMAIN = "chat.openai.com";
18217
- var DEFAULT_AI_CRAWLER_RULES = [
18218
- {
18219
- id: "openai-gptbot",
18220
- operator: "OpenAI",
18221
- product: "GPTBot",
18222
- purpose: "training",
18223
- userAgentPatterns: [/GPTBot\//i]
18224
- },
18225
- {
18226
- id: "openai-searchbot",
18227
- operator: "OpenAI",
18228
- product: "OAI-SearchBot",
18229
- purpose: "search",
18230
- userAgentPatterns: [/OAI-SearchBot\//i]
18231
- },
18232
- {
18233
- id: "openai-chatgpt-user",
18234
- operator: "OpenAI",
18235
- product: "ChatGPT-User",
18236
- purpose: "user-agent",
18237
- userAgentPatterns: [/ChatGPT-User\//i]
18238
- },
18239
- {
18240
- id: "anthropic-claudebot",
18241
- operator: "Anthropic",
18242
- product: "ClaudeBot",
18243
- purpose: "training",
18244
- userAgentPatterns: [/ClaudeBot\//i, /Claude-Web\//i, /anthropic-ai/i]
18245
- },
18246
- {
18247
- id: "perplexity-bot",
18248
- operator: "Perplexity",
18249
- product: "PerplexityBot",
18250
- purpose: "search",
18251
- userAgentPatterns: [/PerplexityBot\//i]
18252
- },
18253
- {
18254
- id: "google-extended",
18255
- operator: "Google",
18256
- product: "Google-Extended",
18257
- purpose: "training-control",
18258
- userAgentPatterns: [/Google-Extended/i]
18259
- },
18260
- {
18261
- id: "bytespider",
18262
- operator: "ByteDance",
18263
- product: "Bytespider",
18264
- purpose: "training",
18265
- userAgentPatterns: [/Bytespider/i]
18266
- },
18267
- {
18268
- id: "applebot-extended",
18269
- operator: "Apple",
18270
- product: "Applebot-Extended",
18271
- purpose: "training",
18272
- userAgentPatterns: [/Applebot-Extended/i]
18273
- },
18274
- {
18275
- id: "meta-externalagent",
18276
- operator: "Meta",
18277
- product: "meta-externalagent",
18278
- purpose: "training",
18279
- userAgentPatterns: [/meta-externalagent/i]
18280
- },
18281
- {
18282
- id: "ccbot",
18283
- operator: "Common Crawl",
18284
- product: "CCBot",
18285
- purpose: "crawl",
18286
- userAgentPatterns: [/CCBot\//i]
18287
- },
18288
- {
18289
- id: "cohere-ai",
18290
- operator: "Cohere",
18291
- product: "cohere-ai",
18292
- purpose: "training",
18293
- userAgentPatterns: [/cohere-ai/i]
18294
- },
18295
- {
18296
- id: "diffbot",
18297
- operator: "Diffbot",
18298
- product: "Diffbot",
18299
- purpose: "crawl",
18300
- userAgentPatterns: [/Diffbot/i]
18301
- },
18302
- {
18303
- id: "mistral-ai",
18304
- operator: "Mistral AI",
18305
- product: "MistralAI-User",
18306
- purpose: "crawl",
18307
- userAgentPatterns: [/MistralAI/i]
18308
- }
18309
- ];
18310
- var DEFAULT_AI_REFERRER_RULES = [
18311
- { domain: AI_ENGINE_DOMAINS.chatgpt, operator: "OpenAI", product: "ChatGPT" },
18312
- { domain: LEGACY_CHATGPT_DOMAIN, operator: "OpenAI", product: "ChatGPT" },
18313
- { domain: AI_ENGINE_DOMAINS.perplexity, operator: "Perplexity", product: "Perplexity" },
18314
- { domain: AI_ENGINE_DOMAINS.claude, operator: "Anthropic", product: "Claude" },
18315
- { domain: AI_ENGINE_DOMAINS.gemini, operator: "Google", product: "Gemini" },
18316
- { domain: AI_ENGINE_DOMAINS.copilotMicrosoft, operator: "Microsoft", product: "Copilot" },
18317
- { domain: AI_ENGINE_DOMAINS.phind, operator: "Phind", product: "Phind" },
18318
- { domain: AI_ENGINE_DOMAINS.you, operator: "You.com", product: "You.com" },
18319
- { domain: AI_ENGINE_DOMAINS.metaAi, operator: "Meta", product: "Meta AI" }
18320
- ];
18321
-
18322
- // ../integration-traffic/src/classifier.ts
18323
- function normalizeHost(host) {
18324
- return host.trim().toLowerCase().replace(/^www\./, "");
18325
- }
18326
- function hostMatches(host, domain) {
18327
- const normalizedHost = normalizeHost(host);
18328
- const normalizedDomain = normalizeHost(domain);
18329
- return normalizedHost === normalizedDomain || normalizedHost.endsWith(`.${normalizedDomain}`);
18330
- }
18331
- function utmTokenMatchesDomain(utmSource, domain) {
18332
- if (hostMatches(utmSource, domain)) return true;
18333
- const normalizedUtm = normalizeHost(utmSource);
18334
- const firstLabel = normalizeHost(domain).split(".")[0];
18335
- return Boolean(firstLabel) && normalizedUtm === firstLabel;
18336
- }
18337
- function hostFromUrl(value) {
18338
- if (!value) return null;
18339
- try {
18340
- return normalizeHost(new URL(value).hostname);
18341
- } catch {
18342
- return null;
18343
- }
18344
- }
18345
- function utmSourceFromQuery(queryString) {
18346
- if (!queryString) return null;
18347
- const params = new URLSearchParams(queryString);
18348
- const source = params.get("utm_source");
18349
- return source ? normalizeHost(source) : null;
18350
- }
18351
- function utmSourceFromUrl(value) {
18352
- if (!value) return null;
18353
- try {
18354
- return utmSourceFromQuery(new URL(value).search.replace(/^\?/, ""));
18355
- } catch {
18356
- return null;
18357
- }
18358
- }
18359
- function classifyCrawler(event) {
18360
- const userAgent = event.userAgent?.trim();
18361
- if (!userAgent) return null;
18362
- for (const rule of DEFAULT_AI_CRAWLER_RULES) {
18363
- if (rule.userAgentPatterns.some((pattern) => pattern.test(userAgent))) {
18364
- return {
18365
- botId: rule.id,
18366
- operator: rule.operator,
18367
- product: rule.product,
18368
- purpose: rule.purpose,
18369
- verificationStatus: "claimed_unverified",
18370
- matchedUserAgent: userAgent
18371
- };
18372
- }
18373
- }
18374
- return null;
18375
- }
18376
- function classifyAiReferral(event) {
18377
- const refererHost = hostFromUrl(event.referer);
18378
- if (refererHost) {
18379
- const rule = DEFAULT_AI_REFERRER_RULES.find((candidate) => hostMatches(refererHost, candidate.domain));
18380
- if (rule) {
18381
- return {
18382
- operator: rule.operator,
18383
- product: rule.product,
18384
- sourceDomain: refererHost,
18385
- evidenceType: "referer"
18386
- };
18387
- }
18388
- }
18389
- const utmSource = utmSourceFromQuery(event.queryString);
18390
- if (utmSource) {
18391
- const rule = DEFAULT_AI_REFERRER_RULES.find((candidate) => utmTokenMatchesDomain(utmSource, candidate.domain));
18392
- if (rule) {
18393
- return {
18394
- operator: rule.operator,
18395
- product: rule.product,
18396
- sourceDomain: utmSource,
18397
- evidenceType: "utm"
18398
- };
18399
- }
18400
- }
18401
- const refererUtmSource = utmSourceFromUrl(event.referer);
18402
- if (refererUtmSource) {
18403
- const rule = DEFAULT_AI_REFERRER_RULES.find((candidate) => utmTokenMatchesDomain(refererUtmSource, candidate.domain));
18404
- if (rule) {
18405
- return {
18406
- operator: rule.operator,
18407
- product: rule.product,
18408
- sourceDomain: refererUtmSource,
18409
- evidenceType: "referer-utm"
18410
- };
18411
- }
18412
- }
18413
- return null;
18414
- }
18309
+ // ../integration-traffic/src/ip-ranges/anthropic.json
18310
+ var anthropic_default = {
18311
+ _source: "ARIN RDAP \u2014 every network registered to Anthropic, PBC (handle AP-2440)",
18312
+ _query: "https://rdap.arin.net/registry/entity/AP-2440",
18313
+ _notes: "Anthropic does not publish a clean machine-readable IP-range JSON like Google or OpenAI do. These prefixes are taken straight from ARIN's authoritative allocation records (NOT bgp.tools' AS-based view, which can include misleading announcements \u2014 e.g. AS60808 is registered to Anthropic but announces 209.249.57.0/24 which actually belongs to Mitel Networks). 216.73.216.0/22 (AWS-ANTHROPIC) is the load-bearing prefix \u2014 empirical Cloud Run logs show 100% of real ClaudeBot traffic comes from there; the other two are Anthropic's own allocations for non-crawler infra (kept since the verification gate still requires a Claude-* UA match). Refresh by re-running the ARIN RDAP query above and replacing the prefix list.",
18314
+ prefixes: [
18315
+ { ipv4Prefix: "216.73.216.0/22" },
18316
+ { ipv4Prefix: "160.79.104.0/21" },
18317
+ { ipv6Prefix: "2607:6bc0::/32" }
18318
+ ]
18319
+ };
18415
18320
 
18416
- // ../integration-traffic/src/rollup.ts
18417
- var DEFAULT_SAMPLE_LIMIT = 25;
18418
- var DEFAULT_AI_REFERRAL_SESSION_WINDOW_MS = 6e4;
18419
- var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18420
- var LONG_HEX_SEGMENT = /^[0-9a-f]{16,}$/i;
18421
- var NUMERIC_SEGMENT = /^\d+$/;
18422
- var ASSET_EXTENSION_PATTERN = /\.(?:avif|bmp|css|gif|ico|jpe?g|js|json|map|mjs|mp4|otf|png|svg|webm|webmanifest|woff2?|xml)$/i;
18423
- var ASSET_PATH_PREFIXES = [
18424
- "/_next/static/",
18425
- "/assets/",
18321
+ // ../integration-traffic/src/ip-ranges/bingbot.json
18322
+ var bingbot_default = {
18323
+ _source: "https://www.bing.com/toolbox/bingbot.json",
18324
+ creationTime: "2024-01-03T10:00:00.121331",
18325
+ prefixes: [
18326
+ {
18327
+ ipv4Prefix: "157.55.39.0/24"
18328
+ },
18329
+ {
18330
+ ipv4Prefix: "207.46.13.0/24"
18331
+ },
18332
+ {
18333
+ ipv4Prefix: "40.77.167.0/24"
18334
+ },
18335
+ {
18336
+ ipv4Prefix: "13.66.139.0/24"
18337
+ },
18338
+ {
18339
+ ipv4Prefix: "13.66.144.0/24"
18340
+ },
18341
+ {
18342
+ ipv4Prefix: "52.167.144.0/24"
18343
+ },
18344
+ {
18345
+ ipv4Prefix: "13.67.10.16/28"
18346
+ },
18347
+ {
18348
+ ipv4Prefix: "13.69.66.240/28"
18349
+ },
18350
+ {
18351
+ ipv4Prefix: "13.71.172.224/28"
18352
+ },
18353
+ {
18354
+ ipv4Prefix: "139.217.52.0/28"
18355
+ },
18356
+ {
18357
+ ipv4Prefix: "191.233.204.224/28"
18358
+ },
18359
+ {
18360
+ ipv4Prefix: "20.36.108.32/28"
18361
+ },
18362
+ {
18363
+ ipv4Prefix: "20.43.120.16/28"
18364
+ },
18365
+ {
18366
+ ipv4Prefix: "40.79.131.208/28"
18367
+ },
18368
+ {
18369
+ ipv4Prefix: "40.79.186.176/28"
18370
+ },
18371
+ {
18372
+ ipv4Prefix: "52.231.148.0/28"
18373
+ },
18374
+ {
18375
+ ipv4Prefix: "20.79.107.240/28"
18376
+ },
18377
+ {
18378
+ ipv4Prefix: "51.105.67.0/28"
18379
+ },
18380
+ {
18381
+ ipv4Prefix: "20.125.163.80/28"
18382
+ },
18383
+ {
18384
+ ipv4Prefix: "40.77.188.0/22"
18385
+ },
18386
+ {
18387
+ ipv4Prefix: "65.55.210.0/24"
18388
+ },
18389
+ {
18390
+ ipv4Prefix: "199.30.24.0/23"
18391
+ },
18392
+ {
18393
+ ipv4Prefix: "40.77.202.0/24"
18394
+ },
18395
+ {
18396
+ ipv4Prefix: "40.77.139.0/25"
18397
+ },
18398
+ {
18399
+ ipv4Prefix: "20.74.197.0/28"
18400
+ },
18401
+ {
18402
+ ipv4Prefix: "20.15.133.160/27"
18403
+ },
18404
+ {
18405
+ ipv4Prefix: "40.77.177.0/24"
18406
+ },
18407
+ {
18408
+ ipv4Prefix: "40.77.178.0/23"
18409
+ }
18410
+ ]
18411
+ };
18412
+
18413
+ // ../integration-traffic/src/ip-ranges/chatgpt-user.json
18414
+ var chatgpt_user_default = {
18415
+ _source: "https://openai.com/chatgpt-user.json",
18416
+ creationTime: "2026-05-17T23:03:25.747192",
18417
+ prefixes: [
18418
+ {
18419
+ ipv4Prefix: "104.210.139.192/28"
18420
+ },
18421
+ {
18422
+ ipv4Prefix: "104.210.139.224/28"
18423
+ },
18424
+ {
18425
+ ipv4Prefix: "13.65.138.112/28"
18426
+ },
18427
+ {
18428
+ ipv4Prefix: "13.65.138.96/28"
18429
+ },
18430
+ {
18431
+ ipv4Prefix: "13.67.72.16/28"
18432
+ },
18433
+ {
18434
+ ipv4Prefix: "13.70.107.160/28"
18435
+ },
18436
+ {
18437
+ ipv4Prefix: "13.71.2.208/28"
18438
+ },
18439
+ {
18440
+ ipv4Prefix: "13.76.115.224/28"
18441
+ },
18442
+ {
18443
+ ipv4Prefix: "13.76.115.240/28"
18444
+ },
18445
+ {
18446
+ ipv4Prefix: "13.76.116.80/28"
18447
+ },
18448
+ {
18449
+ ipv4Prefix: "13.76.32.208/28"
18450
+ },
18451
+ {
18452
+ ipv4Prefix: "13.83.167.128/28"
18453
+ },
18454
+ {
18455
+ ipv4Prefix: "13.83.237.176/28"
18456
+ },
18457
+ {
18458
+ ipv4Prefix: "132.196.82.48/28"
18459
+ },
18460
+ {
18461
+ ipv4Prefix: "135.119.134.128/28"
18462
+ },
18463
+ {
18464
+ ipv4Prefix: "135.119.134.192/28"
18465
+ },
18466
+ {
18467
+ ipv4Prefix: "135.220.73.208/28"
18468
+ },
18469
+ {
18470
+ ipv4Prefix: "135.220.73.240/28"
18471
+ },
18472
+ {
18473
+ ipv4Prefix: "135.237.131.208/28"
18474
+ },
18475
+ {
18476
+ ipv4Prefix: "135.237.133.48/28"
18477
+ },
18478
+ {
18479
+ ipv4Prefix: "137.135.191.176/28"
18480
+ },
18481
+ {
18482
+ ipv4Prefix: "138.91.30.48/28"
18483
+ },
18484
+ {
18485
+ ipv4Prefix: "138.91.46.96/28"
18486
+ },
18487
+ {
18488
+ ipv4Prefix: "168.63.252.240/28"
18489
+ },
18490
+ {
18491
+ ipv4Prefix: "172.170.8.208/28"
18492
+ },
18493
+ {
18494
+ ipv4Prefix: "172.171.4.176/28"
18495
+ },
18496
+ {
18497
+ ipv4Prefix: "172.178.140.144/28"
18498
+ },
18499
+ {
18500
+ ipv4Prefix: "172.178.141.112/28"
18501
+ },
18502
+ {
18503
+ ipv4Prefix: "172.178.141.128/28"
18504
+ },
18505
+ {
18506
+ ipv4Prefix: "172.183.143.224/28"
18507
+ },
18508
+ {
18509
+ ipv4Prefix: "172.183.222.128/28"
18510
+ },
18511
+ {
18512
+ ipv4Prefix: "172.196.40.208/28"
18513
+ },
18514
+ {
18515
+ ipv4Prefix: "172.202.102.112/28"
18516
+ },
18517
+ {
18518
+ ipv4Prefix: "172.204.16.64/28"
18519
+ },
18520
+ {
18521
+ ipv4Prefix: "172.204.27.16/28"
18522
+ },
18523
+ {
18524
+ ipv4Prefix: "172.212.159.64/28"
18525
+ },
18526
+ {
18527
+ ipv4Prefix: "172.212.172.160/28"
18528
+ },
18529
+ {
18530
+ ipv4Prefix: "172.213.11.144/28"
18531
+ },
18532
+ {
18533
+ ipv4Prefix: "172.213.12.112/28"
18534
+ },
18535
+ {
18536
+ ipv4Prefix: "172.213.21.112/28"
18537
+ },
18538
+ {
18539
+ ipv4Prefix: "172.213.21.144/28"
18540
+ },
18541
+ {
18542
+ ipv4Prefix: "172.213.21.16/28"
18543
+ },
18544
+ {
18545
+ ipv4Prefix: "172.215.218.96/28"
18546
+ },
18547
+ {
18548
+ ipv4Prefix: "191.233.1.112/28"
18549
+ },
18550
+ {
18551
+ ipv4Prefix: "191.233.1.128/28"
18552
+ },
18553
+ {
18554
+ ipv4Prefix: "191.233.1.224/28"
18555
+ },
18556
+ {
18557
+ ipv4Prefix: "191.233.194.32/28"
18558
+ },
18559
+ {
18560
+ ipv4Prefix: "191.233.196.112/28"
18561
+ },
18562
+ {
18563
+ ipv4Prefix: "191.233.199.160/28"
18564
+ },
18565
+ {
18566
+ ipv4Prefix: "191.233.2.0/28"
18567
+ },
18568
+ {
18569
+ ipv4Prefix: "191.234.167.128/28"
18570
+ },
18571
+ {
18572
+ ipv4Prefix: "191.235.66.16/28"
18573
+ },
18574
+ {
18575
+ ipv4Prefix: "191.235.98.144/28"
18576
+ },
18577
+ {
18578
+ ipv4Prefix: "191.235.99.80/28"
18579
+ },
18580
+ {
18581
+ ipv4Prefix: "191.237.249.64/28"
18582
+ },
18583
+ {
18584
+ ipv4Prefix: "191.239.245.16/28"
18585
+ },
18586
+ {
18587
+ ipv4Prefix: "20.0.53.96/28"
18588
+ },
18589
+ {
18590
+ ipv4Prefix: "20.102.212.144/28"
18591
+ },
18592
+ {
18593
+ ipv4Prefix: "20.113.218.16/28"
18594
+ },
18595
+ {
18596
+ ipv4Prefix: "20.113.225.112/28"
18597
+ },
18598
+ {
18599
+ ipv4Prefix: "20.125.112.224/28"
18600
+ },
18601
+ {
18602
+ ipv4Prefix: "20.125.144.144/28"
18603
+ },
18604
+ {
18605
+ ipv4Prefix: "20.161.75.208/28"
18606
+ },
18607
+ {
18608
+ ipv4Prefix: "20.168.7.192/28"
18609
+ },
18610
+ {
18611
+ ipv4Prefix: "20.168.7.240/28"
18612
+ },
18613
+ {
18614
+ ipv4Prefix: "20.169.72.112/28"
18615
+ },
18616
+ {
18617
+ ipv4Prefix: "20.169.72.96/28"
18618
+ },
18619
+ {
18620
+ ipv4Prefix: "20.169.73.176/28"
18621
+ },
18622
+ {
18623
+ ipv4Prefix: "20.169.73.32/28"
18624
+ },
18625
+ {
18626
+ ipv4Prefix: "20.169.73.64/28"
18627
+ },
18628
+ {
18629
+ ipv4Prefix: "20.169.78.112/28"
18630
+ },
18631
+ {
18632
+ ipv4Prefix: "20.169.78.128/28"
18633
+ },
18634
+ {
18635
+ ipv4Prefix: "20.169.78.144/28"
18636
+ },
18637
+ {
18638
+ ipv4Prefix: "20.169.78.160/28"
18639
+ },
18640
+ {
18641
+ ipv4Prefix: "20.169.78.176/28"
18642
+ },
18643
+ {
18644
+ ipv4Prefix: "20.169.78.192/28"
18645
+ },
18646
+ {
18647
+ ipv4Prefix: "20.169.78.208/28"
18648
+ },
18649
+ {
18650
+ ipv4Prefix: "20.169.78.48/28"
18651
+ },
18652
+ {
18653
+ ipv4Prefix: "20.169.78.64/28"
18654
+ },
18655
+ {
18656
+ ipv4Prefix: "20.169.78.80/28"
18657
+ },
18658
+ {
18659
+ ipv4Prefix: "20.169.78.96/28"
18660
+ },
18661
+ {
18662
+ ipv4Prefix: "20.169.86.224/28"
18663
+ },
18664
+ {
18665
+ ipv4Prefix: "20.169.86.240/28"
18666
+ },
18667
+ {
18668
+ ipv4Prefix: "20.169.87.112/28"
18669
+ },
18670
+ {
18671
+ ipv4Prefix: "20.17.108.96/28"
18672
+ },
18673
+ {
18674
+ ipv4Prefix: "20.172.29.32/28"
18675
+ },
18676
+ {
18677
+ ipv4Prefix: "20.193.233.240/28"
18678
+ },
18679
+ {
18680
+ ipv4Prefix: "20.193.50.32/28"
18681
+ },
18682
+ {
18683
+ ipv4Prefix: "20.194.0.208/28"
18684
+ },
18685
+ {
18686
+ ipv4Prefix: "20.194.1.0/28"
18687
+ },
18688
+ {
18689
+ ipv4Prefix: "20.194.157.176/28"
18690
+ },
18691
+ {
18692
+ ipv4Prefix: "20.198.67.96/28"
18693
+ },
18694
+ {
18695
+ ipv4Prefix: "20.199.211.160/28"
18696
+ },
18697
+ {
18698
+ ipv4Prefix: "20.203.245.32/28"
18699
+ },
18700
+ {
18701
+ ipv4Prefix: "20.204.24.240/28"
18702
+ },
18703
+ {
18704
+ ipv4Prefix: "20.206.107.192/28"
18705
+ },
18706
+ {
18707
+ ipv4Prefix: "20.210.154.128/28"
18708
+ },
18709
+ {
18710
+ ipv4Prefix: "20.210.174.208/28"
18711
+ },
18712
+ {
18713
+ ipv4Prefix: "20.210.211.192/28"
18714
+ },
18715
+ {
18716
+ ipv4Prefix: "20.215.187.208/28"
18717
+ },
18718
+ {
18719
+ ipv4Prefix: "20.215.188.192/28"
18720
+ },
18721
+ {
18722
+ ipv4Prefix: "20.215.214.16/28"
18723
+ },
18724
+ {
18725
+ ipv4Prefix: "20.215.219.128/28"
18726
+ },
18727
+ {
18728
+ ipv4Prefix: "20.215.219.160/28"
18729
+ },
18730
+ {
18731
+ ipv4Prefix: "20.215.219.208/28"
18732
+ },
18733
+ {
18734
+ ipv4Prefix: "20.215.220.112/28"
18735
+ },
18736
+ {
18737
+ ipv4Prefix: "20.215.220.128/28"
18738
+ },
18739
+ {
18740
+ ipv4Prefix: "20.215.220.144/28"
18741
+ },
18742
+ {
18743
+ ipv4Prefix: "20.215.220.160/28"
18744
+ },
18745
+ {
18746
+ ipv4Prefix: "20.215.220.176/28"
18747
+ },
18748
+ {
18749
+ ipv4Prefix: "20.215.220.192/28"
18750
+ },
18751
+ {
18752
+ ipv4Prefix: "20.215.220.208/28"
18753
+ },
18754
+ {
18755
+ ipv4Prefix: "20.215.220.64/28"
18756
+ },
18757
+ {
18758
+ ipv4Prefix: "20.215.220.80/28"
18759
+ },
18760
+ {
18761
+ ipv4Prefix: "20.215.220.96/28"
18762
+ },
18763
+ {
18764
+ ipv4Prefix: "20.226.32.80/28"
18765
+ },
18766
+ {
18767
+ ipv4Prefix: "20.227.140.32/28"
18768
+ },
18769
+ {
18770
+ ipv4Prefix: "20.228.106.176/28"
18771
+ },
18772
+ {
18773
+ ipv4Prefix: "20.235.75.208/28"
18774
+ },
18775
+ {
18776
+ ipv4Prefix: "20.235.87.224/28"
18777
+ },
18778
+ {
18779
+ ipv4Prefix: "20.249.63.208/28"
18780
+ },
18781
+ {
18782
+ ipv4Prefix: "20.27.94.128/28"
18783
+ },
18784
+ {
18785
+ ipv4Prefix: "20.42.250.32/28"
18786
+ },
18787
+ {
18788
+ ipv4Prefix: "20.45.178.144/28"
18789
+ },
18790
+ {
18791
+ ipv4Prefix: "20.55.229.144/28"
18792
+ },
18793
+ {
18794
+ ipv4Prefix: "20.57.199.192/28"
18795
+ },
18796
+ {
18797
+ ipv4Prefix: "20.63.221.64/28"
18798
+ },
18799
+ {
18800
+ ipv4Prefix: "20.97.189.96/28"
18801
+ },
18802
+ {
18803
+ ipv4Prefix: "23.102.140.144/28"
18804
+ },
18805
+ {
18806
+ ipv4Prefix: "23.102.141.32/28"
18807
+ },
18808
+ {
18809
+ ipv4Prefix: "23.97.109.224/28"
18810
+ },
18811
+ {
18812
+ ipv4Prefix: "23.98.142.176/28"
18813
+ },
18814
+ {
18815
+ ipv4Prefix: "23.98.179.16/28"
18816
+ },
18817
+ {
18818
+ ipv4Prefix: "23.98.186.176/28"
18819
+ },
18820
+ {
18821
+ ipv4Prefix: "23.98.186.192/28"
18822
+ },
18823
+ {
18824
+ ipv4Prefix: "23.98.186.64/28"
18825
+ },
18826
+ {
18827
+ ipv4Prefix: "23.98.186.96/28"
18828
+ },
18829
+ {
18830
+ ipv4Prefix: "4.151.119.48/28"
18831
+ },
18832
+ {
18833
+ ipv4Prefix: "4.151.241.240/28"
18834
+ },
18835
+ {
18836
+ ipv4Prefix: "4.151.71.176/28"
18837
+ },
18838
+ {
18839
+ ipv4Prefix: "4.189.118.208/28"
18840
+ },
18841
+ {
18842
+ ipv4Prefix: "4.189.119.48/28"
18843
+ },
18844
+ {
18845
+ ipv4Prefix: "4.196.118.112/28"
18846
+ },
18847
+ {
18848
+ ipv4Prefix: "4.196.198.80/28"
18849
+ },
18850
+ {
18851
+ ipv4Prefix: "4.197.115.112/28"
18852
+ },
18853
+ {
18854
+ ipv4Prefix: "4.197.19.176/28"
18855
+ },
18856
+ {
18857
+ ipv4Prefix: "4.197.22.112/28"
18858
+ },
18859
+ {
18860
+ ipv4Prefix: "4.197.64.0/28"
18861
+ },
18862
+ {
18863
+ ipv4Prefix: "4.197.64.16/28"
18864
+ },
18865
+ {
18866
+ ipv4Prefix: "4.197.64.48/28"
18867
+ },
18868
+ {
18869
+ ipv4Prefix: "4.197.64.64/28"
18870
+ },
18871
+ {
18872
+ ipv4Prefix: "4.198.72.16/28"
18873
+ },
18874
+ {
18875
+ ipv4Prefix: "4.205.128.176/28"
18876
+ },
18877
+ {
18878
+ ipv4Prefix: "4.226.226.32/28"
18879
+ },
18880
+ {
18881
+ ipv4Prefix: "40.116.73.208/28"
18882
+ },
18883
+ {
18884
+ ipv4Prefix: "40.122.235.112/28"
18885
+ },
18886
+ {
18887
+ ipv4Prefix: "40.67.183.160/28"
18888
+ },
18889
+ {
18890
+ ipv4Prefix: "40.67.183.176/28"
18891
+ },
18892
+ {
18893
+ ipv4Prefix: "40.75.14.224/28"
18894
+ },
18895
+ {
18896
+ ipv4Prefix: "40.81.134.128/28"
18897
+ },
18898
+ {
18899
+ ipv4Prefix: "40.81.134.144/28"
18900
+ },
18901
+ {
18902
+ ipv4Prefix: "40.81.234.144/28"
18903
+ },
18904
+ {
18905
+ ipv4Prefix: "40.84.181.32/28"
18906
+ },
18907
+ {
18908
+ ipv4Prefix: "40.84.221.208/28"
18909
+ },
18910
+ {
18911
+ ipv4Prefix: "40.84.221.224/28"
18912
+ },
18913
+ {
18914
+ ipv4Prefix: "48.193.44.32/28"
18915
+ },
18916
+ {
18917
+ ipv4Prefix: "51.107.70.192/28"
18918
+ },
18919
+ {
18920
+ ipv4Prefix: "51.116.2.64/28"
18921
+ },
18922
+ {
18923
+ ipv4Prefix: "51.116.2.80/28"
18924
+ },
18925
+ {
18926
+ ipv4Prefix: "51.8.155.48/28"
18927
+ },
18928
+ {
18929
+ ipv4Prefix: "51.8.155.64/28"
18930
+ },
18931
+ {
18932
+ ipv4Prefix: "51.8.155.80/28"
18933
+ },
18934
+ {
18935
+ ipv4Prefix: "52.148.129.32/28"
18936
+ },
18937
+ {
18938
+ ipv4Prefix: "52.153.130.48/28"
18939
+ },
18940
+ {
18941
+ ipv4Prefix: "52.153.130.64/28"
18942
+ },
18943
+ {
18944
+ ipv4Prefix: "52.154.22.48/28"
18945
+ },
18946
+ {
18947
+ ipv4Prefix: "52.156.77.144/28"
18948
+ },
18949
+ {
18950
+ ipv4Prefix: "52.159.227.32/28"
18951
+ },
18952
+ {
18953
+ ipv4Prefix: "52.159.249.96/28"
18954
+ },
18955
+ {
18956
+ ipv4Prefix: "52.165.212.16/28"
18957
+ },
18958
+ {
18959
+ ipv4Prefix: "52.165.212.32/28"
18960
+ },
18961
+ {
18962
+ ipv4Prefix: "52.165.212.48/28"
18963
+ },
18964
+ {
18965
+ ipv4Prefix: "52.172.129.160/28"
18966
+ },
18967
+ {
18968
+ ipv4Prefix: "52.172.251.112/28"
18969
+ },
18970
+ {
18971
+ ipv4Prefix: "52.173.123.0/28"
18972
+ },
18973
+ {
18974
+ ipv4Prefix: "52.173.219.112/28"
18975
+ },
18976
+ {
18977
+ ipv4Prefix: "52.173.219.96/28"
18978
+ },
18979
+ {
18980
+ ipv4Prefix: "52.173.221.16/28"
18981
+ },
18982
+ {
18983
+ ipv4Prefix: "52.173.221.176/28"
18984
+ },
18985
+ {
18986
+ ipv4Prefix: "52.173.221.208/28"
18987
+ },
18988
+ {
18989
+ ipv4Prefix: "52.173.234.16/28"
18990
+ },
18991
+ {
18992
+ ipv4Prefix: "52.173.234.80/28"
18993
+ },
18994
+ {
18995
+ ipv4Prefix: "52.173.235.80/28"
18996
+ },
18997
+ {
18998
+ ipv4Prefix: "52.176.139.176/28"
18999
+ },
19000
+ {
19001
+ ipv4Prefix: "52.187.246.128/28"
19002
+ },
19003
+ {
19004
+ ipv4Prefix: "52.190.137.144/28"
19005
+ },
19006
+ {
19007
+ ipv4Prefix: "52.190.137.16/28"
19008
+ },
19009
+ {
19010
+ ipv4Prefix: "52.190.139.48/28"
19011
+ },
19012
+ {
19013
+ ipv4Prefix: "52.190.142.64/28"
19014
+ },
19015
+ {
19016
+ ipv4Prefix: "52.190.190.16/28"
19017
+ },
19018
+ {
19019
+ ipv4Prefix: "52.225.75.208/28"
19020
+ },
19021
+ {
19022
+ ipv4Prefix: "52.230.163.32/28"
19023
+ },
19024
+ {
19025
+ ipv4Prefix: "52.230.164.176/28"
19026
+ },
19027
+ {
19028
+ ipv4Prefix: "52.231.30.48/28"
19029
+ },
19030
+ {
19031
+ ipv4Prefix: "52.231.34.176/28"
19032
+ },
19033
+ {
19034
+ ipv4Prefix: "52.231.39.144/28"
19035
+ },
19036
+ {
19037
+ ipv4Prefix: "52.231.39.192/28"
19038
+ },
19039
+ {
19040
+ ipv4Prefix: "52.231.49.48/28"
19041
+ },
19042
+ {
19043
+ ipv4Prefix: "52.231.50.64/28"
19044
+ },
19045
+ {
19046
+ ipv4Prefix: "52.236.94.144/28"
19047
+ },
19048
+ {
19049
+ ipv4Prefix: "52.241.146.208/28"
19050
+ },
19051
+ {
19052
+ ipv4Prefix: "52.242.132.224/28"
19053
+ },
19054
+ {
19055
+ ipv4Prefix: "52.242.132.240/28"
19056
+ },
19057
+ {
19058
+ ipv4Prefix: "52.242.245.208/28"
19059
+ },
19060
+ {
19061
+ ipv4Prefix: "52.252.113.240/28"
19062
+ },
19063
+ {
19064
+ ipv4Prefix: "52.255.109.112/28"
19065
+ },
19066
+ {
19067
+ ipv4Prefix: "52.255.109.128/28"
19068
+ },
19069
+ {
19070
+ ipv4Prefix: "52.255.109.144/28"
19071
+ },
19072
+ {
19073
+ ipv4Prefix: "52.255.109.80/28"
19074
+ },
19075
+ {
19076
+ ipv4Prefix: "52.255.109.96/28"
19077
+ },
19078
+ {
19079
+ ipv4Prefix: "52.255.111.0/28"
19080
+ },
19081
+ {
19082
+ ipv4Prefix: "52.255.111.112/28"
19083
+ },
19084
+ {
19085
+ ipv4Prefix: "52.255.111.16/28"
19086
+ },
19087
+ {
19088
+ ipv4Prefix: "52.255.111.32/28"
19089
+ },
19090
+ {
19091
+ ipv4Prefix: "52.255.111.48/28"
19092
+ },
19093
+ {
19094
+ ipv4Prefix: "52.255.111.80/28"
19095
+ },
19096
+ {
19097
+ ipv4Prefix: "57.151.131.224/28"
19098
+ },
19099
+ {
19100
+ ipv4Prefix: "57.154.174.112/28"
19101
+ },
19102
+ {
19103
+ ipv4Prefix: "57.154.175.0/28"
19104
+ },
19105
+ {
19106
+ ipv4Prefix: "57.154.187.32/28"
19107
+ },
19108
+ {
19109
+ ipv4Prefix: "68.154.28.96/28"
19110
+ },
19111
+ {
19112
+ ipv4Prefix: "68.218.30.112/28"
19113
+ },
19114
+ {
19115
+ ipv4Prefix: "68.220.57.64/28"
19116
+ },
19117
+ {
19118
+ ipv4Prefix: "68.221.67.160/28"
19119
+ },
19120
+ {
19121
+ ipv4Prefix: "68.221.67.192/28"
19122
+ },
19123
+ {
19124
+ ipv4Prefix: "68.221.67.224/28"
19125
+ },
19126
+ {
19127
+ ipv4Prefix: "68.221.67.240/28"
19128
+ },
19129
+ {
19130
+ ipv4Prefix: "68.221.75.16/28"
19131
+ },
19132
+ {
19133
+ ipv4Prefix: "70.153.76.16/28"
19134
+ },
19135
+ {
19136
+ ipv4Prefix: "74.226.253.160/28"
19137
+ },
19138
+ {
19139
+ ipv4Prefix: "74.249.86.176/28"
19140
+ },
19141
+ {
19142
+ ipv4Prefix: "74.7.35.112/28"
19143
+ },
19144
+ {
19145
+ ipv4Prefix: "74.7.35.48/28"
19146
+ },
19147
+ {
19148
+ ipv4Prefix: "74.7.36.64/28"
19149
+ },
19150
+ {
19151
+ ipv4Prefix: "74.7.36.80/28"
19152
+ },
19153
+ {
19154
+ ipv4Prefix: "74.7.36.96/28"
19155
+ },
19156
+ {
19157
+ ipv4Prefix: "85.211.241.128/28"
19158
+ },
19159
+ {
19160
+ ipv4Prefix: "9.160.163.224/28"
19161
+ },
19162
+ {
19163
+ ipv4Prefix: "9.160.164.128/28"
19164
+ },
19165
+ {
19166
+ ipv4Prefix: "9.234.96.192/28"
19167
+ }
19168
+ ]
19169
+ };
19170
+
19171
+ // ../integration-traffic/src/ip-ranges/googlebot.json
19172
+ var googlebot_default = {
19173
+ _source: "https://developers.google.com/static/search/apis/ipranges/googlebot.json",
19174
+ creationTime: "2026-05-18T14:46:14.000000",
19175
+ prefixes: [
19176
+ {
19177
+ ipv6Prefix: "2001:4860:4801:10::/64"
19178
+ },
19179
+ {
19180
+ ipv6Prefix: "2001:4860:4801:11::/64"
19181
+ },
19182
+ {
19183
+ ipv6Prefix: "2001:4860:4801:12::/64"
19184
+ },
19185
+ {
19186
+ ipv6Prefix: "2001:4860:4801:13::/64"
19187
+ },
19188
+ {
19189
+ ipv6Prefix: "2001:4860:4801:14::/64"
19190
+ },
19191
+ {
19192
+ ipv6Prefix: "2001:4860:4801:15::/64"
19193
+ },
19194
+ {
19195
+ ipv6Prefix: "2001:4860:4801:16::/64"
19196
+ },
19197
+ {
19198
+ ipv6Prefix: "2001:4860:4801:17::/64"
19199
+ },
19200
+ {
19201
+ ipv6Prefix: "2001:4860:4801:18::/64"
19202
+ },
19203
+ {
19204
+ ipv6Prefix: "2001:4860:4801:19::/64"
19205
+ },
19206
+ {
19207
+ ipv6Prefix: "2001:4860:4801:1a::/64"
19208
+ },
19209
+ {
19210
+ ipv6Prefix: "2001:4860:4801:1b::/64"
19211
+ },
19212
+ {
19213
+ ipv6Prefix: "2001:4860:4801:1c::/64"
19214
+ },
19215
+ {
19216
+ ipv6Prefix: "2001:4860:4801:1d::/64"
19217
+ },
19218
+ {
19219
+ ipv6Prefix: "2001:4860:4801:1e::/64"
19220
+ },
19221
+ {
19222
+ ipv6Prefix: "2001:4860:4801:1f::/64"
19223
+ },
19224
+ {
19225
+ ipv6Prefix: "2001:4860:4801:20::/64"
19226
+ },
19227
+ {
19228
+ ipv6Prefix: "2001:4860:4801:21::/64"
19229
+ },
19230
+ {
19231
+ ipv6Prefix: "2001:4860:4801:22::/64"
19232
+ },
19233
+ {
19234
+ ipv6Prefix: "2001:4860:4801:23::/64"
19235
+ },
19236
+ {
19237
+ ipv6Prefix: "2001:4860:4801:24::/64"
19238
+ },
19239
+ {
19240
+ ipv6Prefix: "2001:4860:4801:25::/64"
19241
+ },
19242
+ {
19243
+ ipv6Prefix: "2001:4860:4801:26::/64"
19244
+ },
19245
+ {
19246
+ ipv6Prefix: "2001:4860:4801:27::/64"
19247
+ },
19248
+ {
19249
+ ipv6Prefix: "2001:4860:4801:28::/64"
19250
+ },
19251
+ {
19252
+ ipv6Prefix: "2001:4860:4801:29::/64"
19253
+ },
19254
+ {
19255
+ ipv6Prefix: "2001:4860:4801:2::/64"
19256
+ },
19257
+ {
19258
+ ipv6Prefix: "2001:4860:4801:2a::/64"
19259
+ },
19260
+ {
19261
+ ipv6Prefix: "2001:4860:4801:2b::/64"
19262
+ },
19263
+ {
19264
+ ipv6Prefix: "2001:4860:4801:2c::/64"
19265
+ },
19266
+ {
19267
+ ipv6Prefix: "2001:4860:4801:2d::/64"
19268
+ },
19269
+ {
19270
+ ipv6Prefix: "2001:4860:4801:2e::/64"
19271
+ },
19272
+ {
19273
+ ipv6Prefix: "2001:4860:4801:2f::/64"
19274
+ },
19275
+ {
19276
+ ipv6Prefix: "2001:4860:4801:30::/64"
19277
+ },
19278
+ {
19279
+ ipv6Prefix: "2001:4860:4801:31::/64"
19280
+ },
19281
+ {
19282
+ ipv6Prefix: "2001:4860:4801:32::/64"
19283
+ },
19284
+ {
19285
+ ipv6Prefix: "2001:4860:4801:33::/64"
19286
+ },
19287
+ {
19288
+ ipv6Prefix: "2001:4860:4801:34::/64"
19289
+ },
19290
+ {
19291
+ ipv6Prefix: "2001:4860:4801:35::/64"
19292
+ },
19293
+ {
19294
+ ipv6Prefix: "2001:4860:4801:36::/64"
19295
+ },
19296
+ {
19297
+ ipv6Prefix: "2001:4860:4801:37::/64"
19298
+ },
19299
+ {
19300
+ ipv6Prefix: "2001:4860:4801:38::/64"
19301
+ },
19302
+ {
19303
+ ipv6Prefix: "2001:4860:4801:39::/64"
19304
+ },
19305
+ {
19306
+ ipv6Prefix: "2001:4860:4801:3a::/64"
19307
+ },
19308
+ {
19309
+ ipv6Prefix: "2001:4860:4801:3b::/64"
19310
+ },
19311
+ {
19312
+ ipv6Prefix: "2001:4860:4801:3c::/64"
19313
+ },
19314
+ {
19315
+ ipv6Prefix: "2001:4860:4801:3d::/64"
19316
+ },
19317
+ {
19318
+ ipv6Prefix: "2001:4860:4801:3e::/64"
19319
+ },
19320
+ {
19321
+ ipv6Prefix: "2001:4860:4801:3f::/64"
19322
+ },
19323
+ {
19324
+ ipv6Prefix: "2001:4860:4801:40::/64"
19325
+ },
19326
+ {
19327
+ ipv6Prefix: "2001:4860:4801:41::/64"
19328
+ },
19329
+ {
19330
+ ipv6Prefix: "2001:4860:4801:42::/64"
19331
+ },
19332
+ {
19333
+ ipv6Prefix: "2001:4860:4801:44::/64"
19334
+ },
19335
+ {
19336
+ ipv6Prefix: "2001:4860:4801:45::/64"
19337
+ },
19338
+ {
19339
+ ipv6Prefix: "2001:4860:4801:46::/64"
19340
+ },
19341
+ {
19342
+ ipv6Prefix: "2001:4860:4801:47::/64"
19343
+ },
19344
+ {
19345
+ ipv6Prefix: "2001:4860:4801:48::/64"
19346
+ },
19347
+ {
19348
+ ipv6Prefix: "2001:4860:4801:49::/64"
19349
+ },
19350
+ {
19351
+ ipv6Prefix: "2001:4860:4801:4a::/64"
19352
+ },
19353
+ {
19354
+ ipv6Prefix: "2001:4860:4801:4b::/64"
19355
+ },
19356
+ {
19357
+ ipv6Prefix: "2001:4860:4801:4c::/64"
19358
+ },
19359
+ {
19360
+ ipv6Prefix: "2001:4860:4801:4d::/64"
19361
+ },
19362
+ {
19363
+ ipv6Prefix: "2001:4860:4801:4e::/64"
19364
+ },
19365
+ {
19366
+ ipv6Prefix: "2001:4860:4801:50::/64"
19367
+ },
19368
+ {
19369
+ ipv6Prefix: "2001:4860:4801:51::/64"
19370
+ },
19371
+ {
19372
+ ipv6Prefix: "2001:4860:4801:52::/64"
19373
+ },
19374
+ {
19375
+ ipv6Prefix: "2001:4860:4801:53::/64"
19376
+ },
19377
+ {
19378
+ ipv6Prefix: "2001:4860:4801:54::/64"
19379
+ },
19380
+ {
19381
+ ipv6Prefix: "2001:4860:4801:55::/64"
19382
+ },
19383
+ {
19384
+ ipv6Prefix: "2001:4860:4801:56::/64"
19385
+ },
19386
+ {
19387
+ ipv6Prefix: "2001:4860:4801:57::/64"
19388
+ },
19389
+ {
19390
+ ipv6Prefix: "2001:4860:4801:58::/64"
19391
+ },
19392
+ {
19393
+ ipv6Prefix: "2001:4860:4801:59::/64"
19394
+ },
19395
+ {
19396
+ ipv6Prefix: "2001:4860:4801:60::/64"
19397
+ },
19398
+ {
19399
+ ipv6Prefix: "2001:4860:4801:61::/64"
19400
+ },
19401
+ {
19402
+ ipv6Prefix: "2001:4860:4801:62::/64"
19403
+ },
19404
+ {
19405
+ ipv6Prefix: "2001:4860:4801:63::/64"
19406
+ },
19407
+ {
19408
+ ipv6Prefix: "2001:4860:4801:64::/64"
19409
+ },
19410
+ {
19411
+ ipv6Prefix: "2001:4860:4801:65::/64"
19412
+ },
19413
+ {
19414
+ ipv6Prefix: "2001:4860:4801:66::/64"
19415
+ },
19416
+ {
19417
+ ipv6Prefix: "2001:4860:4801:67::/64"
19418
+ },
19419
+ {
19420
+ ipv6Prefix: "2001:4860:4801:68::/64"
19421
+ },
19422
+ {
19423
+ ipv6Prefix: "2001:4860:4801:69::/64"
19424
+ },
19425
+ {
19426
+ ipv6Prefix: "2001:4860:4801:6a::/64"
19427
+ },
19428
+ {
19429
+ ipv6Prefix: "2001:4860:4801:6b::/64"
19430
+ },
19431
+ {
19432
+ ipv6Prefix: "2001:4860:4801:6c::/64"
19433
+ },
19434
+ {
19435
+ ipv6Prefix: "2001:4860:4801:6d::/64"
19436
+ },
19437
+ {
19438
+ ipv6Prefix: "2001:4860:4801:6e::/64"
19439
+ },
19440
+ {
19441
+ ipv6Prefix: "2001:4860:4801:6f::/64"
19442
+ },
19443
+ {
19444
+ ipv6Prefix: "2001:4860:4801:70::/64"
19445
+ },
19446
+ {
19447
+ ipv6Prefix: "2001:4860:4801:71::/64"
19448
+ },
19449
+ {
19450
+ ipv6Prefix: "2001:4860:4801:72::/64"
19451
+ },
19452
+ {
19453
+ ipv6Prefix: "2001:4860:4801:73::/64"
19454
+ },
19455
+ {
19456
+ ipv6Prefix: "2001:4860:4801:74::/64"
19457
+ },
19458
+ {
19459
+ ipv6Prefix: "2001:4860:4801:75::/64"
19460
+ },
19461
+ {
19462
+ ipv6Prefix: "2001:4860:4801:76::/64"
19463
+ },
19464
+ {
19465
+ ipv6Prefix: "2001:4860:4801:77::/64"
19466
+ },
19467
+ {
19468
+ ipv6Prefix: "2001:4860:4801:78::/64"
19469
+ },
19470
+ {
19471
+ ipv6Prefix: "2001:4860:4801:79::/64"
19472
+ },
19473
+ {
19474
+ ipv6Prefix: "2001:4860:4801:7a::/64"
19475
+ },
19476
+ {
19477
+ ipv6Prefix: "2001:4860:4801:7b::/64"
19478
+ },
19479
+ {
19480
+ ipv6Prefix: "2001:4860:4801:7c::/64"
19481
+ },
19482
+ {
19483
+ ipv6Prefix: "2001:4860:4801:7d::/64"
19484
+ },
19485
+ {
19486
+ ipv6Prefix: "2001:4860:4801:80::/64"
19487
+ },
19488
+ {
19489
+ ipv6Prefix: "2001:4860:4801:81::/64"
19490
+ },
19491
+ {
19492
+ ipv6Prefix: "2001:4860:4801:82::/64"
19493
+ },
19494
+ {
19495
+ ipv6Prefix: "2001:4860:4801:83::/64"
19496
+ },
19497
+ {
19498
+ ipv6Prefix: "2001:4860:4801:84::/64"
19499
+ },
19500
+ {
19501
+ ipv6Prefix: "2001:4860:4801:85::/64"
19502
+ },
19503
+ {
19504
+ ipv6Prefix: "2001:4860:4801:86::/64"
19505
+ },
19506
+ {
19507
+ ipv6Prefix: "2001:4860:4801:87::/64"
19508
+ },
19509
+ {
19510
+ ipv6Prefix: "2001:4860:4801:88::/64"
19511
+ },
19512
+ {
19513
+ ipv6Prefix: "2001:4860:4801:90::/64"
19514
+ },
19515
+ {
19516
+ ipv6Prefix: "2001:4860:4801:91::/64"
19517
+ },
19518
+ {
19519
+ ipv6Prefix: "2001:4860:4801:92::/64"
19520
+ },
19521
+ {
19522
+ ipv6Prefix: "2001:4860:4801:93::/64"
19523
+ },
19524
+ {
19525
+ ipv6Prefix: "2001:4860:4801:94::/64"
19526
+ },
19527
+ {
19528
+ ipv6Prefix: "2001:4860:4801:95::/64"
19529
+ },
19530
+ {
19531
+ ipv6Prefix: "2001:4860:4801:96::/64"
19532
+ },
19533
+ {
19534
+ ipv6Prefix: "2001:4860:4801:97::/64"
19535
+ },
19536
+ {
19537
+ ipv6Prefix: "2001:4860:4801:a0::/64"
19538
+ },
19539
+ {
19540
+ ipv6Prefix: "2001:4860:4801:a1::/64"
19541
+ },
19542
+ {
19543
+ ipv6Prefix: "2001:4860:4801:a2::/64"
19544
+ },
19545
+ {
19546
+ ipv6Prefix: "2001:4860:4801:a3::/64"
19547
+ },
19548
+ {
19549
+ ipv6Prefix: "2001:4860:4801:a4::/64"
19550
+ },
19551
+ {
19552
+ ipv6Prefix: "2001:4860:4801:a5::/64"
19553
+ },
19554
+ {
19555
+ ipv6Prefix: "2001:4860:4801:a6::/64"
19556
+ },
19557
+ {
19558
+ ipv6Prefix: "2001:4860:4801:a7::/64"
19559
+ },
19560
+ {
19561
+ ipv6Prefix: "2001:4860:4801:a8::/64"
19562
+ },
19563
+ {
19564
+ ipv6Prefix: "2001:4860:4801:a9::/64"
19565
+ },
19566
+ {
19567
+ ipv6Prefix: "2001:4860:4801:aa::/64"
19568
+ },
19569
+ {
19570
+ ipv6Prefix: "2001:4860:4801:ab::/64"
19571
+ },
19572
+ {
19573
+ ipv6Prefix: "2001:4860:4801:ac::/64"
19574
+ },
19575
+ {
19576
+ ipv6Prefix: "2001:4860:4801:ad::/64"
19577
+ },
19578
+ {
19579
+ ipv6Prefix: "2001:4860:4801:ae::/64"
19580
+ },
19581
+ {
19582
+ ipv6Prefix: "2001:4860:4801:b0::/64"
19583
+ },
19584
+ {
19585
+ ipv6Prefix: "2001:4860:4801:b1::/64"
19586
+ },
19587
+ {
19588
+ ipv6Prefix: "2001:4860:4801:b2::/64"
19589
+ },
19590
+ {
19591
+ ipv6Prefix: "2001:4860:4801:b3::/64"
19592
+ },
19593
+ {
19594
+ ipv6Prefix: "2001:4860:4801:b4::/64"
19595
+ },
19596
+ {
19597
+ ipv6Prefix: "2001:4860:4801:b5::/64"
19598
+ },
19599
+ {
19600
+ ipv6Prefix: "2001:4860:4801:b6::/64"
19601
+ },
19602
+ {
19603
+ ipv6Prefix: "2001:4860:4801:c::/64"
19604
+ },
19605
+ {
19606
+ ipv6Prefix: "2001:4860:4801:f::/64"
19607
+ },
19608
+ {
19609
+ ipv4Prefix: "192.178.4.0/27"
19610
+ },
19611
+ {
19612
+ ipv4Prefix: "192.178.4.128/27"
19613
+ },
19614
+ {
19615
+ ipv4Prefix: "192.178.4.160/27"
19616
+ },
19617
+ {
19618
+ ipv4Prefix: "192.178.4.192/27"
19619
+ },
19620
+ {
19621
+ ipv4Prefix: "192.178.4.224/27"
19622
+ },
19623
+ {
19624
+ ipv4Prefix: "192.178.4.32/27"
19625
+ },
19626
+ {
19627
+ ipv4Prefix: "192.178.4.64/27"
19628
+ },
19629
+ {
19630
+ ipv4Prefix: "192.178.4.96/27"
19631
+ },
19632
+ {
19633
+ ipv4Prefix: "192.178.5.0/27"
19634
+ },
19635
+ {
19636
+ ipv4Prefix: "192.178.6.0/27"
19637
+ },
19638
+ {
19639
+ ipv4Prefix: "192.178.6.128/27"
19640
+ },
19641
+ {
19642
+ ipv4Prefix: "192.178.6.160/27"
19643
+ },
19644
+ {
19645
+ ipv4Prefix: "192.178.6.192/27"
19646
+ },
19647
+ {
19648
+ ipv4Prefix: "192.178.6.224/27"
19649
+ },
19650
+ {
19651
+ ipv4Prefix: "192.178.6.32/27"
19652
+ },
19653
+ {
19654
+ ipv4Prefix: "192.178.6.64/27"
19655
+ },
19656
+ {
19657
+ ipv4Prefix: "192.178.6.96/27"
19658
+ },
19659
+ {
19660
+ ipv4Prefix: "192.178.7.0/27"
19661
+ },
19662
+ {
19663
+ ipv4Prefix: "192.178.7.128/27"
19664
+ },
19665
+ {
19666
+ ipv4Prefix: "192.178.7.160/27"
19667
+ },
19668
+ {
19669
+ ipv4Prefix: "192.178.7.192/27"
19670
+ },
19671
+ {
19672
+ ipv4Prefix: "192.178.7.224/27"
19673
+ },
19674
+ {
19675
+ ipv4Prefix: "192.178.7.32/27"
19676
+ },
19677
+ {
19678
+ ipv4Prefix: "192.178.7.64/27"
19679
+ },
19680
+ {
19681
+ ipv4Prefix: "192.178.7.96/27"
19682
+ },
19683
+ {
19684
+ ipv4Prefix: "34.100.182.96/28"
19685
+ },
19686
+ {
19687
+ ipv4Prefix: "34.101.50.144/28"
19688
+ },
19689
+ {
19690
+ ipv4Prefix: "34.118.254.0/28"
19691
+ },
19692
+ {
19693
+ ipv4Prefix: "34.118.66.0/28"
19694
+ },
19695
+ {
19696
+ ipv4Prefix: "34.126.178.96/28"
19697
+ },
19698
+ {
19699
+ ipv4Prefix: "34.146.150.144/28"
19700
+ },
19701
+ {
19702
+ ipv4Prefix: "34.147.110.144/28"
19703
+ },
19704
+ {
19705
+ ipv4Prefix: "34.151.74.144/28"
19706
+ },
19707
+ {
19708
+ ipv4Prefix: "34.152.50.64/28"
19709
+ },
19710
+ {
19711
+ ipv4Prefix: "34.154.114.144/28"
19712
+ },
19713
+ {
19714
+ ipv4Prefix: "34.155.98.32/28"
19715
+ },
19716
+ {
19717
+ ipv4Prefix: "34.165.18.176/28"
19718
+ },
19719
+ {
19720
+ ipv4Prefix: "34.175.160.64/28"
19721
+ },
19722
+ {
19723
+ ipv4Prefix: "34.176.130.16/28"
19724
+ },
19725
+ {
19726
+ ipv4Prefix: "34.22.85.0/27"
19727
+ },
19728
+ {
19729
+ ipv4Prefix: "34.64.82.64/28"
19730
+ },
19731
+ {
19732
+ ipv4Prefix: "34.65.242.112/28"
19733
+ },
19734
+ {
19735
+ ipv4Prefix: "34.80.50.80/28"
19736
+ },
19737
+ {
19738
+ ipv4Prefix: "34.88.194.0/28"
19739
+ },
19740
+ {
19741
+ ipv4Prefix: "34.89.10.80/28"
19742
+ },
19743
+ {
19744
+ ipv4Prefix: "34.89.198.80/28"
19745
+ },
19746
+ {
19747
+ ipv4Prefix: "34.96.162.48/28"
19748
+ },
19749
+ {
19750
+ ipv4Prefix: "35.247.243.240/28"
19751
+ },
19752
+ {
19753
+ ipv4Prefix: "66.249.64.0/27"
19754
+ },
19755
+ {
19756
+ ipv4Prefix: "66.249.64.128/27"
19757
+ },
19758
+ {
19759
+ ipv4Prefix: "66.249.64.160/27"
19760
+ },
19761
+ {
19762
+ ipv4Prefix: "66.249.64.192/27"
19763
+ },
19764
+ {
19765
+ ipv4Prefix: "66.249.64.224/27"
19766
+ },
19767
+ {
19768
+ ipv4Prefix: "66.249.64.32/27"
19769
+ },
19770
+ {
19771
+ ipv4Prefix: "66.249.64.64/27"
19772
+ },
19773
+ {
19774
+ ipv4Prefix: "66.249.64.96/27"
19775
+ },
19776
+ {
19777
+ ipv4Prefix: "66.249.65.0/27"
19778
+ },
19779
+ {
19780
+ ipv4Prefix: "66.249.65.128/27"
19781
+ },
19782
+ {
19783
+ ipv4Prefix: "66.249.65.160/27"
19784
+ },
19785
+ {
19786
+ ipv4Prefix: "66.249.65.192/27"
19787
+ },
19788
+ {
19789
+ ipv4Prefix: "66.249.65.224/27"
19790
+ },
19791
+ {
19792
+ ipv4Prefix: "66.249.65.32/27"
19793
+ },
19794
+ {
19795
+ ipv4Prefix: "66.249.65.64/27"
19796
+ },
19797
+ {
19798
+ ipv4Prefix: "66.249.65.96/27"
19799
+ },
19800
+ {
19801
+ ipv4Prefix: "66.249.66.0/27"
19802
+ },
19803
+ {
19804
+ ipv4Prefix: "66.249.66.128/27"
19805
+ },
19806
+ {
19807
+ ipv4Prefix: "66.249.66.160/27"
19808
+ },
19809
+ {
19810
+ ipv4Prefix: "66.249.66.192/27"
19811
+ },
19812
+ {
19813
+ ipv4Prefix: "66.249.66.224/27"
19814
+ },
19815
+ {
19816
+ ipv4Prefix: "66.249.66.32/27"
19817
+ },
19818
+ {
19819
+ ipv4Prefix: "66.249.66.64/27"
19820
+ },
19821
+ {
19822
+ ipv4Prefix: "66.249.66.96/27"
19823
+ },
19824
+ {
19825
+ ipv4Prefix: "66.249.67.0/27"
19826
+ },
19827
+ {
19828
+ ipv4Prefix: "66.249.67.32/27"
19829
+ },
19830
+ {
19831
+ ipv4Prefix: "66.249.67.64/27"
19832
+ },
19833
+ {
19834
+ ipv4Prefix: "66.249.68.0/27"
19835
+ },
19836
+ {
19837
+ ipv4Prefix: "66.249.68.128/27"
19838
+ },
19839
+ {
19840
+ ipv4Prefix: "66.249.68.160/27"
19841
+ },
19842
+ {
19843
+ ipv4Prefix: "66.249.68.192/27"
19844
+ },
19845
+ {
19846
+ ipv4Prefix: "66.249.68.32/27"
19847
+ },
19848
+ {
19849
+ ipv4Prefix: "66.249.68.64/27"
19850
+ },
19851
+ {
19852
+ ipv4Prefix: "66.249.68.96/27"
19853
+ },
19854
+ {
19855
+ ipv4Prefix: "66.249.69.0/27"
19856
+ },
19857
+ {
19858
+ ipv4Prefix: "66.249.69.128/27"
19859
+ },
19860
+ {
19861
+ ipv4Prefix: "66.249.69.160/27"
19862
+ },
19863
+ {
19864
+ ipv4Prefix: "66.249.69.192/27"
19865
+ },
19866
+ {
19867
+ ipv4Prefix: "66.249.69.224/27"
19868
+ },
19869
+ {
19870
+ ipv4Prefix: "66.249.69.32/27"
19871
+ },
19872
+ {
19873
+ ipv4Prefix: "66.249.69.64/27"
19874
+ },
19875
+ {
19876
+ ipv4Prefix: "66.249.69.96/27"
19877
+ },
19878
+ {
19879
+ ipv4Prefix: "66.249.70.0/27"
19880
+ },
19881
+ {
19882
+ ipv4Prefix: "66.249.70.128/27"
19883
+ },
19884
+ {
19885
+ ipv4Prefix: "66.249.70.160/27"
19886
+ },
19887
+ {
19888
+ ipv4Prefix: "66.249.70.192/27"
19889
+ },
19890
+ {
19891
+ ipv4Prefix: "66.249.70.224/27"
19892
+ },
19893
+ {
19894
+ ipv4Prefix: "66.249.70.32/27"
19895
+ },
19896
+ {
19897
+ ipv4Prefix: "66.249.70.64/27"
19898
+ },
19899
+ {
19900
+ ipv4Prefix: "66.249.70.96/27"
19901
+ },
19902
+ {
19903
+ ipv4Prefix: "66.249.71.0/27"
19904
+ },
19905
+ {
19906
+ ipv4Prefix: "66.249.71.128/27"
19907
+ },
19908
+ {
19909
+ ipv4Prefix: "66.249.71.160/27"
19910
+ },
19911
+ {
19912
+ ipv4Prefix: "66.249.71.192/27"
19913
+ },
19914
+ {
19915
+ ipv4Prefix: "66.249.71.224/27"
19916
+ },
19917
+ {
19918
+ ipv4Prefix: "66.249.71.32/27"
19919
+ },
19920
+ {
19921
+ ipv4Prefix: "66.249.71.64/27"
19922
+ },
19923
+ {
19924
+ ipv4Prefix: "66.249.71.96/27"
19925
+ },
19926
+ {
19927
+ ipv4Prefix: "66.249.72.0/27"
19928
+ },
19929
+ {
19930
+ ipv4Prefix: "66.249.72.128/27"
19931
+ },
19932
+ {
19933
+ ipv4Prefix: "66.249.72.160/27"
19934
+ },
19935
+ {
19936
+ ipv4Prefix: "66.249.72.192/27"
19937
+ },
19938
+ {
19939
+ ipv4Prefix: "66.249.72.224/27"
19940
+ },
19941
+ {
19942
+ ipv4Prefix: "66.249.72.32/27"
19943
+ },
19944
+ {
19945
+ ipv4Prefix: "66.249.72.64/27"
19946
+ },
19947
+ {
19948
+ ipv4Prefix: "66.249.72.96/27"
19949
+ },
19950
+ {
19951
+ ipv4Prefix: "66.249.73.0/27"
19952
+ },
19953
+ {
19954
+ ipv4Prefix: "66.249.73.128/27"
19955
+ },
19956
+ {
19957
+ ipv4Prefix: "66.249.73.160/27"
19958
+ },
19959
+ {
19960
+ ipv4Prefix: "66.249.73.192/27"
19961
+ },
19962
+ {
19963
+ ipv4Prefix: "66.249.73.224/27"
19964
+ },
19965
+ {
19966
+ ipv4Prefix: "66.249.73.32/27"
19967
+ },
19968
+ {
19969
+ ipv4Prefix: "66.249.73.64/27"
19970
+ },
19971
+ {
19972
+ ipv4Prefix: "66.249.73.96/27"
19973
+ },
19974
+ {
19975
+ ipv4Prefix: "66.249.74.0/27"
19976
+ },
19977
+ {
19978
+ ipv4Prefix: "66.249.74.128/27"
19979
+ },
19980
+ {
19981
+ ipv4Prefix: "66.249.74.160/27"
19982
+ },
19983
+ {
19984
+ ipv4Prefix: "66.249.74.192/27"
19985
+ },
19986
+ {
19987
+ ipv4Prefix: "66.249.74.224/27"
19988
+ },
19989
+ {
19990
+ ipv4Prefix: "66.249.74.32/27"
19991
+ },
19992
+ {
19993
+ ipv4Prefix: "66.249.74.64/27"
19994
+ },
19995
+ {
19996
+ ipv4Prefix: "66.249.74.96/27"
19997
+ },
19998
+ {
19999
+ ipv4Prefix: "66.249.75.0/27"
20000
+ },
20001
+ {
20002
+ ipv4Prefix: "66.249.75.128/27"
20003
+ },
20004
+ {
20005
+ ipv4Prefix: "66.249.75.160/27"
20006
+ },
20007
+ {
20008
+ ipv4Prefix: "66.249.75.192/27"
20009
+ },
20010
+ {
20011
+ ipv4Prefix: "66.249.75.224/27"
20012
+ },
20013
+ {
20014
+ ipv4Prefix: "66.249.75.32/27"
20015
+ },
20016
+ {
20017
+ ipv4Prefix: "66.249.75.64/27"
20018
+ },
20019
+ {
20020
+ ipv4Prefix: "66.249.75.96/27"
20021
+ },
20022
+ {
20023
+ ipv4Prefix: "66.249.76.0/27"
20024
+ },
20025
+ {
20026
+ ipv4Prefix: "66.249.76.128/27"
20027
+ },
20028
+ {
20029
+ ipv4Prefix: "66.249.76.160/27"
20030
+ },
20031
+ {
20032
+ ipv4Prefix: "66.249.76.192/27"
20033
+ },
20034
+ {
20035
+ ipv4Prefix: "66.249.76.224/27"
20036
+ },
20037
+ {
20038
+ ipv4Prefix: "66.249.76.32/27"
20039
+ },
20040
+ {
20041
+ ipv4Prefix: "66.249.76.64/27"
20042
+ },
20043
+ {
20044
+ ipv4Prefix: "66.249.76.96/27"
20045
+ },
20046
+ {
20047
+ ipv4Prefix: "66.249.77.0/27"
20048
+ },
20049
+ {
20050
+ ipv4Prefix: "66.249.77.128/27"
20051
+ },
20052
+ {
20053
+ ipv4Prefix: "66.249.77.160/27"
20054
+ },
20055
+ {
20056
+ ipv4Prefix: "66.249.77.192/27"
20057
+ },
20058
+ {
20059
+ ipv4Prefix: "66.249.77.224/27"
20060
+ },
20061
+ {
20062
+ ipv4Prefix: "66.249.77.32/27"
20063
+ },
20064
+ {
20065
+ ipv4Prefix: "66.249.77.64/27"
20066
+ },
20067
+ {
20068
+ ipv4Prefix: "66.249.77.96/27"
20069
+ },
20070
+ {
20071
+ ipv4Prefix: "66.249.78.0/27"
20072
+ },
20073
+ {
20074
+ ipv4Prefix: "66.249.78.128/27"
20075
+ },
20076
+ {
20077
+ ipv4Prefix: "66.249.78.160/27"
20078
+ },
20079
+ {
20080
+ ipv4Prefix: "66.249.78.32/27"
20081
+ },
20082
+ {
20083
+ ipv4Prefix: "66.249.78.64/27"
20084
+ },
20085
+ {
20086
+ ipv4Prefix: "66.249.78.96/27"
20087
+ },
20088
+ {
20089
+ ipv4Prefix: "66.249.79.0/27"
20090
+ },
20091
+ {
20092
+ ipv4Prefix: "66.249.79.128/27"
20093
+ },
20094
+ {
20095
+ ipv4Prefix: "66.249.79.160/27"
20096
+ },
20097
+ {
20098
+ ipv4Prefix: "66.249.79.192/27"
20099
+ },
20100
+ {
20101
+ ipv4Prefix: "66.249.79.224/27"
20102
+ },
20103
+ {
20104
+ ipv4Prefix: "66.249.79.32/27"
20105
+ },
20106
+ {
20107
+ ipv4Prefix: "66.249.79.64/27"
20108
+ }
20109
+ ]
20110
+ };
20111
+
20112
+ // ../integration-traffic/src/ip-ranges/gptbot.json
20113
+ var gptbot_default = {
20114
+ _source: "https://openai.com/gptbot.json",
20115
+ creationTime: "2025-10-30T11:00:00.000000",
20116
+ prefixes: [
20117
+ {
20118
+ ipv4Prefix: "132.196.86.0/24"
20119
+ },
20120
+ {
20121
+ ipv4Prefix: "172.182.202.0/25"
20122
+ },
20123
+ {
20124
+ ipv4Prefix: "172.182.204.0/24"
20125
+ },
20126
+ {
20127
+ ipv4Prefix: "172.182.207.0/25"
20128
+ },
20129
+ {
20130
+ ipv4Prefix: "172.182.214.0/24"
20131
+ },
20132
+ {
20133
+ ipv4Prefix: "172.182.215.0/24"
20134
+ },
20135
+ {
20136
+ ipv4Prefix: "20.125.66.80/28"
20137
+ },
20138
+ {
20139
+ ipv4Prefix: "20.171.206.0/24"
20140
+ },
20141
+ {
20142
+ ipv4Prefix: "20.171.207.0/24"
20143
+ },
20144
+ {
20145
+ ipv4Prefix: "4.227.36.0/25"
20146
+ },
20147
+ {
20148
+ ipv4Prefix: "52.230.152.0/24"
20149
+ },
20150
+ {
20151
+ ipv4Prefix: "74.7.175.128/25"
20152
+ },
20153
+ {
20154
+ ipv4Prefix: "74.7.227.0/25"
20155
+ },
20156
+ {
20157
+ ipv4Prefix: "74.7.227.128/25"
20158
+ },
20159
+ {
20160
+ ipv4Prefix: "74.7.228.0/25"
20161
+ },
20162
+ {
20163
+ ipv4Prefix: "74.7.230.0/25"
20164
+ },
20165
+ {
20166
+ ipv4Prefix: "74.7.241.0/25"
20167
+ },
20168
+ {
20169
+ ipv4Prefix: "74.7.241.128/25"
20170
+ },
20171
+ {
20172
+ ipv4Prefix: "74.7.242.0/25"
20173
+ },
20174
+ {
20175
+ ipv4Prefix: "74.7.243.128/25"
20176
+ },
20177
+ {
20178
+ ipv4Prefix: "74.7.244.0/25"
20179
+ }
20180
+ ]
20181
+ };
20182
+
20183
+ // ../integration-traffic/src/ip-ranges/oai-searchbot.json
20184
+ var oai_searchbot_default = {
20185
+ _source: "https://openai.com/searchbot.json",
20186
+ creationTime: "2026-01-02T11:00:00.000000",
20187
+ prefixes: [
20188
+ {
20189
+ ipv4Prefix: "104.210.140.128/28"
20190
+ },
20191
+ {
20192
+ ipv4Prefix: "135.234.64.0/24"
20193
+ },
20194
+ {
20195
+ ipv4Prefix: "172.182.193.224/28"
20196
+ },
20197
+ {
20198
+ ipv4Prefix: "172.182.193.80/28"
20199
+ },
20200
+ {
20201
+ ipv4Prefix: "172.182.194.144/28"
20202
+ },
20203
+ {
20204
+ ipv4Prefix: "172.182.194.32/28"
20205
+ },
20206
+ {
20207
+ ipv4Prefix: "172.182.195.48/28"
20208
+ },
20209
+ {
20210
+ ipv4Prefix: "172.182.209.208/28"
20211
+ },
20212
+ {
20213
+ ipv4Prefix: "172.182.211.192/28"
20214
+ },
20215
+ {
20216
+ ipv4Prefix: "172.182.213.192/28"
20217
+ },
20218
+ {
20219
+ ipv4Prefix: "172.182.224.0/28"
20220
+ },
20221
+ {
20222
+ ipv4Prefix: "172.203.190.128/28"
20223
+ },
20224
+ {
20225
+ ipv4Prefix: "20.14.99.96/28"
20226
+ },
20227
+ {
20228
+ ipv4Prefix: "20.168.18.32/28"
20229
+ },
20230
+ {
20231
+ ipv4Prefix: "20.169.6.224/28"
20232
+ },
20233
+ {
20234
+ ipv4Prefix: "20.169.7.48/28"
20235
+ },
20236
+ {
20237
+ ipv4Prefix: "20.169.77.0/25"
20238
+ },
20239
+ {
20240
+ ipv4Prefix: "20.171.123.64/28"
20241
+ },
20242
+ {
20243
+ ipv4Prefix: "20.171.53.224/28"
20244
+ },
20245
+ {
20246
+ ipv4Prefix: "20.25.151.224/28"
20247
+ },
20248
+ {
20249
+ ipv4Prefix: "20.42.10.176/28"
20250
+ },
20251
+ {
20252
+ ipv4Prefix: "4.227.36.0/25"
20253
+ },
20254
+ {
20255
+ ipv4Prefix: "40.67.175.0/25"
20256
+ },
20257
+ {
20258
+ ipv4Prefix: "40.90.214.16/28"
20259
+ },
20260
+ {
20261
+ ipv4Prefix: "51.8.102.0/24"
20262
+ },
20263
+ {
20264
+ ipv4Prefix: "74.7.175.128/25"
20265
+ },
20266
+ {
20267
+ ipv4Prefix: "74.7.228.0/25"
20268
+ },
20269
+ {
20270
+ ipv4Prefix: "74.7.228.128/25"
20271
+ },
20272
+ {
20273
+ ipv4Prefix: "74.7.229.0/25"
20274
+ },
20275
+ {
20276
+ ipv4Prefix: "74.7.229.128/25"
20277
+ },
20278
+ {
20279
+ ipv4Prefix: "74.7.230.0/25"
20280
+ },
20281
+ {
20282
+ ipv4Prefix: "74.7.241.128/25"
20283
+ },
20284
+ {
20285
+ ipv4Prefix: "74.7.242.128/25"
20286
+ },
20287
+ {
20288
+ ipv4Prefix: "74.7.243.0/25"
20289
+ },
20290
+ {
20291
+ ipv4Prefix: "74.7.244.0/25"
20292
+ }
20293
+ ]
20294
+ };
20295
+
20296
+ // ../integration-traffic/src/ip-ranges/perplexity-user.json
20297
+ var perplexity_user_default = {
20298
+ _source: "https://www.perplexity.ai/perplexity-user.json",
20299
+ creationTime: "2025-10-17T10:17:00.000000",
20300
+ prefixes: [
20301
+ {
20302
+ ipv4Prefix: "44.208.221.197/32"
20303
+ },
20304
+ {
20305
+ ipv4Prefix: "34.193.163.52/32"
20306
+ },
20307
+ {
20308
+ ipv4Prefix: "18.97.21.0/30"
20309
+ },
20310
+ {
20311
+ ipv4Prefix: "18.97.43.80/29"
20312
+ }
20313
+ ]
20314
+ };
20315
+
20316
+ // ../integration-traffic/src/ip-ranges/perplexitybot.json
20317
+ var perplexitybot_default = {
20318
+ _source: "https://www.perplexity.ai/perplexitybot.json",
20319
+ creationTime: "2025-02-07T16:56:00.000000",
20320
+ prefixes: [
20321
+ {
20322
+ ipv4Prefix: "107.20.236.150/32"
20323
+ },
20324
+ {
20325
+ ipv4Prefix: "3.224.62.45/32"
20326
+ },
20327
+ {
20328
+ ipv4Prefix: "18.210.92.235/32"
20329
+ },
20330
+ {
20331
+ ipv4Prefix: "3.222.232.239/32"
20332
+ },
20333
+ {
20334
+ ipv4Prefix: "3.211.124.183/32"
20335
+ },
20336
+ {
20337
+ ipv4Prefix: "3.231.139.107/32"
20338
+ },
20339
+ {
20340
+ ipv4Prefix: "18.97.1.228/30"
20341
+ },
20342
+ {
20343
+ ipv4Prefix: "18.97.9.96/29"
20344
+ }
20345
+ ]
20346
+ };
20347
+
20348
+ // ../integration-traffic/src/ip-verify.ts
20349
+ var RULE_ID_TO_RANGES = {
20350
+ // OpenAI — three separate published lists (training crawler vs
20351
+ // user-on-behalf fetcher vs search engine; OpenAI maintains the
20352
+ // split because the IPs really do differ between products).
20353
+ // src: https://openai.com/gptbot.json
20354
+ "openai-gptbot": gptbot_default,
20355
+ // src: https://openai.com/chatgpt-user.json
20356
+ "openai-chatgpt-user": chatgpt_user_default,
20357
+ // src: https://openai.com/searchbot.json
20358
+ "openai-searchbot": oai_searchbot_default,
20359
+ // Search engines.
20360
+ // src: https://developers.google.com/static/search/apis/ipranges/googlebot.json
20361
+ // (also covers Gemini grounding — Google doesn't publish a
20362
+ // separate Gemini list; Google-Extended traffic comes from the
20363
+ // same Googlebot ranges)
20364
+ "googlebot": googlebot_default,
20365
+ // src: https://www.bing.com/toolbox/bingbot.json
20366
+ // (also covers Copilot grounding — Microsoft routes Copilot's
20367
+ // web fetches through bingbot infrastructure)
20368
+ "bingbot": bingbot_default,
20369
+ // Perplexity — split between crawler and user-on-behalf fetcher,
20370
+ // same shape as OpenAI's split.
20371
+ // src: https://www.perplexity.ai/perplexitybot.json
20372
+ "perplexity-bot": perplexitybot_default,
20373
+ // src: https://www.perplexity.ai/perplexity-user.json
20374
+ "perplexity-user": perplexity_user_default,
20375
+ // Anthropic — no machine-readable JSON published. The bundled
20376
+ // anthropic.json is the set of networks registered to Anthropic,
20377
+ // PBC at ARIN (the authoritative allocation record). Maintained by
20378
+ // hand; refresh by re-querying the ARIN entity below. The crawler
20379
+ // block is AWS-ANTHROPIC 216.73.216.0/22 — empirical Cloud Run
20380
+ // logs show all real ClaudeBot traffic comes from there. Same raw
20381
+ // set is shared across every Claude-* UA the classifier emits.
20382
+ // src: https://rdap.arin.net/registry/entity/AP-2440
20383
+ "anthropic-claudebot": anthropic_default
20384
+ };
20385
+ var CACHE = (() => {
20386
+ const cache = /* @__PURE__ */ new Map();
20387
+ for (const [ruleId, raw] of Object.entries(RULE_ID_TO_RANGES)) {
20388
+ const parsed = [];
20389
+ for (const entry of raw.prefixes) {
20390
+ const cidr = entry.ipv4Prefix ?? entry.ipv6Prefix;
20391
+ if (!cidr) continue;
20392
+ const p = parseCidr(cidr);
20393
+ if (p) parsed.push(p);
20394
+ }
20395
+ cache.set(ruleId, parsed);
20396
+ }
20397
+ return cache;
20398
+ })();
20399
+ function parseIp(ip) {
20400
+ if (!ip) return null;
20401
+ const mappedMatch = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(ip);
20402
+ if (mappedMatch) return parseIp(mappedMatch[1]);
20403
+ if (ip.includes(":")) {
20404
+ const sides = ip.split("::");
20405
+ if (sides.length > 2) return null;
20406
+ const left = sides[0].length > 0 ? sides[0].split(":") : [];
20407
+ const right = sides.length === 2 && sides[1].length > 0 ? sides[1].split(":") : [];
20408
+ const groupCount = left.length + right.length;
20409
+ if (groupCount > 8) return null;
20410
+ if (sides.length === 1 && groupCount !== 8) return null;
20411
+ const fill = 8 - groupCount;
20412
+ const groups = [...left, ...new Array(fill).fill("0"), ...right];
20413
+ let addr2 = 0n;
20414
+ for (const g of groups) {
20415
+ if (g.length === 0 || g.length > 4) return null;
20416
+ const n = Number.parseInt(g, 16);
20417
+ if (!Number.isFinite(n) || n < 0 || n > 65535) return null;
20418
+ addr2 = addr2 << 16n | BigInt(n);
20419
+ }
20420
+ return { version: 6, addr: addr2 };
20421
+ }
20422
+ const octets = ip.split(".");
20423
+ if (octets.length !== 4) return null;
20424
+ let addr = 0n;
20425
+ for (const o of octets) {
20426
+ if (o.length === 0 || o.length > 3) return null;
20427
+ const n = Number.parseInt(o, 10);
20428
+ if (!Number.isInteger(n) || n < 0 || n > 255) return null;
20429
+ addr = addr << 8n | BigInt(n);
20430
+ }
20431
+ return { version: 4, addr };
20432
+ }
20433
+ function parseCidr(cidr) {
20434
+ const [ipPart, prefixStr] = cidr.split("/");
20435
+ if (!ipPart || !prefixStr) return null;
20436
+ const prefix = Number.parseInt(prefixStr, 10);
20437
+ if (!Number.isInteger(prefix)) return null;
20438
+ const parsed = parseIp(ipPart);
20439
+ if (!parsed) return null;
20440
+ const totalBits = parsed.version === 4 ? 32 : 128;
20441
+ if (prefix < 0 || prefix > totalBits) return null;
20442
+ const allOnes = (1n << BigInt(totalBits)) - 1n;
20443
+ const mask = allOnes >> BigInt(totalBits - prefix) << BigInt(totalBits - prefix);
20444
+ return {
20445
+ version: parsed.version,
20446
+ network: parsed.addr & mask,
20447
+ mask
20448
+ };
20449
+ }
20450
+ function verifyIpForRule(ip, ruleId) {
20451
+ if (!ip) return false;
20452
+ const ranges = CACHE.get(ruleId);
20453
+ if (!ranges || ranges.length === 0) return false;
20454
+ const parsed = parseIp(ip);
20455
+ if (!parsed) return false;
20456
+ for (const cidr of ranges) {
20457
+ if (parsed.version !== cidr.version) continue;
20458
+ if ((parsed.addr & cidr.mask) === cidr.network) return true;
20459
+ }
20460
+ return false;
20461
+ }
20462
+
20463
+ // ../integration-traffic/src/rules.ts
20464
+ var LEGACY_CHATGPT_DOMAIN = "chat.openai.com";
20465
+ var DEFAULT_AI_CRAWLER_RULES = [
20466
+ {
20467
+ id: "openai-gptbot",
20468
+ operator: "OpenAI",
20469
+ product: "GPTBot",
20470
+ purpose: "training",
20471
+ userAgentPatterns: [/GPTBot\//i]
20472
+ },
20473
+ {
20474
+ id: "openai-searchbot",
20475
+ operator: "OpenAI",
20476
+ product: "OAI-SearchBot",
20477
+ purpose: "search",
20478
+ userAgentPatterns: [/OAI-SearchBot\//i]
20479
+ },
20480
+ {
20481
+ id: "openai-chatgpt-user",
20482
+ operator: "OpenAI",
20483
+ product: "ChatGPT-User",
20484
+ purpose: "user-agent",
20485
+ userAgentPatterns: [/ChatGPT-User\//i]
20486
+ },
20487
+ {
20488
+ id: "anthropic-claudebot",
20489
+ operator: "Anthropic",
20490
+ product: "ClaudeBot",
20491
+ purpose: "training",
20492
+ // Anthropic ships several Claude-* crawlers (ClaudeBot for training,
20493
+ // Claude-Web for chat fetches, Claude-SearchBot for search). The
20494
+ // `Claude-` prefix + `Bot/` suffix is the stable shape — pattern is
20495
+ // permissive enough to catch new Claude-* variants as Anthropic
20496
+ // adds them, without matching unrelated UAs that happen to mention
20497
+ // "claude".
20498
+ userAgentPatterns: [
20499
+ /ClaudeBot\//i,
20500
+ /Claude-Web\//i,
20501
+ /Claude-SearchBot\//i,
20502
+ /Claude-[A-Z]+Bot\//i,
20503
+ /anthropic-ai/i
20504
+ ]
20505
+ },
20506
+ {
20507
+ id: "perplexity-bot",
20508
+ operator: "Perplexity",
20509
+ product: "PerplexityBot",
20510
+ purpose: "search",
20511
+ userAgentPatterns: [/PerplexityBot\//i]
20512
+ },
20513
+ {
20514
+ // User-initiated fetches when a Perplexity user opens a citation
20515
+ // link. Separate from PerplexityBot (crawl) — different ranges and
20516
+ // different operational signal. Perplexity publishes both UA
20517
+ // patterns at perplexity.ai/perplexity-user.json.
20518
+ id: "perplexity-user",
20519
+ operator: "Perplexity",
20520
+ product: "Perplexity-User",
20521
+ purpose: "user-agent",
20522
+ userAgentPatterns: [/Perplexity-User\//i]
20523
+ },
20524
+ {
20525
+ id: "google-extended",
20526
+ operator: "Google",
20527
+ product: "Google-Extended",
20528
+ purpose: "training-control",
20529
+ userAgentPatterns: [/Google-Extended/i]
20530
+ },
20531
+ {
20532
+ id: "bytespider",
20533
+ operator: "ByteDance",
20534
+ product: "Bytespider",
20535
+ purpose: "training",
20536
+ userAgentPatterns: [/Bytespider/i]
20537
+ },
20538
+ {
20539
+ id: "applebot-extended",
20540
+ operator: "Apple",
20541
+ product: "Applebot-Extended",
20542
+ purpose: "training",
20543
+ userAgentPatterns: [/Applebot-Extended/i]
20544
+ },
20545
+ {
20546
+ // Apple's general crawler (separate from Applebot-Extended, which is
20547
+ // the training-opt-out signaling UA). Both indexes pages for Apple
20548
+ // services (Siri/Spotlight); only Applebot-Extended is gated by
20549
+ // training-data opt-out.
20550
+ id: "applebot",
20551
+ operator: "Apple",
20552
+ product: "Applebot",
20553
+ purpose: "crawl",
20554
+ userAgentPatterns: [/Applebot\//i]
20555
+ },
20556
+ {
20557
+ id: "meta-externalagent",
20558
+ operator: "Meta",
20559
+ product: "meta-externalagent",
20560
+ purpose: "training",
20561
+ userAgentPatterns: [/meta-externalagent/i]
20562
+ },
20563
+ {
20564
+ id: "ccbot",
20565
+ operator: "Common Crawl",
20566
+ product: "CCBot",
20567
+ purpose: "crawl",
20568
+ userAgentPatterns: [/CCBot\//i]
20569
+ },
20570
+ {
20571
+ id: "cohere-ai",
20572
+ operator: "Cohere",
20573
+ product: "cohere-ai",
20574
+ purpose: "training",
20575
+ userAgentPatterns: [/cohere-ai/i]
20576
+ },
20577
+ {
20578
+ id: "diffbot",
20579
+ operator: "Diffbot",
20580
+ product: "Diffbot",
20581
+ purpose: "crawl",
20582
+ userAgentPatterns: [/Diffbot/i]
20583
+ },
20584
+ {
20585
+ id: "mistral-ai",
20586
+ operator: "Mistral AI",
20587
+ product: "MistralAI-User",
20588
+ purpose: "crawl",
20589
+ // Mistral ships both `MistralAI-User/*` (chat-on-behalf-of-user
20590
+ // fetches) and `MistralBot/*` (general crawler). Earlier rule only
20591
+ // matched `MistralAI` and missed the bot — caught on 2026-05-18
20592
+ // when canonry.ai/canonry-landing's classification chart went flat
20593
+ // and the bot UA was sitting in the `unknown` bucket.
20594
+ userAgentPatterns: [/MistralAI/i, /MistralBot/i]
20595
+ },
20596
+ {
20597
+ id: "deepseek",
20598
+ operator: "DeepSeek",
20599
+ product: "DeepSeekBot",
20600
+ purpose: "training",
20601
+ userAgentPatterns: [/DeepSeekBot/i]
20602
+ },
20603
+ // Classic search-engine crawlers. Not strictly "AI" by training origin,
20604
+ // but the same audience: machine traffic indexing the site for query
20605
+ // surfaces. Operators tracking AI visibility want this signal too —
20606
+ // SERP indexing is the upstream that feeds AI answer engines (Bing
20607
+ // powers ChatGPT search; Google powers Gemini grounding). Classified
20608
+ // alongside LLM crawlers; the dashboard's "AI crawler hits" label is
20609
+ // imprecise here but functionally correct (these are still bots, not
20610
+ // humans).
20611
+ {
20612
+ id: "googlebot",
20613
+ operator: "Google",
20614
+ product: "Googlebot",
20615
+ purpose: "search",
20616
+ // Googlebot has Smartphone / Desktop / Image / News / Video variants.
20617
+ // All match the `Googlebot/` prefix on first appearance in the UA.
20618
+ // Excludes `Googlebot-Image` etc. that ride a `Googlebot-` prefix —
20619
+ // they also match `Googlebot/` in their UA strings.
20620
+ userAgentPatterns: [/Googlebot[/-]/i]
20621
+ },
20622
+ {
20623
+ id: "bingbot",
20624
+ operator: "Microsoft",
20625
+ product: "bingbot",
20626
+ purpose: "search",
20627
+ userAgentPatterns: [/bingbot\//i]
20628
+ },
20629
+ {
20630
+ id: "duckduckbot",
20631
+ operator: "DuckDuckGo",
20632
+ product: "DuckDuckBot",
20633
+ purpose: "search",
20634
+ userAgentPatterns: [/DuckDuckBot/i]
20635
+ },
20636
+ {
20637
+ id: "yandexbot",
20638
+ operator: "Yandex",
20639
+ product: "YandexBot",
20640
+ purpose: "search",
20641
+ userAgentPatterns: [/YandexBot\//i]
20642
+ },
20643
+ {
20644
+ id: "baiduspider",
20645
+ operator: "Baidu",
20646
+ product: "Baiduspider",
20647
+ purpose: "search",
20648
+ userAgentPatterns: [/Baiduspider/i]
20649
+ },
20650
+ {
20651
+ id: "amazonbot",
20652
+ operator: "Amazon",
20653
+ product: "Amazonbot",
20654
+ purpose: "crawl",
20655
+ userAgentPatterns: [/Amazonbot\//i]
20656
+ }
20657
+ ];
20658
+ var DEFAULT_AI_REFERRER_RULES = [
20659
+ { domain: AI_ENGINE_DOMAINS.chatgpt, operator: "OpenAI", product: "ChatGPT" },
20660
+ { domain: LEGACY_CHATGPT_DOMAIN, operator: "OpenAI", product: "ChatGPT" },
20661
+ { domain: AI_ENGINE_DOMAINS.perplexity, operator: "Perplexity", product: "Perplexity" },
20662
+ { domain: AI_ENGINE_DOMAINS.claude, operator: "Anthropic", product: "Claude" },
20663
+ { domain: AI_ENGINE_DOMAINS.gemini, operator: "Google", product: "Gemini" },
20664
+ { domain: AI_ENGINE_DOMAINS.copilotMicrosoft, operator: "Microsoft", product: "Copilot" },
20665
+ { domain: AI_ENGINE_DOMAINS.phind, operator: "Phind", product: "Phind" },
20666
+ { domain: AI_ENGINE_DOMAINS.you, operator: "You.com", product: "You.com" },
20667
+ { domain: AI_ENGINE_DOMAINS.metaAi, operator: "Meta", product: "Meta AI" }
20668
+ ];
20669
+
20670
+ // ../integration-traffic/src/classifier.ts
20671
+ function normalizeHost(host) {
20672
+ return host.trim().toLowerCase().replace(/^www\./, "");
20673
+ }
20674
+ function hostMatches(host, domain) {
20675
+ const normalizedHost = normalizeHost(host);
20676
+ const normalizedDomain = normalizeHost(domain);
20677
+ return normalizedHost === normalizedDomain || normalizedHost.endsWith(`.${normalizedDomain}`);
20678
+ }
20679
+ function utmTokenMatchesDomain(utmSource, domain) {
20680
+ if (hostMatches(utmSource, domain)) return true;
20681
+ const normalizedUtm = normalizeHost(utmSource);
20682
+ const firstLabel = normalizeHost(domain).split(".")[0];
20683
+ return Boolean(firstLabel) && normalizedUtm === firstLabel;
20684
+ }
20685
+ function hostFromUrl(value) {
20686
+ if (!value) return null;
20687
+ try {
20688
+ return normalizeHost(new URL(value).hostname);
20689
+ } catch {
20690
+ return null;
20691
+ }
20692
+ }
20693
+ function utmSourceFromQuery(queryString) {
20694
+ if (!queryString) return null;
20695
+ const params = new URLSearchParams(queryString);
20696
+ const source = params.get("utm_source");
20697
+ return source ? normalizeHost(source) : null;
20698
+ }
20699
+ function utmSourceFromUrl(value) {
20700
+ if (!value) return null;
20701
+ try {
20702
+ return utmSourceFromQuery(new URL(value).search.replace(/^\?/, ""));
20703
+ } catch {
20704
+ return null;
20705
+ }
20706
+ }
20707
+ function classifyCrawler(event) {
20708
+ const userAgent = event.userAgent?.trim();
20709
+ if (!userAgent) return null;
20710
+ for (const rule of DEFAULT_AI_CRAWLER_RULES) {
20711
+ if (rule.userAgentPatterns.some((pattern) => pattern.test(userAgent))) {
20712
+ const verified = verifyIpForRule(event.remoteIp, rule.id);
20713
+ return {
20714
+ botId: rule.id,
20715
+ operator: rule.operator,
20716
+ product: rule.product,
20717
+ purpose: rule.purpose,
20718
+ verificationStatus: verified ? "verified" : "claimed_unverified",
20719
+ matchedUserAgent: userAgent
20720
+ };
20721
+ }
20722
+ }
20723
+ return null;
20724
+ }
20725
+ function classifyAiReferral(event) {
20726
+ const refererHost = hostFromUrl(event.referer);
20727
+ if (refererHost) {
20728
+ const rule = DEFAULT_AI_REFERRER_RULES.find((candidate) => hostMatches(refererHost, candidate.domain));
20729
+ if (rule) {
20730
+ return {
20731
+ operator: rule.operator,
20732
+ product: rule.product,
20733
+ sourceDomain: refererHost,
20734
+ evidenceType: "referer"
20735
+ };
20736
+ }
20737
+ }
20738
+ const utmSource = utmSourceFromQuery(event.queryString);
20739
+ if (utmSource) {
20740
+ const rule = DEFAULT_AI_REFERRER_RULES.find((candidate) => utmTokenMatchesDomain(utmSource, candidate.domain));
20741
+ if (rule) {
20742
+ return {
20743
+ operator: rule.operator,
20744
+ product: rule.product,
20745
+ sourceDomain: utmSource,
20746
+ evidenceType: "utm"
20747
+ };
20748
+ }
20749
+ }
20750
+ const refererUtmSource = utmSourceFromUrl(event.referer);
20751
+ if (refererUtmSource) {
20752
+ const rule = DEFAULT_AI_REFERRER_RULES.find((candidate) => utmTokenMatchesDomain(refererUtmSource, candidate.domain));
20753
+ if (rule) {
20754
+ return {
20755
+ operator: rule.operator,
20756
+ product: rule.product,
20757
+ sourceDomain: refererUtmSource,
20758
+ evidenceType: "referer-utm"
20759
+ };
20760
+ }
20761
+ }
20762
+ return null;
20763
+ }
20764
+
20765
+ // ../integration-traffic/src/rollup.ts
20766
+ var DEFAULT_SAMPLE_LIMIT = 25;
20767
+ var DEFAULT_AI_REFERRAL_SESSION_WINDOW_MS = 6e4;
20768
+ var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20769
+ var LONG_HEX_SEGMENT = /^[0-9a-f]{16,}$/i;
20770
+ var NUMERIC_SEGMENT = /^\d+$/;
20771
+ var ASSET_EXTENSION_PATTERN = /\.(?:avif|bmp|css|gif|ico|jpe?g|js|json|map|mjs|mp4|otf|png|svg|webm|webmanifest|woff2?|xml)$/i;
20772
+ var ASSET_PATH_PREFIXES = [
20773
+ "/_next/static/",
20774
+ "/assets/",
18426
20775
  "/build/",
18427
20776
  "/dist/",
18428
20777
  "/fonts/",
@@ -20817,6 +23166,79 @@ var providersConfiguredCheck = {
20817
23166
  };
20818
23167
  var PROVIDERS_CHECKS = [providersConfiguredCheck];
20819
23168
 
23169
+ // ../api-routes/src/doctor/checks/runtime-state.ts
23170
+ import fs7 from "fs";
23171
+ var dbFilePresentCheck = {
23172
+ id: "db.file.present",
23173
+ category: CheckCategories.database,
23174
+ scope: CheckScopes.global,
23175
+ title: "Database file present",
23176
+ run: (ctx) => {
23177
+ const path16 = ctx.runtimeStatePaths?.databasePath;
23178
+ if (!path16) {
23179
+ return {
23180
+ status: CheckStatuses.skipped,
23181
+ code: "db.file.path-not-wired",
23182
+ summary: "No database file path configured for this deployment (cloud DB).",
23183
+ remediation: null
23184
+ };
23185
+ }
23186
+ if (!fs7.existsSync(path16)) {
23187
+ return {
23188
+ status: CheckStatuses.fail,
23189
+ code: "db.file.missing",
23190
+ summary: `Database file at \`${path16}\` has been deleted while the daemon is running.`,
23191
+ remediation: "Restart `canonry serve` so a fresh database is created and migrations re-run. Until you do, the daemon will keep serving stale data from a deleted-but-open file handle and writes will be lost.",
23192
+ details: { path: path16 }
23193
+ };
23194
+ }
23195
+ return {
23196
+ status: CheckStatuses.ok,
23197
+ code: "db.file.present",
23198
+ summary: `Database file present at \`${path16}\`.`,
23199
+ remediation: null,
23200
+ details: { path: path16 }
23201
+ };
23202
+ }
23203
+ };
23204
+ var configFilePresentCheck = {
23205
+ id: "config.file.present",
23206
+ category: CheckCategories.config,
23207
+ scope: CheckScopes.global,
23208
+ title: "Config file present",
23209
+ run: (ctx) => {
23210
+ const path16 = ctx.runtimeStatePaths?.configPath;
23211
+ if (!path16) {
23212
+ return {
23213
+ status: CheckStatuses.skipped,
23214
+ code: "config.file.path-not-wired",
23215
+ summary: "No config file path configured for this deployment.",
23216
+ remediation: null
23217
+ };
23218
+ }
23219
+ if (!fs7.existsSync(path16)) {
23220
+ return {
23221
+ status: CheckStatuses.fail,
23222
+ code: "config.file.missing",
23223
+ summary: `Config file at \`${path16}\` has been deleted while the daemon is running.`,
23224
+ remediation: "Restart `canonry serve` after the file is restored (provider keys, OAuth tokens, and integration credentials live in this file; the in-memory copy is read-only until restart).",
23225
+ details: { path: path16 }
23226
+ };
23227
+ }
23228
+ return {
23229
+ status: CheckStatuses.ok,
23230
+ code: "config.file.present",
23231
+ summary: `Config file present at \`${path16}\`.`,
23232
+ remediation: null,
23233
+ details: { path: path16 }
23234
+ };
23235
+ }
23236
+ };
23237
+ var RUNTIME_STATE_CHECKS = [
23238
+ dbFilePresentCheck,
23239
+ configFilePresentCheck
23240
+ ];
23241
+
20820
23242
  // ../api-routes/src/doctor/checks/traffic-source.ts
20821
23243
  import { and as and20, eq as eq25, gte as gte4, ne as ne4, sql as sql11 } from "drizzle-orm";
20822
23244
  var RECENT_DATA_WARN_DAYS = 7;
@@ -21122,6 +23544,9 @@ var TRAFFIC_SOURCE_CHECKS = [
21122
23544
 
21123
23545
  // ../api-routes/src/doctor/registry.ts
21124
23546
  var ALL_CHECKS = [
23547
+ // Runtime-state checks run first so file-system gone errors surface
23548
+ // before any auth/integration checks try to touch the (orphaned) DB.
23549
+ ...RUNTIME_STATE_CHECKS,
21125
23550
  ...GOOGLE_AUTH_CHECKS,
21126
23551
  ...BING_AUTH_CHECKS,
21127
23552
  ...GA_AUTH_CHECKS,
@@ -21212,7 +23637,8 @@ async function doctorRoutes(app, opts) {
21212
23637
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
21213
23638
  redirectUri,
21214
23639
  providerSummary: opts.providerSummary,
21215
- trafficSourceValidators: opts.trafficSourceValidators
23640
+ trafficSourceValidators: opts.trafficSourceValidators,
23641
+ runtimeStatePaths: opts.runtimeStatePaths
21216
23642
  };
21217
23643
  return runChecks(ctx, ALL_CHECKS, { checkIds });
21218
23644
  });
@@ -21233,7 +23659,8 @@ async function doctorRoutes(app, opts) {
21233
23659
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
21234
23660
  redirectUri,
21235
23661
  providerSummary: opts.providerSummary,
21236
- trafficSourceValidators: opts.trafficSourceValidators
23662
+ trafficSourceValidators: opts.trafficSourceValidators,
23663
+ runtimeStatePaths: opts.runtimeStatePaths
21237
23664
  };
21238
23665
  return runChecks(ctx, ALL_CHECKS, { checkIds });
21239
23666
  });
@@ -21722,6 +24149,21 @@ async function apiRoutes(app, opts) {
21722
24149
  }
21723
24150
  });
21724
24151
  });
24152
+ if (opts.runtimeStatePaths) {
24153
+ const { databasePath, configPath } = opts.runtimeStatePaths;
24154
+ const isDiagnosticUrl = (url) => url === "/health" || /\/doctor(?:\?|$)/.test(url);
24155
+ app.addHook("onRequest", async (request) => {
24156
+ if (isDiagnosticUrl(request.url)) return;
24157
+ const missing = [];
24158
+ if (!fs8.existsSync(databasePath)) missing.push(`database file \`${databasePath}\``);
24159
+ if (configPath && !fs8.existsSync(configPath)) missing.push(`config file \`${configPath}\``);
24160
+ if (missing.length === 0) return;
24161
+ throw runtimeStateMissing(
24162
+ `Runtime state missing: ${missing.join(" and ")}. Restart \`canonry serve\` so a fresh state is created (the daemon's open file handles still point at the deleted inode, so writes are being lost).`,
24163
+ { missing }
24164
+ );
24165
+ });
24166
+ }
21725
24167
  await app.register(async (api) => {
21726
24168
  if (!opts.skipAuth) {
21727
24169
  await authPlugin(api, {
@@ -21845,7 +24287,8 @@ async function apiRoutes(app, opts) {
21845
24287
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
21846
24288
  publicUrl: opts.publicUrl,
21847
24289
  providerSummary: opts.providerSummary,
21848
- trafficSourceValidators: buildTrafficSourceValidators(opts)
24290
+ trafficSourceValidators: buildTrafficSourceValidators(opts),
24291
+ runtimeStatePaths: opts.runtimeStatePaths
21849
24292
  });
21850
24293
  if (opts.registerAuthenticatedRoutes) {
21851
24294
  await opts.registerAuthenticatedRoutes(api);
@@ -22031,7 +24474,7 @@ async function withRetry(fn, options = {}) {
22031
24474
  }
22032
24475
 
22033
24476
  // ../provider-gemini/src/normalize.ts
22034
- var DEFAULT_MODEL = "gemini-3-flash";
24477
+ var DEFAULT_MODEL = "gemini-2.5-flash";
22035
24478
  function isVertexConfig(config) {
22036
24479
  return !!config.vertexProject;
22037
24480
  }
@@ -22368,15 +24811,14 @@ var geminiAdapter = {
22368
24811
  keyUrl: "https://aistudio.google.com/apikey",
22369
24812
  // Upstream model list: https://ai.google.dev/gemini-api/docs/models
22370
24813
  modelRegistry: {
22371
- defaultModel: "gemini-3-flash",
24814
+ defaultModel: "gemini-2.5-flash",
22372
24815
  validationPattern: /./,
22373
- validationHint: "any valid Google model name (e.g. gemini-3-flash, learnlm-1.5-pro-experimental)",
24816
+ validationHint: "any valid Google model name (e.g. gemini-2.5-flash, learnlm-1.5-pro-experimental)",
22374
24817
  knownModels: [
22375
- { id: "gemini-3.1-pro-preview", displayName: "Gemini 3.1 Pro (Preview)", tier: "flagship" },
22376
- { id: "gemini-3-flash", displayName: "Gemini 3 Flash", tier: "standard" },
22377
- { id: "gemini-3-flash-preview", displayName: "Gemini 3 Flash (Preview)", tier: "standard" },
22378
- { id: "gemini-3.1-flash-lite-preview", displayName: "Gemini 3.1 Flash-Lite (Preview)", tier: "economy" },
22379
- { id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", tier: "standard" }
24818
+ { id: "gemini-2.5-pro", displayName: "Gemini 2.5 Pro", tier: "flagship" },
24819
+ { id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", tier: "standard" },
24820
+ { id: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash-Lite", tier: "economy" },
24821
+ { id: "gemini-2.0-flash", displayName: "Gemini 2.0 Flash", tier: "standard" }
22380
24822
  ]
22381
24823
  },
22382
24824
  validateConfig(config) {
@@ -23792,12 +26234,12 @@ function sleep2(ms) {
23792
26234
  }
23793
26235
 
23794
26236
  // ../provider-cdp/src/screenshot.ts
23795
- import fs7 from "fs";
26237
+ import fs9 from "fs";
23796
26238
  import path8 from "path";
23797
26239
  async function captureElementScreenshot(client, selector, outputPath) {
23798
26240
  const dir = path8.dirname(outputPath);
23799
- if (!fs7.existsSync(dir)) {
23800
- fs7.mkdirSync(dir, { recursive: true });
26241
+ if (!fs9.existsSync(dir)) {
26242
+ fs9.mkdirSync(dir, { recursive: true });
23801
26243
  }
23802
26244
  let clip;
23803
26245
  try {
@@ -23831,7 +26273,7 @@ async function captureElementScreenshot(client, selector, outputPath) {
23831
26273
  }
23832
26274
  const { data } = await client.Page.captureScreenshot(screenshotParams);
23833
26275
  const buffer = Buffer.from(data, "base64");
23834
- fs7.writeFileSync(outputPath, buffer);
26276
+ fs9.writeFileSync(outputPath, buffer);
23835
26277
  return outputPath;
23836
26278
  }
23837
26279
 
@@ -24589,7 +27031,7 @@ function removeWordpressConnection(config, projectName) {
24589
27031
 
24590
27032
  // src/job-runner.ts
24591
27033
  import crypto25 from "crypto";
24592
- import fs8 from "fs";
27034
+ import fs10 from "fs";
24593
27035
  import path10 from "path";
24594
27036
  import os5 from "os";
24595
27037
  import { and as and22, eq as eq28, inArray as inArray10, sql as sql12 } from "drizzle-orm";
@@ -25083,12 +27525,12 @@ var JobRunner = class {
25083
27525
  allBrandNames
25084
27526
  );
25085
27527
  let screenshotRelPath = null;
25086
- if (raw.screenshotPath && fs8.existsSync(raw.screenshotPath)) {
27528
+ if (raw.screenshotPath && fs10.existsSync(raw.screenshotPath)) {
25087
27529
  const snapshotId = crypto25.randomUUID();
25088
27530
  const screenshotDir = path10.join(os5.homedir(), ".canonry", "screenshots", runId);
25089
- if (!fs8.existsSync(screenshotDir)) fs8.mkdirSync(screenshotDir, { recursive: true });
27531
+ if (!fs10.existsSync(screenshotDir)) fs10.mkdirSync(screenshotDir, { recursive: true });
25090
27532
  const destPath = path10.join(screenshotDir, `${snapshotId}.png`);
25091
- fs8.renameSync(raw.screenshotPath, destPath);
27533
+ fs10.renameSync(raw.screenshotPath, destPath);
25092
27534
  screenshotRelPath = `${runId}/${snapshotId}.png`;
25093
27535
  this.db.insert(querySnapshots).values({
25094
27536
  id: snapshotId,
@@ -26126,7 +28568,7 @@ function computeSummary(rows) {
26126
28568
 
26127
28569
  // src/backlink-extract.ts
26128
28570
  import crypto30 from "crypto";
26129
- import fs9 from "fs";
28571
+ import fs11 from "fs";
26130
28572
  import { and as and26, desc as desc16, eq as eq33 } from "drizzle-orm";
26131
28573
  var log7 = createLogger("BacklinkExtract");
26132
28574
  function defaultDeps2() {
@@ -26155,7 +28597,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
26155
28597
  if (!sync.vertexPath || !sync.edgesPath) {
26156
28598
  throw new Error(`Release ${sync.release} is missing cached file paths`);
26157
28599
  }
26158
- if (!fs9.existsSync(sync.vertexPath) || !fs9.existsSync(sync.edgesPath)) {
28600
+ if (!fs11.existsSync(sync.vertexPath) || !fs11.existsSync(sync.edgesPath)) {
26159
28601
  throw new Error(
26160
28602
  `Cache for release ${sync.release} is missing from disk (expected at ${sync.vertexPath}). The sync record exists in the database, but the ~16 GB dump was deleted or never present on this machine. Re-sync this release from the Backlinks admin page to restore the cache.`
26161
28603
  );
@@ -26546,7 +28988,7 @@ function buildDiscoveryInsightTitle(input) {
26546
28988
  }
26547
28989
 
26548
28990
  // src/commands/backfill.ts
26549
- import { and as and28, eq as eq35, inArray as inArray11 } from "drizzle-orm";
28991
+ import { and as and28, eq as eq35, inArray as inArray11, isNull, sql as sql15 } from "drizzle-orm";
26550
28992
  var SNAPSHOT_BATCH_SIZE = 500;
26551
28993
  async function backfillAnswerVisibilityCommand(opts) {
26552
28994
  const config = loadConfig();
@@ -27003,7 +29445,7 @@ function readStoredGroundingSources(rawResponse) {
27003
29445
  return result;
27004
29446
  }
27005
29447
  async function backfillInsightsCommand(project, opts) {
27006
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-XMZEWLCW.js");
29448
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-2XL2M7QP.js");
27007
29449
  const config = loadConfig();
27008
29450
  const db = createClient(config.database);
27009
29451
  migrate(db);
@@ -27051,6 +29493,247 @@ Backfill ${isDryRun ? "preview" : "complete"}.`);
27051
29493
  console.log(` No DB writes performed. Re-run without --dry-run to apply.`);
27052
29494
  }
27053
29495
  }
29496
+ function replayQueryAuditLog(events) {
29497
+ const active = [];
29498
+ for (const ev of events) {
29499
+ let diff;
29500
+ try {
29501
+ diff = ev.diff ? JSON.parse(ev.diff) : {};
29502
+ } catch {
29503
+ continue;
29504
+ }
29505
+ if (ev.action === "keywords.appended" || ev.action === "queries.appended") {
29506
+ const added = Array.isArray(diff.added) ? diff.added : [];
29507
+ for (const q of added) {
29508
+ active.push({ text: q, addedAt: ev.createdAt, deletedAt: null });
29509
+ }
29510
+ } else if (ev.action === "keywords.deleted" || ev.action === "queries.deleted") {
29511
+ const deleted = Array.isArray(diff.deleted) ? diff.deleted : [];
29512
+ for (const q of deleted) {
29513
+ for (let i = active.length - 1; i >= 0; i--) {
29514
+ if (active[i].text === q && active[i].deletedAt === null) {
29515
+ active[i].deletedAt = ev.createdAt;
29516
+ break;
29517
+ }
29518
+ }
29519
+ }
29520
+ } else if (ev.action === "queries.replaced") {
29521
+ const newSet = Array.isArray(diff.queries) ? diff.queries : [];
29522
+ for (const e of active) {
29523
+ if (e.deletedAt === null) e.deletedAt = ev.createdAt;
29524
+ }
29525
+ for (const q of newSet) {
29526
+ active.push({ text: q, addedAt: ev.createdAt, deletedAt: null });
29527
+ }
29528
+ }
29529
+ }
29530
+ return active;
29531
+ }
29532
+ function activeQueriesAt(history, t) {
29533
+ return history.filter((e) => e.addedAt <= t && (e.deletedAt === null || e.deletedAt > t)).map((e) => e.text);
29534
+ }
29535
+ var CONTENT_MATCH_STOPWORDS = /* @__PURE__ */ new Set([
29536
+ "the",
29537
+ "a",
29538
+ "an",
29539
+ "and",
29540
+ "or",
29541
+ "but",
29542
+ "is",
29543
+ "are",
29544
+ "was",
29545
+ "were",
29546
+ "be",
29547
+ "been",
29548
+ "being",
29549
+ "have",
29550
+ "has",
29551
+ "had",
29552
+ "do",
29553
+ "does",
29554
+ "did",
29555
+ "will",
29556
+ "would",
29557
+ "could",
29558
+ "should",
29559
+ "may",
29560
+ "might",
29561
+ "can",
29562
+ "this",
29563
+ "that",
29564
+ "these",
29565
+ "those",
29566
+ "i",
29567
+ "you",
29568
+ "he",
29569
+ "she",
29570
+ "it",
29571
+ "we",
29572
+ "they",
29573
+ "what",
29574
+ "which",
29575
+ "who",
29576
+ "how",
29577
+ "why",
29578
+ "when",
29579
+ "where",
29580
+ "with",
29581
+ "for",
29582
+ "from",
29583
+ "of",
29584
+ "in",
29585
+ "on",
29586
+ "at",
29587
+ "to",
29588
+ "by",
29589
+ "as",
29590
+ "best",
29591
+ "most"
29592
+ ]);
29593
+ function tokenizeForMatch(s) {
29594
+ return s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3 && !CONTENT_MATCH_STOPWORDS.has(t));
29595
+ }
29596
+ function contentMatchScore(queryText, answerText) {
29597
+ const queryTokens = [...new Set(tokenizeForMatch(queryText))];
29598
+ if (queryTokens.length === 0) return 0;
29599
+ const answerHead = answerText.slice(0, 300).toLowerCase();
29600
+ const hit = queryTokens.filter((t) => answerHead.includes(t)).length;
29601
+ return hit / queryTokens.length;
29602
+ }
29603
+ async function backfillSnapshotAttributionCommand(opts) {
29604
+ const config = loadConfig();
29605
+ const db = createClient(config.database);
29606
+ migrate(db);
29607
+ const project = db.select().from(projects).where(eq35(projects.name, opts.project)).get();
29608
+ if (!project) {
29609
+ throw new Error(`Project "${opts.project}" not found`);
29610
+ }
29611
+ const isJson = opts.format === "json";
29612
+ const isDryRun = opts.dryRun === true;
29613
+ if (!isJson) {
29614
+ const mode = isDryRun ? " [DRY RUN \u2014 no writes]" : "";
29615
+ process.stderr.write(`Recovering orphan snapshot attribution for "${project.name}"${mode}...
29616
+ `);
29617
+ }
29618
+ const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and28(
29619
+ eq35(auditLog.projectId, project.id),
29620
+ inArray11(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
29621
+ )).orderBy(auditLog.createdAt).all();
29622
+ const history = replayQueryAuditLog(events);
29623
+ const orphanRuns = db.select({
29624
+ runId: runs.id,
29625
+ createdAt: runs.createdAt,
29626
+ location: runs.location
29627
+ }).from(runs).innerJoin(querySnapshots, eq35(querySnapshots.runId, runs.id)).where(and28(
29628
+ eq35(runs.projectId, project.id),
29629
+ isNull(querySnapshots.queryId),
29630
+ isNull(querySnapshots.queryText)
29631
+ )).groupBy(runs.id).orderBy(runs.createdAt).all();
29632
+ const result = {
29633
+ project: project.name,
29634
+ examinedRuns: orphanRuns.length,
29635
+ orphanSnapshots: 0,
29636
+ recoveredByPosition: 0,
29637
+ recoveredByContent: 0,
29638
+ unrecovered: 0,
29639
+ dryRun: isDryRun,
29640
+ perRun: []
29641
+ };
29642
+ const updates = [];
29643
+ for (const run of orphanRuns) {
29644
+ const activeAt = activeQueriesAt(history, run.createdAt);
29645
+ const orphanSnaps = db.select({
29646
+ id: querySnapshots.id,
29647
+ provider: querySnapshots.provider,
29648
+ createdAt: querySnapshots.createdAt,
29649
+ answerText: querySnapshots.answerText
29650
+ }).from(querySnapshots).where(and28(
29651
+ eq35(querySnapshots.runId, run.runId),
29652
+ isNull(querySnapshots.queryId),
29653
+ isNull(querySnapshots.queryText)
29654
+ )).orderBy(querySnapshots.provider, querySnapshots.createdAt).all();
29655
+ const byProvider = /* @__PURE__ */ new Map();
29656
+ for (const s of orphanSnaps) {
29657
+ const arr = byProvider.get(s.provider);
29658
+ if (arr) arr.push(s);
29659
+ else byProvider.set(s.provider, [s]);
29660
+ }
29661
+ let runPosition = 0;
29662
+ let runContent = 0;
29663
+ let runUnrecovered = 0;
29664
+ for (const [, snaps] of byProvider) {
29665
+ if (snaps.length === activeAt.length) {
29666
+ for (let i = 0; i < snaps.length; i++) {
29667
+ updates.push({ id: snaps[i].id, queryText: activeAt[i] });
29668
+ runPosition++;
29669
+ }
29670
+ } else if (snaps.length < activeAt.length) {
29671
+ const LOOKAHEAD_LIMIT = 5;
29672
+ let cidx = 0;
29673
+ for (const snap of snaps) {
29674
+ const answerText = snap.answerText ?? "";
29675
+ let matchedIdx = -1;
29676
+ for (let i = cidx; i < Math.min(activeAt.length, cidx + LOOKAHEAD_LIMIT); i++) {
29677
+ if (contentMatchScore(activeAt[i], answerText) >= 1) {
29678
+ matchedIdx = i;
29679
+ break;
29680
+ }
29681
+ }
29682
+ if (matchedIdx >= 0) {
29683
+ updates.push({ id: snap.id, queryText: activeAt[matchedIdx] });
29684
+ runContent++;
29685
+ cidx = matchedIdx + 1;
29686
+ } else {
29687
+ runUnrecovered++;
29688
+ }
29689
+ }
29690
+ } else {
29691
+ runUnrecovered += snaps.length;
29692
+ }
29693
+ }
29694
+ result.orphanSnapshots += orphanSnaps.length;
29695
+ result.recoveredByPosition += runPosition;
29696
+ result.recoveredByContent += runContent;
29697
+ result.unrecovered += runUnrecovered;
29698
+ result.perRun.push({
29699
+ runId: run.runId,
29700
+ runCreatedAt: run.createdAt,
29701
+ activeQueryCount: activeAt.length,
29702
+ orphanSnaps: orphanSnaps.length,
29703
+ recoveredPosition: runPosition,
29704
+ recoveredContent: runContent,
29705
+ unrecovered: runUnrecovered
29706
+ });
29707
+ if (!isJson) {
29708
+ process.stderr.write(
29709
+ ` ${run.createdAt} loc=${run.location ?? "-"} \u2192 ${orphanSnaps.length} orphan; ${runPosition} position-matched, ${runContent} content-matched, ${runUnrecovered} unrecovered
29710
+ `
29711
+ );
29712
+ }
29713
+ }
29714
+ if (!isDryRun && updates.length > 0) {
29715
+ db.transaction((tx) => {
29716
+ for (const u of updates) {
29717
+ tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq35(querySnapshots.id, u.id)).run();
29718
+ }
29719
+ });
29720
+ }
29721
+ if (isJson) {
29722
+ console.log(JSON.stringify(result, null, 2));
29723
+ return;
29724
+ }
29725
+ console.log(`
29726
+ Snapshot attribution ${isDryRun ? "preview" : "recovery"} complete.`);
29727
+ console.log(` Examined runs: ${result.examinedRuns}`);
29728
+ console.log(` Orphan snapshots: ${result.orphanSnapshots}`);
29729
+ console.log(` Position-matched: ${result.recoveredByPosition}`);
29730
+ console.log(` Content-matched: ${result.recoveredByContent}`);
29731
+ console.log(` Unrecovered: ${result.unrecovered}`);
29732
+ if (isDryRun) {
29733
+ console.log(`
29734
+ No DB writes performed. Re-run without --dry-run to apply.`);
29735
+ }
29736
+ }
27054
29737
  function reparseProviderSnapshot(provider, rawResponse) {
27055
29738
  const envelope = parseJsonColumn(rawResponse, {});
27056
29739
  const apiResponse = resolveStoredApiResponse(envelope);
@@ -27098,6 +29781,157 @@ function stringifyStoredSnapshotEnvelope(rawResponse, reparsed) {
27098
29781
  ...apiResponse ? { apiResponse } : {}
27099
29782
  });
27100
29783
  }
29784
+ async function backfillTrafficClassificationCommand(opts) {
29785
+ const config = loadConfig();
29786
+ const db = createClient(config.database);
29787
+ migrate(db);
29788
+ const projectFilter = opts?.project?.trim();
29789
+ const isDryRun = opts?.dryRun === true;
29790
+ const isJson = opts?.format === "json";
29791
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq35(projects.name, projectFilter)).all() : db.select().from(projects).all();
29792
+ if (scopedProjects.length === 0) {
29793
+ if (projectFilter && !isJson) {
29794
+ process.stderr.write(`No project named "${projectFilter}".
29795
+ `);
29796
+ }
29797
+ if (isJson) console.log(JSON.stringify({ project: projectFilter ?? null, examined: 0, reclassified: 0 }));
29798
+ return;
29799
+ }
29800
+ if (!isJson) {
29801
+ const mode = isDryRun ? " [DRY RUN \u2014 no writes]" : "";
29802
+ const scope = projectFilter ? `"${projectFilter}"` : "all projects";
29803
+ process.stderr.write(`Re-classifying unknown traffic samples for ${scope}${mode}...
29804
+ `);
29805
+ }
29806
+ const projectIds = scopedProjects.map((p) => p.id);
29807
+ const result = {
29808
+ project: projectFilter ?? null,
29809
+ examined: 0,
29810
+ reclassified: 0,
29811
+ unknownBefore: 0,
29812
+ unknownAfter: 0,
29813
+ dryRun: isDryRun,
29814
+ byBot: {}
29815
+ };
29816
+ const unknownCountRow = db.select({ n: sql15`count(*)` }).from(rawEventSamples).where(and28(
29817
+ eq35(rawEventSamples.eventType, "unknown"),
29818
+ inArray11(rawEventSamples.projectId, projectIds)
29819
+ )).get();
29820
+ result.unknownBefore = Number(unknownCountRow?.n ?? 0);
29821
+ const unknownSamples = db.select({
29822
+ id: rawEventSamples.id,
29823
+ projectId: rawEventSamples.projectId,
29824
+ sourceId: rawEventSamples.sourceId,
29825
+ ts: rawEventSamples.ts,
29826
+ userAgent: rawEventSamples.userAgent,
29827
+ pathNormalized: rawEventSamples.pathNormalized,
29828
+ status: rawEventSamples.status
29829
+ }).from(rawEventSamples).where(and28(
29830
+ eq35(rawEventSamples.eventType, "unknown"),
29831
+ inArray11(rawEventSamples.projectId, projectIds)
29832
+ )).all();
29833
+ result.examined = unknownSamples.length;
29834
+ if (unknownSamples.length === 0) {
29835
+ result.unknownAfter = result.unknownBefore;
29836
+ emitTrafficClassificationReport(result, isJson);
29837
+ return;
29838
+ }
29839
+ const now = (/* @__PURE__ */ new Date()).toISOString();
29840
+ for (const snap of unknownSamples) {
29841
+ if (!snap.userAgent) continue;
29842
+ const probe = {
29843
+ sourceType: TrafficSourceTypes["cloud-run"],
29844
+ evidenceKind: TrafficEvidenceKinds["raw-request"],
29845
+ confidence: TrafficEventConfidences.observed,
29846
+ eventId: snap.id,
29847
+ observedAt: snap.ts,
29848
+ method: "GET",
29849
+ requestUrl: `https://placeholder${snap.pathNormalized}`,
29850
+ host: "placeholder",
29851
+ path: snap.pathNormalized,
29852
+ queryString: null,
29853
+ status: snap.status ?? 200,
29854
+ userAgent: snap.userAgent,
29855
+ remoteIp: null,
29856
+ referer: null,
29857
+ latencyMs: null,
29858
+ requestSizeBytes: null,
29859
+ responseSizeBytes: null,
29860
+ providerResource: { type: "cloud_run_revision", labels: {} },
29861
+ providerLabels: {}
29862
+ };
29863
+ const classified = classifyCrawler(probe);
29864
+ if (!classified) continue;
29865
+ result.reclassified++;
29866
+ result.byBot[classified.botId] = (result.byBot[classified.botId] ?? 0) + 1;
29867
+ if (isDryRun) continue;
29868
+ db.update(rawEventSamples).set({ eventType: TrafficEventKinds.crawler }).where(eq35(rawEventSamples.id, snap.id)).run();
29869
+ const tsHour = new Date(snap.ts);
29870
+ tsHour.setUTCMinutes(0, 0, 0);
29871
+ db.insert(crawlerEventsHourly).values({
29872
+ projectId: snap.projectId,
29873
+ sourceId: snap.sourceId,
29874
+ tsHour: tsHour.toISOString(),
29875
+ botId: classified.botId,
29876
+ operator: classified.operator,
29877
+ verificationStatus: classified.verificationStatus,
29878
+ pathNormalized: snap.pathNormalized,
29879
+ status: snap.status ?? 200,
29880
+ hits: 1,
29881
+ sampledUserAgent: snap.userAgent,
29882
+ createdAt: now,
29883
+ updatedAt: now
29884
+ }).onConflictDoUpdate({
29885
+ target: [
29886
+ crawlerEventsHourly.projectId,
29887
+ crawlerEventsHourly.sourceId,
29888
+ crawlerEventsHourly.tsHour,
29889
+ crawlerEventsHourly.botId,
29890
+ crawlerEventsHourly.verificationStatus,
29891
+ crawlerEventsHourly.pathNormalized,
29892
+ crawlerEventsHourly.status
29893
+ ],
29894
+ set: {
29895
+ hits: sql15`${crawlerEventsHourly.hits} + 1`,
29896
+ updatedAt: now
29897
+ }
29898
+ }).run();
29899
+ }
29900
+ if (!isDryRun) {
29901
+ const afterRow = db.select({ n: sql15`count(*)` }).from(rawEventSamples).where(and28(
29902
+ eq35(rawEventSamples.eventType, "unknown"),
29903
+ inArray11(rawEventSamples.projectId, projectIds)
29904
+ )).get();
29905
+ result.unknownAfter = Number(afterRow?.n ?? 0);
29906
+ } else {
29907
+ result.unknownAfter = result.unknownBefore - result.reclassified;
29908
+ }
29909
+ emitTrafficClassificationReport(result, isJson);
29910
+ }
29911
+ function emitTrafficClassificationReport(result, isJson) {
29912
+ if (isJson) {
29913
+ console.log(JSON.stringify(result, null, 2));
29914
+ return;
29915
+ }
29916
+ const verb = result.dryRun ? "Would re-classify" : "Re-classified";
29917
+ console.log(`
29918
+ Traffic classification ${result.dryRun ? "preview" : "recovery"} complete.`);
29919
+ console.log(` Examined unknowns: ${result.examined}`);
29920
+ console.log(` ${verb}: ${result.reclassified}`);
29921
+ console.log(` Unknown before: ${result.unknownBefore}`);
29922
+ console.log(` Unknown after: ${result.unknownAfter}`);
29923
+ if (result.reclassified > 0) {
29924
+ console.log(` By bot:`);
29925
+ const sorted = Object.entries(result.byBot).sort(([, a], [, b]) => b - a);
29926
+ for (const [bot, count] of sorted) {
29927
+ console.log(` ${count.toString().padStart(5)} ${bot}`);
29928
+ }
29929
+ }
29930
+ if (result.dryRun) {
29931
+ console.log(`
29932
+ No DB writes performed. Re-run without --dry-run to apply.`);
29933
+ }
29934
+ }
27101
29935
 
27102
29936
  // src/provider-registry.ts
27103
29937
  var ProviderRegistry = class {
@@ -27650,7 +30484,7 @@ import crypto34 from "crypto";
27650
30484
  import { eq as eq40 } from "drizzle-orm";
27651
30485
 
27652
30486
  // src/agent/session.ts
27653
- import fs12 from "fs";
30487
+ import fs14 from "fs";
27654
30488
  import path14 from "path";
27655
30489
  import { Agent } from "@mariozechner/pi-agent-core";
27656
30490
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
@@ -27791,7 +30625,7 @@ function buildAgentProvidersResponse(config) {
27791
30625
  }
27792
30626
 
27793
30627
  // src/agent/skill-paths.ts
27794
- import fs10 from "fs";
30628
+ import fs12 from "fs";
27795
30629
  import path12 from "path";
27796
30630
  import { fileURLToPath } from "url";
27797
30631
  function resolveAeroSkillDir(pkgDir) {
@@ -27802,14 +30636,14 @@ function resolveAeroSkillDir(pkgDir) {
27802
30636
  path12.join(here, "../../../../skills/aero")
27803
30637
  ];
27804
30638
  for (const candidate of candidates) {
27805
- if (fs10.existsSync(path12.join(candidate, "SKILL.md"))) return candidate;
30639
+ if (fs12.existsSync(path12.join(candidate, "SKILL.md"))) return candidate;
27806
30640
  }
27807
30641
  throw new Error(`Aero skill not found. Searched:
27808
30642
  ${candidates.join("\n ")}`);
27809
30643
  }
27810
30644
 
27811
30645
  // src/agent/skill-tools.ts
27812
- import fs11 from "fs";
30646
+ import fs13 from "fs";
27813
30647
  import path13 from "path";
27814
30648
  import { Type } from "@sinclair/typebox";
27815
30649
  var MAX_DOC_CHARS = 2e4;
@@ -27832,12 +30666,12 @@ function parseDescription(body) {
27832
30666
  }
27833
30667
  function scanSkillDocs(skillDir) {
27834
30668
  const refsDir = path13.join(skillDir ?? resolveAeroSkillDir(), "references");
27835
- if (!fs11.existsSync(refsDir)) return [];
30669
+ if (!fs13.existsSync(refsDir)) return [];
27836
30670
  const entries = [];
27837
- for (const file of fs11.readdirSync(refsDir)) {
30671
+ for (const file of fs13.readdirSync(refsDir)) {
27838
30672
  if (!file.endsWith(".md")) continue;
27839
30673
  const filePath = path13.join(refsDir, file);
27840
- const body = fs11.readFileSync(filePath, "utf-8");
30674
+ const body = fs13.readFileSync(filePath, "utf-8");
27841
30675
  entries.push({
27842
30676
  slug: file.replace(/\.md$/, ""),
27843
30677
  description: parseDescription(body),
@@ -27881,7 +30715,7 @@ function buildReadSkillDocTool() {
27881
30715
  });
27882
30716
  }
27883
30717
  const filePath = path13.join(skillDir, "references", `${match.slug}.md`);
27884
- const content = fs11.readFileSync(filePath, "utf-8");
30718
+ const content = fs13.readFileSync(filePath, "utf-8");
27885
30719
  if (content.length > MAX_DOC_CHARS) {
27886
30720
  return textResult({
27887
30721
  slug: match.slug,
@@ -27975,10 +30809,10 @@ function ensureBuiltinsRegistered() {
27975
30809
  }
27976
30810
  function loadAeroSystemPrompt(pkgDir) {
27977
30811
  const skillDir = resolveAeroSkillDir(pkgDir);
27978
- const skillBody = fs12.readFileSync(path14.join(skillDir, "SKILL.md"), "utf-8");
30812
+ const skillBody = fs14.readFileSync(path14.join(skillDir, "SKILL.md"), "utf-8");
27979
30813
  const soulPath = path14.join(skillDir, "soul.md");
27980
- if (!fs12.existsSync(soulPath)) return skillBody;
27981
- const soulBody = fs12.readFileSync(soulPath, "utf-8");
30814
+ if (!fs14.existsSync(soulPath)) return skillBody;
30815
+ const soulBody = fs14.readFileSync(soulPath, "utf-8");
27982
30816
  return `${soulBody.trimEnd()}
27983
30817
 
27984
30818
  ---
@@ -28036,7 +30870,7 @@ function resolveSessionProviderAndModel(config, opts) {
28036
30870
 
28037
30871
  // src/agent/memory-store.ts
28038
30872
  import crypto33 from "crypto";
28039
- import { and as and31, desc as desc18, eq as eq39, like as like2, sql as sql15 } from "drizzle-orm";
30873
+ import { and as and31, desc as desc18, eq as eq39, like as like2, sql as sql16 } from "drizzle-orm";
28040
30874
  var COMPACTION_KEY_PREFIX = "compaction:";
28041
30875
  var COMPACTION_NOTES_PER_SESSION = 3;
28042
30876
  function rowToDto2(row) {
@@ -28122,7 +30956,7 @@ function writeCompactionNote(db, args) {
28122
30956
  ).orderBy(desc18(agentMemory.updatedAt)).all();
28123
30957
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
28124
30958
  if (stale.length > 0) {
28125
- tx.delete(agentMemory).where(sql15`${agentMemory.id} IN (${sql15.join(stale.map((s) => sql15`${s}`), sql15`, `)})`).run();
30959
+ tx.delete(agentMemory).where(sql16`${agentMemory.id} IN (${sql16.join(stale.map((s) => sql16`${s}`), sql16`, `)})`).run();
28126
30960
  }
28127
30961
  const row = tx.select().from(agentMemory).where(and31(eq39(agentMemory.projectId, args.projectId), eq39(agentMemory.key, key))).get();
28128
30962
  if (row) inserted = rowToDto2(row);
@@ -29915,7 +32749,7 @@ async function createServer(opts) {
29915
32749
  jobRunner.onRunCompleted = (runId, projectId) => runCoordinator.onRunCompleted(runId, projectId);
29916
32750
  const snapshotService = new SnapshotService(registry);
29917
32751
  const orphanedOpenClawDir = path15.join(os6.homedir(), ".openclaw-aero");
29918
- if (fs13.existsSync(orphanedOpenClawDir)) {
32752
+ if (fs15.existsSync(orphanedOpenClawDir)) {
29919
32753
  app.log.warn(
29920
32754
  { path: orphanedOpenClawDir },
29921
32755
  "OpenClaw gateway is no longer used. Remove ~/.openclaw-aero/ manually to reclaim the directory."
@@ -30262,6 +33096,24 @@ async function createServer(opts) {
30262
33096
  sessionCookieName: SESSION_COOKIE_NAME,
30263
33097
  resolveSessionApiKeyId,
30264
33098
  explainContentRecommendation,
33099
+ // On-disk paths the daemon depends on. The api-routes plugin uses these
33100
+ // to fail loud (HTTP 503) when the operator wipes the DB or config out
33101
+ // from under a running serve — SQLite holds the inode open across
33102
+ // `unlink`, so without this the daemon keeps serving stale data from
33103
+ // an orphaned file and `rm ~/.canonry/data.db` silently does nothing.
33104
+ //
33105
+ // Only attach `configPath` if it actually exists at construction time:
33106
+ // production always boots via `serveCommand`, which calls `loadConfig()`
33107
+ // and would have thrown if the file were missing; tests that construct
33108
+ // `createServer` directly (bypassing `loadConfig`) won't have written
33109
+ // a config and shouldn't get 503s from a stub-missing file.
33110
+ runtimeStatePaths: (() => {
33111
+ const configPath = getConfigPath();
33112
+ return {
33113
+ databasePath: opts.config.database,
33114
+ configPath: fs15.existsSync(configPath) ? configPath : null
33115
+ };
33116
+ })(),
30265
33117
  // Local canonry serve runs on the operator's machine, where pointing a
30266
33118
  // webhook at localhost (Discord test container, Pipedream-mock dev server,
30267
33119
  // etc.) is a legitimate workflow. Default to allowing it for the local
@@ -30634,7 +33486,7 @@ async function createServer(opts) {
30634
33486
  });
30635
33487
  const dirname = path15.dirname(fileURLToPath2(import.meta.url));
30636
33488
  const assetsDir = path15.join(dirname, "..", "assets");
30637
- if (fs13.existsSync(assetsDir)) {
33489
+ if (fs15.existsSync(assetsDir)) {
30638
33490
  const indexPath = path15.join(assetsDir, "index.html");
30639
33491
  const injectConfig = (html) => {
30640
33492
  const clientConfig = {};
@@ -30667,8 +33519,8 @@ async function createServer(opts) {
30667
33519
  }
30668
33520
  });
30669
33521
  const serveIndex = (_request, reply) => {
30670
- if (fs13.existsSync(indexPath)) {
30671
- const html = fs13.readFileSync(indexPath, "utf-8");
33522
+ if (fs15.existsSync(indexPath)) {
33523
+ const html = fs15.readFileSync(indexPath, "utf-8");
30672
33524
  return reply.header("Cache-Control", "no-cache, must-revalidate").type("text/html").send(injectConfig(html));
30673
33525
  }
30674
33526
  return reply.status(404).send({ error: "Dashboard not built" });
@@ -30688,8 +33540,8 @@ async function createServer(opts) {
30688
33540
  if (basePath && !url.startsWith(basePath)) {
30689
33541
  return reply.status(404).send({ error: "Not found", path: request.url });
30690
33542
  }
30691
- if (fs13.existsSync(indexPath)) {
30692
- const html = fs13.readFileSync(indexPath, "utf-8");
33543
+ if (fs15.existsSync(indexPath)) {
33544
+ const html = fs15.readFileSync(indexPath, "utf-8");
30693
33545
  return reply.header("Cache-Control", "no-cache, must-revalidate").type("text/html").send(injectConfig(html));
30694
33546
  }
30695
33547
  return reply.status(404).send({ error: "Not found" });
@@ -30777,6 +33629,8 @@ export {
30777
33629
  backfillAiReferralPathsCommand,
30778
33630
  backfillAnswerMentionsCommand,
30779
33631
  backfillInsightsCommand,
33632
+ backfillSnapshotAttributionCommand,
33633
+ backfillTrafficClassificationCommand,
30780
33634
  renderReportHtml,
30781
33635
  setGoogleAuthConfig,
30782
33636
  formatAuditFactorScore,