@canonry/canonry 5.21.0 → 5.22.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 (30) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
  2. package/assets/assets/{AuditHistoryPanel-CjxWA3lx.js → AuditHistoryPanel-teTDC-4-.js} +1 -1
  3. package/assets/assets/{BacklinksPage-Bdd164Up.js → BacklinksPage-Bqm7bido.js} +1 -1
  4. package/assets/assets/{HistoryPage-BvJmE-kP.js → HistoryPage-IcqwPBmt.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-HuP6RMZ-.js → MeasurementPropertyPage-BLpptT3_.js} +1 -1
  6. package/assets/assets/{ProjectPage-DOvXVbyy.js → ProjectPage-nn2Y9H-m.js} +1 -1
  7. package/assets/assets/{RunRow-Cnb3JOBw.js → RunRow-S_SCHr0o.js} +1 -1
  8. package/assets/assets/{RunsPage-Dvz1JYIm.js → RunsPage-DFiif626.js} +1 -1
  9. package/assets/assets/{SettingsPage-DYGvphkg.js → SettingsPage-CitZ6FAS.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-CUNiKaod.js → SiteHealthSection-C2dsyNGY.js} +3 -3
  11. package/assets/assets/{TrafficPage-Bf4Tq95q.js → TrafficPage-lY8na5Pg.js} +1 -1
  12. package/assets/assets/{TrafficSourceDetailPage-CIuLWZgT.js → TrafficSourceDetailPage-BTykoVGT.js} +1 -1
  13. package/assets/assets/{extract-error-message-DZex1e4H.js → extract-error-message-r0Gpc7mv.js} +1 -1
  14. package/assets/assets/{index-CeXW-lzj.js → index-DwlO8KfE.js} +20 -20
  15. package/assets/assets/{react-sigma_core.esm.min-CxvtwEkv.js → react-sigma_core.esm.min-DDrDxbO1.js} +1 -1
  16. package/assets/assets/{v2-overview-adapter-Bh4qIzLp.js → v2-overview-adapter-OsQiglXU.js} +1 -1
  17. package/assets/index.html +1 -1
  18. package/dist/{chunk-HDJHQPW3.js → chunk-3LCR6WU6.js} +2 -2
  19. package/dist/{chunk-WUT3G2GM.js → chunk-DDL2XSNM.js} +4 -2
  20. package/dist/{chunk-FRWZWJB3.js → chunk-GLLY2GEV.js} +6 -3
  21. package/dist/{chunk-FAVYLZQV.js → chunk-P5VXVFQW.js} +845 -445
  22. package/dist/{chunk-2LO7FLVD.js → chunk-S5OSY3PI.js} +30 -0
  23. package/dist/{chunk-NFN5GSEJ.js → chunk-XFHXKLNT.js} +23 -12
  24. package/dist/{chunk-MEAKE5W7.js → chunk-YTSXQUJ3.js} +3 -3
  25. package/dist/cli.js +15 -12
  26. package/dist/{demo-server-7BVVVXPM.js → demo-server-HULNEORN.js} +4 -4
  27. package/dist/index.js +6 -6
  28. package/dist/{intelligence-service-3UUKRAZ5.js → intelligence-service-CXNBKRHV.js} +2 -2
  29. package/dist/mcp.js +4 -4
  30. package/package.json +11 -11
