@canonry/canonry 4.131.0 → 4.132.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +7 -0
  2. package/assets/assets/{AuditHistoryPanel-yJXZMO5t.js → AuditHistoryPanel-Carj_FCP.js} +1 -1
  3. package/assets/assets/{BacklinksPage-6VTLbAYx.js → BacklinksPage-CqOTDVIn.js} +1 -1
  4. package/assets/assets/{ChartPrimitives-D5YGiDLi.js → ChartPrimitives-EuQ82l0S.js} +1 -1
  5. package/assets/assets/{HistoryPage-BxdTM6-R.js → HistoryPage-D6XQfiLX.js} +1 -1
  6. package/assets/assets/{ProjectPage-BJKxZPRO.js → ProjectPage-Dswa9Ah6.js} +1 -1
  7. package/assets/assets/{RunRow-DTrAO7tE.js → RunRow-ThryEZFR.js} +1 -1
  8. package/assets/assets/{RunsPage-CHU0DQGl.js → RunsPage-RFs2WkTC.js} +1 -1
  9. package/assets/assets/{SettingsPage-bOIwju6t.js → SettingsPage-DoUccn9s.js} +1 -1
  10. package/assets/assets/{TrafficPage-BKvuR482.js → TrafficPage-CVvSDZPY.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-3-iUTgQg.js → TrafficSourceDetailPage-BTUQUBeS.js} +1 -1
  12. package/assets/assets/{arrow-left-BXSfGhr6.js → arrow-left-BoCNqusn.js} +1 -1
  13. package/assets/assets/{extract-error-message-DEAY62LS.js → extract-error-message-BsfOVrKi.js} +1 -1
  14. package/assets/assets/{index-CfjIYitV.js → index-jJurRky0.js} +85 -85
  15. package/assets/assets/{trash-2-O1wo9IX1.js → trash-2-BPb4368K.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-73HPY6XQ.js → chunk-5FWAPJFX.js} +45 -1
  18. package/dist/{chunk-3WBAJKRR.js → chunk-7BD2ZOPV.js} +86 -0
  19. package/dist/{chunk-ZXVWL5HX.js → chunk-QIJKS5KN.js} +743 -301
  20. package/dist/{chunk-5GQ6FF5R.js → chunk-RWTOEEG3.js} +4 -4
  21. package/dist/cli.js +67 -5
  22. package/dist/index.js +4 -4
  23. package/dist/{intelligence-service-YJKQQYJQ.js → intelligence-service-RU2ACYPY.js} +2 -2
  24. package/dist/mcp.js +2 -2
  25. package/package.json +8 -8
@@ -200,6 +200,9 @@ import {
200
200
  ga4SocialReferralHistoryEntrySchema,
201
201
  ga4StatusDtoSchema,
202
202
  ga4SyncResponseDtoSchema,
203
+ gaMeasurementAnalysisDtoSchema,
204
+ gaMeasurementAnalysisWindowSchema,
205
+ gaMeasurementHostScopeSchema,
203
206
  gateHarvestedSearchQueries,
204
207
  gbpAccountListResponseSchema,
205
208
  gbpAttributesListResponseSchema,
@@ -344,10 +347,10 @@ import {
344
347
  wordpressSchemaDeployResultDtoSchema,
345
348
  wordpressSchemaStatusResultDtoSchema,
346
349
  wordpressStatusDtoSchema
347
- } from "./chunk-3WBAJKRR.js";
350
+ } from "./chunk-7BD2ZOPV.js";
348
351
 
349
352
  // src/intelligence-service.ts
350
- import { eq as eq42, desc as desc19, asc as asc9, and as and33, ne as ne6, or as or8, inArray as inArray15, gte as gte10, lte as lte6 } from "drizzle-orm";
353
+ import { eq as eq43, desc as desc19, asc as asc9, and as and34, ne as ne6, or as or9, inArray as inArray15, gte as gte11, lte as lte7 } from "drizzle-orm";
351
354
 
352
355
  // ../db/src/client.ts
353
356
  import { mkdirSync } from "fs";
@@ -12620,31 +12623,31 @@ function renderAiReferrals(report) {
12620
12623
  </div>`
12621
12624
  );
12622
12625
  }
12623
- function serverActivityHeading(audience, hasData, windowDays) {
12626
+ function serverActivityHeading(audience, hasData, windowDays2) {
12624
12627
  const isClient = audience === "client";
12625
12628
  return {
12626
12629
  id: "server-activity",
12627
12630
  eyebrow: isClient ? "AI engine attention" : "Section 10",
12628
12631
  title: "AI Visibility \u2014 Server-Side",
12629
- intro: isClient ? hasData ? `What AI engines actually do in your server logs over the last ${windowDays} days \u2014 the other half of citations.` : "Live telemetry from your server logs." : "What AI engines actually do in your server logs \u2014 direct evidence, complementary to citations (which measure what they say)."
12632
+ intro: isClient ? hasData ? `What AI engines actually do in your server logs over the last ${windowDays2} days \u2014 the other half of citations.` : "Live telemetry from your server logs." : "What AI engines actually do in your server logs \u2014 direct evidence, complementary to citations (which measure what they say)."
12630
12633
  };
12631
12634
  }
12632
12635
  function renderServerActivity(report, audience) {
12633
12636
  const sa = report.serverActivity;
12634
12637
  const isClient = audience === "client";
12635
- const windowDays = report.meta.periodDays;
12636
- const windowLabel = `${windowDays}d`;
12637
- const priorWindowLabel = `vs prior ${windowDays} days`;
12638
+ const windowDays2 = report.meta.periodDays;
12639
+ const windowLabel = `${windowDays2}d`;
12640
+ const priorWindowLabel = `vs prior ${windowDays2} days`;
12638
12641
  if (!sa) {
12639
12642
  if (isClient) return "";
12640
12643
  return section(
12641
- serverActivityHeading("agency", false, windowDays),
12644
+ serverActivityHeading("agency", false, windowDays2),
12642
12645
  renderEmpty("Connect a server-side traffic source to surface what AI engines do directly in your server logs \u2014 distinct from GA4 click-throughs.")
12643
12646
  );
12644
12647
  }
12645
12648
  if (!sa.hasData) {
12646
12649
  return section(
12647
- serverActivityHeading(audience, false, windowDays),
12650
+ serverActivityHeading(audience, false, windowDays2),
12648
12651
  renderEmpty(isClient ? "Your server-side traffic source is connected. Numbers will appear after the next sync." : "Source connected \u2014 collecting your first data. Numbers will appear after the next sync.")
12649
12652
  );
12650
12653
  }
@@ -12677,7 +12680,7 @@ function renderServerActivity(report, audience) {
12677
12680
  <td class="numeric">${formatNumber(o.referralArrivals)}</td>
12678
12681
  </tr>`).join("");
12679
12682
  return section(
12680
- serverActivityHeading("client", true, windowDays),
12683
+ serverActivityHeading("client", true, windowDays2),
12681
12684
  `<div class="metric-grid">
12682
12685
  <div class="metric">
12683
12686
  <div class="label">AI bot requests observed</div>
@@ -12738,10 +12741,10 @@ function renderServerActivity(report, audience) {
12738
12741
  const trendChart = sa.dailyTrend.length > 0 ? renderLineChart(
12739
12742
  sa.dailyTrend.map((d) => ({ x: d.date, y: d.verifiedCrawlerHits, label: d.date.slice(5) })),
12740
12743
  COLORS.series[1],
12741
- `Verified crawler hits over time (last ${windowDays} days)`
12744
+ `Verified crawler hits over time (last ${windowDays2} days)`
12742
12745
  ) : "";
12743
12746
  return section(
12744
- serverActivityHeading("agency", true, windowDays),
12747
+ serverActivityHeading("agency", true, windowDays2),
12745
12748
  `<div class="metric-grid">
12746
12749
  <div class="metric">
12747
12750
  <div class="label">Verified crawler hits (${windowLabel})</div>
@@ -13271,11 +13274,11 @@ var TOP_CAMPAIGN_LIMIT = 10;
13271
13274
  var INSIGHT_LOOKBACK_RUNS = 5;
13272
13275
  var SERVER_ACTIVITY_TOP_PATHS_LIMIT = 10;
13273
13276
  var GA_WINDOW_SUMMARY_KEYS = { 7: "7d", 30: "30d", 90: "90d" };
13274
- function windowStartDate(endDate, windowDays) {
13277
+ function windowStartDate(endDate, windowDays2) {
13275
13278
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(endDate);
13276
13279
  if (!m) return endDate;
13277
13280
  const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));
13278
- d.setUTCDate(d.getUTCDate() - (windowDays - 1));
13281
+ d.setUTCDate(d.getUTCDate() - (windowDays2 - 1));
13279
13282
  return d.toISOString().slice(0, 10);
13280
13283
  }
13281
13284
  function safeNum(value) {
@@ -13319,14 +13322,14 @@ function loadQueryLookup(db, projectId) {
13319
13322
  for (const row of rows) byId.set(row.id, row.query);
13320
13323
  return { byId };
13321
13324
  }
13322
- function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trackedQueries, windowDays) {
13325
+ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trackedQueries, windowDays2) {
13323
13326
  const allRows = db.select().from(gscSearchData).where(eq16(gscSearchData.projectId, projectId)).all();
13324
13327
  const allDailyTotals = readGscDailyTotals(db, projectId, "", "9999-12-31");
13325
13328
  let maxDate = "";
13326
13329
  for (const r of allRows) if (r.date > maxDate) maxDate = r.date;
13327
13330
  for (const r of allDailyTotals) if (r.date > maxDate) maxDate = r.date;
13328
13331
  if (!maxDate) return null;
13329
- const startDate = windowStartDate(maxDate, windowDays);
13332
+ const startDate = windowStartDate(maxDate, windowDays2);
13330
13333
  const rows = allRows.filter((r) => r.date >= startDate && r.date <= maxDate);
13331
13334
  const dailyTotals = allDailyTotals.filter((r) => r.date >= startDate && r.date <= maxDate);
13332
13335
  if (rows.length === 0 && dailyTotals.length === 0) return null;
@@ -13419,8 +13422,8 @@ function buildGscSection(db, projectId, projectBrandNames, canonicalDomain, trac
13419
13422
  gscButNotTracked
13420
13423
  };
13421
13424
  }
