@canonry/canonry 4.116.0 → 4.117.1

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 (26) hide show
  1. package/README.md +0 -1
  2. package/assets/agent-workspace/skills/aero/references/reporting.md +10 -0
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -0
  4. package/assets/assets/{BacklinksPage-Dy6Xc-LT.js → BacklinksPage-Bkafm10m.js} +1 -1
  5. package/assets/assets/{ChartPrimitives-CHJh5M8x.js → ChartPrimitives-NV_GgqFn.js} +1 -1
  6. package/assets/assets/{ProjectPage-Bjfa4CI5.js → ProjectPage-0-NnRxys.js} +1 -1
  7. package/assets/assets/{RunRow-DmvQnWPf.js → RunRow-df0d24S6.js} +1 -1
  8. package/assets/assets/{RunsPage-BxkxQEKE.js → RunsPage-CU6-4YTM.js} +1 -1
  9. package/assets/assets/{SettingsPage-DCyqHqDh.js → SettingsPage-BKw6B6cd.js} +1 -1
  10. package/assets/assets/{TrafficPage-BT_ae5aO.js → TrafficPage-CzpiYHKj.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-Cps-tSHn.js → TrafficSourceDetailPage-CC5nuqM1.js} +1 -1
  12. package/assets/assets/{arrow-left-D8vdqhx4.js → arrow-left-CbQcW2pL.js} +1 -1
  13. package/assets/assets/{extract-error-message-BOY5txUA.js → extract-error-message-CvqIZ-le.js} +1 -1
  14. package/assets/assets/index-LjcWHaNZ.js +210 -0
  15. package/assets/assets/{trash-2-ap_Fa4zA.js → trash-2-3J2u0woR.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-RVH6QPTA.js → chunk-BMBHUFMI.js} +50 -1
  18. package/dist/{chunk-IQPMK36Y.js → chunk-IAJ5TQ4S.js} +155 -3
  19. package/dist/{chunk-GV665WOU.js → chunk-NY7SC4ES.js} +6 -5
  20. package/dist/{chunk-WEAXJ25Y.js → chunk-V4SW7SVX.js} +1083 -667
  21. package/dist/cli.js +93 -4
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-HEN5HLUM.js → intelligence-service-C5YLASIP.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +10 -10
  26. package/assets/assets/index-DxjY63VC.js +0 -210