@@ -35,6 +35,7 @@ import {
35
35
  COMPETITOR_LANDSCAPE_OBSERVED_NAME_LIMIT,
36
36
  CcReleaseSyncStatuses,
37
37
  CheckCategories,
38
+ CheckNotificationPolicies,
38
39
  CheckScopes,
39
40
  CheckStatuses,
40
41
  CitationStates,
@@ -377,6 +378,8 @@ import {
377
378
  googleMarketingOAuthConnectRequestSchema,
378
379
  googleMarketingOAuthConnectResponseSchema,
379
380
  googleMarketingProviderSchema,
381
+ gradedReportMonths,
382
+ groupIsoDateRanges,
380
383
  gscCoverageSnapshotDtoSchema,
381
384
  gscCoverageSummaryDtoSchema,
382
385
  gscDeindexedRowSchema,
@@ -629,6 +632,8 @@ import {
629
632
  reportLineChartLabel,
630
633
  reportMarketScope,
631
634
  reportMissRateLabel,
635
+ reportMonthSchema,
636
+ reportMonthsForDoctor,
632
637
  reportMoreChipLabel,
633
638
  reportMovementChangeCopy,
634
639
  reportOpportunityActionLine,
@@ -794,10 +799,10 @@ import {
794
799
  wordpressSchemaDeployResultDtoSchema,
795
800
  wordpressSchemaStatusResultDtoSchema,
796
801
  wordpressStatusDtoSchema
797
- } from "./chunk-2LO7FLVD.js";
802
+ } from "./chunk-S5OSY3PI.js";
798
803
 
799
804
  // src/intelligence-service.ts
800
- import { eq as eq71, desc as desc32, asc as asc12, and as and60, ne as ne11, or as or18, inArray as inArray27, gte as gte18, lte as lte15, isNull as isNull13, sql as sql28, exists } from "drizzle-orm";
805
+ import { eq as eq74, desc as desc32, asc as asc13, and as and62, ne as ne11, or as or18, inArray as inArray28, gte as gte20, lte as lte17, isNull as isNull13, sql as sql28, exists } from "drizzle-orm";
801
806
 
802
807
  // ../db/src/client.ts
803
808
  import { mkdirSync } from "fs";
@@ -25146,7 +25151,7 @@ async function visibilityReportRoutes(app) {
25146
25151
  async (request) => readVisibilityReport(app.db, resolveProject(app.db, request.params.name), request.query)
25147
25152
  );
25148
25153
  }
25149
- function readVisibilityComparisonRuns(db, projectId, since, until) {
25154
+ function readVisibilityComparisonRuns(db, projectId, since, until, includeEvidence = true) {
25150
25155
  const sourceRuns = db.select().from(runs).where(and21(
25151
25156
  eq26(runs.projectId, projectId),
25152
25157
  eq26(runs.kind, RunKinds["answer-visibility"]),
@@ -25172,7 +25177,7 @@ function readVisibilityComparisonRuns(db, projectId, since, until) {
25172
25177
  const presentation = versions.get(heads.get(source.row.id) ?? source.row.id) ?? source;
25173
25178
  const snapshots = byRun.get(run.id) ?? [];
25174
25179
  const models = new Map(snapshots.map((snapshot) => [snapshot.id, snapshot.model]));
25175
- const materialized = advancedRun(run, presentation.row, presentation.plan, snapshots, comparableVersionIds(rowsById, presentation.row.id));
25180
+ const materialized = advancedRun(run, presentation.row, presentation.plan, snapshots, comparableVersionIds(rowsById, presentation.row.id), includeEvidence);
25176
25181
  return [{ ...materialized, observations: materialized.observations.map((observation) => ({ ...observation, model: models.get(observation.answerId) ?? null })) }];
25177
25182
  });
25178
25183
  return { runs: selected, unavailable: unavailable5 };
@@ -35457,70 +35462,23 @@ function readVisibilityCompare(db, projectName, query) {
35457
35462
  if (scope !== "project" && filters.scopeKey === void 0) throw validationError("A non-project comparison scope requires scopeKey.");
35458
35463
  if (scope === "project" && filters.scopeKey !== void 0) throw validationError("scopeKey is not valid for project scope.");
35459
35464
  if (scope === "market" && filters.marketKey !== void 0) throw validationError("marketKey is not valid for market scope.");
35460
- const { from: fromRaw, to: toRaw } = query;
35461
- if (fromRaw === void 0 || fromRaw === "") throw validationError('"from" (YYYY-MM) is required');
35462
- if (toRaw === void 0 || toRaw === "") throw validationError('"to" (YYYY-MM) is required');
35463
- let fromBounds;
35464
- let toBounds;
35465
- try {
35466
- fromBounds = calendarMonthBounds(fromRaw);
35467
- } catch (err) {
35468
- throw validationError(err instanceof RangeError ? `"from": ${err.message}` : '"from" must be in YYYY-MM format');
35469
- }
35470
- try {
35471
- toBounds = calendarMonthBounds(toRaw);
35472
- } catch (err) {
35473
- throw validationError(err instanceof RangeError ? `"to": ${err.message}` : '"to" must be in YYYY-MM format');
35474
- }
35475
- if (Date.parse(fromBounds.since) >= Date.parse(toBounds.since)) {
35476
- throw validationError('"from" must be a month strictly before "to"');
35477
- }
35465
+ const { fromRaw, toRaw, fromBounds, toBounds } = compareMonths(query.from, query.to);
35478
35466
  const providerKey2 = filters.provider === void 0 ? void 0 : normalizeText3(filters.provider);
35479
35467
  const locationKey2 = filters.location === void 0 ? void 0 : normalizeText3(filters.location);
35480
35468
  const scoped = scope !== "project" || filters.marketKey !== void 0;
35481
35469
  let advancedComparison;
35482
35470
  if (activeMeasurementPlan(db, project.id) !== null) {
35483
- let missingScope;
35484
- let measuredScope = 0;
35485
- const loadAdvancedMonth = (bounds) => {
35486
- const selected = readVisibilityComparisonRuns(db, project.id, bounds.since, bounds.until);
35487
- const snapshots = [];
35488
- const classSnapshots = [];
35489
- let runCount = 0;
35490
- for (const run of selected.runs) {
35491
- let population;
35492
- try {
35493
- population = visibilityComparisonPopulation(run, {
35494
- ...filters,
35495
- scope,
35496
- queryClass: "all",
35497
- location: locationKey2 === void 0 ? { kind: "all" } : locationKey2 === "none" ? { kind: "none" } : { kind: "exact", value: filters.location },
35498
- limit: 100
35499
- });
35500
- } catch (error) {
35501
- if (error instanceof VisibilityReportScopeError && error.details !== void 0) {
35502
- missingScope ??= error;
35503
- continue;
35504
- }
35505
- if (error instanceof VisibilityReportScopeError) throw validationError(error.message, error.details);
35506
- throw error;
35507
- }
35508
- runCount += 1;
35509
- const adapt = (snapshot) => ({
35510
- ...snapshot,
35511
- citationState: snapshot.citation === true ? CitationStates.cited : CitationStates["not-cited"],
35512
- citationChecked: snapshot.citation !== null,
35513
- citedDomains: []
35514
- });
35515
- snapshots.push(...population.snapshots.map(adapt));
35516
- classSnapshots.push(...population.classSnapshots.map(adapt));
35517
- }
35518
- measuredScope += runCount;
35519
- return { runCount, snapshots, classSnapshots, classificationAvailable: !selected.unavailable };
35471
+ const readerSelection = {
35472
+ ...filters,
35473
+ scope,
35474
+ queryClass: "all",
35475
+ location: locationKey2 === void 0 ? { kind: "all" } : locationKey2 === "none" ? { kind: "none" } : { kind: "exact", value: filters.location },
35476
+ limit: 100
35520
35477
  };
35521
- const fromMonth2 = loadAdvancedMonth(fromBounds);
35522
- const toMonth2 = loadAdvancedMonth(toBounds);
35523
- if (missingScope !== void 0 && measuredScope === 0) throw validationError(missingScope.message, missingScope.details);
35478
+ const { missingScope: fromMissingScope, ...fromMonth2 } = loadAdvancedCompareMonth(db, project.id, fromBounds, readerSelection);
35479
+ const { missingScope: toMissingScope, ...toMonth2 } = loadAdvancedCompareMonth(db, project.id, toBounds, readerSelection);
35480
+ const missingScope = fromMissingScope ?? toMissingScope;
35481
+ if (missingScope !== void 0 && fromMonth2.runCount + toMonth2.runCount === 0) throw validationError(missingScope.message, missingScope.details);
35524
35482
  const queries2 = [...new Map([...fromMonth2.snapshots, ...toMonth2.snapshots].map((snapshot) => [snapshot.queryId, { id: snapshot.queryId, query: snapshot.queryText }])).values()];
35525
35483
  advancedComparison = { ...computeVisibilityCompare({
35526
35484
  project: project.name,
@@ -35543,12 +35501,7 @@ function readVisibilityCompare(db, projectName, query) {
35543
35501
  const competitorRows2 = db.select({ domain: competitors.domain }).from(competitors).where(eq39(competitors.projectId, project.id)).all();
35544
35502
  const competitorInputs = mentionShareCompetitorsFromDomains(competitorRows2.map((c) => c.domain));
35545
35503
  const loadMonth = (bounds) => {
35546
- const sinceMs = Date.parse(bounds.since);
35547
- const untilMs = Date.parse(bounds.until);
35548
- const monthRuns = db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and35(eq39(runs.projectId, project.id), eq39(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter(
35549
- (r) => (r.status === RunStatuses.completed || r.status === RunStatuses.partial) && Date.parse(r.createdAt) >= sinceMs && Date.parse(r.createdAt) <= untilMs
35550
- );
35551
- const runIds = monthRuns.map((r) => r.id);
35504
+ const runIds = legacyCompareRunIds(db, project.id, bounds);
35552
35505
  const snapshots = runIds.length > 0 && projectQueries.length > 0 ? db.select({
35553
35506
  queryId: querySnapshots.queryId,
35554
35507
  queryText: querySnapshots.queryText,
@@ -35561,7 +35514,7 @@ function readVisibilityCompare(db, projectName, query) {
35561
35514
  location: querySnapshots.location
35562
35515
  }).from(querySnapshots).where(inArray18(querySnapshots.runId, runIds)).all() : [];
35563
35516
  return {
35564
- runCount: monthRuns.length,
35517
+ runCount: runIds.length,
35565
35518
  snapshots: snapshots.filter((snapshot) => (providerKey2 === void 0 || normalizeText3(snapshot.provider) === providerKey2) && (locationKey2 === void 0 || (locationKey2 === "none" ? snapshot.location === null : snapshot.location !== null && normalizeText3(snapshot.location) === locationKey2)))
35566
35519
  };
35567
35520
  };
@@ -35589,6 +35542,104 @@ function readVisibilityCompare(db, projectName, query) {
35589
35542
  }
35590
35543
  return { ...dto, selection: filters };
35591
35544
  }
35545
+ function compareMonths(fromRaw, toRaw) {
35546
+ if (fromRaw === void 0 || fromRaw === "") throw validationError('"from" (YYYY-MM) is required');
35547
+ if (toRaw === void 0 || toRaw === "") throw validationError('"to" (YYYY-MM) is required');
35548
+ let fromBounds;
35549
+ let toBounds;
35550
+ try {
35551
+ fromBounds = calendarMonthBounds(fromRaw);
35552
+ } catch (err) {
35553
+ throw validationError(err instanceof RangeError ? `"from": ${err.message}` : '"from" must be in YYYY-MM format');
35554
+ }
35555
+ try {
35556
+ toBounds = calendarMonthBounds(toRaw);
35557
+ } catch (err) {
35558
+ throw validationError(err instanceof RangeError ? `"to": ${err.message}` : '"to" must be in YYYY-MM format');
35559
+ }
35560
+ if (Date.parse(fromBounds.since) >= Date.parse(toBounds.since)) {
35561
+ throw validationError('"from" must be a month strictly before "to"');
35562
+ }
35563
+ return { fromRaw, toRaw, fromBounds, toBounds };
35564
+ }
35565
+ function legacyCompareRunIds(db, projectId, bounds) {
35566
+ const sinceMs = Date.parse(bounds.since);
35567
+ const untilMs = Date.parse(bounds.until);
35568
+ return db.select({ id: runs.id, createdAt: runs.createdAt, status: runs.status }).from(runs).where(and35(eq39(runs.projectId, projectId), eq39(runs.kind, RunKinds["answer-visibility"]), notProbeRun())).all().filter(
35569
+ (r) => (r.status === RunStatuses.completed || r.status === RunStatuses.partial) && Date.parse(r.createdAt) >= sinceMs && Date.parse(r.createdAt) <= untilMs
35570
+ ).map((r) => r.id);
35571
+ }
35572
+ function loadAdvancedCompareMonth(db, projectId, bounds, selection, includeEvidence = true) {
35573
+ const selected = readVisibilityComparisonRuns(db, projectId, bounds.since, bounds.until, includeEvidence);
35574
+ const snapshots = [];
35575
+ const classSnapshots = [];
35576
+ let runCount = 0;
35577
+ let missingScope;
35578
+ for (const run of selected.runs) {
35579
+ let population;
35580
+ try {
35581
+ population = visibilityComparisonPopulation(run, selection);
35582
+ } catch (error) {
35583
+ if (error instanceof VisibilityReportScopeError && error.details !== void 0) {
35584
+ missingScope ??= error;
35585
+ continue;
35586
+ }
35587
+ if (error instanceof VisibilityReportScopeError) throw validationError(error.message, error.details);
35588
+ throw error;
35589
+ }
35590
+ runCount += 1;
35591
+ const adapt = (snapshot) => ({
35592
+ ...snapshot,
35593
+ citationState: snapshot.citation === true ? CitationStates.cited : CitationStates["not-cited"],
35594
+ citationChecked: snapshot.citation !== null,
35595
+ citedDomains: []
35596
+ });
35597
+ snapshots.push(...population.snapshots.map(adapt));
35598
+ classSnapshots.push(...population.classSnapshots.map(adapt));
35599
+ }
35600
+ return { runCount, snapshots, classSnapshots, classificationAvailable: !selected.unavailable, missingScope };
35601
+ }
35602
+ function readVisibilityContinuity(db, projectName, months) {
35603
+ const project = resolveProject(db, projectName);
35604
+ const { fromRaw, toRaw, fromBounds, toBounds } = compareMonths(months.from, months.to);
35605
+ const bare = (snapshot) => ({
35606
+ queryId: snapshot.queryId,
35607
+ queryText: snapshot.queryText,
35608
+ provider: snapshot.provider,
35609
+ model: snapshot.model,
35610
+ ...snapshot.cohortKey === void 0 ? {} : { cohortKey: snapshot.cohortKey },
35611
+ citationState: CitationStates["not-cited"],
35612
+ answerMentioned: null,
35613
+ answerText: null,
35614
+ citedDomains: []
35615
+ });
35616
+ const gate = (trackedQueries2, fromMonth, toMonth) => computeVisibilityCompare({
35617
+ project: project.name,
35618
+ queries: trackedQueries2,
35619
+ competitors: [],
35620
+ from: { month: fromRaw, ...fromBounds, ...fromMonth },
35621
+ to: { month: toRaw, ...toBounds, ...toMonth }
35622
+ }).continuity;
35623
+ const trackedQueries = db.select({ id: queries.id, query: queries.query }).from(queries).where(eq39(queries.projectId, project.id)).all();
35624
+ const loadProjectMonth = (bounds) => {
35625
+ const runIds = legacyCompareRunIds(db, project.id, bounds);
35626
+ const rows = runIds.length > 0 && trackedQueries.length > 0 ? db.select({ queryId: querySnapshots.queryId, queryText: querySnapshots.queryText, provider: querySnapshots.provider, model: querySnapshots.model }).from(querySnapshots).where(inArray18(querySnapshots.runId, runIds)).all() : [];
35627
+ return { runCount: runIds.length, snapshots: rows.map(bare) };
35628
+ };
35629
+ const frames = [{ frame: "project", continuity: gate(trackedQueries, loadProjectMonth(fromBounds), loadProjectMonth(toBounds)) }];
35630
+ if (activeMeasurementPlan(db, project.id) !== null) {
35631
+ const selection = { scope: "project", queryClass: "all", location: { kind: "all" }, limit: 100 };
35632
+ const fromMonth = loadAdvancedCompareMonth(db, project.id, fromBounds, selection, false);
35633
+ const toMonth = loadAdvancedCompareMonth(db, project.id, toBounds, selection, false);
35634
+ if (fromMonth.classificationAvailable && toMonth.classificationAvailable) {
35635
+ const bareFrom = { runCount: fromMonth.runCount, snapshots: fromMonth.snapshots.map(bare) };
35636
+ const bareTo = { runCount: toMonth.runCount, snapshots: toMonth.snapshots.map(bare) };
35637
+ const frozenQueries = [...new Map([...bareFrom.snapshots, ...bareTo.snapshots].map((snapshot) => [snapshot.queryId, { id: snapshot.queryId, query: snapshot.queryText }])).values()];
35638
+ frames.push({ frame: "class", continuity: gate(frozenQueries, bareFrom, bareTo) });
35639
+ }
35640
+ }
35641
+ return frames;
35642
+ }
35592
35643
 
35593
35644
  // ../api-routes/src/results-export.ts
35594
35645
  import { and as and36, asc as asc5, eq as eq40, gte as gte11, lte as lte9 } from "drizzle-orm";
@@ -42925,6 +42976,12 @@ var routeCatalog = [
42925
42976
  description: "Runs all global-scope checks (provider keys, etc.). Use ?check=<id> or ?check=<prefix>* (comma-separated) to filter. Returns a structured DoctorReport with per-check status, code, summary, remediation, and details.",
42926
42977
  tags: ["doctor"],
42927
42978
  parameters: [
42979
+ {
42980
+ name: "reportMonth",
42981
+ in: "query",
42982
+ description: "Report month YYYY-MM (not future). Omit for this month plus the previous month through UTC day 3. Read selection only; no writes or provider calls by report checks.",
42983
+ schema: { type: "string", pattern: "^[1-9][0-9]{3}-(0[1-9]|1[0-2])$" }
42984
+ },
42928
42985
  {
42929
42986
  name: "check",
42930
42987
  in: "query",
@@ -42933,7 +42990,8 @@ var routeCatalog = [
42933
42990
  }
42934
42991
  ],
42935
42992
  responses: {
42936
- 200: jsonResponse("Doctor report returned.", "DoctorReportDto")
42993
+ 200: jsonResponse("Doctor report returned.", "DoctorReportDto"),
42994
+ 400: errorResponse("Invalid report month.")
42937
42995
  }
42938
42996
  },
42939
42997
  {
@@ -42944,6 +43002,12 @@ var routeCatalog = [
42944
43002
  tags: ["doctor"],
42945
43003
  parameters: [
42946
43004
  nameParameter,
43005
+ {
43006
+ name: "reportMonth",
43007
+ in: "query",
43008
+ description: "Report month YYYY-MM (not future). Omit for this month plus the previous month through UTC day 3. Read selection only; no writes or provider calls by report checks.",
43009
+ schema: { type: "string", pattern: "^[1-9][0-9]{3}-(0[1-9]|1[0-2])$" }
43010
+ },
42947
43011
  {
42948
43012
  name: "check",
42949
43013
  in: "query",
@@ -42953,6 +43017,7 @@ var routeCatalog = [
42953
43017
  ],
42954
43018
  responses: {
42955
43019
  200: jsonResponse("Doctor report returned.", "DoctorReportDto"),
43020
+ 400: errorResponse("Invalid report month."),
42956
43021
  404: errorResponse("Project not found.")
42957
43022
  }
42958
43023
  },
@@ -80029,6 +80094,322 @@ async function trafficRoutes(app, opts) {
80029
80094
  });
80030
80095
  }
80031
80096
 
80097
+ // ../api-routes/src/doctor/checks/report-sweeps.ts
80098
+ import { and as and52, asc as asc11, eq as eq58, gte as gte15, lte as lte15 } from "drizzle-orm";
80099
+
80100
+ // ../api-routes/src/visibility-attribution.ts
80101
+ function buildQueryAttribution2(projectQueries) {
80102
+ const byId = /* @__PURE__ */ new Map();
80103
+ const byText = /* @__PURE__ */ new Map();
80104
+ for (const q of projectQueries) {
80105
+ byId.set(q.id, q);
80106
+ byText.set(q.query, q);
80107
+ }
80108
+ return { byId, byText };
80109
+ }
80110
+ function resolveCurrentQuery2(attribution, snap) {
80111
+ if (snap.queryId && attribution.byId.has(snap.queryId)) return attribution.byId.get(snap.queryId);
80112
+ if (snap.queryText && attribution.byText.has(snap.queryText)) return attribution.byText.get(snap.queryText);
80113
+ return void 0;
80114
+ }
80115
+
80116
+ // ../api-routes/src/doctor/checks/report-sweeps.ts
80117
+ function slotCoverage(expected, answered2) {
80118
+ return { signature: JSON.stringify([...expected].sort()), expected, answered: answered2 };
80119
+ }
80120
+ function inspectSweep(ctx, run) {
80121
+ const base = { runId: run.id, planVersionId: run.measurementPlanVersionId };
80122
+ if (run.measurementScope !== null) return { detail: { ...base, complete: false, reason: "spot-check" } };
80123
+ if (run.queries !== null) return { detail: { ...base, complete: false, reason: "query-subset" } };
80124
+ if (run.status !== RunStatuses.completed && run.status !== RunStatuses.partial) return { detail: { ...base, complete: false, reason: "not-terminal" } };
80125
+ const snapshots = ctx.db.select({
80126
+ queryId: querySnapshots.queryId,
80127
+ queryText: querySnapshots.queryText,
80128
+ provider: querySnapshots.provider,
80129
+ answerMentioned: querySnapshots.answerMentioned,
80130
+ answerText: querySnapshots.answerText,
80131
+ measurementExecutionId: querySnapshots.measurementExecutionId
80132
+ }).from(querySnapshots).where(eq58(querySnapshots.runId, run.id)).all();
80133
+ if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
80134
+ try {
80135
+ if (run.measurementPlanVersionId) {
80136
+ const version = ctx.db.select().from(measurementPlanVersions).where(and52(eq58(measurementPlanVersions.projectId, ctx.project.id), eq58(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
80137
+ if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
80138
+ const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
80139
+ const manifest = measurementRunExpectedSlots(run, plan);
80140
+ const expected2 = new Set(manifest.expectedSlots.map((slot) => measurementSlotKey(slot.executionId, slot.provider)));
80141
+ const answered3 = /* @__PURE__ */ new Set();
80142
+ let unbound = false;
80143
+ for (const snapshot of snapshots) {
80144
+ const executionId = snapshot.measurementExecutionId?.trim();
80145
+ if (!executionId) {
80146
+ unbound = true;
80147
+ continue;
80148
+ }
80149
+ const key = measurementSlotKey(executionId, snapshot.provider);
80150
+ if (expected2.has(key) && snapshot.answerText !== null) answered3.add(key);
80151
+ }
80152
+ const detail = {
80153
+ ...base,
80154
+ schemaVersion: plan.schemaVersion,
80155
+ complete: expected2.size > 0 && !unbound && answered3.size === expected2.size,
80156
+ reason: unbound ? "unbound-answers" : "frozen-plan",
80157
+ expected: expected2.size,
80158
+ answered: answered3.size,
80159
+ providers: [...new Set(manifest.expectedSlots.map((slot) => slot.provider))],
80160
+ ...plan.schemaVersion === MEASUREMENT_PLAN_V2_SCHEMA_VERSION ? {
80161
+ targetKeys: plan.targets.map((target) => target.stableKey),
80162
+ marketKeys: (plan.reportingScopes ?? []).map((market) => market.stableKey),
80163
+ queryClasses: [...new Set(plan.assignments.map((assignment) => assignment.queryClass))]
80164
+ } : {}
80165
+ };
80166
+ return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
80167
+ }
80168
+ const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq58(simpleMeasurementDefinitions.runId, run.id)).get();
80169
+ const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
80170
+ const project = ctx.db.select().from(projects).where(eq58(projects.id, ctx.project.id)).get();
80171
+ const basket = frozen ? frozen.queries.map((query) => ({ id: query.queryId, query: query.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq58(queries.projectId, project.id)).all();
80172
+ const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
80173
+ const attribution = buildQueryAttribution2(basket);
80174
+ const expected = new Set(basket.flatMap((query) => providers.map((provider) => JSON.stringify([query.id, provider.trim().toLowerCase()]))));
80175
+ const answered2 = /* @__PURE__ */ new Set();
80176
+ for (const snapshot of snapshots) {
80177
+ const query = resolveCurrentQuery2(attribution, snapshot);
80178
+ if (!query || snapshot.answerMentioned === null && !snapshot.answerText) continue;
80179
+ const key = JSON.stringify([query.id, snapshot.provider.trim().toLowerCase()]);
80180
+ if (expected.has(key)) answered2.add(key);
80181
+ }
80182
+ return {
80183
+ detail: { ...base, complete: expected.size > 0 && answered2.size === expected.size, reason: frozen ? "frozen-simple" : "current-basket", expected: expected.size, answered: answered2.size, providers },
80184
+ coverage: slotCoverage(expected, answered2)
80185
+ };
80186
+ } catch {
80187
+ return { detail: { ...base, complete: false, reason: "unreadable-evidence" } };
80188
+ }
80189
+ }
80190
+ function monthCoverage(evidence) {
80191
+ const groups = /* @__PURE__ */ new Map();
80192
+ for (const { detail, coverage } of evidence) {
80193
+ if (!coverage) continue;
80194
+ const group = groups.get(coverage.signature) ?? { expected: coverage.expected, answered: /* @__PURE__ */ new Set(), runIds: [] };
80195
+ for (const key of coverage.answered) group.answered.add(key);
80196
+ group.runIds.push(String(detail.runId));
80197
+ groups.set(coverage.signature, group);
80198
+ }
80199
+ const all = [...groups.values()];
80200
+ const ranked = all.filter((group) => !all.some((wider) => wider.expected.size > group.expected.size && [...group.expected].every((key) => wider.expected.has(key)))).map((group) => ({ ...group, covered: group.expected.size > 0 && [...group.expected].every((key) => group.answered.has(key)) })).sort((left, right) => Number(right.covered) - Number(left.covered) || right.answered.size / Math.max(1, right.expected.size) - left.answered.size / Math.max(1, left.expected.size));
80201
+ const best = ranked[0];
80202
+ return {
80203
+ eligibleRunIds: ranked.filter((group) => group.covered).flatMap((group) => group.runIds),
80204
+ coverage: best ? { expected: best.expected.size, answered: best.answered.size, sweeps: best.runIds.length } : null
80205
+ };
80206
+ }
80207
+ var reportSweepsCheck = {
80208
+ id: "report.sweeps",
80209
+ category: CheckCategories.schedules,
80210
+ scope: CheckScopes.project,
80211
+ notificationPolicy: CheckNotificationPolicies.silent,
80212
+ title: "Monthly report sweep readiness",
80213
+ run(ctx) {
80214
+ if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.sweeps.no-project", summary: "Project context required." };
80215
+ const now = /* @__PURE__ */ new Date();
80216
+ const reportMonths = reportMonthsForDoctor(ctx.reportMonth, now);
80217
+ const graded = gradedReportMonths(reportMonths, now);
80218
+ const months = reportMonths.map((month) => {
80219
+ const bounds = calendarMonthBounds(month);
80220
+ const monthRuns = ctx.db.select().from(runs).where(and52(eq58(runs.projectId, ctx.project.id), eq58(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte15(runs.createdAt, bounds.since), lte15(runs.createdAt, bounds.until))).orderBy(asc11(runs.createdAt), asc11(runs.id)).all();
80221
+ const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
80222
+ return {
80223
+ month,
80224
+ // Through day 3 the new month is shown but only the closed month is graded.
80225
+ graded: graded.has(month),
80226
+ daysRemaining: Math.max(0, Math.ceil((Date.parse(bounds.until) - now.getTime()) / 864e5) - 1),
80227
+ ...monthCoverage(evidence),
80228
+ runs: evidence.map((run) => run.detail)
80229
+ };
80230
+ });
80231
+ const missing = months.filter((month) => month.graded && month.eligibleRunIds.length === 0);
80232
+ return {
80233
+ status: missing.length ? CheckStatuses.warn : CheckStatuses.ok,
80234
+ code: missing.length ? "report.sweeps.missing" : "report.sweeps.ready",
80235
+ summary: missing.length ? `Whole-project sweeps do not yet answer every expected slot for ${missing.map((month) => `${month.month} (${month.daysRemaining} days left)`).join(", ")}.` : `Whole-project sweeps answer every expected slot for ${months.filter((month) => month.graded).map((month) => month.month).join(", ")}.`,
80236
+ remediation: missing.length ? `Review the monthly schedule and sweep scope for ${ctx.project.name}; running a sweep spends provider quota.` : null,
80237
+ details: { months }
80238
+ };
80239
+ }
80240
+ };
80241
+
80242
+ // ../api-routes/src/doctor/checks/report-models.ts
80243
+ import { and as and53, eq as eq59, gte as gte16, inArray as inArray23, lte as lte16 } from "drizzle-orm";
80244
+ var reportModelsCheck = {
80245
+ id: "report.models",
80246
+ category: CheckCategories.providers,
80247
+ scope: CheckScopes.project,
80248
+ notificationPolicy: CheckNotificationPolicies.silent,
80249
+ title: "Monthly report model continuity",
80250
+ run(ctx) {
80251
+ if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.models.no-project", summary: "Project context required." };
80252
+ const now = /* @__PURE__ */ new Date();
80253
+ const reportMonths = reportMonthsForDoctor(ctx.reportMonth, now);
80254
+ const graded = gradedReportMonths(reportMonths, now);
80255
+ const months = reportMonths.flatMap((month) => {
80256
+ const bounds = calendarMonthBounds(month);
80257
+ const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
80258
+ const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq59(runs.id, querySnapshots.runId)).where(and53(eq59(runs.projectId, ctx.project.id), eq59(runs.kind, RunKinds["answer-visibility"]), inArray23(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte16(runs.createdAt, bounds.since), lte16(runs.createdAt, bounds.until))).all();
80259
+ return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
80260
+ month,
80261
+ previousMonth: previous,
80262
+ // Through day 3 the new month is shown but only the closed month is graded.
80263
+ graded: graded.has(month),
80264
+ selection: {},
80265
+ frame,
80266
+ continuity: continuity2.status,
80267
+ providers: continuity2.providers.map((provider) => {
80268
+ const modelDates = provider.toModels.map((model) => ({
80269
+ model,
80270
+ firstObservedAt: observations.filter((observation) => observation.provider === provider.provider && observation.model?.trim() === model).map((observation) => observation.at).sort()[0] ?? null
80271
+ }));
80272
+ const addedModelDates = modelDates.filter((model) => !provider.fromModels.includes(model.model));
80273
+ return {
80274
+ ...provider,
80275
+ // Dates are project observations in this report month, not provider deployment dates.
80276
+ firstObservedAtBasis: "new-project-provider-model-in-report-month",
80277
+ firstObservedAt: addedModelDates.map((model) => model.firstObservedAt).filter((at) => at !== null).sort()[0] ?? null,
80278
+ modelDates
80279
+ };
80280
+ })
80281
+ }));
80282
+ });
80283
+ const frameLabel = (month) => month.frame === "class" ? `${month.month}, class basket` : month.month;
80284
+ const gradedMonths = months.filter((month) => month.graded);
80285
+ const excluded = gradedMonths.flatMap((month) => month.providers.filter((provider) => provider.status !== "included").map((provider) => `${provider.provider}: ${provider.fromModels.join(", ") || "unknown"} \u2192 ${provider.toModels.join(", ") || "unknown"} (${frameLabel(month)}; ${provider.firstObservedAt ? `new model first observed ${provider.firstObservedAt}` : "change date unknown"})`));
80286
+ const insufficient = gradedMonths.filter((month) => month.providers.length === 0);
80287
+ const flagged = gradedMonths.find((month) => month.providers.length === 0 || month.providers.some((provider) => provider.status !== "included")) ?? gradedMonths[0];
80288
+ return {
80289
+ status: excluded.length || insufficient.length ? CheckStatuses.warn : CheckStatuses.ok,
80290
+ code: excluded.length ? "report.models.excluded" : insufficient.length ? "report.models.insufficient" : "report.models.continuous",
80291
+ summary: excluded.length ? `Monthly comparison excludes ${excluded.join("; ")}.` : insufficient.length ? `No common query/provider evidence establishes model continuity for ${insufficient.map(frameLabel).join(", ")}.` : "All compared providers have continuous snapshot model evidence.",
80292
+ remediation: excluded.length || insufficient.length ? `Inspect canonry visibility-compare ${ctx.project.name} --from ${flagged.previousMonth} --to ${flagged.month} --format json; do not pool excluded providers into a trend.` : null,
80293
+ details: { months }
80294
+ };
80295
+ }
80296
+ };
80297
+
80298
+ // ../api-routes/src/doctor/checks/report-daily-data.ts
80299
+ import { eq as eq60 } from "drizzle-orm";
80300
+ var REPORTING_LAG_DAYS = 3;
80301
+ function dateSpan(dates) {
80302
+ const sorted = [...dates].sort();
80303
+ return sorted.length === 0 ? null : { start: sorted[0], end: sorted.at(-1) };
80304
+ }
80305
+ function latestSyncRange(ctx, source) {
80306
+ const projectId = ctx.project.id;
80307
+ if (source === "ga") {
80308
+ const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq60(gaDailyTotals.projectId, projectId)).all();
80309
+ const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
80310
+ const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq60(gaTrafficSummaries.projectId, projectId)).get();
80311
+ if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
80312
+ const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
80313
+ return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
80314
+ }
80315
+ const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq60(gscDailyTotals.projectId, projectId)).all();
80316
+ const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
80317
+ const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
80318
+ if (!span || latest === null) return null;
80319
+ const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq60(gscDataWatermarks.projectId, projectId)).get();
80320
+ return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
80321
+ }
80322
+ function dailyCoverage(ctx, month, source) {
80323
+ const project = ctx.project;
80324
+ const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
80325
+ const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq60(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq60(gscDailyTotals.projectId, project.id)).all();
80326
+ if (!connection && stored.length === 0) return null;
80327
+ const bounds = calendarMonthBounds(month);
80328
+ const start = bounds.since.slice(0, 10);
80329
+ const end = bounds.until.slice(0, 10);
80330
+ const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
80331
+ const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
80332
+ const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq60(projects.id, project.id)).get().date.slice(0, 10);
80333
+ const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
80334
+ const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
80335
+ const byDate = new Map(stored.map((row) => [row.date, row.count]));
80336
+ const coverage = latestSyncRange(ctx, source);
80337
+ const lagFloor = coverage ? isoDateDaysBeforeInTimeZone(coverage.syncedAt, REPORTING_LAG_DAYS, timeZone) : null;
80338
+ const zeroProvenThrough = coverage && lagFloor ? coverage.end < lagFloor ? coverage.end : lagFloor : null;
80339
+ const absent = [];
80340
+ let observedDays = 0;
80341
+ let observedZeroDays = 0;
80342
+ let queriedEmptyDays = 0;
80343
+ let pendingDays = 0;
80344
+ let beforeConnectionDays = 0;
80345
+ for (const date of calendarDateRange(start, end)) {
80346
+ if (date > matureThrough) {
80347
+ pendingDays++;
80348
+ continue;
80349
+ }
80350
+ if (date < onboardingDate) {
80351
+ beforeConnectionDays++;
80352
+ continue;
80353
+ }
80354
+ const count4 = byDate.get(date);
80355
+ if (count4 !== void 0) {
80356
+ observedDays++;
80357
+ if (count4 === 0) observedZeroDays++;
80358
+ } else if (coverage && zeroProvenThrough && date >= coverage.start && date <= zeroProvenThrough) queriedEmptyDays++;
80359
+ else absent.push(date);
80360
+ }
80361
+ return {
80362
+ source,
80363
+ start,
80364
+ end,
80365
+ matureThrough,
80366
+ timeZone,
80367
+ timeZoneBasis: source === "ga" ? "fallback" : "provider",
80368
+ onboardingDate,
80369
+ observedDays,
80370
+ observedZeroDays,
80371
+ // Queried by the latest sync and returned no row: the source reports no activity for them.
80372
+ latestSyncRange: coverage,
80373
+ zeroProvenThrough,
80374
+ queriedEmptyDays,
80375
+ unknownDays: absent.length,
80376
+ unknownRanges: groupIsoDateRanges(absent),
80377
+ // Only the latest sync's range is on record, so an absent date outside it may never have been queried.
80378
+ confirmedMissingDays: 0,
80379
+ pendingDays,
80380
+ beforeConnectionDays
80381
+ };
80382
+ }
80383
+ var reportDailyDataCheck = {
80384
+ id: "report.daily-data",
80385
+ category: CheckCategories.integrations,
80386
+ scope: CheckScopes.project,
80387
+ notificationPolicy: CheckNotificationPolicies.silent,
80388
+ title: "Monthly report daily data coverage",
80389
+ run(ctx) {
80390
+ if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.daily-data.no-project", summary: "Project context required." };
80391
+ const months = reportMonthsForDoctor(ctx.reportMonth).map((month) => ({ month, sources: ["ga", "gsc"].flatMap((source) => {
80392
+ const coverage = dailyCoverage(ctx, month, source);
80393
+ return coverage ? [coverage] : [];
80394
+ }) }));
80395
+ const sources = months.flatMap((month) => month.sources);
80396
+ if (!sources.length) return { status: CheckStatuses.skipped, code: "report.daily-data.not-connected", summary: "No connected or stored GA4/Search Console evidence.", details: { months } };
80397
+ const unknown = sources.reduce((sum, source) => sum + source.unknownDays, 0);
80398
+ const mature = sources.some((source) => source.observedDays + source.queriedEmptyDays + source.unknownDays > 0);
80399
+ const gaUnknown = sources.some((source) => source.source === "ga" && source.unknownDays > 0);
80400
+ const oldest = months[0].month;
80401
+ const days = inclusiveDayCount(calendarMonthBounds(oldest).since.slice(0, 10), (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) ?? 1;
80402
+ const commands = [...new Set(sources.filter((source) => source.unknownDays > 0).map((source) => source.source === "ga" ? `canonry ga sync ${ctx.project.name} --days ${Math.min(90, days)}` : `canonry google sync ${ctx.project.name} --days ${Math.min(480, days)}`))];
80403
+ return {
80404
+ status: unknown ? CheckStatuses.warn : mature ? CheckStatuses.ok : CheckStatuses.skipped,
80405
+ code: unknown ? "report.daily-data.unknown" : mature ? "report.daily-data.observed" : "report.daily-data.pending",
80406
+ summary: unknown ? `${unknown} source-days have unknown coverage: no row, and not proven quiet by the latest sync (outside its range, or too close to when it ran for the source to have published them). Absent rows can mean zero activity or unsynced data.` : mature ? "Every mature date has an observed daily total or was queried by the latest sync and returned no activity." : "No mature dates since connection; reporting latency is excluded.",
80407
+ remediation: unknown ? `Review coverage and backfill: ${commands.map((command) => `\`${command}\``).join("; ")}. A sync whose window covers these dates records them as queried, so dates it returns no row for then count as zero activity.${gaUnknown && days > 90 ? " GA4 sync supports at most 90 days; older unknown dates need an external export." : ""}` : null,
80408
+ details: { months }
80409
+ };
80410
+ }
80411
+ };
80412
+
80032
80413
  // ../api-routes/src/doctor/checks/agent.ts
80033
80414
  import crypto46 from "crypto";
80034
80415
  import fs6 from "fs";
@@ -80357,7 +80738,7 @@ var skillsTriggerSurfaceCheck = {
80357
80738
  var AGENT_CHECKS = [skillsInstalledCheck, skillsCurrentCheck, skillsTriggerSurfaceCheck];
80358
80739
 
80359
80740
  // ../api-routes/src/doctor/checks/backlinks.ts
80360
- import { and as and52, eq as eq58 } from "drizzle-orm";
80741
+ import { and as and54, eq as eq61 } from "drizzle-orm";
80361
80742
  function skippedNoProject() {
80362
80743
  return {
80363
80744
  status: CheckStatuses.skipped,
@@ -80373,8 +80754,8 @@ var BACKLINKS_CHECKS = [
80373
80754
  title: "Backlinks source connected",
80374
80755
  run: (ctx) => {
80375
80756
  if (!ctx.project) return skippedNoProject();
80376
- const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq58(projects.id, ctx.project.id)).get();
80377
- const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq58(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
80757
+ const projectRow = ctx.db.select({ autoExtract: projects.autoExtractBacklinks }).from(projects).where(eq61(projects.id, ctx.project.id)).get();
80758
+ const readySync = ctx.db.select({ id: ccReleaseSyncs.id }).from(ccReleaseSyncs).where(eq61(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).limit(1).get();
80378
80759
  const ccConnected = projectRow?.autoExtract === true && !!readySync;
80379
80760
  const connected = [];
80380
80761
  if (ccConnected) connected.push(BacklinkSources.commoncrawl);
@@ -80387,9 +80768,9 @@ var BACKLINKS_CHECKS = [
80387
80768
  details: { commoncrawl: ccConnected }
80388
80769
  };
80389
80770
  }
80390
- const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and52(
80391
- eq58(backlinkSummaries.projectId, ctx.project.id),
80392
- eq58(backlinkSummaries.source, BacklinkSources.commoncrawl)
80771
+ const ccHasData = ccConnected ? !!ctx.db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and54(
80772
+ eq61(backlinkSummaries.projectId, ctx.project.id),
80773
+ eq61(backlinkSummaries.source, BacklinkSources.commoncrawl)
80393
80774
  )).limit(1).get() : false;
80394
80775
  return {
80395
80776
  status: CheckStatuses.ok,
@@ -80549,7 +80930,7 @@ var BING_AUTH_CHECKS = [
80549
80930
  ];
80550
80931
 
80551
80932
  // ../api-routes/src/doctor/checks/content.ts
80552
- import { eq as eq59 } from "drizzle-orm";
80933
+ import { eq as eq62 } from "drizzle-orm";
80553
80934
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
80554
80935
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
80555
80936
  function skippedNoProject2() {
@@ -80562,7 +80943,7 @@ function skippedNoProject2() {
80562
80943
  }
80563
80944
  function loadProject(ctx) {
80564
80945
  if (!ctx.project) return null;
80565
- return ctx.db.select().from(projects).where(eq59(projects.id, ctx.project.id)).get() ?? null;
80946
+ return ctx.db.select().from(projects).where(eq62(projects.id, ctx.project.id)).get() ?? null;
80566
80947
  }
80567
80948
  function percent(value) {
80568
80949
  return Math.round(value * 100);
@@ -80654,7 +81035,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
80654
81035
  );
80655
81036
 
80656
81037
  // ../api-routes/src/doctor/checks/ads.ts
80657
- import { eq as eq60 } from "drizzle-orm";
81038
+ import { eq as eq63 } from "drizzle-orm";
80658
81039
  var RECENT_SYNC_WARN_DAYS = 7;
80659
81040
  var RECENT_SYNC_FAIL_DAYS = 30;
80660
81041
  var adsConnectionCheck = {
@@ -80671,7 +81052,7 @@ var adsConnectionCheck = {
80671
81052
  remediation: null
80672
81053
  };
80673
81054
  }
80674
- const row = ctx.db.select().from(adsConnections).where(eq60(adsConnections.projectId, ctx.project.id)).get();
81055
+ const row = ctx.db.select().from(adsConnections).where(eq63(adsConnections.projectId, ctx.project.id)).get();
80675
81056
  if (!row) {
80676
81057
  return {
80677
81058
  status: CheckStatuses.skipped,
@@ -80721,7 +81102,7 @@ var adsRecentSyncCheck = {
80721
81102
  remediation: null
80722
81103
  };
80723
81104
  }
80724
- const row = ctx.db.select().from(adsConnections).where(eq60(adsConnections.projectId, ctx.project.id)).get();
81105
+ const row = ctx.db.select().from(adsConnections).where(eq63(adsConnections.projectId, ctx.project.id)).get();
80725
81106
  if (!row) {
80726
81107
  return {
80727
81108
  status: CheckStatuses.skipped,
@@ -80912,7 +81293,7 @@ var ga4ConnectionCheck = {
80912
81293
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
80913
81294
 
80914
81295
  // ../api-routes/src/doctor/checks/data-freshness.ts
80915
- import { and as and53, desc as desc28, eq as eq61, inArray as inArray23 } from "drizzle-orm";
81296
+ import { and as and55, desc as desc28, eq as eq64, inArray as inArray24 } from "drizzle-orm";
80916
81297
  var GA_DATA_AGING_DAYS = 3;
80917
81298
  var GA_DATA_STALE_DAYS = 5;
80918
81299
  var GSC_DATA_AGING_DAYS = 5;
@@ -80940,7 +81321,7 @@ function skipped(code, summary3) {
80940
81321
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
80941
81322
  }
80942
81323
  function lastSyncAgeDays(ctx, kinds) {
80943
- const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and53(eq61(runs.projectId, ctx.project.id), inArray23(runs.kind, kinds), eq61(runs.status, "completed"))).orderBy(desc28(runs.createdAt)).limit(1).get();
81324
+ const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and55(eq64(runs.projectId, ctx.project.id), inArray24(runs.kind, kinds), eq64(runs.status, "completed"))).orderBy(desc28(runs.createdAt)).limit(1).get();
80944
81325
  if (!row?.createdAt) return null;
80945
81326
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
80946
81327
  return Number.isFinite(age) ? age : null;
@@ -81036,7 +81417,7 @@ var gaRecentDataCheck = {
81036
81417
  const connection = gaConnected(ctx);
81037
81418
  if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
81038
81419
  if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
81039
- const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq61(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc28(gaDailyTotals.date)).limit(1).get();
81420
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq64(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc28(gaDailyTotals.date)).limit(1).get();
81040
81421
  return grade(ctx, GA_SPEC, newest?.date);
81041
81422
  }
81042
81423
  };
@@ -81051,9 +81432,9 @@ var gscRecentDataCheck = {
81051
81432
  if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
81052
81433
  return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
81053
81434
  }
81054
- const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq61(gscDataWatermarks.projectId, ctx.project.id)).get();
81435
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq64(gscDataWatermarks.projectId, ctx.project.id)).get();
81055
81436
  if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
81056
- const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq61(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc28(gscDailyTotals.date)).limit(1).get();
81437
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq64(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc28(gscDailyTotals.date)).limit(1).get();
81057
81438
  return grade(ctx, GSC_SPEC, newest?.date);
81058
81439
  }
81059
81440
  };
@@ -81321,7 +81702,7 @@ var siteReachabilityCheck = {
81321
81702
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
81322
81703
 
81323
81704
  // ../api-routes/src/doctor/checks/gbp-auth.ts
81324
- import { and as and54, eq as eq62 } from "drizzle-orm";
81705
+ import { and as and56, eq as eq65 } from "drizzle-orm";
81325
81706
  var RECENT_SYNC_WARN_DAYS2 = 4;
81326
81707
  var RECENT_SYNC_FAIL_DAYS2 = 30;
81327
81708
  function skippedNoProject3() {
@@ -81554,7 +81935,7 @@ var recentSyncCheck = {
81554
81935
  title: "GBP recent sync",
81555
81936
  run: (ctx) => {
81556
81937
  if (!ctx.project) return skippedNoProject3();
81557
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and54(eq62(gbpLocations.projectId, ctx.project.id), eq62(gbpLocations.selected, true))).all();
81938
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and56(eq65(gbpLocations.projectId, ctx.project.id), eq65(gbpLocations.selected, true))).all();
81558
81939
  if (selected.length === 0) {
81559
81940
  return {
81560
81941
  status: CheckStatuses.skipped,
@@ -81614,7 +81995,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
81614
81995
  );
81615
81996
 
81616
81997
  // ../api-routes/src/doctor/checks/places.ts
81617
- import { eq as eq63 } from "drizzle-orm";
81998
+ import { eq as eq66 } from "drizzle-orm";
81618
81999
  var apiKeyCheck = {
81619
82000
  id: "gbp.places.api-key",
81620
82001
  category: CheckCategories.auth,
@@ -81659,7 +82040,7 @@ var apiKeyCheck = {
81659
82040
  details: { tier: cfg.tier }
81660
82041
  };
81661
82042
  }
81662
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq63(gbpLocations.projectId, ctx.project.id)).all();
82043
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq66(gbpLocations.projectId, ctx.project.id)).all();
81663
82044
  const selected = rows.filter((r) => r.selected);
81664
82045
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
81665
82046
  const details = {
@@ -82201,7 +82582,7 @@ var RUNTIME_STATE_CHECKS = [
82201
82582
  ];
82202
82583
 
82203
82584
  // ../api-routes/src/doctor/checks/traffic-source.ts
82204
- import { and as and55, eq as eq64, gte as gte15, inArray as inArray24, ne as ne10, sql as sql25 } from "drizzle-orm";
82585
+ import { and as and57, eq as eq67, gte as gte17, inArray as inArray25, ne as ne10, sql as sql25 } from "drizzle-orm";
82205
82586
  var RECENT_DATA_WARN_DAYS = 7;
82206
82587
  var RECENT_DATA_FAIL_DAYS = 30;
82207
82588
  function isCloudflareDirectPush(source) {
@@ -82240,8 +82621,8 @@ function skippedNoProject5() {
82240
82621
  function loadProbes(ctx) {
82241
82622
  if (!ctx.project) return [];
82242
82623
  const rows = ctx.db.select().from(trafficSources).where(
82243
- and55(
82244
- eq64(trafficSources.projectId, ctx.project.id),
82624
+ and57(
82625
+ eq67(trafficSources.projectId, ctx.project.id),
82245
82626
  ne10(trafficSources.status, TrafficSourceStatuses.archived)
82246
82627
  )
82247
82628
  ).all();
@@ -82347,28 +82728,28 @@ var recentDataCheck = {
82347
82728
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
82348
82729
  const recentCrawlers = Number(
82349
82730
  ctx.db.select({ total: sql25`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
82350
- and55(
82351
- eq64(crawlerEventsHourly.projectId, ctx.project.id),
82352
- inArray24(crawlerEventsHourly.sourceId, activeSourceIds),
82353
- gte15(crawlerEventsHourly.tsHour, warnCutoff)
82731
+ and57(
82732
+ eq67(crawlerEventsHourly.projectId, ctx.project.id),
82733
+ inArray25(crawlerEventsHourly.sourceId, activeSourceIds),
82734
+ gte17(crawlerEventsHourly.tsHour, warnCutoff)
82354
82735
  )
82355
82736
  ).get()?.total ?? 0
82356
82737
  );
82357
82738
  const recentReferrals = Number(
82358
82739
  ctx.db.select({ total: sql25`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
82359
- and55(
82360
- eq64(aiReferralEventsHourly.projectId, ctx.project.id),
82361
- inArray24(aiReferralEventsHourly.sourceId, activeSourceIds),
82362
- gte15(aiReferralEventsHourly.tsHour, warnCutoff)
82740
+ and57(
82741
+ eq67(aiReferralEventsHourly.projectId, ctx.project.id),
82742
+ inArray25(aiReferralEventsHourly.sourceId, activeSourceIds),
82743
+ gte17(aiReferralEventsHourly.tsHour, warnCutoff)
82363
82744
  )
82364
82745
  ).get()?.total ?? 0
82365
82746
  );
82366
82747
  const recentUserFetches = Number(
82367
82748
  ctx.db.select({ total: sql25`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
82368
- and55(
82369
- eq64(aiUserFetchEventsHourly.projectId, ctx.project.id),
82370
- inArray24(aiUserFetchEventsHourly.sourceId, activeSourceIds),
82371
- gte15(aiUserFetchEventsHourly.tsHour, warnCutoff)
82749
+ and57(
82750
+ eq67(aiUserFetchEventsHourly.projectId, ctx.project.id),
82751
+ inArray25(aiUserFetchEventsHourly.sourceId, activeSourceIds),
82752
+ gte17(aiUserFetchEventsHourly.tsHour, warnCutoff)
82372
82753
  )
82373
82754
  ).get()?.total ?? 0
82374
82755
  );
@@ -82391,28 +82772,28 @@ var recentDataCheck = {
82391
82772
  }
82392
82773
  const olderCrawlers = Number(
82393
82774
  ctx.db.select({ total: sql25`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
82394
- and55(
82395
- eq64(crawlerEventsHourly.projectId, ctx.project.id),
82396
- inArray24(crawlerEventsHourly.sourceId, activeSourceIds),
82397
- gte15(crawlerEventsHourly.tsHour, failCutoff)
82775
+ and57(
82776
+ eq67(crawlerEventsHourly.projectId, ctx.project.id),
82777
+ inArray25(crawlerEventsHourly.sourceId, activeSourceIds),
82778
+ gte17(crawlerEventsHourly.tsHour, failCutoff)
82398
82779
  )
82399
82780
  ).get()?.total ?? 0
82400
82781
  );
82401
82782
  const olderReferrals = Number(
82402
82783
  ctx.db.select({ total: sql25`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
82403
- and55(
82404
- eq64(aiReferralEventsHourly.projectId, ctx.project.id),
82405
- inArray24(aiReferralEventsHourly.sourceId, activeSourceIds),
82406
- gte15(aiReferralEventsHourly.tsHour, failCutoff)
82784
+ and57(
82785
+ eq67(aiReferralEventsHourly.projectId, ctx.project.id),
82786
+ inArray25(aiReferralEventsHourly.sourceId, activeSourceIds),
82787
+ gte17(aiReferralEventsHourly.tsHour, failCutoff)
82407
82788
  )
82408
82789
  ).get()?.total ?? 0
82409
82790
  );
82410
82791
  const olderUserFetches = Number(
82411
82792
  ctx.db.select({ total: sql25`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
82412
- and55(
82413
- eq64(aiUserFetchEventsHourly.projectId, ctx.project.id),
82414
- inArray24(aiUserFetchEventsHourly.sourceId, activeSourceIds),
82415
- gte15(aiUserFetchEventsHourly.tsHour, failCutoff)
82793
+ and57(
82794
+ eq67(aiUserFetchEventsHourly.projectId, ctx.project.id),
82795
+ inArray25(aiUserFetchEventsHourly.sourceId, activeSourceIds),
82796
+ gte17(aiUserFetchEventsHourly.tsHour, failCutoff)
82416
82797
  )
82417
82798
  ).get()?.total ?? 0
82418
82799
  );
@@ -83253,6 +83634,9 @@ var ALL_CHECKS = [
83253
83634
  ...WORDPRESS_PUBLISH_CHECKS,
83254
83635
  ...GA_AUTH_CHECKS,
83255
83636
  ...DATA_FRESHNESS_CHECKS,
83637
+ reportSweepsCheck,
83638
+ reportModelsCheck,
83639
+ reportDailyDataCheck,
83256
83640
  ...ADS_CHECKS,
83257
83641
  ...GOOGLE_MARKETING_DOCTOR_CHECKS,
83258
83642
  ...PROVIDERS_CHECKS,
@@ -83263,6 +83647,9 @@ var ALL_CHECKS = [
83263
83647
  // Network probe last, so a slow or unreachable site never delays the local checks.
83264
83648
  ...SITE_REACHABILITY_CHECKS
83265
83649
  ];
83650
+ function scheduledHealthCheckIds() {
83651
+ return ALL_CHECKS.filter((check2) => check2.scope === CheckScopes.project && !check2.optIn && check2.notificationPolicy !== CheckNotificationPolicies.silent).map((check2) => check2.id);
83652
+ }
83266
83653
 
83267
83654
  // ../api-routes/src/doctor/runner.ts
83268
83655
  function matchesCheckId(checkId, filters) {
@@ -83295,7 +83682,9 @@ async function runChecks(ctx, checks, options = {}) {
83295
83682
  } catch (err) {
83296
83683
  const message = describeError(err);
83297
83684
  output = {
83298
- status: CheckStatuses.fail,
83685
+ // An advisory that cannot run is still only an advisory: it must not
83686
+ // fail the doctor (and its exit code) the way a broken health check does.
83687
+ status: definition.notificationPolicy === CheckNotificationPolicies.silent ? CheckStatuses.warn : CheckStatuses.fail,
83299
83688
  code: `${definition.id}.runtime-error`,
83300
83689
  summary: `Check threw an unexpected error: ${message}`,
83301
83690
  remediation: null,
@@ -83308,6 +83697,7 @@ async function runChecks(ctx, checks, options = {}) {
83308
83697
  scope: definition.scope,
83309
83698
  title: definition.title,
83310
83699
  status: output.status,
83700
+ ...definition.notificationPolicy ? { notificationPolicy: definition.notificationPolicy } : {},
83311
83701
  code: output.code,
83312
83702
  summary: output.summary,
83313
83703
  remediation: output.remediation ?? null,
@@ -83318,6 +83708,7 @@ async function runChecks(ctx, checks, options = {}) {
83318
83708
  return {
83319
83709
  scope: targetScope,
83320
83710
  project: projectName,
83711
+ ...ctx.project ? { reportMonths: reportMonthsForDoctor(ctx.reportMonth, startedAt) } : {},
83321
83712
  generatedAt: startedAt.toISOString(),
83322
83713
  durationMs: Date.now() - startedAt.getTime(),
83323
83714
  summary: summarizeCheckResults(results),
@@ -83326,6 +83717,12 @@ async function runChecks(ctx, checks, options = {}) {
83326
83717
  }
83327
83718
 
83328
83719
  // ../api-routes/src/doctor.ts
83720
+ function parseReportMonth(raw) {
83721
+ if (raw === void 0) return void 0;
83722
+ const parsed = reportMonthSchema.safeParse(raw);
83723
+ if (!parsed.success || raw > (/* @__PURE__ */ new Date()).toISOString().slice(0, 7)) throw validationError('"reportMonth" must be YYYY-MM and cannot be in the future');
83724
+ return parsed.data;
83725
+ }
83329
83726
  function parseCheckIds(raw) {
83330
83727
  if (!raw) return [];
83331
83728
  return raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
@@ -83340,6 +83737,7 @@ async function doctorRoutes(app, opts) {
83340
83737
  const checkIds = parseCheckIds(request.query.check);
83341
83738
  const ctx = {
83342
83739
  db: app.db,
83740
+ reportMonth: parseReportMonth(request.query.reportMonth),
83343
83741
  project: null,
83344
83742
  googleConnectionStore: opts.googleConnectionStore,
83345
83743
  bingConnectionStore: opts.bingConnectionStore,
@@ -83367,6 +83765,7 @@ async function doctorRoutes(app, opts) {
83367
83765
  const checkIds = parseCheckIds(request.query.check);
83368
83766
  const ctx = {
83369
83767
  db: app.db,
83768
+ reportMonth: parseReportMonth(request.query.reportMonth),
83370
83769
  project: {
83371
83770
  id: project.id,
83372
83771
  name: project.name,
@@ -83398,7 +83797,7 @@ async function doctorRoutes(app, opts) {
83398
83797
 
83399
83798
  // ../api-routes/src/discovery/routes.ts
83400
83799
  import crypto47 from "crypto";
83401
- import { and as and56, desc as desc29, eq as eq65, gte as gte16, inArray as inArray25, isNull as isNull11, or as or15 } from "drizzle-orm";
83800
+ import { and as and58, desc as desc29, eq as eq68, gte as gte18, inArray as inArray26, isNull as isNull11, or as or15 } from "drizzle-orm";
83402
83801
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
83403
83802
  async function discoveryRoutes(app, opts) {
83404
83803
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -83432,13 +83831,13 @@ async function discoveryRoutes(app, opts) {
83432
83831
  const now = (/* @__PURE__ */ new Date()).toISOString();
83433
83832
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
83434
83833
  const decision = app.db.transaction((tx) => {
83435
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and56(
83436
- eq65(discoverySessions.projectId, project.id),
83437
- eq65(discoverySessions.icpDescription, icpDescription),
83834
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and58(
83835
+ eq68(discoverySessions.projectId, project.id),
83836
+ eq68(discoverySessions.icpDescription, icpDescription),
83438
83837
  // Buyer is part of session identity: it changes the seed prompt's
83439
83838
  // semantics, so a request with a different (or no) buyer must start
83440
83839
  // its own session, never adopt another buyer's probes.
83441
- parsed.data.buyerDescription == null ? isNull11(discoverySessions.buyerDescription) : eq65(discoverySessions.buyerDescription, parsed.data.buyerDescription),
83840
+ parsed.data.buyerDescription == null ? isNull11(discoverySessions.buyerDescription) : eq68(discoverySessions.buyerDescription, parsed.data.buyerDescription),
83442
83841
  // Locations are identity too: a different service-area subset seeds
83443
83842
  // and probes a different geo, so it must never reuse another geo's
83444
83843
  // session. resolveLocations is deterministic (project-config order),
@@ -83448,17 +83847,17 @@ async function discoveryRoutes(app, opts) {
83448
83847
  // locations a NULL row's subset is unknowable, so it conservatively
83449
83848
  // never reuses — a one-time, bounded (2h window) non-reuse after
83450
83849
  // upgrade, never a wrong reuse.
83451
- locations.length === 0 ? or15(isNull11(discoverySessions.locations), eq65(discoverySessions.locations, locations)) : eq65(discoverySessions.locations, locations),
83850
+ locations.length === 0 ? or15(isNull11(discoverySessions.locations), eq68(discoverySessions.locations, locations)) : eq68(discoverySessions.locations, locations),
83452
83851
  // Seed provider set is identity: a different phrasing distribution
83453
83852
  // must never reuse another set's session. Null (= Gemini-only
83454
83853
  // default, including explicit ['gemini']) matches legacy rows.
83455
- seedProviders == null ? isNull11(discoverySessions.seedProviders) : eq65(discoverySessions.seedProviders, seedProviders),
83456
- inArray25(discoverySessions.status, [
83854
+ seedProviders == null ? isNull11(discoverySessions.seedProviders) : eq68(discoverySessions.seedProviders, seedProviders),
83855
+ inArray26(discoverySessions.status, [
83457
83856
  DiscoverySessionStatuses.queued,
83458
83857
  DiscoverySessionStatuses.seeding,
83459
83858
  DiscoverySessionStatuses.probing
83460
83859
  ]),
83461
- gte16(discoverySessions.createdAt, ageFloorIso)
83860
+ gte18(discoverySessions.createdAt, ageFloorIso)
83462
83861
  )).orderBy(desc29(discoverySessions.createdAt)).get();
83463
83862
  if (existing && existing.runId) {
83464
83863
  return { reused: true, sessionId: existing.id, runId: existing.runId };
@@ -83528,7 +83927,7 @@ async function discoveryRoutes(app, opts) {
83528
83927
  const project = resolveProject(app.db, request.params.name);
83529
83928
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
83530
83929
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
83531
- const rows = app.db.select().from(discoverySessions).where(eq65(discoverySessions.projectId, project.id)).orderBy(desc29(discoverySessions.createdAt)).limit(limit).all();
83930
+ const rows = app.db.select().from(discoverySessions).where(eq68(discoverySessions.projectId, project.id)).orderBy(desc29(discoverySessions.createdAt)).limit(limit).all();
83532
83931
  return reply.send(rows.map(serializeSession));
83533
83932
  }
83534
83933
  );
@@ -83536,11 +83935,11 @@ async function discoveryRoutes(app, opts) {
83536
83935
  "/projects/:name/discover/sessions/:id",
83537
83936
  async (request, reply) => {
83538
83937
  const project = resolveProject(app.db, request.params.name);
83539
- const session = app.db.select().from(discoverySessions).where(eq65(discoverySessions.id, request.params.id)).get();
83938
+ const session = app.db.select().from(discoverySessions).where(eq68(discoverySessions.id, request.params.id)).get();
83540
83939
  if (!session || session.projectId !== project.id) {
83541
83940
  throw notFound("Discovery session", request.params.id);
83542
83941
  }
83543
- const probeRows = app.db.select().from(discoveryProbes).where(eq65(discoveryProbes.sessionId, session.id)).all();
83942
+ const probeRows = app.db.select().from(discoveryProbes).where(eq68(discoveryProbes.sessionId, session.id)).all();
83544
83943
  const detail = {
83545
83944
  ...serializeSession(session),
83546
83945
  probes: probeRows.map(serializeProbe)
@@ -83552,7 +83951,7 @@ async function discoveryRoutes(app, opts) {
83552
83951
  "/projects/:name/discover/sessions/:id/harvest",
83553
83952
  async (request, reply) => {
83554
83953
  const project = resolveProject(app.db, request.params.name);
83555
- const session = app.db.select().from(discoverySessions).where(eq65(discoverySessions.id, request.params.id)).get();
83954
+ const session = app.db.select().from(discoverySessions).where(eq68(discoverySessions.id, request.params.id)).get();
83556
83955
  if (!session || session.projectId !== project.id) {
83557
83956
  throw notFound("Discovery session", request.params.id);
83558
83957
  }
@@ -83560,7 +83959,7 @@ async function discoveryRoutes(app, opts) {
83560
83959
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
83561
83960
  const applyAnchor = request.query.anchor !== "false";
83562
83961
  const provider = session.seedProvider ?? "gemini";
83563
- const probeRows = app.db.select().from(discoveryProbes).where(eq65(discoveryProbes.sessionId, session.id)).all();
83962
+ const probeRows = app.db.select().from(discoveryProbes).where(eq68(discoveryProbes.sessionId, session.id)).all();
83564
83963
  const extract = opts.harvestSearchQueries;
83565
83964
  const probesWithQueries = probeRows.map((row) => {
83566
83965
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -83571,7 +83970,7 @@ async function discoveryRoutes(app, opts) {
83571
83970
  return { searchQueries: [] };
83572
83971
  }
83573
83972
  });
83574
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq65(queries.projectId, project.id)).all().map((r) => r.query);
83973
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq68(queries.projectId, project.id)).all().map((r) => r.query);
83575
83974
  const anchorTerms = buildHarvestAnchorTerms(
83576
83975
  [session.icpDescription ?? "", ...trackedQueries],
83577
83976
  effectiveDomains(project)
@@ -83618,12 +84017,12 @@ async function discoveryRoutes(app, opts) {
83618
84017
  "/projects/:name/discover/sessions/:id/promote",
83619
84018
  async (request, reply) => {
83620
84019
  const project = resolveProject(app.db, request.params.name);
83621
- const session = app.db.select().from(discoverySessions).where(eq65(discoverySessions.id, request.params.id)).get();
84020
+ const session = app.db.select().from(discoverySessions).where(eq68(discoverySessions.id, request.params.id)).get();
83622
84021
  if (!session || session.projectId !== project.id) {
83623
84022
  throw notFound("Discovery session", request.params.id);
83624
84023
  }
83625
- const probeRows = app.db.select().from(discoveryProbes).where(eq65(discoveryProbes.sessionId, session.id)).all();
83626
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq65(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
84024
+ const probeRows = app.db.select().from(discoveryProbes).where(eq68(discoveryProbes.sessionId, session.id)).all();
84025
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq68(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
83627
84026
  const seenCompetitors = new Set(existingCompetitors);
83628
84027
  const cited = /* @__PURE__ */ new Set();
83629
84028
  const aspirational = /* @__PURE__ */ new Set();
@@ -83652,7 +84051,7 @@ async function discoveryRoutes(app, opts) {
83652
84051
  );
83653
84052
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
83654
84053
  const project = resolveProject(app.db, request.params.name);
83655
- const session = app.db.select().from(discoverySessions).where(eq65(discoverySessions.id, request.params.id)).get();
84054
+ const session = app.db.select().from(discoverySessions).where(eq68(discoverySessions.id, request.params.id)).get();
83656
84055
  if (!session || session.projectId !== project.id) {
83657
84056
  throw notFound("Discovery session", request.params.id);
83658
84057
  }
@@ -83675,7 +84074,7 @@ async function discoveryRoutes(app, opts) {
83675
84074
  const bucketSet = new Set(buckets);
83676
84075
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
83677
84076
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
83678
- const probeRows = app.db.select().from(discoveryProbes).where(eq65(discoveryProbes.sessionId, session.id)).all();
84077
+ const probeRows = app.db.select().from(discoveryProbes).where(eq68(discoveryProbes.sessionId, session.id)).all();
83679
84078
  const candidateQueries = /* @__PURE__ */ new Set();
83680
84079
  for (const probe of probeRows) {
83681
84080
  if (!probe.bucket) continue;
@@ -83683,7 +84082,7 @@ async function discoveryRoutes(app, opts) {
83683
84082
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
83684
84083
  }
83685
84084
  const existingQueries = new Set(
83686
- app.db.select({ query: queries.query }).from(queries).where(eq65(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
84085
+ app.db.select({ query: queries.query }).from(queries).where(eq68(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
83687
84086
  );
83688
84087
  const promotedQueries = [];
83689
84088
  const skippedQueries = [];
@@ -83699,7 +84098,7 @@ async function discoveryRoutes(app, opts) {
83699
84098
  const skippedCompetitors = [];
83700
84099
  if (includeCompetitors) {
83701
84100
  const existingCompetitors = new Set(
83702
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq65(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
84101
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq68(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
83703
84102
  );
83704
84103
  const competitorMap = parseCompetitorMap(session.competitorMap);
83705
84104
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -83818,7 +84217,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
83818
84217
 
83819
84218
  // ../api-routes/src/discovery/orchestrate.ts
83820
84219
  import crypto48 from "crypto";
83821
- import { eq as eq66 } from "drizzle-orm";
84220
+ import { eq as eq69 } from "drizzle-orm";
83822
84221
  var DEFAULT_MAX_PROBES = 100;
83823
84222
  var ABSOLUTE_MAX_PROBES = 500;
83824
84223
  function classifyProbeBucket(input) {
@@ -83905,7 +84304,7 @@ async function executeDiscovery(opts) {
83905
84304
  status: DiscoverySessionStatuses.seeding,
83906
84305
  dedupThreshold,
83907
84306
  startedAt
83908
- }).where(eq66(discoverySessions.id, opts.sessionId)).run();
84307
+ }).where(eq69(discoverySessions.id, opts.sessionId)).run();
83909
84308
  const seedResult = await opts.deps.seed({
83910
84309
  project: opts.project,
83911
84310
  icpDescription: opts.icpDescription,
@@ -83966,7 +84365,7 @@ async function executeDiscovery(opts) {
83966
84365
  dedupBandPairFraction: dedupStats.bandPairFraction,
83967
84366
  dedupPairsTotal: dedupStats.pairsTotal,
83968
84367
  warning
83969
- }).where(eq66(discoverySessions.id, opts.sessionId)).run();
84368
+ }).where(eq69(discoverySessions.id, opts.sessionId)).run();
83970
84369
  const probeLocation = opts.locations?.[0];
83971
84370
  const probeResults = await mapWithConcurrency(
83972
84371
  probedCanonicals,
@@ -84020,7 +84419,7 @@ async function executeDiscovery(opts) {
84020
84419
  wastedCount: buckets["wasted-surface"],
84021
84420
  competitorMap,
84022
84421
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
84023
- }).where(eq66(discoverySessions.id, opts.sessionId)).run();
84422
+ }).where(eq69(discoverySessions.id, opts.sessionId)).run();
84024
84423
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
84025
84424
  return {
84026
84425
  buckets,
@@ -84060,7 +84459,7 @@ function markSessionFailed(db, sessionId, error) {
84060
84459
  status: DiscoverySessionStatuses.failed,
84061
84460
  error,
84062
84461
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
84063
- }).where(eq66(discoverySessions.id, sessionId)).run();
84462
+ }).where(eq69(discoverySessions.id, sessionId)).run();
84064
84463
  }
84065
84464
  function dedupeStrings(input) {
84066
84465
  const seen2 = /* @__PURE__ */ new Set();
@@ -84078,7 +84477,7 @@ function dedupeStrings(input) {
84078
84477
 
84079
84478
  // ../api-routes/src/technical-aeo.ts
84080
84479
  import crypto49 from "crypto";
84081
- import { and as and57, asc as asc11, count as count2, desc as desc30, eq as eq67, inArray as inArray26, isNotNull as isNotNull5, isNull as isNull12, lt as lt11, or as or16, sql as sql26 } from "drizzle-orm";
84480
+ import { and as and59, asc as asc12, count as count2, desc as desc30, eq as eq70, inArray as inArray27, isNotNull as isNotNull5, isNull as isNull12, lt as lt11, or as or16, sql as sql26 } from "drizzle-orm";
84082
84481
  import { alias } from "drizzle-orm/sqlite-core";
84083
84482
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
84084
84483
  ...SiteCrawlFetchedStates,
@@ -84291,8 +84690,8 @@ function parseLinkKind(value) {
84291
84690
  return allowed.data;
84292
84691
  }
84293
84692
  function linkKindFilter(linkKind) {
84294
- if (linkKind === SiteHealthLinkKinds.content) return eq67(siteCrawlEdges.isTemplate, false);
84295
- if (linkKind === SiteHealthLinkKinds.template) return eq67(siteCrawlEdges.isTemplate, true);
84693
+ if (linkKind === SiteHealthLinkKinds.content) return eq70(siteCrawlEdges.isTemplate, false);
84694
+ if (linkKind === SiteHealthLinkKinds.template) return eq70(siteCrawlEdges.isTemplate, true);
84296
84695
  return void 0;
84297
84696
  }
84298
84697
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -84397,50 +84796,50 @@ function changedFields(before, after, fields) {
84397
84796
  async function technicalAeoRoutes(app, opts) {
84398
84797
  const resolveCrawl = (projectId, runId) => {
84399
84798
  const filters = [
84400
- eq67(siteCrawlSnapshots.projectId, projectId),
84401
- eq67(runs.projectId, projectId),
84402
- eq67(runs.kind, RunKinds["site-audit"]),
84403
- inArray26(runs.status, SURFACEABLE_STATUSES),
84799
+ eq70(siteCrawlSnapshots.projectId, projectId),
84800
+ eq70(runs.projectId, projectId),
84801
+ eq70(runs.kind, RunKinds["site-audit"]),
84802
+ inArray27(runs.status, SURFACEABLE_STATUSES),
84404
84803
  notProbeRun()
84405
84804
  ];
84406
84805
  if (runId) {
84407
- filters.push(eq67(siteCrawlSnapshots.runId, runId));
84806
+ filters.push(eq70(siteCrawlSnapshots.runId, runId));
84408
84807
  } else {
84409
- filters.push(eq67(siteCrawlSnapshots.complete, true), eq67(runs.status, RunStatuses.completed));
84808
+ filters.push(eq70(siteCrawlSnapshots.complete, true), eq70(runs.status, RunStatuses.completed));
84410
84809
  }
84411
- return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq67(siteCrawlSnapshots.runId, runs.id)).where(and57(...filters)).orderBy(desc30(siteCrawlSnapshots.createdAt), desc30(siteCrawlSnapshots.runId)).limit(1).get();
84810
+ return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq70(siteCrawlSnapshots.runId, runs.id)).where(and59(...filters)).orderBy(desc30(siteCrawlSnapshots.createdAt), desc30(siteCrawlSnapshots.runId)).limit(1).get();
84412
84811
  };
84413
84812
  const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
84414
84813
  const pageInScope = (scope, selector) => {
84415
84814
  if (!selector.nodeKey && !selector.url) return null;
84416
- return app.db.select().from(siteCrawlPages).where(and57(
84417
- eq67(siteCrawlPages.projectId, scope.projectId),
84418
- eq67(siteCrawlPages.runId, scope.runId),
84419
- eq67(siteCrawlPages.attemptId, scope.attemptId),
84420
- selector.nodeKey ? eq67(siteCrawlPages.nodeKey, selector.nodeKey) : eq67(siteCrawlPages.url, selector.url)
84421
- )).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
84815
+ return app.db.select().from(siteCrawlPages).where(and59(
84816
+ eq70(siteCrawlPages.projectId, scope.projectId),
84817
+ eq70(siteCrawlPages.runId, scope.runId),
84818
+ eq70(siteCrawlPages.attemptId, scope.attemptId),
84819
+ selector.nodeKey ? eq70(siteCrawlPages.nodeKey, selector.nodeKey) : eq70(siteCrawlPages.url, selector.url)
84820
+ )).orderBy(asc12(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
84422
84821
  };
84423
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and57(
84424
- eq67(siteCrawlPages.projectId, scope.projectId),
84425
- eq67(siteCrawlPages.runId, scope.runId),
84426
- eq67(siteCrawlPages.attemptId, scope.attemptId),
84427
- eq67(siteCrawlPages.depth, 0)
84428
- )).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
84429
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and57(
84430
- eq67(runs.id, runId),
84431
- eq67(runs.projectId, projectId),
84432
- eq67(runs.kind, RunKinds["site-audit"]),
84433
- inArray26(runs.status, SURFACEABLE_STATUSES),
84822
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and59(
84823
+ eq70(siteCrawlPages.projectId, scope.projectId),
84824
+ eq70(siteCrawlPages.runId, scope.runId),
84825
+ eq70(siteCrawlPages.attemptId, scope.attemptId),
84826
+ eq70(siteCrawlPages.depth, 0)
84827
+ )).orderBy(asc12(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
84828
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and59(
84829
+ eq70(runs.id, runId),
84830
+ eq70(runs.projectId, projectId),
84831
+ eq70(runs.kind, RunKinds["site-audit"]),
84832
+ inArray27(runs.status, SURFACEABLE_STATUSES),
84434
84833
  notProbeRun()
84435
84834
  )).limit(1).get());
84436
84835
  const assertKnownAuditRun = (projectId, runId) => {
84437
84836
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
84438
84837
  };
84439
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq67(siteAuditSnapshots.runId, runs.id)).where(and57(
84440
- eq67(siteAuditSnapshots.projectId, projectId),
84441
- eq67(runs.projectId, projectId),
84442
- eq67(runs.kind, RunKinds["site-audit"]),
84443
- inArray26(runs.status, SURFACEABLE_STATUSES),
84838
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq70(siteAuditSnapshots.runId, runs.id)).where(and59(
84839
+ eq70(siteAuditSnapshots.projectId, projectId),
84840
+ eq70(runs.projectId, projectId),
84841
+ eq70(runs.kind, RunKinds["site-audit"]),
84842
+ inArray27(runs.status, SURFACEABLE_STATUSES),
84444
84843
  notProbeRun()
84445
84844
  )).limit(1).get());
84446
84845
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -84461,20 +84860,20 @@ async function technicalAeoRoutes(app, opts) {
84461
84860
  app.get("/projects/:name/technical-aeo", async (request) => {
84462
84861
  const project = resolveProject(app.db, request.params.name);
84463
84862
  const baseFilters = [
84464
- eq67(siteAuditSnapshots.projectId, project.id),
84465
- eq67(runs.projectId, project.id),
84466
- eq67(runs.kind, RunKinds["site-audit"]),
84467
- inArray26(runs.status, SURFACEABLE_STATUSES),
84863
+ eq70(siteAuditSnapshots.projectId, project.id),
84864
+ eq70(runs.projectId, project.id),
84865
+ eq70(runs.kind, RunKinds["site-audit"]),
84866
+ inArray27(runs.status, SURFACEABLE_STATUSES),
84468
84867
  notProbeRun()
84469
84868
  ];
84470
- const targetFilters = request.query.runId ? [...baseFilters, eq67(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
84471
- const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq67(siteAuditSnapshots.runId, runs.id)).where(and57(...targetFilters)).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(1).get();
84869
+ const targetFilters = request.query.runId ? [...baseFilters, eq70(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
84870
+ const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq70(siteAuditSnapshots.runId, runs.id)).where(and59(...targetFilters)).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(1).get();
84472
84871
  if (!latest) {
84473
84872
  if (request.query.runId) throw notFound("Site audit run", request.query.runId);
84474
84873
  return emptyScore(project.name);
84475
84874
  }
84476
84875
  const snap = latest.snap;
84477
- const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq67(siteAuditSnapshots.runId, runs.id)).where(and57(...baseFilters, lt11(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
84876
+ const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq70(siteAuditSnapshots.runId, runs.id)).where(and59(...baseFilters, lt11(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
84478
84877
  const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
84479
84878
  const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
84480
84879
  return {
@@ -84501,14 +84900,14 @@ async function technicalAeoRoutes(app, opts) {
84501
84900
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
84502
84901
  const project = resolveProject(app.db, request.params.name);
84503
84902
  const targetFilters = [
84504
- eq67(siteAuditSnapshots.projectId, project.id),
84505
- eq67(runs.projectId, project.id),
84506
- eq67(runs.kind, RunKinds["site-audit"]),
84507
- inArray26(runs.status, SURFACEABLE_STATUSES),
84903
+ eq70(siteAuditSnapshots.projectId, project.id),
84904
+ eq70(runs.projectId, project.id),
84905
+ eq70(runs.kind, RunKinds["site-audit"]),
84906
+ inArray27(runs.status, SURFACEABLE_STATUSES),
84508
84907
  notProbeRun()
84509
84908
  ];
84510
- if (request.query.runId) targetFilters.push(eq67(siteAuditSnapshots.runId, request.query.runId));
84511
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq67(siteAuditSnapshots.runId, runs.id)).where(and57(...targetFilters)).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(1).get();
84909
+ if (request.query.runId) targetFilters.push(eq70(siteAuditSnapshots.runId, request.query.runId));
84910
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq70(siteAuditSnapshots.runId, runs.id)).where(and59(...targetFilters)).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(1).get();
84512
84911
  if (!latest && request.query.runId) {
84513
84912
  throw notFound("Site audit run", request.query.runId);
84514
84913
  }
@@ -84516,14 +84915,14 @@ async function technicalAeoRoutes(app, opts) {
84516
84915
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
84517
84916
  }
84518
84917
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
84519
- const conds = [eq67(siteAuditPages.projectId, project.id), eq67(siteAuditPages.runId, latest.runId)];
84520
- if (statusFilter) conds.push(eq67(siteAuditPages.status, statusFilter));
84521
- const where = and57(...conds);
84918
+ const conds = [eq70(siteAuditPages.projectId, project.id), eq70(siteAuditPages.runId, latest.runId)];
84919
+ if (statusFilter) conds.push(eq70(siteAuditPages.status, statusFilter));
84920
+ const where = and59(...conds);
84522
84921
  const totalRow = app.db.select({ value: count2() }).from(siteAuditPages).where(where).get();
84523
84922
  const total = totalRow?.value ?? 0;
84524
84923
  const limit = parsePositiveInt(request.query.limit, 100, 500);
84525
84924
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
84526
- const orderBy = request.query.sort === "score-desc" ? desc30(siteAuditPages.overallScore) : request.query.sort === "url" ? asc11(siteAuditPages.url) : asc11(siteAuditPages.overallScore);
84925
+ const orderBy = request.query.sort === "score-desc" ? desc30(siteAuditPages.overallScore) : request.query.sort === "url" ? asc12(siteAuditPages.url) : asc12(siteAuditPages.overallScore);
84527
84926
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
84528
84927
  const pages = rows.map((row) => ({
84529
84928
  url: row.url,
@@ -84542,11 +84941,11 @@ async function technicalAeoRoutes(app, opts) {
84542
84941
  auditedAt: siteAuditSnapshots.auditedAt,
84543
84942
  aggregateScore: siteAuditSnapshots.aggregateScore,
84544
84943
  pagesAudited: siteAuditSnapshots.pagesAudited
84545
- }).from(siteAuditSnapshots).innerJoin(runs, eq67(siteAuditSnapshots.runId, runs.id)).where(and57(
84546
- eq67(siteAuditSnapshots.projectId, project.id),
84547
- eq67(runs.projectId, project.id),
84548
- eq67(runs.kind, RunKinds["site-audit"]),
84549
- inArray26(runs.status, SURFACEABLE_STATUSES),
84944
+ }).from(siteAuditSnapshots).innerJoin(runs, eq70(siteAuditSnapshots.runId, runs.id)).where(and59(
84945
+ eq70(siteAuditSnapshots.projectId, project.id),
84946
+ eq70(runs.projectId, project.id),
84947
+ eq70(runs.kind, RunKinds["site-audit"]),
84948
+ inArray27(runs.status, SURFACEABLE_STATUSES),
84550
84949
  notProbeRun()
84551
84950
  )).orderBy(desc30(siteAuditSnapshots.createdAt)).limit(limit).all();
84552
84951
  return { project: project.name, points: rows.reverse() };
@@ -84640,10 +85039,10 @@ async function technicalAeoRoutes(app, opts) {
84640
85039
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
84641
85040
  snapshot.rootUrl
84642
85041
  )?.nodeKey ?? null;
84643
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and57(
84644
- eq67(siteCrawlGraphLayouts.projectId, project.id),
84645
- eq67(siteCrawlGraphLayouts.runId, snapshot.runId),
84646
- eq67(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
85042
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and59(
85043
+ eq70(siteCrawlGraphLayouts.projectId, project.id),
85044
+ eq70(siteCrawlGraphLayouts.runId, snapshot.runId),
85045
+ eq70(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
84647
85046
  )).limit(1).get();
84648
85047
  if (!persistedLayout) {
84649
85048
  return {
@@ -84694,9 +85093,9 @@ async function technicalAeoRoutes(app, opts) {
84694
85093
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
84695
85094
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
84696
85095
  const graphScope = [
84697
- eq67(siteCrawlGraphNodes.projectId, project.id),
84698
- eq67(siteCrawlGraphNodes.runId, snapshot.runId),
84699
- eq67(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
85096
+ eq70(siteCrawlGraphNodes.projectId, project.id),
85097
+ eq70(siteCrawlGraphNodes.runId, snapshot.runId),
85098
+ eq70(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
84700
85099
  ];
84701
85100
  const nodeRows = app.db.select({
84702
85101
  nodeKey: siteCrawlPages.nodeKey,
@@ -84715,17 +85114,17 @@ async function technicalAeoRoutes(app, opts) {
84715
85114
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
84716
85115
  x: siteCrawlGraphNodes.x,
84717
85116
  y: siteCrawlGraphNodes.y
84718
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and57(
84719
- eq67(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
84720
- eq67(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
84721
- eq67(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
84722
- eq67(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
84723
- )).where(and57(...graphScope, lt11(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc11(siteCrawlGraphNodes.sampleRank)).all();
85117
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and59(
85118
+ eq70(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
85119
+ eq70(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
85120
+ eq70(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
85121
+ eq70(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
85122
+ )).where(and59(...graphScope, lt11(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc12(siteCrawlGraphNodes.sampleRank)).all();
84724
85123
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
84725
85124
  ...row,
84726
85125
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey })
84727
85126
  }));
84728
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq67(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
85127
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq70(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
84729
85128
  const edges = app.db.select({
84730
85129
  edgeKey: siteCrawlGraphEdges.edgeKey,
84731
85130
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -84733,25 +85132,25 @@ async function technicalAeoRoutes(app, opts) {
84733
85132
  followable: siteCrawlGraphEdges.followable,
84734
85133
  occurrences: siteCrawlGraphEdges.occurrences,
84735
85134
  isTemplate: siteCrawlGraphEdges.isTemplate
84736
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and57(
84737
- eq67(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
84738
- eq67(graphSourceNode.runId, siteCrawlGraphEdges.runId),
84739
- eq67(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
84740
- eq67(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
85135
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and59(
85136
+ eq70(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
85137
+ eq70(graphSourceNode.runId, siteCrawlGraphEdges.runId),
85138
+ eq70(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
85139
+ eq70(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
84741
85140
  lt11(graphSourceNode.sampleRank, maxNodes)
84742
- )).innerJoin(graphTargetNode, and57(
84743
- eq67(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
84744
- eq67(graphTargetNode.runId, siteCrawlGraphEdges.runId),
84745
- eq67(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
84746
- eq67(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
85141
+ )).innerJoin(graphTargetNode, and59(
85142
+ eq70(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
85143
+ eq70(graphTargetNode.runId, siteCrawlGraphEdges.runId),
85144
+ eq70(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
85145
+ eq70(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
84747
85146
  lt11(graphTargetNode.sampleRank, maxNodes)
84748
- )).where(and57(
84749
- eq67(siteCrawlGraphEdges.projectId, project.id),
84750
- eq67(siteCrawlGraphEdges.runId, snapshot.runId),
84751
- eq67(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
85147
+ )).where(and59(
85148
+ eq70(siteCrawlGraphEdges.projectId, project.id),
85149
+ eq70(siteCrawlGraphEdges.runId, snapshot.runId),
85150
+ eq70(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
84752
85151
  lt11(siteCrawlGraphEdges.sampleRank, maxEdges),
84753
85152
  graphLinkKindFilter
84754
- )).orderBy(asc11(siteCrawlGraphEdges.sampleRank)).all();
85153
+ )).orderBy(asc12(siteCrawlGraphEdges.sampleRank)).all();
84755
85154
  const totalNodes = persistedLayout.totalNodes;
84756
85155
  const totalEdges = persistedLayout.totalEdges;
84757
85156
  const totalTemplateEdges = persistedLayout.totalTemplateEdges;
@@ -84842,17 +85241,17 @@ async function technicalAeoRoutes(app, opts) {
84842
85241
  let frontier = [focus.nodeKey];
84843
85242
  let queryTruncated = false;
84844
85243
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
84845
- const candidates = app.db.select().from(siteCrawlEdges).where(and57(
84846
- eq67(siteCrawlEdges.projectId, scope.projectId),
84847
- eq67(siteCrawlEdges.runId, scope.runId),
84848
- eq67(siteCrawlEdges.attemptId, scope.attemptId),
84849
- eq67(siteCrawlEdges.internal, true),
85244
+ const candidates = app.db.select().from(siteCrawlEdges).where(and59(
85245
+ eq70(siteCrawlEdges.projectId, scope.projectId),
85246
+ eq70(siteCrawlEdges.runId, scope.runId),
85247
+ eq70(siteCrawlEdges.attemptId, scope.attemptId),
85248
+ eq70(siteCrawlEdges.internal, true),
84850
85249
  isNotNull5(siteCrawlEdges.targetNodeKey),
84851
85250
  or16(
84852
- inArray26(siteCrawlEdges.sourceNodeKey, frontier),
84853
- inArray26(siteCrawlEdges.targetNodeKey, frontier)
85251
+ inArray27(siteCrawlEdges.sourceNodeKey, frontier),
85252
+ inArray27(siteCrawlEdges.targetNodeKey, frontier)
84854
85253
  )
84855
- )).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
85254
+ )).orderBy(asc12(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
84856
85255
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
84857
85256
  const next = /* @__PURE__ */ new Set();
84858
85257
  for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
@@ -84883,22 +85282,22 @@ async function technicalAeoRoutes(app, opts) {
84883
85282
  frontier = [...next].sort();
84884
85283
  }
84885
85284
  const nodeKeys = [...distances.keys()];
84886
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and57(
84887
- eq67(siteCrawlPages.projectId, scope.projectId),
84888
- eq67(siteCrawlPages.runId, scope.runId),
84889
- eq67(siteCrawlPages.attemptId, scope.attemptId),
84890
- inArray26(siteCrawlPages.nodeKey, nodeKeys)
85285
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and59(
85286
+ eq70(siteCrawlPages.projectId, scope.projectId),
85287
+ eq70(siteCrawlPages.runId, scope.runId),
85288
+ eq70(siteCrawlPages.attemptId, scope.attemptId),
85289
+ inArray27(siteCrawlPages.nodeKey, nodeKeys)
84891
85290
  )).all();
84892
85291
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
84893
85292
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
84894
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and57(
84895
- eq67(siteCrawlEdges.projectId, scope.projectId),
84896
- eq67(siteCrawlEdges.runId, scope.runId),
84897
- eq67(siteCrawlEdges.attemptId, scope.attemptId),
84898
- eq67(siteCrawlEdges.internal, true),
85293
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and59(
85294
+ eq70(siteCrawlEdges.projectId, scope.projectId),
85295
+ eq70(siteCrawlEdges.runId, scope.runId),
85296
+ eq70(siteCrawlEdges.attemptId, scope.attemptId),
85297
+ eq70(siteCrawlEdges.internal, true),
84899
85298
  isNotNull5(siteCrawlEdges.targetNodeKey),
84900
- inArray26(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
84901
- inArray26(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
85299
+ inArray27(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
85300
+ inArray27(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
84902
85301
  )).limit(1).get();
84903
85302
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
84904
85303
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -84993,23 +85392,23 @@ async function technicalAeoRoutes(app, opts) {
84993
85392
  let found = fromPage.nodeKey === toPage.nodeKey;
84994
85393
  let truncated = false;
84995
85394
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
84996
- const candidates = app.db.select().from(siteCrawlEdges).where(and57(
84997
- eq67(siteCrawlEdges.projectId, scope.projectId),
84998
- eq67(siteCrawlEdges.runId, scope.runId),
84999
- eq67(siteCrawlEdges.attemptId, scope.attemptId),
85000
- eq67(siteCrawlEdges.internal, true),
85001
- eq67(siteCrawlEdges.followable, true),
85002
- eq67(siteCrawlEdges.relation, "anchor"),
85395
+ const candidates = app.db.select().from(siteCrawlEdges).where(and59(
85396
+ eq70(siteCrawlEdges.projectId, scope.projectId),
85397
+ eq70(siteCrawlEdges.runId, scope.runId),
85398
+ eq70(siteCrawlEdges.attemptId, scope.attemptId),
85399
+ eq70(siteCrawlEdges.internal, true),
85400
+ eq70(siteCrawlEdges.followable, true),
85401
+ eq70(siteCrawlEdges.relation, "anchor"),
85003
85402
  isNotNull5(siteCrawlEdges.targetNodeKey),
85004
- inArray26(siteCrawlEdges.sourceNodeKey, frontier)
85005
- )).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
85403
+ inArray27(siteCrawlEdges.sourceNodeKey, frontier)
85404
+ )).orderBy(asc12(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
85006
85405
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
85007
85406
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
85008
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and57(
85009
- eq67(siteCrawlPages.projectId, scope.projectId),
85010
- eq67(siteCrawlPages.runId, scope.runId),
85011
- eq67(siteCrawlPages.attemptId, scope.attemptId),
85012
- inArray26(siteCrawlPages.nodeKey, candidateTargetKeys)
85407
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and59(
85408
+ eq70(siteCrawlPages.projectId, scope.projectId),
85409
+ eq70(siteCrawlPages.runId, scope.runId),
85410
+ eq70(siteCrawlPages.attemptId, scope.attemptId),
85411
+ inArray27(siteCrawlPages.nodeKey, candidateTargetKeys)
85013
85412
  )).all().map((row) => row.nodeKey));
85014
85413
  const next = /* @__PURE__ */ new Set();
85015
85414
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -85051,11 +85450,11 @@ async function technicalAeoRoutes(app, opts) {
85051
85450
  pathEdges.unshift(step.edge);
85052
85451
  pathKeys.unshift(step.nodeKey);
85053
85452
  }
85054
- const pathRows = app.db.select().from(siteCrawlPages).where(and57(
85055
- eq67(siteCrawlPages.projectId, scope.projectId),
85056
- eq67(siteCrawlPages.runId, scope.runId),
85057
- eq67(siteCrawlPages.attemptId, scope.attemptId),
85058
- inArray26(siteCrawlPages.nodeKey, pathKeys)
85453
+ const pathRows = app.db.select().from(siteCrawlPages).where(and59(
85454
+ eq70(siteCrawlPages.projectId, scope.projectId),
85455
+ eq70(siteCrawlPages.runId, scope.runId),
85456
+ eq70(siteCrawlPages.attemptId, scope.attemptId),
85457
+ inArray27(siteCrawlPages.nodeKey, pathKeys)
85059
85458
  )).all();
85060
85459
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
85061
85460
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -85084,21 +85483,21 @@ async function technicalAeoRoutes(app, opts) {
85084
85483
  throw validationError("change must be all, added, removed, or changed");
85085
85484
  }
85086
85485
  const completeSnapshotFilters = [
85087
- eq67(siteCrawlSnapshots.projectId, project.id),
85088
- eq67(runs.projectId, project.id),
85089
- eq67(runs.kind, RunKinds["site-audit"]),
85090
- eq67(runs.status, RunStatuses.completed),
85091
- eq67(siteCrawlSnapshots.complete, true),
85486
+ eq70(siteCrawlSnapshots.projectId, project.id),
85487
+ eq70(runs.projectId, project.id),
85488
+ eq70(runs.kind, RunKinds["site-audit"]),
85489
+ eq70(runs.status, RunStatuses.completed),
85490
+ eq70(siteCrawlSnapshots.complete, true),
85092
85491
  notProbeRun()
85093
85492
  ];
85094
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq67(siteCrawlSnapshots.runId, runs.id)).where(and57(...completeSnapshotFilters)).orderBy(desc30(siteCrawlSnapshots.createdAt), desc30(siteCrawlSnapshots.runId)).limit(1).get();
85493
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq70(siteCrawlSnapshots.runId, runs.id)).where(and59(...completeSnapshotFilters)).orderBy(desc30(siteCrawlSnapshots.createdAt), desc30(siteCrawlSnapshots.runId)).limit(1).get();
85095
85494
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
85096
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq67(siteCrawlSnapshots.runId, runs.id)).where(and57(
85495
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq70(siteCrawlSnapshots.runId, runs.id)).where(and59(
85097
85496
  ...completeSnapshotFilters,
85098
85497
  or16(
85099
85498
  lt11(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
85100
- and57(
85101
- eq67(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
85499
+ and59(
85500
+ eq70(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
85102
85501
  lt11(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
85103
85502
  )
85104
85503
  )
@@ -85333,18 +85732,18 @@ async function technicalAeoRoutes(app, opts) {
85333
85732
  const visibleKeys = keyRows.slice(0, limit);
85334
85733
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
85335
85734
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
85336
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and57(
85337
- eq67(siteCrawlPages.projectId, scope.projectId),
85338
- eq67(siteCrawlPages.runId, scope.runId),
85339
- eq67(siteCrawlPages.attemptId, scope.attemptId),
85340
- inArray26(siteCrawlPages.nodeKey, pageKeys)
85735
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and59(
85736
+ eq70(siteCrawlPages.projectId, scope.projectId),
85737
+ eq70(siteCrawlPages.runId, scope.runId),
85738
+ eq70(siteCrawlPages.attemptId, scope.attemptId),
85739
+ inArray27(siteCrawlPages.nodeKey, pageKeys)
85341
85740
  )).all();
85342
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and57(
85343
- eq67(siteCrawlEdges.projectId, scope.projectId),
85344
- eq67(siteCrawlEdges.runId, scope.runId),
85345
- eq67(siteCrawlEdges.attemptId, scope.attemptId),
85346
- eq67(siteCrawlEdges.internal, true),
85347
- inArray26(siteCrawlEdges.edgeKey, linkKeys)
85741
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and59(
85742
+ eq70(siteCrawlEdges.projectId, scope.projectId),
85743
+ eq70(siteCrawlEdges.runId, scope.runId),
85744
+ eq70(siteCrawlEdges.attemptId, scope.attemptId),
85745
+ eq70(siteCrawlEdges.internal, true),
85746
+ inArray27(siteCrawlEdges.edgeKey, linkKeys)
85348
85747
  )).all();
85349
85748
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
85350
85749
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -85469,30 +85868,30 @@ async function technicalAeoRoutes(app, opts) {
85469
85868
  return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, healthStateFilter: null, pages: [] };
85470
85869
  }
85471
85870
  const filters = [
85472
- eq67(siteCrawlPages.projectId, project.id),
85473
- eq67(siteCrawlPages.runId, snapshot.runId),
85474
- eq67(siteCrawlPages.attemptId, snapshot.attemptId)
85871
+ eq70(siteCrawlPages.projectId, project.id),
85872
+ eq70(siteCrawlPages.runId, snapshot.runId),
85873
+ eq70(siteCrawlPages.attemptId, snapshot.attemptId)
85475
85874
  ];
85476
- if (request.query.nodeKey) filters.push(eq67(siteCrawlPages.nodeKey, request.query.nodeKey));
85875
+ if (request.query.nodeKey) filters.push(eq70(siteCrawlPages.nodeKey, request.query.nodeKey));
85477
85876
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
85478
- if (inventoryEligible != null) filters.push(eq67(siteCrawlPages.inventoryEligible, inventoryEligible));
85479
- if (request.query.fetchState) filters.push(eq67(siteCrawlPages.fetchState, request.query.fetchState));
85480
- if (request.query.indexabilityState) filters.push(eq67(siteCrawlPages.indexabilityState, request.query.indexabilityState));
85481
- if (request.query.auditState) filters.push(eq67(siteCrawlPages.auditState, request.query.auditState));
85877
+ if (inventoryEligible != null) filters.push(eq70(siteCrawlPages.inventoryEligible, inventoryEligible));
85878
+ if (request.query.fetchState) filters.push(eq70(siteCrawlPages.fetchState, request.query.fetchState));
85879
+ if (request.query.indexabilityState) filters.push(eq70(siteCrawlPages.indexabilityState, request.query.indexabilityState));
85880
+ if (request.query.auditState) filters.push(eq70(siteCrawlPages.auditState, request.query.auditState));
85482
85881
  const healthState = parseSiteHealthState(request.query.healthState);
85483
85882
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
85484
85883
  const offset = decodeCursor(request.query.cursor);
85485
- const orderBy = request.query.sort === "score-desc" ? [desc30(siteCrawlPages.auditScore), asc11(siteCrawlPages.nodeKey)] : request.query.sort === "score-asc" ? [asc11(siteCrawlPages.auditScore), asc11(siteCrawlPages.nodeKey)] : request.query.sort === "path" ? [asc11(siteCrawlPages.path), asc11(siteCrawlPages.nodeKey)] : [asc11(siteCrawlPages.url), asc11(siteCrawlPages.nodeKey)];
85884
+ const orderBy = request.query.sort === "score-desc" ? [desc30(siteCrawlPages.auditScore), asc12(siteCrawlPages.nodeKey)] : request.query.sort === "score-asc" ? [asc12(siteCrawlPages.auditScore), asc12(siteCrawlPages.nodeKey)] : request.query.sort === "path" ? [asc12(siteCrawlPages.path), asc12(siteCrawlPages.nodeKey)] : [asc12(siteCrawlPages.url), asc12(siteCrawlPages.nodeKey)];
85486
85885
  let healthStateFilter = null;
85487
85886
  if (healthState) {
85488
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and57(
85489
- eq67(siteCrawlPages.projectId, project.id),
85490
- eq67(siteCrawlPages.runId, snapshot.runId),
85491
- eq67(siteCrawlPages.attemptId, snapshot.attemptId),
85887
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and59(
85888
+ eq70(siteCrawlPages.projectId, project.id),
85889
+ eq70(siteCrawlPages.runId, snapshot.runId),
85890
+ eq70(siteCrawlPages.attemptId, snapshot.attemptId),
85492
85891
  isNull12(siteCrawlPages.healthState)
85493
85892
  )).limit(1).get();
85494
85893
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
85495
- if (healthStateFilter === "applied") filters.push(eq67(siteCrawlPages.healthState, healthState));
85894
+ if (healthStateFilter === "applied") filters.push(eq70(siteCrawlPages.healthState, healthState));
85496
85895
  }
85497
85896
  if (healthStateFilter === "unavailable-legacy-scan") {
85498
85897
  return {
@@ -85505,7 +85904,7 @@ async function technicalAeoRoutes(app, opts) {
85505
85904
  pages: []
85506
85905
  };
85507
85906
  }
85508
- const where = and57(...filters);
85907
+ const where = and59(...filters);
85509
85908
  const total = app.db.select({ value: count2() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
85510
85909
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
85511
85910
  const nextOffset = offset + rows.length;
@@ -85536,10 +85935,10 @@ async function technicalAeoRoutes(app, opts) {
85536
85935
  url: siteCrawlPages.url,
85537
85936
  inventoryEligible: siteCrawlPages.inventoryEligible,
85538
85937
  fetchState: siteCrawlPages.fetchState
85539
- }).from(siteCrawlPages).where(and57(
85540
- eq67(siteCrawlPages.projectId, project.id),
85541
- eq67(siteCrawlPages.runId, snapshot.runId),
85542
- eq67(siteCrawlPages.attemptId, snapshot.attemptId)
85938
+ }).from(siteCrawlPages).where(and59(
85939
+ eq70(siteCrawlPages.projectId, project.id),
85940
+ eq70(siteCrawlPages.runId, snapshot.runId),
85941
+ eq70(siteCrawlPages.attemptId, snapshot.attemptId)
85543
85942
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
85544
85943
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
85545
85944
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -85618,21 +86017,21 @@ async function technicalAeoRoutes(app, opts) {
85618
86017
  };
85619
86018
  }
85620
86019
  const filters = [
85621
- eq67(siteCrawlEdges.projectId, project.id),
85622
- eq67(siteCrawlEdges.runId, snapshot.runId),
85623
- eq67(siteCrawlEdges.attemptId, snapshot.attemptId),
85624
- eq67(siteCrawlEdges.internal, true),
86020
+ eq70(siteCrawlEdges.projectId, project.id),
86021
+ eq70(siteCrawlEdges.runId, snapshot.runId),
86022
+ eq70(siteCrawlEdges.attemptId, snapshot.attemptId),
86023
+ eq70(siteCrawlEdges.internal, true),
85625
86024
  linkKindFilter(linkKind)
85626
86025
  ];
85627
- if (request.query.sourceUrl) filters.push(eq67(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
85628
- if (request.query.targetUrl) filters.push(eq67(siteCrawlEdges.targetUrl, request.query.targetUrl));
86026
+ if (request.query.sourceUrl) filters.push(eq70(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
86027
+ if (request.query.targetUrl) filters.push(eq70(siteCrawlEdges.targetUrl, request.query.targetUrl));
85629
86028
  const followable = parseBoolean2(request.query.followable);
85630
- if (followable != null) filters.push(eq67(siteCrawlEdges.followable, followable));
85631
- const where = and57(...filters);
86029
+ if (followable != null) filters.push(eq70(siteCrawlEdges.followable, followable));
86030
+ const where = and59(...filters);
85632
86031
  const total = app.db.select({ value: count2() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
85633
86032
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
85634
86033
  const offset = decodeCursor(request.query.cursor);
85635
- const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
86034
+ const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc12(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
85636
86035
  const nextOffset = offset + rows.length;
85637
86036
  return {
85638
86037
  project: project.name,
@@ -85686,17 +86085,17 @@ async function technicalAeoRoutes(app, opts) {
85686
86085
  };
85687
86086
  }
85688
86087
  const scope = [
85689
- eq67(siteCrawlEdges.projectId, project.id),
85690
- eq67(siteCrawlEdges.runId, snapshot.runId),
85691
- eq67(siteCrawlEdges.attemptId, snapshot.attemptId),
85692
- eq67(siteCrawlEdges.internal, true),
86088
+ eq70(siteCrawlEdges.projectId, project.id),
86089
+ eq70(siteCrawlEdges.runId, snapshot.runId),
86090
+ eq70(siteCrawlEdges.attemptId, snapshot.attemptId),
86091
+ eq70(siteCrawlEdges.internal, true),
85693
86092
  linkKindFilter(linkKind)
85694
86093
  ];
85695
- const inboundMatch = request.query.nodeKey && request.query.url ? or16(eq67(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq67(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq67(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq67(siteCrawlEdges.targetUrl, request.query.url);
85696
- const outboundMatch = request.query.nodeKey && request.query.url ? or16(eq67(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq67(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq67(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq67(siteCrawlEdges.sourceUrl, request.query.url);
86094
+ const inboundMatch = request.query.nodeKey && request.query.url ? or16(eq70(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq70(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq70(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq70(siteCrawlEdges.targetUrl, request.query.url);
86095
+ const outboundMatch = request.query.nodeKey && request.query.url ? or16(eq70(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq70(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq70(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq70(siteCrawlEdges.sourceUrl, request.query.url);
85697
86096
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
85698
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and57(...scope, inboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
85699
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and57(...scope, outboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
86097
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and59(...scope, inboundMatch)).orderBy(asc12(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
86098
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and59(...scope, outboundMatch)).orderBy(asc12(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
85700
86099
  return {
85701
86100
  project: project.name,
85702
86101
  hasCrawlData: true,
@@ -85729,11 +86128,11 @@ async function technicalAeoRoutes(app, opts) {
85729
86128
  if (!snapshot.attemptId) {
85730
86129
  return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [] };
85731
86130
  }
85732
- const where = and57(
85733
- eq67(siteCrawlFindings.projectId, project.id),
85734
- eq67(siteCrawlFindings.runId, snapshot.runId),
85735
- eq67(siteCrawlFindings.attemptId, snapshot.attemptId),
85736
- eq67(siteCrawlFindings.findingType, "dead-link"),
86131
+ const where = and59(
86132
+ eq70(siteCrawlFindings.projectId, project.id),
86133
+ eq70(siteCrawlFindings.runId, snapshot.runId),
86134
+ eq70(siteCrawlFindings.attemptId, snapshot.attemptId),
86135
+ eq70(siteCrawlFindings.findingType, "dead-link"),
85737
86136
  // Belt and braces on the client-facing surface: a row without a status
85738
86137
  // code is not evidence of a broken link, and this response is where such
85739
86138
  // a row would be read as one. Migration 140 removed every stored row that
@@ -85748,7 +86147,7 @@ async function technicalAeoRoutes(app, opts) {
85748
86147
  const total = app.db.select({ value: count2() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
85749
86148
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
85750
86149
  const offset = decodeCursor(request.query.cursor);
85751
- const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc11(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
86150
+ const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc12(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
85752
86151
  const nextOffset = offset + rows.length;
85753
86152
  const deadLinks = rows.map((row) => ({
85754
86153
  findingKey: row.findingKey,
@@ -85782,19 +86181,19 @@ async function technicalAeoRoutes(app, opts) {
85782
86181
  createdAt: runs.createdAt,
85783
86182
  startedAt: runs.startedAt,
85784
86183
  finishedAt: runs.finishedAt
85785
- }).from(runs).where(and57(
85786
- eq67(runs.projectId, project.id),
85787
- eq67(runs.kind, RunKinds["site-audit"]),
86184
+ }).from(runs).where(and59(
86185
+ eq70(runs.projectId, project.id),
86186
+ eq70(runs.kind, RunKinds["site-audit"]),
85788
86187
  notProbeRun()
85789
86188
  )).orderBy(desc30(runs.createdAt), desc30(runs.id)).limit(limit).all();
85790
86189
  const runIds = rows.map((row) => row.runId);
85791
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq67(siteCrawlSnapshots.runId, runs.id)).where(and57(
85792
- eq67(siteCrawlSnapshots.projectId, project.id),
85793
- eq67(runs.projectId, project.id),
85794
- eq67(runs.kind, RunKinds["site-audit"]),
85795
- inArray26(runs.status, SURFACEABLE_STATUSES),
86190
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq70(siteCrawlSnapshots.runId, runs.id)).where(and59(
86191
+ eq70(siteCrawlSnapshots.projectId, project.id),
86192
+ eq70(runs.projectId, project.id),
86193
+ eq70(runs.kind, RunKinds["site-audit"]),
86194
+ inArray27(runs.status, SURFACEABLE_STATUSES),
85796
86195
  notProbeRun(),
85797
- inArray26(siteCrawlSnapshots.runId, runIds)
86196
+ inArray27(siteCrawlSnapshots.runId, runIds)
85798
86197
  )).all().map((row) => row.runId));
85799
86198
  return {
85800
86199
  project: project.name,
@@ -85807,21 +86206,21 @@ async function technicalAeoRoutes(app, opts) {
85807
86206
  });
85808
86207
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
85809
86208
  const project = resolveProject(app.db, request.params.name);
85810
- const run = app.db.select().from(runs).where(and57(
85811
- eq67(runs.id, request.params.runId),
85812
- eq67(runs.projectId, project.id),
85813
- eq67(runs.kind, RunKinds["site-audit"]),
86209
+ const run = app.db.select().from(runs).where(and59(
86210
+ eq70(runs.id, request.params.runId),
86211
+ eq70(runs.projectId, project.id),
86212
+ eq70(runs.kind, RunKinds["site-audit"]),
85814
86213
  notProbeRun()
85815
86214
  )).get();
85816
86215
  if (!run) throw notFound("Site audit run", request.params.runId);
85817
- const attempt = app.db.select().from(siteCrawlAttempts).where(and57(
85818
- eq67(siteCrawlAttempts.projectId, project.id),
85819
- eq67(siteCrawlAttempts.runId, run.id)
86216
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and59(
86217
+ eq70(siteCrawlAttempts.projectId, project.id),
86218
+ eq70(siteCrawlAttempts.runId, run.id)
85820
86219
  )).orderBy(desc30(siteCrawlAttempts.attemptNumber), desc30(siteCrawlAttempts.updatedAt)).limit(1).get();
85821
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and57(
85822
- eq67(siteCrawlGraphLayouts.projectId, project.id),
85823
- eq67(siteCrawlGraphLayouts.runId, run.id),
85824
- eq67(siteCrawlGraphLayouts.attemptId, attempt.id)
86220
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and59(
86221
+ eq70(siteCrawlGraphLayouts.projectId, project.id),
86222
+ eq70(siteCrawlGraphLayouts.runId, run.id),
86223
+ eq70(siteCrawlGraphLayouts.attemptId, attempt.id)
85825
86224
  )).get() : void 0;
85826
86225
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
85827
86226
  return {
@@ -85852,18 +86251,18 @@ async function technicalAeoRoutes(app, opts) {
85852
86251
  };
85853
86252
  });
85854
86253
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
85855
- const project = tx.select().from(projects).where(eq67(projects.name, request.params.name)).get();
86254
+ const project = tx.select().from(projects).where(eq70(projects.name, request.params.name)).get();
85856
86255
  if (!project) throw notFound("Project", request.params.name);
85857
- const run = tx.select().from(runs).where(and57(
85858
- eq67(runs.id, request.params.runId),
85859
- eq67(runs.projectId, project.id),
85860
- eq67(runs.kind, RunKinds["site-audit"]),
86256
+ const run = tx.select().from(runs).where(and59(
86257
+ eq70(runs.id, request.params.runId),
86258
+ eq70(runs.projectId, project.id),
86259
+ eq70(runs.kind, RunKinds["site-audit"]),
85861
86260
  notProbeRun()
85862
86261
  )).get();
85863
86262
  if (!run) throw notFound("Site audit run", request.params.runId);
85864
- const attempt = tx.select().from(siteCrawlAttempts).where(and57(
85865
- eq67(siteCrawlAttempts.projectId, project.id),
85866
- eq67(siteCrawlAttempts.runId, run.id)
86263
+ const attempt = tx.select().from(siteCrawlAttempts).where(and59(
86264
+ eq70(siteCrawlAttempts.projectId, project.id),
86265
+ eq70(siteCrawlAttempts.runId, run.id)
85867
86266
  )).orderBy(desc30(siteCrawlAttempts.attemptNumber), desc30(siteCrawlAttempts.updatedAt)).limit(1).get();
85868
86267
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
85869
86268
  const base = {
@@ -85877,11 +86276,11 @@ async function technicalAeoRoutes(app, opts) {
85877
86276
  if (!attempt) {
85878
86277
  return { ...base, pagesAudited: 0, examples: [] };
85879
86278
  }
85880
- const auditedWhere = and57(
85881
- eq67(siteCrawlPages.projectId, project.id),
85882
- eq67(siteCrawlPages.runId, run.id),
85883
- eq67(siteCrawlPages.attemptId, attempt.id),
85884
- eq67(siteCrawlPages.auditState, "success")
86279
+ const auditedWhere = and59(
86280
+ eq70(siteCrawlPages.projectId, project.id),
86281
+ eq70(siteCrawlPages.runId, run.id),
86282
+ eq70(siteCrawlPages.attemptId, attempt.id),
86283
+ eq70(siteCrawlPages.auditState, "success")
85885
86284
  );
85886
86285
  const pagesAudited = tx.select({ value: count2() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
85887
86286
  if (state !== "collecting") {
@@ -85892,11 +86291,11 @@ async function technicalAeoRoutes(app, opts) {
85892
86291
  url: siteCrawlPages.url,
85893
86292
  auditScore: siteCrawlPages.auditScore,
85894
86293
  auditFields: siteCrawlPages.auditFields
85895
- }).from(siteCrawlPages).where(and57(
86294
+ }).from(siteCrawlPages).where(and59(
85896
86295
  auditedWhere,
85897
86296
  isNotNull5(siteCrawlPages.auditScore),
85898
86297
  lt11(siteCrawlPages.auditScore, 70)
85899
- )).orderBy(asc11(siteCrawlPages.auditScore), asc11(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
86298
+ )).orderBy(asc12(siteCrawlPages.auditScore), asc12(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
85900
86299
  const examples = candidates.flatMap((candidate) => {
85901
86300
  if (candidate.auditScore == null) return [];
85902
86301
  const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
@@ -85928,13 +86327,13 @@ async function technicalAeoRoutes(app, opts) {
85928
86327
  status: runs.status,
85929
86328
  identityKey: siteCrawlRunRequests.identityKey,
85930
86329
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
85931
- }).from(runs).leftJoin(siteCrawlRunRequests, and57(
85932
- eq67(siteCrawlRunRequests.projectId, runs.projectId),
85933
- eq67(siteCrawlRunRequests.runId, runs.id)
85934
- )).where(and57(
85935
- eq67(runs.projectId, project.id),
85936
- eq67(runs.kind, RunKinds["site-audit"]),
85937
- inArray26(runs.status, [RunStatuses.queued, RunStatuses.running])
86330
+ }).from(runs).leftJoin(siteCrawlRunRequests, and59(
86331
+ eq70(siteCrawlRunRequests.projectId, runs.projectId),
86332
+ eq70(siteCrawlRunRequests.runId, runs.id)
86333
+ )).where(and59(
86334
+ eq70(runs.projectId, project.id),
86335
+ eq70(runs.kind, RunKinds["site-audit"]),
86336
+ inArray27(runs.status, [RunStatuses.queued, RunStatuses.running])
85938
86337
  )).get();
85939
86338
  if (existing) {
85940
86339
  if (existing.identityKey === identityKey) {
@@ -85988,7 +86387,7 @@ async function technicalAeoRoutes(app, opts) {
85988
86387
  // ../api-routes/src/research.ts
85989
86388
  import crypto50 from "crypto";
85990
86389
  import { z as z5 } from "zod";
85991
- import { and as and58, count as count3, desc as desc31, eq as eq68, gte as gte17, lt as lt12, or as or17, sql as sql27 } from "drizzle-orm";
86390
+ import { and as and60, count as count3, desc as desc31, eq as eq71, gte as gte19, lt as lt12, or as or17, sql as sql27 } from "drizzle-orm";
85992
86391
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
85993
86392
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
85994
86393
  async function researchRoutes(app, opts) {
@@ -86006,7 +86405,7 @@ async function researchRoutes(app, opts) {
86006
86405
  const input = parsed.data;
86007
86406
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
86008
86407
  const requestHash2 = directResearchRequestHash(input);
86009
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and58(eq68(researchRuns.projectId, project.id), eq68(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
86408
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and60(eq71(researchRuns.projectId, project.id), eq71(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
86010
86409
  if (existingReceipt) {
86011
86410
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
86012
86411
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -86038,7 +86437,7 @@ async function researchRoutes(app, opts) {
86038
86437
  const initiatedBy = researchPrincipal(request);
86039
86438
  const decision = app.db.transaction((tx) => {
86040
86439
  if (input.idempotencyKey) {
86041
- const existing = tx.select().from(researchRuns).where(and58(eq68(researchRuns.projectId, project.id), eq68(researchRuns.idempotencyKey, input.idempotencyKey))).get();
86440
+ const existing = tx.select().from(researchRuns).where(and60(eq71(researchRuns.projectId, project.id), eq71(researchRuns.idempotencyKey, input.idempotencyKey))).get();
86042
86441
  if (existing) {
86043
86442
  assertSameDirectResearchRequest(existing, input, requestHash2);
86044
86443
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -86046,9 +86445,9 @@ async function researchRoutes(app, opts) {
86046
86445
  }
86047
86446
  if (initiatedBy?.limited) {
86048
86447
  const { start, end, date } = utcDayBounds(now);
86049
- const used = tx.select({ value: count3() }).from(researchRuns).where(and58(
86050
- eq68(researchRuns.projectId, project.id),
86051
- gte17(researchRuns.createdAt, start),
86448
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and60(
86449
+ eq71(researchRuns.projectId, project.id),
86450
+ gte19(researchRuns.createdAt, start),
86052
86451
  lt12(researchRuns.createdAt, end),
86053
86452
  sql27`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
86054
86453
  )).get()?.value ?? 0;
@@ -86078,15 +86477,15 @@ async function researchRoutes(app, opts) {
86078
86477
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
86079
86478
  const requestHash2 = researchBatchRequestHash(input);
86080
86479
  const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
86081
- const firstReceipt = app.db.select().from(researchRuns).where(and58(
86082
- eq68(researchRuns.projectId, project.id),
86083
- eq68(researchRuns.idempotencyKey, receiptKeys[0])
86480
+ const firstReceipt = app.db.select().from(researchRuns).where(and60(
86481
+ eq71(researchRuns.projectId, project.id),
86482
+ eq71(researchRuns.idempotencyKey, receiptKeys[0])
86084
86483
  )).get();
86085
86484
  if (firstReceipt) {
86086
86485
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
86087
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and58(
86088
- eq68(researchRuns.projectId, project.id),
86089
- eq68(researchRuns.idempotencyKey, key)
86486
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and60(
86487
+ eq71(researchRuns.projectId, project.id),
86488
+ eq71(researchRuns.idempotencyKey, key)
86090
86489
  )).get());
86091
86490
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
86092
86491
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -86101,9 +86500,9 @@ async function researchRoutes(app, opts) {
86101
86500
  const decision = app.db.transaction((tx) => {
86102
86501
  if (initiatedBy?.limited) {
86103
86502
  const { start, end, date } = utcDayBounds(now);
86104
- const used = tx.select({ value: count3() }).from(researchRuns).where(and58(
86105
- eq68(researchRuns.projectId, project.id),
86106
- gte17(researchRuns.createdAt, start),
86503
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and60(
86504
+ eq71(researchRuns.projectId, project.id),
86505
+ gte19(researchRuns.createdAt, start),
86107
86506
  lt12(researchRuns.createdAt, end),
86108
86507
  sql27`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
86109
86508
  )).get()?.value ?? 0;
@@ -86156,9 +86555,9 @@ async function researchRoutes(app, opts) {
86156
86555
  const requested = Number.parseInt(request.query.limit ?? "", 10);
86157
86556
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
86158
86557
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
86159
- const rows = app.db.select().from(researchRuns).where(and58(
86160
- eq68(researchRuns.projectId, project.id),
86161
- cursor ? or17(lt12(researchRuns.createdAt, cursor.createdAt), and58(eq68(researchRuns.createdAt, cursor.createdAt), lt12(researchRuns.id, cursor.id))) : void 0
86558
+ const rows = app.db.select().from(researchRuns).where(and60(
86559
+ eq71(researchRuns.projectId, project.id),
86560
+ cursor ? or17(lt12(researchRuns.createdAt, cursor.createdAt), and60(eq71(researchRuns.createdAt, cursor.createdAt), lt12(researchRuns.id, cursor.id))) : void 0
86162
86561
  )).orderBy(desc31(researchRuns.createdAt), desc31(researchRuns.id)).limit(limit + 1).all();
86163
86562
  const runs2 = rows.slice(0, limit).map(serializeRun);
86164
86563
  const last = runs2.at(-1);
@@ -86285,9 +86684,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
86285
86684
  return stored;
86286
86685
  }
86287
86686
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
86288
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and58(
86289
- eq68(measurementQueryTemplates.projectId, projectId),
86290
- eq68(measurementQueryTemplates.id, selection.templateId)
86687
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and60(
86688
+ eq71(measurementQueryTemplates.projectId, projectId),
86689
+ eq71(measurementQueryTemplates.id, selection.templateId)
86291
86690
  )).get();
86292
86691
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
86293
86692
  const templateVersion = savedTemplate.updatedAt;
@@ -86296,7 +86695,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
86296
86695
  }
86297
86696
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
86298
86697
  if (bindingLocation) {
86299
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq68(projects.id, projectId)).get();
86698
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq71(projects.id, projectId)).get();
86300
86699
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
86301
86700
  }
86302
86701
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -86309,9 +86708,9 @@ function classifyResearchQueries(project, plan, queries2) {
86309
86708
  return queries2.map((query) => classifier?.classify(query) ?? null);
86310
86709
  }
86311
86710
  function getDetail(app, projectId, id) {
86312
- const row = app.db.select().from(researchRuns).where(and58(eq68(researchRuns.id, id), eq68(researchRuns.projectId, projectId))).get();
86711
+ const row = app.db.select().from(researchRuns).where(and60(eq71(researchRuns.id, id), eq71(researchRuns.projectId, projectId))).get();
86313
86712
  if (!row) throw notFound("Research run", id);
86314
- const queries2 = app.db.select().from(researchRunQueries).where(eq68(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
86713
+ const queries2 = app.db.select().from(researchRunQueries).where(eq71(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
86315
86714
  return { ...serializeRun(row), queries: queries2 };
86316
86715
  }
86317
86716
  function serializeRun(row) {
@@ -86340,7 +86739,7 @@ function utcDayBounds(now) {
86340
86739
  }
86341
86740
 
86342
86741
  // ../api-routes/src/plan-competitors.ts
86343
- import { eq as eq69 } from "drizzle-orm";
86742
+ import { eq as eq72 } from "drizzle-orm";
86344
86743
  function competitorKey(value) {
86345
86744
  const host = (hostOf(value) ?? value).trim().toLowerCase().replace(/\.$/, "");
86346
86745
  return host.startsWith("www.") ? host.slice(4) : host;
@@ -86356,7 +86755,7 @@ function toList(merged) {
86356
86755
  return [...merged].sort(([left], [right]) => left.localeCompare(right)).map(([domain, names]) => ({ domain, aliases: [...names] }));
86357
86756
  }
86358
86757
  function readScope(db, versionId) {
86359
- const row = db.select({ canonicalJson: measurementPlanVersions.canonicalJson }).from(measurementPlanVersions).where(eq69(measurementPlanVersions.id, versionId)).get();
86758
+ const row = db.select({ canonicalJson: measurementPlanVersions.canonicalJson }).from(measurementPlanVersions).where(eq72(measurementPlanVersions.id, versionId)).get();
86360
86759
  if (!row) return null;
86361
86760
  let groups;
86362
86761
  let edges;
@@ -86420,7 +86819,7 @@ function createRunCompetitorResolver(db, projectCompetitorDomains) {
86420
86819
 
86421
86820
  // ../api-routes/src/simple-measurement-definitions.ts
86422
86821
  import crypto51 from "crypto";
86423
- import { and as and59, eq as eq70 } from "drizzle-orm";
86822
+ import { and as and61, eq as eq73 } from "drizzle-orm";
86424
86823
  function captureSimpleMeasurementDefinition(db, input) {
86425
86824
  return db.transaction((tx) => {
86426
86825
  const run = tx.select({
@@ -86428,7 +86827,7 @@ function captureSimpleMeasurementDefinition(db, input) {
86428
86827
  trigger: runs.trigger,
86429
86828
  status: runs.status,
86430
86829
  measurementPlanVersionId: runs.measurementPlanVersionId
86431
- }).from(runs).where(and59(eq70(runs.projectId, input.projectId), eq70(runs.id, input.runId))).get();
86830
+ }).from(runs).where(and61(eq73(runs.projectId, input.projectId), eq73(runs.id, input.runId))).get();
86432
86831
  if (!run) throw notFound("Run", input.runId);
86433
86832
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
86434
86833
  return null;
@@ -86438,7 +86837,7 @@ function captureSimpleMeasurementDefinition(db, input) {
86438
86837
  if (definition.queries.some((query) => query.queryClass !== (classifier?.classify(query.queryText) ?? null))) {
86439
86838
  throw validationError("Query classes must match the captured identity and text.");
86440
86839
  }
86441
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and59(eq70(simpleMeasurementDefinitions.projectId, input.projectId), eq70(simpleMeasurementDefinitions.runId, input.runId))).get();
86840
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and61(eq73(simpleMeasurementDefinitions.projectId, input.projectId), eq73(simpleMeasurementDefinitions.runId, input.runId))).get();
86442
86841
  if (existing) {
86443
86842
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
86444
86843
  const replayDefinition = {
@@ -86459,15 +86858,15 @@ function captureSimpleMeasurementDefinition(db, input) {
86459
86858
  if (run.status !== RunStatuses.running) {
86460
86859
  throw validationError("Only a running run can capture a new measurement definition.");
86461
86860
  }
86462
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq70(querySnapshots.runId, input.runId)).limit(1).get()) {
86861
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq73(querySnapshots.runId, input.runId)).limit(1).get()) {
86463
86862
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
86464
86863
  }
86465
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq70(queries.projectId, input.projectId)).all().map((query) => query.id));
86864
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq73(queries.projectId, input.projectId)).all().map((query) => query.id));
86466
86865
  if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
86467
86866
  throw validationError("Every captured query must belong to the run project.");
86468
86867
  }
86469
86868
  if (definition.competitors !== void 0) {
86470
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq70(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
86869
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq73(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
86471
86870
  const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
86472
86871
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
86473
86872
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
@@ -87418,10 +87817,10 @@ var log = createLogger("IntelligenceService");
87418
87817
  var runChronologyKey = sql28`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
87419
87818
  function analyzableRunPredicates(projectId) {
87420
87819
  return [
87421
- eq71(runs.projectId, projectId),
87820
+ eq74(runs.projectId, projectId),
87422
87821
  // Only this kind writes query_snapshots.
87423
- eq71(runs.kind, RunKinds["answer-visibility"]),
87424
- or18(eq71(runs.status, RunStatuses.completed), eq71(runs.status, RunStatuses.partial)),
87822
+ eq74(runs.kind, RunKinds["answer-visibility"]),
87823
+ or18(eq74(runs.status, RunStatuses.completed), eq74(runs.status, RunStatuses.partial)),
87425
87824
  // Defensive: RunCoordinator already skips probes before analyzeAndPersist
87426
87825
  // is called, but a future call site invoking it directly for a probe must
87427
87826
  // still not pollute the intelligence window.
@@ -87466,24 +87865,24 @@ var IntelligenceService = class {
87466
87865
  * Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
87467
87866
  */
87468
87867
  analyzeAndPersist(runId, projectId) {
87469
- const currentRunRecord = this.db.select().from(runs).where(and60(eq71(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
87868
+ const currentRunRecord = this.db.select().from(runs).where(and62(eq74(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
87470
87869
  if (!currentRunRecord) {
87471
87870
  log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
87472
87871
  return null;
87473
87872
  }
87474
87873
  const currentLocation = currentRunRecord.location ?? null;
87475
87874
  const recentRuns = this.db.select().from(runs).where(
87476
- and60(
87875
+ and62(
87477
87876
  ...analyzableRunPredicates(projectId),
87478
- currentLocation === null ? isNull13(runs.location) : eq71(runs.location, currentLocation),
87877
+ currentLocation === null ? isNull13(runs.location) : eq74(runs.location, currentLocation),
87479
87878
  // Never look forward. The window is this run's own past, so
87480
87879
  // re-analyzing a historical run compares it against its true
87481
87880
  // predecessor rather than against sweeps that came after it.
87482
- lte15(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
87881
+ lte17(runChronologyKey, currentRunRecord.finishedAt ?? currentRunRecord.createdAt),
87483
87882
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
87484
87883
  // Index seek on idx_snapshots_run, short-circuited on the first row.
87485
87884
  exists(
87486
- this.db.select({ one: sql28`1` }).from(querySnapshots).where(eq71(querySnapshots.runId, runs.id))
87885
+ this.db.select({ one: sql28`1` }).from(querySnapshots).where(eq74(querySnapshots.runId, runs.id))
87487
87886
  )
87488
87887
  )
87489
87888
  ).orderBy(desc32(runs.finishedAt), desc32(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
@@ -87558,7 +87957,7 @@ var IntelligenceService = class {
87558
87957
  * Returns the persisted insights so the coordinator can count critical/high.
87559
87958
  */
87560
87959
  analyzeAndPersistGbp(runId, projectId) {
87561
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq71(runs.id, runId)).get();
87960
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq74(runs.id, runId)).get();
87562
87961
  if (!runRow) {
87563
87962
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
87564
87963
  this.persistGbpInsights(runId, projectId, [], []);
@@ -87566,11 +87965,11 @@ var IntelligenceService = class {
87566
87965
  }
87567
87966
  const windowStart = runRow.startedAt ?? runRow.createdAt;
87568
87967
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
87569
- const selected = this.db.select().from(gbpLocations).where(and60(
87570
- eq71(gbpLocations.projectId, projectId),
87571
- eq71(gbpLocations.selected, true),
87572
- gte18(gbpLocations.syncedAt, windowStart),
87573
- lte15(gbpLocations.syncedAt, windowEnd)
87968
+ const selected = this.db.select().from(gbpLocations).where(and62(
87969
+ eq74(gbpLocations.projectId, projectId),
87970
+ eq74(gbpLocations.selected, true),
87971
+ gte20(gbpLocations.syncedAt, windowStart),
87972
+ lte17(gbpLocations.syncedAt, windowEnd)
87574
87973
  )).all();
87575
87974
  if (selected.length === 0) {
87576
87975
  log.info("gbp-intelligence.skip", { runId, reason: "no locations synced during run" });
@@ -87603,12 +88002,12 @@ var IntelligenceService = class {
87603
88002
  }
87604
88003
  /** Build the per-location signal bundle the GBP analyzer consumes. */
87605
88004
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
87606
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and60(eq71(gbpDailyMetrics.projectId, projectId), eq71(gbpDailyMetrics.locationName, locationName))).all();
87607
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and60(eq71(gbpPlaceActions.projectId, projectId), eq71(gbpPlaceActions.locationName, locationName))).all();
87608
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and60(eq71(gbpLodgingSnapshots.projectId, projectId), eq71(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc32(gbpLodgingSnapshots.syncedAt)).limit(1).get();
87609
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and60(eq71(gbpLocations.projectId, projectId), eq71(gbpLocations.locationName, locationName))).get();
88005
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and62(eq74(gbpDailyMetrics.projectId, projectId), eq74(gbpDailyMetrics.locationName, locationName))).all();
88006
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and62(eq74(gbpPlaceActions.projectId, projectId), eq74(gbpPlaceActions.locationName, locationName))).all();
88007
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and62(eq74(gbpLodgingSnapshots.projectId, projectId), eq74(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc32(gbpLodgingSnapshots.syncedAt)).limit(1).get();
88008
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and62(eq74(gbpLocations.projectId, projectId), eq74(gbpLocations.locationName, locationName))).get();
87610
88009
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
87611
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and60(eq71(gbpPlaceDetails.projectId, projectId), eq71(gbpPlaceDetails.locationName, locationName))).orderBy(desc32(gbpPlaceDetails.syncedAt)).limit(1).get();
88010
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and62(eq74(gbpPlaceDetails.projectId, projectId), eq74(gbpPlaceDetails.locationName, locationName))).orderBy(desc32(gbpPlaceDetails.syncedAt)).limit(1).get();
87612
88011
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
87613
88012
  const summary3 = buildGbpSummary({
87614
88013
  locationName,
@@ -87644,7 +88043,7 @@ var IntelligenceService = class {
87644
88043
  /** Build the month-over-month keyword series for a location from the
87645
88044
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
87646
88045
  buildGbpKeywordTrend(projectId, locationName) {
87647
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and60(eq71(gbpKeywordMonthly.projectId, projectId), eq71(gbpKeywordMonthly.locationName, locationName))).all();
88046
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and62(eq74(gbpKeywordMonthly.projectId, projectId), eq74(gbpKeywordMonthly.locationName, locationName))).all();
87648
88047
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
87649
88048
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
87650
88049
  const recentMonth = months[0] ?? null;
@@ -87675,7 +88074,7 @@ var IntelligenceService = class {
87675
88074
  */
87676
88075
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
87677
88076
  const covered = new Set(coveredLocationNames);
87678
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and60(eq71(insights.projectId, projectId), eq71(insights.provider, GBP_INSIGHT_PROVIDER))).all();
88077
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and62(eq74(insights.projectId, projectId), eq74(insights.provider, GBP_INSIGHT_PROVIDER))).all();
87679
88078
  const staleIds = [];
87680
88079
  const dismissedSlots = /* @__PURE__ */ new Set();
87681
88080
  for (const row of existing) {
@@ -87686,7 +88085,7 @@ var IntelligenceService = class {
87686
88085
  }
87687
88086
  this.db.transaction((tx) => {
87688
88087
  for (const id of staleIds) {
87689
- tx.delete(insights).where(eq71(insights.id, id)).run();
88088
+ tx.delete(insights).where(eq74(insights.id, id)).run();
87690
88089
  }
87691
88090
  for (const insight of gbpInsights) {
87692
88091
  const parsed = parseGbpInsightId(insight.id);
@@ -87772,7 +88171,7 @@ var IntelligenceService = class {
87772
88171
  * create per run + aggregate). DB is left untouched.
87773
88172
  */
87774
88173
  backfill(projectName, opts, onProgress) {
87775
- const project = this.db.select().from(projects).where(eq71(projects.name, projectName)).get();
88174
+ const project = this.db.select().from(projects).where(eq74(projects.name, projectName)).get();
87776
88175
  if (!project) {
87777
88176
  throw new Error(`Project "${projectName}" not found`);
87778
88177
  }
@@ -87784,7 +88183,7 @@ var IntelligenceService = class {
87784
88183
  }
87785
88184
  sinceTimestamp = parsed;
87786
88185
  }
87787
- const allRuns = this.db.select().from(runs).where(and60(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
88186
+ const allRuns = this.db.select().from(runs).where(and62(...analyzableRunPredicates(project.id))).orderBy(asc13(runs.finishedAt)).all();
87788
88187
  const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
87789
88188
  const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
87790
88189
  let startIdx = 0;
@@ -87815,7 +88214,7 @@ var IntelligenceService = class {
87815
88214
  let wouldDeleteTotal = 0;
87816
88215
  const existingByRunId = /* @__PURE__ */ new Map();
87817
88216
  if (isDryRun && targetRuns.length > 0) {
87818
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray27(insights.runId, targetRuns.map((r) => r.id))).all();
88217
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray28(insights.runId, targetRuns.map((r) => r.id))).all();
87819
88218
  for (const r of rows) {
87820
88219
  if (r.runId == null) continue;
87821
88220
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -87861,7 +88260,7 @@ var IntelligenceService = class {
87861
88260
  return { processed, skipped: skipped3, totalInsights };
87862
88261
  }
87863
88262
  loadTrackedCompetitors(projectId) {
87864
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq71(competitors.projectId, projectId)).all().map((r) => r.domain);
88263
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq74(competitors.projectId, projectId)).all().map((r) => r.domain);
87865
88264
  }
87866
88265
  /**
87867
88266
  * Which of `runIds` actually wrote query snapshots. One grouped read, so
@@ -87870,7 +88269,7 @@ var IntelligenceService = class {
87870
88269
  */
87871
88270
  runIdsWithSnapshots(runIds) {
87872
88271
  if (runIds.length === 0) return /* @__PURE__ */ new Set();
87873
- const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray27(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
88272
+ const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray28(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
87874
88273
  return new Set(rows.map((r) => r.runId));
87875
88274
  }
87876
88275
  /**
@@ -87892,15 +88291,15 @@ var IntelligenceService = class {
87892
88291
  }
87893
88292
  persistResult(result, runId, projectId) {
87894
88293
  const previouslyDismissed = /* @__PURE__ */ new Set();
87895
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq71(insights.runId, runId)).all();
88294
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq74(insights.runId, runId)).all();
87896
88295
  for (const row of existingInsights) {
87897
88296
  if (row.dismissed) {
87898
88297
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
87899
88298
  }
87900
88299
  }
87901
88300
  this.db.transaction((tx) => {
87902
- tx.delete(insights).where(eq71(insights.runId, runId)).run();
87903
- tx.delete(healthSnapshots).where(eq71(healthSnapshots.runId, runId)).run();
88301
+ tx.delete(insights).where(eq74(insights.runId, runId)).run();
88302
+ tx.delete(healthSnapshots).where(eq74(healthSnapshots.runId, runId)).run();
87904
88303
  const now = (/* @__PURE__ */ new Date()).toISOString();
87905
88304
  for (const insight of result.insights) {
87906
88305
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -87947,10 +88346,10 @@ var IntelligenceService = class {
87947
88346
  clicks: gscSearchData.clicks,
87948
88347
  impressions: gscSearchData.impressions,
87949
88348
  position: gscSearchData.position
87950
- }).from(gscSearchData).where(and60(
87951
- eq71(gscSearchData.projectId, projectId),
87952
- gte18(gscSearchData.date, window.startDate),
87953
- lte15(gscSearchData.date, window.endDate)
88349
+ }).from(gscSearchData).where(and62(
88350
+ eq74(gscSearchData.projectId, projectId),
88351
+ gte20(gscSearchData.date, window.startDate),
88352
+ lte17(gscSearchData.date, window.endDate)
87954
88353
  )).all();
87955
88354
  const byDay = /* @__PURE__ */ new Map();
87956
88355
  for (const r of rows) {
@@ -88001,17 +88400,17 @@ var IntelligenceService = class {
88001
88400
  const key = row.query.toLowerCase();
88002
88401
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
88003
88402
  }
88004
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq71(projects.id, projectId)).get();
88403
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq74(projects.id, projectId)).get();
88005
88404
  const locationCount = Math.max(
88006
88405
  1,
88007
88406
  (projectRow?.locations ?? []).length
88008
88407
  );
88009
88408
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
88010
88409
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
88011
- and60(
88012
- eq71(runs.projectId, projectId),
88013
- eq71(runs.kind, RunKinds["answer-visibility"]),
88014
- or18(eq71(runs.status, "completed"), eq71(runs.status, "partial")),
88410
+ and62(
88411
+ eq74(runs.projectId, projectId),
88412
+ eq74(runs.kind, RunKinds["answer-visibility"]),
88413
+ or18(eq74(runs.status, "completed"), eq74(runs.status, "partial")),
88015
88414
  // Defensive — see top of file.
88016
88415
  ne11(runs.trigger, RunTriggers.probe)
88017
88416
  )
@@ -88031,7 +88430,7 @@ var IntelligenceService = class {
88031
88430
  const haveHistory = recentRunIds.length > 0;
88032
88431
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
88033
88432
  if (haveHistory) {
88034
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and60(eq71(insights.type, "regression"), inArray27(insights.runId, recentRunIds))).all();
88433
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and62(eq74(insights.type, "regression"), inArray28(insights.runId, recentRunIds))).all();
88035
88434
  const regressionGroups = /* @__PURE__ */ new Map();
88036
88435
  for (const row of priorRows) {
88037
88436
  if (!row.runId) continue;
@@ -88060,7 +88459,7 @@ var IntelligenceService = class {
88060
88459
  });
88061
88460
  }
88062
88461
  buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
88063
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq71(projects.id, projectId)).get();
88462
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq74(projects.id, projectId)).get();
88064
88463
  const projectDomains = projectDomainRow ? effectiveDomains({
88065
88464
  canonicalDomain: projectDomainRow.canonicalDomain,
88066
88465
  ownedDomains: projectDomainRow.ownedDomains
@@ -88078,7 +88477,7 @@ var IntelligenceService = class {
88078
88477
  citedDomains: querySnapshots.citedDomains,
88079
88478
  rawResponse: querySnapshots.rawResponse,
88080
88479
  snapshotLocation: querySnapshots.location
88081
- }).from(querySnapshots).leftJoin(queries, eq71(querySnapshots.queryId, queries.id)).where(eq71(querySnapshots.runId, runId)).all();
88480
+ }).from(querySnapshots).leftJoin(queries, eq74(querySnapshots.queryId, queries.id)).where(eq74(querySnapshots.runId, runId)).all();
88082
88481
  const snapshots = [];
88083
88482
  let orphanCount = 0;
88084
88483
  for (const r of rows) {
@@ -88300,6 +88699,7 @@ export {
88300
88699
  generateWranglerToml,
88301
88700
  SITE_REACHABILITY_CHECK_ID,
88302
88701
  SITE_REACHABILITY_CHECKS,
88702
+ scheduledHealthCheckIds,
88303
88703
  runChecks,
88304
88704
  executeDiscovery,
88305
88705
  markSessionFailed,