13422
- function buildGaSection(db, projectId, windowDays) {
13423
- const gaWindowKey = GA_WINDOW_SUMMARY_KEYS[windowDays];
13425
+ function buildGaSection(db, projectId, windowDays2) {
13426
+ const gaWindowKey = GA_WINDOW_SUMMARY_KEYS[windowDays2];
13424
13427
  const windowSummary = gaWindowKey ? db.select().from(gaTrafficWindowSummaries).where(
13425
13428
  and12(
13426
13429
  eq16(gaTrafficWindowSummaries.projectId, projectId),
@@ -13432,7 +13435,7 @@ function buildGaSection(db, projectId, windowDays) {
13432
13435
  if (!windowSummary && !fallbackSummary && allSnapshotRows.length === 0) return null;
13433
13436
  let snapshotMaxDate = "";
13434
13437
  for (const r of allSnapshotRows) if (r.date > snapshotMaxDate) snapshotMaxDate = r.date;
13435
- const snapshotStartDate = snapshotMaxDate ? windowStartDate(snapshotMaxDate, windowDays) : "";
13438
+ const snapshotStartDate = snapshotMaxDate ? windowStartDate(snapshotMaxDate, windowDays2) : "";
13436
13439
  const snapshotRows = snapshotStartDate ? allSnapshotRows.filter((r) => r.date >= snapshotStartDate && r.date <= snapshotMaxDate) : allSnapshotRows;
13437
13440
  const totalSessions = windowSummary?.totalSessions ?? fallbackSummary?.totalSessions ?? snapshotRows.reduce((s, r) => s + r.sessions, 0);
13438
13441
  const totalUsers = windowSummary?.totalUsers ?? fallbackSummary?.totalUsers ?? snapshotRows.reduce((s, r) => s + r.users, 0);
@@ -13544,8 +13547,8 @@ function buildSocialReferrals(db, projectId) {
13544
13547
  topCampaigns
13545
13548
  };
13546
13549
  }
13547
- function resolveAiReferralWindow(db, projectId, windowDays) {
13548
- const windowKey = GA_WINDOW_SUMMARY_KEYS[windowDays];
13550
+ function resolveAiReferralWindow(db, projectId, windowDays2) {
13551
+ const windowKey = GA_WINDOW_SUMMARY_KEYS[windowDays2];
13549
13552
  const windowSummary = windowKey ? db.select({ periodStart: gaTrafficWindowSummaries.periodStart, periodEnd: gaTrafficWindowSummaries.periodEnd }).from(gaTrafficWindowSummaries).where(and12(
13550
13553
  eq16(gaTrafficWindowSummaries.projectId, projectId),
13551
13554
  eq16(gaTrafficWindowSummaries.windowKey, windowKey)
@@ -13553,10 +13556,10 @@ function resolveAiReferralWindow(db, projectId, windowDays) {
13553
13556
  if (windowSummary) return { start: windowSummary.periodStart, end: windowSummary.periodEnd };
13554
13557
  const latestSummary = db.select({ periodEnd: gaTrafficSummaries.periodEnd }).from(gaTrafficSummaries).where(eq16(gaTrafficSummaries.projectId, projectId)).orderBy(desc7(gaTrafficSummaries.syncedAt)).get();
13555
13558
  const end = latestSummary?.periodEnd ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
13556
- return { start: windowStartDate(end, windowDays), end };
13559
+ return { start: windowStartDate(end, windowDays2), end };
13557
13560
  }
13558
- function buildAiReferrals(db, projectId, windowDays) {
13559
- const window = resolveAiReferralWindow(db, projectId, windowDays);
13561
+ function buildAiReferrals(db, projectId, windowDays2) {
13562
+ const window = resolveAiReferralWindow(db, projectId, windowDays2);
13560
13563
  const rows = db.select().from(gaAiReferrals).where(and12(
13561
13564
  eq16(gaAiReferrals.projectId, projectId),
13562
13565
  sql8`${gaAiReferrals.date} >= ${window.start}`,
@@ -13658,7 +13661,7 @@ function nonSubresourceReferralPathCondition() {
13658
13661
  AND LOWER(${aiReferralEventsHourly.landingPathNormalized}) NOT LIKE '%.woff2'
13659
13662
  `;
13660
13663
  }
13661
- function buildServerActivity(db, projectId, windowDays) {
13664
+ function buildServerActivity(db, projectId, windowDays2) {
13662
13665
  const sourceRows = db.select({ id: trafficSources.id }).from(trafficSources).where(
13663
13666
  and12(
13664
13667
  eq16(trafficSources.projectId, projectId),
@@ -13668,8 +13671,8 @@ function buildServerActivity(db, projectId, windowDays) {
13668
13671
  if (sourceRows.length === 0) return null;
13669
13672
  const now = /* @__PURE__ */ new Date();
13670
13673
  const headlineEnd = now.toISOString();
13671
- const headlineStartMs = now.getTime() - windowDays * 24 * 60 * 6e4;
13672
- const priorStartMs = headlineStartMs - windowDays * 24 * 60 * 6e4;
13674
+ const headlineStartMs = now.getTime() - windowDays2 * 24 * 60 * 6e4;
13675
+ const priorStartMs = headlineStartMs - windowDays2 * 24 * 60 * 6e4;
13673
13676
  const trendStartMs = headlineStartMs;
13674
13677
  const headlineStart = new Date(headlineStartMs).toISOString();
13675
13678
  const priorStart = new Date(priorStartMs).toISOString();
@@ -16511,6 +16514,7 @@ var SCHEMA_TABLE = {
16511
16514
  ResearchRunListDto: researchRunListSchema,
16512
16515
  DoctorReportDto: doctorReportSchema,
16513
16516
  GA4AiReferralHistoryEntry: ga4AiReferralHistoryEntrySchema,
16517
+ GA4MeasurementAnalysisDto: gaMeasurementAnalysisDtoSchema,
16514
16518
  GA4SessionHistoryEntry: ga4SessionHistoryEntrySchema,
16515
16519
  GA4SocialReferralHistoryEntry: ga4SocialReferralHistoryEntrySchema,
16516
16520
  GA4StatusDto: ga4StatusDtoSchema,
@@ -20122,6 +20126,47 @@ var routeCatalog = [
20122
20126
  404: errorResponse("Project not found.")
20123
20127
  }
20124
20128
  },
20129
+ {
20130
+ method: "get",
20131
+ path: "/api/v1/projects/{name}/ga/measurement-analysis",
20132
+ summary: "Get GA4 measurement analysis",
20133
+ tags: ["ga4"],
20134
+ parameters: [
20135
+ nameParameter,
20136
+ {
20137
+ name: "window",
20138
+ in: "query",
20139
+ description: "30-day cohorts to return.",
20140
+ schema: { type: "string", enum: ["30d", "60d", "90d"], default: "90d" }
20141
+ },
20142
+ {
20143
+ name: "hostScope",
20144
+ in: "query",
20145
+ description: "Configured marketing hosts or every observed host.",
20146
+ schema: { type: "string", enum: ["marketing", "all"], default: "marketing" }
20147
+ },
20148
+ {
20149
+ name: "pathPrefix",
20150
+ in: "query",
20151
+ description: "Optional boundary-safe normalized landing-page prefix.",
20152
+ schema: stringSchema
20153
+ },
20154
+ {
20155
+ name: "limit",
20156
+ in: "query",
20157
+ description: "Maximum page and query detail rows. Native channels are never truncated.",
20158
+ schema: { type: "integer", minimum: 1, maximum: 100, default: 100 }
20159
+ }
20160
+ ],
20161
+ responses: {
20162
+ 200: jsonResponse(
20163
+ "GA4 measurement analysis returned.",
20164
+ "GA4MeasurementAnalysisDto"
20165
+ ),
20166
+ 400: errorResponse("Invalid measurement analysis filters."),
20167
+ 404: errorResponse("Project not found.")
20168
+ }
20169
+ },
20125
20170
  {
20126
20171
  method: "post",
20127
20172
  path: "/api/v1/projects/{name}/ga/sync",
@@ -22059,9 +22104,9 @@ function computeFreshness(rows, asOfDate) {
22059
22104
  const pendingDays = dataThroughDate ? Math.max(0, daysBetween(dataThroughDate, asOfDate)) : 0;
22060
22105
  return { dataThroughDate, latestStoredDate: latestStoredDate || null, pendingDays };
22061
22106
  }
22062
- function buildTimeseries(rows, freshness, windowDays = 30) {
22107
+ function buildTimeseries(rows, freshness, windowDays2 = 30) {
22063
22108
  if (rows.length === 0 || freshness.latestStoredDate === null) return [];
22064
- const windowLow = shiftDate(freshness.latestStoredDate, -(windowDays - 1));
22109
+ const windowLow = shiftDate(freshness.latestStoredDate, -(windowDays2 - 1));
22065
22110
  const metricKeys = /* @__PURE__ */ new Set();
22066
22111
  const byDate = /* @__PURE__ */ new Map();
22067
22112
  for (const row of rows) {
@@ -31230,6 +31275,402 @@ async function ga4Routes(app, opts) {
31230
31275
  });
31231
31276
  }
31232
31277
 
31278
+ // ../api-routes/src/ga-measurement-analysis.ts
31279
+ import { and as and24, eq as eq30, gte as gte7, lte as lte5, or as or7, sql as sql14 } from "drizzle-orm";
31280
+ function addDays(date, offset) {
31281
+ const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
31282
+ value.setUTCDate(value.getUTCDate() + offset);
31283
+ return value.toISOString().slice(0, 10);
31284
+ }
31285
+ function windowDays(window) {
31286
+ if (window === "30d") return 30;
31287
+ if (window === "60d") return 60;
31288
+ return 90;
31289
+ }
31290
+ function buildPeriods(anchor, days) {
31291
+ const labels = days === 30 ? ["latest"] : days === 60 ? ["previous", "latest"] : ["earliest", "middle", "latest"];
31292
+ return labels.map((label, index2) => ({
31293
+ label,
31294
+ startDate: addDays(anchor, -days + index2 * 30 + 1),
31295
+ endDate: addDays(anchor, -days + (index2 + 1) * 30)
31296
+ }));
31297
+ }
31298
+ function aggregateByKey(rows, periods, keyOf, valueOf) {
31299
+ const result = /* @__PURE__ */ new Map();
31300
+ for (const row of rows) {
31301
+ const periodIndex = periods.findIndex(
31302
+ (period2) => row.date >= period2.startDate && row.date <= period2.endDate
31303
+ );
31304
+ if (periodIndex < 0) continue;
31305
+ const values = result.get(keyOf(row)) ?? Array(periods.length).fill(0);
31306
+ values[periodIndex] = (values[periodIndex] ?? 0) + valueOf(row);
31307
+ result.set(keyOf(row), values);
31308
+ }
31309
+ return result;
31310
+ }
31311
+ function sessionPeriods(periods, values) {
31312
+ return periods.map((period2, index2) => ({
31313
+ ...period2,
31314
+ sessions: values[index2] ?? 0
31315
+ }));
31316
+ }
31317
+ function eventPeriods(periods, values) {
31318
+ return periods.map((period2, index2) => ({
31319
+ ...period2,
31320
+ eventCount: values[index2] ?? 0
31321
+ }));
31322
+ }
31323
+ function clickPeriods(periods, clicks, impressions) {
31324
+ return periods.map((period2, index2) => ({
31325
+ ...period2,
31326
+ clicks: clicks[index2] ?? 0,
31327
+ impressions: impressions[index2] ?? 0
31328
+ }));
31329
+ }
31330
+ function rankEntries(entries) {
31331
+ const score = (values) => ({
31332
+ latest: values.at(-1) ?? 0,
31333
+ total: values.reduce((sum, value) => sum + value, 0)
31334
+ });
31335
+ return [...entries].sort(([leftKey, leftValues], [rightKey, rightValues]) => {
31336
+ const left = score(leftValues);
31337
+ const right = score(rightValues);
31338
+ return right.latest - left.latest || right.total - left.total || leftKey.localeCompare(rightKey);
31339
+ });
31340
+ }
31341
+ function normalizeHost(value) {
31342
+ return hostOf(value) ?? value.trim().toLowerCase().replace(/^www\./, "");
31343
+ }
31344
+ function stableUnique(values, normalize) {
31345
+ const result = [];
31346
+ const seen = /* @__PURE__ */ new Set();
31347
+ for (const value of values) {
31348
+ const normalized = normalize(value);
31349
+ if (!normalized || seen.has(normalized)) continue;
31350
+ seen.add(normalized);
31351
+ result.push(normalized);
31352
+ }
31353
+ return result;
31354
+ }
31355
+ function matchesHost(value, marketingHosts) {
31356
+ const normalized = normalizeHost(value);
31357
+ return marketingHosts.some(
31358
+ (candidate) => normalized === candidate || normalized.endsWith(`.${candidate}`)
31359
+ );
31360
+ }
31361
+ function normalizeLandingPage(value) {
31362
+ return normalizeUrlPath(value) ?? "/";
31363
+ }
31364
+ function normalizePathPrefix(value) {
31365
+ if (!value) return null;
31366
+ const normalized = normalizeLandingPage(value);
31367
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
31368
+ }
31369
+ function matchesPathPrefix(value, prefix) {
31370
+ if (!prefix) return true;
31371
+ const pathname = value.split("?")[0] ?? value;
31372
+ if (prefix === "/") return true;
31373
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
31374
+ }
31375
+ function splitKey(key) {
31376
+ const separator = key.indexOf("\0");
31377
+ if (separator < 0) return [key, "/"];
31378
+ return [key.slice(0, separator), key.slice(separator + 1)];
31379
+ }
31380
+ function parseGscPage(row) {
31381
+ try {
31382
+ const url = new URL(row.page);
31383
+ return {
31384
+ ...row,
31385
+ hostName: url.hostname,
31386
+ landingPage: normalizeLandingPage(url.pathname)
31387
+ };
31388
+ } catch {
31389
+ return null;
31390
+ }
31391
+ }
31392
+ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
31393
+ const parsedWindow = gaMeasurementAnalysisWindowSchema.safeParse(options.window ?? "90d");
31394
+ if (!parsedWindow.success) {
31395
+ throw validationError('"window" must be one of: 30d, 60d, 90d');
31396
+ }
31397
+ const parsedHostScope = gaMeasurementHostScopeSchema.safeParse(
31398
+ options.hostScope ?? "marketing"
31399
+ );
31400
+ if (!parsedHostScope.success) {
31401
+ throw validationError('"hostScope" must be one of: marketing, all');
31402
+ }
31403
+ const limit = Number(options.limit ?? 100);
31404
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
31405
+ throw validationError('"limit" must be an integer between 1 and 100');
31406
+ }
31407
+ const project = resolveProject(db, projectName);
31408
+ const days = windowDays(parsedWindow.data);
31409
+ const pathPrefix = normalizePathPrefix(options.pathPrefix);
31410
+ const marketingHosts = stableUnique(
31411
+ [
31412
+ project.canonicalDomain,
31413
+ ...project.ownedDomains,
31414
+ ...project.measurement.marketingHosts
31415
+ ],
31416
+ normalizeHost
31417
+ );
31418
+ const brandTerms = stableUnique(
31419
+ [project.displayName, ...project.aliases, ...project.measurement.brandTerms],
31420
+ (value) => value.trim()
31421
+ );
31422
+ const hostIsIncluded = (hostName) => parsedHostScope.data === "all" || matchesHost(hostName, marketingHosts);
31423
+ const pageIsIncluded = (landingPage) => matchesPathPrefix(landingPage, pathPrefix);
31424
+ const scopedConditions = (hostColumn, normalizedPathColumn, rawPathColumn) => {
31425
+ const conditions = [];
31426
+ if (parsedHostScope.data === "marketing") {
31427
+ const lowerHost = sql14`lower(${hostColumn})`;
31428
+ const normalizedHost = sql14`case when ${lowerHost} like 'www.%' then substr(${lowerHost}, 5) else ${lowerHost} end`;
31429
+ conditions.push(or7(...marketingHosts.flatMap((host) => [
31430
+ sql14`${normalizedHost} = ${host}`,
31431
+ sql14`${normalizedHost} like ${`%.${host}`}`
31432
+ ])));
31433
+ }
31434
+ if (pathPrefix && pathPrefix !== "/") {
31435
+ const rawPath = sql14`case when instr(${rawPathColumn}, '?') = 0 then ${rawPathColumn} else substr(${rawPathColumn}, 1, instr(${rawPathColumn}, '?') - 1) end`;
31436
+ const landingPath = sql14`case when trim(coalesce(${normalizedPathColumn}, '')) = '' then ${rawPath} else ${normalizedPathColumn} end`;
31437
+ conditions.push(or7(sql14`${landingPath} = ${pathPrefix}`, sql14`${landingPath} like ${`${pathPrefix}/%`}`));
31438
+ }
31439
+ return conditions;
31440
+ };
31441
+ const acquisitionScope = scopedConditions(gaAcquisitionDaily.hostName, gaAcquisitionDaily.landingPageNormalized, gaAcquisitionDaily.landingPage);
31442
+ const leadLandingScope = scopedConditions(gaLeadEventsDaily.hostName, gaLeadEventsDaily.landingPageNormalized, gaLeadEventsDaily.landingPage);
31443
+ const acquisitionAnchor = db.select({ date: sql14`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(and24(eq30(gaAcquisitionDaily.projectId, project.id), ...acquisitionScope)).get()?.date ?? null;
31444
+ const leadAnchor = db.select({ date: sql14`max(${gaLeadEventsDaily.date})` }).from(gaLeadEventsDaily).where(and24(eq30(gaLeadEventsDaily.projectId, project.id), or7(
31445
+ eq30(gaLeadEventsDaily.attributionScope, "channel"),
31446
+ and24(eq30(gaLeadEventsDaily.attributionScope, "landing-page"), ...leadLandingScope)
31447
+ ))).get()?.date ?? null;
31448
+ const gaAnchor = [acquisitionAnchor, leadAnchor].filter((date) => date !== null).sort().at(-1) ?? null;
31449
+ const gaPeriods = gaAnchor ? buildPeriods(gaAnchor, days) : [];
31450
+ const gaStartDate = gaPeriods[0]?.startDate;
31451
+ const acquisitionRows = gaStartDate ? db.select().from(gaAcquisitionDaily).where(and24(eq30(gaAcquisitionDaily.projectId, project.id), gte7(gaAcquisitionDaily.date, gaStartDate), lte5(gaAcquisitionDaily.date, gaAnchor))).all() : [];
31452
+ const leadRows = gaStartDate ? db.select().from(gaLeadEventsDaily).where(and24(eq30(gaLeadEventsDaily.projectId, project.id), gte7(gaLeadEventsDaily.date, gaStartDate), lte5(gaLeadEventsDaily.date, gaAnchor))).all() : [];
31453
+ const gscAnchor = db.select({ date: sql14`max(${gscDailyTotals.date})` }).from(gscDailyTotals).where(eq30(gscDailyTotals.projectId, project.id)).get()?.date ?? null;
31454
+ const gscPeriods = gscAnchor ? buildPeriods(gscAnchor, days) : [];
31455
+ const gscStartDate = gscPeriods[0]?.startDate;
31456
+ const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and24(eq30(gscDailyTotals.projectId, project.id), gte7(gscDailyTotals.date, gscStartDate), lte5(gscDailyTotals.date, gscAnchor))).all() : [];
31457
+ const queryRows = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and24(eq30(gscQueryDailyTotals.projectId, project.id), gte7(gscQueryDailyTotals.date, gscStartDate), lte5(gscQueryDailyTotals.date, gscAnchor))).all() : [];
31458
+ const rawPageRows = gscStartDate ? db.select().from(gscSearchData).where(and24(eq30(gscSearchData.projectId, project.id), gte7(gscSearchData.date, gscStartDate), lte5(gscSearchData.date, gscAnchor))).all() : [];
31459
+ const state = db.select().from(gaMeasurementSyncStates).where(eq30(gaMeasurementSyncStates.projectId, project.id)).get();
31460
+ const acquisition = acquisitionRows.filter((row) => {
31461
+ const landingPage = row.landingPageNormalized ?? normalizeLandingPage(row.landingPage);
31462
+ return hostIsIncluded(row.hostName) && pageIsIncluded(landingPage);
31463
+ });
31464
+ const acquisitionTotals = aggregateByKey(
31465
+ acquisition,
31466
+ gaPeriods,
31467
+ () => "all",
31468
+ (row) => row.sessions
31469
+ ).get("all") ?? [];
31470
+ const acquisitionChannels = aggregateByKey(
31471
+ acquisition,
31472
+ gaPeriods,
31473
+ (row) => row.channelGroup,
31474
+ (row) => row.sessions
31475
+ );
31476
+ const acquisitionPages = aggregateByKey(
31477
+ acquisition,
31478
+ gaPeriods,
31479
+ (row) => `${row.hostName}\0${row.landingPageNormalized ?? normalizeLandingPage(row.landingPage)}`,
31480
+ (row) => row.sessions
31481
+ );
31482
+ const selectedLeadRows = leadRows.filter((row) => gaPeriods.some((period2) => row.date >= period2.startDate && row.date <= period2.endDate));
31483
+ const hasLeadTimeline = leadRows.length > 0 || state?.leadSyncedAt != null;
31484
+ const hasChannelOnlyLeads = selectedLeadRows.some((row) => row.attributionScope === "channel");
31485
+ const stateLeadScope = state === void 0 ? null : state.leadAttributionScope;
31486
+ const firstObservedLeadScope = selectedLeadRows.length > 0 ? selectedLeadRows[0].attributionScope : null;
31487
+ const attributionScope = hasChannelOnlyLeads ? "channel" : stateLeadScope ?? firstObservedLeadScope;
31488
+ const hostAndPathFiltersApplied = attributionScope === "landing-page";
31489
+ const leads = selectedLeadRows.filter((row) => {
31490
+ if (row.attributionScope === "channel") return true;
31491
+ const landingPage = row.landingPageNormalized ?? normalizeLandingPage(row.landingPage);
31492
+ return hostIsIncluded(row.hostName) && pageIsIncluded(landingPage);
31493
+ });
31494
+ const leadTotals = aggregateByKey(
31495
+ leads,
31496
+ gaPeriods,
31497
+ () => "all",
31498
+ (row) => row.eventCount
31499
+ ).get("all") ?? [];
31500
+ const leadChannels = aggregateByKey(
31501
+ leads,
31502
+ gaPeriods,
31503
+ (row) => row.channelGroup,
31504
+ (row) => row.eventCount
31505
+ );
31506
+ const propertyClicks = aggregateByKey(
31507
+ propertyRows,
31508
+ gscPeriods,
31509
+ () => "all",
31510
+ (row) => row.clicks
31511
+ ).get("all") ?? [];
31512
+ const propertyImpressions = aggregateByKey(
31513
+ propertyRows,
31514
+ gscPeriods,
31515
+ () => "all",
31516
+ (row) => row.impressions
31517
+ ).get("all") ?? [];
31518
+ const queryClicks = aggregateByKey(
31519
+ queryRows,
31520
+ gscPeriods,
31521
+ () => "all",
31522
+ (row) => row.clicks
31523
+ ).get("all") ?? [];
31524
+ const queryImpressions = aggregateByKey(
31525
+ queryRows,
31526
+ gscPeriods,
31527
+ () => "all",
31528
+ (row) => row.impressions
31529
+ ).get("all") ?? [];
31530
+ const brandedQueries = new Set(filterBrandedSeedCandidates({
31531
+ candidates: queryRows.map((row) => row.query),
31532
+ brandNames: brandTerms,
31533
+ canonicalDomains: [project.canonicalDomain, ...project.ownedDomains]
31534
+ }).droppedBranded);
31535
+ const brandedClicks = aggregateByKey(
31536
+ queryRows.filter((row) => brandedQueries.has(row.query)),
31537
+ gscPeriods,
31538
+ () => "all",
31539
+ (row) => row.clicks
31540
+ ).get("all") ?? [];
31541
+ const brandedImpressions = aggregateByKey(
31542
+ queryRows.filter((row) => brandedQueries.has(row.query)),
31543
+ gscPeriods,
31544
+ () => "all",
31545
+ (row) => row.impressions
31546
+ ).get("all") ?? [];
31547
+ const perQueryClicks = aggregateByKey(
31548
+ queryRows,
31549
+ gscPeriods,
31550
+ (row) => row.query,
31551
+ (row) => row.clicks
31552
+ );
31553
+ const perQueryImpressions = aggregateByKey(
31554
+ queryRows,
31555
+ gscPeriods,
31556
+ (row) => row.query,
31557
+ (row) => row.impressions
31558
+ );
31559
+ const gscPageRows = rawPageRows.map(parseGscPage).filter((row) => row !== null).filter((row) => hostIsIncluded(row.hostName) && pageIsIncluded(row.landingPage));
31560
+ const perPageClicks = aggregateByKey(
31561
+ gscPageRows,
31562
+ gscPeriods,
31563
+ (row) => `${row.hostName}\0${row.landingPage}`,
31564
+ (row) => row.clicks
31565
+ );
31566
+ const perPageImpressions = aggregateByKey(
31567
+ gscPageRows,
31568
+ gscPeriods,
31569
+ (row) => `${row.hostName}\0${row.landingPage}`,
31570
+ (row) => row.impressions
31571
+ );
31572
+ return gaMeasurementAnalysisDtoSchema.parse({
31573
+ window: parsedWindow.data,
31574
+ bucketDays: 30,
31575
+ filters: {
31576
+ hostScope: parsedHostScope.data,
31577
+ marketingHosts,
31578
+ pathPrefix,
31579
+ brandTerms,
31580
+ queryMixScope: "property"
31581
+ },
31582
+ acquisition: {
31583
+ status: state?.acquisitionStatus ?? "never-synced",
31584
+ error: state?.acquisitionError ?? null,
31585
+ syncedAt: state?.acquisitionSyncedAt ?? null,
31586
+ periods: gaAnchor ? sessionPeriods(gaPeriods, acquisitionTotals) : [],
31587
+ channels: rankEntries(acquisitionChannels).map(([channelGroup, values]) => ({
31588
+ channelGroup,
31589
+ periods: sessionPeriods(gaPeriods, values)
31590
+ })),
31591
+ pages: rankEntries(acquisitionPages).slice(0, limit).map(([key, values]) => {
31592
+ const [hostName, landingPage] = splitKey(key);
31593
+ return {
31594
+ hostName,
31595
+ landingPage,
31596
+ periods: sessionPeriods(gaPeriods, values)
31597
+ };
31598
+ })
31599
+ },
31600
+ leads: {
31601
+ status: state?.leadStatus ?? "never-synced",
31602
+ error: state?.leadError ?? null,
31603
+ syncedAt: state?.leadSyncedAt ?? null,
31604
+ attributionScope,
31605
+ hostAndPathFiltersApplied,
31606
+ periods: gaAnchor && hasLeadTimeline ? eventPeriods(gaPeriods, leadTotals) : [],
31607
+ channels: rankEntries(leadChannels).map(([channelGroup, values]) => ({
31608
+ channelGroup,
31609
+ periods: eventPeriods(gaPeriods, values)
31610
+ }))
31611
+ },
31612
+ searchDemand: gscAnchor === null ? {
31613
+ status: "unavailable",
31614
+ periods: [],
31615
+ queries: [],
31616
+ pages: [],
31617
+ latestDate: null
31618
+ } : {
31619
+ status: "ready",
31620
+ latestDate: gscAnchor,
31621
+ periods: gscPeriods.map((period2, index2) => ({
31622
+ ...period2,
31623
+ propertyClicks: propertyClicks[index2] ?? 0,
31624
+ propertyImpressions: propertyImpressions[index2] ?? 0,
31625
+ reportedQueryClicks: queryClicks[index2] ?? 0,
31626
+ reportedQueryImpressions: queryImpressions[index2] ?? 0,
31627
+ brandedClicks: brandedClicks[index2] ?? 0,
31628
+ brandedImpressions: brandedImpressions[index2] ?? 0,
31629
+ nonBrandedClicks: Math.max(
31630
+ 0,
31631
+ (queryClicks[index2] ?? 0) - (brandedClicks[index2] ?? 0)
31632
+ ),
31633
+ nonBrandedImpressions: Math.max(
31634
+ 0,
31635
+ (queryImpressions[index2] ?? 0) - (brandedImpressions[index2] ?? 0)
31636
+ ),
31637
+ unreportedClicks: Math.max(
31638
+ 0,
31639
+ (propertyClicks[index2] ?? 0) - (queryClicks[index2] ?? 0)
31640
+ ),
31641
+ unreportedImpressions: Math.max(
31642
+ 0,
31643
+ (propertyImpressions[index2] ?? 0) - (queryImpressions[index2] ?? 0)
31644
+ )
31645
+ })),
31646
+ queries: rankEntries(perQueryClicks).slice(0, limit).map(([query, values]) => ({
31647
+ query,
31648
+ classification: brandedQueries.has(query) ? "branded" : "non-branded",
31649
+ periods: clickPeriods(
31650
+ gscPeriods,
31651
+ values,
31652
+ perQueryImpressions.get(query) ?? []
31653
+ )
31654
+ })),
31655
+ pages: rankEntries(perPageImpressions).slice(0, limit).map(([key, impressions]) => {
31656
+ const [hostName, landingPage] = splitKey(key);
31657
+ return {
31658
+ hostName,
31659
+ landingPage,
31660
+ periods: clickPeriods(
31661
+ gscPeriods,
31662
+ perPageClicks.get(key) ?? [],
31663
+ impressions
31664
+ )
31665
+ };
31666
+ })
31667
+ }
31668
+ });
31669
+ }
31670
+ async function gaMeasurementAnalysisRoutes(app) {
31671
+ app.get("/projects/:name/ga/measurement-analysis", (request) => buildGaMeasurementAnalysis(app.db, request.params.name, request.query));
31672
+ }
31673
+
31233
31674
  // ../integration-wordpress/src/types.ts
31234
31675
  var WordpressApiError = class extends Error {
31235
31676
  statusCode;
@@ -32866,7 +33307,7 @@ async function wordpressRoutes(app, opts) {
32866
33307
 
32867
33308
  // ../api-routes/src/backlinks.ts
32868
33309
  import crypto26 from "crypto";
32869
- import { and as and25, asc as asc7, desc as desc14, eq as eq30, sql as sql14 } from "drizzle-orm";
33310
+ import { and as and26, asc as asc7, desc as desc14, eq as eq31, sql as sql15 } from "drizzle-orm";
32870
33311
 
32871
33312
  // ../integration-commoncrawl/src/constants.ts
32872
33313
  import os2 from "os";
@@ -33156,7 +33597,7 @@ async function queryBacklinks(opts) {
33156
33597
  const reversed = opts.targets.map(reverseDomain);
33157
33598
  const targetList = reversed.map(quote).join(", ");
33158
33599
  const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
33159
- const sql17 = `
33600
+ const sql18 = `
33160
33601
  WITH vertices AS (
33161
33602
  SELECT * FROM read_csv(
33162
33603
  ${quote(opts.vertexPath)},
@@ -33192,7 +33633,7 @@ async function queryBacklinks(opts) {
33192
33633
  const conn = await instance.connect();
33193
33634
  let rows;
33194
33635
  try {
33195
- const reader = await conn.runAndReadAll(sql17);
33636
+ const reader = await conn.runAndReadAll(sql18);
33196
33637
  rows = reader.getRowObjects();
33197
33638
  } finally {
33198
33639
  conn.disconnectSync?.();
@@ -33269,7 +33710,7 @@ function pruneCachedRelease(release, opts = {}) {
33269
33710
  }
33270
33711
 
33271
33712
  // ../api-routes/src/backlinks-filter.ts
33272
- import { and as and24, ne as ne4, notLike } from "drizzle-orm";
33713
+ import { and as and25, ne as ne4, notLike } from "drizzle-orm";
33273
33714
  var BACKLINK_FILTER_PATTERNS = [
33274
33715
  "*.google.com",
33275
33716
  "*.googleusercontent.com",
@@ -33292,7 +33733,7 @@ function backlinkCrawlerExclusionClause() {
33292
33733
  conditions.push(ne4(backlinkDomains.linkingDomain, pattern));
33293
33734
  }
33294
33735
  }
33295
- const combined = and24(...conditions);
33736
+ const combined = and25(...conditions);
33296
33737
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
33297
33738
  return combined;
33298
33739
  }
@@ -33363,11 +33804,11 @@ function mapRunRow(row) {
33363
33804
  }
33364
33805
  function latestSummaryForProject(db, projectId, source, release) {
33365
33806
  const conditions = [
33366
- eq30(backlinkSummaries.projectId, projectId),
33367
- eq30(backlinkSummaries.source, source)
33807
+ eq31(backlinkSummaries.projectId, projectId),
33808
+ eq31(backlinkSummaries.source, source)
33368
33809
  ];
33369
- if (release) conditions.push(eq30(backlinkSummaries.release, release));
33370
- return db.select().from(backlinkSummaries).where(and25(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
33810
+ if (release) conditions.push(eq31(backlinkSummaries.release, release));
33811
+ return db.select().from(backlinkSummaries).where(and26(...conditions)).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
33371
33812
  }
33372
33813
  function parseExcludeCrawlers(value) {
33373
33814
  if (!value) return false;
@@ -33375,19 +33816,19 @@ function parseExcludeCrawlers(value) {
33375
33816
  return lower === "1" || lower === "true" || lower === "yes";
33376
33817
  }
33377
33818
  function computeFilteredSummary(db, base) {
33378
- const baseDomainCondition = and25(
33379
- eq30(backlinkDomains.projectId, base.projectId),
33380
- eq30(backlinkDomains.source, base.source),
33381
- eq30(backlinkDomains.release, base.release)
33819
+ const baseDomainCondition = and26(
33820
+ eq31(backlinkDomains.projectId, base.projectId),
33821
+ eq31(backlinkDomains.source, base.source),
33822
+ eq31(backlinkDomains.release, base.release)
33382
33823
  );
33383
- const filteredCondition = and25(baseDomainCondition, backlinkCrawlerExclusionClause());
33824
+ const filteredCondition = and26(baseDomainCondition, backlinkCrawlerExclusionClause());
33384
33825
  const unfilteredAgg = db.select({
33385
- count: sql14`count(*)`,
33386
- total: sql14`coalesce(sum(${backlinkDomains.numHosts}), 0)`
33826
+ count: sql15`count(*)`,
33827
+ total: sql15`coalesce(sum(${backlinkDomains.numHosts}), 0)`
33387
33828
  }).from(backlinkDomains).where(baseDomainCondition).get();
33388
33829
  const filteredAgg = db.select({
33389
- count: sql14`count(*)`,
33390
- total: sql14`coalesce(sum(${backlinkDomains.numHosts}), 0)`
33830
+ count: sql15`count(*)`,
33831
+ total: sql15`coalesce(sum(${backlinkDomains.numHosts}), 0)`
33391
33832
  }).from(backlinkDomains).where(filteredCondition).get();
33392
33833
  const top10Rows = db.select({ numHosts: backlinkDomains.numHosts }).from(backlinkDomains).where(filteredCondition).orderBy(desc14(backlinkDomains.numHosts)).limit(10).all();
33393
33834
  const totalLinkingDomains = Number(filteredAgg?.count ?? 0);
@@ -33410,13 +33851,13 @@ function computeFilteredSummary(db, base) {
33410
33851
  };
33411
33852
  }
33412
33853
  function buildSourceAvailability(db, projectId, source, connected, excludeCrawlers) {
33413
- const summary = db.select().from(backlinkSummaries).where(and25(eq30(backlinkSummaries.projectId, projectId), eq30(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
33854
+ const summary = db.select().from(backlinkSummaries).where(and26(eq31(backlinkSummaries.projectId, projectId), eq31(backlinkSummaries.source, source))).orderBy(desc14(backlinkSummaries.queriedAt)).limit(1).get();
33414
33855
  let totalLinkingDomains = summary?.totalLinkingDomains ?? 0;
33415
33856
  if (summary && excludeCrawlers) {
33416
- const filtered = db.select({ count: sql14`count(*)` }).from(backlinkDomains).where(and25(
33417
- eq30(backlinkDomains.projectId, projectId),
33418
- eq30(backlinkDomains.source, source),
33419
- eq30(backlinkDomains.release, summary.release),
33857
+ const filtered = db.select({ count: sql15`count(*)` }).from(backlinkDomains).where(and26(
33858
+ eq31(backlinkDomains.projectId, projectId),
33859
+ eq31(backlinkDomains.source, source),
33860
+ eq31(backlinkDomains.release, summary.release),
33420
33861
  backlinkCrawlerExclusionClause()
33421
33862
  )).get();
33422
33863
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -33431,7 +33872,7 @@ function buildSourceAvailability(db, projectId, source, connected, excludeCrawle
33431
33872
  };
33432
33873
  }
33433
33874
  function computeSourceAvailability(db, project, bingStore, excludeCrawlers) {
33434
- const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq30(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
33875
+ const ccReadySync = db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq31(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
33435
33876
  const ccConnected = project.autoExtractBacklinks === true && !!ccReadySync;
33436
33877
  const bingConnected = !!bingStore?.getConnection(project.canonicalDomain);
33437
33878
  const sources = [
@@ -33487,7 +33928,7 @@ async function backlinksRoutes(app, opts) {
33487
33928
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
33488
33929
  );
33489
33930
  }
33490
- const existing = app.db.select().from(ccReleaseSyncs).where(eq30(ccReleaseSyncs.release, release)).get();
33931
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq31(ccReleaseSyncs.release, release)).get();
33491
33932
  const now = (/* @__PURE__ */ new Date()).toISOString();
33492
33933
  if (existing) {
33493
33934
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -33498,9 +33939,9 @@ async function backlinksRoutes(app, opts) {
33498
33939
  phaseDetail: null,
33499
33940
  error: null,
33500
33941
  updatedAt: now
33501
- }).where(eq30(ccReleaseSyncs.id, existing.id)).run();
33942
+ }).where(eq31(ccReleaseSyncs.id, existing.id)).run();
33502
33943
  opts.onReleaseSyncRequested(existing.id, release);
33503
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq30(ccReleaseSyncs.id, existing.id)).get();
33944
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq31(ccReleaseSyncs.id, existing.id)).get();
33504
33945
  return reply.status(200).send(mapSyncRow(refreshed));
33505
33946
  }
33506
33947
  const id = crypto26.randomUUID();
@@ -33512,7 +33953,7 @@ async function backlinksRoutes(app, opts) {
33512
33953
  updatedAt: now
33513
33954
  }).run();
33514
33955
  opts.onReleaseSyncRequested(id, release);
33515
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq30(ccReleaseSyncs.id, id)).get();
33956
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq31(ccReleaseSyncs.id, id)).get();
33516
33957
  return reply.status(201).send(mapSyncRow(inserted));
33517
33958
  });
33518
33959
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -33570,7 +34011,7 @@ async function backlinksRoutes(app, opts) {
33570
34011
  createdAt: now
33571
34012
  }).run();
33572
34013
  opts.onBacklinkExtractRequested(runId, project.id, release);
33573
- const run = app.db.select().from(runs).where(eq30(runs.id, runId)).get();
34014
+ const run = app.db.select().from(runs).where(eq31(runs.id, runId)).get();
33574
34015
  return reply.status(201).send(mapRunRow(run));
33575
34016
  });
33576
34017
  app.get(
@@ -33596,13 +34037,13 @@ async function backlinksRoutes(app, opts) {
33596
34037
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
33597
34038
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
33598
34039
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
33599
- const baseDomainCondition = and25(
33600
- eq30(backlinkDomains.projectId, project.id),
33601
- eq30(backlinkDomains.source, source),
33602
- eq30(backlinkDomains.release, targetRelease)
34040
+ const baseDomainCondition = and26(
34041
+ eq31(backlinkDomains.projectId, project.id),
34042
+ eq31(backlinkDomains.source, source),
34043
+ eq31(backlinkDomains.release, targetRelease)
33603
34044
  );
33604
- const domainCondition = excludeCrawlers ? and25(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
33605
- const totalRow = app.db.select({ count: sql14`count(*)` }).from(backlinkDomains).where(domainCondition).get();
34045
+ const domainCondition = excludeCrawlers ? and26(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
34046
+ const totalRow = app.db.select({ count: sql15`count(*)` }).from(backlinkDomains).where(domainCondition).get();
33606
34047
  const rows = app.db.select({
33607
34048
  linkingDomain: backlinkDomains.linkingDomain,
33608
34049
  numHosts: backlinkDomains.numHosts,
@@ -33625,7 +34066,7 @@ async function backlinksRoutes(app, opts) {
33625
34066
  async (request, reply) => {
33626
34067
  const project = resolveProject(app.db, request.params.name);
33627
34068
  const source = parseSourceParam(request.query.source);
33628
- const rows = app.db.select().from(backlinkSummaries).where(and25(eq30(backlinkSummaries.projectId, project.id), eq30(backlinkSummaries.source, source))).orderBy(asc7(backlinkSummaries.queriedAt)).all();
34069
+ const rows = app.db.select().from(backlinkSummaries).where(and26(eq31(backlinkSummaries.projectId, project.id), eq31(backlinkSummaries.source, source))).orderBy(asc7(backlinkSummaries.queriedAt)).all();
33629
34070
  const response = rows.map((r) => ({
33630
34071
  release: r.release,
33631
34072
  totalLinkingDomains: r.totalLinkingDomains,
@@ -33672,7 +34113,7 @@ async function backlinksRoutes(app, opts) {
33672
34113
  createdAt: now
33673
34114
  }).run();
33674
34115
  opts.onBingBacklinkSyncRequested(runId, project.id);
33675
- const run = app.db.select().from(runs).where(eq30(runs.id, runId)).get();
34116
+ const run = app.db.select().from(runs).where(eq31(runs.id, runId)).get();
33676
34117
  return reply.status(201).send(mapRunRow(run));
33677
34118
  }
33678
34119
  );
@@ -33681,7 +34122,7 @@ async function backlinksRoutes(app, opts) {
33681
34122
  // ../api-routes/src/traffic.ts
33682
34123
  import crypto28 from "crypto";
33683
34124
  import { Agent as UndiciAgent } from "undici";
33684
- import { and as and26, desc as desc15, eq as eq31, gte as gte7, lte as lte5, sql as sql15 } from "drizzle-orm";
34125
+ import { and as and27, desc as desc15, eq as eq32, gte as gte8, lte as lte6, sql as sql16 } from "drizzle-orm";
33685
34126
 
33686
34127
  // ../integration-cloud-run/src/auth.ts
33687
34128
  import crypto27 from "crypto";
@@ -36464,24 +36905,24 @@ var DEFAULT_AI_REFERRER_RULES = [
36464
36905
 
36465
36906
  // ../integration-traffic/src/classifier.ts
36466
36907
  var USER_FETCH_PURPOSE = "user-agent";
36467
- function normalizeHost(host) {
36908
+ function normalizeHost2(host) {
36468
36909
  return host.trim().toLowerCase().replace(/^www\./, "");
36469
36910
  }
36470
36911
  function hostMatches(host, domain) {
36471
- const normalizedHost = normalizeHost(host);
36472
- const normalizedDomain = normalizeHost(domain);
36912
+ const normalizedHost = normalizeHost2(host);
36913
+ const normalizedDomain = normalizeHost2(domain);
36473
36914
  return normalizedHost === normalizedDomain || normalizedHost.endsWith(`.${normalizedDomain}`);
36474
36915
  }
36475
36916
  function utmTokenMatchesDomain(utmSource, domain) {
36476
36917
  if (hostMatches(utmSource, domain)) return true;
36477
- const normalizedUtm = normalizeHost(utmSource);
36478
- const firstLabel = normalizeHost(domain).split(".")[0];
36918
+ const normalizedUtm = normalizeHost2(utmSource);
36919
+ const firstLabel = normalizeHost2(domain).split(".")[0];
36479
36920
  return Boolean(firstLabel) && normalizedUtm === firstLabel;
36480
36921
  }
36481
36922
  function hostFromUrl(value) {
36482
36923
  if (!value) return null;
36483
36924
  try {
36484
- return normalizeHost(new URL(value).hostname);
36925
+ return normalizeHost2(new URL(value).hostname);
36485
36926
  } catch {
36486
36927
  return null;
36487
36928
  }
@@ -36490,7 +36931,7 @@ function utmSourceFromQuery(queryString) {
36490
36931
  if (!queryString) return null;
36491
36932
  const params = new URLSearchParams(queryString);
36492
36933
  const source = params.get("utm_source");
36493
- return source ? normalizeHost(source) : null;
36934
+ return source ? normalizeHost2(source) : null;
36494
36935
  }
36495
36936
  function utmSourceFromUrl(value) {
36496
36937
  if (!value) return null;
@@ -36654,13 +37095,13 @@ function sessionWindowBucket(value, windowMs) {
36654
37095
  if (Number.isNaN(date.getTime())) return value;
36655
37096
  return new Date(Math.floor(date.getTime() / windowMs) * windowMs).toISOString();
36656
37097
  }
36657
- function normalizeHost2(host) {
37098
+ function normalizeHost3(host) {
36658
37099
  if (!host) return null;
36659
37100
  return host.trim().toLowerCase().replace(/^www\./, "") || null;
36660
37101
  }
36661
37102
  function sameHost(a, b) {
36662
- const normalizedA = normalizeHost2(a);
36663
- const normalizedB = normalizeHost2(b);
37103
+ const normalizedA = normalizeHost3(a);
37104
+ const normalizedB = normalizeHost3(b);
36664
37105
  return !!normalizedA && !!normalizedB && normalizedA === normalizedB;
36665
37106
  }
36666
37107
  function pathFromSameOriginReferer(event) {
@@ -37577,8 +38018,8 @@ async function runBackfillTask(options) {
37577
38018
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
37578
38019
  try {
37579
38020
  app.db.transaction((tx) => {
37580
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq31(runs.id, runId)).run();
37581
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq31(trafficSources.id, sourceRow.id)).run();
38021
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq32(runs.id, runId)).run();
38022
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq32(trafficSources.id, sourceRow.id)).run();
37582
38023
  });
37583
38024
  } catch {
37584
38025
  }
@@ -37593,7 +38034,7 @@ async function runBackfillTask(options) {
37593
38034
  if (allEvents.length === 0) {
37594
38035
  const finishedAt2 = (/* @__PURE__ */ new Date()).toISOString();
37595
38036
  try {
37596
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq31(runs.id, runId)).run();
38037
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: finishedAt2 }).where(eq32(runs.id, runId)).run();
37597
38038
  } catch {
37598
38039
  }
37599
38040
  return;
@@ -37615,31 +38056,31 @@ async function runBackfillTask(options) {
37615
38056
  try {
37616
38057
  app.db.transaction((tx) => {
37617
38058
  tx.delete(crawlerEventsHourly).where(
37618
- and26(
37619
- eq31(crawlerEventsHourly.sourceId, sourceRow.id),
37620
- gte7(crawlerEventsHourly.tsHour, windowStartIso),
37621
- lte5(crawlerEventsHourly.tsHour, windowEndIso)
38059
+ and27(
38060
+ eq32(crawlerEventsHourly.sourceId, sourceRow.id),
38061
+ gte8(crawlerEventsHourly.tsHour, windowStartIso),
38062
+ lte6(crawlerEventsHourly.tsHour, windowEndIso)
37622
38063
  )
37623
38064
  ).run();
37624
38065
  tx.delete(aiUserFetchEventsHourly).where(
37625
- and26(
37626
- eq31(aiUserFetchEventsHourly.sourceId, sourceRow.id),
37627
- gte7(aiUserFetchEventsHourly.tsHour, windowStartIso),
37628
- lte5(aiUserFetchEventsHourly.tsHour, windowEndIso)
38066
+ and27(
38067
+ eq32(aiUserFetchEventsHourly.sourceId, sourceRow.id),
38068
+ gte8(aiUserFetchEventsHourly.tsHour, windowStartIso),
38069
+ lte6(aiUserFetchEventsHourly.tsHour, windowEndIso)
37629
38070
  )
37630
38071
  ).run();
37631
38072
  tx.delete(aiReferralEventsHourly).where(
37632
- and26(
37633
- eq31(aiReferralEventsHourly.sourceId, sourceRow.id),
37634
- gte7(aiReferralEventsHourly.tsHour, windowStartIso),
37635
- lte5(aiReferralEventsHourly.tsHour, windowEndIso)
38073
+ and27(
38074
+ eq32(aiReferralEventsHourly.sourceId, sourceRow.id),
38075
+ gte8(aiReferralEventsHourly.tsHour, windowStartIso),
38076
+ lte6(aiReferralEventsHourly.tsHour, windowEndIso)
37636
38077
  )
37637
38078
  ).run();
37638
38079
  tx.delete(rawEventSamples).where(
37639
- and26(
37640
- eq31(rawEventSamples.sourceId, sourceRow.id),
37641
- gte7(rawEventSamples.ts, windowStartIso),
37642
- lte5(rawEventSamples.ts, windowEndIso)
38080
+ and27(
38081
+ eq32(rawEventSamples.sourceId, sourceRow.id),
38082
+ gte8(rawEventSamples.ts, windowStartIso),
38083
+ lte6(rawEventSamples.ts, windowEndIso)
37643
38084
  )
37644
38085
  ).run();
37645
38086
  for (const bucket of report.crawlerEventsHourly) {
@@ -37728,8 +38169,8 @@ async function runBackfillTask(options) {
37728
38169
  lastError: null,
37729
38170
  lastEventIds: newRingBuffer,
37730
38171
  updatedAt: finishedAt
37731
- }).where(eq31(trafficSources.id, sourceRow.id)).run();
37732
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq31(runs.id, runId)).run();
38172
+ }).where(eq32(trafficSources.id, sourceRow.id)).run();
38173
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq32(runs.id, runId)).run();
37733
38174
  });
37734
38175
  } catch (e) {
37735
38176
  markFailed(`Backfill rollup write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -37811,7 +38252,7 @@ async function trafficRoutes(app, opts) {
37811
38252
  createdAt: existing?.createdAt ?? now,
37812
38253
  updatedAt: now
37813
38254
  });
37814
- const activeSource = app.db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
38255
+ const activeSource = app.db.select().from(trafficSources).where(eq32(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
37815
38256
  const config = {
37816
38257
  gcpProjectId,
37817
38258
  serviceName: serviceName ?? null,
@@ -37827,8 +38268,8 @@ async function trafficRoutes(app, opts) {
37827
38268
  lastError: null,
37828
38269
  configJson: config,
37829
38270
  updatedAt: now
37830
- }).where(eq31(trafficSources.id, activeSource.id)).run();
37831
- sourceRow = app.db.select().from(trafficSources).where(eq31(trafficSources.id, activeSource.id)).get();
38271
+ }).where(eq32(trafficSources.id, activeSource.id)).run();
38272
+ sourceRow = app.db.select().from(trafficSources).where(eq32(trafficSources.id, activeSource.id)).get();
37832
38273
  } else {
37833
38274
  const newId = crypto28.randomUUID();
37834
38275
  app.db.insert(trafficSources).values({
@@ -37845,7 +38286,7 @@ async function trafficRoutes(app, opts) {
37845
38286
  createdAt: now,
37846
38287
  updatedAt: now
37847
38288
  }).run();
37848
- sourceRow = app.db.select().from(trafficSources).where(eq31(trafficSources.id, newId)).get();
38289
+ sourceRow = app.db.select().from(trafficSources).where(eq32(trafficSources.id, newId)).get();
37849
38290
  }
37850
38291
  writeAuditLog(app.db, {
37851
38292
  projectId: project.id,
@@ -37897,7 +38338,7 @@ async function trafficRoutes(app, opts) {
37897
38338
  createdAt: existing?.createdAt ?? now,
37898
38339
  updatedAt: now
37899
38340
  });
37900
- const activeSource = app.db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
38341
+ const activeSource = app.db.select().from(trafficSources).where(eq32(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
37901
38342
  const config = { baseUrl, username };
37902
38343
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
37903
38344
  let sourceRow;
@@ -37908,8 +38349,8 @@ async function trafficRoutes(app, opts) {
37908
38349
  lastError: null,
37909
38350
  configJson: config,
37910
38351
  updatedAt: now
37911
- }).where(eq31(trafficSources.id, activeSource.id)).run();
37912
- sourceRow = app.db.select().from(trafficSources).where(eq31(trafficSources.id, activeSource.id)).get();
38352
+ }).where(eq32(trafficSources.id, activeSource.id)).run();
38353
+ sourceRow = app.db.select().from(trafficSources).where(eq32(trafficSources.id, activeSource.id)).get();
37913
38354
  } else {
37914
38355
  const newId = crypto28.randomUUID();
37915
38356
  app.db.insert(trafficSources).values({
@@ -37926,7 +38367,7 @@ async function trafficRoutes(app, opts) {
37926
38367
  createdAt: now,
37927
38368
  updatedAt: now
37928
38369
  }).run();
37929
- sourceRow = app.db.select().from(trafficSources).where(eq31(trafficSources.id, newId)).get();
38370
+ sourceRow = app.db.select().from(trafficSources).where(eq32(trafficSources.id, newId)).get();
37930
38371
  }
37931
38372
  writeAuditLog(app.db, {
37932
38373
  projectId: project.id,
@@ -37980,7 +38421,7 @@ async function trafficRoutes(app, opts) {
37980
38421
  createdAt: existing?.createdAt ?? now,
37981
38422
  updatedAt: now
37982
38423
  });
37983
- const activeSource = app.db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
38424
+ const activeSource = app.db.select().from(trafficSources).where(eq32(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.vercel && row.status !== TrafficSourceStatuses.archived);
37984
38425
  const config = { projectId, teamId, environment };
37985
38426
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
37986
38427
  const { sourceRow, scheduleCreated } = app.db.transaction((tx) => {
@@ -37992,8 +38433,8 @@ async function trafficRoutes(app, opts) {
37992
38433
  lastError: null,
37993
38434
  configJson: config,
37994
38435
  updatedAt: now
37995
- }).where(eq31(trafficSources.id, activeSource.id)).run();
37996
- row = tx.select().from(trafficSources).where(eq31(trafficSources.id, activeSource.id)).get();
38436
+ }).where(eq32(trafficSources.id, activeSource.id)).run();
38437
+ row = tx.select().from(trafficSources).where(eq32(trafficSources.id, activeSource.id)).get();
37997
38438
  } else {
37998
38439
  const newId = crypto28.randomUUID();
37999
38440
  tx.insert(trafficSources).values({
@@ -38018,12 +38459,12 @@ async function trafficRoutes(app, opts) {
38018
38459
  createdAt: now,
38019
38460
  updatedAt: now
38020
38461
  }).run();
38021
- row = tx.select().from(trafficSources).where(eq31(trafficSources.id, newId)).get();
38462
+ row = tx.select().from(trafficSources).where(eq32(trafficSources.id, newId)).get();
38022
38463
  }
38023
38464
  const existingSchedule = tx.select().from(schedules).where(
38024
- and26(
38025
- eq31(schedules.projectId, project.id),
38026
- eq31(schedules.kind, SchedulableRunKinds["traffic-sync"])
38465
+ and27(
38466
+ eq32(schedules.projectId, project.id),
38467
+ eq32(schedules.kind, SchedulableRunKinds["traffic-sync"])
38027
38468
  )
38028
38469
  ).get();
38029
38470
  let created = false;
@@ -38072,7 +38513,7 @@ async function trafficRoutes(app, opts) {
38072
38513
  });
38073
38514
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
38074
38515
  const project = resolveProject(app.db, request.params.name);
38075
- const sourceRow = app.db.select().from(trafficSources).where(eq31(trafficSources.id, request.params.id)).get();
38516
+ const sourceRow = app.db.select().from(trafficSources).where(eq32(trafficSources.id, request.params.id)).get();
38076
38517
  if (!sourceRow || sourceRow.projectId !== project.id) {
38077
38518
  throw notFound("Traffic source", request.params.id);
38078
38519
  }
@@ -38098,8 +38539,8 @@ async function trafficRoutes(app, opts) {
38098
38539
  const markFailed = (msg, errorCode) => {
38099
38540
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
38100
38541
  app.db.transaction((tx) => {
38101
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq31(runs.id, runId)).run();
38102
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq31(trafficSources.id, sourceRow.id)).run();
38542
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq32(runs.id, runId)).run();
38543
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq32(trafficSources.id, sourceRow.id)).run();
38103
38544
  });
38104
38545
  try {
38105
38546
  opts.onTrafficSynced?.({
@@ -38180,7 +38621,7 @@ async function trafficRoutes(app, opts) {
38180
38621
  }
38181
38622
  const credential = credentialStore.getConnection(project.name);
38182
38623
  if (!credential) {
38183
- app.db.delete(runs).where(eq31(runs.id, runId)).run();
38624
+ app.db.delete(runs).where(eq32(runs.id, runId)).run();
38184
38625
  throw validationError(
38185
38626
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
38186
38627
  );
@@ -38229,12 +38670,12 @@ async function trafficRoutes(app, opts) {
38229
38670
  auditAction = "traffic.vercel.synced";
38230
38671
  const credentialStore = opts.vercelTrafficCredentialStore;
38231
38672
  if (!credentialStore) {
38232
- app.db.delete(runs).where(eq31(runs.id, runId)).run();
38673
+ app.db.delete(runs).where(eq32(runs.id, runId)).run();
38233
38674
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
38234
38675
  }
38235
38676
  const credential = credentialStore.getConnection(project.name);
38236
38677
  if (!credential) {
38237
- app.db.delete(runs).where(eq31(runs.id, runId)).run();
38678
+ app.db.delete(runs).where(eq32(runs.id, runId)).run();
38238
38679
  throw validationError(
38239
38680
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
38240
38681
  );
@@ -38334,7 +38775,7 @@ async function trafficRoutes(app, opts) {
38334
38775
  let aiReferralHitsCount = 0;
38335
38776
  let unknownHitsCount = 0;
38336
38777
  app.db.transaction((tx) => {
38337
- const latestRow = tx.select().from(trafficSources).where(eq31(trafficSources.id, sourceRow.id)).get();
38778
+ const latestRow = tx.select().from(trafficSources).where(eq32(trafficSources.id, sourceRow.id)).get();
38338
38779
  const previousIds = latestRow.lastEventIds ?? [];
38339
38780
  const seenEventIds = new Set(previousIds);
38340
38781
  const dedupedEvents = seenEventIds.size === 0 ? allEvents : allEvents.filter((e) => !seenEventIds.has(e.eventId));
@@ -38382,7 +38823,7 @@ async function trafficRoutes(app, opts) {
38382
38823
  crawlerEventsHourly.status
38383
38824
  ],
38384
38825
  set: {
38385
- hits: sql15`${crawlerEventsHourly.hits} + ${bucket.hits}`,
38826
+ hits: sql16`${crawlerEventsHourly.hits} + ${bucket.hits}`,
38386
38827
  sampledUserAgent: bucket.sampledUserAgent,
38387
38828
  updatedAt: finishedAt
38388
38829
  }
@@ -38415,7 +38856,7 @@ async function trafficRoutes(app, opts) {
38415
38856
  aiUserFetchEventsHourly.status
38416
38857
  ],
38417
38858
  set: {
38418
- hits: sql15`${aiUserFetchEventsHourly.hits} + ${bucket.hits}`,
38859
+ hits: sql16`${aiUserFetchEventsHourly.hits} + ${bucket.hits}`,
38419
38860
  sampledUserAgent: bucket.sampledUserAgent,
38420
38861
  updatedAt: finishedAt
38421
38862
  }
@@ -38455,9 +38896,9 @@ async function trafficRoutes(app, opts) {
38455
38896
  // colliding bucket adds its paid and organic arrivals to the ones
38456
38897
  // already there rather than overwriting a single label.
38457
38898
  set: {
38458
- sessionsOrHits: sql15`${aiReferralEventsHourly.sessionsOrHits} + ${bucket.hits}`,
38459
- paidSessionsOrHits: sql15`${aiReferralEventsHourly.paidSessionsOrHits} + ${bucket.paidHits}`,
38460
- organicSessionsOrHits: sql15`${aiReferralEventsHourly.organicSessionsOrHits} + ${bucket.organicHits}`,
38899
+ sessionsOrHits: sql16`${aiReferralEventsHourly.sessionsOrHits} + ${bucket.hits}`,
38900
+ paidSessionsOrHits: sql16`${aiReferralEventsHourly.paidSessionsOrHits} + ${bucket.paidHits}`,
38901
+ organicSessionsOrHits: sql16`${aiReferralEventsHourly.organicSessionsOrHits} + ${bucket.organicHits}`,
38461
38902
  updatedAt: finishedAt
38462
38903
  }
38463
38904
  }).run();
@@ -38509,8 +38950,8 @@ async function trafficRoutes(app, opts) {
38509
38950
  if (sourceRow.sourceType === TrafficSourceTypes.wordpress) {
38510
38951
  sourceUpdate.lastCursor = nextCursor ?? null;
38511
38952
  }
38512
- tx.update(trafficSources).set(sourceUpdate).where(eq31(trafficSources.id, sourceRow.id)).run();
38513
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq31(runs.id, runId)).run();
38953
+ tx.update(trafficSources).set(sourceUpdate).where(eq32(trafficSources.id, sourceRow.id)).run();
38954
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq32(runs.id, runId)).run();
38514
38955
  });
38515
38956
  writeAuditLog(app.db, {
38516
38957
  projectId: project.id,
@@ -38562,7 +39003,7 @@ async function trafficRoutes(app, opts) {
38562
39003
  });
38563
39004
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => {
38564
39005
  const project = resolveProject(app.db, request.params.name);
38565
- const sourceRow = app.db.select().from(trafficSources).where(eq31(trafficSources.id, request.params.id)).get();
39006
+ const sourceRow = app.db.select().from(trafficSources).where(eq32(trafficSources.id, request.params.id)).get();
38566
39007
  if (!sourceRow || sourceRow.projectId !== project.id) {
38567
39008
  throw notFound("Traffic source", request.params.id);
38568
39009
  }
@@ -38748,40 +39189,40 @@ async function trafficRoutes(app, opts) {
38748
39189
  function buildSourceDetail(projectId, row, since) {
38749
39190
  const crawlerPathRows = app.db.select({
38750
39191
  pathNormalized: crawlerEventsHourly.pathNormalized,
38751
- hits: sql15`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
39192
+ hits: sql16`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
38752
39193
  }).from(crawlerEventsHourly).where(
38753
- and26(
38754
- eq31(crawlerEventsHourly.sourceId, row.id),
38755
- gte7(crawlerEventsHourly.tsHour, since)
39194
+ and27(
39195
+ eq32(crawlerEventsHourly.sourceId, row.id),
39196
+ gte8(crawlerEventsHourly.tsHour, since)
38756
39197
  )
38757
39198
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
38758
39199
  const crawlerSegments = segmentCrawlerHits(
38759
39200
  crawlerPathRows.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
38760
39201
  );
38761
39202
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
38762
- const aiUserFetchTotals = app.db.select({ total: sql15`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
38763
- and26(
38764
- eq31(aiUserFetchEventsHourly.sourceId, row.id),
38765
- gte7(aiUserFetchEventsHourly.tsHour, since)
39203
+ const aiUserFetchTotals = app.db.select({ total: sql16`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
39204
+ and27(
39205
+ eq32(aiUserFetchEventsHourly.sourceId, row.id),
39206
+ gte8(aiUserFetchEventsHourly.tsHour, since)
38766
39207
  )
38767
39208
  ).get();
38768
- const aiTotals = app.db.select({ total: sql15`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
38769
- and26(
38770
- eq31(aiReferralEventsHourly.sourceId, row.id),
38771
- gte7(aiReferralEventsHourly.tsHour, since)
39209
+ const aiTotals = app.db.select({ total: sql16`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
39210
+ and27(
39211
+ eq32(aiReferralEventsHourly.sourceId, row.id),
39212
+ gte8(aiReferralEventsHourly.tsHour, since)
38772
39213
  )
38773
39214
  ).get();
38774
- const sampleTotals = app.db.select({ total: sql15`COUNT(*)` }).from(rawEventSamples).where(
38775
- and26(
38776
- eq31(rawEventSamples.sourceId, row.id),
38777
- gte7(rawEventSamples.ts, since)
39215
+ const sampleTotals = app.db.select({ total: sql16`COUNT(*)` }).from(rawEventSamples).where(
39216
+ and27(
39217
+ eq32(rawEventSamples.sourceId, row.id),
39218
+ gte8(rawEventSamples.ts, since)
38778
39219
  )
38779
39220
  ).get();
38780
39221
  const latestRun = app.db.select().from(runs).where(
38781
- and26(
38782
- eq31(runs.projectId, projectId),
38783
- eq31(runs.kind, RunKinds["traffic-sync"]),
38784
- eq31(runs.sourceId, row.id)
39222
+ and27(
39223
+ eq32(runs.projectId, projectId),
39224
+ eq32(runs.kind, RunKinds["traffic-sync"]),
39225
+ eq32(runs.sourceId, row.id)
38785
39226
  )
38786
39227
  ).orderBy(desc15(runs.startedAt)).limit(1).get();
38787
39228
  return {
@@ -38812,7 +39253,7 @@ async function trafficRoutes(app, opts) {
38812
39253
  "`advanceToNow` must be `true`. There is no implicit reset."
38813
39254
  );
38814
39255
  }
38815
- const sourceRow = app.db.select().from(trafficSources).where(and26(eq31(trafficSources.projectId, project.id), eq31(trafficSources.id, request.params.id))).get();
39256
+ const sourceRow = app.db.select().from(trafficSources).where(and27(eq32(trafficSources.projectId, project.id), eq32(trafficSources.id, request.params.id))).get();
38816
39257
  if (!sourceRow) {
38817
39258
  throw notFound("traffic source", request.params.id);
38818
39259
  }
@@ -38829,7 +39270,7 @@ async function trafficRoutes(app, opts) {
38829
39270
  status: TrafficSourceStatuses.connected,
38830
39271
  lastError: null,
38831
39272
  updatedAt: now
38832
- }).where(eq31(trafficSources.id, sourceRow.id)).run();
39273
+ }).where(eq32(trafficSources.id, sourceRow.id)).run();
38833
39274
  writeAuditLog(tx, auditFromRequest(request, {
38834
39275
  projectId: project.id,
38835
39276
  actor: "api",
@@ -38837,20 +39278,20 @@ async function trafficRoutes(app, opts) {
38837
39278
  entityType: "traffic_source",
38838
39279
  entityId: sourceRow.id
38839
39280
  }));
38840
- updatedRow = tx.select().from(trafficSources).where(eq31(trafficSources.id, sourceRow.id)).get();
39281
+ updatedRow = tx.select().from(trafficSources).where(eq32(trafficSources.id, sourceRow.id)).get();
38841
39282
  });
38842
39283
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
38843
39284
  });
38844
39285
  app.get("/projects/:name/traffic/sources", async (request) => {
38845
39286
  const project = resolveProject(app.db, request.params.name);
38846
- const rows = app.db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
39287
+ const rows = app.db.select().from(trafficSources).where(eq32(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
38847
39288
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
38848
39289
  const response = { sources };
38849
39290
  return response;
38850
39291
  });
38851
39292
  app.get("/projects/:name/traffic/status", async (request) => {
38852
39293
  const project = resolveProject(app.db, request.params.name);
38853
- const rows = app.db.select().from(trafficSources).where(eq31(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
39294
+ const rows = app.db.select().from(trafficSources).where(eq32(trafficSources.projectId, project.id)).orderBy(desc15(trafficSources.createdAt)).all();
38854
39295
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
38855
39296
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
38856
39297
  const response = { sources };
@@ -38860,7 +39301,7 @@ async function trafficRoutes(app, opts) {
38860
39301
  "/projects/:name/traffic/sources/:id",
38861
39302
  async (request) => {
38862
39303
  const project = resolveProject(app.db, request.params.name);
38863
- const row = app.db.select().from(trafficSources).where(eq31(trafficSources.id, request.params.id)).get();
39304
+ const row = app.db.select().from(trafficSources).where(eq32(trafficSources.id, request.params.id)).get();
38864
39305
  if (!row || row.projectId !== project.id) {
38865
39306
  throw notFound("Traffic source", request.params.id);
38866
39307
  }
@@ -38925,26 +39366,26 @@ async function trafficRoutes(app, opts) {
38925
39366
  const seriesByBucket = /* @__PURE__ */ new Map();
38926
39367
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
38927
39368
  const crawlerFilters = [
38928
- eq31(crawlerEventsHourly.projectId, project.id),
38929
- gte7(crawlerEventsHourly.tsHour, sinceIso),
38930
- lte5(crawlerEventsHourly.tsHour, untilIso)
39369
+ eq32(crawlerEventsHourly.projectId, project.id),
39370
+ gte8(crawlerEventsHourly.tsHour, sinceIso),
39371
+ lte6(crawlerEventsHourly.tsHour, untilIso)
38931
39372
  ];
38932
- if (sourceIdParam) crawlerFilters.push(eq31(crawlerEventsHourly.sourceId, sourceIdParam));
38933
- const crawlerWhere = and26(...crawlerFilters);
39373
+ if (sourceIdParam) crawlerFilters.push(eq32(crawlerEventsHourly.sourceId, sourceIdParam));
39374
+ const crawlerWhere = and27(...crawlerFilters);
38934
39375
  const pathTotals = app.db.select({
38935
39376
  pathNormalized: crawlerEventsHourly.pathNormalized,
38936
- hits: sql15`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
38937
- rows: sql15`COUNT(*)`
39377
+ hits: sql16`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
39378
+ rows: sql16`COUNT(*)`
38938
39379
  }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerEventsHourly.pathNormalized).all();
38939
39380
  crawlerSegments = segmentCrawlerHits(
38940
39381
  pathTotals.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
38941
39382
  );
38942
39383
  crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
38943
39384
  totalEventRows += pathTotals.reduce((sum, row) => sum + Number(row.rows), 0);
38944
- const crawlerSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql15`substr(${crawlerEventsHourly.tsHour}, 1, 10)` : crawlerEventsHourly.tsHour;
39385
+ const crawlerSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql16`substr(${crawlerEventsHourly.tsHour}, 1, 10)` : crawlerEventsHourly.tsHour;
38945
39386
  const crawlerSeries = app.db.select({
38946
39387
  bucket: crawlerSeriesBucket,
38947
- hits: sql15`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
39388
+ hits: sql16`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
38948
39389
  }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerSeriesBucket).all();
38949
39390
  for (const row of crawlerSeries) {
38950
39391
  trafficSeriesPoint(seriesByBucket, row.bucket).crawlerHits = Number(row.hits);
@@ -38967,22 +39408,22 @@ async function trafficRoutes(app, opts) {
38967
39408
  }
38968
39409
  if (kind === "all" || kind === TrafficEventKinds["ai-user-fetch"]) {
38969
39410
  const userFetchFilters = [
38970
- eq31(aiUserFetchEventsHourly.projectId, project.id),
38971
- gte7(aiUserFetchEventsHourly.tsHour, sinceIso),
38972
- lte5(aiUserFetchEventsHourly.tsHour, untilIso)
39411
+ eq32(aiUserFetchEventsHourly.projectId, project.id),
39412
+ gte8(aiUserFetchEventsHourly.tsHour, sinceIso),
39413
+ lte6(aiUserFetchEventsHourly.tsHour, untilIso)
38973
39414
  ];
38974
- if (sourceIdParam) userFetchFilters.push(eq31(aiUserFetchEventsHourly.sourceId, sourceIdParam));
38975
- const userFetchWhere = and26(...userFetchFilters);
39415
+ if (sourceIdParam) userFetchFilters.push(eq32(aiUserFetchEventsHourly.sourceId, sourceIdParam));
39416
+ const userFetchWhere = and27(...userFetchFilters);
38976
39417
  const total = app.db.select({
38977
- total: sql15`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
38978
- rows: sql15`COUNT(*)`
39418
+ total: sql16`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
39419
+ rows: sql16`COUNT(*)`
38979
39420
  }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
38980
39421
  aiUserFetchTotal = Number(total?.total ?? 0);
38981
39422
  totalEventRows += Number(total?.rows ?? 0);
38982
- const userFetchSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql15`substr(${aiUserFetchEventsHourly.tsHour}, 1, 10)` : aiUserFetchEventsHourly.tsHour;
39423
+ const userFetchSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql16`substr(${aiUserFetchEventsHourly.tsHour}, 1, 10)` : aiUserFetchEventsHourly.tsHour;
38983
39424
  const userFetchSeries = app.db.select({
38984
39425
  bucket: userFetchSeriesBucket,
38985
- hits: sql15`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
39426
+ hits: sql16`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
38986
39427
  }).from(aiUserFetchEventsHourly).where(userFetchWhere).groupBy(userFetchSeriesBucket).all();
38987
39428
  for (const row of userFetchSeries) {
38988
39429
  trafficSeriesPoint(seriesByBucket, row.bucket).aiUserFetchHits = Number(row.hits);
@@ -39004,17 +39445,17 @@ async function trafficRoutes(app, opts) {
39004
39445
  }
39005
39446
  if (kind === "all" || kind === TrafficEventKinds["ai-referral"]) {
39006
39447
  const aiFilters = [
39007
- eq31(aiReferralEventsHourly.projectId, project.id),
39008
- gte7(aiReferralEventsHourly.tsHour, sinceIso),
39009
- lte5(aiReferralEventsHourly.tsHour, untilIso)
39448
+ eq32(aiReferralEventsHourly.projectId, project.id),
39449
+ gte8(aiReferralEventsHourly.tsHour, sinceIso),
39450
+ lte6(aiReferralEventsHourly.tsHour, untilIso)
39010
39451
  ];
39011
- if (sourceIdParam) aiFilters.push(eq31(aiReferralEventsHourly.sourceId, sourceIdParam));
39012
- const aiWhere = and26(...aiFilters);
39452
+ if (sourceIdParam) aiFilters.push(eq32(aiReferralEventsHourly.sourceId, sourceIdParam));
39453
+ const aiWhere = and27(...aiFilters);
39013
39454
  const total = app.db.select({
39014
- total: sql15`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
39015
- paid: sql15`COALESCE(SUM(${aiReferralEventsHourly.paidSessionsOrHits}), 0)`,
39016
- organic: sql15`COALESCE(SUM(${aiReferralEventsHourly.organicSessionsOrHits}), 0)`,
39017
- rows: sql15`COUNT(*)`
39455
+ total: sql16`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
39456
+ paid: sql16`COALESCE(SUM(${aiReferralEventsHourly.paidSessionsOrHits}), 0)`,
39457
+ organic: sql16`COALESCE(SUM(${aiReferralEventsHourly.organicSessionsOrHits}), 0)`,
39458
+ rows: sql16`COUNT(*)`
39018
39459
  }).from(aiReferralEventsHourly).where(aiWhere).get();
39019
39460
  aiReferralCounts = aiReferralClassCounts(
39020
39461
  Number(total?.total ?? 0),
@@ -39022,10 +39463,10 @@ async function trafficRoutes(app, opts) {
39022
39463
  Number(total?.organic ?? 0)
39023
39464
  );
39024
39465
  totalEventRows += Number(total?.rows ?? 0);
39025
- const referralSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql15`substr(${aiReferralEventsHourly.tsHour}, 1, 10)` : aiReferralEventsHourly.tsHour;
39466
+ const referralSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql16`substr(${aiReferralEventsHourly.tsHour}, 1, 10)` : aiReferralEventsHourly.tsHour;
39026
39467
  const referralSeries = app.db.select({
39027
39468
  bucket: referralSeriesBucket,
39028
- hits: sql15`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
39469
+ hits: sql16`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`
39029
39470
  }).from(aiReferralEventsHourly).where(aiWhere).groupBy(referralSeriesBucket).all();
39030
39471
  for (const row of referralSeries) {
39031
39472
  trafficSeriesPoint(seriesByBucket, row.bucket).aiReferralHits = Number(row.hits);
@@ -39328,7 +39769,7 @@ function readInstalledManifest(skillDir) {
39328
39769
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck];
39329
39770
 
39330
39771
  // ../api-routes/src/doctor/checks/backlinks.ts
39331
- import { and as and27, eq as eq32 } from "drizzle-orm";
39772
+ import { and as and28, eq as eq33 } from "drizzle-orm";
39332
39773
  function skippedNoProject() {
39333
39774
  return {
39334
39775
  status: CheckStatuses.skipped,
@@ -39344,8 +39785,8 @@ var BACKLINKS_CHECKS = [
39344
39785
  title: "Backlinks source connected",
39345
39786
  run: (ctx) => {
39346
39787
  if (!ctx.project) return skippedNoProject();
39347
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq32(projects.id, ctx.project.id)).get();
39348
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq32(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
39788
+ const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq33(projects.id, ctx.project.id)).get();
39789
+ const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq33(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
39349
39790
  const ccConnected = projectRow?.autoExtract === true && !!readySync;
39350
39791
  const bingConnected = !!ctx.bingConnectionStore?.getConnection(ctx.project.canonicalDomain);
39351
39792
  const connected = [];
@@ -39360,9 +39801,9 @@ var BACKLINKS_CHECKS = [
39360
39801
  details: { commoncrawl: ccConnected, bingWebmaster: bingConnected }
39361
39802
  };
39362
39803
  }
39363
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and27(
39364
- eq32(backlinkSummaries.projectId, ctx.project.id),
39365
- eq32(backlinkSummaries.source, BacklinkSources.commoncrawl)
39804
+ const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and28(
39805
+ eq33(backlinkSummaries.projectId, ctx.project.id),
39806
+ eq33(backlinkSummaries.source, BacklinkSources.commoncrawl)
39366
39807
  )).limit(1).get() : false;
39367
39808
  return {
39368
39809
  status: CheckStatuses.ok,
@@ -39522,7 +39963,7 @@ var BING_AUTH_CHECKS = [
39522
39963
  ];
39523
39964
 
39524
39965
  // ../api-routes/src/doctor/checks/content.ts
39525
- import { eq as eq33 } from "drizzle-orm";
39966
+ import { eq as eq34 } from "drizzle-orm";
39526
39967
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
39527
39968
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
39528
39969
  function skippedNoProject2() {
@@ -39535,7 +39976,7 @@ function skippedNoProject2() {
39535
39976
  }
39536
39977
  function loadProject(ctx) {
39537
39978
  if (!ctx.project) return null;
39538
- return ctx.db.select().from(projects).where(eq33(projects.id, ctx.project.id)).get() ?? null;
39979
+ return ctx.db.select().from(projects).where(eq34(projects.id, ctx.project.id)).get() ?? null;
39539
39980
  }
39540
39981
  function percent(value) {
39541
39982
  return Math.round(value * 100);
@@ -39627,7 +40068,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
39627
40068
  );
39628
40069
 
39629
40070
  // ../api-routes/src/doctor/checks/ads.ts
39630
- import { eq as eq34 } from "drizzle-orm";
40071
+ import { eq as eq35 } from "drizzle-orm";
39631
40072
  var RECENT_SYNC_WARN_DAYS = 7;
39632
40073
  var RECENT_SYNC_FAIL_DAYS = 30;
39633
40074
  var adsConnectionCheck = {
@@ -39644,7 +40085,7 @@ var adsConnectionCheck = {
39644
40085
  remediation: null
39645
40086
  };
39646
40087
  }
39647
- const row = ctx.db.select().from(adsConnections).where(eq34(adsConnections.projectId, ctx.project.id)).get();
40088
+ const row = ctx.db.select().from(adsConnections).where(eq35(adsConnections.projectId, ctx.project.id)).get();
39648
40089
  if (!row) {
39649
40090
  return {
39650
40091
  status: CheckStatuses.skipped,
@@ -39694,7 +40135,7 @@ var adsRecentSyncCheck = {
39694
40135
  remediation: null
39695
40136
  };
39696
40137
  }
39697
- const row = ctx.db.select().from(adsConnections).where(eq34(adsConnections.projectId, ctx.project.id)).get();
40138
+ const row = ctx.db.select().from(adsConnections).where(eq35(adsConnections.projectId, ctx.project.id)).get();
39698
40139
  if (!row) {
39699
40140
  return {
39700
40141
  status: CheckStatuses.skipped,
@@ -39885,7 +40326,7 @@ var ga4ConnectionCheck = {
39885
40326
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
39886
40327
 
39887
40328
  // ../api-routes/src/doctor/checks/gbp-auth.ts
39888
- import { and as and28, eq as eq35 } from "drizzle-orm";
40329
+ import { and as and29, eq as eq36 } from "drizzle-orm";
39889
40330
  var RECENT_SYNC_WARN_DAYS2 = 7;
39890
40331
  var RECENT_SYNC_FAIL_DAYS2 = 30;
39891
40332
  function skippedNoProject3() {
@@ -40118,7 +40559,7 @@ var recentSyncCheck = {
40118
40559
  title: "GBP recent sync",
40119
40560
  run: (ctx) => {
40120
40561
  if (!ctx.project) return skippedNoProject3();
40121
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and28(eq35(gbpLocations.projectId, ctx.project.id), eq35(gbpLocations.selected, true))).all();
40562
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and29(eq36(gbpLocations.projectId, ctx.project.id), eq36(gbpLocations.selected, true))).all();
40122
40563
  if (selected.length === 0) {
40123
40564
  return {
40124
40565
  status: CheckStatuses.skipped,
@@ -40178,7 +40619,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
40178
40619
  );
40179
40620
 
40180
40621
  // ../api-routes/src/doctor/checks/places.ts
40181
- import { eq as eq36 } from "drizzle-orm";
40622
+ import { eq as eq37 } from "drizzle-orm";
40182
40623
  var apiKeyCheck = {
40183
40624
  id: "gbp.places.api-key",
40184
40625
  category: CheckCategories.auth,
@@ -40223,7 +40664,7 @@ var apiKeyCheck = {
40223
40664
  details: { tier: cfg.tier }
40224
40665
  };
40225
40666
  }
40226
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq36(gbpLocations.projectId, ctx.project.id)).all();
40667
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq37(gbpLocations.projectId, ctx.project.id)).all();
40227
40668
  const selected = rows.filter((r) => r.selected);
40228
40669
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
40229
40670
  const details = {
@@ -40720,7 +41161,7 @@ var RUNTIME_STATE_CHECKS = [
40720
41161
  ];
40721
41162
 
40722
41163
  // ../api-routes/src/doctor/checks/traffic-source.ts
40723
- import { and as and29, eq as eq37, gte as gte8, ne as ne5, sql as sql16 } from "drizzle-orm";
41164
+ import { and as and30, eq as eq38, gte as gte9, ne as ne5, sql as sql17 } from "drizzle-orm";
40724
41165
  var RECENT_DATA_WARN_DAYS = 7;
40725
41166
  var RECENT_DATA_FAIL_DAYS = 30;
40726
41167
  function skippedNoProject5() {
@@ -40734,8 +41175,8 @@ function skippedNoProject5() {
40734
41175
  function loadProbes(ctx) {
40735
41176
  if (!ctx.project) return [];
40736
41177
  const rows = ctx.db.select().from(trafficSources).where(
40737
- and29(
40738
- eq37(trafficSources.projectId, ctx.project.id),
41178
+ and30(
41179
+ eq38(trafficSources.projectId, ctx.project.id),
40739
41180
  ne5(trafficSources.status, TrafficSourceStatuses.archived)
40740
41181
  )
40741
41182
  ).all();
@@ -40814,18 +41255,18 @@ var recentDataCheck = {
40814
41255
  const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
40815
41256
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
40816
41257
  const recentCrawlers = Number(
40817
- ctx.db.select({ total: sql16`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
40818
- and29(
40819
- eq37(crawlerEventsHourly.projectId, ctx.project.id),
40820
- gte8(crawlerEventsHourly.tsHour, warnCutoff)
41258
+ ctx.db.select({ total: sql17`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
41259
+ and30(
41260
+ eq38(crawlerEventsHourly.projectId, ctx.project.id),
41261
+ gte9(crawlerEventsHourly.tsHour, warnCutoff)
40821
41262
  )
40822
41263
  ).get()?.total ?? 0
40823
41264
  );
40824
41265
  const recentReferrals = Number(
40825
- ctx.db.select({ total: sql16`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
40826
- and29(
40827
- eq37(aiReferralEventsHourly.projectId, ctx.project.id),
40828
- gte8(aiReferralEventsHourly.tsHour, warnCutoff)
41266
+ ctx.db.select({ total: sql17`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
41267
+ and30(
41268
+ eq38(aiReferralEventsHourly.projectId, ctx.project.id),
41269
+ gte9(aiReferralEventsHourly.tsHour, warnCutoff)
40829
41270
  )
40830
41271
  ).get()?.total ?? 0
40831
41272
  );
@@ -40838,18 +41279,18 @@ var recentDataCheck = {
40838
41279
  };
40839
41280
  }
40840
41281
  const olderCrawlers = Number(
40841
- ctx.db.select({ total: sql16`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
40842
- and29(
40843
- eq37(crawlerEventsHourly.projectId, ctx.project.id),
40844
- gte8(crawlerEventsHourly.tsHour, failCutoff)
41282
+ ctx.db.select({ total: sql17`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
41283
+ and30(
41284
+ eq38(crawlerEventsHourly.projectId, ctx.project.id),
41285
+ gte9(crawlerEventsHourly.tsHour, failCutoff)
40845
41286
  )
40846
41287
  ).get()?.total ?? 0
40847
41288
  );
40848
41289
  const olderReferrals = Number(
40849
- ctx.db.select({ total: sql16`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
40850
- and29(
40851
- eq37(aiReferralEventsHourly.projectId, ctx.project.id),
40852
- gte8(aiReferralEventsHourly.tsHour, failCutoff)
41290
+ ctx.db.select({ total: sql17`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
41291
+ and30(
41292
+ eq38(aiReferralEventsHourly.projectId, ctx.project.id),
41293
+ gte9(aiReferralEventsHourly.tsHour, failCutoff)
40853
41294
  )
40854
41295
  ).get()?.total ?? 0
40855
41296
  );
@@ -41266,7 +41707,7 @@ async function doctorRoutes(app, opts) {
41266
41707
 
41267
41708
  // ../api-routes/src/discovery/routes.ts
41268
41709
  import crypto30 from "crypto";
41269
- import { and as and30, desc as desc16, eq as eq38, gte as gte9, inArray as inArray13, isNull as isNull3, or as or7 } from "drizzle-orm";
41710
+ import { and as and31, desc as desc16, eq as eq39, gte as gte10, inArray as inArray13, isNull as isNull3, or as or8 } from "drizzle-orm";
41270
41711
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
41271
41712
  async function discoveryRoutes(app, opts) {
41272
41713
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -41300,13 +41741,13 @@ async function discoveryRoutes(app, opts) {
41300
41741
  const now = (/* @__PURE__ */ new Date()).toISOString();
41301
41742
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
41302
41743
  const decision = app.db.transaction((tx) => {
41303
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and30(
41304
- eq38(discoverySessions.projectId, project.id),
41305
- eq38(discoverySessions.icpDescription, icpDescription),
41744
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and31(
41745
+ eq39(discoverySessions.projectId, project.id),
41746
+ eq39(discoverySessions.icpDescription, icpDescription),
41306
41747
  // Buyer is part of session identity: it changes the seed prompt's
41307
41748
  // semantics, so a request with a different (or no) buyer must start
41308
41749
  // its own session, never adopt another buyer's probes.
41309
- parsed.data.buyerDescription == null ? isNull3(discoverySessions.buyerDescription) : eq38(discoverySessions.buyerDescription, parsed.data.buyerDescription),
41750
+ parsed.data.buyerDescription == null ? isNull3(discoverySessions.buyerDescription) : eq39(discoverySessions.buyerDescription, parsed.data.buyerDescription),
41310
41751
  // Locations are identity too: a different service-area subset seeds
41311
41752
  // and probes a different geo, so it must never reuse another geo's
41312
41753
  // session. resolveLocations is deterministic (project-config order),
@@ -41316,17 +41757,17 @@ async function discoveryRoutes(app, opts) {
41316
41757
  // locations a NULL row's subset is unknowable, so it conservatively
41317
41758
  // never reuses — a one-time, bounded (2h window) non-reuse after
41318
41759
  // upgrade, never a wrong reuse.
41319
- locations.length === 0 ? or7(isNull3(discoverySessions.locations), eq38(discoverySessions.locations, locations)) : eq38(discoverySessions.locations, locations),
41760
+ locations.length === 0 ? or8(isNull3(discoverySessions.locations), eq39(discoverySessions.locations, locations)) : eq39(discoverySessions.locations, locations),
41320
41761
  // Seed provider set is identity: a different phrasing distribution
41321
41762
  // must never reuse another set's session. Null (= Gemini-only
41322
41763
  // default, including explicit ['gemini']) matches legacy rows.
41323
- seedProviders == null ? isNull3(discoverySessions.seedProviders) : eq38(discoverySessions.seedProviders, seedProviders),
41764
+ seedProviders == null ? isNull3(discoverySessions.seedProviders) : eq39(discoverySessions.seedProviders, seedProviders),
41324
41765
  inArray13(discoverySessions.status, [
41325
41766
  DiscoverySessionStatuses.queued,
41326
41767
  DiscoverySessionStatuses.seeding,
41327
41768
  DiscoverySessionStatuses.probing
41328
41769
  ]),
41329
- gte9(discoverySessions.createdAt, ageFloorIso)
41770
+ gte10(discoverySessions.createdAt, ageFloorIso)
41330
41771
  )).orderBy(desc16(discoverySessions.createdAt)).get();
41331
41772
  if (existing && existing.runId) {
41332
41773
  return { reused: true, sessionId: existing.id, runId: existing.runId };
@@ -41396,7 +41837,7 @@ async function discoveryRoutes(app, opts) {
41396
41837
  const project = resolveProject(app.db, request.params.name);
41397
41838
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
41398
41839
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
41399
- const rows = app.db.select().from(discoverySessions).where(eq38(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
41840
+ const rows = app.db.select().from(discoverySessions).where(eq39(discoverySessions.projectId, project.id)).orderBy(desc16(discoverySessions.createdAt)).limit(limit).all();
41400
41841
  return reply.send(rows.map(serializeSession));
41401
41842
  }
41402
41843
  );
@@ -41404,11 +41845,11 @@ async function discoveryRoutes(app, opts) {
41404
41845
  "/projects/:name/discover/sessions/:id",
41405
41846
  async (request, reply) => {
41406
41847
  const project = resolveProject(app.db, request.params.name);
41407
- const session = app.db.select().from(discoverySessions).where(eq38(discoverySessions.id, request.params.id)).get();
41848
+ const session = app.db.select().from(discoverySessions).where(eq39(discoverySessions.id, request.params.id)).get();
41408
41849
  if (!session || session.projectId !== project.id) {
41409
41850
  throw notFound("Discovery session", request.params.id);
41410
41851
  }
41411
- const probeRows = app.db.select().from(discoveryProbes).where(eq38(discoveryProbes.sessionId, session.id)).all();
41852
+ const probeRows = app.db.select().from(discoveryProbes).where(eq39(discoveryProbes.sessionId, session.id)).all();
41412
41853
  const detail = {
41413
41854
  ...serializeSession(session),
41414
41855
  probes: probeRows.map(serializeProbe)
@@ -41420,7 +41861,7 @@ async function discoveryRoutes(app, opts) {
41420
41861
  "/projects/:name/discover/sessions/:id/harvest",
41421
41862
  async (request, reply) => {
41422
41863
  const project = resolveProject(app.db, request.params.name);
41423
- const session = app.db.select().from(discoverySessions).where(eq38(discoverySessions.id, request.params.id)).get();
41864
+ const session = app.db.select().from(discoverySessions).where(eq39(discoverySessions.id, request.params.id)).get();
41424
41865
  if (!session || session.projectId !== project.id) {
41425
41866
  throw notFound("Discovery session", request.params.id);
41426
41867
  }
@@ -41428,7 +41869,7 @@ async function discoveryRoutes(app, opts) {
41428
41869
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
41429
41870
  const applyAnchor = request.query.anchor !== "false";
41430
41871
  const provider = session.seedProvider ?? "gemini";
41431
- const probeRows = app.db.select().from(discoveryProbes).where(eq38(discoveryProbes.sessionId, session.id)).all();
41872
+ const probeRows = app.db.select().from(discoveryProbes).where(eq39(discoveryProbes.sessionId, session.id)).all();
41432
41873
  const extract = opts.harvestSearchQueries;
41433
41874
  const probesWithQueries = probeRows.map((row) => {
41434
41875
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -41439,7 +41880,7 @@ async function discoveryRoutes(app, opts) {
41439
41880
  return { searchQueries: [] };
41440
41881
  }
41441
41882
  });
41442
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq38(queries.projectId, project.id)).all().map((r) => r.query);
41883
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq39(queries.projectId, project.id)).all().map((r) => r.query);
41443
41884
  const anchorTerms = buildHarvestAnchorTerms(
41444
41885
  [session.icpDescription ?? "", ...trackedQueries],
41445
41886
  effectiveDomains(project)
@@ -41486,12 +41927,12 @@ async function discoveryRoutes(app, opts) {
41486
41927
  "/projects/:name/discover/sessions/:id/promote",
41487
41928
  async (request, reply) => {
41488
41929
  const project = resolveProject(app.db, request.params.name);
41489
- const session = app.db.select().from(discoverySessions).where(eq38(discoverySessions.id, request.params.id)).get();
41930
+ const session = app.db.select().from(discoverySessions).where(eq39(discoverySessions.id, request.params.id)).get();
41490
41931
  if (!session || session.projectId !== project.id) {
41491
41932
  throw notFound("Discovery session", request.params.id);
41492
41933
  }
41493
- const probeRows = app.db.select().from(discoveryProbes).where(eq38(discoveryProbes.sessionId, session.id)).all();
41494
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq38(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
41934
+ const probeRows = app.db.select().from(discoveryProbes).where(eq39(discoveryProbes.sessionId, session.id)).all();
41935
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq39(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
41495
41936
  const seenCompetitors = new Set(existingCompetitors);
41496
41937
  const cited = /* @__PURE__ */ new Set();
41497
41938
  const aspirational = /* @__PURE__ */ new Set();
@@ -41520,7 +41961,7 @@ async function discoveryRoutes(app, opts) {
41520
41961
  );
41521
41962
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
41522
41963
  const project = resolveProject(app.db, request.params.name);
41523
- const session = app.db.select().from(discoverySessions).where(eq38(discoverySessions.id, request.params.id)).get();
41964
+ const session = app.db.select().from(discoverySessions).where(eq39(discoverySessions.id, request.params.id)).get();
41524
41965
  if (!session || session.projectId !== project.id) {
41525
41966
  throw notFound("Discovery session", request.params.id);
41526
41967
  }
@@ -41543,7 +41984,7 @@ async function discoveryRoutes(app, opts) {
41543
41984
  const bucketSet = new Set(buckets);
41544
41985
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
41545
41986
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
41546
- const probeRows = app.db.select().from(discoveryProbes).where(eq38(discoveryProbes.sessionId, session.id)).all();
41987
+ const probeRows = app.db.select().from(discoveryProbes).where(eq39(discoveryProbes.sessionId, session.id)).all();
41547
41988
  const candidateQueries = /* @__PURE__ */ new Set();
41548
41989
  for (const probe of probeRows) {
41549
41990
  if (!probe.bucket) continue;
@@ -41551,7 +41992,7 @@ async function discoveryRoutes(app, opts) {
41551
41992
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
41552
41993
  }
41553
41994
  const existingQueries = new Set(
41554
- app.db.select({ query: queries.query }).from(queries).where(eq38(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
41995
+ app.db.select({ query: queries.query }).from(queries).where(eq39(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
41555
41996
  );
41556
41997
  const promotedQueries = [];
41557
41998
  const skippedQueries = [];
@@ -41567,7 +42008,7 @@ async function discoveryRoutes(app, opts) {
41567
42008
  const skippedCompetitors = [];
41568
42009
  if (includeCompetitors) {
41569
42010
  const existingCompetitors = new Set(
41570
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq38(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
42011
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq39(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
41571
42012
  );
41572
42013
  const competitorMap = parseCompetitorMap(session.competitorMap);
41573
42014
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -41686,7 +42127,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
41686
42127
 
41687
42128
  // ../api-routes/src/discovery/orchestrate.ts
41688
42129
  import crypto31 from "crypto";
41689
- import { eq as eq39 } from "drizzle-orm";
42130
+ import { eq as eq40 } from "drizzle-orm";
41690
42131
  var DEFAULT_MAX_PROBES = 100;
41691
42132
  var ABSOLUTE_MAX_PROBES = 500;
41692
42133
  function classifyProbeBucket(input) {
@@ -41773,7 +42214,7 @@ async function executeDiscovery(opts) {
41773
42214
  status: DiscoverySessionStatuses.seeding,
41774
42215
  dedupThreshold,
41775
42216
  startedAt
41776
- }).where(eq39(discoverySessions.id, opts.sessionId)).run();
42217
+ }).where(eq40(discoverySessions.id, opts.sessionId)).run();
41777
42218
  const seedResult = await opts.deps.seed({
41778
42219
  project: opts.project,
41779
42220
  icpDescription: opts.icpDescription,
@@ -41834,7 +42275,7 @@ async function executeDiscovery(opts) {
41834
42275
  dedupBandPairFraction: dedupStats.bandPairFraction,
41835
42276
  dedupPairsTotal: dedupStats.pairsTotal,
41836
42277
  warning
41837
- }).where(eq39(discoverySessions.id, opts.sessionId)).run();
42278
+ }).where(eq40(discoverySessions.id, opts.sessionId)).run();
41838
42279
  const probeLocation = opts.locations?.[0];
41839
42280
  const probeResults = await mapWithConcurrency(
41840
42281
  probedCanonicals,
@@ -41888,7 +42329,7 @@ async function executeDiscovery(opts) {
41888
42329
  wastedCount: buckets["wasted-surface"],
41889
42330
  competitorMap,
41890
42331
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
41891
- }).where(eq39(discoverySessions.id, opts.sessionId)).run();
42332
+ }).where(eq40(discoverySessions.id, opts.sessionId)).run();
41892
42333
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
41893
42334
  return {
41894
42335
  buckets,
@@ -41928,7 +42369,7 @@ function markSessionFailed(db, sessionId, error) {
41928
42369
  status: DiscoverySessionStatuses.failed,
41929
42370
  error,
41930
42371
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
41931
- }).where(eq39(discoverySessions.id, sessionId)).run();
42372
+ }).where(eq40(discoverySessions.id, sessionId)).run();
41932
42373
  }
41933
42374
  function dedupeStrings(input) {
41934
42375
  const seen = /* @__PURE__ */ new Set();
@@ -41946,7 +42387,7 @@ function dedupeStrings(input) {
41946
42387
 
41947
42388
  // ../api-routes/src/technical-aeo.ts
41948
42389
  import crypto32 from "crypto";
41949
- import { and as and31, asc as asc8, count, desc as desc17, eq as eq40, inArray as inArray14, lt as lt4 } from "drizzle-orm";
42390
+ import { and as and32, asc as asc8, count, desc as desc17, eq as eq41, inArray as inArray14, lt as lt4 } from "drizzle-orm";
41950
42391
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
41951
42392
  function emptyScore(projectName) {
41952
42393
  return {
@@ -41979,19 +42420,19 @@ async function technicalAeoRoutes(app, opts) {
41979
42420
  app.get("/projects/:name/technical-aeo", async (request) => {
41980
42421
  const project = resolveProject(app.db, request.params.name);
41981
42422
  const baseFilters = [
41982
- eq40(siteAuditSnapshots.projectId, project.id),
41983
- eq40(runs.kind, RunKinds["site-audit"]),
42423
+ eq41(siteAuditSnapshots.projectId, project.id),
42424
+ eq41(runs.kind, RunKinds["site-audit"]),
41984
42425
  inArray14(runs.status, SURFACEABLE_STATUSES),
41985
42426
  notProbeRun()
41986
42427
  ];
41987
- const targetFilters = request.query.runId ? [...baseFilters, eq40(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
41988
- const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq40(siteAuditSnapshots.runId, runs.id)).where(and31(...targetFilters)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
42428
+ const targetFilters = request.query.runId ? [...baseFilters, eq41(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
42429
+ const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq41(siteAuditSnapshots.runId, runs.id)).where(and32(...targetFilters)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
41989
42430
  if (!latest) {
41990
42431
  if (request.query.runId) throw notFound("Site audit run", request.query.runId);
41991
42432
  return emptyScore(project.name);
41992
42433
  }
41993
42434
  const snap = latest.snap;
41994
- const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq40(siteAuditSnapshots.runId, runs.id)).where(and31(...baseFilters, lt4(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
42435
+ const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq41(siteAuditSnapshots.runId, runs.id)).where(and32(...baseFilters, lt4(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
41995
42436
  const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
41996
42437
  const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
41997
42438
  return {
@@ -42018,13 +42459,13 @@ async function technicalAeoRoutes(app, opts) {
42018
42459
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
42019
42460
  const project = resolveProject(app.db, request.params.name);
42020
42461
  const targetFilters = [
42021
- eq40(siteAuditSnapshots.projectId, project.id),
42022
- eq40(runs.kind, RunKinds["site-audit"]),
42462
+ eq41(siteAuditSnapshots.projectId, project.id),
42463
+ eq41(runs.kind, RunKinds["site-audit"]),
42023
42464
  inArray14(runs.status, SURFACEABLE_STATUSES),
42024
42465
  notProbeRun()
42025
42466
  ];
42026
- if (request.query.runId) targetFilters.push(eq40(siteAuditSnapshots.runId, request.query.runId));
42027
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq40(siteAuditSnapshots.runId, runs.id)).where(and31(...targetFilters)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
42467
+ if (request.query.runId) targetFilters.push(eq41(siteAuditSnapshots.runId, request.query.runId));
42468
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq41(siteAuditSnapshots.runId, runs.id)).where(and32(...targetFilters)).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(1).get();
42028
42469
  if (!latest && request.query.runId) {
42029
42470
  throw notFound("Site audit run", request.query.runId);
42030
42471
  }
@@ -42032,9 +42473,9 @@ async function technicalAeoRoutes(app, opts) {
42032
42473
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
42033
42474
  }
42034
42475
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
42035
- const conds = [eq40(siteAuditPages.runId, latest.runId)];
42036
- if (statusFilter) conds.push(eq40(siteAuditPages.status, statusFilter));
42037
- const where = and31(...conds);
42476
+ const conds = [eq41(siteAuditPages.runId, latest.runId)];
42477
+ if (statusFilter) conds.push(eq41(siteAuditPages.status, statusFilter));
42478
+ const where = and32(...conds);
42038
42479
  const totalRow = app.db.select({ value: count() }).from(siteAuditPages).where(where).get();
42039
42480
  const total = totalRow?.value ?? 0;
42040
42481
  const limit = parsePositiveInt(request.query.limit, 100, 500);
@@ -42058,9 +42499,9 @@ async function technicalAeoRoutes(app, opts) {
42058
42499
  auditedAt: siteAuditSnapshots.auditedAt,
42059
42500
  aggregateScore: siteAuditSnapshots.aggregateScore,
42060
42501
  pagesAudited: siteAuditSnapshots.pagesAudited
42061
- }).from(siteAuditSnapshots).innerJoin(runs, eq40(siteAuditSnapshots.runId, runs.id)).where(and31(
42062
- eq40(siteAuditSnapshots.projectId, project.id),
42063
- eq40(runs.kind, RunKinds["site-audit"]),
42502
+ }).from(siteAuditSnapshots).innerJoin(runs, eq41(siteAuditSnapshots.runId, runs.id)).where(and32(
42503
+ eq41(siteAuditSnapshots.projectId, project.id),
42504
+ eq41(runs.kind, RunKinds["site-audit"]),
42064
42505
  inArray14(runs.status, SURFACEABLE_STATUSES),
42065
42506
  notProbeRun()
42066
42507
  )).orderBy(desc17(siteAuditSnapshots.createdAt)).limit(limit).all();
@@ -42072,9 +42513,9 @@ async function technicalAeoRoutes(app, opts) {
42072
42513
  if (!parsed.success) {
42073
42514
  throw validationError(parsed.error.issues[0]?.message ?? "Invalid site-audit request");
42074
42515
  }
42075
- const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and31(
42076
- eq40(runs.projectId, project.id),
42077
- eq40(runs.kind, RunKinds["site-audit"]),
42516
+ const existing = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and32(
42517
+ eq41(runs.projectId, project.id),
42518
+ eq41(runs.kind, RunKinds["site-audit"]),
42078
42519
  inArray14(runs.status, [RunStatuses.queued, RunStatuses.running])
42079
42520
  )).get();
42080
42521
  if (existing) {
@@ -42100,7 +42541,7 @@ async function technicalAeoRoutes(app, opts) {
42100
42541
 
42101
42542
  // ../api-routes/src/research.ts
42102
42543
  import crypto33 from "crypto";
42103
- import { and as and32, desc as desc18, eq as eq41 } from "drizzle-orm";
42544
+ import { and as and33, desc as desc18, eq as eq42 } from "drizzle-orm";
42104
42545
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
42105
42546
  async function researchRoutes(app, opts) {
42106
42547
  app.post("/projects/:name/research/runs", async (request, reply) => {
@@ -42130,7 +42571,7 @@ async function researchRoutes(app, opts) {
42130
42571
  const now = (/* @__PURE__ */ new Date()).toISOString();
42131
42572
  const decision = app.db.transaction((tx) => {
42132
42573
  if (input.idempotencyKey) {
42133
- const existing = tx.select().from(researchRuns).where(and32(eq41(researchRuns.projectId, project.id), eq41(researchRuns.idempotencyKey, input.idempotencyKey))).get();
42574
+ const existing = tx.select().from(researchRuns).where(and33(eq42(researchRuns.projectId, project.id), eq42(researchRuns.idempotencyKey, input.idempotencyKey))).get();
42134
42575
  if (existing) {
42135
42576
  if (existing.requestHash !== requestHash2) throw alreadyExists("Research idempotency key", input.idempotencyKey);
42136
42577
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -42151,7 +42592,7 @@ async function researchRoutes(app, opts) {
42151
42592
  const project = resolveProject(app.db, request.params.name);
42152
42593
  const requested = Number.parseInt(request.query.limit ?? "", 10);
42153
42594
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
42154
- const runs2 = app.db.select().from(researchRuns).where(eq41(researchRuns.projectId, project.id)).orderBy(desc18(researchRuns.createdAt)).limit(limit).all().map(serializeRun);
42595
+ const runs2 = app.db.select().from(researchRuns).where(eq42(researchRuns.projectId, project.id)).orderBy(desc18(researchRuns.createdAt)).limit(limit).all().map(serializeRun);
42155
42596
  return { runs: runs2 };
42156
42597
  });
42157
42598
  app.get("/projects/:name/research/runs/:runId", async (request) => {
@@ -42160,9 +42601,9 @@ async function researchRoutes(app, opts) {
42160
42601
  });
42161
42602
  }
42162
42603
  function getDetail(app, projectId, id) {
42163
- const row = app.db.select().from(researchRuns).where(and32(eq41(researchRuns.id, id), eq41(researchRuns.projectId, projectId))).get();
42604
+ const row = app.db.select().from(researchRuns).where(and33(eq42(researchRuns.id, id), eq42(researchRuns.projectId, projectId))).get();
42164
42605
  if (!row) throw notFound("Research run", id);
42165
- const queries2 = app.db.select().from(researchRunQueries).where(eq41(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
42606
+ const queries2 = app.db.select().from(researchRunQueries).where(eq42(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
42166
42607
  return { ...serializeRun(row), queries: queries2 };
42167
42608
  }
42168
42609
  function serializeRun(row) {
@@ -42329,6 +42770,7 @@ async function apiRoutes(app, opts) {
42329
42770
  googleConnectionStore: opts.googleConnectionStore,
42330
42771
  getGoogleAuthConfig: opts.getGoogleAuthConfig
42331
42772
  });
42773
+ await api.register(gaMeasurementAnalysisRoutes);
42332
42774
  await api.register(trafficRoutes, {
42333
42775
  cloudRunCredentialStore: opts.cloudRunCredentialStore,
42334
42776
  pullCloudRunEvents: opts.pullCloudRunEvents,
@@ -42756,9 +43198,9 @@ var IntelligenceService = class {
42756
43198
  */
42757
43199
  analyzeAndPersist(runId, projectId) {
42758
43200
  const recentRuns = this.db.select().from(runs).where(
42759
- and33(
42760
- eq42(runs.projectId, projectId),
42761
- or8(eq42(runs.status, "completed"), eq42(runs.status, "partial")),
43201
+ and34(
43202
+ eq43(runs.projectId, projectId),
43203
+ or9(eq43(runs.status, "completed"), eq43(runs.status, "partial")),
42762
43204
  // Defensive: RunCoordinator already skips probes before this is
42763
43205
  // called, but if a future call site invokes analyzeAndPersist
42764
43206
  // directly for a probe, probes still must not pollute the
@@ -42840,7 +43282,7 @@ var IntelligenceService = class {
42840
43282
  * Returns the persisted insights so the coordinator can count critical/high.
42841
43283
  */
42842
43284
  analyzeAndPersistGbp(runId, projectId) {
42843
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq42(runs.id, runId)).get();
43285
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq43(runs.id, runId)).get();
42844
43286
  if (!runRow) {
42845
43287
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
42846
43288
  this.persistGbpInsights(runId, projectId, [], []);
@@ -42848,11 +43290,11 @@ var IntelligenceService = class {
42848
43290
  }
42849
43291
  const windowStart = runRow.startedAt ?? runRow.createdAt;
42850
43292
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
42851
- const selected = this.db.select().from(gbpLocations).where(and33(
42852
- eq42(gbpLocations.projectId, projectId),
42853
- eq42(gbpLocations.selected, true),
42854
- gte10(gbpLocations.syncedAt, windowStart),
42855
- lte6(gbpLocations.syncedAt, windowEnd)
43293
+ const selected = this.db.select().from(gbpLocations).where(and34(
43294
+ eq43(gbpLocations.projectId, projectId),
43295
+ eq43(gbpLocations.selected, true),
43296
+ gte11(gbpLocations.syncedAt, windowStart),
43297
+ lte7(gbpLocations.syncedAt, windowEnd)
42856
43298
  )).all();
42857
43299
  if (selected.length === 0) {
42858
43300
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });
@@ -42885,12 +43327,12 @@ var IntelligenceService = class {
42885
43327
  }
42886
43328
  /** Build the per-location signal bundle the GBP analyzer consumes. */
42887
43329
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
42888
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and33(eq42(gbpDailyMetrics.projectId, projectId), eq42(gbpDailyMetrics.locationName, locationName))).all();
42889
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and33(eq42(gbpPlaceActions.projectId, projectId), eq42(gbpPlaceActions.locationName, locationName))).all();
42890
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and33(eq42(gbpLodgingSnapshots.projectId, projectId), eq42(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc19(gbpLodgingSnapshots.syncedAt)).limit(1).get();
42891
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and33(eq42(gbpLocations.projectId, projectId), eq42(gbpLocations.locationName, locationName))).get();
43330
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and34(eq43(gbpDailyMetrics.projectId, projectId), eq43(gbpDailyMetrics.locationName, locationName))).all();
43331
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and34(eq43(gbpPlaceActions.projectId, projectId), eq43(gbpPlaceActions.locationName, locationName))).all();
43332
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and34(eq43(gbpLodgingSnapshots.projectId, projectId), eq43(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc19(gbpLodgingSnapshots.syncedAt)).limit(1).get();
43333
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and34(eq43(gbpLocations.projectId, projectId), eq43(gbpLocations.locationName, locationName))).get();
42892
43334
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
42893
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and33(eq42(gbpPlaceDetails.projectId, projectId), eq42(gbpPlaceDetails.locationName, locationName))).orderBy(desc19(gbpPlaceDetails.syncedAt)).limit(1).get();
43335
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and34(eq43(gbpPlaceDetails.projectId, projectId), eq43(gbpPlaceDetails.locationName, locationName))).orderBy(desc19(gbpPlaceDetails.syncedAt)).limit(1).get();
42894
43336
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
42895
43337
  const summary = buildGbpSummary({
42896
43338
  locationName,
@@ -42926,7 +43368,7 @@ var IntelligenceService = class {
42926
43368
  /** Build the month-over-month keyword series for a location from the
42927
43369
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
42928
43370
  buildGbpKeywordTrend(projectId, locationName) {
42929
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and33(eq42(gbpKeywordMonthly.projectId, projectId), eq42(gbpKeywordMonthly.locationName, locationName))).all();
43371
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and34(eq43(gbpKeywordMonthly.projectId, projectId), eq43(gbpKeywordMonthly.locationName, locationName))).all();
42930
43372
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
42931
43373
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
42932
43374
  const recentMonth = months[0] ?? null;
@@ -42957,7 +43399,7 @@ var IntelligenceService = class {
42957
43399
  */
42958
43400
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
42959
43401
  const covered = new Set(coveredLocationNames);
42960
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and33(eq42(insights.projectId, projectId), eq42(insights.provider, GBP_INSIGHT_PROVIDER))).all();
43402
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and34(eq43(insights.projectId, projectId), eq43(insights.provider, GBP_INSIGHT_PROVIDER))).all();
42961
43403
  const staleIds = [];
42962
43404
  const dismissedSlots = /* @__PURE__ */ new Set();
42963
43405
  for (const row of existing) {
@@ -42968,7 +43410,7 @@ var IntelligenceService = class {
42968
43410
  }
42969
43411
  this.db.transaction((tx) => {
42970
43412
  for (const id of staleIds) {
42971
- tx.delete(insights).where(eq42(insights.id, id)).run();
43413
+ tx.delete(insights).where(eq43(insights.id, id)).run();
42972
43414
  }
42973
43415
  for (const insight of gbpInsights) {
42974
43416
  const parsed = parseGbpInsightId(insight.id);
@@ -43046,7 +43488,7 @@ var IntelligenceService = class {
43046
43488
  * create per run + aggregate). DB is left untouched.
43047
43489
  */
43048
43490
  backfill(projectName, opts, onProgress) {
43049
- const project = this.db.select().from(projects).where(eq42(projects.name, projectName)).get();
43491
+ const project = this.db.select().from(projects).where(eq43(projects.name, projectName)).get();
43050
43492
  if (!project) {
43051
43493
  throw new Error(`Project "${projectName}" not found`);
43052
43494
  }
@@ -43059,9 +43501,9 @@ var IntelligenceService = class {
43059
43501
  sinceTimestamp = parsed;
43060
43502
  }
43061
43503
  const allRuns = this.db.select().from(runs).where(
43062
- and33(
43063
- eq42(runs.projectId, project.id),
43064
- or8(eq42(runs.status, "completed"), eq42(runs.status, "partial")),
43504
+ and34(
43505
+ eq43(runs.projectId, project.id),
43506
+ or9(eq43(runs.status, "completed"), eq43(runs.status, "partial")),
43065
43507
  // Backfill must not replay probe runs as if they were real sweeps.
43066
43508
  ne6(runs.trigger, RunTriggers.probe)
43067
43509
  )
@@ -43140,7 +43582,7 @@ var IntelligenceService = class {
43140
43582
  return { processed, skipped, totalInsights };
43141
43583
  }
43142
43584
  loadTrackedCompetitors(projectId) {
43143
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq42(competitors.projectId, projectId)).all().map((r) => r.domain);
43585
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq43(competitors.projectId, projectId)).all().map((r) => r.domain);
43144
43586
  }
43145
43587
  /**
43146
43588
  * Wipe transition signals from an analysis result while keeping health.
@@ -43161,15 +43603,15 @@ var IntelligenceService = class {
43161
43603
  }
43162
43604
  persistResult(result, runId, projectId) {
43163
43605
  const previouslyDismissed = /* @__PURE__ */ new Set();
43164
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq42(insights.runId, runId)).all();
43606
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq43(insights.runId, runId)).all();
43165
43607
  for (const row of existingInsights) {
43166
43608
  if (row.dismissed) {
43167
43609
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
43168
43610
  }
43169
43611
  }
43170
43612
  this.db.transaction((tx) => {
43171
- tx.delete(insights).where(eq42(insights.runId, runId)).run();
43172
- tx.delete(healthSnapshots).where(eq42(healthSnapshots.runId, runId)).run();
43613
+ tx.delete(insights).where(eq43(insights.runId, runId)).run();
43614
+ tx.delete(healthSnapshots).where(eq43(healthSnapshots.runId, runId)).run();
43173
43615
  const now = (/* @__PURE__ */ new Date()).toISOString();
43174
43616
  for (const insight of result.insights) {
43175
43617
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -43222,24 +43664,24 @@ var IntelligenceService = class {
43222
43664
  applySeverityTiering(rawInsights, excludeRunId, projectId) {
43223
43665
  const regressions = rawInsights.filter((i) => i.type === "regression");
43224
43666
  if (regressions.length === 0) return rawInsights;
43225
- const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq42(gscSearchData.projectId, projectId)).all();
43667
+ const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq43(gscSearchData.projectId, projectId)).all();
43226
43668
  const gscConnected = gscRows.length > 0;
43227
43669
  const gscImpressionsByQuery = /* @__PURE__ */ new Map();
43228
43670
  for (const row of gscRows) {
43229
43671
  const key = row.query.toLowerCase();
43230
43672
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
43231
43673
  }
43232
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq42(projects.id, projectId)).get();
43674
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq43(projects.id, projectId)).get();
43233
43675
  const locationCount = Math.max(
43234
43676
  1,
43235
43677
  (projectRow?.locations ?? []).length
43236
43678
  );
43237
43679
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
43238
43680
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
43239
- and33(
43240
- eq42(runs.projectId, projectId),
43241
- eq42(runs.kind, RunKinds["answer-visibility"]),
43242
- or8(eq42(runs.status, "completed"), eq42(runs.status, "partial")),
43681
+ and34(
43682
+ eq43(runs.projectId, projectId),
43683
+ eq43(runs.kind, RunKinds["answer-visibility"]),
43684
+ or9(eq43(runs.status, "completed"), eq43(runs.status, "partial")),
43243
43685
  // Defensive — see top of file.
43244
43686
  ne6(runs.trigger, RunTriggers.probe)
43245
43687
  )
@@ -43259,7 +43701,7 @@ var IntelligenceService = class {
43259
43701
  const haveHistory = recentRunIds.length > 0;
43260
43702
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
43261
43703
  if (haveHistory) {
43262
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and33(eq42(insights.type, "regression"), inArray15(insights.runId, recentRunIds))).all();
43704
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and34(eq43(insights.type, "regression"), inArray15(insights.runId, recentRunIds))).all();
43263
43705
  const regressionGroups = /* @__PURE__ */ new Map();
43264
43706
  for (const row of priorRows) {
43265
43707
  if (!row.runId) continue;
@@ -43288,7 +43730,7 @@ var IntelligenceService = class {
43288
43730
  });
43289
43731
  }
43290
43732
  buildRunData(runId, projectId, completedAt, location = null) {
43291
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq42(projects.id, projectId)).get();
43733
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq43(projects.id, projectId)).get();
43292
43734
  const projectDomains = projectDomainRow ? effectiveDomains({
43293
43735
  canonicalDomain: projectDomainRow.canonicalDomain,
43294
43736
  ownedDomains: projectDomainRow.ownedDomains
@@ -43305,7 +43747,7 @@ var IntelligenceService = class {
43305
43747
  citedDomains: querySnapshots.citedDomains,
43306
43748
  competitorOverlap: querySnapshots.competitorOverlap,
43307
43749
  snapshotLocation: querySnapshots.location
43308
- }).from(querySnapshots).leftJoin(queries, eq42(querySnapshots.queryId, queries.id)).where(eq42(querySnapshots.runId, runId)).all();
43750
+ }).from(querySnapshots).leftJoin(queries, eq43(querySnapshots.queryId, queries.id)).where(eq43(querySnapshots.runId, runId)).all();
43309
43751
  const snapshots = [];
43310
43752
  let orphanCount = 0;
43311
43753
  for (const r of rows) {