@@ -173,6 +173,7 @@ import {
173
173
  gscSitemapListResponseDtoSchema,
174
174
  gscUrlInspectionDtoSchema,
175
175
  hasLocationLabel,
176
+ hostOf,
176
177
  indexingRequestResponseDtoSchema,
177
178
  internalError,
178
179
  isReadOnlyKey,
@@ -259,8 +260,10 @@ import {
259
260
  trafficSyncResponseSchema,
260
261
  unsupportedKind,
261
262
  validationError,
263
+ visibilityCompareDtoSchema,
262
264
  visibilityStateFromAnswerMentioned,
263
265
  visibilityStatsDtoSchema,
266
+ wilsonInterval,
264
267
  windowCutoff,
265
268
  winnabilityClassLabel,
266
269
  winnabilityClassSchema,
@@ -277,10 +280,10 @@ import {
277
280
  wordpressSchemaDeployResultDtoSchema,
278
281
  wordpressSchemaStatusResultDtoSchema,
279
282
  wordpressStatusDtoSchema
280
- } from "./chunk-IQPMK36Y.js";
283
+ } from "./chunk-IAJ5TQ4S.js";
281
284
 
282
285
  // src/intelligence-service.ts
283
- import { eq as eq38, desc as desc18, asc as asc6, and as and30, ne as ne5, or as or6, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
286
+ import { eq as eq39, desc as desc18, asc as asc6, and as and30, ne as ne5, or as or6, inArray as inArray15, gte as gte7, lte as lte4 } from "drizzle-orm";
284
287
 
285
288
  // ../db/src/client.ts
286
289
  import { mkdirSync } from "fs";
@@ -1665,6 +1668,32 @@ CREATE TABLE IF NOT EXISTS _migrations (
1665
1668
  applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
1666
1669
  );
1667
1670
  `;
1671
+ function relinkOrphanedSnapshotQueryIds(tx) {
1672
+ const orphans = tx.all(sql`
1673
+ SELECT qs.id AS snapId, qs.query_text AS text, r.project_id AS projectId
1674
+ FROM query_snapshots qs
1675
+ JOIN runs r ON r.id = qs.run_id
1676
+ WHERE qs.query_id IS NULL AND qs.query_text IS NOT NULL
1677
+ `);
1678
+ if (orphans.length === 0) return;
1679
+ const queryRows = tx.all(sql`SELECT id, project_id AS projectId, query FROM queries`);
1680
+ const byProject = /* @__PURE__ */ new Map();
1681
+ for (const row of queryRows) {
1682
+ let perProject = byProject.get(row.projectId);
1683
+ if (!perProject) {
1684
+ perProject = /* @__PURE__ */ new Map();
1685
+ byProject.set(row.projectId, perProject);
1686
+ }
1687
+ const key = normalizeQueryText(row.query);
1688
+ if (!perProject.has(key)) perProject.set(key, row.id);
1689
+ }
1690
+ for (const orphan of orphans) {
1691
+ const queryId = byProject.get(orphan.projectId)?.get(normalizeQueryText(orphan.text));
1692
+ if (queryId) {
1693
+ tx.run(sql`UPDATE query_snapshots SET query_id = ${queryId} WHERE id = ${orphan.snapId}`);
1694
+ }
1695
+ }
1696
+ }
1668
1697
  var MIGRATION_VERSIONS = [
1669
1698
  {
1670
1699
  version: 2,
@@ -3555,6 +3584,19 @@ var MIGRATION_VERSIONS = [
3555
3584
  `ALTER TABLE ai_referral_events_hourly ADD COLUMN paid_sessions_or_hits INTEGER NOT NULL DEFAULT 0`,
3556
3585
  `ALTER TABLE ai_referral_events_hourly ADD COLUMN organic_sessions_or_hits INTEGER NOT NULL DEFAULT 0`
3557
3586
  ]
3587
+ },
3588
+ {
3589
+ // One-time self-heal for installs whose snapshot->query FKs were orphaned
3590
+ // by the pre-fix delete-all + reinsert replace paths. Data-only UPDATEs
3591
+ // (no schema change) via run() — TS matching because SQLite lower() is
3592
+ // ASCII-only; see the doc comment on relinkOrphanedSnapshotQueryIds and
3593
+ // the downgrade-safety RUN_HOOK_ALLOWLIST justification.
3594
+ version: 98,
3595
+ name: "relink-orphaned-snapshot-query-ids",
3596
+ statements: [],
3597
+ run: (tx) => {
3598
+ relinkOrphanedSnapshotQueryIds(tx);
3599
+ }
3558
3600
  }
3559
3601
  ];
3560
3602
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -5647,8 +5689,8 @@ var METRIC_LABELS = {
5647
5689
  WEBSITE_CLICKS: "Website clicks",
5648
5690
  CALL_CLICKS: "Call clicks"
5649
5691
  };
5650
- function metricLabel(metric) {
5651
- return METRIC_LABELS[metric] ?? metric;
5692
+ function metricLabel(metric2) {
5693
+ return METRIC_LABELS[metric2] ?? metric2;
5652
5694
  }
5653
5695
  function formatAmenityList(amenities) {
5654
5696
  if (amenities.length === 1) return amenities[0];
@@ -5742,12 +5784,12 @@ function analyzeGbp(signals) {
5742
5784
  }
5743
5785
  function pickWorstMetricDrop(loc) {
5744
5786
  let worst = null;
5745
- for (const metric of GBP_HEADLINE_METRICS) {
5746
- const delta = loc.metricDeltaPct[metric];
5747
- const prior = loc.metricPrior7d[metric] ?? 0;
5787
+ for (const metric2 of GBP_HEADLINE_METRICS) {
5788
+ const delta = loc.metricDeltaPct[metric2];
5789
+ const prior = loc.metricPrior7d[metric2] ?? 0;
5748
5790
  if (delta == null || delta > -GBP_METRIC_DROP_PCT || prior < GBP_METRIC_MIN_BASELINE) continue;
5749
5791
  if (!worst || delta < worst.deltaPct) {
5750
- worst = { metric, deltaPct: delta, recent: loc.metricRecent7d[metric] ?? 0, prior };
5792
+ worst = { metric: metric2, deltaPct: delta, recent: loc.metricRecent7d[metric2] ?? 0, prior };
5751
5793
  }
5752
5794
  }
5753
5795
  return worst;
@@ -6462,18 +6504,88 @@ function aliasArraysEqual(a, b) {
6462
6504
  }
6463
6505
 
6464
6506
  // ../api-routes/src/queries.ts
6507
+ import crypto6 from "crypto";
6508
+ import { and, eq as eq5, inArray as inArray2, sql as sql4 } from "drizzle-orm";
6509
+
6510
+ // ../api-routes/src/query-replace.ts
6465
6511
  import crypto5 from "crypto";
6466
- import { and, eq as eq4, inArray, sql as sql4 } from "drizzle-orm";
6512
+ import { eq as eq4, inArray } from "drizzle-orm";
6467
6513
  function preserveSnapshotQueryText(tx, projectId, queryIds) {
6468
6514
  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();
6469
6515
  for (const q of candidates) {
6470
6516
  tx.update(querySnapshots).set({ queryText: q.text }).where(eq4(querySnapshots.queryId, q.id)).run();
6471
6517
  }
6472
6518
  }
6519
+ function diffProjectQueries(existing, incomingTexts) {
6520
+ const groups = /* @__PURE__ */ new Map();
6521
+ for (const row of existing) {
6522
+ const key = normalizeQueryText(row.text);
6523
+ const group = groups.get(key);
6524
+ if (group) group.push(row);
6525
+ else groups.set(key, [row]);
6526
+ }
6527
+ const kept = [];
6528
+ const duplicates = [];
6529
+ const removed = [];
6530
+ const insertedTexts = [];
6531
+ const seenIncoming = /* @__PURE__ */ new Set();
6532
+ for (const text2 of incomingTexts) {
6533
+ const key = normalizeQueryText(text2);
6534
+ if (seenIncoming.has(key)) continue;
6535
+ seenIncoming.add(key);
6536
+ const group = groups.get(key);
6537
+ if (group) {
6538
+ groups.delete(key);
6539
+ const [canonical, ...extras] = group;
6540
+ kept.push({ id: canonical.id, currentText: canonical.text, incomingText: text2 });
6541
+ for (const extra of extras) {
6542
+ duplicates.push({ id: extra.id, text: extra.text, keptId: canonical.id });
6543
+ }
6544
+ } else {
6545
+ insertedTexts.push(text2);
6546
+ }
6547
+ }
6548
+ for (const group of groups.values()) {
6549
+ for (const row of group) removed.push({ id: row.id, text: row.text });
6550
+ }
6551
+ return { kept, duplicates, removed, insertedTexts };
6552
+ }
6553
+ function replaceProjectQueries(tx, projectId, incomingTexts, now) {
6554
+ const existing = tx.select({ id: queries.id, text: queries.query }).from(queries).where(eq4(queries.projectId, projectId)).all();
6555
+ const diff = diffProjectQueries(existing, incomingTexts);
6556
+ if (diff.duplicates.length > 0) {
6557
+ for (const dup of diff.duplicates) {
6558
+ tx.update(querySnapshots).set({ queryId: dup.keptId }).where(eq4(querySnapshots.queryId, dup.id)).run();
6559
+ }
6560
+ tx.delete(queries).where(inArray(queries.id, diff.duplicates.map((d) => d.id))).run();
6561
+ }
6562
+ if (diff.removed.length > 0) {
6563
+ const removedIds = diff.removed.map((r) => r.id);
6564
+ preserveSnapshotQueryText(tx, projectId, removedIds);
6565
+ tx.delete(queries).where(inArray(queries.id, removedIds)).run();
6566
+ }
6567
+ for (const keptRow of diff.kept) {
6568
+ if (keptRow.currentText !== keptRow.incomingText) {
6569
+ tx.update(queries).set({ query: keptRow.incomingText }).where(eq4(queries.id, keptRow.id)).run();
6570
+ }
6571
+ }
6572
+ for (const text2 of diff.insertedTexts) {
6573
+ tx.insert(queries).values({
6574
+ id: crypto5.randomUUID(),
6575
+ projectId,
6576
+ query: text2,
6577
+ provenance: "cli",
6578
+ createdAt: now
6579
+ }).run();
6580
+ }
6581
+ return diff;
6582
+ }
6583
+
6584
+ // ../api-routes/src/queries.ts
6473
6585
  async function queryRoutes(app, opts) {
6474
6586
  app.get("/projects/:name/queries", async (request, reply) => {
6475
6587
  const project = resolveProject(app.db, request.params.name);
6476
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6588
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6477
6589
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
6478
6590
  });
6479
6591
  app.put("/projects/:name/queries", async (request, reply) => {
@@ -6484,17 +6596,7 @@ async function queryRoutes(app, opts) {
6484
6596
  }
6485
6597
  const now = (/* @__PURE__ */ new Date()).toISOString();
6486
6598
  app.db.transaction((tx) => {
6487
- preserveSnapshotQueryText(tx, project.id);
6488
- tx.delete(queries).where(eq4(queries.projectId, project.id)).run();
6489
- for (const q of body.queries) {
6490
- tx.insert(queries).values({
6491
- id: crypto5.randomUUID(),
6492
- projectId: project.id,
6493
- query: q,
6494
- provenance: "cli",
6495
- createdAt: now
6496
- }).run();
6497
- }
6599
+ replaceProjectQueries(tx, project.id, body.queries, now);
6498
6600
  writeAuditLog(tx, auditFromRequest(request, {
6499
6601
  projectId: project.id,
6500
6602
  actor: "api",
@@ -6503,7 +6605,7 @@ async function queryRoutes(app, opts) {
6503
6605
  diff: { queries: body.queries }
6504
6606
  }));
6505
6607
  });
6506
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6608
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6507
6609
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
6508
6610
  });
6509
6611
  app.post("/projects/:name/queries/replace-preview", async (request, reply) => {
@@ -6512,20 +6614,22 @@ async function queryRoutes(app, opts) {
6512
6614
  if (!body || !Array.isArray(body.queries)) {
6513
6615
  throw validationError('Body must contain a "queries" array');
6514
6616
  }
6515
- const currentRows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6617
+ const currentRows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6516
6618
  const currentTexts = currentRows.map((r) => r.query);
6517
- const currentSet = new Set(currentTexts);
6518
- const proposedSet = new Set(body.queries);
6519
- const removed = currentTexts.filter((q) => !proposedSet.has(q));
6520
- const added = body.queries.filter((q) => !currentSet.has(q));
6521
- const unchanged = currentTexts.filter((q) => proposedSet.has(q));
6522
- const currentIds = currentRows.map((r) => r.id);
6619
+ const diff = diffProjectQueries(
6620
+ currentRows.map((r) => ({ id: r.id, text: r.query })),
6621
+ body.queries
6622
+ );
6623
+ const removed = diff.removed.map((r) => r.text);
6624
+ const added = diff.insertedTexts;
6625
+ const unchanged = diff.kept.map((k) => k.currentText);
6626
+ const removedIds = diff.removed.map((r) => r.id);
6523
6627
  let snapshotsDetached = 0;
6524
6628
  let affectedQueries = 0;
6525
- if (currentIds.length > 0) {
6526
- const snapshotCount = app.db.select({ n: sql4`count(*)` }).from(querySnapshots).where(inArray(querySnapshots.queryId, currentIds)).get();
6629
+ if (removedIds.length > 0) {
6630
+ const snapshotCount = app.db.select({ n: sql4`count(*)` }).from(querySnapshots).where(inArray2(querySnapshots.queryId, removedIds)).get();
6527
6631
  snapshotsDetached = snapshotCount?.n ?? 0;
6528
- const distinctAffected = app.db.select({ n: sql4`count(distinct ${querySnapshots.queryId})` }).from(querySnapshots).where(inArray(querySnapshots.queryId, currentIds)).get();
6632
+ const distinctAffected = app.db.select({ n: sql4`count(distinct ${querySnapshots.queryId})` }).from(querySnapshots).where(inArray2(querySnapshots.queryId, removedIds)).get();
6529
6633
  affectedQueries = distinctAffected?.n ?? 0;
6530
6634
  }
6531
6635
  return reply.send({
@@ -6542,14 +6646,14 @@ async function queryRoutes(app, opts) {
6542
6646
  if (!body || !Array.isArray(body.queries) || body.queries.length === 0) {
6543
6647
  throw validationError('Body must contain a non-empty "queries" array');
6544
6648
  }
6545
- const existing = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6649
+ const existing = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6546
6650
  const toDelete = new Set(body.queries);
6547
6651
  const idsToDelete = existing.filter((q) => toDelete.has(q.query)).map((q) => q.id);
6548
6652
  if (idsToDelete.length > 0) {
6549
6653
  app.db.transaction((tx) => {
6550
6654
  preserveSnapshotQueryText(tx, project.id, idsToDelete);
6551
6655
  for (const id of idsToDelete) {
6552
- tx.delete(queries).where(eq4(queries.id, id)).run();
6656
+ tx.delete(queries).where(eq5(queries.id, id)).run();
6553
6657
  }
6554
6658
  writeAuditLog(tx, auditFromRequest(request, {
6555
6659
  projectId: project.id,
@@ -6560,18 +6664,18 @@ async function queryRoutes(app, opts) {
6560
6664
  }));
6561
6665
  });
6562
6666
  }
6563
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6667
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6564
6668
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
6565
6669
  });
6566
6670
  app.delete("/projects/:name/queries/:id", async (request, reply) => {
6567
6671
  const project = resolveProject(app.db, request.params.name);
6568
- const query = app.db.select().from(queries).where(and(eq4(queries.projectId, project.id), eq4(queries.id, request.params.id))).get();
6672
+ const query = app.db.select().from(queries).where(and(eq5(queries.projectId, project.id), eq5(queries.id, request.params.id))).get();
6569
6673
  if (!query) {
6570
6674
  throw notFound("Query", request.params.id);
6571
6675
  }
6572
6676
  app.db.transaction((tx) => {
6573
6677
  preserveSnapshotQueryText(tx, project.id, [query.id]);
6574
- tx.delete(queries).where(eq4(queries.id, query.id)).run();
6678
+ tx.delete(queries).where(eq5(queries.id, query.id)).run();
6575
6679
  writeAuditLog(tx, auditFromRequest(request, {
6576
6680
  projectId: project.id,
6577
6681
  actor: "api",
@@ -6590,13 +6694,13 @@ async function queryRoutes(app, opts) {
6590
6694
  throw validationError('Body must contain a "queries" array');
6591
6695
  }
6592
6696
  const now = (/* @__PURE__ */ new Date()).toISOString();
6593
- const existing = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6697
+ const existing = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6594
6698
  const existingSet = new Set(existing.map((q) => q.query));
6595
6699
  const added = [];
6596
6700
  for (const q of body.queries) {
6597
6701
  if (!existingSet.has(q)) {
6598
6702
  app.db.insert(queries).values({
6599
- id: crypto5.randomUUID(),
6703
+ id: crypto6.randomUUID(),
6600
6704
  projectId: project.id,
6601
6705
  query: q,
6602
6706
  provenance: "cli",
@@ -6615,7 +6719,7 @@ async function queryRoutes(app, opts) {
6615
6719
  diff: { added }
6616
6720
  }));
6617
6721
  }
6618
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6722
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6619
6723
  return reply.send(rows.map((r) => ({ id: r.id, query: r.query, createdAt: r.createdAt })));
6620
6724
  });
6621
6725
  app.post("/projects/:name/queries/generate", async (request, reply) => {
@@ -6642,7 +6746,7 @@ async function queryRoutes(app, opts) {
6642
6746
  if (!opts.onGenerateQueries) {
6643
6747
  throw notImplemented("Query generation is not supported in this deployment");
6644
6748
  }
6645
- const existingRows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6749
+ const existingRows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6646
6750
  const existingQueries = existingRows.map((r) => r.query);
6647
6751
  try {
6648
6752
  const generated = await opts.onGenerateQueries(provider, count2, {
@@ -6660,7 +6764,7 @@ async function queryRoutes(app, opts) {
6660
6764
  });
6661
6765
  app.get("/projects/:name/keywords", async (request, reply) => {
6662
6766
  const project = resolveProject(app.db, request.params.name);
6663
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6767
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6664
6768
  return reply.send(rows.map((r) => ({ id: r.id, keyword: r.query, createdAt: r.createdAt })));
6665
6769
  });
6666
6770
  app.put("/projects/:name/keywords", async (request, reply) => {
@@ -6671,17 +6775,7 @@ async function queryRoutes(app, opts) {
6671
6775
  }
6672
6776
  const now = (/* @__PURE__ */ new Date()).toISOString();
6673
6777
  app.db.transaction((tx) => {
6674
- preserveSnapshotQueryText(tx, project.id);
6675
- tx.delete(queries).where(eq4(queries.projectId, project.id)).run();
6676
- for (const keyword of body.keywords) {
6677
- tx.insert(queries).values({
6678
- id: crypto5.randomUUID(),
6679
- projectId: project.id,
6680
- query: keyword,
6681
- provenance: "cli",
6682
- createdAt: now
6683
- }).run();
6684
- }
6778
+ replaceProjectQueries(tx, project.id, body.keywords, now);
6685
6779
  writeAuditLog(tx, auditFromRequest(request, {
6686
6780
  projectId: project.id,
6687
6781
  actor: "api",
@@ -6690,7 +6784,7 @@ async function queryRoutes(app, opts) {
6690
6784
  diff: { queries: body.keywords }
6691
6785
  }));
6692
6786
  });
6693
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6787
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6694
6788
  return reply.send(rows.map((r) => ({ id: r.id, keyword: r.query, createdAt: r.createdAt })));
6695
6789
  });
6696
6790
  app.delete("/projects/:name/keywords", async (request, reply) => {
@@ -6699,14 +6793,14 @@ async function queryRoutes(app, opts) {
6699
6793
  if (!body || !Array.isArray(body.keywords) || body.keywords.length === 0) {
6700
6794
  throw validationError('Body must contain a non-empty "keywords" array');
6701
6795
  }
6702
- const existing = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6796
+ const existing = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6703
6797
  const toDelete = new Set(body.keywords);
6704
6798
  const idsToDelete = existing.filter((q) => toDelete.has(q.query)).map((q) => q.id);
6705
6799
  if (idsToDelete.length > 0) {
6706
6800
  app.db.transaction((tx) => {
6707
6801
  preserveSnapshotQueryText(tx, project.id, idsToDelete);
6708
6802
  for (const id of idsToDelete) {
6709
- tx.delete(queries).where(eq4(queries.id, id)).run();
6803
+ tx.delete(queries).where(eq5(queries.id, id)).run();
6710
6804
  }
6711
6805
  writeAuditLog(tx, auditFromRequest(request, {
6712
6806
  projectId: project.id,
@@ -6717,7 +6811,7 @@ async function queryRoutes(app, opts) {
6717
6811
  }));
6718
6812
  });
6719
6813
  }
6720
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6814
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6721
6815
  return reply.send(rows.map((r) => ({ id: r.id, keyword: r.query, createdAt: r.createdAt })));
6722
6816
  });
6723
6817
  app.post("/projects/:name/keywords", async (request, reply) => {
@@ -6727,13 +6821,13 @@ async function queryRoutes(app, opts) {
6727
6821
  throw validationError('Body must contain a "keywords" array');
6728
6822
  }
6729
6823
  const now = (/* @__PURE__ */ new Date()).toISOString();
6730
- const existing = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6824
+ const existing = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6731
6825
  const existingSet = new Set(existing.map((q) => q.query));
6732
6826
  const added = [];
6733
6827
  for (const keyword of body.keywords) {
6734
6828
  if (!existingSet.has(keyword)) {
6735
6829
  app.db.insert(queries).values({
6736
- id: crypto5.randomUUID(),
6830
+ id: crypto6.randomUUID(),
6737
6831
  projectId: project.id,
6738
6832
  query: keyword,
6739
6833
  provenance: "cli",
@@ -6752,7 +6846,7 @@ async function queryRoutes(app, opts) {
6752
6846
  diff: { added }
6753
6847
  }));
6754
6848
  }
6755
- const rows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6849
+ const rows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6756
6850
  return reply.send(rows.map((r) => ({ id: r.id, keyword: r.query, createdAt: r.createdAt })));
6757
6851
  });
6758
6852
  app.post("/projects/:name/keywords/generate", async (request, reply) => {
@@ -6779,7 +6873,7 @@ async function queryRoutes(app, opts) {
6779
6873
  if (!opts.onGenerateQueries) {
6780
6874
  throw notImplemented("Keyword generation is not supported in this deployment");
6781
6875
  }
6782
- const existingRows = app.db.select().from(queries).where(eq4(queries.projectId, project.id)).all();
6876
+ const existingRows = app.db.select().from(queries).where(eq5(queries.projectId, project.id)).all();
6783
6877
  const existingQueries = existingRows.map((r) => r.query);
6784
6878
  try {
6785
6879
  const generated = await opts.onGenerateQueries(provider, count2, {
@@ -6798,8 +6892,8 @@ async function queryRoutes(app, opts) {
6798
6892
  }
6799
6893
 
6800
6894
  // ../api-routes/src/competitors.ts
6801
- import crypto6 from "crypto";
6802
- import { and as and2, eq as eq5 } from "drizzle-orm";
6895
+ import crypto7 from "crypto";
6896
+ import { and as and2, eq as eq6 } from "drizzle-orm";
6803
6897
  function normalizeCompetitor(domain) {
6804
6898
  const reg = registrableDomain(domain);
6805
6899
  if (reg) return reg;
@@ -6824,7 +6918,7 @@ function serializeCompetitor(row) {
6824
6918
  async function competitorRoutes(app) {
6825
6919
  app.get("/projects/:name/competitors", async (request, reply) => {
6826
6920
  const project = resolveProject(app.db, request.params.name);
6827
- const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
6921
+ const rows = app.db.select().from(competitors).where(eq6(competitors.projectId, project.id)).all();
6828
6922
  return reply.send(rows.map(serializeCompetitor));
6829
6923
  });
6830
6924
  app.put("/projects/:name/competitors", async (request, reply) => {
@@ -6836,10 +6930,10 @@ async function competitorRoutes(app) {
6836
6930
  const now = (/* @__PURE__ */ new Date()).toISOString();
6837
6931
  const normalizedCompetitors = normalizeCompetitorList(body.competitors);
6838
6932
  app.db.transaction((tx) => {
6839
- tx.delete(competitors).where(eq5(competitors.projectId, project.id)).run();
6933
+ tx.delete(competitors).where(eq6(competitors.projectId, project.id)).run();
6840
6934
  for (const domain of normalizedCompetitors) {
6841
6935
  tx.insert(competitors).values({
6842
- id: crypto6.randomUUID(),
6936
+ id: crypto7.randomUUID(),
6843
6937
  projectId: project.id,
6844
6938
  domain,
6845
6939
  provenance: "cli",
@@ -6854,7 +6948,7 @@ async function competitorRoutes(app) {
6854
6948
  diff: { competitors: normalizedCompetitors }
6855
6949
  });
6856
6950
  });
6857
- const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
6951
+ const rows = app.db.select().from(competitors).where(eq6(competitors.projectId, project.id)).all();
6858
6952
  return reply.send(rows.map(serializeCompetitor));
6859
6953
  });
6860
6954
  app.post("/projects/:name/competitors", async (request, reply) => {
@@ -6863,13 +6957,13 @@ async function competitorRoutes(app) {
6863
6957
  const now = (/* @__PURE__ */ new Date()).toISOString();
6864
6958
  const requested = normalizeCompetitorList(body.competitors);
6865
6959
  app.db.transaction((tx) => {
6866
- const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
6960
+ const existing = tx.select().from(competitors).where(eq6(competitors.projectId, project.id)).all();
6867
6961
  const existingSet = new Set(existing.map((c) => c.domain));
6868
6962
  const added = requested.filter((domain) => !existingSet.has(domain));
6869
6963
  if (added.length === 0) return;
6870
6964
  for (const domain of added) {
6871
6965
  tx.insert(competitors).values({
6872
- id: crypto6.randomUUID(),
6966
+ id: crypto7.randomUUID(),
6873
6967
  projectId: project.id,
6874
6968
  domain,
6875
6969
  provenance: "cli",
@@ -6886,7 +6980,7 @@ async function competitorRoutes(app) {
6886
6980
  diff: { added }
6887
6981
  });
6888
6982
  });
6889
- const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
6983
+ const rows = app.db.select().from(competitors).where(eq6(competitors.projectId, project.id)).all();
6890
6984
  return reply.send(rows.map(serializeCompetitor));
6891
6985
  });
6892
6986
  app.delete("/projects/:name/competitors", async (request, reply) => {
@@ -6894,11 +6988,11 @@ async function competitorRoutes(app) {
6894
6988
  const body = parseCompetitorBatch(request.body);
6895
6989
  const requested = new Set(normalizeCompetitorList(body.competitors));
6896
6990
  app.db.transaction((tx) => {
6897
- const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
6991
+ const existing = tx.select().from(competitors).where(eq6(competitors.projectId, project.id)).all();
6898
6992
  const rowsToDelete = existing.filter((c) => requested.has(c.domain));
6899
6993
  if (rowsToDelete.length === 0) return;
6900
6994
  for (const row of rowsToDelete) {
6901
- tx.delete(competitors).where(eq5(competitors.id, row.id)).run();
6995
+ tx.delete(competitors).where(eq6(competitors.id, row.id)).run();
6902
6996
  }
6903
6997
  writeAuditLog(tx, {
6904
6998
  projectId: project.id,
@@ -6908,17 +7002,17 @@ async function competitorRoutes(app) {
6908
7002
  diff: { deleted: rowsToDelete.map((row) => row.domain) }
6909
7003
  });
6910
7004
  });
6911
- const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
7005
+ const rows = app.db.select().from(competitors).where(eq6(competitors.projectId, project.id)).all();
6912
7006
  return reply.send(rows.map(serializeCompetitor));
6913
7007
  });
6914
7008
  app.delete("/projects/:name/competitors/:id", async (request, reply) => {
6915
7009
  const project = resolveProject(app.db, request.params.name);
6916
- const competitor = app.db.select().from(competitors).where(and2(eq5(competitors.projectId, project.id), eq5(competitors.id, request.params.id))).get();
7010
+ const competitor = app.db.select().from(competitors).where(and2(eq6(competitors.projectId, project.id), eq6(competitors.id, request.params.id))).get();
6917
7011
  if (!competitor) {
6918
7012
  throw notFound("Competitor", request.params.id);
6919
7013
  }
6920
7014
  app.db.transaction((tx) => {
6921
- tx.delete(competitors).where(eq5(competitors.id, competitor.id)).run();
7015
+ tx.delete(competitors).where(eq6(competitors.id, competitor.id)).run();
6922
7016
  writeAuditLog(tx, auditFromRequest(request, {
6923
7017
  projectId: project.id,
6924
7018
  actor: "api",
@@ -6943,23 +7037,23 @@ function parseCompetitorBatch(value) {
6943
7037
  }
6944
7038
 
6945
7039
  // ../api-routes/src/runs.ts
6946
- import crypto8 from "crypto";
6947
- import { and as and4, eq as eq7, asc, desc, or as or2, sql as sql5 } from "drizzle-orm";
7040
+ import crypto9 from "crypto";
7041
+ import { and as and4, eq as eq8, asc, desc, or as or2, sql as sql5 } from "drizzle-orm";
6948
7042
  import { gte } from "drizzle-orm";
6949
7043
 
6950
7044
  // ../api-routes/src/run-queue.ts
6951
- import crypto7 from "crypto";
6952
- import { and as and3, eq as eq6, or } from "drizzle-orm";
7045
+ import crypto8 from "crypto";
7046
+ import { and as and3, eq as eq7, or } from "drizzle-orm";
6953
7047
  function queueRunIfProjectIdle(db, params) {
6954
7048
  const createdAt = params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
6955
7049
  const kind = params.kind ?? "answer-visibility";
6956
7050
  const trigger = params.trigger ?? "manual";
6957
- const runId = crypto7.randomUUID();
7051
+ const runId = crypto8.randomUUID();
6958
7052
  return db.transaction((tx) => {
6959
7053
  const activeRun = tx.select().from(runs).where(
6960
7054
  and3(
6961
- eq6(runs.projectId, params.projectId),
6962
- or(eq6(runs.status, "queued"), eq6(runs.status, "running"))
7055
+ eq7(runs.projectId, params.projectId),
7056
+ or(eq7(runs.status, "queued"), eq7(runs.status, "running"))
6963
7057
  )
6964
7058
  ).get();
6965
7059
  if (activeRun) {
@@ -7005,7 +7099,7 @@ async function runRoutes(app, opts) {
7005
7099
  const providers = rawProviders?.length ? rawProviders : void 0;
7006
7100
  let scopedQueries = null;
7007
7101
  if (body.queries?.length) {
7008
- const trackedRows = app.db.select({ query: queries.query }).from(queries).where(eq7(queries.projectId, project.id)).all();
7102
+ const trackedRows = app.db.select({ query: queries.query }).from(queries).where(eq8(queries.projectId, project.id)).all();
7009
7103
  const tracked = new Set(trackedRows.map((r) => r.query));
7010
7104
  const missing = body.queries.filter((q) => !tracked.has(q));
7011
7105
  if (missing.length) {
@@ -7041,15 +7135,15 @@ async function runRoutes(app, opts) {
7041
7135
  }
7042
7136
  const result = app.db.transaction((tx) => {
7043
7137
  const activeRun = tx.select({ id: runs.id }).from(runs).where(and4(
7044
- eq7(runs.projectId, project.id),
7045
- or2(eq7(runs.status, "queued"), eq7(runs.status, "running"))
7138
+ eq8(runs.projectId, project.id),
7139
+ or2(eq8(runs.status, "queued"), eq8(runs.status, "running"))
7046
7140
  )).get();
7047
7141
  if (activeRun) {
7048
7142
  return { conflict: true };
7049
7143
  }
7050
7144
  const inserted = [];
7051
7145
  for (const loc of projectLocations) {
7052
- const runId2 = crypto8.randomUUID();
7146
+ const runId2 = crypto9.randomUUID();
7053
7147
  tx.insert(runs).values({
7054
7148
  id: runId2,
7055
7149
  projectId: project.id,
@@ -7076,7 +7170,7 @@ async function runRoutes(app, opts) {
7076
7170
  entityType: "run",
7077
7171
  entityId: runId2
7078
7172
  });
7079
- const r = app.db.select().from(runs).where(eq7(runs.id, runId2)).get();
7173
+ const r = app.db.select().from(runs).where(eq8(runs.id, runId2)).get();
7080
7174
  if (opts.onRunCreated) {
7081
7175
  opts.onRunCreated(runId2, project.id, providers, loc);
7082
7176
  }
@@ -7102,7 +7196,7 @@ async function runRoutes(app, opts) {
7102
7196
  entityType: "run",
7103
7197
  entityId: runId
7104
7198
  });
7105
- const run = app.db.select().from(runs).where(eq7(runs.id, runId)).get();
7199
+ const run = app.db.select().from(runs).where(eq8(runs.id, runId)).get();
7106
7200
  if (opts.onRunCreated) {
7107
7201
  opts.onRunCreated(runId, project.id, providers, resolvedLocation);
7108
7202
  }
@@ -7113,15 +7207,15 @@ async function runRoutes(app, opts) {
7113
7207
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
7114
7208
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? void 0 : parsedLimit;
7115
7209
  const kind = parseListKind(request.query.kind);
7116
- const where = kind ? and4(eq7(runs.projectId, project.id), eq7(runs.kind, kind)) : eq7(runs.projectId, project.id);
7210
+ const where = kind ? and4(eq8(runs.projectId, project.id), eq8(runs.kind, kind)) : eq8(runs.projectId, project.id);
7117
7211
  const rows = limit == null ? app.db.select().from(runs).where(where).orderBy(asc(runs.createdAt)).all() : app.db.select().from(runs).where(where).orderBy(desc(runs.createdAt)).limit(limit).all().reverse();
7118
7212
  return reply.send(rows.map(formatRun));
7119
7213
  });
7120
7214
  app.get("/projects/:name/runs/latest", async (request, reply) => {
7121
7215
  const project = resolveProject(app.db, request.params.name);
7122
- const countRow = app.db.select({ count: sql5`count(*)` }).from(runs).where(and4(eq7(runs.projectId, project.id), notProbeRun())).get();
7216
+ const countRow = app.db.select({ count: sql5`count(*)` }).from(runs).where(and4(eq8(runs.projectId, project.id), notProbeRun())).get();
7123
7217
  const totalRuns = countRow?.count ?? 0;
7124
- const latestRun = app.db.select().from(runs).where(and4(eq7(runs.projectId, project.id), notProbeRun())).orderBy(desc(runs.createdAt), desc(runs.id)).limit(1).get();
7218
+ const latestRun = app.db.select().from(runs).where(and4(eq8(runs.projectId, project.id), notProbeRun())).orderBy(desc(runs.createdAt), desc(runs.id)).limit(1).get();
7125
7219
  if (!latestRun) {
7126
7220
  return reply.send({ totalRuns: 0, run: null });
7127
7221
  }
@@ -7137,15 +7231,15 @@ async function runRoutes(app, opts) {
7137
7231
  const kind = parseListKind(request.query.kind);
7138
7232
  const filters = [gte(runs.createdAt, since)];
7139
7233
  if (!includeProbe) filters.push(notProbeRun());
7140
- if (kind) filters.push(eq7(runs.kind, kind));
7234
+ if (kind) filters.push(eq8(runs.kind, kind));
7141
7235
  const scopedProjectId = request.apiKey?.projectId;
7142
- if (scopedProjectId) filters.push(eq7(runs.projectId, scopedProjectId));
7236
+ if (scopedProjectId) filters.push(eq8(runs.projectId, scopedProjectId));
7143
7237
  const rows = app.db.select().from(runs).where(and4(...filters)).orderBy(desc(runs.createdAt), desc(runs.id)).limit(limit).all();
7144
7238
  return reply.send(rows.map(formatRun));
7145
7239
  });
7146
7240
  app.post("/runs", async (request, reply) => {
7147
7241
  const scopedProjectId = request.apiKey?.projectId;
7148
- const allProjects = (scopedProjectId ? app.db.select().from(projects).where(eq7(projects.id, scopedProjectId)) : app.db.select().from(projects)).all();
7242
+ const allProjects = (scopedProjectId ? app.db.select().from(projects).where(eq8(projects.id, scopedProjectId)) : app.db.select().from(projects)).all();
7149
7243
  if (allProjects.length === 0) {
7150
7244
  return reply.status(207).send([]);
7151
7245
  }
@@ -7200,7 +7294,7 @@ async function runRoutes(app, opts) {
7200
7294
  entityType: "run",
7201
7295
  entityId: runId
7202
7296
  });
7203
- const run = app.db.select().from(runs).where(eq7(runs.id, runId)).get();
7297
+ const run = app.db.select().from(runs).where(eq8(runs.id, runId)).get();
7204
7298
  if (opts.onRunCreated) {
7205
7299
  opts.onRunCreated(runId, project.id, providers, resolvedLocation);
7206
7300
  }
@@ -7209,13 +7303,13 @@ async function runRoutes(app, opts) {
7209
7303
  return reply.status(207).send(results);
7210
7304
  });
7211
7305
  app.post("/runs/:id/cancel", async (request, reply) => {
7212
- const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
7306
+ const run = app.db.select().from(runs).where(eq8(runs.id, request.params.id)).get();
7213
7307
  if (!run) throw notFound("Run", request.params.id);
7214
7308
  assertProjectScope(request, run.projectId);
7215
7309
  const terminalStatuses = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
7216
7310
  if (terminalStatuses.has(run.status)) throw runNotCancellable(run.id, run.status);
7217
7311
  const now = (/* @__PURE__ */ new Date()).toISOString();
7218
- app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: serializeRunError({ message: "Cancelled by user" }) }).where(eq7(runs.id, run.id)).run();
7312
+ app.db.update(runs).set({ status: "cancelled", finishedAt: now, error: serializeRunError({ message: "Cancelled by user" }) }).where(eq8(runs.id, run.id)).run();
7219
7313
  writeAuditLog(app.db, {
7220
7314
  projectId: run.projectId,
7221
7315
  actor: "api",
@@ -7223,11 +7317,11 @@ async function runRoutes(app, opts) {
7223
7317
  entityType: "run",
7224
7318
  entityId: run.id
7225
7319
  });
7226
- const updated = app.db.select().from(runs).where(eq7(runs.id, run.id)).get();
7320
+ const updated = app.db.select().from(runs).where(eq8(runs.id, run.id)).get();
7227
7321
  return reply.send(formatRun(updated));
7228
7322
  });
7229
7323
  app.get("/runs/:id", async (request, reply) => {
7230
- const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
7324
+ const run = app.db.select().from(runs).where(eq8(runs.id, request.params.id)).get();
7231
7325
  if (!run) throw notFound("Run", request.params.id);
7232
7326
  assertProjectScope(request, run.projectId);
7233
7327
  return reply.send(loadRunDetail(app, run));
@@ -7299,7 +7393,7 @@ function loadRunDetail(app, run) {
7299
7393
  canonicalDomain: projects.canonicalDomain,
7300
7394
  ownedDomains: projects.ownedDomains,
7301
7395
  aliases: projects.aliases
7302
- }).from(projects).where(eq7(projects.id, run.projectId)).get();
7396
+ }).from(projects).where(eq8(projects.id, run.projectId)).get();
7303
7397
  const snapshots = app.db.select({
7304
7398
  id: querySnapshots.id,
7305
7399
  runId: querySnapshots.runId,
@@ -7316,7 +7410,7 @@ function loadRunDetail(app, run) {
7316
7410
  location: querySnapshots.location,
7317
7411
  rawResponse: querySnapshots.rawResponse,
7318
7412
  createdAt: querySnapshots.createdAt
7319
- }).from(querySnapshots).leftJoin(queries, eq7(querySnapshots.queryId, queries.id)).where(eq7(querySnapshots.runId, run.id)).all();
7413
+ }).from(querySnapshots).leftJoin(queries, eq8(querySnapshots.queryId, queries.id)).where(eq8(querySnapshots.runId, run.id)).all();
7320
7414
  return {
7321
7415
  ...formatRun(run),
7322
7416
  snapshots: snapshots.map((s) => {
@@ -7350,8 +7444,8 @@ function loadRunDetail(app, run) {
7350
7444
  }
7351
7445
 
7352
7446
  // ../api-routes/src/apply.ts
7353
- import crypto10 from "crypto";
7354
- import { and as and5, eq as eq8 } from "drizzle-orm";
7447
+ import crypto11 from "crypto";
7448
+ import { and as and5, eq as eq9 } from "drizzle-orm";
7355
7449
 
7356
7450
  // ../api-routes/src/schedule-utils.ts
7357
7451
  import { CronExpressionParser } from "cron-parser";
@@ -7454,7 +7548,7 @@ function nextRunFromCron(cronExpr, timezone, from = /* @__PURE__ */ new Date())
7454
7548
  }
7455
7549
 
7456
7550
  // ../api-routes/src/webhooks.ts
7457
- import crypto9 from "crypto";
7551
+ import crypto10 from "crypto";
7458
7552
  import dns from "dns/promises";
7459
7553
  import http from "http";
7460
7554
  import https from "https";
@@ -7506,7 +7600,7 @@ async function deliverWebhook(target, payload, webhookSecret) {
7506
7600
  "User-Agent": "Canonry/0.1.0"
7507
7601
  };
7508
7602
  if (webhookSecret) {
7509
- headers["X-Canonry-Signature"] = "sha256=" + crypto9.createHmac("sha256", webhookSecret).update(body).digest("hex");
7603
+ headers["X-Canonry-Signature"] = "sha256=" + crypto10.createHmac("sha256", webhookSecret).update(body).digest("hex");
7510
7604
  }
7511
7605
  return await new Promise((resolve) => {
7512
7606
  const requestOptions = {
@@ -7687,7 +7781,7 @@ async function applyRoutes(app, opts) {
7687
7781
  const configQueries = resolveConfigSpecQueries(config.spec);
7688
7782
  const scopedProjectId = request.apiKey?.projectId;
7689
7783
  if (scopedProjectId) {
7690
- const target = app.db.select({ id: projects.id }).from(projects).where(eq8(projects.name, name)).get();
7784
+ const target = app.db.select({ id: projects.id }).from(projects).where(eq9(projects.name, name)).get();
7691
7785
  if (!target || target.id !== scopedProjectId) {
7692
7786
  throw forbidden("This API key is scoped to a single project and cannot apply this config.");
7693
7787
  }
@@ -7696,7 +7790,7 @@ async function applyRoutes(app, opts) {
7696
7790
  let scheduleAction = null;
7697
7791
  let aliasesChanged = false;
7698
7792
  app.db.transaction((tx) => {
7699
- const existing = tx.select().from(projects).where(eq8(projects.name, name)).get();
7793
+ const existing = tx.select().from(projects).where(eq9(projects.name, name)).get();
7700
7794
  const nextAliases = normalizeProjectAliases(config.spec.displayName, config.spec.aliases ?? []);
7701
7795
  if (existing) {
7702
7796
  const prevAliases = existing.aliases;
@@ -7719,7 +7813,7 @@ async function applyRoutes(app, opts) {
7719
7813
  configSource: "config-file",
7720
7814
  configRevision: existing.configRevision + 1,
7721
7815
  updatedAt: now
7722
- }).where(eq8(projects.id, existing.id)).run();
7816
+ }).where(eq9(projects.id, existing.id)).run();
7723
7817
  writeAuditLog(tx, {
7724
7818
  projectId,
7725
7819
  actor: "api",
@@ -7728,7 +7822,7 @@ async function applyRoutes(app, opts) {
7728
7822
  entityId: projectId
7729
7823
  });
7730
7824
  } else {
7731
- projectId = crypto10.randomUUID();
7825
+ projectId = crypto11.randomUUID();
7732
7826
  tx.insert(projects).values({
7733
7827
  id: projectId,
7734
7828
  name,
@@ -7757,16 +7851,7 @@ async function applyRoutes(app, opts) {
7757
7851
  entityId: projectId
7758
7852
  });
7759
7853
  }
7760
- tx.delete(queries).where(eq8(queries.projectId, projectId)).run();
7761
- for (const q of configQueries) {
7762
- tx.insert(queries).values({
7763
- id: crypto10.randomUUID(),
7764
- projectId,
7765
- query: q,
7766
- provenance: "cli",
7767
- createdAt: now
7768
- }).run();
7769
- }
7854
+ replaceProjectQueries(tx, projectId, configQueries, now);
7770
7855
  writeAuditLog(tx, {
7771
7856
  projectId,
7772
7857
  actor: "api",
@@ -7774,11 +7859,11 @@ async function applyRoutes(app, opts) {
7774
7859
  entityType: "query",
7775
7860
  diff: { queries: configQueries }
7776
7861
  });
7777
- tx.delete(competitors).where(eq8(competitors.projectId, projectId)).run();
7862
+ tx.delete(competitors).where(eq9(competitors.projectId, projectId)).run();
7778
7863
  const normalizedCompetitors = normalizeCompetitorList2(config.spec.competitors);
7779
7864
  for (const domain of normalizedCompetitors) {
7780
7865
  tx.insert(competitors).values({
7781
- id: crypto10.randomUUID(),
7866
+ id: crypto11.randomUUID(),
7782
7867
  projectId,
7783
7868
  domain,
7784
7869
  provenance: "cli",
@@ -7794,7 +7879,7 @@ async function applyRoutes(app, opts) {
7794
7879
  });
7795
7880
  const AV_KIND = SchedulableRunKinds["answer-visibility"];
7796
7881
  if (resolvedSchedule) {
7797
- const existingSched = tx.select().from(schedules).where(and5(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
7882
+ const existingSched = tx.select().from(schedules).where(and5(eq9(schedules.projectId, projectId), eq9(schedules.kind, AV_KIND))).get();
7798
7883
  if (existingSched) {
7799
7884
  tx.update(schedules).set({
7800
7885
  cronExpr: resolvedSchedule.cronExpr,
@@ -7803,10 +7888,10 @@ async function applyRoutes(app, opts) {
7803
7888
  providers: config.spec.schedule?.providers ?? [],
7804
7889
  enabled: true,
7805
7890
  updatedAt: now
7806
- }).where(eq8(schedules.id, existingSched.id)).run();
7891
+ }).where(eq9(schedules.id, existingSched.id)).run();
7807
7892
  } else {
7808
7893
  tx.insert(schedules).values({
7809
- id: crypto10.randomUUID(),
7894
+ id: crypto11.randomUUID(),
7810
7895
  projectId,
7811
7896
  kind: AV_KIND,
7812
7897
  cronExpr: resolvedSchedule.cronExpr,
@@ -7820,21 +7905,21 @@ async function applyRoutes(app, opts) {
7820
7905
  }
7821
7906
  scheduleAction = "upsert";
7822
7907
  } else if (deleteSchedule) {
7823
- const existingSched = tx.select().from(schedules).where(and5(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).get();
7908
+ const existingSched = tx.select().from(schedules).where(and5(eq9(schedules.projectId, projectId), eq9(schedules.kind, AV_KIND))).get();
7824
7909
  if (existingSched) {
7825
- tx.delete(schedules).where(and5(eq8(schedules.projectId, projectId), eq8(schedules.kind, AV_KIND))).run();
7910
+ tx.delete(schedules).where(and5(eq9(schedules.projectId, projectId), eq9(schedules.kind, AV_KIND))).run();
7826
7911
  scheduleAction = "delete";
7827
7912
  }
7828
7913
  }
7829
7914
  if (hasNotifications) {
7830
- tx.delete(notifications).where(eq8(notifications.projectId, projectId)).run();
7915
+ tx.delete(notifications).where(eq9(notifications.projectId, projectId)).run();
7831
7916
  for (const notif of config.spec.notifications) {
7832
7917
  tx.insert(notifications).values({
7833
- id: crypto10.randomUUID(),
7918
+ id: crypto11.randomUUID(),
7834
7919
  projectId,
7835
7920
  channel: notif.channel,
7836
7921
  config: { url: notif.url, events: notif.events },
7837
- webhookSecret: crypto10.randomBytes(32).toString("hex"),
7922
+ webhookSecret: crypto11.randomBytes(32).toString("hex"),
7838
7923
  enabled: true,
7839
7924
  createdAt: now,
7840
7925
  updatedAt: now
@@ -7861,7 +7946,7 @@ async function applyRoutes(app, opts) {
7861
7946
  if ("google" in rawSpec && config.spec.google?.gsc?.propertyUrl) {
7862
7947
  opts?.onGoogleConnectionPropertyUpdated?.(config.spec.canonicalDomain, "gsc", config.spec.google.gsc.propertyUrl);
7863
7948
  }
7864
- const project = app.db.select().from(projects).where(eq8(projects.id, projectId)).get();
7949
+ const project = app.db.select().from(projects).where(eq9(projects.id, projectId)).get();
7865
7950
  return reply.status(200).send({
7866
7951
  id: project.id,
7867
7952
  name: project.name,
@@ -7906,7 +7991,7 @@ function normalizeCompetitorList2(domains) {
7906
7991
  }
7907
7992
 
7908
7993
  // ../api-routes/src/history.ts
7909
- import { and as and6, eq as eq9, desc as desc2, inArray as inArray2 } from "drizzle-orm";
7994
+ import { and as and6, eq as eq10, desc as desc2, inArray as inArray3 } from "drizzle-orm";
7910
7995
 
7911
7996
  // ../api-routes/src/notification-redaction.ts
7912
7997
  var REDACTED_URL = {
@@ -7952,19 +8037,19 @@ function redactNotificationDiff(value) {
7952
8037
  async function historyRoutes(app) {
7953
8038
  app.get("/projects/:name/history", async (request, reply) => {
7954
8039
  const project = resolveProject(app.db, request.params.name);
7955
- const rows = app.db.select().from(auditLog).where(eq9(auditLog.projectId, project.id)).orderBy(desc2(auditLog.createdAt)).all();
8040
+ const rows = app.db.select().from(auditLog).where(eq10(auditLog.projectId, project.id)).orderBy(desc2(auditLog.createdAt)).all();
7956
8041
  return reply.send(rows.map(formatAuditEntry));
7957
8042
  });
7958
8043
  app.get("/history", async (request, reply) => {
7959
8044
  const scopedProjectId = request.apiKey?.projectId;
7960
- const rows = app.db.select().from(auditLog).where(scopedProjectId ? eq9(auditLog.projectId, scopedProjectId) : void 0).orderBy(desc2(auditLog.createdAt)).all();
8045
+ const rows = app.db.select().from(auditLog).where(scopedProjectId ? eq10(auditLog.projectId, scopedProjectId) : void 0).orderBy(desc2(auditLog.createdAt)).all();
7961
8046
  return reply.send(rows.map(formatAuditEntry));
7962
8047
  });
7963
8048
  app.get("/projects/:name/snapshots", async (request, reply) => {
7964
8049
  const project = resolveProject(app.db, request.params.name);
7965
8050
  const limit = parseInt(request.query.limit ?? "50", 10);
7966
8051
  const offset = parseInt(request.query.offset ?? "0", 10);
7967
- const projectRuns = app.db.select({ id: runs.id }).from(runs).where(and6(eq9(runs.projectId, project.id), notProbeRun())).all();
8052
+ const projectRuns = app.db.select({ id: runs.id }).from(runs).where(and6(eq10(runs.projectId, project.id), notProbeRun())).all();
7968
8053
  if (projectRuns.length === 0) {
7969
8054
  return reply.send({ snapshots: [], total: 0 });
7970
8055
  }
@@ -7983,7 +8068,7 @@ async function historyRoutes(app) {
7983
8068
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
7984
8069
  location: querySnapshots.location,
7985
8070
  createdAt: querySnapshots.createdAt
7986
- }).from(querySnapshots).leftJoin(queries, eq9(querySnapshots.queryId, queries.id)).where(inArray2(querySnapshots.runId, projectRuns.map((r) => r.id))).orderBy(desc2(querySnapshots.createdAt)).all();
8071
+ }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray3(querySnapshots.runId, projectRuns.map((r) => r.id))).orderBy(desc2(querySnapshots.createdAt)).all();
7987
8072
  const locationFilter = request.query.location;
7988
8073
  const filtered = locationFilter !== void 0 ? allSnapshots.filter((s) => s.location === (locationFilter || null)) : allSnapshots;
7989
8074
  const total = filtered.length;
@@ -8012,13 +8097,13 @@ async function historyRoutes(app) {
8012
8097
  });
8013
8098
  app.get("/projects/:name/timeline", async (request, reply) => {
8014
8099
  const project = resolveProject(app.db, request.params.name);
8015
- const projectQueries = app.db.select().from(queries).where(eq9(queries.projectId, project.id)).all();
8016
- const projectRuns = app.db.select().from(runs).where(and6(eq9(runs.projectId, project.id), notProbeRun())).orderBy(runs.createdAt).all();
8100
+ const projectQueries = app.db.select().from(queries).where(eq10(queries.projectId, project.id)).all();
8101
+ const projectRuns = app.db.select().from(runs).where(and6(eq10(runs.projectId, project.id), notProbeRun())).orderBy(runs.createdAt).all();
8017
8102
  if (projectRuns.length === 0 || projectQueries.length === 0) {
8018
8103
  return reply.send([]);
8019
8104
  }
8020
8105
  const runIds = new Set(projectRuns.map((r) => r.id));
8021
- const rawSnapshots = app.db.select().from(querySnapshots).where(inArray2(querySnapshots.runId, [...runIds])).all();
8106
+ const rawSnapshots = app.db.select().from(querySnapshots).where(inArray3(querySnapshots.runId, [...runIds])).all();
8022
8107
  const timelineLocationFilter = request.query.location;
8023
8108
  const filteredSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
8024
8109
  const allSnapshots = filteredSnapshots.map((snapshot) => ({
@@ -8139,7 +8224,7 @@ async function historyRoutes(app) {
8139
8224
  throw validationError("Both run1 and run2 query params are required");
8140
8225
  }
8141
8226
  const requestedRunIds = [.../* @__PURE__ */ new Set([run1, run2])];
8142
- const runRows = app.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(inArray2(runs.id, requestedRunIds)).all();
8227
+ const runRows = app.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(inArray3(runs.id, requestedRunIds)).all();
8143
8228
  const runsById = new Map(runRows.map((row) => [row.id, row]));
8144
8229
  for (const runId of requestedRunIds) {
8145
8230
  const run = runsById.get(runId);
@@ -8153,14 +8238,14 @@ async function historyRoutes(app) {
8153
8238
  citationState: querySnapshots.citationState,
8154
8239
  answerMentioned: querySnapshots.answerMentioned,
8155
8240
  answerText: querySnapshots.answerText
8156
- }).from(querySnapshots).leftJoin(queries, eq9(querySnapshots.queryId, queries.id)).where(eq9(querySnapshots.runId, run1)).all();
8241
+ }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(eq10(querySnapshots.runId, run1)).all();
8157
8242
  const snaps2 = app.db.select({
8158
8243
  queryId: querySnapshots.queryId,
8159
8244
  query: queries.query,
8160
8245
  citationState: querySnapshots.citationState,
8161
8246
  answerMentioned: querySnapshots.answerMentioned,
8162
8247
  answerText: querySnapshots.answerText
8163
- }).from(querySnapshots).leftJoin(queries, eq9(querySnapshots.queryId, queries.id)).where(eq9(querySnapshots.runId, run2)).all();
8248
+ }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(eq10(querySnapshots.runId, run2)).all();
8164
8249
  const map1 = /* @__PURE__ */ new Map();
8165
8250
  for (const s of snaps1) {
8166
8251
  const resolved = {
@@ -8224,13 +8309,13 @@ function formatAuditEntry(row) {
8224
8309
  }
8225
8310
 
8226
8311
  // ../api-routes/src/analytics.ts
8227
- import { and as and7, eq as eq10, desc as desc3, inArray as inArray3 } from "drizzle-orm";
8312
+ import { and as and7, eq as eq11, desc as desc3, inArray as inArray4 } from "drizzle-orm";
8228
8313
  async function analyticsRoutes(app) {
8229
8314
  app.get("/projects/:name/analytics/metrics", async (request, reply) => {
8230
8315
  const project = resolveProject(app.db, request.params.name);
8231
8316
  const window = parseWindow(request.query.window);
8232
8317
  const cutoff = windowCutoff(window);
8233
- const projectRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
8318
+ const projectRuns = app.db.select().from(runs).where(and7(eq11(runs.projectId, project.id), eq11(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
8234
8319
  if (projectRuns.length === 0) {
8235
8320
  return reply.send({
8236
8321
  window,
@@ -8251,14 +8336,14 @@ async function analyticsRoutes(app) {
8251
8336
  answerMentioned: querySnapshots.answerMentioned,
8252
8337
  answerText: querySnapshots.answerText,
8253
8338
  createdAt: querySnapshots.createdAt
8254
- }).from(querySnapshots).where(inArray3(querySnapshots.runId, runIds)).all());
8339
+ }).from(querySnapshots).where(inArray4(querySnapshots.runId, runIds)).all());
8255
8340
  const allSnapshots = rawSnapshots.map((s) => ({
8256
8341
  ...s,
8257
8342
  resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
8258
8343
  }));
8259
- const projectQueries = app.db.select({ id: queries.id, createdAt: queries.createdAt }).from(queries).where(eq10(queries.projectId, project.id)).all();
8344
+ const projectQueries = app.db.select({ id: queries.id, createdAt: queries.createdAt }).from(queries).where(eq11(queries.projectId, project.id)).all();
8260
8345
  const queryCreatedAt = new Map(projectQueries.map((q) => [q.id, q.createdAt]));
8261
- const mentionShareCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq10(competitors.projectId, project.id)).all().map((c) => ({
8346
+ const mentionShareCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, project.id)).all().map((c) => ({
8262
8347
  domain: c.domain,
8263
8348
  brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
8264
8349
  }));
@@ -8282,14 +8367,14 @@ async function analyticsRoutes(app) {
8282
8367
  const project = resolveProject(app.db, request.params.name);
8283
8368
  const window = parseWindow(request.query.window);
8284
8369
  const cutoff = windowCutoff(window);
8285
- const completedRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc3(runs.createdAt), desc3(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial");
8370
+ const completedRuns = app.db.select().from(runs).where(and7(eq11(runs.projectId, project.id), eq11(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc3(runs.createdAt), desc3(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial");
8286
8371
  const latestGroup = groupRunsByCreatedAt(completedRuns)[0] ?? [];
8287
8372
  const latestGroupRunIds = latestGroup.map((r) => r.id);
8288
8373
  const latestRun = pickGroupRepresentative(latestGroup);
8289
8374
  if (!latestRun) {
8290
8375
  return reply.send({ cited: [], gap: [], uncited: [], mentionedQueries: [], mentionGap: [], notMentioned: [], runId: "", window });
8291
8376
  }
8292
- const windowRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
8377
+ const windowRuns = app.db.select().from(runs).where(and7(eq11(runs.projectId, project.id), eq11(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(runs.createdAt).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
8293
8378
  const windowRunIds = windowRuns.map((r) => r.id);
8294
8379
  const runIdToCreatedAt = new Map(windowRuns.map((r) => [r.id, r.createdAt]));
8295
8380
  const consistencyMap = /* @__PURE__ */ new Map();
@@ -8300,7 +8385,7 @@ async function analyticsRoutes(app) {
8300
8385
  citationState: querySnapshots.citationState,
8301
8386
  answerMentioned: querySnapshots.answerMentioned,
8302
8387
  answerText: querySnapshots.answerText
8303
- }).from(querySnapshots).where(inArray3(querySnapshots.runId, windowRunIds)).all());
8388
+ }).from(querySnapshots).where(inArray4(querySnapshots.runId, windowRunIds)).all());
8304
8389
  for (const s of allWindowSnaps) {
8305
8390
  const timePoint = runIdToCreatedAt.get(s.runId) ?? s.runId;
8306
8391
  let entry = consistencyMap.get(s.queryId);
@@ -8321,7 +8406,7 @@ async function analyticsRoutes(app) {
8321
8406
  answerMentioned: querySnapshots.answerMentioned,
8322
8407
  answerText: querySnapshots.answerText,
8323
8408
  competitorOverlap: querySnapshots.competitorOverlap
8324
- }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray3(querySnapshots.runId, latestGroupRunIds)).all());
8409
+ }).from(querySnapshots).leftJoin(queries, eq11(querySnapshots.queryId, queries.id)).where(inArray4(querySnapshots.runId, latestGroupRunIds)).all());
8325
8410
  const snapshots = rawSnapshots.map((s) => ({
8326
8411
  ...s,
8327
8412
  resolvedMentioned: resolveSnapshotAnswerMentioned(s, project)
@@ -8413,14 +8498,14 @@ async function analyticsRoutes(app) {
8413
8498
  }
8414
8499
  const classifyCtx = {
8415
8500
  projectDomains: effectiveDomains(project),
8416
- competitorDomains: app.db.select({ domain: competitors.domain }).from(competitors).where(eq10(competitors.projectId, project.id)).all().map((r) => r.domain)
8501
+ competitorDomains: app.db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, project.id)).all().map((r) => r.domain)
8417
8502
  };
8418
8503
  const storedSurfaceClasses = /* @__PURE__ */ new Map();
8419
- for (const row of app.db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq10(domainClassifications.projectId, project.id)).all()) {
8504
+ for (const row of app.db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq11(domainClassifications.projectId, project.id)).all()) {
8420
8505
  const mapped = surfaceClassFromCompetitorType(row.competitorType);
8421
8506
  if (mapped) storedSurfaceClasses.set(normalizeProjectDomain(row.domain), mapped);
8422
8507
  }
8423
- const windowRuns = app.db.select().from(runs).where(and7(eq10(runs.projectId, project.id), eq10(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc3(runs.createdAt), desc3(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
8508
+ const windowRuns = app.db.select().from(runs).where(and7(eq11(runs.projectId, project.id), eq11(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc3(runs.createdAt), desc3(runs.id)).all().filter((r) => r.status === "completed" || r.status === "partial").filter((r) => !cutoff || r.createdAt >= cutoff);
8424
8509
  if (windowRuns.length === 0) {
8425
8510
  return reply.send({
8426
8511
  overall: [],
@@ -8440,7 +8525,7 @@ async function analyticsRoutes(app) {
8440
8525
  query: queries.query,
8441
8526
  provider: querySnapshots.provider,
8442
8527
  rawResponse: querySnapshots.rawResponse
8443
- }).from(querySnapshots).leftJoin(queries, eq10(querySnapshots.queryId, queries.id)).where(inArray3(querySnapshots.runId, windowRunIds)).all();
8528
+ }).from(querySnapshots).leftJoin(queries, eq11(querySnapshots.queryId, queries.id)).where(inArray4(querySnapshots.runId, windowRunIds)).all();
8444
8529
  const overallCounts = /* @__PURE__ */ new Map();
8445
8530
  const byQuery = {};
8446
8531
  const overallDomains = /* @__PURE__ */ new Map();
@@ -8541,7 +8626,7 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt, ment
8541
8626
  });
8542
8627
  if (eligible.length > 0) usable = eligible;
8543
8628
  }
8544
- const metric = computeProviderMetric(usable);
8629
+ const metric2 = computeProviderMetric(usable);
8545
8630
  const queryCount = new Set(usable.map((s) => s.queryId)).size;
8546
8631
  const byProvider = {};
8547
8632
  for (const provider of new Set(usable.map((s) => s.provider))) {
@@ -8550,12 +8635,12 @@ function computeBuckets(snapshots, projectRuns, bucketDays, queryCreatedAt, ment
8550
8635
  buckets.push({
8551
8636
  startDate: startISO,
8552
8637
  endDate: endISO,
8553
- citationRate: metric.citationRate,
8554
- cited: metric.cited,
8555
- total: metric.total,
8638
+ citationRate: metric2.citationRate,
8639
+ cited: metric2.cited,
8640
+ total: metric2.total,
8556
8641
  queryCount,
8557
- mentionRate: metric.mentionRate,
8558
- mentionedCount: metric.mentionedCount,
8642
+ mentionRate: metric2.mentionRate,
8643
+ mentionedCount: metric2.mentionedCount,
8559
8644
  mentionShare: computeMentionShareBucketMetric(usable, mentionShareCompetitors),
8560
8645
  byProvider
8561
8646
  });
@@ -8685,7 +8770,7 @@ function buildCategoryCounts(counts2) {
8685
8770
  }
8686
8771
 
8687
8772
  // ../api-routes/src/intelligence.ts
8688
- import { eq as eq11, desc as desc4, and as and8, inArray as inArray4, like } from "drizzle-orm";
8773
+ import { eq as eq12, desc as desc4, and as and8, inArray as inArray5, like } from "drizzle-orm";
8689
8774
  var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
8690
8775
  function severitiesAtOrAbove(min) {
8691
8776
  const floor = SEVERITY_RANK[min];
@@ -8803,19 +8888,19 @@ function aggregateHealthSnapshots(projectId, rows) {
8803
8888
  async function intelligenceRoutes(app) {
8804
8889
  app.get("/projects/:name/insights", async (request, reply) => {
8805
8890
  const project = resolveProject(app.db, request.params.name);
8806
- const conditions = [eq11(insights.projectId, project.id)];
8891
+ const conditions = [eq12(insights.projectId, project.id)];
8807
8892
  if (request.query.runId) {
8808
- conditions.push(eq11(insights.runId, request.query.runId));
8893
+ conditions.push(eq12(insights.runId, request.query.runId));
8809
8894
  }
8810
8895
  const typeFilter = request.query.type?.trim();
8811
8896
  if (typeFilter) {
8812
8897
  conditions.push(
8813
- typeFilter.endsWith("*") ? like(insights.type, `${typeFilter.slice(0, -1)}%`) : eq11(insights.type, typeFilter)
8898
+ typeFilter.endsWith("*") ? like(insights.type, `${typeFilter.slice(0, -1)}%`) : eq12(insights.type, typeFilter)
8814
8899
  );
8815
8900
  }
8816
8901
  const severityFilter = request.query.severity?.trim();
8817
8902
  if (severityFilter) {
8818
- conditions.push(inArray4(insights.severity, severitiesAtOrAbove(severityFilter)));
8903
+ conditions.push(inArray5(insights.severity, severitiesAtOrAbove(severityFilter)));
8819
8904
  }
8820
8905
  let limit;
8821
8906
  if (request.query.limit !== void 0) {
@@ -8833,7 +8918,7 @@ async function intelligenceRoutes(app) {
8833
8918
  });
8834
8919
  app.get("/projects/:name/insights/:id", async (request, reply) => {
8835
8920
  const project = resolveProject(app.db, request.params.name);
8836
- const row = app.db.select().from(insights).where(eq11(insights.id, request.params.id)).get();
8921
+ const row = app.db.select().from(insights).where(eq12(insights.id, request.params.id)).get();
8837
8922
  if (!row || row.projectId !== project.id) {
8838
8923
  throw notFound("Insight", request.params.id);
8839
8924
  }
@@ -8841,19 +8926,19 @@ async function intelligenceRoutes(app) {
8841
8926
  });
8842
8927
  app.post("/projects/:name/insights/:id/dismiss", async (request, reply) => {
8843
8928
  const project = resolveProject(app.db, request.params.name);
8844
- const row = app.db.select().from(insights).where(eq11(insights.id, request.params.id)).get();
8929
+ const row = app.db.select().from(insights).where(eq12(insights.id, request.params.id)).get();
8845
8930
  if (!row || row.projectId !== project.id) {
8846
8931
  throw notFound("Insight", request.params.id);
8847
8932
  }
8848
- app.db.update(insights).set({ dismissed: true }).where(eq11(insights.id, request.params.id)).run();
8933
+ app.db.update(insights).set({ dismissed: true }).where(eq12(insights.id, request.params.id)).run();
8849
8934
  return reply.send({ ok: true });
8850
8935
  });
8851
8936
  app.get("/projects/:name/health/latest", async (request, reply) => {
8852
8937
  const project = resolveProject(app.db, request.params.name);
8853
8938
  const projectVisRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and8(
8854
- eq11(runs.projectId, project.id),
8855
- eq11(runs.kind, RunKinds["answer-visibility"]),
8856
- inArray4(runs.status, [RunStatuses.completed, RunStatuses.partial]),
8939
+ eq12(runs.projectId, project.id),
8940
+ eq12(runs.kind, RunKinds["answer-visibility"]),
8941
+ inArray5(runs.status, [RunStatuses.completed, RunStatuses.partial]),
8857
8942
  // Health-latest is the dashboard headline; probe runs must not
8858
8943
  // displace the most recent real visibility sweep.
8859
8944
  notProbeRun()
@@ -8862,14 +8947,14 @@ async function intelligenceRoutes(app) {
8862
8947
  const latestGroupRunIds = latestGroup.map((r) => r.id);
8863
8948
  if (latestGroupRunIds.length > 0) {
8864
8949
  const groupRows = app.db.select().from(healthSnapshots).where(and8(
8865
- eq11(healthSnapshots.projectId, project.id),
8866
- inArray4(healthSnapshots.runId, latestGroupRunIds)
8950
+ eq12(healthSnapshots.projectId, project.id),
8951
+ inArray5(healthSnapshots.runId, latestGroupRunIds)
8867
8952
  )).all();
8868
8953
  if (groupRows.length > 0) {
8869
8954
  return reply.send(aggregateHealthSnapshots(project.id, groupRows));
8870
8955
  }
8871
8956
  }
8872
- const fallback = app.db.select().from(healthSnapshots).where(eq11(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(1).get();
8957
+ const fallback = app.db.select().from(healthSnapshots).where(eq12(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(1).get();
8873
8958
  if (!fallback) {
8874
8959
  return reply.send(emptyHealthSnapshot(project.id));
8875
8960
  }
@@ -8879,20 +8964,20 @@ async function intelligenceRoutes(app) {
8879
8964
  const project = resolveProject(app.db, request.params.name);
8880
8965
  const parsed = request.query.limit ? parseInt(request.query.limit, 10) : NaN;
8881
8966
  const limit = Number.isNaN(parsed) ? 30 : Math.min(Math.max(parsed, 1), 100);
8882
- const rows = app.db.select().from(healthSnapshots).where(eq11(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(limit).all();
8967
+ const rows = app.db.select().from(healthSnapshots).where(eq12(healthSnapshots.projectId, project.id)).orderBy(desc4(healthSnapshots.createdAt)).limit(limit).all();
8883
8968
  return reply.send(rows.map(mapHealthRow));
8884
8969
  });
8885
8970
  }
8886
8971
 
8887
8972
  // ../api-routes/src/report.ts
8888
- import { and as and12, desc as desc7, eq as eq15, gte as gte2, inArray as inArray6, lt, lte, ne as ne2, or as or3, sql as sql7 } from "drizzle-orm";
8973
+ import { and as and12, desc as desc7, eq as eq16, gte as gte2, inArray as inArray7, lt, lte, ne as ne2, or as or3, sql as sql7 } from "drizzle-orm";
8889
8974
 
8890
8975
  // ../api-routes/src/content.ts
8891
- import crypto11 from "crypto";
8892
- import { and as and10, desc as desc6, eq as eq13 } from "drizzle-orm";
8976
+ import crypto12 from "crypto";
8977
+ import { and as and10, desc as desc6, eq as eq14 } from "drizzle-orm";
8893
8978
 
8894
8979
  // ../api-routes/src/content-data.ts
8895
- import { and as and9, eq as eq12, desc as desc5, inArray as inArray5 } from "drizzle-orm";
8980
+ import { and as and9, eq as eq13, desc as desc5, inArray as inArray6 } from "drizzle-orm";
8896
8981
  var RECENT_RUNS_WINDOW = 5;
8897
8982
  function loadOrchestratorInput(db, project, locationFilter = void 0) {
8898
8983
  const projectId = project.id;
@@ -8941,7 +9026,7 @@ function loadOrchestratorInput(db, project, locationFilter = void 0) {
8941
9026
  };
8942
9027
  }
8943
9028
  function loadDomainClasses(db, projectId) {
8944
- const rows = db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq12(domainClassifications.projectId, projectId)).all();
9029
+ const rows = db.select({ domain: domainClassifications.domain, competitorType: domainClassifications.competitorType }).from(domainClassifications).where(eq13(domainClassifications.projectId, projectId)).all();
8945
9030
  return new Map(rows.map((r) => [normalizeDomain(r.domain), r.competitorType]));
8946
9031
  }
8947
9032
  function buildQueryIntentModifiers(project, locationFilter) {
@@ -9013,22 +9098,22 @@ var US_REGION_ABBREVIATIONS = {
9013
9098
  wyoming: "wy"
9014
9099
  };
9015
9100
  function listQueries(db, projectId) {
9016
- const rows = db.select({ text: queries.query }).from(queries).where(eq12(queries.projectId, projectId)).all();
9101
+ const rows = db.select({ text: queries.query }).from(queries).where(eq13(queries.projectId, projectId)).all();
9017
9102
  return rows.map((r) => r.text);
9018
9103
  }
9019
9104
  function listCompetitorDomains(db, projectId) {
9020
- const rows = db.select({ domain: competitors.domain }).from(competitors).where(eq12(competitors.projectId, projectId)).all();
9105
+ const rows = db.select({ domain: competitors.domain }).from(competitors).where(eq13(competitors.projectId, projectId)).all();
9021
9106
  return rows.map((r) => r.domain);
9022
9107
  }
9023
9108
  function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter) {
9024
9109
  const rows = db.select({ id: runs.id, location: runs.location }).from(runs).where(
9025
9110
  and9(
9026
- eq12(runs.projectId, projectId),
9027
- eq12(runs.kind, RunKinds["answer-visibility"]),
9111
+ eq13(runs.projectId, projectId),
9112
+ eq13(runs.kind, RunKinds["answer-visibility"]),
9028
9113
  // Queued/running/failed/cancelled runs may have partial or no
9029
9114
  // snapshots; including them risks pointing latestRunId at a run with
9030
9115
  // no usable evidence.
9031
- inArray5(runs.status, [RunStatuses.completed, RunStatuses.partial]),
9116
+ inArray6(runs.status, [RunStatuses.completed, RunStatuses.partial]),
9032
9117
  // Probe runs are operator/agent test runs; they must not poison the
9033
9118
  // recent-runs window the content engine uses to recommend actions.
9034
9119
  notProbeRun()
@@ -9038,22 +9123,22 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
9038
9123
  return filtered.slice(0, limit).map((r) => r.id);
9039
9124
  }
9040
9125
  function lookupRunTimestamp(db, runId) {
9041
- const row = db.select({ createdAt: runs.createdAt }).from(runs).where(eq12(runs.id, runId)).get();
9126
+ const row = db.select({ createdAt: runs.createdAt }).from(runs).where(eq13(runs.id, runId)).get();
9042
9127
  return row?.createdAt ?? "";
9043
9128
  }
9044
9129
  function listGscPagesForProject(db, projectId) {
9045
- const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(eq12(gscSearchData.projectId, projectId)).all();
9130
+ const rows = db.selectDistinct({ page: gscSearchData.page }).from(gscSearchData).where(eq13(gscSearchData.projectId, projectId)).all();
9046
9131
  return rows.map((r) => r.page);
9047
9132
  }
9048
9133
  function listGa4LandingPagesForProject(db, projectId) {
9049
- const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(eq12(gaTrafficSnapshots.projectId, projectId)).all();
9134
+ const rows = db.selectDistinct({ landingPage: gaTrafficSnapshots.landingPage }).from(gaTrafficSnapshots).where(eq13(gaTrafficSnapshots.projectId, projectId)).all();
9050
9135
  return rows.map((r) => r.landingPage);
9051
9136
  }
9052
9137
  function buildGaTrafficByPage(db, projectId) {
9053
9138
  const rows = db.select({
9054
9139
  landingPage: gaTrafficSnapshots.landingPage,
9055
9140
  sessions: gaTrafficSnapshots.sessions
9056
- }).from(gaTrafficSnapshots).where(eq12(gaTrafficSnapshots.projectId, projectId)).all();
9141
+ }).from(gaTrafficSnapshots).where(eq13(gaTrafficSnapshots.projectId, projectId)).all();
9057
9142
  const map = /* @__PURE__ */ new Map();
9058
9143
  for (const row of rows) {
9059
9144
  const path8 = extractPath2(row.landingPage);
@@ -9063,24 +9148,24 @@ function buildGaTrafficByPage(db, projectId) {
9063
9148
  return map;
9064
9149
  }
9065
9150
  function sumAiReferralSessions(db, projectId) {
9066
- const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(eq12(gaAiReferrals.projectId, projectId)).all();
9151
+ const rows = db.select({ sessions: gaAiReferrals.sessions }).from(gaAiReferrals).where(eq13(gaAiReferrals.projectId, projectId)).all();
9067
9152
  return rows.reduce((acc, r) => acc + (r.sessions ?? 0), 0);
9068
9153
  }
9069
9154
  function buildCandidateQueries(opts) {
9070
9155
  if (opts.candidateQueryStrings.length === 0 || opts.recentRunIds.length === 0) {
9071
9156
  return opts.candidateQueryStrings.map((query) => emptyCandidate(query));
9072
9157
  }
9073
- const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq12(queries.projectId, opts.projectId)).all();
9158
+ const queryRows = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq13(queries.projectId, opts.projectId)).all();
9074
9159
  const queryIdByText = new Map(queryRows.map((r) => [r.text, r.id]));
9075
9160
  const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
9076
- const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray5(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
9161
+ const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray6(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
9077
9162
  const snapshotsByQuery = /* @__PURE__ */ new Map();
9078
9163
  for (const row of snapshotRows) {
9079
9164
  const list = snapshotsByQuery.get(row.queryId) ?? [];
9080
9165
  list.push(row);
9081
9166
  snapshotsByQuery.set(row.queryId, list);
9082
9167
  }
9083
- const gscRows = opts.db.select().from(gscSearchData).where(eq12(gscSearchData.projectId, opts.projectId)).all();
9168
+ const gscRows = opts.db.select().from(gscSearchData).where(eq13(gscSearchData.projectId, opts.projectId)).all();
9084
9169
  const gscByQuery = aggregateGscByQuery(gscRows);
9085
9170
  return opts.candidateQueryStrings.map((query) => {
9086
9171
  const queryId = queryIdByText.get(query);
@@ -9269,7 +9354,7 @@ function extractPath2(url) {
9269
9354
 
9270
9355
  // ../api-routes/src/content.ts
9271
9356
  function loadDismissedTargetRefs(db, projectId) {
9272
- const rows = db.select({ targetRef: contentTargetDismissals.targetRef }).from(contentTargetDismissals).where(eq13(contentTargetDismissals.projectId, projectId)).all();
9357
+ const rows = db.select({ targetRef: contentTargetDismissals.targetRef }).from(contentTargetDismissals).where(eq14(contentTargetDismissals.projectId, projectId)).all();
9273
9358
  return new Set(rows.map((r) => r.targetRef));
9274
9359
  }
9275
9360
  function formatDismissalRow(row) {
@@ -9364,7 +9449,7 @@ async function contentRoutes(app, opts = {}) {
9364
9449
  });
9365
9450
  app.get("/projects/:name/content/dismissals", async (request) => {
9366
9451
  const project = resolveProject(app.db, request.params.name);
9367
- const rows = app.db.select().from(contentTargetDismissals).where(eq13(contentTargetDismissals.projectId, project.id)).orderBy(contentTargetDismissals.dismissedAt).all();
9452
+ const rows = app.db.select().from(contentTargetDismissals).where(eq14(contentTargetDismissals.projectId, project.id)).orderBy(contentTargetDismissals.dismissedAt).all();
9368
9453
  const response = {
9369
9454
  dismissals: rows.map(formatDismissalRow)
9370
9455
  };
@@ -9379,7 +9464,7 @@ async function contentRoutes(app, opts = {}) {
9379
9464
  const { targetRef, addressedUrl, note } = parsed.data;
9380
9465
  const now = (/* @__PURE__ */ new Date()).toISOString();
9381
9466
  app.db.insert(contentTargetDismissals).values({
9382
- id: crypto11.randomUUID(),
9467
+ id: crypto12.randomUUID(),
9383
9468
  projectId: project.id,
9384
9469
  targetRef,
9385
9470
  addressedUrl: addressedUrl ?? null,
@@ -9394,8 +9479,8 @@ async function contentRoutes(app, opts = {}) {
9394
9479
  }
9395
9480
  }).run();
9396
9481
  const row = app.db.select().from(contentTargetDismissals).where(and10(
9397
- eq13(contentTargetDismissals.projectId, project.id),
9398
- eq13(contentTargetDismissals.targetRef, targetRef)
9482
+ eq14(contentTargetDismissals.projectId, project.id),
9483
+ eq14(contentTargetDismissals.targetRef, targetRef)
9399
9484
  )).get();
9400
9485
  if (!row) throw notFound("contentTargetDismissal", targetRef);
9401
9486
  return formatDismissalRow(row);
@@ -9404,8 +9489,8 @@ async function contentRoutes(app, opts = {}) {
9404
9489
  const project = resolveProject(app.db, request.params.name);
9405
9490
  const { targetRef } = request.params;
9406
9491
  const result = app.db.delete(contentTargetDismissals).where(and10(
9407
- eq13(contentTargetDismissals.projectId, project.id),
9408
- eq13(contentTargetDismissals.targetRef, targetRef)
9492
+ eq14(contentTargetDismissals.projectId, project.id),
9493
+ eq14(contentTargetDismissals.targetRef, targetRef)
9409
9494
  )).run();
9410
9495
  if (result.changes === 0) {
9411
9496
  throw notFound("contentTargetDismissal", targetRef);
@@ -9415,8 +9500,8 @@ async function contentRoutes(app, opts = {}) {
9415
9500
  app.get("/projects/:name/content/recommendations/:targetRef/analysis", async (request, reply) => {
9416
9501
  const project = resolveProject(app.db, request.params.name);
9417
9502
  const row = app.db.select().from(recommendationExplanations).where(and10(
9418
- eq13(recommendationExplanations.projectId, project.id),
9419
- eq13(recommendationExplanations.targetRef, request.params.targetRef)
9503
+ eq14(recommendationExplanations.projectId, project.id),
9504
+ eq14(recommendationExplanations.targetRef, request.params.targetRef)
9420
9505
  )).orderBy(desc6(recommendationExplanations.generatedAt)).limit(1).get();
9421
9506
  if (!row) {
9422
9507
  return reply.status(404).send({ error: { code: "NOT_FOUND", message: "No cached explanation for this targetRef." } });
@@ -9443,8 +9528,8 @@ async function contentRoutes(app, opts = {}) {
9443
9528
  }
9444
9529
  if (!body.forceRefresh) {
9445
9530
  const cached = app.db.select().from(recommendationExplanations).where(and10(
9446
- eq13(recommendationExplanations.projectId, project.id),
9447
- eq13(recommendationExplanations.targetRef, targetRef)
9531
+ eq14(recommendationExplanations.projectId, project.id),
9532
+ eq14(recommendationExplanations.targetRef, targetRef)
9448
9533
  )).orderBy(desc6(recommendationExplanations.generatedAt)).limit(1).get();
9449
9534
  if (cached) return reply.send(formatExplanationRow(cached));
9450
9535
  }
@@ -9458,7 +9543,7 @@ async function contentRoutes(app, opts = {}) {
9458
9543
  });
9459
9544
  const now = (/* @__PURE__ */ new Date()).toISOString();
9460
9545
  app.db.insert(recommendationExplanations).values({
9461
- id: crypto11.randomUUID(),
9546
+ id: crypto12.randomUUID(),
9462
9547
  projectId: project.id,
9463
9548
  targetRef,
9464
9549
  promptVersion: result.promptVersion,
@@ -9482,9 +9567,9 @@ async function contentRoutes(app, opts = {}) {
9482
9567
  }
9483
9568
  }).run();
9484
9569
  const row = app.db.select().from(recommendationExplanations).where(and10(
9485
- eq13(recommendationExplanations.projectId, project.id),
9486
- eq13(recommendationExplanations.targetRef, targetRef),
9487
- eq13(recommendationExplanations.promptVersion, result.promptVersion)
9570
+ eq14(recommendationExplanations.projectId, project.id),
9571
+ eq14(recommendationExplanations.targetRef, targetRef),
9572
+ eq14(recommendationExplanations.promptVersion, result.promptVersion)
9488
9573
  )).get();
9489
9574
  if (!row) throw notFound("recommendationExplanation", targetRef);
9490
9575
  return reply.send(formatExplanationRow(row));
@@ -9542,7 +9627,7 @@ async function contentRoutes(app, opts = {}) {
9542
9627
  });
9543
9628
  const now = (/* @__PURE__ */ new Date()).toISOString();
9544
9629
  app.db.insert(recommendationBriefs).values({
9545
- id: crypto11.randomUUID(),
9630
+ id: crypto12.randomUUID(),
9546
9631
  projectId: project.id,
9547
9632
  targetRef,
9548
9633
  promptVersion: result.promptVersion,
@@ -9566,16 +9651,16 @@ async function contentRoutes(app, opts = {}) {
9566
9651
  }
9567
9652
  }).run();
9568
9653
  const row = app.db.select().from(recommendationBriefs).where(and10(
9569
- eq13(recommendationBriefs.projectId, project.id),
9570
- eq13(recommendationBriefs.targetRef, targetRef),
9571
- eq13(recommendationBriefs.promptVersion, result.promptVersion)
9654
+ eq14(recommendationBriefs.projectId, project.id),
9655
+ eq14(recommendationBriefs.targetRef, targetRef),
9656
+ eq14(recommendationBriefs.promptVersion, result.promptVersion)
9572
9657
  )).get();
9573
9658
  if (!row) throw notFound("recommendationBrief", targetRef);
9574
9659
  return reply.send(formatBriefRow(row));
9575
9660
  });
9576
9661
  app.get("/projects/:name/content/domain-classifications", async (request) => {
9577
9662
  const project = resolveProject(app.db, request.params.name);
9578
- const rows = app.db.select().from(domainClassifications).where(eq13(domainClassifications.projectId, project.id)).orderBy(desc6(domainClassifications.hits)).all();
9663
+ const rows = app.db.select().from(domainClassifications).where(eq14(domainClassifications.projectId, project.id)).orderBy(desc6(domainClassifications.hits)).all();
9579
9664
  const response = {
9580
9665
  classifications: rows.map((r) => ({
9581
9666
  domain: r.domain,
@@ -9589,11 +9674,11 @@ async function contentRoutes(app, opts = {}) {
9589
9674
  }
9590
9675
  function lookupCachedBrief(db, projectId, targetRef, promptVersion) {
9591
9676
  const conditions = [
9592
- eq13(recommendationBriefs.projectId, projectId),
9593
- eq13(recommendationBriefs.targetRef, targetRef)
9677
+ eq14(recommendationBriefs.projectId, projectId),
9678
+ eq14(recommendationBriefs.targetRef, targetRef)
9594
9679
  ];
9595
9680
  if (promptVersion !== void 0) {
9596
- conditions.push(eq13(recommendationBriefs.promptVersion, promptVersion));
9681
+ conditions.push(eq14(recommendationBriefs.promptVersion, promptVersion));
9597
9682
  }
9598
9683
  return db.select().from(recommendationBriefs).where(and10(...conditions)).orderBy(desc6(recommendationBriefs.generatedAt)).limit(1).get();
9599
9684
  }
@@ -9621,7 +9706,7 @@ function winnabilityClassRank(winnabilityClass) {
9621
9706
  }
9622
9707
 
9623
9708
  // ../api-routes/src/gsc-totals.ts
9624
- import { and as and11, asc as asc2, eq as eq14, sql as sql6 } from "drizzle-orm";
9709
+ import { and as and11, asc as asc2, eq as eq15, sql as sql6 } from "drizzle-orm";
9625
9710
  function readGscDailyTotals(db, projectId, startDate, endDate) {
9626
9711
  const rows = db.select({
9627
9712
  date: gscDailyTotals.date,
@@ -9630,7 +9715,7 @@ function readGscDailyTotals(db, projectId, startDate, endDate) {
9630
9715
  position: gscDailyTotals.position
9631
9716
  }).from(gscDailyTotals).where(
9632
9717
  and11(
9633
- eq14(gscDailyTotals.projectId, projectId),
9718
+ eq15(gscDailyTotals.projectId, projectId),
9634
9719
  sql6`${gscDailyTotals.date} >= ${startDate}`,
9635
9720
  sql6`${gscDailyTotals.date} <= ${endDate}`
9636
9721
  )
@@ -11950,7 +12035,7 @@ function loadSnapshotsForRun(db, runId) {
11950
12035
  }
11951
12036
  function loadSnapshotsForRunIds(db, runIds) {
11952
12037
  if (runIds.length === 0) return [];
11953
- const rows = db.select().from(querySnapshots).where(inArray6(querySnapshots.runId, [...runIds])).all();
12038
+ const rows = db.select().from(querySnapshots).where(inArray7(querySnapshots.runId, [...runIds])).all();
11954
12039
  return rows.filter((r) => r.queryId !== null).map((r) => ({
11955
12040
  id: r.id,
11956
12041
  runId: r.runId,
@@ -11967,13 +12052,13 @@ function loadSnapshotsForRunIds(db, runIds) {
11967
12052
  }));
11968
12053
  }
11969
12054
  function loadQueryLookup(db, projectId) {
11970
- const rows = db.select().from(queries).where(eq15(queries.projectId, projectId)).all();
12055
+ const rows = db.select().from(queries).where(eq16(queries.projectId, projectId)).all();
11971
12056
  const byId = /* @__PURE__ */ new Map();
11972
12057
  for (const row of rows) byId.set(row.id, row.query);
11973
12058
  return { byId };
11974
12059
  }
11975
12060
  function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trackedQueries, windowDays) {
11976
- const allRows = db.select().from(gscSearchData).where(eq15(gscSearchData.projectId, projectId)).all();
12061
+ const allRows = db.select().from(gscSearchData).where(eq16(gscSearchData.projectId, projectId)).all();
11977
12062
  const allDailyTotals = readGscDailyTotals(db, projectId, "", "9999-12-31");
11978
12063
  let maxDate = "";
11979
12064
  for (const r of allRows) if (r.date > maxDate) maxDate = r.date;
@@ -12065,12 +12150,12 @@ function buildGaSection(db, projectId, windowDays) {
12065
12150
  const gaWindowKey = GA_WINDOW_SUMMARY_KEYS[windowDays];
12066
12151
  const windowSummary = gaWindowKey ? db.select().from(gaTrafficWindowSummaries).where(
12067
12152
  and12(
12068
- eq15(gaTrafficWindowSummaries.projectId, projectId),
12069
- eq15(gaTrafficWindowSummaries.windowKey, gaWindowKey)
12153
+ eq16(gaTrafficWindowSummaries.projectId, projectId),
12154
+ eq16(gaTrafficWindowSummaries.windowKey, gaWindowKey)
12070
12155
  )
12071
12156
  ).limit(1).get() : void 0;
12072
- const fallbackSummary = windowSummary ? null : db.select().from(gaTrafficSummaries).where(eq15(gaTrafficSummaries.projectId, projectId)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
12073
- const allSnapshotRows = db.select().from(gaTrafficSnapshots).where(eq15(gaTrafficSnapshots.projectId, projectId)).all();
12157
+ const fallbackSummary = windowSummary ? null : db.select().from(gaTrafficSummaries).where(eq16(gaTrafficSummaries.projectId, projectId)).orderBy(desc7(gaTrafficSummaries.syncedAt)).limit(1).get();
12158
+ const allSnapshotRows = db.select().from(gaTrafficSnapshots).where(eq16(gaTrafficSnapshots.projectId, projectId)).all();
12074
12159
  if (!windowSummary && !fallbackSummary && allSnapshotRows.length === 0) return null;
12075
12160
  let snapshotMaxDate = "";
12076
12161
  for (const r of allSnapshotRows) if (r.date > snapshotMaxDate) snapshotMaxDate = r.date;
@@ -12097,8 +12182,8 @@ function buildGaSection(db, projectId, windowDays) {
12097
12182
  organicSessions: data.organic
12098
12183
  })).sort((a, b) => b.sessions - a.sessions).slice(0, TOP_LANDING_PAGES_LIMIT);
12099
12184
  const aiConditions = [
12100
- eq15(gaAiReferrals.projectId, projectId),
12101
- eq15(gaAiReferrals.sourceDimension, "session")
12185
+ eq16(gaAiReferrals.projectId, projectId),
12186
+ eq16(gaAiReferrals.sourceDimension, "session")
12102
12187
  ];
12103
12188
  if (snapshotStartDate && snapshotMaxDate) {
12104
12189
  aiConditions.push(gte2(gaAiReferrals.date, snapshotStartDate));
@@ -12155,7 +12240,7 @@ function buildGaSection(db, projectId, windowDays) {
12155
12240
  };
12156
12241
  }
12157
12242
  function buildSocialReferrals(db, projectId) {
12158
- const rows = db.select().from(gaSocialReferrals).where(eq15(gaSocialReferrals.projectId, projectId)).all();
12243
+ const rows = db.select().from(gaSocialReferrals).where(eq16(gaSocialReferrals.projectId, projectId)).all();
12159
12244
  if (rows.length === 0) return null;
12160
12245
  let total = 0;
12161
12246
  let organic = 0;
@@ -12187,7 +12272,7 @@ function buildSocialReferrals(db, projectId) {
12187
12272
  };
12188
12273
  }
12189
12274
  function buildAiReferrals(db, projectId) {
12190
- const rows = db.select().from(gaAiReferrals).where(eq15(gaAiReferrals.projectId, projectId)).all();
12275
+ const rows = db.select().from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, projectId)).all();
12191
12276
  if (rows.length === 0) return null;
12192
12277
  const dimSessionsByTuple = /* @__PURE__ */ new Map();
12193
12278
  for (const r of rows) {
@@ -12300,7 +12385,7 @@ function nonSubresourceReferralPathCondition() {
12300
12385
  function buildServerActivity(db, projectId, windowDays) {
12301
12386
  const sourceRows = db.select({ id: trafficSources.id }).from(trafficSources).where(
12302
12387
  and12(
12303
- eq15(trafficSources.projectId, projectId),
12388
+ eq16(trafficSources.projectId, projectId),
12304
12389
  ne2(trafficSources.status, TrafficSourceStatuses.archived)
12305
12390
  )
12306
12391
  ).all();
@@ -12316,8 +12401,8 @@ function buildServerActivity(db, projectId, windowDays) {
12316
12401
  const sumVerifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
12317
12402
  db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
12318
12403
  and12(
12319
- eq15(crawlerEventsHourly.projectId, projectId),
12320
- eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12404
+ eq16(crawlerEventsHourly.projectId, projectId),
12405
+ eq16(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12321
12406
  gte2(crawlerEventsHourly.tsHour, windowStartIso),
12322
12407
  exclusiveEnd ? lt(crawlerEventsHourly.tsHour, windowEndIso) : lte(crawlerEventsHourly.tsHour, windowEndIso)
12323
12408
  )
@@ -12326,7 +12411,7 @@ function buildServerActivity(db, projectId, windowDays) {
12326
12411
  const sumUnverifiedCrawlers = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
12327
12412
  db.select({ total: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
12328
12413
  and12(
12329
- eq15(crawlerEventsHourly.projectId, projectId),
12414
+ eq16(crawlerEventsHourly.projectId, projectId),
12330
12415
  ne2(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12331
12416
  gte2(crawlerEventsHourly.tsHour, windowStartIso),
12332
12417
  exclusiveEnd ? lt(crawlerEventsHourly.tsHour, windowEndIso) : lte(crawlerEventsHourly.tsHour, windowEndIso)
@@ -12340,7 +12425,7 @@ function buildServerActivity(db, projectId, windowDays) {
12340
12425
  organic: sql7`COALESCE(SUM(${aiReferralEventsHourly.organicSessionsOrHits}), 0)`
12341
12426
  }).from(aiReferralEventsHourly).where(
12342
12427
  and12(
12343
- eq15(aiReferralEventsHourly.projectId, projectId),
12428
+ eq16(aiReferralEventsHourly.projectId, projectId),
12344
12429
  nonSubresourceReferralPathCondition(),
12345
12430
  gte2(aiReferralEventsHourly.tsHour, windowStartIso),
12346
12431
  exclusiveEnd ? lt(aiReferralEventsHourly.tsHour, windowEndIso) : lte(aiReferralEventsHourly.tsHour, windowEndIso)
@@ -12351,7 +12436,7 @@ function buildServerActivity(db, projectId, windowDays) {
12351
12436
  const sumUserFetches = (windowStartIso, windowEndIso, exclusiveEnd = false) => Number(
12352
12437
  db.select({ total: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
12353
12438
  and12(
12354
- eq15(aiUserFetchEventsHourly.projectId, projectId),
12439
+ eq16(aiUserFetchEventsHourly.projectId, projectId),
12355
12440
  gte2(aiUserFetchEventsHourly.tsHour, windowStartIso),
12356
12441
  exclusiveEnd ? lt(aiUserFetchEventsHourly.tsHour, windowEndIso) : lte(aiUserFetchEventsHourly.tsHour, windowEndIso)
12357
12442
  )
@@ -12371,7 +12456,7 @@ function buildServerActivity(db, projectId, windowDays) {
12371
12456
  hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
12372
12457
  }).from(crawlerEventsHourly).where(
12373
12458
  and12(
12374
- eq15(crawlerEventsHourly.projectId, projectId),
12459
+ eq16(crawlerEventsHourly.projectId, projectId),
12375
12460
  gte2(crawlerEventsHourly.tsHour, headlineStart),
12376
12461
  lte(crawlerEventsHourly.tsHour, headlineEnd)
12377
12462
  )
@@ -12381,8 +12466,8 @@ function buildServerActivity(db, projectId, windowDays) {
12381
12466
  hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
12382
12467
  }).from(crawlerEventsHourly).where(
12383
12468
  and12(
12384
- eq15(crawlerEventsHourly.projectId, projectId),
12385
- eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12469
+ eq16(crawlerEventsHourly.projectId, projectId),
12470
+ eq16(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12386
12471
  gte2(crawlerEventsHourly.tsHour, priorStart),
12387
12472
  lt(crawlerEventsHourly.tsHour, headlineStart)
12388
12473
  )
@@ -12392,7 +12477,7 @@ function buildServerActivity(db, projectId, windowDays) {
12392
12477
  hits: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
12393
12478
  }).from(aiReferralEventsHourly).where(
12394
12479
  and12(
12395
- eq15(aiReferralEventsHourly.projectId, projectId),
12480
+ eq16(aiReferralEventsHourly.projectId, projectId),
12396
12481
  nonSubresourceReferralPathCondition(),
12397
12482
  gte2(aiReferralEventsHourly.tsHour, headlineStart),
12398
12483
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
@@ -12403,7 +12488,7 @@ function buildServerActivity(db, projectId, windowDays) {
12403
12488
  hits: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
12404
12489
  }).from(aiUserFetchEventsHourly).where(
12405
12490
  and12(
12406
- eq15(aiUserFetchEventsHourly.projectId, projectId),
12491
+ eq16(aiUserFetchEventsHourly.projectId, projectId),
12407
12492
  gte2(aiUserFetchEventsHourly.tsHour, headlineStart),
12408
12493
  lte(aiUserFetchEventsHourly.tsHour, headlineEnd)
12409
12494
  )
@@ -12447,8 +12532,8 @@ function buildServerActivity(db, projectId, windowDays) {
12447
12532
  operators: sql7`COUNT(DISTINCT ${crawlerEventsHourly.operator})`
12448
12533
  }).from(crawlerEventsHourly).where(
12449
12534
  and12(
12450
- eq15(crawlerEventsHourly.projectId, projectId),
12451
- eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12535
+ eq16(crawlerEventsHourly.projectId, projectId),
12536
+ eq16(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12452
12537
  gte2(crawlerEventsHourly.tsHour, headlineStart),
12453
12538
  lte(crawlerEventsHourly.tsHour, headlineEnd)
12454
12539
  )
@@ -12464,7 +12549,7 @@ function buildServerActivity(db, projectId, windowDays) {
12464
12549
  landingPaths: sql7`COUNT(DISTINCT ${aiReferralEventsHourly.landingPathNormalized})`
12465
12550
  }).from(aiReferralEventsHourly).where(
12466
12551
  and12(
12467
- eq15(aiReferralEventsHourly.projectId, projectId),
12552
+ eq16(aiReferralEventsHourly.projectId, projectId),
12468
12553
  nonSubresourceReferralPathCondition(),
12469
12554
  gte2(aiReferralEventsHourly.tsHour, headlineStart),
12470
12555
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
@@ -12481,7 +12566,7 @@ function buildServerActivity(db, projectId, windowDays) {
12481
12566
  products: sql7`COUNT(DISTINCT ${aiReferralEventsHourly.product})`
12482
12567
  }).from(aiReferralEventsHourly).where(
12483
12568
  and12(
12484
- eq15(aiReferralEventsHourly.projectId, projectId),
12569
+ eq16(aiReferralEventsHourly.projectId, projectId),
12485
12570
  nonSubresourceReferralPathCondition(),
12486
12571
  gte2(aiReferralEventsHourly.tsHour, headlineStart),
12487
12572
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
@@ -12497,8 +12582,8 @@ function buildServerActivity(db, projectId, windowDays) {
12497
12582
  hits: sql7`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
12498
12583
  }).from(crawlerEventsHourly).where(
12499
12584
  and12(
12500
- eq15(crawlerEventsHourly.projectId, projectId),
12501
- eq15(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12585
+ eq16(crawlerEventsHourly.projectId, projectId),
12586
+ eq16(crawlerEventsHourly.verificationStatus, VerificationStatuses.verified),
12502
12587
  gte2(crawlerEventsHourly.tsHour, trendStart),
12503
12588
  lte(crawlerEventsHourly.tsHour, headlineEnd)
12504
12589
  )
@@ -12508,7 +12593,7 @@ function buildServerActivity(db, projectId, windowDays) {
12508
12593
  hits: sql7`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
12509
12594
  }).from(aiReferralEventsHourly).where(
12510
12595
  and12(
12511
- eq15(aiReferralEventsHourly.projectId, projectId),
12596
+ eq16(aiReferralEventsHourly.projectId, projectId),
12512
12597
  nonSubresourceReferralPathCondition(),
12513
12598
  gte2(aiReferralEventsHourly.tsHour, trendStart),
12514
12599
  lte(aiReferralEventsHourly.tsHour, headlineEnd)
@@ -12519,7 +12604,7 @@ function buildServerActivity(db, projectId, windowDays) {
12519
12604
  hits: sql7`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
12520
12605
  }).from(aiUserFetchEventsHourly).where(
12521
12606
  and12(
12522
- eq15(aiUserFetchEventsHourly.projectId, projectId),
12607
+ eq16(aiUserFetchEventsHourly.projectId, projectId),
12523
12608
  gte2(aiUserFetchEventsHourly.tsHour, trendStart),
12524
12609
  lte(aiUserFetchEventsHourly.tsHour, headlineEnd)
12525
12610
  )
@@ -12592,7 +12677,7 @@ function buildServerActivity(db, projectId, windowDays) {
12592
12677
  };
12593
12678
  }
12594
12679
  function buildIndexingHealth(db, projectId) {
12595
- const gsc = db.select().from(gscCoverageSnapshots).where(eq15(gscCoverageSnapshots.projectId, projectId)).orderBy(desc7(gscCoverageSnapshots.date)).limit(1).get();
12680
+ const gsc = db.select().from(gscCoverageSnapshots).where(eq16(gscCoverageSnapshots.projectId, projectId)).orderBy(desc7(gscCoverageSnapshots.date)).limit(1).get();
12596
12681
  if (gsc) {
12597
12682
  const total = gsc.indexed + gsc.notIndexed;
12598
12683
  return {
@@ -12605,7 +12690,7 @@ function buildIndexingHealth(db, projectId) {
12605
12690
  indexedPct: total > 0 ? Math.round(gsc.indexed / total * 100) : 0
12606
12691
  };
12607
12692
  }
12608
- const bing = db.select().from(bingCoverageSnapshots).where(eq15(bingCoverageSnapshots.projectId, projectId)).orderBy(desc7(bingCoverageSnapshots.date)).limit(1).get();
12693
+ const bing = db.select().from(bingCoverageSnapshots).where(eq16(bingCoverageSnapshots.projectId, projectId)).orderBy(desc7(bingCoverageSnapshots.date)).limit(1).get();
12609
12694
  if (bing) {
12610
12695
  const total = bing.indexed + bing.notIndexed + bing.unknown;
12611
12696
  return {
@@ -12621,7 +12706,7 @@ function buildIndexingHealth(db, projectId) {
12621
12706
  return null;
12622
12707
  }
12623
12708
  function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
12624
- const visibilityRuns = db.select().from(runs).where(and12(eq15(runs.projectId, projectId), eq15(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
12709
+ const visibilityRuns = db.select().from(runs).where(and12(eq16(runs.projectId, projectId), eq16(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter);
12625
12710
  const totalQueries = queryLookup.byId.size;
12626
12711
  const points = [];
12627
12712
  for (const run of visibilityRuns) {
@@ -12670,14 +12755,14 @@ function buildCitationsTrend(db, projectId, queryLookup, locationFilter) {
12670
12755
  function buildInsightList(db, projectId, locationFilter) {
12671
12756
  const recentRunIds = db.select({ id: runs.id, location: runs.location }).from(runs).where(
12672
12757
  and12(
12673
- eq15(runs.projectId, projectId),
12674
- eq15(runs.kind, RunKinds["answer-visibility"]),
12675
- or3(eq15(runs.status, RunStatuses.completed), eq15(runs.status, RunStatuses.partial)),
12758
+ eq16(runs.projectId, projectId),
12759
+ eq16(runs.kind, RunKinds["answer-visibility"]),
12760
+ or3(eq16(runs.status, RunStatuses.completed), eq16(runs.status, RunStatuses.partial)),
12676
12761
  notProbeRun()
12677
12762
  )
12678
12763
  ).orderBy(desc7(runs.createdAt)).all().filter((r) => locationFilter === void 0 || (r.location ?? null) === locationFilter).slice(0, INSIGHT_LOOKBACK_RUNS).map((r) => r.id);
12679
12764
  if (recentRunIds.length === 0) return [];
12680
- const rows = db.select().from(insights).where(and12(eq15(insights.projectId, projectId), inArray6(insights.runId, recentRunIds))).orderBy(desc7(insights.createdAt)).all();
12765
+ const rows = db.select().from(insights).where(and12(eq16(insights.projectId, projectId), inArray7(insights.runId, recentRunIds))).orderBy(desc7(insights.createdAt)).all();
12681
12766
  const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
12682
12767
  const flat = rows.filter((r) => !r.dismissed).map((r) => {
12683
12768
  const recommendation = r.recommendation;
@@ -13229,7 +13314,7 @@ function buildProjectReport(db, projectName, periodDays) {
13229
13314
  const project = resolveProject(db, projectName);
13230
13315
  const queryLookup = loadQueryLookup(db, project.id);
13231
13316
  const comparisonWindowDays = reportComparisonWindowDays(periodDays);
13232
- const allRuns = db.select().from(runs).where(and12(eq15(runs.projectId, project.id), notProbeRun())).orderBy(desc7(runs.createdAt), desc7(runs.id)).all();
13317
+ const allRuns = db.select().from(runs).where(and12(eq16(runs.projectId, project.id), notProbeRun())).orderBy(desc7(runs.createdAt), desc7(runs.id)).all();
13233
13318
  const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
13234
13319
  const completedVisRunGroups = groupRunsByCreatedAt(
13235
13320
  visibilityRuns.filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial)
@@ -13238,7 +13323,7 @@ function buildProjectReport(db, projectName, periodDays) {
13238
13323
  const representativeLatestRun = pickGroupRepresentative(latestVisRunGroup) ?? visibilityRuns[0] ?? null;
13239
13324
  const latestSnapshots = loadSnapshotsForRunIds(db, latestVisRunGroup.map((r) => r.id));
13240
13325
  const latestRunLocation = representativeLatestRun?.location ?? null;
13241
- const competitorRows = db.select().from(competitors).where(eq15(competitors.projectId, project.id)).all();
13326
+ const competitorRows = db.select().from(competitors).where(eq16(competitors.projectId, project.id)).all();
13242
13327
  const competitorDomains = competitorRows.map((c) => c.domain);
13243
13328
  const ownedDomains = project.ownedDomains;
13244
13329
  const projectDomains = [project.canonicalDomain, ...ownedDomains];
@@ -13465,16 +13550,16 @@ async function reportRoutes(app) {
13465
13550
  }
13466
13551
 
13467
13552
  // ../api-routes/src/citations.ts
13468
- import { and as and13, eq as eq16, inArray as inArray7 } from "drizzle-orm";
13553
+ import { and as and13, eq as eq17, inArray as inArray8 } from "drizzle-orm";
13469
13554
  async function citationRoutes(app) {
13470
13555
  app.get("/projects/:name/citations/visibility", async (request, reply) => {
13471
13556
  const project = resolveProject(app.db, request.params.name);
13472
13557
  const configuredProviders = project.providers;
13473
- const projectQueries = app.db.select().from(queries).where(eq16(queries.projectId, project.id)).all();
13558
+ const projectQueries = app.db.select().from(queries).where(eq17(queries.projectId, project.id)).all();
13474
13559
  if (projectQueries.length === 0) {
13475
13560
  return reply.send(emptyCitationVisibility("no-queries"));
13476
13561
  }
13477
- const projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and13(eq16(runs.projectId, project.id), notProbeRun())).all();
13562
+ const projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and13(eq17(runs.projectId, project.id), notProbeRun())).all();
13478
13563
  if (projectRuns.length === 0) {
13479
13564
  return reply.send(emptyCitationVisibility("no-runs-yet"));
13480
13565
  }
@@ -13489,7 +13574,7 @@ async function citationRoutes(app) {
13489
13574
  competitorOverlap: querySnapshots.competitorOverlap,
13490
13575
  answerMentioned: querySnapshots.answerMentioned,
13491
13576
  createdAt: querySnapshots.createdAt
13492
- }).from(querySnapshots).where(inArray7(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
13577
+ }).from(querySnapshots).where(inArray8(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
13493
13578
  if (rawSnapshots.length === 0) {
13494
13579
  return reply.send(emptyCitationVisibility("no-runs-yet"));
13495
13580
  }
@@ -13498,7 +13583,7 @@ async function citationRoutes(app) {
13498
13583
  queryId: s.queryId,
13499
13584
  runCreatedAt: runCreatedAt.get(s.runId) ?? s.createdAt
13500
13585
  }));
13501
- const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq16(competitors.projectId, project.id)).all().map((c) => normalizeDomain2(c.domain)).filter((d) => d.length > 0);
13586
+ const projectCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq17(competitors.projectId, project.id)).all().map((c) => normalizeDomain2(c.domain)).filter((d) => d.length > 0);
13502
13587
  const response = computeCitationVisibility({
13503
13588
  queries: projectQueries.map((q) => ({ id: q.id, query: q.query })),
13504
13589
  snapshots,
@@ -13630,7 +13715,254 @@ function normalizeDomain2(domain) {
13630
13715
  }
13631
13716
 
13632
13717
  // ../api-routes/src/visibility-stats.ts
13633
- import { and as and14, desc as desc8, eq as eq17, inArray as inArray8 } from "drizzle-orm";
13718
+ import { and as and14, desc as desc8, eq as eq18, inArray as inArray9 } from "drizzle-orm";
13719
+
13720
+ // ../api-routes/src/visibility-compare.ts
13721
+ var VISIBILITY_COMPARE_MIN_RUNS = 5;
13722
+ function basketPairKey(queryId, provider) {
13723
+ return JSON.stringify([queryId, provider]);
13724
+ }
13725
+ function citedHostMatches(citedHost, competitorHost) {
13726
+ return citedHost === competitorHost || citedHost.endsWith(`.${competitorHost}`);
13727
+ }
13728
+ function restrict(snapshots, attribution, pairs) {
13729
+ const out = [];
13730
+ for (const snap of snapshots) {
13731
+ const resolved = resolveCurrentQuery(attribution, snap);
13732
+ if (!resolved) continue;
13733
+ if (!pairs.has(basketPairKey(resolved.id, snap.provider))) continue;
13734
+ out.push({ ...snap, queryId: resolved.id });
13735
+ }
13736
+ return out;
13737
+ }
13738
+ function observed(snapshots, attribution) {
13739
+ const queryIds = /* @__PURE__ */ new Set();
13740
+ const providers = /* @__PURE__ */ new Set();
13741
+ for (const snap of snapshots) {
13742
+ const resolved = resolveCurrentQuery(attribution, snap);
13743
+ if (!resolved) continue;
13744
+ queryIds.add(resolved.id);
13745
+ providers.add(snap.provider);
13746
+ }
13747
+ return { queryIds, providers };
13748
+ }
13749
+ function observedPairs(snapshots, attribution, queryIds) {
13750
+ const pairs = /* @__PURE__ */ new Map();
13751
+ for (const snap of snapshots) {
13752
+ const resolved = resolveCurrentQuery(attribution, snap);
13753
+ if (!resolved || !queryIds.has(resolved.id)) continue;
13754
+ const pair = { queryId: resolved.id, provider: snap.provider };
13755
+ pairs.set(basketPairKey(pair.queryId, pair.provider), pair);
13756
+ }
13757
+ return pairs;
13758
+ }
13759
+ function period(numerator, denominator) {
13760
+ const ci = wilsonInterval(numerator, denominator);
13761
+ return {
13762
+ point: denominator > 0 ? Math.round(numerator / denominator * 1e4) / 1e4 : null,
13763
+ ciLow: ci ? ci.low : null,
13764
+ ciHigh: ci ? ci.high : null,
13765
+ numerator,
13766
+ denominator
13767
+ };
13768
+ }
13769
+ function ciOverlap(a, b) {
13770
+ if (a.ciLow === null || a.ciHigh === null || b.ciLow === null || b.ciHigh === null) return true;
13771
+ return a.ciLow <= b.ciHigh && b.ciLow <= a.ciHigh;
13772
+ }
13773
+ function metric(key, label, driftRobust, from, to, continuityBlock) {
13774
+ const verdict = continuityBlock ?? (from.denominator === 0 || to.denominator === 0 ? "insufficient-data" : ciOverlap(from, to) ? "within-noise" : "moved");
13775
+ const direction = from.point === null || to.point === null ? null : to.point > from.point ? "up" : to.point < from.point ? "down" : "flat";
13776
+ const rateRatio = from.point === null || from.point === 0 || to.point === null ? null : Math.round(to.point / from.point * 100) / 100;
13777
+ return { key, label, driftRobust, from, to, rateRatio, direction, verdict };
13778
+ }
13779
+ function countPeriod(snaps, competitors2) {
13780
+ let checked = 0;
13781
+ let mentioned = 0;
13782
+ let cited = 0;
13783
+ let competitorCited = 0;
13784
+ const perProvider = /* @__PURE__ */ new Map();
13785
+ const models = /* @__PURE__ */ new Map();
13786
+ const modelUnknownProviders = /* @__PURE__ */ new Set();
13787
+ const mentionedQueries = /* @__PURE__ */ new Set();
13788
+ const competitorHosts = competitors2.map((c) => hostOf(c.domain)).filter((h) => h !== null && h.length > 0);
13789
+ for (const snap of snaps) {
13790
+ const isMentioned = snap.answerMentioned === true;
13791
+ if (snap.answerMentioned === true || snap.answerMentioned === false) checked += 1;
13792
+ if (isMentioned) {
13793
+ mentioned += 1;
13794
+ mentionedQueries.add(snap.queryId);
13795
+ }
13796
+ const isCited = snap.citationState === CitationStates.cited;
13797
+ if (isCited) cited += 1;
13798
+ const pp = perProvider.get(snap.provider) ?? { checked: 0, mentioned: 0, cited: 0 };
13799
+ if (snap.answerMentioned === true || snap.answerMentioned === false) pp.checked += 1;
13800
+ if (isMentioned) pp.mentioned += 1;
13801
+ if (isCited) pp.cited += 1;
13802
+ perProvider.set(snap.provider, pp);
13803
+ if (snap.model) {
13804
+ const set = models.get(snap.provider) ?? /* @__PURE__ */ new Set();
13805
+ set.add(snap.model);
13806
+ models.set(snap.provider, set);
13807
+ } else {
13808
+ modelUnknownProviders.add(snap.provider);
13809
+ }
13810
+ if (competitorHosts.length > 0 && snap.citedDomains.length > 0) {
13811
+ const citedHosts = snap.citedDomains.map((d) => hostOf(d)).filter((h) => h !== null && h.length > 0);
13812
+ for (const compHost of competitorHosts) {
13813
+ if (citedHosts.some((ch) => citedHostMatches(ch, compHost))) competitorCited += 1;
13814
+ }
13815
+ }
13816
+ }
13817
+ const mentionShare = buildMentionShare(
13818
+ snaps.map((s) => ({ projectMentioned: s.answerMentioned === true, answerText: s.answerText })),
13819
+ { competitors: competitors2 }
13820
+ );
13821
+ return {
13822
+ checked,
13823
+ mentioned,
13824
+ total: snaps.length,
13825
+ cited,
13826
+ projectCited: cited,
13827
+ competitorCited,
13828
+ queriesMentioned: mentionedQueries.size,
13829
+ perProvider,
13830
+ models,
13831
+ modelUnknownProviders,
13832
+ mentionShare,
13833
+ competitors: mentionShare.breakdown.perCompetitor.map((c) => ({ domain: c.domain, mentions: c.mentionSnapshots }))
13834
+ };
13835
+ }
13836
+ function computeVisibilityCompare(input) {
13837
+ const attribution = buildQueryAttribution(input.queries);
13838
+ const fromObs = observed(input.from.snapshots, attribution);
13839
+ const toObs = observed(input.to.snapshots, attribution);
13840
+ const queriesObservedBoth = new Set([...fromObs.queryIds].filter((q) => toObs.queryIds.has(q)));
13841
+ const fromPairs = observedPairs(input.from.snapshots, attribution, queriesObservedBoth);
13842
+ const toPairs = observedPairs(input.to.snapshots, attribution, queriesObservedBoth);
13843
+ const pairsBoth = new Map([...fromPairs].filter(([key]) => toPairs.has(key)));
13844
+ const candidateProviders = new Set([...pairsBoth.values()].map((pair) => pair.provider));
13845
+ const fromCandidateSnaps = restrict(input.from.snapshots, attribution, pairsBoth);
13846
+ const toCandidateSnaps = restrict(input.to.snapshots, attribution, pairsBoth);
13847
+ const fromCandidateCounts = countPeriod(fromCandidateSnaps, input.competitors);
13848
+ const toCandidateCounts = countPeriod(toCandidateSnaps, input.competitors);
13849
+ const continuityProviders = [...candidateProviders].sort((a, b) => a.localeCompare(b)).map((provider) => {
13850
+ const fromModels = [...fromCandidateCounts.models.get(provider) ?? /* @__PURE__ */ new Set()].sort((a, b) => a.localeCompare(b));
13851
+ const toModels = [...toCandidateCounts.models.get(provider) ?? /* @__PURE__ */ new Set()].sort((a, b) => a.localeCompare(b));
13852
+ const fromModelUnknown = fromCandidateCounts.modelUnknownProviders.has(provider) || fromModels.length === 0;
13853
+ const toModelUnknown = toCandidateCounts.modelUnknownProviders.has(provider) || toModels.length === 0;
13854
+ const status = fromModelUnknown || toModelUnknown ? "model-unknown" : fromModels.length === 1 && toModels.length === 1 && fromModels[0] === toModels[0] ? "included" : "model-discontinuous";
13855
+ return { provider, status, fromModels, toModels };
13856
+ });
13857
+ const providersBoth = new Set(
13858
+ continuityProviders.filter((provider) => provider.status === "included").map((provider) => provider.provider)
13859
+ );
13860
+ const continuityStatus = candidateProviders.size === 0 ? "insufficient-data" : providersBoth.size > 0 ? "comparable" : continuityProviders.every((provider) => provider.status === "model-unknown") ? "model-unknown" : "model-discontinuous";
13861
+ const continuityBlock = continuityStatus === "model-discontinuous" || continuityStatus === "model-unknown" ? continuityStatus : null;
13862
+ const comparedPairs = new Map([...pairsBoth].filter(([, pair]) => providersBoth.has(pair.provider)));
13863
+ const queriesBoth = new Set([...comparedPairs.values()].map((pair) => pair.queryId));
13864
+ const excludedProviders = [.../* @__PURE__ */ new Set([...fromObs.providers, ...toObs.providers])].filter((p) => !providersBoth.has(p)).sort((a, b) => a.localeCompare(b));
13865
+ const fromSnaps = restrict(input.from.snapshots, attribution, comparedPairs);
13866
+ const toSnaps = restrict(input.to.snapshots, attribution, comparedPairs);
13867
+ const fromCounts = countPeriod(fromSnaps, input.competitors);
13868
+ const toCounts = countPeriod(toSnaps, input.competitors);
13869
+ const shareCounts = (c) => ({
13870
+ proj: c.mentionShare.breakdown.projectMentionSnapshots,
13871
+ comp: c.mentionShare.breakdown.competitorMentionSnapshots
13872
+ });
13873
+ const fromShare = shareCounts(fromCounts);
13874
+ const toShare = shareCounts(toCounts);
13875
+ const metrics = [
13876
+ metric(
13877
+ "mention-share-of-voice",
13878
+ "Named share of voice",
13879
+ true,
13880
+ period(fromShare.proj, fromShare.proj + fromShare.comp),
13881
+ period(toShare.proj, toShare.proj + toShare.comp),
13882
+ continuityBlock
13883
+ ),
13884
+ // Share of voice is undefined without a competitive frame: with zero
13885
+ // configured competitors the denominator degenerates to the project's own
13886
+ // count and the metric would fabricate a 100%. buildMentionShare already
13887
+ // refuses that on the mention side ("reporting 100% would mislead");
13888
+ // degrade the cited side identically to a 0/0 period -> insufficient-data.
13889
+ metric(
13890
+ "cited-share-of-voice",
13891
+ "Cited share of voice",
13892
+ true,
13893
+ input.competitors.length > 0 ? period(fromCounts.projectCited, fromCounts.projectCited + fromCounts.competitorCited) : period(0, 0),
13894
+ input.competitors.length > 0 ? period(toCounts.projectCited, toCounts.projectCited + toCounts.competitorCited) : period(0, 0),
13895
+ continuityBlock
13896
+ ),
13897
+ metric(
13898
+ "mention-rate",
13899
+ "Named rate",
13900
+ false,
13901
+ period(fromCounts.mentioned, fromCounts.checked),
13902
+ period(toCounts.mentioned, toCounts.checked),
13903
+ continuityBlock
13904
+ ),
13905
+ metric(
13906
+ "cited-rate",
13907
+ "Cited rate",
13908
+ false,
13909
+ period(fromCounts.cited, fromCounts.total),
13910
+ period(toCounts.cited, toCounts.total),
13911
+ continuityBlock
13912
+ )
13913
+ ];
13914
+ const modelChanges = [...candidateProviders].sort((a, b) => a.localeCompare(b)).map((provider) => {
13915
+ const fromModels = [...fromCandidateCounts.models.get(provider) ?? /* @__PURE__ */ new Set()].sort((a, b) => a.localeCompare(b));
13916
+ const toModels = [...toCandidateCounts.models.get(provider) ?? /* @__PURE__ */ new Set()].sort((a, b) => a.localeCompare(b));
13917
+ return { provider, fromModels, toModels };
13918
+ }).filter(
13919
+ (c) => c.fromModels.length > 0 && c.toModels.length > 0 && JSON.stringify(c.fromModels) !== JSON.stringify(c.toModels)
13920
+ );
13921
+ const byProvider = [...providersBoth].sort((a, b) => a.localeCompare(b)).map((provider) => ({
13922
+ provider,
13923
+ from: fromCounts.perProvider.get(provider) ?? { checked: 0, mentioned: 0, cited: 0 },
13924
+ to: toCounts.perProvider.get(provider) ?? { checked: 0, mentioned: 0, cited: 0 }
13925
+ }));
13926
+ return {
13927
+ project: input.project,
13928
+ from: {
13929
+ month: input.from.month,
13930
+ since: input.from.since,
13931
+ until: input.from.until,
13932
+ runCount: input.from.runCount,
13933
+ lowRunCount: input.from.runCount < VISIBILITY_COMPARE_MIN_RUNS
13934
+ },
13935
+ to: {
13936
+ month: input.to.month,
13937
+ since: input.to.since,
13938
+ until: input.to.until,
13939
+ runCount: input.to.runCount,
13940
+ lowRunCount: input.to.runCount < VISIBILITY_COMPARE_MIN_RUNS
13941
+ },
13942
+ basket: {
13943
+ queryCount: queriesBoth.size,
13944
+ excludedFromOnly: [...fromObs.queryIds].filter((q) => !queriesBoth.has(q)).length,
13945
+ excludedToOnly: [...toObs.queryIds].filter((q) => !queriesBoth.has(q)).length,
13946
+ providers: [...providersBoth].sort((a, b) => a.localeCompare(b)),
13947
+ excludedProviders
13948
+ },
13949
+ metrics,
13950
+ queriesMentioned: {
13951
+ from: { count: fromCounts.queriesMentioned, of: queriesBoth.size },
13952
+ to: { count: toCounts.queriesMentioned, of: queriesBoth.size }
13953
+ },
13954
+ byProvider,
13955
+ modelChanges,
13956
+ continuity: {
13957
+ status: continuityStatus,
13958
+ comparedProviders: [...providersBoth].sort((a, b) => a.localeCompare(b)),
13959
+ providers: continuityProviders
13960
+ },
13961
+ competitors: { from: fromCounts.competitors, to: toCounts.competitors }
13962
+ };
13963
+ }
13964
+
13965
+ // ../api-routes/src/visibility-stats.ts
13634
13966
  function round42(value) {
13635
13967
  return Math.round(value * 1e4) / 1e4;
13636
13968
  }
@@ -13781,8 +14113,8 @@ async function visibilityStatsRoutes(app) {
13781
14113
  if (!Number.isInteger(n) || n <= 0) throw validationError('"lastRuns" must be a positive integer');
13782
14114
  lastRuns = n;
13783
14115
  }
13784
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
13785
- let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and14(eq17(runs.projectId, project.id), eq17(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
14116
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq18(queries.projectId, project.id)).all();
14117
+ let projectRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and14(eq18(runs.projectId, project.id), eq18(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).orderBy(desc8(runs.createdAt)).all().filter((r) => r.status === RunStatuses.completed || r.status === RunStatuses.partial);
13786
14118
  if (sinceMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) >= sinceMs);
13787
14119
  if (untilMs !== null) projectRuns = projectRuns.filter((r) => Date.parse(r.createdAt) <= untilMs);
13788
14120
  if (lastRuns !== null) projectRuns = projectRuns.slice(0, lastRuns);
@@ -13795,12 +14127,12 @@ async function visibilityStatsRoutes(app) {
13795
14127
  citationState: querySnapshots.citationState,
13796
14128
  answerMentioned: querySnapshots.answerMentioned,
13797
14129
  createdAt: querySnapshots.createdAt
13798
- }).from(querySnapshots).where(inArray8(querySnapshots.runId, runIds)).all() : [];
14130
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, runIds)).all() : [];
13799
14131
  const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
13800
14132
  const queryAttribution = buildQueryAttribution(projectQueries);
13801
14133
  let shareOfVoice;
13802
14134
  if (wantShareOfVoice) {
13803
- const competitorRows = app.db.select({ domain: competitors.domain }).from(competitors).where(eq17(competitors.projectId, project.id)).all();
14135
+ const competitorRows = app.db.select({ domain: competitors.domain }).from(competitors).where(eq18(competitors.projectId, project.id)).all();
13804
14136
  const mentionShareCompetitors = competitorRows.map((c) => ({
13805
14137
  domain: c.domain,
13806
14138
  brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
@@ -13810,7 +14142,7 @@ async function visibilityStatsRoutes(app) {
13810
14142
  queryText: querySnapshots.queryText,
13811
14143
  answerMentioned: querySnapshots.answerMentioned,
13812
14144
  answerText: querySnapshots.answerText
13813
- }).from(querySnapshots).where(inArray8(querySnapshots.runId, runIds)).all() : [];
14145
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, runIds)).all() : [];
13814
14146
  const attributedSovSnapshots = sovSnapshots.filter((s) => resolveCurrentQuery(queryAttribution, s) !== void 0);
13815
14147
  const result = buildMentionShare(
13816
14148
  attributedSovSnapshots.map((s) => ({ projectMentioned: s.answerMentioned === true, answerText: s.answerText })),
@@ -13849,10 +14181,66 @@ async function visibilityStatsRoutes(app) {
13849
14181
  };
13850
14182
  return reply.send(response);
13851
14183
  });
14184
+ app.get("/projects/:name/visibility-compare", async (request, reply) => {
14185
+ const project = resolveProject(app.db, request.params.name);
14186
+ const { from: fromRaw, to: toRaw } = request.query;
14187
+ if (fromRaw === void 0 || fromRaw === "") throw validationError('"from" (YYYY-MM) is required');
14188
+ if (toRaw === void 0 || toRaw === "") throw validationError('"to" (YYYY-MM) is required');
14189
+ let fromBounds;
14190
+ let toBounds;
14191
+ try {
14192
+ fromBounds = calendarMonthBounds(fromRaw);
14193
+ } catch (err) {
14194
+ throw validationError(err instanceof RangeError ? `"from": ${err.message}` : '"from" must be in YYYY-MM format');
14195
+ }
14196
+ try {
14197
+ toBounds = calendarMonthBounds(toRaw);
14198
+ } catch (err) {
14199
+ throw validationError(err instanceof RangeError ? `"to": ${err.message}` : '"to" must be in YYYY-MM format');
14200
+ }
14201
+ if (Date.parse(fromBounds.since) >= Date.parse(toBounds.since)) {
14202
+ throw validationError('"from" must be a month strictly before "to"');
14203
+ }
14204
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq18(queries.projectId, project.id)).all();
14205
+ const competitorRows = app.db.select({ domain: competitors.domain }).from(competitors).where(eq18(competitors.projectId, project.id)).all();
14206
+ const competitorInputs = competitorRows.map((c) => ({
14207
+ domain: c.domain,
14208
+ brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
14209
+ }));
14210
+ const loadMonth = (bounds) => {
14211
+ const sinceMs = Date.parse(bounds.since);
14212
+ const untilMs = Date.parse(bounds.until);
14213
+ const monthRuns = app.db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and14(eq18(runs.projectId, project.id), eq18(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter(
14214
+ (r) => (r.status === RunStatuses.completed || r.status === RunStatuses.partial) && Date.parse(r.createdAt) >= sinceMs && Date.parse(r.createdAt) <= untilMs
14215
+ );
14216
+ const runIds = monthRuns.map((r) => r.id);
14217
+ const snapshots = runIds.length > 0 && projectQueries.length > 0 ? app.db.select({
14218
+ queryId: querySnapshots.queryId,
14219
+ queryText: querySnapshots.queryText,
14220
+ provider: querySnapshots.provider,
14221
+ model: querySnapshots.model,
14222
+ citationState: querySnapshots.citationState,
14223
+ answerMentioned: querySnapshots.answerMentioned,
14224
+ answerText: querySnapshots.answerText,
14225
+ citedDomains: querySnapshots.citedDomains
14226
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, runIds)).all() : [];
14227
+ return { runCount: monthRuns.length, snapshots };
14228
+ };
14229
+ const fromMonth = loadMonth(fromBounds);
14230
+ const toMonth = loadMonth(toBounds);
14231
+ const dto = computeVisibilityCompare({
14232
+ project: project.name,
14233
+ queries: projectQueries,
14234
+ competitors: competitorInputs,
14235
+ from: { month: fromRaw, since: fromBounds.since, until: fromBounds.until, runCount: fromMonth.runCount, snapshots: fromMonth.snapshots },
14236
+ to: { month: toRaw, since: toBounds.since, until: toBounds.until, runCount: toMonth.runCount, snapshots: toMonth.snapshots }
14237
+ });
14238
+ return reply.send(dto);
14239
+ });
13852
14240
  }
13853
14241
 
13854
14242
  // ../api-routes/src/composites.ts
13855
- import { eq as eq18, and as and15, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray9 } from "drizzle-orm";
14243
+ import { eq as eq19, and as and15, desc as desc9, sql as sql8, like as like2, or as or4, inArray as inArray10 } from "drizzle-orm";
13856
14244
  var TOP_INSIGHT_LIMIT = 5;
13857
14245
  var SEARCH_HIT_HARD_LIMIT = 50;
13858
14246
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -13870,7 +14258,7 @@ async function compositeRoutes(app) {
13870
14258
  const project = resolveProject(app.db, request.params.name);
13871
14259
  const filterLocation = (request.query.location ?? "").trim() || null;
13872
14260
  const sinceIso = parseSinceFilter(request.query.since);
13873
- const allRunsRaw = app.db.select().from(runs).where(and15(eq18(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
14261
+ const allRunsRaw = app.db.select().from(runs).where(and15(eq19(runs.projectId, project.id), notProbeRun())).orderBy(desc9(runs.createdAt), desc9(runs.id)).all();
13874
14262
  const allRuns = allRunsRaw.filter((r) => runMatchesFilters(r, filterLocation, sinceIso));
13875
14263
  const totalRuns = allRuns.length;
13876
14264
  const visibilityRuns = allRuns.filter((r) => r.kind === RunKinds["answer-visibility"]);
@@ -13883,15 +14271,15 @@ async function compositeRoutes(app) {
13883
14271
  const previousVisibilityRun = pickGroupRepresentative(previousVisRunGroup);
13884
14272
  const latestRunRow = allRuns[0] ?? null;
13885
14273
  const latestRun = latestRunRow ? { totalRuns, run: summarizeRun(latestRunRow) } : { totalRuns: 0, run: null };
13886
- const healthRow = app.db.select().from(healthSnapshots).where(eq18(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
14274
+ const healthRow = app.db.select().from(healthSnapshots).where(eq19(healthSnapshots.projectId, project.id)).orderBy(desc9(healthSnapshots.createdAt)).limit(1).get();
13887
14275
  const health = healthRow ? mapHealthRow2(healthRow) : null;
13888
- const insightRows = app.db.select().from(insights).where(eq18(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
14276
+ const insightRows = app.db.select().from(insights).where(eq19(insights.projectId, project.id)).orderBy(desc9(insights.createdAt)).all();
13889
14277
  const topInsights = insightRows.filter((row) => !row.dismissed).slice(0, TOP_INSIGHT_LIMIT).map(mapInsightRow2);
13890
14278
  const sparklineRunIds = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => r.id);
13891
14279
  const snapshotRunIds = new Set(sparklineRunIds);
13892
14280
  for (const run of latestVisRunGroup) snapshotRunIds.add(run.id);
13893
14281
  for (const run of previousVisRunGroup) snapshotRunIds.add(run.id);
13894
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq18(queries.projectId, project.id)).all();
14282
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq19(queries.projectId, project.id)).all();
13895
14283
  const queryIdByText = new Map(projectQueries.map((q) => [normalizeQueryText(q.query), q.id]));
13896
14284
  const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds], queryIdByText);
13897
14285
  const latestSnapshots = latestVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
@@ -13907,7 +14295,7 @@ async function compositeRoutes(app) {
13907
14295
  trackedPrevious,
13908
14296
  previousVisibilityRun?.createdAt ?? null
13909
14297
  );
13910
- const competitorRows = app.db.select().from(competitors).where(eq18(competitors.projectId, project.id)).all();
14298
+ const competitorRows = app.db.select().from(competitors).where(eq19(competitors.projectId, project.id)).all();
13911
14299
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
13912
14300
  for (const snapshots of snapshotsByRun.values()) {
13913
14301
  for (const snapshot of snapshots) {
@@ -14025,9 +14413,9 @@ async function compositeRoutes(app) {
14025
14413
  citedDomains: querySnapshots.citedDomains,
14026
14414
  rawResponse: querySnapshots.rawResponse,
14027
14415
  createdAt: querySnapshots.createdAt
14028
- }).from(querySnapshots).innerJoin(queries, eq18(querySnapshots.queryId, queries.id)).where(
14416
+ }).from(querySnapshots).innerJoin(queries, eq19(querySnapshots.queryId, queries.id)).where(
14029
14417
  and15(
14030
- eq18(queries.projectId, project.id),
14418
+ eq19(queries.projectId, project.id),
14031
14419
  or4(
14032
14420
  sql8`${querySnapshots.answerText} LIKE ${pattern} ESCAPE '\\'`,
14033
14421
  sql8`${querySnapshots.citedDomains} LIKE ${pattern} ESCAPE '\\'`,
@@ -14038,7 +14426,7 @@ async function compositeRoutes(app) {
14038
14426
  ).orderBy(desc9(querySnapshots.createdAt)).limit(limit + 1).all());
14039
14427
  const insightMatches = app.db.select().from(insights).where(
14040
14428
  and15(
14041
- eq18(insights.projectId, project.id),
14429
+ eq19(insights.projectId, project.id),
14042
14430
  or4(
14043
14431
  like2(insights.title, pattern),
14044
14432
  like2(insights.query, pattern),
@@ -14117,7 +14505,7 @@ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
14117
14505
  answerText: querySnapshots.answerText,
14118
14506
  competitorOverlap: querySnapshots.competitorOverlap,
14119
14507
  citedDomains: querySnapshots.citedDomains
14120
- }).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all();
14508
+ }).from(querySnapshots).where(inArray10(querySnapshots.runId, [...runIds])).all();
14121
14509
  for (const row of rows) {
14122
14510
  const queryText = row.queryText?.trim() || null;
14123
14511
  let queryId;
@@ -14255,7 +14643,7 @@ function buildSuggestedQueriesFromGsc(app, projectId, trackedQueries) {
14255
14643
  // which the JS coerces to 0 — caught by the impression floor anyway).
14256
14644
  avgPosition: sql8`COALESCE(SUM(${gscSearchData.position} * ${gscSearchData.impressions}) * 1.0 / NULLIF(SUM(${gscSearchData.impressions}), 0), 0)`
14257
14645
  }).from(gscSearchData).where(and15(
14258
- eq18(gscSearchData.projectId, projectId),
14646
+ eq19(gscSearchData.projectId, projectId),
14259
14647
  sql8`${gscSearchData.date} >= ${cutoff}`,
14260
14648
  sql8`${gscSearchData.impressions} > 0`
14261
14649
  )).groupBy(gscSearchData.query).orderBy(sql8`SUM(${gscSearchData.impressions}) DESC`).limit(100).all();
@@ -14278,8 +14666,8 @@ function buildIndexCoverageScore(app, projectId) {
14278
14666
  tooltip,
14279
14667
  trend: []
14280
14668
  };
14281
- const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq18(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
14282
- const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq18(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
14669
+ const gscRow = app.db.select().from(gscCoverageSnapshots).where(eq19(gscCoverageSnapshots.projectId, projectId)).orderBy(desc9(gscCoverageSnapshots.date)).limit(1).get();
14670
+ const bingRow = app.db.select().from(bingCoverageSnapshots).where(eq19(bingCoverageSnapshots.projectId, projectId)).orderBy(desc9(bingCoverageSnapshots.date)).limit(1).get();
14283
14671
  const chosen = pickIndexCoverageRow(gscRow, bingRow);
14284
14672
  if (!chosen) return empty;
14285
14673
  const total = chosen.indexed + chosen.notIndexed;
@@ -14305,7 +14693,7 @@ function countGoogleDeindexedUrls(app, projectId) {
14305
14693
  url: gscUrlInspections.url,
14306
14694
  indexingState: gscUrlInspections.indexingState,
14307
14695
  inspectedAt: gscUrlInspections.inspectedAt
14308
- }).from(gscUrlInspections).where(eq18(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
14696
+ }).from(gscUrlInspections).where(eq19(gscUrlInspections.projectId, projectId)).orderBy(desc9(gscUrlInspections.inspectedAt)).all();
14309
14697
  if (rows.length === 0) return 0;
14310
14698
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
14311
14699
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -14654,6 +15042,7 @@ var SCHEMA_TABLE = {
14654
15042
  TrafficSourceListResponse: trafficSourceListResponseSchema,
14655
15043
  TrafficStatusResponse: trafficStatusResponseSchema,
14656
15044
  TrafficSyncResponse: trafficSyncResponseSchema,
15045
+ VisibilityCompareDto: visibilityCompareDtoSchema,
14657
15046
  VisibilityStatsDto: visibilityStatsDtoSchema,
14658
15047
  WordpressAuditPageDto: wordpressAuditPageDtoSchema,
14659
15048
  WordpressBulkMetaResultDto: wordpressBulkMetaResultDtoSchema,
@@ -14930,6 +15319,20 @@ var monthQueryParameter = {
14930
15319
  description: `Aggregate a single calendar month (YYYY-MM), expanded to that month's inclusive UTC bounds. Mutually exclusive with "since"/"until"/"lastRuns".`,
14931
15320
  schema: stringSchema
14932
15321
  };
15322
+ var compareFromQueryParameter = {
15323
+ name: "from",
15324
+ in: "query",
15325
+ required: true,
15326
+ description: 'Earlier calendar month (YYYY-MM) \u2014 the baseline period. Must be strictly before "to".',
15327
+ schema: stringSchema
15328
+ };
15329
+ var compareToQueryParameter = {
15330
+ name: "to",
15331
+ in: "query",
15332
+ required: true,
15333
+ description: 'Later calendar month (YYYY-MM) \u2014 the reporting period compared against "from".',
15334
+ schema: stringSchema
15335
+ };
14933
15336
  var shareOfVoiceQueryParameter = {
14934
15337
  name: "shareOfVoice",
14935
15338
  in: "query",
@@ -15705,6 +16108,19 @@ var routeCatalog = [
15705
16108
  404: errorResponse("Project not found.")
15706
16109
  }
15707
16110
  },
16111
+ {
16112
+ method: "get",
16113
+ path: "/api/v1/projects/{name}/visibility-compare",
16114
+ summary: "Compare AEO visibility month over month",
16115
+ description: "Statistically honest month-over-month AEO comparison in one call. PRIMARY metric is share of voice (brand vs competitor mentions in the same answers), which is less exposed to broad model-wide naming propensity than an absolute rate but never bypasses model continuity. Rates are pooled per-snapshot over each month (invariant to sweep count), restricted to query/provider pairs present in BOTH months, and then restricted again to providers with exactly one known, identical configured model id in both months. `continuity` reports every provider and its model evidence; changed, mixed mid-month, or legacy-unknown models are excluded. When no provider remains, metrics return a continuity-blocked verdict rather than a directional call. `from` must be a month strictly before `to`. A silent upstream version bump under an unchanged configured id remains undetectable.",
16116
+ tags: ["analytics"],
16117
+ parameters: [nameParameter, compareFromQueryParameter, compareToQueryParameter],
16118
+ responses: {
16119
+ 200: jsonResponse("Month-over-month visibility comparison returned.", "VisibilityCompareDto"),
16120
+ 400: errorResponse("Invalid or missing from/to months."),
16121
+ 404: errorResponse("Project not found.")
16122
+ }
16123
+ },
15708
16124
  {
15709
16125
  method: "get",
15710
16126
  path: "/api/v1/projects/{name}/snapshots/diff",
@@ -19015,8 +19431,8 @@ async function settingsRoutes(app, opts) {
19015
19431
  }
19016
19432
 
19017
19433
  // ../api-routes/src/keys.ts
19018
- import crypto12 from "crypto";
19019
- import { desc as desc10, eq as eq19 } from "drizzle-orm";
19434
+ import crypto13 from "crypto";
19435
+ import { desc as desc10, eq as eq20 } from "drizzle-orm";
19020
19436
  var KEYS_WRITE_SCOPE = "keys.write";
19021
19437
  function toApiKeyDto(row) {
19022
19438
  const scopes = Array.isArray(row.scopes) ? row.scopes : [];
@@ -19035,7 +19451,7 @@ function toApiKeyDto(row) {
19035
19451
  async function keysRoutes(app) {
19036
19452
  app.get("/keys", async (request) => {
19037
19453
  const scopedProjectId = request.apiKey?.projectId;
19038
- const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq19(apiKeys.projectId, scopedProjectId)).orderBy(desc10(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
19454
+ const rows = scopedProjectId ? app.db.select().from(apiKeys).where(eq20(apiKeys.projectId, scopedProjectId)).orderBy(desc10(apiKeys.createdAt)).all() : app.db.select().from(apiKeys).orderBy(desc10(apiKeys.createdAt)).all();
19039
19455
  return { keys: rows.map(toApiKeyDto) };
19040
19456
  });
19041
19457
  app.get("/keys/self", async (request) => {
@@ -19043,7 +19459,7 @@ async function keysRoutes(app) {
19043
19459
  if (!id) {
19044
19460
  throw notFound("API key", "self");
19045
19461
  }
19046
- const row = app.db.select().from(apiKeys).where(eq19(apiKeys.id, id)).get();
19462
+ const row = app.db.select().from(apiKeys).where(eq20(apiKeys.id, id)).get();
19047
19463
  if (!row) {
19048
19464
  throw notFound("API key", id);
19049
19465
  }
@@ -19061,15 +19477,15 @@ async function keysRoutes(app) {
19061
19477
  throw forbidden("A project-scoped key can only mint keys scoped to its own project.");
19062
19478
  }
19063
19479
  if (projectId) {
19064
- const proj = app.db.select({ id: projects.id }).from(projects).where(eq19(projects.id, projectId)).get();
19480
+ const proj = app.db.select({ id: projects.id }).from(projects).where(eq20(projects.id, projectId)).get();
19065
19481
  if (!proj) {
19066
19482
  throw notFound("Project", projectId);
19067
19483
  }
19068
19484
  }
19069
- const raw = `cnry_${crypto12.randomBytes(16).toString("hex")}`;
19485
+ const raw = `cnry_${crypto13.randomBytes(16).toString("hex")}`;
19070
19486
  const keyHash = hashApiKey(raw);
19071
19487
  const keyPrefix = raw.slice(0, 9);
19072
- const id = crypto12.randomUUID();
19488
+ const id = crypto13.randomUUID();
19073
19489
  const now = (/* @__PURE__ */ new Date()).toISOString();
19074
19490
  const effectiveScopes = scopes ?? ["*"];
19075
19491
  app.db.transaction((tx) => {
@@ -19107,7 +19523,7 @@ async function keysRoutes(app) {
19107
19523
  app.post("/keys/:id/revoke", async (request) => {
19108
19524
  requireScope(request, KEYS_WRITE_SCOPE);
19109
19525
  const { id } = request.params;
19110
- const row = app.db.select().from(apiKeys).where(eq19(apiKeys.id, id)).get();
19526
+ const row = app.db.select().from(apiKeys).where(eq20(apiKeys.id, id)).get();
19111
19527
  if (!row) {
19112
19528
  throw notFound("API key", id);
19113
19529
  }
@@ -19123,7 +19539,7 @@ async function keysRoutes(app) {
19123
19539
  }
19124
19540
  const now = (/* @__PURE__ */ new Date()).toISOString();
19125
19541
  app.db.transaction((tx) => {
19126
- tx.update(apiKeys).set({ revokedAt: now }).where(eq19(apiKeys.id, id)).run();
19542
+ tx.update(apiKeys).set({ revokedAt: now }).where(eq20(apiKeys.id, id)).run();
19127
19543
  writeAuditLog(tx, auditFromRequest(request, {
19128
19544
  actor: "api",
19129
19545
  action: "api-key.revoked",
@@ -19195,8 +19611,8 @@ async function telemetryRoutes(app, opts) {
19195
19611
  }
19196
19612
 
19197
19613
  // ../api-routes/src/schedules.ts
19198
- import crypto13 from "crypto";
19199
- import { and as and16, eq as eq20 } from "drizzle-orm";
19614
+ import crypto14 from "crypto";
19615
+ import { and as and16, eq as eq21 } from "drizzle-orm";
19200
19616
  function parseKindParam(raw) {
19201
19617
  if (raw === void 0 || raw === null || raw === "") return SchedulableRunKinds["answer-visibility"];
19202
19618
  const parsed = schedulableRunKindSchema.safeParse(raw);
@@ -19223,7 +19639,7 @@ async function scheduleRoutes(app, opts) {
19223
19639
  if (!sourceId) {
19224
19640
  throw validationError('"sourceId" is required when kind is "traffic-sync"');
19225
19641
  }
19226
- const sourceRow = app.db.select().from(trafficSources).where(eq20(trafficSources.id, sourceId)).get();
19642
+ const sourceRow = app.db.select().from(trafficSources).where(eq21(trafficSources.id, sourceId)).get();
19227
19643
  if (!sourceRow || sourceRow.projectId !== project.id) {
19228
19644
  throw notFound("Traffic source", sourceId);
19229
19645
  }
@@ -19268,7 +19684,7 @@ async function scheduleRoutes(app, opts) {
19268
19684
  }
19269
19685
  const now = (/* @__PURE__ */ new Date()).toISOString();
19270
19686
  const enabledBool = enabled !== false;
19271
- const existing = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
19687
+ const existing = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
19272
19688
  if (existing) {
19273
19689
  app.db.update(schedules).set({
19274
19690
  cronExpr,
@@ -19278,10 +19694,10 @@ async function scheduleRoutes(app, opts) {
19278
19694
  sourceId: sourceId ?? null,
19279
19695
  enabled: enabledBool,
19280
19696
  updatedAt: now
19281
- }).where(eq20(schedules.id, existing.id)).run();
19697
+ }).where(eq21(schedules.id, existing.id)).run();
19282
19698
  } else {
19283
19699
  app.db.insert(schedules).values({
19284
- id: crypto13.randomUUID(),
19700
+ id: crypto14.randomUUID(),
19285
19701
  projectId: project.id,
19286
19702
  kind,
19287
19703
  cronExpr,
@@ -19302,13 +19718,13 @@ async function scheduleRoutes(app, opts) {
19302
19718
  diff: { kind, cronExpr, preset, timezone, providers, sourceId }
19303
19719
  });
19304
19720
  opts.onScheduleUpdated?.("upsert", project.id, kind);
19305
- const schedule = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
19721
+ const schedule = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
19306
19722
  return reply.status(existing ? 200 : 201).send(formatSchedule(schedule));
19307
19723
  });
19308
19724
  app.get("/projects/:name/schedule", async (request, reply) => {
19309
19725
  const project = resolveProject(app.db, request.params.name);
19310
19726
  const kind = parseKindParam(request.query?.kind);
19311
- const schedule = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
19727
+ const schedule = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
19312
19728
  if (!schedule) {
19313
19729
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
19314
19730
  }
@@ -19317,11 +19733,11 @@ async function scheduleRoutes(app, opts) {
19317
19733
  app.delete("/projects/:name/schedule", async (request, reply) => {
19318
19734
  const project = resolveProject(app.db, request.params.name);
19319
19735
  const kind = parseKindParam(request.query?.kind);
19320
- const schedule = app.db.select().from(schedules).where(and16(eq20(schedules.projectId, project.id), eq20(schedules.kind, kind))).get();
19736
+ const schedule = app.db.select().from(schedules).where(and16(eq21(schedules.projectId, project.id), eq21(schedules.kind, kind))).get();
19321
19737
  if (!schedule) {
19322
19738
  throw notFound("Schedule", `${request.params.name} (kind=${kind})`);
19323
19739
  }
19324
- app.db.delete(schedules).where(eq20(schedules.id, schedule.id)).run();
19740
+ app.db.delete(schedules).where(eq21(schedules.id, schedule.id)).run();
19325
19741
  writeAuditLog(app.db, {
19326
19742
  projectId: project.id,
19327
19743
  actor: "api",
@@ -19353,8 +19769,8 @@ function formatSchedule(row) {
19353
19769
  }
19354
19770
 
19355
19771
  // ../api-routes/src/notifications.ts
19356
- import crypto14 from "crypto";
19357
- import { eq as eq21 } from "drizzle-orm";
19772
+ import crypto15 from "crypto";
19773
+ import { eq as eq22 } from "drizzle-orm";
19358
19774
  var VALID_EVENTS = ["citation.lost", "citation.gained", "run.completed", "run.failed", "insight.critical", "insight.high"];
19359
19775
  async function notificationRoutes(app, opts = {}) {
19360
19776
  const allowLoopback = opts.allowLoopbackWebhooks === true;
@@ -19373,8 +19789,8 @@ async function notificationRoutes(app, opts = {}) {
19373
19789
  throw validationError(`Invalid event(s): ${invalid.join(", ")}. Must be one of: ${VALID_EVENTS.join(", ")}`);
19374
19790
  }
19375
19791
  const now = (/* @__PURE__ */ new Date()).toISOString();
19376
- const id = crypto14.randomUUID();
19377
- const webhookSecret = crypto14.randomBytes(32).toString("hex");
19792
+ const id = crypto15.randomUUID();
19793
+ const webhookSecret = crypto15.randomBytes(32).toString("hex");
19378
19794
  app.db.insert(notifications).values({
19379
19795
  id,
19380
19796
  projectId: project.id,
@@ -19394,22 +19810,22 @@ async function notificationRoutes(app, opts = {}) {
19394
19810
  diff: { channel, ...redactNotificationUrl(url), events }
19395
19811
  });
19396
19812
  return reply.status(201).send({
19397
- ...formatNotification(app.db.select().from(notifications).where(eq21(notifications.id, id)).get()),
19813
+ ...formatNotification(app.db.select().from(notifications).where(eq22(notifications.id, id)).get()),
19398
19814
  webhookSecret
19399
19815
  });
19400
19816
  });
19401
19817
  app.get("/projects/:name/notifications", async (request, reply) => {
19402
19818
  const project = resolveProject(app.db, request.params.name);
19403
- const rows = app.db.select().from(notifications).where(eq21(notifications.projectId, project.id)).all();
19819
+ const rows = app.db.select().from(notifications).where(eq22(notifications.projectId, project.id)).all();
19404
19820
  return reply.send(rows.map(formatNotification));
19405
19821
  });
19406
19822
  app.delete("/projects/:name/notifications/:id", async (request, reply) => {
19407
19823
  const project = resolveProject(app.db, request.params.name);
19408
- const notification = app.db.select().from(notifications).where(eq21(notifications.id, request.params.id)).get();
19824
+ const notification = app.db.select().from(notifications).where(eq22(notifications.id, request.params.id)).get();
19409
19825
  if (!notification || notification.projectId !== project.id) {
19410
19826
  throw notFound("Notification", request.params.id);
19411
19827
  }
19412
- app.db.delete(notifications).where(eq21(notifications.id, notification.id)).run();
19828
+ app.db.delete(notifications).where(eq22(notifications.id, notification.id)).run();
19413
19829
  writeAuditLog(app.db, {
19414
19830
  projectId: project.id,
19415
19831
  actor: "api",
@@ -19421,7 +19837,7 @@ async function notificationRoutes(app, opts = {}) {
19421
19837
  });
19422
19838
  app.post("/projects/:name/notifications/:id/test", async (request, reply) => {
19423
19839
  const project = resolveProject(app.db, request.params.name);
19424
- const notification = app.db.select().from(notifications).where(eq21(notifications.id, request.params.id)).get();
19840
+ const notification = app.db.select().from(notifications).where(eq22(notifications.id, request.params.id)).get();
19425
19841
  if (!notification || notification.projectId !== project.id) {
19426
19842
  throw notFound("Notification", request.params.id);
19427
19843
  }
@@ -19473,8 +19889,8 @@ function formatNotification(row) {
19473
19889
  }
19474
19890
 
19475
19891
  // ../api-routes/src/google.ts
19476
- import crypto18 from "crypto";
19477
- import { eq as eq22, and as and17, desc as desc11, sql as sql9, inArray as inArray10 } from "drizzle-orm";
19892
+ import crypto19 from "crypto";
19893
+ import { eq as eq23, and as and17, desc as desc11, sql as sql9, inArray as inArray11 } from "drizzle-orm";
19478
19894
 
19479
19895
  // ../api-routes/src/gbp-summary.ts
19480
19896
  function computeMetricTotals(rows) {
@@ -19516,12 +19932,12 @@ function computeWindowDelta(rows, referenceDate) {
19516
19932
  }
19517
19933
  const deltaPct = {};
19518
19934
  const metrics = /* @__PURE__ */ new Set([...Object.keys(recent7d), ...Object.keys(prior7d)]);
19519
- for (const metric of metrics) {
19520
- const recent = recent7d[metric] ?? 0;
19521
- const prior = prior7d[metric] ?? 0;
19522
- recent7d[metric] = recent;
19523
- prior7d[metric] = prior;
19524
- deltaPct[metric] = prior === 0 ? null : Math.round((recent - prior) / prior * 100);
19935
+ for (const metric2 of metrics) {
19936
+ const recent = recent7d[metric2] ?? 0;
19937
+ const prior = prior7d[metric2] ?? 0;
19938
+ recent7d[metric2] = recent;
19939
+ prior7d[metric2] = prior;
19940
+ deltaPct[metric2] = prior === 0 ? null : Math.round((recent - prior) / prior * 100);
19525
19941
  }
19526
19942
  return { recent7d, prior7d, deltaPct };
19527
19943
  }
@@ -20009,7 +20425,7 @@ async function inspectUrl(accessToken, inspectionUrl, siteUrl) {
20009
20425
  }
20010
20426
 
20011
20427
  // ../integration-google-analytics/src/ga4-client.ts
20012
- import crypto15 from "crypto";
20428
+ import crypto16 from "crypto";
20013
20429
 
20014
20430
  // ../integration-google-analytics/src/constants.ts
20015
20431
  var GA4_DATA_API_BASE = "https://analyticsdata.googleapis.com/v1beta";
@@ -20116,7 +20532,7 @@ function createServiceAccountJwt(clientEmail, privateKey, scope) {
20116
20532
  const headerB64 = encode(header);
20117
20533
  const payloadB64 = encode(payload);
20118
20534
  const signingInput = `${headerB64}.${payloadB64}`;
20119
- const sign = crypto15.createSign("RSA-SHA256");
20535
+ const sign = crypto16.createSign("RSA-SHA256");
20120
20536
  sign.update(signingInput);
20121
20537
  const signature = sign.sign(privateKey, "base64url");
20122
20538
  return `${signingInput}.${signature}`;
@@ -20996,7 +21412,7 @@ async function listPlaceActionLinks(accessToken, locationName, opts = {}) {
20996
21412
  }
20997
21413
 
20998
21414
  // ../integration-google-business-profile/src/lodging-client.ts
20999
- import crypto16 from "crypto";
21415
+ import crypto17 from "crypto";
21000
21416
  async function getLodging(accessToken, locationName, opts = {}) {
21001
21417
  const url = `${GBP_LODGING_BASE}/${locationName}/lodging?readMask=*`;
21002
21418
  try {
@@ -21023,7 +21439,7 @@ function isPopulated(value) {
21023
21439
  return true;
21024
21440
  }
21025
21441
  function hashLodging(lodging) {
21026
- return crypto16.createHash("sha256").update(stableStringify2(lodging)).digest("hex");
21442
+ return crypto17.createHash("sha256").update(stableStringify2(lodging)).digest("hex");
21027
21443
  }
21028
21444
  function stableStringify2(value) {
21029
21445
  if (value === null || typeof value !== "object") return JSON.stringify(value);
@@ -21034,7 +21450,7 @@ function stableStringify2(value) {
21034
21450
  }
21035
21451
 
21036
21452
  // ../integration-google-business-profile/src/attributes-client.ts
21037
- import crypto17 from "crypto";
21453
+ import crypto18 from "crypto";
21038
21454
  async function getAttributes(accessToken, locationName, opts = {}) {
21039
21455
  const url = `${GBP_BUSINESS_INFO_BASE}/${locationName}/attributes`;
21040
21456
  let res;
@@ -21064,7 +21480,7 @@ function countAttributes(attrs) {
21064
21480
  }
21065
21481
  function hashAttributes(attrs) {
21066
21482
  const sorted = [...attrs].sort((a, b) => a.name.localeCompare(b.name));
21067
- return crypto17.createHash("sha256").update(stableStringify3(sorted)).digest("hex");
21483
+ return crypto18.createHash("sha256").update(stableStringify3(sorted)).digest("hex");
21068
21484
  }
21069
21485
  function stableStringify3(value) {
21070
21486
  if (value === null || typeof value !== "object") return JSON.stringify(value);
@@ -21086,7 +21502,7 @@ function scopesForConnectionType(type) {
21086
21502
  }
21087
21503
  }
21088
21504
  function signState(payload, secret) {
21089
- return crypto18.createHmac("sha256", secret).update(payload).digest("hex");
21505
+ return crypto19.createHmac("sha256", secret).update(payload).digest("hex");
21090
21506
  }
21091
21507
  function buildSignedState(data, secret) {
21092
21508
  const payload = JSON.stringify(data);
@@ -21097,7 +21513,7 @@ function verifySignedState(encoded, secret) {
21097
21513
  try {
21098
21514
  const { payload, sig } = JSON.parse(Buffer.from(encoded, "base64url").toString());
21099
21515
  const expected = signState(payload, secret);
21100
- if (!crypto18.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
21516
+ if (!crypto19.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"))) return null;
21101
21517
  return JSON.parse(payload);
21102
21518
  } catch {
21103
21519
  return null;
@@ -21314,7 +21730,7 @@ async function googleRoutes(app, opts) {
21314
21730
  if (!projectId) {
21315
21731
  return reply.status(400).send("Stale OAuth state \u2014 restart the connect flow.");
21316
21732
  }
21317
- const project = app.db.select().from(projects).where(eq22(projects.id, projectId)).get();
21733
+ const project = app.db.select().from(projects).where(eq23(projects.id, projectId)).get();
21318
21734
  if (!project) {
21319
21735
  return reply.status(400).send("Project no longer exists. Restart the connect flow.");
21320
21736
  }
@@ -21433,7 +21849,7 @@ async function googleRoutes(app, opts) {
21433
21849
  throw validationError('No GSC connection found for this domain. Run "canonry google connect" first.');
21434
21850
  }
21435
21851
  const now = (/* @__PURE__ */ new Date()).toISOString();
21436
- const runId = crypto18.randomUUID();
21852
+ const runId = crypto19.randomUUID();
21437
21853
  app.db.insert(runs).values({
21438
21854
  id: runId,
21439
21855
  projectId: project.id,
@@ -21446,14 +21862,14 @@ async function googleRoutes(app, opts) {
21446
21862
  if (opts.onGscSyncRequested) {
21447
21863
  opts.onGscSyncRequested(runId, project.id, { days, full });
21448
21864
  }
21449
- const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
21865
+ const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
21450
21866
  return run;
21451
21867
  });
21452
21868
  app.get("/projects/:name/google/gsc/performance", async (request) => {
21453
21869
  const project = resolveProject(app.db, request.params.name);
21454
21870
  const { startDate, endDate, query, page, limit, offset } = request.query;
21455
21871
  const cutoffDate = !startDate ? windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null : null;
21456
- const conditions = [eq22(gscSearchData.projectId, project.id)];
21872
+ const conditions = [eq23(gscSearchData.projectId, project.id)];
21457
21873
  if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
21458
21874
  else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
21459
21875
  if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
@@ -21481,7 +21897,7 @@ async function googleRoutes(app, opts) {
21481
21897
  const windowStart = startDate ?? cutoffDate ?? "";
21482
21898
  const windowEnd = endDate ?? "9999-12-31";
21483
21899
  const dailyTotals = readGscDailyTotals(app.db, project.id, windowStart, windowEnd);
21484
- const conditions = [eq22(gscSearchData.projectId, project.id)];
21900
+ const conditions = [eq23(gscSearchData.projectId, project.id)];
21485
21901
  if (startDate) conditions.push(sql9`${gscSearchData.date} >= ${startDate}`);
21486
21902
  else if (cutoffDate) conditions.push(sql9`${gscSearchData.date} >= ${cutoffDate}`);
21487
21903
  if (endDate) conditions.push(sql9`${gscSearchData.date} <= ${endDate}`);
@@ -21542,7 +21958,7 @@ async function googleRoutes(app, opts) {
21542
21958
  const mob = ir.mobileUsabilityResult;
21543
21959
  const rich = ir.richResultsResult;
21544
21960
  const now = (/* @__PURE__ */ new Date()).toISOString();
21545
- const id = crypto18.randomUUID();
21961
+ const id = crypto19.randomUUID();
21546
21962
  app.db.insert(gscUrlInspections).values({
21547
21963
  id,
21548
21964
  projectId: project.id,
@@ -21580,8 +21996,8 @@ async function googleRoutes(app, opts) {
21580
21996
  app.get("/projects/:name/google/gsc/inspections", async (request) => {
21581
21997
  const project = resolveProject(app.db, request.params.name);
21582
21998
  const { url, limit } = request.query;
21583
- const conditions = [eq22(gscUrlInspections.projectId, project.id)];
21584
- if (url) conditions.push(eq22(gscUrlInspections.url, url));
21999
+ const conditions = [eq23(gscUrlInspections.projectId, project.id)];
22000
+ if (url) conditions.push(eq23(gscUrlInspections.url, url));
21585
22001
  const rows = app.db.select().from(gscUrlInspections).where(and17(...conditions)).orderBy(desc11(gscUrlInspections.inspectedAt)).limit(parseInt(limit ?? "100", 10)).all();
21586
22002
  return rows.map((r) => ({
21587
22003
  id: r.id,
@@ -21601,7 +22017,7 @@ async function googleRoutes(app, opts) {
21601
22017
  });
21602
22018
  app.get("/projects/:name/google/gsc/deindexed", async (request) => {
21603
22019
  const project = resolveProject(app.db, request.params.name);
21604
- const allInspections = app.db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22020
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21605
22021
  const byUrl = /* @__PURE__ */ new Map();
21606
22022
  for (const row of allInspections) {
21607
22023
  const existing = byUrl.get(row.url);
@@ -21629,7 +22045,7 @@ async function googleRoutes(app, opts) {
21629
22045
  });
21630
22046
  app.get("/projects/:name/google/gsc/coverage", async (request) => {
21631
22047
  const project = resolveProject(app.db, request.params.name);
21632
- const allInspections = app.db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22048
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21633
22049
  const canonicalUrl = (url) => url.replace(/^http:\/\//, "https://");
21634
22050
  const latestByUrl = /* @__PURE__ */ new Map();
21635
22051
  const historyByUrl = /* @__PURE__ */ new Map();
@@ -21678,7 +22094,7 @@ async function googleRoutes(app, opts) {
21678
22094
  const total = latestByUrl.size;
21679
22095
  const indexed = indexedUrls.length;
21680
22096
  const notIndexed = notIndexedUrls.length;
21681
- const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq22(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
22097
+ const latestSnapshot = app.db.select({ createdAt: gscCoverageSnapshots.createdAt }).from(gscCoverageSnapshots).where(eq23(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.createdAt)).limit(1).get();
21682
22098
  const lastSyncedAt = latestSnapshot?.createdAt ?? null;
21683
22099
  const formatRow = (r) => ({
21684
22100
  id: r.id,
@@ -21729,7 +22145,7 @@ async function googleRoutes(app, opts) {
21729
22145
  const project = resolveProject(app.db, request.params.name);
21730
22146
  const parsed = parseInt(request.query.limit ?? "90", 10);
21731
22147
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
21732
- const rows = app.db.select().from(gscCoverageSnapshots).where(eq22(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
22148
+ const rows = app.db.select().from(gscCoverageSnapshots).where(eq23(gscCoverageSnapshots.projectId, project.id)).orderBy(desc11(gscCoverageSnapshots.date)).limit(limit).all();
21733
22149
  return rows.map((r) => ({
21734
22150
  date: r.date,
21735
22151
  indexed: r.indexed,
@@ -21786,7 +22202,7 @@ async function googleRoutes(app, opts) {
21786
22202
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
21787
22203
  });
21788
22204
  const now = (/* @__PURE__ */ new Date()).toISOString();
21789
- const runId = crypto18.randomUUID();
22205
+ const runId = crypto19.randomUUID();
21790
22206
  app.db.insert(runs).values({
21791
22207
  id: runId,
21792
22208
  projectId: project.id,
@@ -21798,7 +22214,7 @@ async function googleRoutes(app, opts) {
21798
22214
  if (opts.onInspectSitemapRequested) {
21799
22215
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl });
21800
22216
  }
21801
- const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
22217
+ const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
21802
22218
  return { sitemaps, primarySitemapUrl: sitemapUrl, run };
21803
22219
  });
21804
22220
  app.post("/projects/:name/google/gsc/inspect-sitemap", async (request) => {
@@ -21812,7 +22228,7 @@ async function googleRoutes(app, opts) {
21812
22228
  throw validationError("No GSC property configured for this connection");
21813
22229
  }
21814
22230
  const now = (/* @__PURE__ */ new Date()).toISOString();
21815
- const runId = crypto18.randomUUID();
22231
+ const runId = crypto19.randomUUID();
21816
22232
  app.db.insert(runs).values({
21817
22233
  id: runId,
21818
22234
  projectId: project.id,
@@ -21825,7 +22241,7 @@ async function googleRoutes(app, opts) {
21825
22241
  if (opts.onInspectSitemapRequested) {
21826
22242
  opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
21827
22243
  }
21828
- const run = app.db.select().from(runs).where(eq22(runs.id, runId)).get();
22244
+ const run = app.db.select().from(runs).where(eq23(runs.id, runId)).get();
21829
22245
  return run;
21830
22246
  });
21831
22247
  app.put("/projects/:name/google/connections/:type/sitemap", async (request) => {
@@ -21872,7 +22288,7 @@ async function googleRoutes(app, opts) {
21872
22288
  const { accessToken } = await getValidToken(store, project.canonicalDomain, "gsc", googleClientId, googleClientSecret);
21873
22289
  let urlsToNotify = request.body?.urls ?? [];
21874
22290
  if (request.body?.allUnindexed) {
21875
- const allInspections = app.db.select().from(gscUrlInspections).where(eq22(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
22291
+ const allInspections = app.db.select().from(gscUrlInspections).where(eq23(gscUrlInspections.projectId, project.id)).orderBy(desc11(gscUrlInspections.inspectedAt)).all();
21876
22292
  const latestByUrl = /* @__PURE__ */ new Map();
21877
22293
  for (const row of allInspections) {
21878
22294
  if (!latestByUrl.has(row.url)) {
@@ -21990,7 +22406,7 @@ async function googleRoutes(app, opts) {
21990
22406
  };
21991
22407
  }
21992
22408
  function listSelectionResponse(projectId) {
21993
- const rows = app.db.select().from(gbpLocations).where(eq22(gbpLocations.projectId, projectId)).all();
22409
+ const rows = app.db.select().from(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).all();
21994
22410
  const dtos = rows.map(rowToDto2);
21995
22411
  return {
21996
22412
  locations: dtos,
@@ -21999,15 +22415,15 @@ async function googleRoutes(app, opts) {
21999
22415
  };
22000
22416
  }
22001
22417
  function clearGbpProjectData(tx, projectId) {
22002
- tx.delete(gbpDailyMetrics).where(eq22(gbpDailyMetrics.projectId, projectId)).run();
22003
- tx.delete(gbpKeywordImpressions).where(eq22(gbpKeywordImpressions.projectId, projectId)).run();
22004
- tx.delete(gbpKeywordMonthly).where(eq22(gbpKeywordMonthly.projectId, projectId)).run();
22005
- tx.delete(gbpPlaceActions).where(eq22(gbpPlaceActions.projectId, projectId)).run();
22006
- tx.delete(gbpLodgingSnapshots).where(eq22(gbpLodgingSnapshots.projectId, projectId)).run();
22007
- tx.delete(gbpLocations).where(eq22(gbpLocations.projectId, projectId)).run();
22418
+ tx.delete(gbpDailyMetrics).where(eq23(gbpDailyMetrics.projectId, projectId)).run();
22419
+ tx.delete(gbpKeywordImpressions).where(eq23(gbpKeywordImpressions.projectId, projectId)).run();
22420
+ tx.delete(gbpKeywordMonthly).where(eq23(gbpKeywordMonthly.projectId, projectId)).run();
22421
+ tx.delete(gbpPlaceActions).where(eq23(gbpPlaceActions.projectId, projectId)).run();
22422
+ tx.delete(gbpLodgingSnapshots).where(eq23(gbpLodgingSnapshots.projectId, projectId)).run();
22423
+ tx.delete(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).run();
22008
22424
  }
22009
22425
  function currentProjectAccount(projectId) {
22010
- const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq22(gbpLocations.projectId, projectId)).limit(1).get();
22426
+ const row = app.db.select({ accountName: gbpLocations.accountName }).from(gbpLocations).where(eq23(gbpLocations.projectId, projectId)).limit(1).get();
22011
22427
  return row?.accountName ?? null;
22012
22428
  }
22013
22429
  app.post("/projects/:name/gbp/locations/discover", async (request) => {
@@ -22077,7 +22493,7 @@ async function googleRoutes(app, opts) {
22077
22493
  app.db.transaction((tx) => {
22078
22494
  if (switching) clearGbpProjectData(tx, project.id);
22079
22495
  for (const remote of remoteLocations) {
22080
- const existing = tx.select().from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.locationName, remote.name))).get();
22496
+ const existing = tx.select().from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), eq23(gbpLocations.locationName, remote.name))).get();
22081
22497
  const profile = buildLocationProfileFields(remote);
22082
22498
  if (existing) {
22083
22499
  tx.update(gbpLocations).set({
@@ -22090,10 +22506,10 @@ async function googleRoutes(app, opts) {
22090
22506
  mapsUri: remote.metadata?.mapsUri ?? null,
22091
22507
  ...profile,
22092
22508
  updatedAt: now
22093
- }).where(eq22(gbpLocations.id, existing.id)).run();
22509
+ }).where(eq23(gbpLocations.id, existing.id)).run();
22094
22510
  } else {
22095
22511
  tx.insert(gbpLocations).values({
22096
- id: crypto18.randomUUID(),
22512
+ id: crypto19.randomUUID(),
22097
22513
  projectId: project.id,
22098
22514
  accountName,
22099
22515
  locationName: remote.name,
@@ -22174,11 +22590,11 @@ async function googleRoutes(app, opts) {
22174
22590
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid selection request");
22175
22591
  }
22176
22592
  const { selected } = parsed.data;
22177
- const existing = app.db.select().from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.locationName, locationName))).get();
22593
+ const existing = app.db.select().from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), eq23(gbpLocations.locationName, locationName))).get();
22178
22594
  if (!existing) throw notFound("GBP location", locationName);
22179
22595
  const now = (/* @__PURE__ */ new Date()).toISOString();
22180
22596
  app.db.transaction((tx) => {
22181
- tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq22(gbpLocations.id, existing.id)).run();
22597
+ tx.update(gbpLocations).set({ selected, updatedAt: now }).where(eq23(gbpLocations.id, existing.id)).run();
22182
22598
  writeAuditLog(tx, {
22183
22599
  projectId: project.id,
22184
22600
  actor: "api",
@@ -22187,7 +22603,7 @@ async function googleRoutes(app, opts) {
22187
22603
  entityId: locationName
22188
22604
  });
22189
22605
  });
22190
- const refreshed = app.db.select().from(gbpLocations).where(eq22(gbpLocations.id, existing.id)).get();
22606
+ const refreshed = app.db.select().from(gbpLocations).where(eq23(gbpLocations.id, existing.id)).get();
22191
22607
  return rowToDto2(refreshed);
22192
22608
  });
22193
22609
  app.delete("/projects/:name/gbp/connection", async (request, reply) => {
@@ -22217,7 +22633,7 @@ async function googleRoutes(app, opts) {
22217
22633
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid sync request");
22218
22634
  }
22219
22635
  const now = (/* @__PURE__ */ new Date()).toISOString();
22220
- const runId = crypto18.randomUUID();
22636
+ const runId = crypto19.randomUUID();
22221
22637
  app.db.insert(runs).values({
22222
22638
  id: runId,
22223
22639
  projectId: project.id,
@@ -22231,9 +22647,9 @@ async function googleRoutes(app, opts) {
22231
22647
  });
22232
22648
  app.get("/projects/:name/gbp/metrics", async (request) => {
22233
22649
  const project = resolveProject(app.db, request.params.name);
22234
- const conditions = [eq22(gbpDailyMetrics.projectId, project.id)];
22235
- if (request.query.locationName) conditions.push(eq22(gbpDailyMetrics.locationName, request.query.locationName));
22236
- if (request.query.metric) conditions.push(eq22(gbpDailyMetrics.metric, request.query.metric));
22650
+ const conditions = [eq23(gbpDailyMetrics.projectId, project.id)];
22651
+ if (request.query.locationName) conditions.push(eq23(gbpDailyMetrics.locationName, request.query.locationName));
22652
+ if (request.query.metric) conditions.push(eq23(gbpDailyMetrics.metric, request.query.metric));
22237
22653
  const rows = app.db.select().from(gbpDailyMetrics).where(and17(...conditions)).orderBy(desc11(gbpDailyMetrics.date)).all();
22238
22654
  return {
22239
22655
  metrics: rows.map((r) => ({ locationName: r.locationName, date: r.date, metric: r.metric, value: r.value })),
@@ -22242,8 +22658,8 @@ async function googleRoutes(app, opts) {
22242
22658
  });
22243
22659
  app.get("/projects/:name/gbp/keywords", async (request) => {
22244
22660
  const project = resolveProject(app.db, request.params.name);
22245
- const conditions = [eq22(gbpKeywordImpressions.projectId, project.id)];
22246
- if (request.query.locationName) conditions.push(eq22(gbpKeywordImpressions.locationName, request.query.locationName));
22661
+ const conditions = [eq23(gbpKeywordImpressions.projectId, project.id)];
22662
+ if (request.query.locationName) conditions.push(eq23(gbpKeywordImpressions.locationName, request.query.locationName));
22247
22663
  const rows = app.db.select().from(gbpKeywordImpressions).where(and17(...conditions)).all();
22248
22664
  rows.sort((a, b) => (b.valueCount ?? -1) - (a.valueCount ?? -1));
22249
22665
  const thresholded = rows.filter((r) => r.valueThreshold !== null).length;
@@ -22262,8 +22678,8 @@ async function googleRoutes(app, opts) {
22262
22678
  });
22263
22679
  app.get("/projects/:name/gbp/place-actions", async (request) => {
22264
22680
  const project = resolveProject(app.db, request.params.name);
22265
- const conditions = [eq22(gbpPlaceActions.projectId, project.id)];
22266
- if (request.query.locationName) conditions.push(eq22(gbpPlaceActions.locationName, request.query.locationName));
22681
+ const conditions = [eq23(gbpPlaceActions.projectId, project.id)];
22682
+ if (request.query.locationName) conditions.push(eq23(gbpPlaceActions.locationName, request.query.locationName));
22267
22683
  const rows = app.db.select().from(gbpPlaceActions).where(and17(...conditions)).all();
22268
22684
  return {
22269
22685
  placeActions: rows.map((r) => ({
@@ -22279,8 +22695,8 @@ async function googleRoutes(app, opts) {
22279
22695
  });
22280
22696
  app.get("/projects/:name/gbp/lodging", async (request) => {
22281
22697
  const project = resolveProject(app.db, request.params.name);
22282
- const conditions = [eq22(gbpLodgingSnapshots.projectId, project.id)];
22283
- if (request.query.locationName) conditions.push(eq22(gbpLodgingSnapshots.locationName, request.query.locationName));
22698
+ const conditions = [eq23(gbpLodgingSnapshots.projectId, project.id)];
22699
+ if (request.query.locationName) conditions.push(eq23(gbpLodgingSnapshots.locationName, request.query.locationName));
22284
22700
  const rows = app.db.select().from(gbpLodgingSnapshots).where(and17(...conditions)).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
22285
22701
  const latestByLocation = /* @__PURE__ */ new Map();
22286
22702
  for (const row of rows) {
@@ -22296,8 +22712,8 @@ async function googleRoutes(app, opts) {
22296
22712
  });
22297
22713
  app.get("/projects/:name/gbp/attributes", async (request) => {
22298
22714
  const project = resolveProject(app.db, request.params.name);
22299
- const conditions = [eq22(gbpAttributesSnapshots.projectId, project.id)];
22300
- if (request.query.locationName) conditions.push(eq22(gbpAttributesSnapshots.locationName, request.query.locationName));
22715
+ const conditions = [eq23(gbpAttributesSnapshots.projectId, project.id)];
22716
+ if (request.query.locationName) conditions.push(eq23(gbpAttributesSnapshots.locationName, request.query.locationName));
22301
22717
  const rows = app.db.select().from(gbpAttributesSnapshots).where(and17(...conditions)).orderBy(desc11(gbpAttributesSnapshots.syncedAt)).all();
22302
22718
  const latestByLocation = /* @__PURE__ */ new Map();
22303
22719
  for (const row of rows) {
@@ -22316,8 +22732,8 @@ async function googleRoutes(app, opts) {
22316
22732
  });
22317
22733
  app.get("/projects/:name/gbp/places", async (request) => {
22318
22734
  const project = resolveProject(app.db, request.params.name);
22319
- const conditions = [eq22(gbpPlaceDetails.projectId, project.id)];
22320
- if (request.query.locationName) conditions.push(eq22(gbpPlaceDetails.locationName, request.query.locationName));
22735
+ const conditions = [eq23(gbpPlaceDetails.projectId, project.id)];
22736
+ if (request.query.locationName) conditions.push(eq23(gbpPlaceDetails.locationName, request.query.locationName));
22321
22737
  const rows = app.db.select().from(gbpPlaceDetails).where(and17(...conditions)).orderBy(desc11(gbpPlaceDetails.syncedAt)).all();
22322
22738
  const latestByLocation = /* @__PURE__ */ new Map();
22323
22739
  for (const row of rows) {
@@ -22337,7 +22753,7 @@ async function googleRoutes(app, opts) {
22337
22753
  app.get("/projects/:name/gbp/summary", async (request) => {
22338
22754
  const project = resolveProject(app.db, request.params.name);
22339
22755
  const locationName = request.query.locationName ?? null;
22340
- const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), eq22(gbpLocations.selected, true))).all().map((r) => r.n);
22756
+ const locationNames = locationName ? [locationName] : app.db.select({ n: gbpLocations.locationName }).from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), eq23(gbpLocations.selected, true))).all().map((r) => r.n);
22341
22757
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22342
22758
  if (locationNames.length === 0) {
22343
22759
  return buildGbpSummary({
@@ -22351,10 +22767,10 @@ async function googleRoutes(app, opts) {
22351
22767
  locationProfiles: []
22352
22768
  });
22353
22769
  }
22354
- const metricRows = app.db.select().from(gbpDailyMetrics).where(and17(eq22(gbpDailyMetrics.projectId, project.id), inArray10(gbpDailyMetrics.locationName, locationNames))).all();
22355
- const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and17(eq22(gbpKeywordImpressions.projectId, project.id), inArray10(gbpKeywordImpressions.locationName, locationNames))).all();
22356
- const placeActionRows = app.db.select().from(gbpPlaceActions).where(and17(eq22(gbpPlaceActions.projectId, project.id), inArray10(gbpPlaceActions.locationName, locationNames))).all();
22357
- const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and17(eq22(gbpLodgingSnapshots.projectId, project.id), inArray10(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
22770
+ const metricRows = app.db.select().from(gbpDailyMetrics).where(and17(eq23(gbpDailyMetrics.projectId, project.id), inArray11(gbpDailyMetrics.locationName, locationNames))).all();
22771
+ const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and17(eq23(gbpKeywordImpressions.projectId, project.id), inArray11(gbpKeywordImpressions.locationName, locationNames))).all();
22772
+ const placeActionRows = app.db.select().from(gbpPlaceActions).where(and17(eq23(gbpPlaceActions.projectId, project.id), inArray11(gbpPlaceActions.locationName, locationNames))).all();
22773
+ const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and17(eq23(gbpLodgingSnapshots.projectId, project.id), inArray11(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc11(gbpLodgingSnapshots.syncedAt)).all();
22358
22774
  const latestLodgingByLocation = /* @__PURE__ */ new Map();
22359
22775
  for (const row of lodgingRows) {
22360
22776
  if (!latestLodgingByLocation.has(row.locationName)) {
@@ -22368,7 +22784,7 @@ async function googleRoutes(app, opts) {
22368
22784
  regularHours: gbpLocations.regularHours,
22369
22785
  primaryPhone: gbpLocations.primaryPhone,
22370
22786
  openStatus: gbpLocations.openStatus
22371
- }).from(gbpLocations).where(and17(eq22(gbpLocations.projectId, project.id), inArray10(gbpLocations.locationName, locationNames))).all();
22787
+ }).from(gbpLocations).where(and17(eq23(gbpLocations.projectId, project.id), inArray11(gbpLocations.locationName, locationNames))).all();
22372
22788
  return buildGbpSummary({
22373
22789
  locationName,
22374
22790
  locationCount: locationNames.length,
@@ -22383,8 +22799,8 @@ async function googleRoutes(app, opts) {
22383
22799
  }
22384
22800
 
22385
22801
  // ../api-routes/src/ads.ts
22386
- import crypto19 from "crypto";
22387
- import { eq as eq23, and as and18, asc as asc3, gte as gte3, lte as lte2, inArray as inArray11 } from "drizzle-orm";
22802
+ import crypto20 from "crypto";
22803
+ import { eq as eq24, and as and18, asc as asc3, gte as gte3, lte as lte2, inArray as inArray12 } from "drizzle-orm";
22388
22804
  function statusDto(row) {
22389
22805
  if (!row) return { connected: false };
22390
22806
  return {
@@ -22434,7 +22850,7 @@ async function adsRoutes(app, opts) {
22434
22850
  createdAt: existingCfg?.createdAt ?? now,
22435
22851
  updatedAt: now
22436
22852
  });
22437
- const existingRow = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22853
+ const existingRow = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
22438
22854
  app.db.transaction((tx) => {
22439
22855
  if (existingRow) {
22440
22856
  tx.update(adsConnections).set({
@@ -22444,10 +22860,10 @@ async function adsRoutes(app, opts) {
22444
22860
  timezone: account.timezone,
22445
22861
  status: account.status,
22446
22862
  updatedAt: now
22447
- }).where(eq23(adsConnections.id, existingRow.id)).run();
22863
+ }).where(eq24(adsConnections.id, existingRow.id)).run();
22448
22864
  } else {
22449
22865
  tx.insert(adsConnections).values({
22450
- id: crypto19.randomUUID(),
22866
+ id: crypto20.randomUUID(),
22451
22867
  projectId: project.id,
22452
22868
  adAccountId: account.id,
22453
22869
  displayName: account.name,
@@ -22466,16 +22882,16 @@ async function adsRoutes(app, opts) {
22466
22882
  entityId: account.id
22467
22883
  }));
22468
22884
  });
22469
- const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22885
+ const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
22470
22886
  return statusDto(row);
22471
22887
  }
22472
22888
  );
22473
22889
  app.delete("/projects/:name/ads/connection", async (request) => {
22474
22890
  const project = resolveProject(app.db, request.params.name);
22475
- const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22891
+ const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
22476
22892
  if (row) {
22477
22893
  app.db.transaction((tx) => {
22478
- tx.delete(adsConnections).where(eq23(adsConnections.id, row.id)).run();
22894
+ tx.delete(adsConnections).where(eq24(adsConnections.id, row.id)).run();
22479
22895
  writeAuditLog(tx, auditFromRequest(request, {
22480
22896
  projectId: project.id,
22481
22897
  actor: "api",
@@ -22491,25 +22907,25 @@ async function adsRoutes(app, opts) {
22491
22907
  });
22492
22908
  app.get("/projects/:name/ads/status", async (request) => {
22493
22909
  const project = resolveProject(app.db, request.params.name);
22494
- const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22910
+ const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
22495
22911
  return statusDto(row);
22496
22912
  });
22497
22913
  app.post("/projects/:name/ads/sync", async (request) => {
22498
22914
  const project = resolveProject(app.db, request.params.name);
22499
- const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22915
+ const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
22500
22916
  if (!row) {
22501
22917
  throw validationError('No ads connection for this project. Run "canonry ads connect" first.');
22502
22918
  }
22503
22919
  const inFlight = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and18(
22504
- eq23(runs.projectId, project.id),
22505
- eq23(runs.kind, RunKinds["ads-sync"]),
22506
- inArray11(runs.status, [RunStatuses.queued, RunStatuses.running])
22920
+ eq24(runs.projectId, project.id),
22921
+ eq24(runs.kind, RunKinds["ads-sync"]),
22922
+ inArray12(runs.status, [RunStatuses.queued, RunStatuses.running])
22507
22923
  )).get();
22508
22924
  if (inFlight) {
22509
22925
  const existing = { runId: inFlight.id, status: inFlight.status };
22510
22926
  return existing;
22511
22927
  }
22512
- const runId = crypto19.randomUUID();
22928
+ const runId = crypto20.randomUUID();
22513
22929
  app.db.insert(runs).values({
22514
22930
  id: runId,
22515
22931
  projectId: project.id,
@@ -22524,9 +22940,9 @@ async function adsRoutes(app, opts) {
22524
22940
  });
22525
22941
  app.get("/projects/:name/ads/campaigns", async (request) => {
22526
22942
  const project = resolveProject(app.db, request.params.name);
22527
- const campaignRows = app.db.select().from(adsCampaigns).where(eq23(adsCampaigns.projectId, project.id)).all();
22528
- const groupRows = app.db.select().from(adsAdGroups).where(eq23(adsAdGroups.projectId, project.id)).all();
22529
- const adRows = app.db.select().from(adsAds).where(eq23(adsAds.projectId, project.id)).all();
22943
+ const campaignRows = app.db.select().from(adsCampaigns).where(eq24(adsCampaigns.projectId, project.id)).all();
22944
+ const groupRows = app.db.select().from(adsAdGroups).where(eq24(adsAdGroups.projectId, project.id)).all();
22945
+ const adRows = app.db.select().from(adsAds).where(eq24(adsAds.projectId, project.id)).all();
22530
22946
  const adsByGroup = /* @__PURE__ */ new Map();
22531
22947
  for (const ad of adRows) {
22532
22948
  const dto = {
@@ -22580,9 +22996,9 @@ async function adsRoutes(app, opts) {
22580
22996
  }
22581
22997
  parsedLevel = result.data;
22582
22998
  }
22583
- const conditions = [eq23(adsInsightsDaily.projectId, project.id)];
22584
- if (parsedLevel) conditions.push(eq23(adsInsightsDaily.level, parsedLevel));
22585
- if (entityId) conditions.push(eq23(adsInsightsDaily.entityId, entityId));
22999
+ const conditions = [eq24(adsInsightsDaily.projectId, project.id)];
23000
+ if (parsedLevel) conditions.push(eq24(adsInsightsDaily.level, parsedLevel));
23001
+ if (entityId) conditions.push(eq24(adsInsightsDaily.entityId, entityId));
22586
23002
  if (from) conditions.push(gte3(adsInsightsDaily.date, from));
22587
23003
  if (to) conditions.push(lte2(adsInsightsDaily.date, to));
22588
23004
  const rows = app.db.select().from(adsInsightsDaily).where(and18(...conditions)).orderBy(asc3(adsInsightsDaily.date)).all();
@@ -22597,19 +23013,19 @@ async function adsRoutes(app, opts) {
22597
23013
  ctr: adsCtr(row.clicks, row.impressions),
22598
23014
  cpcMicros: adsCpcMicros(row.spendMicros, row.clicks)
22599
23015
  }));
22600
- const conn = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
23016
+ const conn = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
22601
23017
  const response = { rows: dtoRows, currencyCode: conn?.currencyCode ?? null };
22602
23018
  return response;
22603
23019
  });
22604
23020
  app.get("/projects/:name/ads/summary", async (request) => {
22605
23021
  const project = resolveProject(app.db, request.params.name);
22606
- const row = app.db.select().from(adsConnections).where(eq23(adsConnections.projectId, project.id)).get();
22607
- const campaignCount = app.db.select().from(adsCampaigns).where(eq23(adsCampaigns.projectId, project.id)).all().length;
22608
- const adGroupCount = app.db.select().from(adsAdGroups).where(eq23(adsAdGroups.projectId, project.id)).all().length;
22609
- const adCount = app.db.select().from(adsAds).where(eq23(adsAds.projectId, project.id)).all().length;
23022
+ const row = app.db.select().from(adsConnections).where(eq24(adsConnections.projectId, project.id)).get();
23023
+ const campaignCount = app.db.select().from(adsCampaigns).where(eq24(adsCampaigns.projectId, project.id)).all().length;
23024
+ const adGroupCount = app.db.select().from(adsAdGroups).where(eq24(adsAdGroups.projectId, project.id)).all().length;
23025
+ const adCount = app.db.select().from(adsAds).where(eq24(adsAds.projectId, project.id)).all().length;
22610
23026
  const campaignInsights = app.db.select().from(adsInsightsDaily).where(and18(
22611
- eq23(adsInsightsDaily.projectId, project.id),
22612
- eq23(adsInsightsDaily.level, "campaign")
23027
+ eq24(adsInsightsDaily.projectId, project.id),
23028
+ eq24(adsInsightsDaily.level, "campaign")
22613
23029
  )).all();
22614
23030
  let impressions = 0;
22615
23031
  let clicks = 0;
@@ -22648,8 +23064,8 @@ async function adsRoutes(app, opts) {
22648
23064
  }
22649
23065
 
22650
23066
  // ../api-routes/src/bing.ts
22651
- import crypto20 from "crypto";
22652
- import { eq as eq24, and as and19, desc as desc12 } from "drizzle-orm";
23067
+ import crypto21 from "crypto";
23068
+ import { eq as eq25, and as and19, desc as desc12 } from "drizzle-orm";
22653
23069
 
22654
23070
  // ../integration-bing/src/constants.ts
22655
23071
  var BING_WMT_API_BASE = "https://ssl.bing.com/webmaster/api.svc/json";
@@ -23023,7 +23439,7 @@ async function bingRoutes(app, opts) {
23023
23439
  const store = requireConnectionStore();
23024
23440
  const project = resolveProject(app.db, request.params.name);
23025
23441
  requireConnection(store, project.canonicalDomain);
23026
- const allInspections = app.db.select().from(bingUrlInspections).where(eq24(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23442
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq25(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23027
23443
  const latestByUrl = /* @__PURE__ */ new Map();
23028
23444
  const definitiveByUrl = /* @__PURE__ */ new Map();
23029
23445
  for (const row of allInspections) {
@@ -23080,7 +23496,7 @@ async function bingRoutes(app, opts) {
23080
23496
  const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
23081
23497
  const now = (/* @__PURE__ */ new Date()).toISOString();
23082
23498
  app.db.insert(bingCoverageSnapshots).values({
23083
- id: crypto20.randomUUID(),
23499
+ id: crypto21.randomUUID(),
23084
23500
  projectId: project.id,
23085
23501
  syncRunId: snapshotRunId,
23086
23502
  date: snapshotDate,
@@ -23112,7 +23528,7 @@ async function bingRoutes(app, opts) {
23112
23528
  const project = resolveProject(app.db, request.params.name);
23113
23529
  const parsed = parseInt(request.query.limit ?? "90", 10);
23114
23530
  const limit = Number.isNaN(parsed) || parsed <= 0 ? 90 : parsed;
23115
- const rows = app.db.select().from(bingCoverageSnapshots).where(eq24(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
23531
+ const rows = app.db.select().from(bingCoverageSnapshots).where(eq25(bingCoverageSnapshots.projectId, project.id)).orderBy(desc12(bingCoverageSnapshots.date)).limit(limit).all();
23116
23532
  return rows.map((r) => ({
23117
23533
  date: r.date,
23118
23534
  indexed: r.indexed,
@@ -23124,7 +23540,7 @@ async function bingRoutes(app, opts) {
23124
23540
  requireConnectionStore();
23125
23541
  const project = resolveProject(app.db, request.params.name);
23126
23542
  const { url, limit } = request.query;
23127
- const whereClause = url ? and19(eq24(bingUrlInspections.projectId, project.id), eq24(bingUrlInspections.url, url)) : eq24(bingUrlInspections.projectId, project.id);
23543
+ const whereClause = url ? and19(eq25(bingUrlInspections.projectId, project.id), eq25(bingUrlInspections.url, url)) : eq25(bingUrlInspections.projectId, project.id);
23128
23544
  const filtered = app.db.select().from(bingUrlInspections).where(whereClause).orderBy(desc12(bingUrlInspections.inspectedAt)).limit(Math.max(1, Math.min(parseInt(limit ?? "100", 10) || 100, 1e3))).all();
23129
23545
  return filtered.map((r) => ({
23130
23546
  id: r.id,
@@ -23151,7 +23567,7 @@ async function bingRoutes(app, opts) {
23151
23567
  throw validationError("url is required");
23152
23568
  }
23153
23569
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
23154
- const runId = crypto20.randomUUID();
23570
+ const runId = crypto21.randomUUID();
23155
23571
  app.db.insert(runs).values({
23156
23572
  id: runId,
23157
23573
  projectId: project.id,
@@ -23172,7 +23588,7 @@ async function bingRoutes(app, opts) {
23172
23588
  discoveryDate: result.DiscoveryDate ?? null
23173
23589
  });
23174
23590
  const now = (/* @__PURE__ */ new Date()).toISOString();
23175
- const id = crypto20.randomUUID();
23591
+ const id = crypto21.randomUUID();
23176
23592
  const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
23177
23593
  const lastCrawledDate = parseBingDate(result.LastCrawledDate);
23178
23594
  const inIndexDate = parseBingDate(result.InIndexDate);
@@ -23214,7 +23630,7 @@ async function bingRoutes(app, opts) {
23214
23630
  anchorCount: result.AnchorCount ?? null,
23215
23631
  discoveryDate
23216
23632
  }).run();
23217
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq24(runs.id, runId)).run();
23633
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq25(runs.id, runId)).run();
23218
23634
  return {
23219
23635
  id,
23220
23636
  url,
@@ -23230,7 +23646,7 @@ async function bingRoutes(app, opts) {
23230
23646
  } catch (e) {
23231
23647
  const msg = e instanceof Error ? e.message : String(e);
23232
23648
  bingLog("error", "inspect-url.failed", { domain: project.canonicalDomain, url, error: msg });
23233
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(runs.id, runId)).run();
23649
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq25(runs.id, runId)).run();
23234
23650
  throw e;
23235
23651
  }
23236
23652
  });
@@ -23242,7 +23658,7 @@ async function bingRoutes(app, opts) {
23242
23658
  throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
23243
23659
  }
23244
23660
  const now = (/* @__PURE__ */ new Date()).toISOString();
23245
- const runId = crypto20.randomUUID();
23661
+ const runId = crypto21.randomUUID();
23246
23662
  app.db.insert(runs).values({
23247
23663
  id: runId,
23248
23664
  projectId: project.id,
@@ -23257,7 +23673,7 @@ async function bingRoutes(app, opts) {
23257
23673
  } else {
23258
23674
  bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
23259
23675
  }
23260
- const run = app.db.select().from(runs).where(eq24(runs.id, runId)).get();
23676
+ const run = app.db.select().from(runs).where(eq25(runs.id, runId)).get();
23261
23677
  return run;
23262
23678
  });
23263
23679
  app.post("/projects/:name/bing/request-indexing", async (request) => {
@@ -23269,7 +23685,7 @@ async function bingRoutes(app, opts) {
23269
23685
  }
23270
23686
  let urlsToSubmit = request.body?.urls ?? [];
23271
23687
  if (request.body?.allUnindexed) {
23272
- const allInspections = app.db.select().from(bingUrlInspections).where(eq24(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23688
+ const allInspections = app.db.select().from(bingUrlInspections).where(eq25(bingUrlInspections.projectId, project.id)).orderBy(desc12(bingUrlInspections.inspectedAt)).all();
23273
23689
  const latestByUrl = /* @__PURE__ */ new Map();
23274
23690
  for (const row of allInspections) {
23275
23691
  if (!latestByUrl.has(row.url)) {
@@ -23356,14 +23772,14 @@ async function bingRoutes(app, opts) {
23356
23772
  import fs from "fs";
23357
23773
  import path from "path";
23358
23774
  import os from "os";
23359
- import { eq as eq25, and as and20 } from "drizzle-orm";
23775
+ import { eq as eq26, and as and20 } from "drizzle-orm";
23360
23776
  function getScreenshotDir() {
23361
23777
  return path.join(os.homedir(), ".canonry", "screenshots");
23362
23778
  }
23363
23779
  async function cdpRoutes(app, opts) {
23364
23780
  app.get("/screenshots/:snapshotId", async (request, reply) => {
23365
23781
  const { snapshotId } = request.params;
23366
- const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs, eq25(querySnapshots.runId, runs.id)).where(eq25(querySnapshots.id, snapshotId)).get();
23782
+ const snapshot = app.db.select({ screenshotPath: querySnapshots.screenshotPath, projectId: runs.projectId }).from(querySnapshots).innerJoin(runs, eq26(querySnapshots.runId, runs.id)).where(eq26(querySnapshots.id, snapshotId)).get();
23367
23783
  if (!snapshot?.screenshotPath) {
23368
23784
  const err = notFound("Screenshot", snapshotId);
23369
23785
  return reply.code(err.statusCode).send(err.toJSON());
@@ -23431,7 +23847,7 @@ async function cdpRoutes(app, opts) {
23431
23847
  async (request, reply) => {
23432
23848
  const project = resolveProject(app.db, request.params.name);
23433
23849
  const { runId } = request.params;
23434
- const run = app.db.select().from(runs).where(and20(eq25(runs.id, runId), eq25(runs.projectId, project.id))).get();
23850
+ const run = app.db.select().from(runs).where(and20(eq26(runs.id, runId), eq26(runs.projectId, project.id))).get();
23435
23851
  if (!run) {
23436
23852
  const err = notFound("Run", runId);
23437
23853
  return reply.code(err.statusCode).send(err.toJSON());
@@ -23444,8 +23860,8 @@ async function cdpRoutes(app, opts) {
23444
23860
  citedDomains: querySnapshots.citedDomains,
23445
23861
  screenshotPath: querySnapshots.screenshotPath,
23446
23862
  rawResponse: querySnapshots.rawResponse
23447
- }).from(querySnapshots).where(eq25(querySnapshots.runId, runId)).all());
23448
- const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq25(queries.projectId, project.id)).all();
23863
+ }).from(querySnapshots).where(eq26(querySnapshots.runId, runId)).all());
23864
+ const queryRows = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq26(queries.projectId, project.id)).all();
23449
23865
  const queryMap = new Map(queryRows.map((q) => [q.id, q.query]));
23450
23866
  const byQuery = /* @__PURE__ */ new Map();
23451
23867
  for (const snap of snapshots) {
@@ -23527,8 +23943,8 @@ async function cdpRoutes(app, opts) {
23527
23943
  }
23528
23944
 
23529
23945
  // ../api-routes/src/ga.ts
23530
- import crypto21 from "crypto";
23531
- import { eq as eq26, desc as desc13, and as and21, sql as sql10 } from "drizzle-orm";
23946
+ import crypto22 from "crypto";
23947
+ import { eq as eq27, desc as desc13, and as and21, sql as sql10 } from "drizzle-orm";
23532
23948
  function gaLog(level, action, ctx) {
23533
23949
  const entry = { ts: (/* @__PURE__ */ new Date()).toISOString(), level, module: "GA4Routes", action, ...ctx };
23534
23950
  const stream = level === "error" ? process.stderr : process.stdout;
@@ -23805,10 +24221,10 @@ async function ga4Routes(app, opts) {
23805
24221
  if (!saConn && !oauthConn) {
23806
24222
  throw notFound("GA4 connection", project.name);
23807
24223
  }
23808
- app.db.delete(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, project.id)).run();
23809
- app.db.delete(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).run();
23810
- app.db.delete(gaAiReferrals).where(eq26(gaAiReferrals.projectId, project.id)).run();
23811
- app.db.delete(gaSocialReferrals).where(eq26(gaSocialReferrals.projectId, project.id)).run();
24224
+ app.db.delete(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, project.id)).run();
24225
+ app.db.delete(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).run();
24226
+ app.db.delete(gaAiReferrals).where(eq27(gaAiReferrals.projectId, project.id)).run();
24227
+ app.db.delete(gaSocialReferrals).where(eq27(gaSocialReferrals.projectId, project.id)).run();
23812
24228
  const propertyId = saConn?.propertyId ?? oauthConn?.propertyId ?? null;
23813
24229
  opts.ga4CredentialStore?.deleteConnection(project.name);
23814
24230
  opts.googleConnectionStore?.deleteConnection(project.canonicalDomain, "ga4");
@@ -23829,7 +24245,7 @@ async function ga4Routes(app, opts) {
23829
24245
  if (!connected) {
23830
24246
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
23831
24247
  }
23832
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24248
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
23833
24249
  return {
23834
24250
  connected: true,
23835
24251
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -23853,7 +24269,7 @@ async function ga4Routes(app, opts) {
23853
24269
  const syncAi = !only || only === "ai";
23854
24270
  const syncSocial = !only || only === "social";
23855
24271
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
23856
- const runId = crypto21.randomUUID();
24272
+ const runId = crypto22.randomUUID();
23857
24273
  app.db.insert(runs).values({
23858
24274
  id: runId,
23859
24275
  projectId: project.id,
@@ -23894,14 +24310,14 @@ async function ga4Routes(app, opts) {
23894
24310
  if (syncTraffic) {
23895
24311
  tx.delete(gaTrafficSnapshots).where(
23896
24312
  and21(
23897
- eq26(gaTrafficSnapshots.projectId, project.id),
24313
+ eq27(gaTrafficSnapshots.projectId, project.id),
23898
24314
  sql10`${gaTrafficSnapshots.date} >= ${summary.periodStart}`,
23899
24315
  sql10`${gaTrafficSnapshots.date} <= ${summary.periodEnd}`
23900
24316
  )
23901
24317
  ).run();
23902
24318
  for (const row of rows) {
23903
24319
  tx.insert(gaTrafficSnapshots).values({
23904
- id: crypto21.randomUUID(),
24320
+ id: crypto22.randomUUID(),
23905
24321
  projectId: project.id,
23906
24322
  date: row.date,
23907
24323
  landingPage: row.landingPage,
@@ -23918,14 +24334,14 @@ async function ga4Routes(app, opts) {
23918
24334
  if (syncAi) {
23919
24335
  tx.delete(gaAiReferrals).where(
23920
24336
  and21(
23921
- eq26(gaAiReferrals.projectId, project.id),
24337
+ eq27(gaAiReferrals.projectId, project.id),
23922
24338
  sql10`${gaAiReferrals.date} >= ${summary.periodStart}`,
23923
24339
  sql10`${gaAiReferrals.date} <= ${summary.periodEnd}`
23924
24340
  )
23925
24341
  ).run();
23926
24342
  for (const row of aiReferrals) {
23927
24343
  tx.insert(gaAiReferrals).values({
23928
- id: crypto21.randomUUID(),
24344
+ id: crypto22.randomUUID(),
23929
24345
  projectId: project.id,
23930
24346
  date: row.date,
23931
24347
  source: row.source,
@@ -23950,14 +24366,14 @@ async function ga4Routes(app, opts) {
23950
24366
  if (syncSocial) {
23951
24367
  tx.delete(gaSocialReferrals).where(
23952
24368
  and21(
23953
- eq26(gaSocialReferrals.projectId, project.id),
24369
+ eq27(gaSocialReferrals.projectId, project.id),
23954
24370
  sql10`${gaSocialReferrals.date} >= ${summary.periodStart}`,
23955
24371
  sql10`${gaSocialReferrals.date} <= ${summary.periodEnd}`
23956
24372
  )
23957
24373
  ).run();
23958
24374
  for (const row of socialReferrals) {
23959
24375
  tx.insert(gaSocialReferrals).values({
23960
- id: crypto21.randomUUID(),
24376
+ id: crypto22.randomUUID(),
23961
24377
  projectId: project.id,
23962
24378
  date: row.date,
23963
24379
  source: row.source,
@@ -23971,9 +24387,9 @@ async function ga4Routes(app, opts) {
23971
24387
  }
23972
24388
  }
23973
24389
  if (syncSummary) {
23974
- tx.delete(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).run();
24390
+ tx.delete(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).run();
23975
24391
  tx.insert(gaTrafficSummaries).values({
23976
- id: crypto21.randomUUID(),
24392
+ id: crypto22.randomUUID(),
23977
24393
  projectId: project.id,
23978
24394
  periodStart: summary.periodStart,
23979
24395
  periodEnd: summary.periodEnd,
@@ -23983,10 +24399,10 @@ async function ga4Routes(app, opts) {
23983
24399
  syncedAt: now,
23984
24400
  syncRunId: runId
23985
24401
  }).run();
23986
- tx.delete(gaTrafficWindowSummaries).where(eq26(gaTrafficWindowSummaries.projectId, project.id)).run();
24402
+ tx.delete(gaTrafficWindowSummaries).where(eq27(gaTrafficWindowSummaries.projectId, project.id)).run();
23987
24403
  for (const ws of windowSummaries) {
23988
24404
  tx.insert(gaTrafficWindowSummaries).values({
23989
- id: crypto21.randomUUID(),
24405
+ id: crypto22.randomUUID(),
23990
24406
  projectId: project.id,
23991
24407
  windowKey: ws.windowKey,
23992
24408
  periodStart: ws.periodStart,
@@ -24001,7 +24417,7 @@ async function ga4Routes(app, opts) {
24001
24417
  }
24002
24418
  }
24003
24419
  });
24004
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq26(runs.id, runId)).run();
24420
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: now }).where(eq27(runs.id, runId)).run();
24005
24421
  const syncedComponents = only ? [
24006
24422
  ...syncTraffic ? ["traffic"] : [],
24007
24423
  ...syncSummary ? ["summary"] : [],
@@ -24030,7 +24446,7 @@ async function ga4Routes(app, opts) {
24030
24446
  } catch (e) {
24031
24447
  const msg = e instanceof Error ? e.message : String(e);
24032
24448
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
24033
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(runs.id, runId)).run();
24449
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq27(runs.id, runId)).run();
24034
24450
  throw e;
24035
24451
  }
24036
24452
  });
@@ -24041,11 +24457,11 @@ async function ga4Routes(app, opts) {
24041
24457
  const window = parseWindow(request.query.window);
24042
24458
  const cutoff = windowCutoff(window);
24043
24459
  const cutoffDate = cutoff?.slice(0, 10) ?? null;
24044
- const snapshotConditions = [eq26(gaTrafficSnapshots.projectId, project.id)];
24460
+ const snapshotConditions = [eq27(gaTrafficSnapshots.projectId, project.id)];
24045
24461
  if (cutoffDate) snapshotConditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
24046
- const aiConditions = [eq26(gaAiReferrals.projectId, project.id)];
24462
+ const aiConditions = [eq27(gaAiReferrals.projectId, project.id)];
24047
24463
  if (cutoffDate) aiConditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
24048
- const socialConditions = [eq26(gaSocialReferrals.projectId, project.id)];
24464
+ const socialConditions = [eq27(gaSocialReferrals.projectId, project.id)];
24049
24465
  if (cutoffDate) socialConditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
24050
24466
  const windowSummaryRow = cutoffDate ? app.db.select({
24051
24467
  totalSessions: gaTrafficWindowSummaries.totalSessions,
@@ -24054,8 +24470,8 @@ async function ga4Routes(app, opts) {
24054
24470
  totalUsers: gaTrafficWindowSummaries.totalUsers
24055
24471
  }).from(gaTrafficWindowSummaries).where(
24056
24472
  and21(
24057
- eq26(gaTrafficWindowSummaries.projectId, project.id),
24058
- eq26(gaTrafficWindowSummaries.windowKey, window)
24473
+ eq27(gaTrafficWindowSummaries.projectId, project.id),
24474
+ eq27(gaTrafficWindowSummaries.windowKey, window)
24059
24475
  )
24060
24476
  ).get() : null;
24061
24477
  const snapshotTotalsRow = cutoffDate && !windowSummaryRow ? app.db.select({
@@ -24067,14 +24483,14 @@ async function ga4Routes(app, opts) {
24067
24483
  totalSessions: gaTrafficSummaries.totalSessions,
24068
24484
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
24069
24485
  totalUsers: gaTrafficSummaries.totalUsers
24070
- }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).get();
24486
+ }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).get();
24071
24487
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
24072
24488
  totalDirectSessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
24073
24489
  }).from(gaTrafficSnapshots).where(and21(...snapshotConditions)).get();
24074
24490
  const summaryMeta = app.db.select({
24075
24491
  periodStart: gaTrafficSummaries.periodStart,
24076
24492
  periodEnd: gaTrafficSummaries.periodEnd
24077
- }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).get();
24493
+ }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).get();
24078
24494
  const rows = app.db.select({
24079
24495
  landingPage: sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
24080
24496
  sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
@@ -24135,7 +24551,7 @@ async function ga4Routes(app, opts) {
24135
24551
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`,
24136
24552
  users: sql10`SUM(${gaSocialReferrals.users})`
24137
24553
  }).from(gaSocialReferrals).where(and21(...socialConditions)).get();
24138
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq26(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24554
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq27(gaTrafficSummaries.projectId, project.id)).orderBy(desc13(gaTrafficSummaries.syncedAt)).limit(1).get();
24139
24555
  const total = summaryRow?.totalSessions ?? 0;
24140
24556
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
24141
24557
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -24233,7 +24649,7 @@ async function ga4Routes(app, opts) {
24233
24649
  const project = resolveProject(app.db, request.params.name);
24234
24650
  requireGa4Connection(opts, project.name, project.canonicalDomain);
24235
24651
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
24236
- const conditions = [eq26(gaAiReferrals.projectId, project.id)];
24652
+ const conditions = [eq27(gaAiReferrals.projectId, project.id)];
24237
24653
  if (cutoffDate) conditions.push(sql10`${gaAiReferrals.date} >= ${cutoffDate}`);
24238
24654
  const rows = app.db.select({
24239
24655
  date: gaAiReferrals.date,
@@ -24258,7 +24674,7 @@ async function ga4Routes(app, opts) {
24258
24674
  const project = resolveProject(app.db, request.params.name);
24259
24675
  requireGa4Connection(opts, project.name, project.canonicalDomain);
24260
24676
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
24261
- const conditions = [eq26(gaSocialReferrals.projectId, project.id)];
24677
+ const conditions = [eq27(gaSocialReferrals.projectId, project.id)];
24262
24678
  if (cutoffDate) conditions.push(sql10`${gaSocialReferrals.date} >= ${cutoffDate}`);
24263
24679
  const rows = app.db.select({
24264
24680
  date: gaSocialReferrals.date,
@@ -24281,7 +24697,7 @@ async function ga4Routes(app, opts) {
24281
24697
  return fmt(d);
24282
24698
  };
24283
24699
  const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(
24284
- eq26(gaSocialReferrals.projectId, project.id),
24700
+ eq27(gaSocialReferrals.projectId, project.id),
24285
24701
  sql10`${gaSocialReferrals.date} >= ${from}`,
24286
24702
  sql10`${gaSocialReferrals.date} < ${to}`
24287
24703
  )).get();
@@ -24294,7 +24710,7 @@ async function ga4Routes(app, opts) {
24294
24710
  source: gaSocialReferrals.source,
24295
24711
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`
24296
24712
  }).from(gaSocialReferrals).where(and21(
24297
- eq26(gaSocialReferrals.projectId, project.id),
24713
+ eq27(gaSocialReferrals.projectId, project.id),
24298
24714
  sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
24299
24715
  sql10`${gaSocialReferrals.date} < ${fmt(today)}`
24300
24716
  )).groupBy(gaSocialReferrals.source).all();
@@ -24302,7 +24718,7 @@ async function ga4Routes(app, opts) {
24302
24718
  source: gaSocialReferrals.source,
24303
24719
  sessions: sql10`SUM(${gaSocialReferrals.sessions})`
24304
24720
  }).from(gaSocialReferrals).where(and21(
24305
- eq26(gaSocialReferrals.projectId, project.id),
24721
+ eq27(gaSocialReferrals.projectId, project.id),
24306
24722
  sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
24307
24723
  sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`
24308
24724
  )).groupBy(gaSocialReferrals.source).all();
@@ -24343,16 +24759,16 @@ async function ga4Routes(app, opts) {
24343
24759
  return fmt(d);
24344
24760
  };
24345
24761
  const pct = (cur, prev) => prev === 0 ? null : Math.round((cur - prev) / prev * 100);
24346
- const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24347
- const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24348
- const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq26(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24762
+ const sumTotal = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq27(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24763
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq27(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24764
+ const sumDirect = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and21(eq27(gaTrafficSnapshots.projectId, project.id), sql10`${gaTrafficSnapshots.date} >= ${from}`, sql10`${gaTrafficSnapshots.date} < ${to}`)).get();
24349
24765
  const sumAi = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
24350
- eq26(gaAiReferrals.projectId, project.id),
24766
+ eq27(gaAiReferrals.projectId, project.id),
24351
24767
  sql10`${gaAiReferrals.date} >= ${from}`,
24352
24768
  sql10`${gaAiReferrals.date} < ${to}`,
24353
- eq26(gaAiReferrals.sourceDimension, "session")
24769
+ eq27(gaAiReferrals.sourceDimension, "session")
24354
24770
  )).get();
24355
- const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
24771
+ const sumSocial = (from, to) => app.db.select({ sessions: sql10`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and21(eq27(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${from}`, sql10`${gaSocialReferrals.date} < ${to}`)).get();
24356
24772
  const todayStr = fmt(today);
24357
24773
  const buildTrend = (sum) => {
24358
24774
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -24362,16 +24778,16 @@ async function ga4Routes(app, opts) {
24362
24778
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: pct(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: pct(c30, p30) };
24363
24779
  };
24364
24780
  const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
24365
- eq26(gaAiReferrals.projectId, project.id),
24781
+ eq27(gaAiReferrals.projectId, project.id),
24366
24782
  sql10`${gaAiReferrals.date} >= ${daysAgo(7)}`,
24367
24783
  sql10`${gaAiReferrals.date} < ${todayStr}`,
24368
- eq26(gaAiReferrals.sourceDimension, "session")
24784
+ eq27(gaAiReferrals.sourceDimension, "session")
24369
24785
  )).groupBy(gaAiReferrals.source).all();
24370
24786
  const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql10`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and21(
24371
- eq26(gaAiReferrals.projectId, project.id),
24787
+ eq27(gaAiReferrals.projectId, project.id),
24372
24788
  sql10`${gaAiReferrals.date} >= ${daysAgo(14)}`,
24373
24789
  sql10`${gaAiReferrals.date} < ${daysAgo(7)}`,
24374
- eq26(gaAiReferrals.sourceDimension, "session")
24790
+ eq27(gaAiReferrals.sourceDimension, "session")
24375
24791
  )).groupBy(gaAiReferrals.source).all();
24376
24792
  const findBiggestMover = (current, prev) => {
24377
24793
  const prevMap = new Map(prev.map((r) => [r.source, r.sessions]));
@@ -24387,8 +24803,8 @@ async function ga4Routes(app, opts) {
24387
24803
  }
24388
24804
  return mover;
24389
24805
  };
24390
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
24391
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq26(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
24806
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq27(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql10`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
24807
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql10`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and21(eq27(gaSocialReferrals.projectId, project.id), sql10`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql10`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
24392
24808
  return {
24393
24809
  total: buildTrend(sumTotal),
24394
24810
  organic: buildTrend(sumOrganic),
@@ -24403,7 +24819,7 @@ async function ga4Routes(app, opts) {
24403
24819
  const project = resolveProject(app.db, request.params.name);
24404
24820
  requireGa4Connection(opts, project.name, project.canonicalDomain);
24405
24821
  const cutoffDate = windowCutoff(parseWindow(request.query.window))?.slice(0, 10) ?? null;
24406
- const conditions = [eq26(gaTrafficSnapshots.projectId, project.id)];
24822
+ const conditions = [eq27(gaTrafficSnapshots.projectId, project.id)];
24407
24823
  if (cutoffDate) conditions.push(sql10`${gaTrafficSnapshots.date} >= ${cutoffDate}`);
24408
24824
  const rows = app.db.select({
24409
24825
  date: gaTrafficSnapshots.date,
@@ -24426,7 +24842,7 @@ async function ga4Routes(app, opts) {
24426
24842
  sessions: sql10`SUM(${gaTrafficSnapshots.sessions})`,
24427
24843
  organicSessions: sql10`SUM(${gaTrafficSnapshots.organicSessions})`,
24428
24844
  users: sql10`SUM(${gaTrafficSnapshots.users})`
24429
- }).from(gaTrafficSnapshots).where(eq26(gaTrafficSnapshots.projectId, project.id)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
24845
+ }).from(gaTrafficSnapshots).where(eq27(gaTrafficSnapshots.projectId, project.id)).groupBy(sql10`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql10`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
24430
24846
  return {
24431
24847
  pages: trafficPages.map((r) => ({
24432
24848
  landingPage: r.landingPage,
@@ -24560,7 +24976,7 @@ function parseSchemaPageEntry(entry) {
24560
24976
  }
24561
24977
 
24562
24978
  // ../integration-wordpress/src/wordpress-client.ts
24563
- import crypto22 from "crypto";
24979
+ import crypto23 from "crypto";
24564
24980
  function validateUsername(username) {
24565
24981
  if (!username || typeof username !== "string" || username.trim().length === 0) {
24566
24982
  throw new WordpressApiError("AUTH_INVALID", "Username is required and must be a non-empty string", 400);
@@ -24773,7 +25189,7 @@ function buildSnippet(content) {
24773
25189
  return `${text2.slice(0, 157)}...`;
24774
25190
  }
24775
25191
  function contentHash(content) {
24776
- return crypto22.createHash("sha256").update(content).digest("hex");
25192
+ return crypto23.createHash("sha256").update(content).digest("hex");
24777
25193
  }
24778
25194
  function buildAmbiguousSlugMessage(slug, pages) {
24779
25195
  const candidates = pages.map((page) => {
@@ -26073,8 +26489,8 @@ async function wordpressRoutes(app, opts) {
26073
26489
  }
26074
26490
 
26075
26491
  // ../api-routes/src/backlinks.ts
26076
- import crypto23 from "crypto";
26077
- import { and as and23, asc as asc4, desc as desc14, eq as eq27, sql as sql11 } from "drizzle-orm";
26492
+ import crypto24 from "crypto";
26493
+ import { and as and23, asc as asc4, desc as desc14, eq as eq28, sql as sql11 } from "drizzle-orm";
26078
26494
 
26079
26495
  // ../integration-commoncrawl/src/constants.ts
26080
26496
  import os2 from "os";
@@ -26571,10 +26987,10 @@ function mapRunRow(row) {
26571
26987
  }
26572
26988
  function latestSummaryForProject(db, projectId, source, release) {
26573
26989
  const conditions = [
26574
- eq27(backlinkSummaries.projectId, projectId),
26575
- eq27(backlinkSummaries.source, source)
26990
+ eq28(backlinkSummaries.projectId, projectId),
26991
+ eq28(backlinkSummaries.source, source)
26576
26992
  ];
26577
- if (release) conditions.push(eq27(backlinkSummaries.release, release));
26993
+ if (release) conditions.push(eq28(backlinkSummaries.release, release));
26578
26994
  return db.select().from(backlinkSummaries).where(and23(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
26579
26995
  }
26580
26996
  function parseExcludeCrawlers(value) {
@@ -26584,9 +27000,9 @@ function parseExcludeCrawlers(value) {
26584
27000
  }
26585
27001
  function computeFilteredSummary(db, base) {
26586
27002
  const baseDomainCondition = and23(
26587
- eq27(backlinkDomains.projectId, base.projectId),
26588
- eq27(backlinkDomains.source, base.source),
26589
- eq27(backlinkDomains.release, base.release)
27003
+ eq28(backlinkDomains.projectId, base.projectId),
27004
+ eq28(backlinkDomains.source, base.source),
27005
+ eq28(backlinkDomains.release, base.release)
26590
27006
  );
26591
27007
  const filteredCondition = and23(baseDomainCondition, backlinkCrawlerExclusionClause());
26592
27008
  const unfilteredAgg = db.select({
@@ -26618,13 +27034,13 @@ function computeFilteredSummary(db, base) {
26618
27034
  };
26619
27035
  }
26620
27036
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
26621
- const summary = db.select().from(backlinkSummaries).where(and23(eq27(backlinkSummaries.projectId, projectId), eq27(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
27037
+ const summary = db.select().from(backlinkSummaries).where(and23(eq28(backlinkSummaries.projectId, projectId), eq28(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
26622
27038
  let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
26623
27039
  if (summary && excludeCrawlers) {
26624
27040
  const filtered = db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(and23(
26625
- eq27(backlinkDomains.projectId, projectId),
26626
- eq27(backlinkDomains.source, source),
26627
- eq27(backlinkDomains.release, summary.release),
27041
+ eq28(backlinkDomains.projectId, projectId),
27042
+ eq28(backlinkDomains.source, source),
27043
+ eq28(backlinkDomains.release, summary.release),
26628
27044
  backlinkCrawlerExclusionClause()
26629
27045
  )).get();
26630
27046
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -26639,7 +27055,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
26639
27055
  };
26640
27056
  }
26641
27057
  function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
26642
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
27058
+ const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
26643
27059
  const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
26644
27060
  const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
26645
27061
  const sources = [
@@ -26695,7 +27111,7 @@ async function backlinksRoutes(app, opts) {
26695
27111
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
26696
27112
  );
26697
27113
  }
26698
- const existing = app.db.select().from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.release, release)).get();
27114
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.release, release)).get();
26699
27115
  const now = (/* @__PURE__ */ new Date()).toISOString();
26700
27116
  if (existing) {
26701
27117
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -26706,12 +27122,12 @@ async function backlinksRoutes(app, opts) {
26706
27122
  phaseDetail: null,
26707
27123
  error: null,
26708
27124
  updatedAt: now
26709
- }).where(eq27(ccReleaseSyncs.id, existing.id)).run();
27125
+ }).where(eq28(ccReleaseSyncs.id, existing.id)).run();
26710
27126
  opts.onReleaseSyncRequested(existing.id, release);
26711
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.id, existing.id)).get();
27127
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.id, existing.id)).get();
26712
27128
  return reply.status(200).send(mapSyncRow(refreshed));
26713
27129
  }
26714
- const id = crypto23.randomUUID();
27130
+ const id = crypto24.randomUUID();
26715
27131
  app.db.insert(ccReleaseSyncs).values({
26716
27132
  id,
26717
27133
  release,
@@ -26720,7 +27136,7 @@ async function backlinksRoutes(app, opts) {
26720
27136
  updatedAt: now
26721
27137
  }).run();
26722
27138
  opts.onReleaseSyncRequested(id, release);
26723
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq27(ccReleaseSyncs.id, id)).get();
27139
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq28(ccReleaseSyncs.id, id)).get();
26724
27140
  return reply.status(201).send(mapSyncRow(inserted));
26725
27141
  });
26726
27142
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -26768,7 +27184,7 @@ async function backlinksRoutes(app, opts) {
26768
27184
  throw validationError("Invalid release id");
26769
27185
  }
26770
27186
  const now = (/* @__PURE__ */ new Date()).toISOString();
26771
- const runId = crypto23.randomUUID();
27187
+ const runId = crypto24.randomUUID();
26772
27188
  app.db.insert(runs).values({
26773
27189
  id: runId,
26774
27190
  projectId: project.id,
@@ -26778,7 +27194,7 @@ async function backlinksRoutes(app, opts) {
26778
27194
  createdAt: now
26779
27195
  }).run();
26780
27196
  opts.onBacklinkExtractRequested(runId, project.id, release);
26781
- const run = app.db.select().from(runs).where(eq27(runs.id, runId)).get();
27197
+ const run = app.db.select().from(runs).where(eq28(runs.id, runId)).get();
26782
27198
  return reply.status(201).send(mapRunRow(run));
26783
27199
  });
26784
27200
  app.get(
@@ -26805,9 +27221,9 @@ async function backlinksRoutes(app, opts) {
26805
27221
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
26806
27222
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
26807
27223
  const baseDomainCondition = and23(
26808
- eq27(backlinkDomains.projectId, project.id),
26809
- eq27(backlinkDomains.source, source),
26810
- eq27(backlinkDomains.release, targetRelease)
27224
+ eq28(backlinkDomains.projectId, project.id),
27225
+ eq28(backlinkDomains.source, source),
27226
+ eq28(backlinkDomains.release, targetRelease)
26811
27227
  );
26812
27228
  const domainCondition = excludeCrawlers ? and23(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
26813
27229
  const totalRow = app.db.select({ count: sql11`count(*)` }).from(backlinkDomains).where(domainCondition).get();
@@ -26833,7 +27249,7 @@ async function backlinksRoutes(app, opts) {
26833
27249
  async (request, reply) => {
26834
27250
  const project = resolveProject(app.db, request.params.name);
26835
27251
  const source = parseSourceParam(request.query.source);
26836
- const rows = app.db.select().from(backlinkSummaries).where(and23(eq27(backlinkSummaries.projectId, project.id), eq27(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
27252
+ const rows = app.db.select().from(backlinkSummaries).where(and23(eq28(backlinkSummaries.projectId, project.id), eq28(backlinkSummaries.source, source))).orderBy(asc4(backlinkSummaries.queriedAt)).all();
26837
27253
  const response = rows.map((r) => ({
26838
27254
  release: r.release,
26839
27255
  totalLinkingDomains: r.totalLinkingDomains,
@@ -26870,7 +27286,7 @@ async function backlinksRoutes(app, opts) {
26870
27286
  );
26871
27287
  }
26872
27288
  const now = (/* @__PURE__ */ new Date()).toISOString();
26873
- const runId = crypto23.randomUUID();
27289
+ const runId = crypto24.randomUUID();
26874
27290
  app.db.insert(runs).values({
26875
27291
  id: runId,
26876
27292
  projectId: project.id,
@@ -26880,19 +27296,19 @@ async function backlinksRoutes(app, opts) {
26880
27296
  createdAt: now
26881
27297
  }).run();
26882
27298
  opts.onBingBacklinkSyncRequested(runId, project.id);
26883
- const run = app.db.select().from(runs).where(eq27(runs.id, runId)).get();
27299
+ const run = app.db.select().from(runs).where(eq28(runs.id, runId)).get();
26884
27300
  return reply.status(201).send(mapRunRow(run));
26885
27301
  }
26886
27302
  );
26887
27303
  }
26888
27304
 
26889
27305
  // ../api-routes/src/traffic.ts
26890
- import crypto25 from "crypto";
27306
+ import crypto26 from "crypto";
26891
27307
  import { Agent as UndiciAgent } from "undici";
26892
- import { and as and24, desc as desc15, eq as eq28, gte as gte4, lte as lte3, sql as sql12 } from "drizzle-orm";
27308
+ import { and as and24, desc as desc15, eq as eq29, gte as gte4, lte as lte3, sql as sql12 } from "drizzle-orm";
26893
27309
 
26894
27310
  // ../integration-cloud-run/src/auth.ts
26895
- import crypto24 from "crypto";
27311
+ import crypto25 from "crypto";
26896
27312
  var GOOGLE_TOKEN_URL3 = "https://oauth2.googleapis.com/token";
26897
27313
  var CLOUD_LOGGING_READ_SCOPE = "https://www.googleapis.com/auth/logging.read";
26898
27314
  var TOKEN_REQUEST_TIMEOUT_MS = 3e4;
@@ -26923,7 +27339,7 @@ function createServiceAccountJwt2(clientEmail, privateKey, scope) {
26923
27339
  const headerB64 = encode(header);
26924
27340
  const payloadB64 = encode(payload);
26925
27341
  const signingInput = `${headerB64}.${payloadB64}`;
26926
- const sign = crypto24.createSign("RSA-SHA256");
27342
+ const sign = crypto25.createSign("RSA-SHA256");
26927
27343
  sign.update(signingInput);
26928
27344
  const signature = sign.sign(privateKey, "base64url");
26929
27345
  return `${signingInput}.${signature}`;
@@ -30756,8 +31172,8 @@ async function runBackfillTask(options) {
30756
31172
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
30757
31173
  try {
30758
31174
  app.db.transaction((tx) => {
30759
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq28(runs.id, runId)).run();
30760
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq28(trafficSources.id, sourceRow.id)).run();
31175
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq29(runs.id, runId)).run();
31176
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq29(trafficSources.id, sourceRow.id)).run();
30761
31177
  });
30762
31178
  } catch {
30763
31179
  }
@@ -30772,7 +31188,7 @@ async function runBackfillTask(options) {
30772
31188
  if (allEvents.length === 0) {
30773
31189
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
30774
31190
  try {
30775
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq28(runs.id, runId)).run();
31191
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq29(runs.id, runId)).run();
30776
31192
  } catch {
30777
31193
  }
30778
31194
  return;
@@ -30795,28 +31211,28 @@ async function runBackfillTask(options) {
30795
31211
  app.db.transaction((tx) => {
30796
31212
  tx.delete(crawlerEventsHourly).where(
30797
31213
  and24(
30798
- eq28(crawlerEventsHourly.sourceId, sourceRow.id),
31214
+ eq29(crawlerEventsHourly.sourceId, sourceRow.id),
30799
31215
  gte4(crawlerEventsHourly.tsHour, windowStartIso),
30800
31216
  lte3(crawlerEventsHourly.tsHour, windowEndIso)
30801
31217
  )
30802
31218
  ).run();
30803
31219
  tx.delete(aiUserFetchEventsHourly).where(
30804
31220
  and24(
30805
- eq28(aiUserFetchEventsHourly.sourceId, sourceRow.id),
31221
+ eq29(aiUserFetchEventsHourly.sourceId, sourceRow.id),
30806
31222
  gte4(aiUserFetchEventsHourly.tsHour, windowStartIso),
30807
31223
  lte3(aiUserFetchEventsHourly.tsHour, windowEndIso)
30808
31224
  )
30809
31225
  ).run();
30810
31226
  tx.delete(aiReferralEventsHourly).where(
30811
31227
  and24(
30812
- eq28(aiReferralEventsHourly.sourceId, sourceRow.id),
31228
+ eq29(aiReferralEventsHourly.sourceId, sourceRow.id),
30813
31229
  gte4(aiReferralEventsHourly.tsHour, windowStartIso),
30814
31230
  lte3(aiReferralEventsHourly.tsHour, windowEndIso)
30815
31231
  )
30816
31232
  ).run();
30817
31233
  tx.delete(rawEventSamples).where(
30818
31234
  and24(
30819
- eq28(rawEventSamples.sourceId, sourceRow.id),
31235
+ eq29(rawEventSamples.sourceId, sourceRow.id),
30820
31236
  gte4(rawEventSamples.ts, windowStartIso),
30821
31237
  lte3(rawEventSamples.ts, windowEndIso)
30822
31238
  )
@@ -30883,7 +31299,7 @@ async function runBackfillTask(options) {
30883
31299
  }
30884
31300
  })();
30885
31301
  tx.insert(rawEventSamples).values({
30886
- id: crypto25.randomUUID(),
31302
+ id: crypto26.randomUUID(),
30887
31303
  projectId: project.id,
30888
31304
  sourceId: sourceRow.id,
30889
31305
  ts: sample.observedAt,
@@ -30907,8 +31323,8 @@ async function runBackfillTask(options) {
30907
31323
  lastError: null,
30908
31324
  lastEventIds: newRingBuffer,
30909
31325
  updatedAt: finishedAt
30910
- }).where(eq28(trafficSources.id, sourceRow.id)).run();
30911
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq28(runs.id, runId)).run();
31326
+ }).where(eq29(trafficSources.id, sourceRow.id)).run();
31327
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq29(runs.id, runId)).run();
30912
31328
  });
30913
31329
  } catch (e) {
30914
31330
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -30990,7 +31406,7 @@ async function trafficRoutes(app, opts) {
30990
31406
  createdAt: existing?.createdAt ?? now,
30991
31407
  updatedAt: now
30992
31408
  });
30993
- const activeSource = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
31409
+ const activeSource = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
30994
31410
  const config = {
30995
31411
  gcpProjectId,
30996
31412
  serviceName: serviceName ?? null,
@@ -31006,10 +31422,10 @@ async function trafficRoutes(app, opts) {
31006
31422
  lastError: null,
31007
31423
  configJson: config,
31008
31424
  updatedAt: now
31009
- }).where(eq28(trafficSources.id, activeSource.id)).run();
31010
- sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, activeSource.id)).get();
31425
+ }).where(eq29(trafficSources.id, activeSource.id)).run();
31426
+ sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, activeSource.id)).get();
31011
31427
  } else {
31012
- const newId = crypto25.randomUUID();
31428
+ const newId = crypto26.randomUUID();
31013
31429
  app.db.insert(trafficSources).values({
31014
31430
  id: newId,
31015
31431
  projectId: project.id,
@@ -31024,7 +31440,7 @@ async function trafficRoutes(app, opts) {
31024
31440
  createdAt: now,
31025
31441
  updatedAt: now
31026
31442
  }).run();
31027
- sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
31443
+ sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, newId)).get();
31028
31444
  }
31029
31445
  writeAuditLog(app.db, {
31030
31446
  projectId: project.id,
@@ -31076,7 +31492,7 @@ async function trafficRoutes(app, opts) {
31076
31492
  createdAt: existing?.createdAt ?? now,
31077
31493
  updatedAt: now
31078
31494
  });
31079
- const activeSource = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
31495
+ const activeSource = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
31080
31496
  const config = { baseUrl, username };
31081
31497
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
31082
31498
  let sourceRow;
@@ -31087,10 +31503,10 @@ async function trafficRoutes(app, opts) {
31087
31503
  lastError: null,
31088
31504
  configJson: config,
31089
31505
  updatedAt: now
31090
- }).where(eq28(trafficSources.id, activeSource.id)).run();
31091
- sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, activeSource.id)).get();
31506
+ }).where(eq29(trafficSources.id, activeSource.id)).run();
31507
+ sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, activeSource.id)).get();
31092
31508
  } else {
31093
- const newId = crypto25.randomUUID();
31509
+ const newId = crypto26.randomUUID();
31094
31510
  app.db.insert(trafficSources).values({
31095
31511
  id: newId,
31096
31512
  projectId: project.id,
@@ -31105,7 +31521,7 @@ async function trafficRoutes(app, opts) {
31105
31521
  createdAt: now,
31106
31522
  updatedAt: now
31107
31523
  }).run();
31108
- sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
31524
+ sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, newId)).get();
31109
31525
  }
31110
31526
  writeAuditLog(app.db, {
31111
31527
  projectId: project.id,
@@ -31159,7 +31575,7 @@ async function trafficRoutes(app, opts) {
31159
31575
  createdAt: existing?.createdAt ?? now,
31160
31576
  updatedAt: now
31161
31577
  });
31162
- const activeSource = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
31578
+ const activeSource = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
31163
31579
  const config = { projectId, teamId, environment };
31164
31580
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
31165
31581
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -31171,10 +31587,10 @@ async function trafficRoutes(app, opts) {
31171
31587
  lastError: null,
31172
31588
  configJson: config,
31173
31589
  updatedAt: now
31174
- }).where(eq28(trafficSources.id, activeSource.id)).run();
31175
- row = tx.select().from(trafficSources).where(eq28(trafficSources.id, activeSource.id)).get();
31590
+ }).where(eq29(trafficSources.id, activeSource.id)).run();
31591
+ row = tx.select().from(trafficSources).where(eq29(trafficSources.id, activeSource.id)).get();
31176
31592
  } else {
31177
- const newId = crypto25.randomUUID();
31593
+ const newId = crypto26.randomUUID();
31178
31594
  tx.insert(trafficSources).values({
31179
31595
  id: newId,
31180
31596
  projectId: project.id,
@@ -31197,18 +31613,18 @@ async function trafficRoutes(app, opts) {
31197
31613
  createdAt: now,
31198
31614
  updatedAt: now
31199
31615
  }).run();
31200
- row = tx.select().from(trafficSources).where(eq28(trafficSources.id, newId)).get();
31616
+ row = tx.select().from(trafficSources).where(eq29(trafficSources.id, newId)).get();
31201
31617
  }
31202
31618
  const existingSchedule = tx.select().from(schedules).where(
31203
31619
  and24(
31204
- eq28(schedules.projectId, project.id),
31205
- eq28(schedules.kind, SchedulableRunKinds["traffic-sync"])
31620
+ eq29(schedules.projectId, project.id),
31621
+ eq29(schedules.kind, SchedulableRunKinds["traffic-sync"])
31206
31622
  )
31207
31623
  ).get();
31208
31624
  let created = false;
31209
31625
  if (!existingSchedule) {
31210
31626
  tx.insert(schedules).values({
31211
- id: crypto25.randomUUID(),
31627
+ id: crypto26.randomUUID(),
31212
31628
  projectId: project.id,
31213
31629
  kind: SchedulableRunKinds["traffic-sync"],
31214
31630
  cronExpr: DEFAULT_TRAFFIC_SYNC_CRON,
@@ -31251,7 +31667,7 @@ async function trafficRoutes(app, opts) {
31251
31667
  });
31252
31668
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
31253
31669
  const project = resolveProject(app.db, request.params.name);
31254
- const sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, request.params.id)).get();
31670
+ const sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, request.params.id)).get();
31255
31671
  if (!sourceRow || sourceRow.projectId !== project.id) {
31256
31672
  throw notFound("Traffic source", request.params.id);
31257
31673
  }
@@ -31263,7 +31679,7 @@ async function trafficRoutes(app, opts) {
31263
31679
  const windowEnd = /* @__PURE__ */ new Date();
31264
31680
  const startedAt = windowEnd.toISOString();
31265
31681
  const syncStartedAtMs = windowEnd.getTime();
31266
- const runId = crypto25.randomUUID();
31682
+ const runId = crypto26.randomUUID();
31267
31683
  app.db.insert(runs).values({
31268
31684
  id: runId,
31269
31685
  projectId: project.id,
@@ -31277,8 +31693,8 @@ async function trafficRoutes(app, opts) {
31277
31693
  const markFailed = (msg, errorCode) => {
31278
31694
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
31279
31695
  app.db.transaction((tx) => {
31280
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq28(runs.id, runId)).run();
31281
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq28(trafficSources.id, sourceRow.id)).run();
31696
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq29(runs.id, runId)).run();
31697
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq29(trafficSources.id, sourceRow.id)).run();
31282
31698
  });
31283
31699
  try {
31284
31700
  opts.onTrafficSynced?.({
@@ -31359,7 +31775,7 @@ async function trafficRoutes(app, opts) {
31359
31775
  }
31360
31776
  const credential = credentialStore.getConnection(project.name);
31361
31777
  if (!credential) {
31362
- app.db.delete(runs).where(eq28(runs.id, runId)).run();
31778
+ app.db.delete(runs).where(eq29(runs.id, runId)).run();
31363
31779
  throw validationError(
31364
31780
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
31365
31781
  );
@@ -31408,12 +31824,12 @@ async function trafficRoutes(app, opts) {
31408
31824
  auditAction = "traffic.vercel.synced";
31409
31825
  const credentialStore = opts.vercelTrafficCredentialStore;
31410
31826
  if (!credentialStore) {
31411
- app.db.delete(runs).where(eq28(runs.id, runId)).run();
31827
+ app.db.delete(runs).where(eq29(runs.id, runId)).run();
31412
31828
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
31413
31829
  }
31414
31830
  const credential = credentialStore.getConnection(project.name);
31415
31831
  if (!credential) {
31416
- app.db.delete(runs).where(eq28(runs.id, runId)).run();
31832
+ app.db.delete(runs).where(eq29(runs.id, runId)).run();
31417
31833
  throw validationError(
31418
31834
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
31419
31835
  );
@@ -31513,7 +31929,7 @@ async function trafficRoutes(app, opts) {
31513
31929
  let aiReferralHitsCount = 0;
31514
31930
  let unknownHitsCount = 0;
31515
31931
  app.db.transaction((tx) => {
31516
- const latestRow = tx.select().from(trafficSources).where(eq28(trafficSources.id, sourceRow.id)).get();
31932
+ const latestRow = tx.select().from(trafficSources).where(eq29(trafficSources.id, sourceRow.id)).get();
31517
31933
  const previousIds = latestRow.lastEventIds ?? [];
31518
31934
  const seenEventIds = new Set(previousIds);
31519
31935
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -31653,7 +32069,7 @@ async function trafficRoutes(app, opts) {
31653
32069
  }
31654
32070
  })();
31655
32071
  tx.insert(rawEventSamples).values({
31656
- id: crypto25.randomUUID(),
32072
+ id: crypto26.randomUUID(),
31657
32073
  projectId: project.id,
31658
32074
  sourceId: sourceRow.id,
31659
32075
  ts: sample.observedAt,
@@ -31688,8 +32104,8 @@ async function trafficRoutes(app, opts) {
31688
32104
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
31689
32105
  sourceUpdate.lastCursor = nextCursor ?? null;
31690
32106
  }
31691
- tx.update(trafficSources).set(sourceUpdate).where(eq28(trafficSources.id, sourceRow.id)).run();
31692
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq28(runs.id, runId)).run();
32107
+ tx.update(trafficSources).set(sourceUpdate).where(eq29(trafficSources.id, sourceRow.id)).run();
32108
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq29(runs.id, runId)).run();
31693
32109
  });
31694
32110
  writeAuditLog(app.db, {
31695
32111
  projectId: project.id,
@@ -31741,7 +32157,7 @@ async function trafficRoutes(app, opts) {
31741
32157
  });
31742
32158
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
31743
32159
  const project = resolveProject(app.db, request.params.name);
31744
- const sourceRow = app.db.select().from(trafficSources).where(eq28(trafficSources.id, request.params.id)).get();
32160
+ const sourceRow = app.db.select().from(trafficSources).where(eq29(trafficSources.id, request.params.id)).get();
31745
32161
  if (!sourceRow || sourceRow.projectId !== project.id) {
31746
32162
  throw notFound("Traffic source", request.params.id);
31747
32163
  }
@@ -31891,7 +32307,7 @@ async function trafficRoutes(app, opts) {
31891
32307
  };
31892
32308
  }
31893
32309
  const startedAt = windowEnd.toISOString();
31894
- const runId = crypto25.randomUUID();
32310
+ const runId = crypto26.randomUUID();
31895
32311
  app.db.insert(runs).values({
31896
32312
  id: runId,
31897
32313
  projectId: project.id,
@@ -31930,7 +32346,7 @@ async function trafficRoutes(app, opts) {
31930
32346
  hits: sql12`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
31931
32347
  }).from(crawlerEventsHourly).where(
31932
32348
  and24(
31933
- eq28(crawlerEventsHourly.sourceId, row.id),
32349
+ eq29(crawlerEventsHourly.sourceId, row.id),
31934
32350
  gte4(crawlerEventsHourly.tsHour, since)
31935
32351
  )
31936
32352
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
@@ -31940,27 +32356,27 @@ async function trafficRoutes(app, opts) {
31940
32356
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
31941
32357
  const aiUserFetchTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
31942
32358
  and24(
31943
- eq28(aiUserFetchEventsHourly.sourceId, row.id),
32359
+ eq29(aiUserFetchEventsHourly.sourceId, row.id),
31944
32360
  gte4(aiUserFetchEventsHourly.tsHour, since)
31945
32361
  )
31946
32362
  ).get();
31947
32363
  const aiTotals = app.db.select({ total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
31948
32364
  and24(
31949
- eq28(aiReferralEventsHourly.sourceId, row.id),
32365
+ eq29(aiReferralEventsHourly.sourceId, row.id),
31950
32366
  gte4(aiReferralEventsHourly.tsHour, since)
31951
32367
  )
31952
32368
  ).get();
31953
32369
  const sampleTotals = app.db.select({ total: sql12`COUNT(*)` }).from(rawEventSamples).where(
31954
32370
  and24(
31955
- eq28(rawEventSamples.sourceId, row.id),
32371
+ eq29(rawEventSamples.sourceId, row.id),
31956
32372
  gte4(rawEventSamples.ts, since)
31957
32373
  )
31958
32374
  ).get();
31959
32375
  const latestRun = app.db.select().from(runs).where(
31960
32376
  and24(
31961
- eq28(runs.projectId, projectId),
31962
- eq28(runs.kind, RunKinds["traffic-sync"]),
31963
- eq28(runs.sourceId, row.id)
32377
+ eq29(runs.projectId, projectId),
32378
+ eq29(runs.kind, RunKinds["traffic-sync"]),
32379
+ eq29(runs.sourceId, row.id)
31964
32380
  )
31965
32381
  ).orderBy(desc15(runs.startedAt)).limit(1).get();
31966
32382
  return {
@@ -31991,7 +32407,7 @@ async function trafficRoutes(app, opts) {
31991
32407
  "`advanceToNow` must be `true`. There is no implicit reset."
31992
32408
  );
31993
32409
  }
31994
- const sourceRow = app.db.select().from(trafficSources).where(and24(eq28(trafficSources.projectId, project.id), eq28(trafficSources.id, request.params.id))).get();
32410
+ const sourceRow = app.db.select().from(trafficSources).where(and24(eq29(trafficSources.projectId, project.id), eq29(trafficSources.id, request.params.id))).get();
31995
32411
  if (!sourceRow) {
31996
32412
  throw notFound("traffic source", request.params.id);
31997
32413
  }
@@ -32008,7 +32424,7 @@ async function trafficRoutes(app, opts) {
32008
32424
  status: TrafficSourceStatuses.connected,
32009
32425
  lastError: null,
32010
32426
  updatedAt: now
32011
- }).where(eq28(trafficSources.id, sourceRow.id)).run();
32427
+ }).where(eq29(trafficSources.id, sourceRow.id)).run();
32012
32428
  writeAuditLog(tx, auditFromRequest(request, {
32013
32429
  projectId: project.id,
32014
32430
  actor: "api",
@@ -32016,20 +32432,20 @@ async function trafficRoutes(app, opts) {
32016
32432
  entityType: "traffic_source",
32017
32433
  entityId: sourceRow.id
32018
32434
  }));
32019
- updatedRow = tx.select().from(trafficSources).where(eq28(trafficSources.id, sourceRow.id)).get();
32435
+ updatedRow = tx.select().from(trafficSources).where(eq29(trafficSources.id, sourceRow.id)).get();
32020
32436
  });
32021
32437
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
32022
32438
  });
32023
32439
  app.get("/projects/:name/traffic/sources", async (request) => {
32024
32440
  const project = resolveProject(app.db, request.params.name);
32025
- const rows = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32441
+ const rows = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32026
32442
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
32027
32443
  const response = { sources };
32028
32444
  return response;
32029
32445
  });
32030
32446
  app.get("/projects/:name/traffic/status", async (request) => {
32031
32447
  const project = resolveProject(app.db, request.params.name);
32032
- const rows = app.db.select().from(trafficSources).where(eq28(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32448
+ const rows = app.db.select().from(trafficSources).where(eq29(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
32033
32449
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
32034
32450
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
32035
32451
  const response = { sources };
@@ -32039,7 +32455,7 @@ async function trafficRoutes(app, opts) {
32039
32455
  "/projects/:name/traffic/sources/:id",
32040
32456
  async (request) => {
32041
32457
  const project = resolveProject(app.db, request.params.name);
32042
- const row = app.db.select().from(trafficSources).where(eq28(trafficSources.id, request.params.id)).get();
32458
+ const row = app.db.select().from(trafficSources).where(eq29(trafficSources.id, request.params.id)).get();
32043
32459
  if (!row || row.projectId !== project.id) {
32044
32460
  throw notFound("Traffic source", request.params.id);
32045
32461
  }
@@ -32091,11 +32507,11 @@ async function trafficRoutes(app, opts) {
32091
32507
  let aiReferralCounts = aiReferralClassCounts(0, 0, 0);
32092
32508
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
32093
32509
  const crawlerFilters = [
32094
- eq28(crawlerEventsHourly.projectId, project.id),
32510
+ eq29(crawlerEventsHourly.projectId, project.id),
32095
32511
  gte4(crawlerEventsHourly.tsHour, sinceIso),
32096
32512
  lte3(crawlerEventsHourly.tsHour, untilIso)
32097
32513
  ];
32098
- if (sourceIdParam) crawlerFilters.push(eq28(crawlerEventsHourly.sourceId, sourceIdParam));
32514
+ if (sourceIdParam) crawlerFilters.push(eq29(crawlerEventsHourly.sourceId, sourceIdParam));
32099
32515
  const crawlerWhere = and24(...crawlerFilters);
32100
32516
  const pathTotals = app.db.select({
32101
32517
  pathNormalized: crawlerEventsHourly.pathNormalized,
@@ -32123,11 +32539,11 @@ async function trafficRoutes(app, opts) {
32123
32539
  }
32124
32540
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
32125
32541
  const userFetchFilters = [
32126
- eq28(aiUserFetchEventsHourly.projectId, project.id),
32542
+ eq29(aiUserFetchEventsHourly.projectId, project.id),
32127
32543
  gte4(aiUserFetchEventsHourly.tsHour, sinceIso),
32128
32544
  lte3(aiUserFetchEventsHourly.tsHour, untilIso)
32129
32545
  ];
32130
- if (sourceIdParam) userFetchFilters.push(eq28(aiUserFetchEventsHourly.sourceId, sourceIdParam));
32546
+ if (sourceIdParam) userFetchFilters.push(eq29(aiUserFetchEventsHourly.sourceId, sourceIdParam));
32131
32547
  const userFetchWhere = and24(...userFetchFilters);
32132
32548
  const total = app.db.select({ total: sql12`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
32133
32549
  aiUserFetchTotal = Number(total?.total ?? 0);
@@ -32148,11 +32564,11 @@ async function trafficRoutes(app, opts) {
32148
32564
  }
32149
32565
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
32150
32566
  const aiFilters = [
32151
- eq28(aiReferralEventsHourly.projectId, project.id),
32567
+ eq29(aiReferralEventsHourly.projectId, project.id),
32152
32568
  gte4(aiReferralEventsHourly.tsHour, sinceIso),
32153
32569
  lte3(aiReferralEventsHourly.tsHour, untilIso)
32154
32570
  ];
32155
- if (sourceIdParam) aiFilters.push(eq28(aiReferralEventsHourly.sourceId, sourceIdParam));
32571
+ if (sourceIdParam) aiFilters.push(eq29(aiReferralEventsHourly.sourceId, sourceIdParam));
32156
32572
  const aiWhere = and24(...aiFilters);
32157
32573
  const total = app.db.select({
32158
32574
  total: sql12`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
@@ -32207,7 +32623,7 @@ async function trafficRoutes(app, opts) {
32207
32623
  }
32208
32624
 
32209
32625
  // ../api-routes/src/doctor/checks/agent.ts
32210
- import crypto26 from "crypto";
32626
+ import crypto27 from "crypto";
32211
32627
  import fs6 from "fs";
32212
32628
  import path7 from "path";
32213
32629
  var REQUIRED_SKILLS = ["canonry", "aero"];
@@ -32360,7 +32776,7 @@ function isInstalled(dir) {
32360
32776
  }
32361
32777
  function hashInstalledFile(filePath) {
32362
32778
  try {
32363
- return crypto26.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
32779
+ return crypto27.createHash("sha256").update(fs6.readFileSync(filePath)).digest("hex");
32364
32780
  } catch {
32365
32781
  return void 0;
32366
32782
  }
@@ -32375,7 +32791,7 @@ function readInstalledManifest(skillDir) {
32375
32791
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
32376
32792
 
32377
32793
  // ../api-routes/src/doctor/checks/backlinks.ts
32378
- import { and as and25, eq as eq29 } from "drizzle-orm";
32794
+ import { and as and25, eq as eq30 } from "drizzle-orm";
32379
32795
  function skippedNoProject() {
32380
32796
  return {
32381
32797
  status: CheckStatuses.skipped,
@@ -32391,8 +32807,8 @@ var BACKLINKS_CHECKS = [
32391
32807
  title: "Backlinks source connected",
32392
32808
  run: (ctx) => {
32393
32809
  if (!ctx.project) return skippedNoProject();
32394
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq29(projects.id, ctx.project.id)).get();
32395
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq29(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
32810
+ const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq30(projects.id, ctx.project.id)).get();
32811
+ const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq30(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
32396
32812
  const ccConnected = projectRow?.autoExtract === true && !!readySync;
32397
32813
  const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
32398
32814
  const connected = [];
@@ -32408,8 +32824,8 @@ var BACKLINKS_CHECKS = [
32408
32824
  };
32409
32825
  }
32410
32826
  const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and25(
32411
- eq29(backlinkSummaries.projectId, ctx.project.id),
32412
- eq29(backlinkSummaries.source, BacklinkSources.commoncrawl)
32827
+ eq30(backlinkSummaries.projectId, ctx.project.id),
32828
+ eq30(backlinkSummaries.source, BacklinkSources.commoncrawl)
32413
32829
  )).limit(1).get() : false;
32414
32830
  return {
32415
32831
  status: CheckStatuses.ok,
@@ -32569,7 +32985,7 @@ var BING_AUTH_CHECKS = [
32569
32985
  ];
32570
32986
 
32571
32987
  // ../api-routes/src/doctor/checks/content.ts
32572
- import { eq as eq30 } from "drizzle-orm";
32988
+ import { eq as eq31 } from "drizzle-orm";
32573
32989
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
32574
32990
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
32575
32991
  function skippedNoProject2() {
@@ -32582,7 +32998,7 @@ function skippedNoProject2() {
32582
32998
  }
32583
32999
  function loadProject(ctx) {
32584
33000
  if (!ctx.project) return null;
32585
- return ctx.db.select().from(projects).where(eq30(projects.id, ctx.project.id)).get() ?? null;
33001
+ return ctx.db.select().from(projects).where(eq31(projects.id, ctx.project.id)).get() ?? null;
32586
33002
  }
32587
33003
  function percent(value) {
32588
33004
  return Math.round(value * 100);
@@ -32674,7 +33090,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
32674
33090
  );
32675
33091
 
32676
33092
  // ../api-routes/src/doctor/checks/ads.ts
32677
- import { eq as eq31 } from "drizzle-orm";
33093
+ import { eq as eq32 } from "drizzle-orm";
32678
33094
  var RECENT_SYNC_WARN_DAYS = 7;
32679
33095
  var RECENT_SYNC_FAIL_DAYS = 30;
32680
33096
  var adsConnectionCheck = {
@@ -32691,7 +33107,7 @@ var adsConnectionCheck = {
32691
33107
  remediation: null
32692
33108
  };
32693
33109
  }
32694
- const row = ctx.db.select().from(adsConnections).where(eq31(adsConnections.projectId, ctx.project.id)).get();
33110
+ const row = ctx.db.select().from(adsConnections).where(eq32(adsConnections.projectId, ctx.project.id)).get();
32695
33111
  if (!row) {
32696
33112
  return {
32697
33113
  status: CheckStatuses.skipped,
@@ -32741,7 +33157,7 @@ var adsRecentSyncCheck = {
32741
33157
  remediation: null
32742
33158
  };
32743
33159
  }
32744
- const row = ctx.db.select().from(adsConnections).where(eq31(adsConnections.projectId, ctx.project.id)).get();
33160
+ const row = ctx.db.select().from(adsConnections).where(eq32(adsConnections.projectId, ctx.project.id)).get();
32745
33161
  if (!row) {
32746
33162
  return {
32747
33163
  status: CheckStatuses.skipped,
@@ -32932,7 +33348,7 @@ var ga4ConnectionCheck = {
32932
33348
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
32933
33349
 
32934
33350
  // ../api-routes/src/doctor/checks/gbp-auth.ts
32935
- import { and as and26, eq as eq32 } from "drizzle-orm";
33351
+ import { and as and26, eq as eq33 } from "drizzle-orm";
32936
33352
  var RECENT_SYNC_WARN_DAYS2 = 7;
32937
33353
  var RECENT_SYNC_FAIL_DAYS2 = 30;
32938
33354
  function skippedNoProject3() {
@@ -33165,7 +33581,7 @@ var recentSyncCheck = {
33165
33581
  title: "GBP recent sync",
33166
33582
  run: (ctx) => {
33167
33583
  if (!ctx.project) return skippedNoProject3();
33168
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and26(eq32(gbpLocations.projectId, ctx.project.id), eq32(gbpLocations.selected, true))).all();
33584
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and26(eq33(gbpLocations.projectId, ctx.project.id), eq33(gbpLocations.selected, true))).all();
33169
33585
  if (selected.length === 0) {
33170
33586
  return {
33171
33587
  status: CheckStatuses.skipped,
@@ -33225,7 +33641,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
33225
33641
  );
33226
33642
 
33227
33643
  // ../api-routes/src/doctor/checks/places.ts
33228
- import { eq as eq33 } from "drizzle-orm";
33644
+ import { eq as eq34 } from "drizzle-orm";
33229
33645
  var apiKeyCheck = {
33230
33646
  id: "gbp.places.api-key",
33231
33647
  category: CheckCategories.auth,
@@ -33270,7 +33686,7 @@ var apiKeyCheck = {
33270
33686
  details: { tier: cfg.tier }
33271
33687
  };
33272
33688
  }
33273
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq33(gbpLocations.projectId, ctx.project.id)).all();
33689
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq34(gbpLocations.projectId, ctx.project.id)).all();
33274
33690
  const selected = rows.filter((r) => r.selected);
33275
33691
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
33276
33692
  const details = {
@@ -33767,7 +34183,7 @@ var RUNTIME_STATE_CHECKS = [
33767
34183
  ];
33768
34184
 
33769
34185
  // ../api-routes/src/doctor/checks/traffic-source.ts
33770
- import { and as and27, eq as eq34, gte as gte5, ne as ne4, sql as sql13 } from "drizzle-orm";
34186
+ import { and as and27, eq as eq35, gte as gte5, ne as ne4, sql as sql13 } from "drizzle-orm";
33771
34187
  var RECENT_DATA_WARN_DAYS = 7;
33772
34188
  var RECENT_DATA_FAIL_DAYS = 30;
33773
34189
  function skippedNoProject5() {
@@ -33782,7 +34198,7 @@ function loadProbes(ctx) {
33782
34198
  if (!ctx.project) return [];
33783
34199
  const rows = ctx.db.select().from(trafficSources).where(
33784
34200
  and27(
33785
- eq34(trafficSources.projectId, ctx.project.id),
34201
+ eq35(trafficSources.projectId, ctx.project.id),
33786
34202
  ne4(trafficSources.status, TrafficSourceStatuses.archived)
33787
34203
  )
33788
34204
  ).all();
@@ -33863,7 +34279,7 @@ var recentDataCheck = {
33863
34279
  const recentCrawlers = Number(
33864
34280
  ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
33865
34281
  and27(
33866
- eq34(crawlerEventsHourly.projectId, ctx.project.id),
34282
+ eq35(crawlerEventsHourly.projectId, ctx.project.id),
33867
34283
  gte5(crawlerEventsHourly.tsHour, warnCutoff)
33868
34284
  )
33869
34285
  ).get()?.total ?? 0
@@ -33871,7 +34287,7 @@ var recentDataCheck = {
33871
34287
  const recentReferrals = Number(
33872
34288
  ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
33873
34289
  and27(
33874
- eq34(aiReferralEventsHourly.projectId, ctx.project.id),
34290
+ eq35(aiReferralEventsHourly.projectId, ctx.project.id),
33875
34291
  gte5(aiReferralEventsHourly.tsHour, warnCutoff)
33876
34292
  )
33877
34293
  ).get()?.total ?? 0
@@ -33887,7 +34303,7 @@ var recentDataCheck = {
33887
34303
  const olderCrawlers = Number(
33888
34304
  ctx.db.select({ total: sql13`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
33889
34305
  and27(
33890
- eq34(crawlerEventsHourly.projectId, ctx.project.id),
34306
+ eq35(crawlerEventsHourly.projectId, ctx.project.id),
33891
34307
  gte5(crawlerEventsHourly.tsHour, failCutoff)
33892
34308
  )
33893
34309
  ).get()?.total ?? 0
@@ -33895,7 +34311,7 @@ var recentDataCheck = {
33895
34311
  const olderReferrals = Number(
33896
34312
  ctx.db.select({ total: sql13`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
33897
34313
  and27(
33898
- eq34(aiReferralEventsHourly.projectId, ctx.project.id),
34314
+ eq35(aiReferralEventsHourly.projectId, ctx.project.id),
33899
34315
  gte5(aiReferralEventsHourly.tsHour, failCutoff)
33900
34316
  )
33901
34317
  ).get()?.total ?? 0
@@ -34310,8 +34726,8 @@ async function doctorRoutes(app, opts) {
34310
34726
  }
34311
34727
 
34312
34728
  // ../api-routes/src/discovery/routes.ts
34313
- import crypto27 from "crypto";
34314
- import { and as and28, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12, isNull, or as or5 } from "drizzle-orm";
34729
+ import crypto28 from "crypto";
34730
+ import { and as and28, desc as desc16, eq as eq36, gte as gte6, inArray as inArray13, isNull, or as or5 } from "drizzle-orm";
34315
34731
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
34316
34732
  async function discoveryRoutes(app, opts) {
34317
34733
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -34346,12 +34762,12 @@ async function discoveryRoutes(app, opts) {
34346
34762
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
34347
34763
  const decision = app.db.transaction((tx) => {
34348
34764
  const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and28(
34349
- eq35(discoverySessions.projectId, project.id),
34350
- eq35(discoverySessions.icpDescription, icpDescription),
34765
+ eq36(discoverySessions.projectId, project.id),
34766
+ eq36(discoverySessions.icpDescription, icpDescription),
34351
34767
  // Buyer is part of session identity: it changes the seed prompt's
34352
34768
  // semantics, so a request with a different (or no) buyer must start
34353
34769
  // its own session, never adopt another buyer's probes.
34354
- parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq35(discoverySessions.buyerDescription, parsed.data.buyerDescription),
34770
+ parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq36(discoverySessions.buyerDescription, parsed.data.buyerDescription),
34355
34771
  // Locations are identity too: a different service-area subset seeds
34356
34772
  // and probes a different geo, so it must never reuse another geo's
34357
34773
  // session. resolveLocations is deterministic (project-config order),
@@ -34361,12 +34777,12 @@ async function discoveryRoutes(app, opts) {
34361
34777
  // locations a NULL row's subset is unknowable, so it conservatively
34362
34778
  // never reuses — a one-time, bounded (2h window) non-reuse after
34363
34779
  // upgrade, never a wrong reuse.
34364
- locations.length === 0 ? or5(isNull(discoverySessions.locations), eq35(discoverySessions.locations, locations)) : eq35(discoverySessions.locations, locations),
34780
+ locations.length === 0 ? or5(isNull(discoverySessions.locations), eq36(discoverySessions.locations, locations)) : eq36(discoverySessions.locations, locations),
34365
34781
  // Seed provider set is identity: a different phrasing distribution
34366
34782
  // must never reuse another set's session. Null (= Gemini-only
34367
34783
  // default, including explicit ['gemini']) matches legacy rows.
34368
- seedProviders == null ? isNull(discoverySessions.seedProviders) : eq35(discoverySessions.seedProviders, seedProviders),
34369
- inArray12(discoverySessions.status, [
34784
+ seedProviders == null ? isNull(discoverySessions.seedProviders) : eq36(discoverySessions.seedProviders, seedProviders),
34785
+ inArray13(discoverySessions.status, [
34370
34786
  DiscoverySessionStatuses.queued,
34371
34787
  DiscoverySessionStatuses.seeding,
34372
34788
  DiscoverySessionStatuses.probing
@@ -34376,8 +34792,8 @@ async function discoveryRoutes(app, opts) {
34376
34792
  if (existing && existing.runId) {
34377
34793
  return { reused: true, sessionId: existing.id, runId: existing.runId };
34378
34794
  }
34379
- const sessionId = crypto27.randomUUID();
34380
- const runId = crypto27.randomUUID();
34795
+ const sessionId = crypto28.randomUUID();
34796
+ const runId = crypto28.randomUUID();
34381
34797
  tx.insert(discoverySessions).values({
34382
34798
  id: sessionId,
34383
34799
  projectId: project.id,
@@ -34441,7 +34857,7 @@ async function discoveryRoutes(app, opts) {
34441
34857
  const project = resolveProject(app.db, request.params.name);
34442
34858
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
34443
34859
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
34444
- const rows = app.db.select().from(discoverySessions).where(eq35(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
34860
+ const rows = app.db.select().from(discoverySessions).where(eq36(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
34445
34861
  return reply.send(rows.map(serializeSession));
34446
34862
  }
34447
34863
  );
@@ -34449,11 +34865,11 @@ async function discoveryRoutes(app, opts) {
34449
34865
  "/projects/:name/discover/sessions/:id",
34450
34866
  async (request, reply) => {
34451
34867
  const project = resolveProject(app.db, request.params.name);
34452
- const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
34868
+ const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
34453
34869
  if (!session || session.projectId !== project.id) {
34454
34870
  throw notFound("Discovery session", request.params.id);
34455
34871
  }
34456
- const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
34872
+ const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
34457
34873
  const detail = {
34458
34874
  ...serializeSession(session),
34459
34875
  probes: probeRows.map(serializeProbe)
@@ -34465,7 +34881,7 @@ async function discoveryRoutes(app, opts) {
34465
34881
  "/projects/:name/discover/sessions/:id/harvest",
34466
34882
  async (request, reply) => {
34467
34883
  const project = resolveProject(app.db, request.params.name);
34468
- const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
34884
+ const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
34469
34885
  if (!session || session.projectId !== project.id) {
34470
34886
  throw notFound("Discovery session", request.params.id);
34471
34887
  }
@@ -34473,7 +34889,7 @@ async function discoveryRoutes(app, opts) {
34473
34889
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
34474
34890
  const applyAnchor = request.query.anchor !== "false";
34475
34891
  const provider = session.seedProvider ?? "gemini";
34476
- const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
34892
+ const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
34477
34893
  const extract = opts.harvestSearchQueries;
34478
34894
  const probesWithQueries = probeRows.map((row) => {
34479
34895
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -34484,7 +34900,7 @@ async function discoveryRoutes(app, opts) {
34484
34900
  return { searchQueries: [] };
34485
34901
  }
34486
34902
  });
34487
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq35(queries.projectId, project.id)).all().map((r) => r.query);
34903
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq36(queries.projectId, project.id)).all().map((r) => r.query);
34488
34904
  const anchorTerms = buildHarvestAnchorTerms(
34489
34905
  [session.icpDescription ?? "", ...trackedQueries],
34490
34906
  effectiveDomains(project)
@@ -34531,12 +34947,12 @@ async function discoveryRoutes(app, opts) {
34531
34947
  "/projects/:name/discover/sessions/:id/promote",
34532
34948
  async (request, reply) => {
34533
34949
  const project = resolveProject(app.db, request.params.name);
34534
- const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
34950
+ const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
34535
34951
  if (!session || session.projectId !== project.id) {
34536
34952
  throw notFound("Discovery session", request.params.id);
34537
34953
  }
34538
- const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
34539
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
34954
+ const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
34955
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
34540
34956
  const seenCompetitors = new Set(existingCompetitors);
34541
34957
  const cited = /* @__PURE__ */ new Set();
34542
34958
  const aspirational = /* @__PURE__ */ new Set();
@@ -34565,7 +34981,7 @@ async function discoveryRoutes(app, opts) {
34565
34981
  );
34566
34982
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
34567
34983
  const project = resolveProject(app.db, request.params.name);
34568
- const session = app.db.select().from(discoverySessions).where(eq35(discoverySessions.id, request.params.id)).get();
34984
+ const session = app.db.select().from(discoverySessions).where(eq36(discoverySessions.id, request.params.id)).get();
34569
34985
  if (!session || session.projectId !== project.id) {
34570
34986
  throw notFound("Discovery session", request.params.id);
34571
34987
  }
@@ -34588,7 +35004,7 @@ async function discoveryRoutes(app, opts) {
34588
35004
  const bucketSet = new Set(buckets);
34589
35005
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
34590
35006
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
34591
- const probeRows = app.db.select().from(discoveryProbes).where(eq35(discoveryProbes.sessionId, session.id)).all();
35007
+ const probeRows = app.db.select().from(discoveryProbes).where(eq36(discoveryProbes.sessionId, session.id)).all();
34592
35008
  const candidateQueries = /* @__PURE__ */ new Set();
34593
35009
  for (const probe of probeRows) {
34594
35010
  if (!probe.bucket) continue;
@@ -34596,7 +35012,7 @@ async function discoveryRoutes(app, opts) {
34596
35012
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
34597
35013
  }
34598
35014
  const existingQueries = new Set(
34599
- app.db.select({ query: queries.query }).from(queries).where(eq35(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
35015
+ app.db.select({ query: queries.query }).from(queries).where(eq36(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
34600
35016
  );
34601
35017
  const promotedQueries = [];
34602
35018
  const skippedQueries = [];
@@ -34612,7 +35028,7 @@ async function discoveryRoutes(app, opts) {
34612
35028
  const skippedCompetitors = [];
34613
35029
  if (includeCompetitors) {
34614
35030
  const existingCompetitors = new Set(
34615
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
35031
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
34616
35032
  );
34617
35033
  const competitorMap = parseCompetitorMap(session.competitorMap);
34618
35034
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -34631,7 +35047,7 @@ async function discoveryRoutes(app, opts) {
34631
35047
  app.db.transaction((tx) => {
34632
35048
  for (const query of promotedQueries) {
34633
35049
  tx.insert(queries).values({
34634
- id: crypto27.randomUUID(),
35050
+ id: crypto28.randomUUID(),
34635
35051
  projectId: project.id,
34636
35052
  query,
34637
35053
  provenance,
@@ -34640,7 +35056,7 @@ async function discoveryRoutes(app, opts) {
34640
35056
  }
34641
35057
  for (const domain of promotedCompetitors) {
34642
35058
  tx.insert(competitors).values({
34643
- id: crypto27.randomUUID(),
35059
+ id: crypto28.randomUUID(),
34644
35060
  projectId: project.id,
34645
35061
  domain,
34646
35062
  provenance,
@@ -34730,8 +35146,8 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
34730
35146
  }
34731
35147
 
34732
35148
  // ../api-routes/src/discovery/orchestrate.ts
34733
- import crypto28 from "crypto";
34734
- import { eq as eq36 } from "drizzle-orm";
35149
+ import crypto29 from "crypto";
35150
+ import { eq as eq37 } from "drizzle-orm";
34735
35151
  var DEFAULT_MAX_PROBES = 100;
34736
35152
  var ABSOLUTE_MAX_PROBES = 500;
34737
35153
  function classifyProbeBucket(input) {
@@ -34818,7 +35234,7 @@ async function executeDiscovery(opts) {
34818
35234
  status: DiscoverySessionStatuses.seeding,
34819
35235
  dedupThreshold,
34820
35236
  startedAt
34821
- }).where(eq36(discoverySessions.id, opts.sessionId)).run();
35237
+ }).where(eq37(discoverySessions.id, opts.sessionId)).run();
34822
35238
  const seedResult = await opts.deps.seed({
34823
35239
  project: opts.project,
34824
35240
  icpDescription: opts.icpDescription,
@@ -34879,7 +35295,7 @@ async function executeDiscovery(opts) {
34879
35295
  dedupBandPairFraction: dedupStats.bandPairFraction,
34880
35296
  dedupPairsTotal: dedupStats.pairsTotal,
34881
35297
  warning
34882
- }).where(eq36(discoverySessions.id, opts.sessionId)).run();
35298
+ }).where(eq37(discoverySessions.id, opts.sessionId)).run();
34883
35299
  const probeLocation = opts.locations?.[0];
34884
35300
  const probeResults = await mapWithConcurrency(
34885
35301
  probedCanonicals,
@@ -34898,7 +35314,7 @@ async function executeDiscovery(opts) {
34898
35314
  probeRows.push({ citedDomains: probe.citedDomains, bucket });
34899
35315
  buckets[bucket]++;
34900
35316
  return {
34901
- id: crypto28.randomUUID(),
35317
+ id: crypto29.randomUUID(),
34902
35318
  sessionId: opts.sessionId,
34903
35319
  projectId: opts.project.id,
34904
35320
  query: probedCanonicals[index2],
@@ -34933,7 +35349,7 @@ async function executeDiscovery(opts) {
34933
35349
  wastedCount: buckets["wasted-surface"],
34934
35350
  competitorMap,
34935
35351
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
34936
- }).where(eq36(discoverySessions.id, opts.sessionId)).run();
35352
+ }).where(eq37(discoverySessions.id, opts.sessionId)).run();
34937
35353
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
34938
35354
  return {
34939
35355
  buckets,
@@ -34950,7 +35366,7 @@ function upsertDomainClassifications(db, projectId, sessionId, competitorMap) {
34950
35366
  const domain = normalizeDomain(entry.domain);
34951
35367
  if (!domain) continue;
34952
35368
  db.insert(domainClassifications).values({
34953
- id: crypto28.randomUUID(),
35369
+ id: crypto29.randomUUID(),
34954
35370
  projectId,
34955
35371
  domain,
34956
35372
  competitorType: entry.competitorType,
@@ -34973,7 +35389,7 @@ function markSessionFailed(db, sessionId, error) {
34973
35389
  status: DiscoverySessionStatuses.failed,
34974
35390
  error,
34975
35391
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
34976
- }).where(eq36(discoverySessions.id, sessionId)).run();
35392
+ }).where(eq37(discoverySessions.id, sessionId)).run();
34977
35393
  }
34978
35394
  function dedupeStrings(input) {
34979
35395
  const seen = /* @__PURE__ */ new Set();
@@ -34990,8 +35406,8 @@ function dedupeStrings(input) {
34990
35406
  }
34991
35407
 
34992
35408
  // ../api-routes/src/technical-aeo.ts
34993
- import crypto29 from "crypto";
34994
- import { and as and29, asc as asc5, count, desc as desc17, eq as eq37, inArray as inArray13 } from "drizzle-orm";
35409
+ import crypto30 from "crypto";
35410
+ import { and as and29, asc as asc5, count, desc as desc17, eq as eq38, inArray as inArray14 } from "drizzle-orm";
34995
35411
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
34996
35412
  function emptyScore(projectName) {
34997
35413
  return {
@@ -35023,10 +35439,10 @@ function parsePositiveInt(value, fallback, max) {
35023
35439
  async function technicalAeoRoutes(app, opts) {
35024
35440
  app.get("/projects/:name/technical-aeo", async (request) => {
35025
35441
  const project = resolveProject(app.db, request.params.name);
35026
- const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and29(
35027
- eq37(siteAuditSnapshots.projectId, project.id),
35028
- eq37(runs.kind, RunKinds["site-audit"]),
35029
- inArray13(runs.status, SURFACEABLE_STATUSES),
35442
+ const rows = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq38(siteAuditSnapshots.runId, runs.id)).where(and29(
35443
+ eq38(siteAuditSnapshots.projectId, project.id),
35444
+ eq38(runs.kind, RunKinds["site-audit"]),
35445
+ inArray14(runs.status, SURFACEABLE_STATUSES),
35030
35446
  notProbeRun()
35031
35447
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(2).all();
35032
35448
  const latest = rows[0];
@@ -35058,18 +35474,18 @@ async function technicalAeoRoutes(app, opts) {
35058
35474
  });
35059
35475
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
35060
35476
  const project = resolveProject(app.db, request.params.name);
35061
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and29(
35062
- eq37(siteAuditSnapshots.projectId, project.id),
35063
- eq37(runs.kind, RunKinds["site-audit"]),
35064
- inArray13(runs.status, SURFACEABLE_STATUSES),
35477
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq38(siteAuditSnapshots.runId, runs.id)).where(and29(
35478
+ eq38(siteAuditSnapshots.projectId, project.id),
35479
+ eq38(runs.kind, RunKinds["site-audit"]),
35480
+ inArray14(runs.status, SURFACEABLE_STATUSES),
35065
35481
  notProbeRun()
35066
35482
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
35067
35483
  if (!latest) {
35068
35484
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
35069
35485
  }
35070
35486
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
35071
- const conds = [eq37(siteAuditPages.runId, latest.runId)];
35072
- if (statusFilter) conds.push(eq37(siteAuditPages.status, statusFilter));
35487
+ const conds = [eq38(siteAuditPages.runId, latest.runId)];
35488
+ if (statusFilter) conds.push(eq38(siteAuditPages.status, statusFilter));
35073
35489
  const where = and29(...conds);
35074
35490
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
35075
35491
  const total = totalRow?.value ?? 0;
@@ -35094,10 +35510,10 @@ async function technicalAeoRoutes(app, opts) {
35094
35510
  auditedAt: siteAuditSnapshots.auditedAt,
35095
35511
  aggregateScore: siteAuditSnapshots.aggregateScore,
35096
35512
  pagesAudited: siteAuditSnapshots.pagesAudited
35097
- }).from(siteAuditSnapshots).innerJoin(runs, eq37(siteAuditSnapshots.runId, runs.id)).where(and29(
35098
- eq37(siteAuditSnapshots.projectId, project.id),
35099
- eq37(runs.kind, RunKinds["site-audit"]),
35100
- inArray13(runs.status, SURFACEABLE_STATUSES),
35513
+ }).from(siteAuditSnapshots).innerJoin(runs, eq38(siteAuditSnapshots.runId, runs.id)).where(and29(
35514
+ eq38(siteAuditSnapshots.projectId, project.id),
35515
+ eq38(runs.kind, RunKinds["site-audit"]),
35516
+ inArray14(runs.status, SURFACEABLE_STATUSES),
35101
35517
  notProbeRun()
35102
35518
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
35103
35519
  return { project: project.name, points: rows.reverse() };
@@ -35109,15 +35525,15 @@ async function technicalAeoRoutes(app, opts) {
35109
35525
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
35110
35526
  }
35111
35527
  const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and29(
35112
- eq37(runs.projectId, project.id),
35113
- eq37(runs.kind, RunKinds["site-audit"]),
35114
- inArray13(runs.status, [RunStatuses.queued, RunStatuses.running])
35528
+ eq38(runs.projectId, project.id),
35529
+ eq38(runs.kind, RunKinds["site-audit"]),
35530
+ inArray14(runs.status, [RunStatuses.queued, RunStatuses.running])
35115
35531
  )).get();
35116
35532
  if (existing) {
35117
35533
  return { runId: existing.id, status: existing.status };
35118
35534
  }
35119
35535
  const now = (/* @__PURE__ */ new Date()).toISOString();
35120
- const runId = crypto29.randomUUID();
35536
+ const runId = crypto30.randomUUID();
35121
35537
  app.db.insert(runs).values({
35122
35538
  id: runId,
35123
35539
  projectId: project.id,
@@ -35470,7 +35886,7 @@ function buildTrafficSourceValidators(opts) {
35470
35886
  }
35471
35887
 
35472
35888
  // src/intelligence-service.ts
35473
- import crypto30 from "crypto";
35889
+ import crypto31 from "crypto";
35474
35890
 
35475
35891
  // src/logger.ts
35476
35892
  var IS_TTY = process.stdout.isTTY === true;
@@ -35703,8 +36119,8 @@ var IntelligenceService = class {
35703
36119
  analyzeAndPersist(runId, projectId) {
35704
36120
  const recentRuns = this.db.select().from(runs).where(
35705
36121
  and30(
35706
- eq38(runs.projectId, projectId),
35707
- or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
36122
+ eq39(runs.projectId, projectId),
36123
+ or6(eq39(runs.status, "completed"), eq39(runs.status, "partial")),
35708
36124
  // Defensive: RunCoordinator already skips probes before this is
35709
36125
  // called, but if a future call site invokes analyzeAndPersist
35710
36126
  // directly for a probe, probes still must not pollute the
@@ -35786,7 +36202,7 @@ var IntelligenceService = class {
35786
36202
  * Returns the persisted insights so the coordinator can count critical/high.
35787
36203
  */
35788
36204
  analyzeAndPersistGbp(runId, projectId) {
35789
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq38(runs.id, runId)).get();
36205
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq39(runs.id, runId)).get();
35790
36206
  if (!runRow) {
35791
36207
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
35792
36208
  this.persistGbpInsights(runId, projectId, [], []);
@@ -35795,8 +36211,8 @@ var IntelligenceService = class {
35795
36211
  const windowStart = runRow.startedAt ?? runRow.createdAt;
35796
36212
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
35797
36213
  const selected = this.db.select().from(gbpLocations).where(and30(
35798
- eq38(gbpLocations.projectId, projectId),
35799
- eq38(gbpLocations.selected, true),
36214
+ eq39(gbpLocations.projectId, projectId),
36215
+ eq39(gbpLocations.selected, true),
35800
36216
  gte7(gbpLocations.syncedAt, windowStart),
35801
36217
  lte4(gbpLocations.syncedAt, windowEnd)
35802
36218
  )).all();
@@ -35831,12 +36247,12 @@ var IntelligenceService = class {
35831
36247
  }
35832
36248
  /** Build the per-location signal bundle the GBP analyzer consumes. */
35833
36249
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
35834
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and30(eq38(gbpDailyMetrics.projectId, projectId), eq38(gbpDailyMetrics.locationName, locationName))).all();
35835
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and30(eq38(gbpPlaceActions.projectId, projectId), eq38(gbpPlaceActions.locationName, locationName))).all();
35836
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and30(eq38(gbpLodgingSnapshots.projectId, projectId), eq38(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
35837
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and30(eq38(gbpLocations.projectId, projectId), eq38(gbpLocations.locationName, locationName))).get();
36250
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and30(eq39(gbpDailyMetrics.projectId, projectId), eq39(gbpDailyMetrics.locationName, locationName))).all();
36251
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and30(eq39(gbpPlaceActions.projectId, projectId), eq39(gbpPlaceActions.locationName, locationName))).all();
36252
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and30(eq39(gbpLodgingSnapshots.projectId, projectId), eq39(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc18(gbpLodgingSnapshots.syncedAt)).limit(1).get();
36253
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and30(eq39(gbpLocations.projectId, projectId), eq39(gbpLocations.locationName, locationName))).get();
35838
36254
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
35839
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and30(eq38(gbpPlaceDetails.projectId, projectId), eq38(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
36255
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and30(eq39(gbpPlaceDetails.projectId, projectId), eq39(gbpPlaceDetails.locationName, locationName))).orderBy(desc18(gbpPlaceDetails.syncedAt)).limit(1).get();
35840
36256
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
35841
36257
  const summary = buildGbpSummary({
35842
36258
  locationName,
@@ -35872,7 +36288,7 @@ var IntelligenceService = class {
35872
36288
  /** Build the month-over-month keyword series for a location from the
35873
36289
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
35874
36290
  buildGbpKeywordTrend(projectId, locationName) {
35875
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and30(eq38(gbpKeywordMonthly.projectId, projectId), eq38(gbpKeywordMonthly.locationName, locationName))).all();
36291
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and30(eq39(gbpKeywordMonthly.projectId, projectId), eq39(gbpKeywordMonthly.locationName, locationName))).all();
35876
36292
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
35877
36293
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
35878
36294
  const recentMonth = months[0] ?? null;
@@ -35903,7 +36319,7 @@ var IntelligenceService = class {
35903
36319
  */
35904
36320
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
35905
36321
  const covered = new Set(coveredLocationNames);
35906
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and30(eq38(insights.projectId, projectId), eq38(insights.provider, GBP_INSIGHT_PROVIDER))).all();
36322
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and30(eq39(insights.projectId, projectId), eq39(insights.provider, GBP_INSIGHT_PROVIDER))).all();
35907
36323
  const staleIds = [];
35908
36324
  const dismissedSlots = /* @__PURE__ */ new Set();
35909
36325
  for (const row of existing) {
@@ -35914,7 +36330,7 @@ var IntelligenceService = class {
35914
36330
  }
35915
36331
  this.db.transaction((tx) => {
35916
36332
  for (const id of staleIds) {
35917
- tx.delete(insights).where(eq38(insights.id, id)).run();
36333
+ tx.delete(insights).where(eq39(insights.id, id)).run();
35918
36334
  }
35919
36335
  for (const insight of gbpInsights) {
35920
36336
  const parsed = parseGbpInsightId(insight.id);
@@ -35992,7 +36408,7 @@ var IntelligenceService = class {
35992
36408
  * create per run + aggregate). DB is left untouched.
35993
36409
  */
35994
36410
  backfill(projectName, opts, onProgress) {
35995
- const project = this.db.select().from(projects).where(eq38(projects.name, projectName)).get();
36411
+ const project = this.db.select().from(projects).where(eq39(projects.name, projectName)).get();
35996
36412
  if (!project) {
35997
36413
  throw new Error(`Project "${projectName}" not found`);
35998
36414
  }
@@ -36006,8 +36422,8 @@ var IntelligenceService = class {
36006
36422
  }
36007
36423
  const allRuns = this.db.select().from(runs).where(
36008
36424
  and30(
36009
- eq38(runs.projectId, project.id),
36010
- or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
36425
+ eq39(runs.projectId, project.id),
36426
+ or6(eq39(runs.status, "completed"), eq39(runs.status, "partial")),
36011
36427
  // Backfill must not replay probe runs as if they were real sweeps.
36012
36428
  ne5(runs.trigger, RunTriggers.probe)
36013
36429
  )
@@ -36040,7 +36456,7 @@ var IntelligenceService = class {
36040
36456
  let wouldDeleteTotal = 0;
36041
36457
  const existingByRunId = /* @__PURE__ */ new Map();
36042
36458
  if (isDryRun && targetRuns.length > 0) {
36043
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray14(insights.runId, targetRuns.map((r) => r.id))).all();
36459
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray15(insights.runId, targetRuns.map((r) => r.id))).all();
36044
36460
  for (const r of rows) {
36045
36461
  if (r.runId == null) continue;
36046
36462
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -36086,7 +36502,7 @@ var IntelligenceService = class {
36086
36502
  return { processed, skipped, totalInsights };
36087
36503
  }
36088
36504
  loadTrackedCompetitors(projectId) {
36089
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq38(competitors.projectId, projectId)).all().map((r) => r.domain);
36505
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq39(competitors.projectId, projectId)).all().map((r) => r.domain);
36090
36506
  }
36091
36507
  /**
36092
36508
  * Wipe transition signals from an analysis result while keeping health.
@@ -36107,15 +36523,15 @@ var IntelligenceService = class {
36107
36523
  }
36108
36524
  persistResult(result, runId, projectId) {
36109
36525
  const previouslyDismissed = /* @__PURE__ */ new Set();
36110
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq38(insights.runId, runId)).all();
36526
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq39(insights.runId, runId)).all();
36111
36527
  for (const row of existingInsights) {
36112
36528
  if (row.dismissed) {
36113
36529
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
36114
36530
  }
36115
36531
  }
36116
36532
  this.db.transaction((tx) => {
36117
- tx.delete(insights).where(eq38(insights.runId, runId)).run();
36118
- tx.delete(healthSnapshots).where(eq38(healthSnapshots.runId, runId)).run();
36533
+ tx.delete(insights).where(eq39(insights.runId, runId)).run();
36534
+ tx.delete(healthSnapshots).where(eq39(healthSnapshots.runId, runId)).run();
36119
36535
  const now = (/* @__PURE__ */ new Date()).toISOString();
36120
36536
  for (const insight of result.insights) {
36121
36537
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -36135,7 +36551,7 @@ var IntelligenceService = class {
36135
36551
  }).run();
36136
36552
  }
36137
36553
  tx.insert(healthSnapshots).values({
36138
- id: crypto30.randomUUID(),
36554
+ id: crypto31.randomUUID(),
36139
36555
  projectId,
36140
36556
  runId,
36141
36557
  overallCitedRate: String(result.health.overallCitedRate),
@@ -36168,14 +36584,14 @@ var IntelligenceService = class {
36168
36584
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
36169
36585
  const regressions = rawInsights.filter((i) => i.type === "regression");
36170
36586
  if (regressions.length === 0) return rawInsights;
36171
- const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq38(gscSearchData.projectId, projectId)).all();
36587
+ const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq39(gscSearchData.projectId, projectId)).all();
36172
36588
  const gscConnected = gscRows.length > 0;
36173
36589
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
36174
36590
  for (const row of gscRows) {
36175
36591
  const key = row.query.toLowerCase();
36176
36592
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
36177
36593
  }
36178
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq38(projects.id, projectId)).get();
36594
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq39(projects.id, projectId)).get();
36179
36595
  const locationCount = Math.max(
36180
36596
  1,
36181
36597
  (projectRow?.locations ?? []).length
@@ -36183,9 +36599,9 @@ var IntelligenceService = class {
36183
36599
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
36184
36600
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
36185
36601
  and30(
36186
- eq38(runs.projectId, projectId),
36187
- eq38(runs.kind, RunKinds["answer-visibility"]),
36188
- or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
36602
+ eq39(runs.projectId, projectId),
36603
+ eq39(runs.kind, RunKinds["answer-visibility"]),
36604
+ or6(eq39(runs.status, "completed"), eq39(runs.status, "partial")),
36189
36605
  // Defensive — see top of file.
36190
36606
  ne5(runs.trigger, RunTriggers.probe)
36191
36607
  )
@@ -36205,7 +36621,7 @@ var IntelligenceService = class {
36205
36621
  const haveHistory = recentRunIds.length > 0;
36206
36622
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
36207
36623
  if (haveHistory) {
36208
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and30(eq38(insights.type, "regression"), inArray14(insights.runId, recentRunIds))).all();
36624
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and30(eq39(insights.type, "regression"), inArray15(insights.runId, recentRunIds))).all();
36209
36625
  const regressionGroups = /* @__PURE__ */ new Map();
36210
36626
  for (const row of priorRows) {
36211
36627
  if (!row.runId) continue;
@@ -36234,7 +36650,7 @@ var IntelligenceService = class {
36234
36650
  });
36235
36651
  }
36236
36652
  buildRunData(runId, projectId, completedAt, location = null) {
36237
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq38(projects.id, projectId)).get();
36653
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq39(projects.id, projectId)).get();
36238
36654
  const projectDomains = projectDomainRow ? effectiveDomains({
36239
36655
  canonicalDomain: projectDomainRow.canonicalDomain,
36240
36656
  ownedDomains: projectDomainRow.ownedDomains
@@ -36251,7 +36667,7 @@ var IntelligenceService = class {
36251
36667
  citedDomains: querySnapshots.citedDomains,
36252
36668
  competitorOverlap: querySnapshots.competitorOverlap,
36253
36669
  snapshotLocation: querySnapshots.location
36254
- }).from(querySnapshots).leftJoin(queries, eq38(querySnapshots.queryId, queries.id)).where(eq38(querySnapshots.runId, runId)).all();
36670
+ }).from(querySnapshots).leftJoin(queries, eq39(querySnapshots.queryId, queries.id)).where(eq39(querySnapshots.runId, runId)).all();
36255
36671
  const snapshots = [];
36256
36672
  let orphanCount = 0;
36257
36673
  for (const r of rows) {