@ainyc/canonry 5.2.1 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
  3. package/assets/assets/{AuditHistoryPanel-CkBLHz2H.js → AuditHistoryPanel-D5cOFMKE.js} +1 -1
  4. package/assets/assets/{BacklinksPage-D2YWJUf9.js → BacklinksPage-BiOj8ztV.js} +1 -1
  5. package/assets/assets/{HistoryPage-C9j3RzMN.js → HistoryPage-DDBCwooU.js} +1 -1
  6. package/assets/assets/MeasurementPropertyPage-CMoU2yA_.js +1 -0
  7. package/assets/assets/ProjectPage-CrFybBls.js +8 -0
  8. package/assets/assets/{RunRow-DTJuJ8mb.js → RunRow-BLBAwIot.js} +1 -1
  9. package/assets/assets/{RunsPage-CdKorpOj.js → RunsPage-BVgBbE1M.js} +1 -1
  10. package/assets/assets/{SettingsPage-BOjZh2vW.js → SettingsPage-DYtN-1_-.js} +1 -1
  11. package/assets/assets/{SiteHealthSection-D7sPztpW.js → SiteHealthSection-ElUwcAS4.js} +3 -3
  12. package/assets/assets/{TrafficPage-BCodKdgN.js → TrafficPage-D6QT_2hU.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-D_zueOK5.js → TrafficSourceDetailPage-BnLzF82w.js} +1 -1
  14. package/assets/assets/{extract-error-message-BZYGBapN.js → extract-error-message-DIzjGZxl.js} +1 -1
  15. package/assets/assets/{index-aZ69fB-u.js → index-CoQVOsWw.js} +3 -3
  16. package/assets/assets/{react-sigma_core.esm.min-m72qsd73.js → react-sigma_core.esm.min-DTbIcdn4.js} +1 -1
  17. package/assets/assets/v2-overview-adapter-B_N4ilQV.js +1 -0
  18. package/assets/assets/{vendor-lucide-D_WBzb76.js → vendor-lucide-nVpYViBH.js} +1 -1
  19. package/assets/index.html +2 -2
  20. package/dist/{chunk-5VONCLVQ.js → chunk-3COTRCK3.js} +1 -1
  21. package/dist/{chunk-YQNKULAD.js → chunk-IACO5R7A.js} +837 -367
  22. package/dist/{chunk-AGUCTX7L.js → chunk-TF6HFT3Q.js} +198 -57
  23. package/dist/cli.js +6 -6
  24. package/dist/{demo-server-BIPT53EY.js → demo-server-TXW3UX3P.js} +2 -2
  25. package/dist/index.js +3 -3
  26. package/dist/{intelligence-service-SJO5E7WN.js → intelligence-service-H34EOB6K.js} +1 -1
  27. package/package.json +13 -13
  28. package/assets/assets/MeasurementPropertyPage-Ci3CUr7Q.js +0 -1
  29. package/assets/assets/ProjectPage-Bdq_KTXX.js +0 -8
  30. package/assets/assets/v2-overview-adapter-BgsdlByS.js +0 -1
@@ -709,7 +709,7 @@ import {
709
709
  } from "./chunk-2U5IW3G5.js";
710
710
 
711
711
  // src/intelligence-service.ts
712
- import { eq as eq67, desc as desc29, asc as asc12, and as and57, ne as ne11, or as or16, inArray as inArray25, gte as gte18, lte as lte15, isNull as isNull12, sql as sql27, exists } from "drizzle-orm";
712
+ import { eq as eq68, desc as desc30, asc as asc12, and as and58, ne as ne11, or as or16, inArray as inArray26, gte as gte18, lte as lte15, isNull as isNull12, sql as sql27, exists } from "drizzle-orm";
713
713
 
714
714
  // ../db/src/client.ts
715
715
  import { mkdirSync } from "fs";
@@ -825,6 +825,7 @@ __export(schema_exports, {
825
825
  siteCrawlPages: () => siteCrawlPages,
826
826
  siteCrawlRunRequests: () => siteCrawlRunRequests,
827
827
  siteCrawlSnapshots: () => siteCrawlSnapshots,
828
+ siteLivenessState: () => siteLivenessState,
828
829
  trafficEventReceipts: () => trafficEventReceipts,
829
830
  trafficSources: () => trafficSources,
830
831
  usageCounters: () => usageCounters,
@@ -1385,6 +1386,28 @@ var doctorHealthState = sqliteTable("doctor_health_state", {
1385
1386
  summary: text("summary").notNull(),
1386
1387
  checkedAt: text("checked_at").notNull(),
1387
1388
  /** When we last emitted for this (status, code). Null until first emit. */
1389
+ notifiedAt: text("notified_at"),
1390
+ /**
1391
+ * Every failing check from the last pass as sorted `status:code` pairs. The
1392
+ * headline code alone cannot see a second breach arriving UNDER an existing
1393
+ * one: a GBP outage opening beneath a standing GA warning left the worst code
1394
+ * unchanged, so it was graded, listed in the payload, and then never sent.
1395
+ * NULL on rows written before this column existed — unknown is not the same
1396
+ * as changed, so the trigger falls back to the code rule for one pass rather
1397
+ * than paging every already-degraded project the moment this ships.
1398
+ */
1399
+ failingSignature: text("failing_signature")
1400
+ });
1401
+ var siteLivenessState = sqliteTable("site_liveness_state", {
1402
+ projectId: text("project_id").primaryKey(),
1403
+ /** Last graded probe: ok | fail. Skipped probes are not recorded. */
1404
+ status: text("status").notNull(),
1405
+ code: text("code").notNull(),
1406
+ summary: text("summary").notNull(),
1407
+ /** Failed passes in a row. Paging waits for two so one blip never alerts. */
1408
+ consecutiveFailures: integer("consecutive_failures").notNull().default(0),
1409
+ checkedAt: text("checked_at").notNull(),
1410
+ /** Set when an outage was actually paged; cleared on recovery. */
1388
1411
  notifiedAt: text("notified_at")
1389
1412
  });
1390
1413
  var notifications = sqliteTable("notifications", {
@@ -7291,6 +7314,35 @@ var MIGRATION_VERSIONS = [
7291
7314
  `CREATE INDEX IF NOT EXISTS idx_runtime_logs_module_level_ts ON runtime_logs(module, level, ts, sequence)`,
7292
7315
  `CREATE INDEX IF NOT EXISTS idx_runtime_logs_ts ON runtime_logs(ts, sequence)`
7293
7316
  ]
7317
+ },
7318
+ {
7319
+ // Liveness gets its own row per project. See siteLivenessState in
7320
+ // schema.ts: a fast "site is up" pass must never overwrite the 6h doctor
7321
+ // state, or it would clear an unrelated outage and send a false recovery.
7322
+ version: 156,
7323
+ name: "site-liveness-state",
7324
+ statements: [
7325
+ `CREATE TABLE IF NOT EXISTS site_liveness_state (
7326
+ project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
7327
+ status TEXT NOT NULL,
7328
+ code TEXT NOT NULL,
7329
+ summary TEXT NOT NULL,
7330
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
7331
+ checked_at TEXT NOT NULL,
7332
+ notified_at TEXT
7333
+ )`
7334
+ ]
7335
+ },
7336
+ {
7337
+ // A breach opening underneath an existing one never reached anyone: the
7338
+ // trigger keyed on the worst check's code, and a lower-ranked check going
7339
+ // bad does not change it. Existing rows get NULL, which the notifier reads
7340
+ // as "unknown" rather than "changed" so shipping this pages nobody.
7341
+ version: 157,
7342
+ name: "doctor-health-failing-signature",
7343
+ statements: [
7344
+ `ALTER TABLE doctor_health_state ADD COLUMN failing_signature TEXT`
7345
+ ]
7294
7346
  }
7295
7347
  ];
7296
7348
  function addRunsMeasurementPlanVersionForeignKey(tx) {
@@ -7757,8 +7809,8 @@ function projectContext(event, nested) {
7757
7809
  const value = event[key] ?? nested[key];
7758
7810
  if (typeof value === "boolean") context[key] = value;
7759
7811
  }
7760
- const errorCode = optionalString(event.errorCode ?? nested.errorCode, 128);
7761
- if (errorCode !== void 0) context.errorCode = errorCode;
7812
+ const errorCode2 = optionalString(event.errorCode ?? nested.errorCode, 128);
7813
+ if (errorCode2 !== void 0) context.errorCode = errorCode2;
7762
7814
  return context;
7763
7815
  }
7764
7816
  var ID_FIELDS = [
@@ -43383,9 +43435,10 @@ function healthView(payload) {
43383
43435
  value: bulletList(others.map((check2) => `${check2.status} \u2014 ${check2.code}`))
43384
43436
  });
43385
43437
  }
43438
+ const website = health.code.startsWith("site.reachability.");
43386
43439
  return {
43387
43440
  severity,
43388
- title: recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
43441
+ title: website ? recovered ? `${project.name} website is back up` : `${project.name} website is down` : recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
43389
43442
  body: health.summary,
43390
43443
  fields,
43391
43444
  url: payload.dashboardUrl,
@@ -52229,14 +52282,14 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
52229
52282
  const now = (/* @__PURE__ */ new Date()).toISOString();
52230
52283
  const entityId = outcome.entity?.id ?? row.entityId;
52231
52284
  const exhausted = outcome.state === AdsOperationStates.unknown && row.reconcileAttempts >= maxAttempts;
52232
- const errorCode = exhausted ? ADS_RECONCILIATION_QUARANTINED : outcome.errorCode ?? null;
52285
+ const errorCode2 = exhausted ? ADS_RECONCILIATION_QUARANTINED : outcome.errorCode ?? null;
52233
52286
  const errorMessage = exhausted ? `Reconciliation stopped after ${maxAttempts} inconclusive attempts; manual remediation is required` : outcome.errorMessage ?? null;
52234
52287
  app.db.transaction((tx) => {
52235
52288
  const result = tx.update(adsOperations).set({
52236
52289
  state: outcome.state,
52237
52290
  entityId,
52238
52291
  upstreamUpdatedAt: outcome.entity?.updatedAt ?? row.upstreamUpdatedAt,
52239
- errorCode,
52292
+ errorCode: errorCode2,
52240
52293
  errorMessage,
52241
52294
  lastReconciledAt: now,
52242
52295
  leaseOwner: null,
@@ -52251,7 +52304,7 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
52251
52304
  if (result.changes > 0) {
52252
52305
  writeAuditLog(tx, reconciliationAuditEntry(row, outcome.state, context, {
52253
52306
  entityId,
52254
- errorCode
52307
+ errorCode: errorCode2
52255
52308
  }));
52256
52309
  }
52257
52310
  });
@@ -57516,11 +57569,11 @@ async function wordpressRoutes(app, opts) {
57516
57569
  const result = await bulkSetSeoMeta(connection, metaEntries);
57517
57570
  const applied = result.results.filter((r) => r.status === "applied").length;
57518
57571
  const manual = result.results.filter((r) => r.status === "manual").length;
57519
- const skipped = result.results.filter((r) => r.status === "skipped").length;
57572
+ const skipped3 = result.results.filter((r) => r.status === "skipped").length;
57520
57573
  steps.push({
57521
57574
  name: "set-meta",
57522
57575
  status: "completed",
57523
- summary: `${applied} applied, ${manual} manual-assist, ${skipped} skipped`
57576
+ summary: `${applied} applied, ${manual} manual-assist, ${skipped3} skipped`
57524
57577
  });
57525
57578
  }
57526
57579
  } catch (err) {
@@ -57542,11 +57595,11 @@ async function wordpressRoutes(app, opts) {
57542
57595
  const result = await deploySchemaFromProfile(connection, profile);
57543
57596
  const deployed = result.results.filter((r) => r.status === "deployed").length;
57544
57597
  const stripped = result.results.filter((r) => r.status === "stripped").length;
57545
- const skipped = result.results.filter((r) => r.status === "skipped").length;
57598
+ const skipped3 = result.results.filter((r) => r.status === "skipped").length;
57546
57599
  steps.push({
57547
57600
  name: "schema-deploy",
57548
57601
  status: "completed",
57549
- summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped} skipped`
57602
+ summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped3} skipped`
57550
57603
  });
57551
57604
  }
57552
57605
  } catch (err) {
@@ -77300,7 +77353,7 @@ async function trafficRoutes(app, opts) {
77300
77353
  startedAt,
77301
77354
  createdAt: startedAt
77302
77355
  }).run();
77303
- const markFailed = (msg, errorCode) => {
77356
+ const markFailed = (msg, errorCode2) => {
77304
77357
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
77305
77358
  app.db.transaction((tx) => {
77306
77359
  tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq55(runs.id, runId)).run();
@@ -77320,7 +77373,7 @@ async function trafficRoutes(app, opts) {
77320
77373
  aiUserFetchHits: 0,
77321
77374
  aiReferralHits: 0,
77322
77375
  durationMs: Date.now() - syncStartedAtMs,
77323
- errorCode
77376
+ errorCode: errorCode2
77324
77377
  });
77325
77378
  } catch {
77326
77379
  }
@@ -79472,9 +79525,418 @@ var ga4ConnectionCheck = {
79472
79525
  };
79473
79526
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
79474
79527
 
79528
+ // ../api-routes/src/doctor/checks/data-freshness.ts
79529
+ import { and as and51, desc as desc26, eq as eq59, inArray as inArray22 } from "drizzle-orm";
79530
+ var GA_DATA_AGING_DAYS = 3;
79531
+ var GA_DATA_STALE_DAYS = 5;
79532
+ var GSC_DATA_AGING_DAYS = 5;
79533
+ var GSC_DATA_STALE_DAYS = 7;
79534
+ var SYNC_IDLE_DAYS = 3;
79535
+ var GSC_REPORTING_TIME_ZONE2 = "America/Los_Angeles";
79536
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
79537
+ var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
79538
+ function utcFromIsoDate(date) {
79539
+ const match = ISO_DATE.exec(date);
79540
+ if (!match) return null;
79541
+ return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
79542
+ }
79543
+ function daysBetweenIsoDates(from, to) {
79544
+ const start = utcFromIsoDate(from);
79545
+ const end = utcFromIsoDate(to);
79546
+ if (start === null || end === null) return null;
79547
+ return Math.floor((end - start) / DAY_MS2);
79548
+ }
79549
+ function daysSinceDate(date, now = /* @__PURE__ */ new Date(), timeZone) {
79550
+ const today = timeZone ? formatIsoDateInTimeZone(now.toISOString(), timeZone) : `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`;
79551
+ return daysBetweenIsoDates(date, today);
79552
+ }
79553
+ function skipped(code, summary2) {
79554
+ return { status: CheckStatuses.skipped, code, summary: summary2, remediation: null };
79555
+ }
79556
+ function lastSyncAgeDays(ctx, kinds) {
79557
+ const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and51(eq59(runs.projectId, ctx.project.id), inArray22(runs.kind, kinds), eq59(runs.status, "completed"))).orderBy(desc26(runs.createdAt)).limit(1).get();
79558
+ if (!row?.createdAt) return null;
79559
+ const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
79560
+ return Number.isFinite(age) ? age : null;
79561
+ }
79562
+ function grade(ctx, spec, newestDate) {
79563
+ const projectName = ctx.project.name;
79564
+ const code = (suffix) => `${spec.source}.data.${suffix}`;
79565
+ const sync = spec.syncCommand(projectName);
79566
+ const syncAge = lastSyncAgeDays(ctx, spec.syncKinds);
79567
+ const syncIdle = syncAge === null || syncAge >= SYNC_IDLE_DAYS;
79568
+ if (!newestDate) {
79569
+ return {
79570
+ status: CheckStatuses.warn,
79571
+ code: code("never-synced"),
79572
+ summary: `${spec.label} is connected but no daily data has been stored yet.`,
79573
+ remediation: `Run \`${sync}\`, or set a data-refresh schedule so it keeps itself current.`,
79574
+ details: { lastSyncAgeDays: syncAge }
79575
+ };
79576
+ }
79577
+ const age = daysSinceDate(newestDate, /* @__PURE__ */ new Date(), spec.timeZone);
79578
+ if (age === null) {
79579
+ return {
79580
+ status: CheckStatuses.warn,
79581
+ code: code("unreadable-date"),
79582
+ summary: `Newest stored ${spec.label} date "${newestDate}" is not a calendar date.`,
79583
+ remediation: null,
79584
+ details: { newestDate }
79585
+ };
79586
+ }
79587
+ const details = { newestDate, ageDays: age, agingDays: spec.agingDays, staleDays: spec.staleDays, lastSyncAgeDays: syncAge };
79588
+ if (age < spec.agingDays) {
79589
+ return {
79590
+ status: CheckStatuses.ok,
79591
+ code: code("fresh"),
79592
+ summary: `${spec.label} data is current through ${newestDate}.`,
79593
+ remediation: null,
79594
+ details
79595
+ };
79596
+ }
79597
+ if (syncIdle) {
79598
+ return {
79599
+ status: CheckStatuses.warn,
79600
+ code: code("not-syncing"),
79601
+ summary: `No ${spec.label} sync has completed in ${syncAge === null ? "any recorded run" : `${syncAge} days`}, and stored data ends ${newestDate}.`,
79602
+ remediation: `Run \`${sync}\`, or set a data-refresh schedule: \`canonry schedule set ${projectName} --kind data-refresh --preset daily\`.`,
79603
+ details
79604
+ };
79605
+ }
79606
+ const remediation = `${spec.quietExplanation} ${spec.brokenExplanation}`;
79607
+ return {
79608
+ status: CheckStatuses.warn,
79609
+ code: code(age >= spec.staleDays ? "stale" : "aging"),
79610
+ summary: `${spec.label} syncs are running but have stored nothing newer than ${newestDate} (${age} days).`,
79611
+ remediation,
79612
+ details
79613
+ };
79614
+ }
79615
+ var GA_SPEC = {
79616
+ source: "ga",
79617
+ label: "GA4",
79618
+ agingDays: GA_DATA_AGING_DAYS,
79619
+ staleDays: GA_DATA_STALE_DAYS,
79620
+ syncKinds: ["ga-sync"],
79621
+ syncCommand: (name) => `canonry ga sync ${name}`,
79622
+ quietExplanation: "GA4 omits days with no sessions, so a genuinely quiet period clears itself on the next visit.",
79623
+ brokenExplanation: "If the site did have visitors, check that the GA4 tag or its Tag Manager container is still on the live site."
79624
+ };
79625
+ var GSC_SPEC = {
79626
+ source: "gsc",
79627
+ label: "Search Console",
79628
+ agingDays: GSC_DATA_AGING_DAYS,
79629
+ staleDays: GSC_DATA_STALE_DAYS,
79630
+ timeZone: GSC_REPORTING_TIME_ZONE2,
79631
+ syncKinds: ["gsc-sync"],
79632
+ syncCommand: (name) => `canonry google sync ${name}`,
79633
+ quietExplanation: "Search Console omits days with no impressions and reports two to three days behind, so a quiet period clears itself.",
79634
+ brokenExplanation: "If the site did have impressions, check that the property still lists this site and the connected account still has access."
79635
+ };
79636
+ function gaConnected(ctx) {
79637
+ const project = ctx.project;
79638
+ if (!ctx.ga4CredentialStore && !ctx.googleConnectionStore) return "store-unavailable";
79639
+ if (ctx.ga4CredentialStore?.getConnection(project.name)) return "connected";
79640
+ if (ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4")) return "connected";
79641
+ return "not-connected";
79642
+ }
79643
+ var gaRecentDataCheck = {
79644
+ id: "ga.data.recent-data",
79645
+ category: CheckCategories.integrations,
79646
+ scope: CheckScopes.project,
79647
+ title: "GA4 data still arriving",
79648
+ run: (ctx) => {
79649
+ if (!ctx.project) return skipped("ga.data.no-project", "Project context required.");
79650
+ const connection = gaConnected(ctx);
79651
+ if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
79652
+ if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
79653
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq59(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gaDailyTotals.date)).limit(1).get();
79654
+ return grade(ctx, GA_SPEC, newest?.date);
79655
+ }
79656
+ };
79657
+ var gscRecentDataCheck = {
79658
+ id: "gsc.data.recent-data",
79659
+ category: CheckCategories.integrations,
79660
+ scope: CheckScopes.project,
79661
+ title: "Search Console data still arriving",
79662
+ run: (ctx) => {
79663
+ if (!ctx.project) return skipped("gsc.data.no-project", "Project context required.");
79664
+ if (!ctx.googleConnectionStore) return skipped("gsc.data.store-unavailable", "No Google connection store configured for this deployment.");
79665
+ if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
79666
+ return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
79667
+ }
79668
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq59(gscDataWatermarks.projectId, ctx.project.id)).get();
79669
+ if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
79670
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq59(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gscDailyTotals.date)).limit(1).get();
79671
+ return grade(ctx, GSC_SPEC, newest?.date);
79672
+ }
79673
+ };
79674
+ var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
79675
+
79676
+ // ../api-routes/src/site-reachability.ts
79677
+ import dns3 from "dns/promises";
79678
+ import http3 from "http";
79679
+ import https3 from "https";
79680
+ import net3 from "net";
79681
+ var SITE_REACHABILITY_USER_AGENT = "Canonry site-liveness (+https://canonry.ai)";
79682
+ var DEFAULT_SITE_MAX_REDIRECTS = 10;
79683
+ var DEFAULT_TIMEOUT_MS5 = 1e4;
79684
+ var DEFAULT_RETRY_DELAY_MS = 2e3;
79685
+ var DNS_TIMEOUT_MS = 5e3;
79686
+ var MAX_ADDRESSES_PER_HOP = 4;
79687
+ var LENIENT_RETRY_CODES = /* @__PURE__ */ new Set([
79688
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
79689
+ "UNABLE_TO_GET_ISSUER_CERT",
79690
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
79691
+ "SELF_SIGNED_CERT_IN_CHAIN",
79692
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
79693
+ "ERR_TLS_CERT_ALTNAME_INVALID",
79694
+ "HPE_INVALID_HEADER_TOKEN",
79695
+ "HPE_UNEXPECTED_CONTENT_LENGTH",
79696
+ "HPE_INVALID_CONSTANT"
79697
+ ]);
79698
+ var NO_SUCH_HOST_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "ENODATA"]);
79699
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
79700
+ function errorCode(err) {
79701
+ const code = err?.code;
79702
+ return typeof code === "string" ? code : "";
79703
+ }
79704
+ async function lookupSiteAddresses(hostname, timeoutMs = DNS_TIMEOUT_MS) {
79705
+ const literalFamily = net3.isIPv6(hostname) ? 6 : net3.isIPv4(hostname) ? 4 : null;
79706
+ if (literalFamily) return { kind: "addresses", addresses: [{ address: hostname, family: literalFamily }] };
79707
+ let timer;
79708
+ const deadline = new Promise((resolve) => {
79709
+ timer = setTimeout(() => resolve("timeout"), timeoutMs);
79710
+ });
79711
+ try {
79712
+ const settled = await Promise.race([
79713
+ Promise.allSettled([dns3.resolve4(hostname), dns3.resolve6(hostname)]),
79714
+ deadline
79715
+ ]);
79716
+ if (settled === "timeout") return { kind: "resolver-error", reason: `DNS did not answer within ${Math.round(timeoutMs / 1e3)}s` };
79717
+ const [ipv4, ipv6] = settled;
79718
+ const found = /* @__PURE__ */ new Map();
79719
+ if (ipv4.status === "fulfilled") for (const address of ipv4.value) found.set(`4:${address}`, { address, family: 4 });
79720
+ if (ipv6.status === "fulfilled") for (const address of ipv6.value) found.set(`6:${address}`, { address, family: 6 });
79721
+ if (found.size > 0) return { kind: "addresses", addresses: [...found.values()] };
79722
+ const codes = [ipv4, ipv6].map((r) => r.status === "rejected" ? errorCode(r.reason) : "");
79723
+ if (codes.every((code) => NO_SUCH_HOST_CODES.has(code))) {
79724
+ return { kind: "no-such-host", reason: `${hostname} has no DNS address (${codes[0]})` };
79725
+ }
79726
+ const failing = codes.find((code) => code && !NO_SUCH_HOST_CODES.has(code)) ?? "unknown resolver error";
79727
+ return { kind: "resolver-error", reason: `DNS lookup for ${hostname} failed (${failing})` };
79728
+ } finally {
79729
+ if (timer) clearTimeout(timer);
79730
+ }
79731
+ }
79732
+ async function requestPinnedStatus(target, timeoutMs, options = {}) {
79733
+ const secure = target.url.protocol === "https:";
79734
+ const port = target.url.port ? Number(target.url.port) : secure ? 443 : 80;
79735
+ const requestOptions = {
79736
+ hostname: target.address,
79737
+ family: target.family,
79738
+ port,
79739
+ method: "GET",
79740
+ path: `${target.url.pathname}${target.url.search}`,
79741
+ headers: { Host: target.url.host, "User-Agent": SITE_REACHABILITY_USER_AGENT, Accept: "text/html,*/*;q=0.8" },
79742
+ insecureHTTPParser: options.lenient === true
79743
+ };
79744
+ if (secure) {
79745
+ requestOptions.servername = target.url.hostname.replace(/^\[|\]$/g, "");
79746
+ if (options.lenient) requestOptions.rejectUnauthorized = false;
79747
+ }
79748
+ return await new Promise((resolve, reject) => {
79749
+ let settled = false;
79750
+ const request = (secure ? https3.request : http3.request)(requestOptions, (response) => {
79751
+ if (settled) {
79752
+ response.destroy();
79753
+ return;
79754
+ }
79755
+ settled = true;
79756
+ clearTimeout(deadline);
79757
+ const location = typeof response.headers.location === "string" ? response.headers.location : null;
79758
+ resolve({ status: response.statusCode ?? 0, location });
79759
+ response.destroy();
79760
+ request.destroy();
79761
+ });
79762
+ const deadline = setTimeout(() => {
79763
+ if (settled) return;
79764
+ settled = true;
79765
+ request.destroy();
79766
+ reject(new Error(`no response within ${Math.round(timeoutMs / 1e3)}s`));
79767
+ }, timeoutMs);
79768
+ request.on("error", (err) => {
79769
+ if (settled) return;
79770
+ settled = true;
79771
+ clearTimeout(deadline);
79772
+ reject(err);
79773
+ });
79774
+ request.end();
79775
+ });
79776
+ }
79777
+ async function probeSiteReachability(url, options = {}) {
79778
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
79779
+ const maxRedirects = options.maxRedirects ?? DEFAULT_SITE_MAX_REDIRECTS;
79780
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
79781
+ const transport = options.transport ?? ((target, ms, opts) => requestPinnedStatus(target, ms, opts));
79782
+ const resolveAddresses = options.resolveAddresses ?? ((hostname) => lookupSiteAddresses(hostname));
79783
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
79784
+ const started = Date.now();
79785
+ const approveHop = async (hopUrl) => {
79786
+ let hostname;
79787
+ try {
79788
+ hostname = new URL(hopUrl).hostname.replace(/^\[|\]$/g, "");
79789
+ } catch {
79790
+ return { kind: "refused", reason: `${hopUrl} is not a valid URL` };
79791
+ }
79792
+ const lookup = await resolveAddresses(hostname);
79793
+ if (lookup.kind !== "addresses") return lookup;
79794
+ const targets = [];
79795
+ let refusal = "";
79796
+ for (const address of lookup.addresses.slice(0, MAX_ADDRESSES_PER_HOP)) {
79797
+ const approved = await resolveMeasurementSitemapTarget(hopUrl, { resolveAddresses: async () => [address] });
79798
+ if (approved.ok) targets.push(approved.target);
79799
+ else if (!refusal) refusal = approved.message;
79800
+ }
79801
+ if (targets.length === 0) return { kind: "refused", reason: refusal || `${hostname} has no address this instance may dial` };
79802
+ return { kind: "targets", targets };
79803
+ };
79804
+ const attempt = async () => {
79805
+ let current = url;
79806
+ for (let hop = 0; ; hop += 1) {
79807
+ const approved = await approveHop(current);
79808
+ if (approved.kind === "no-such-host") return { kind: "down", finalUrl: current, httpStatus: null, reason: approved.reason };
79809
+ if (approved.kind === "resolver-error") return { kind: "unavailable", reason: approved.reason };
79810
+ if (approved.kind === "refused") return { kind: "unreachable", finalUrl: current, reason: approved.reason };
79811
+ let response = null;
79812
+ let lastError = "";
79813
+ for (const target of approved.targets) {
79814
+ try {
79815
+ response = await transport(target, timeoutMs);
79816
+ break;
79817
+ } catch (err) {
79818
+ lastError = describeError(err);
79819
+ if (LENIENT_RETRY_CODES.has(errorCode(err))) {
79820
+ try {
79821
+ const lenient = await transport(target, timeoutMs, { lenient: true });
79822
+ response = { ...lenient, lenient: `${errorCode(err)} (accepted leniently)` };
79823
+ break;
79824
+ } catch (retryErr) {
79825
+ lastError = describeError(retryErr);
79826
+ }
79827
+ }
79828
+ }
79829
+ }
79830
+ if (!response) return { kind: "down", finalUrl: current, httpStatus: null, reason: lastError || "no address answered" };
79831
+ if (REDIRECT_STATUSES.has(response.status) && response.location) {
79832
+ if (hop >= maxRedirects) {
79833
+ return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `more than ${maxRedirects} redirects` };
79834
+ }
79835
+ try {
79836
+ current = new URL(response.location, current).toString();
79837
+ } catch {
79838
+ return { kind: "down", finalUrl: current, httpStatus: response.status, reason: "redirect with an invalid Location header" };
79839
+ }
79840
+ continue;
79841
+ }
79842
+ if (response.status >= 500 || response.status === 0) {
79843
+ return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `HTTP ${response.status}` };
79844
+ }
79845
+ return { kind: "up", finalUrl: current, httpStatus: response.status, lenient: response.lenient };
79846
+ }
79847
+ };
79848
+ let attempts = 1;
79849
+ let outcome = await attempt();
79850
+ if (outcome.kind === "down" || outcome.kind === "unreachable") {
79851
+ await sleep(retryDelayMs);
79852
+ attempts = 2;
79853
+ outcome = await attempt();
79854
+ }
79855
+ const durationMs = Date.now() - started;
79856
+ if (outcome.kind === "unavailable") return { state: "unavailable", url, reason: outcome.reason };
79857
+ if (outcome.kind === "up") {
79858
+ return { state: "up", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, attempts, durationMs, ...outcome.lenient ? { lenient: outcome.lenient } : {} };
79859
+ }
79860
+ if (outcome.kind === "unreachable") return { state: "unreachable", url, finalUrl: outcome.finalUrl, reason: outcome.reason, attempts, durationMs };
79861
+ return { state: "down", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, reason: outcome.reason, attempts, durationMs };
79862
+ }
79863
+
79864
+ // ../api-routes/src/doctor/checks/site-reachability.ts
79865
+ var SITE_REACHABILITY_CHECK_ID = "site.reachability";
79866
+ function probeHostFromDomain(value) {
79867
+ const trimmed = (value ?? "").trim();
79868
+ if (!trimmed) return null;
79869
+ let hostname;
79870
+ try {
79871
+ hostname = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`).hostname;
79872
+ } catch {
79873
+ return null;
79874
+ }
79875
+ hostname = hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
79876
+ if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
79877
+ return hostname;
79878
+ }
79879
+ var skipped2 = (code, summary2) => ({ status: CheckStatuses.skipped, code, summary: summary2, remediation: null });
79880
+ var siteReachabilityCheck = {
79881
+ id: SITE_REACHABILITY_CHECK_ID,
79882
+ category: CheckCategories.integrations,
79883
+ scope: CheckScopes.project,
79884
+ title: "Website reachable",
79885
+ // Reaches the network, and one bad answer is not an outage: the liveness loop
79886
+ // names it explicitly and applies the two-pass debounce. An unfiltered
79887
+ // `canonry doctor --project` must not exit 1 on a single dropped packet.
79888
+ optIn: true,
79889
+ run: async (ctx) => {
79890
+ if (!ctx.project) return skipped2("site.reachability.no-project", "Project context required.");
79891
+ const host = probeHostFromDomain(ctx.project.canonicalDomain);
79892
+ if (!host) {
79893
+ return skipped2("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
79894
+ }
79895
+ const url = `https://${host}/`;
79896
+ const probe = ctx.probeSiteReachability ?? probeSiteReachability;
79897
+ const result = await probe(url);
79898
+ if (result.state === "unavailable") {
79899
+ return skipped2("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
79900
+ }
79901
+ if (result.state === "up") {
79902
+ return {
79903
+ status: CheckStatuses.ok,
79904
+ code: "site.reachability.up",
79905
+ summary: `${url} answered HTTP ${result.httpStatus} in ${result.durationMs} ms.`,
79906
+ remediation: null,
79907
+ details: {
79908
+ url,
79909
+ finalUrl: result.finalUrl,
79910
+ httpStatus: result.httpStatus,
79911
+ attempts: result.attempts,
79912
+ durationMs: result.durationMs,
79913
+ ...result.lenient ? { tlsOrParserWarning: result.lenient } : {}
79914
+ }
79915
+ };
79916
+ }
79917
+ if (result.state === "unreachable") {
79918
+ return {
79919
+ status: CheckStatuses.fail,
79920
+ code: "site.reachability.refused-address",
79921
+ summary: `${url} resolves only to addresses that cannot serve visitors: ${result.reason}.`,
79922
+ remediation: "Check the domain registration and its DNS records. A suspended or parked domain resolves this way.",
79923
+ details: { url, finalUrl: result.finalUrl, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
79924
+ };
79925
+ }
79926
+ return {
79927
+ status: CheckStatuses.fail,
79928
+ code: "site.reachability.down",
79929
+ summary: `${url} is not responding: ${result.reason}.`,
79930
+ remediation: "Open the site in a browser and check with the host. Canonry keeps checking and sends health.recovered when it answers again.",
79931
+ details: { url, finalUrl: result.finalUrl, httpStatus: result.httpStatus, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
79932
+ };
79933
+ }
79934
+ };
79935
+ var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
79936
+
79475
79937
  // ../api-routes/src/doctor/checks/gbp-auth.ts
79476
- import { and as and51, eq as eq59 } from "drizzle-orm";
79477
- var RECENT_SYNC_WARN_DAYS2 = 7;
79938
+ import { and as and52, eq as eq60 } from "drizzle-orm";
79939
+ var RECENT_SYNC_WARN_DAYS2 = 4;
79478
79940
  var RECENT_SYNC_FAIL_DAYS2 = 30;
79479
79941
  function skippedNoProject3() {
79480
79942
  return {
@@ -79706,7 +80168,7 @@ var recentSyncCheck = {
79706
80168
  title: "GBP recent sync",
79707
80169
  run: (ctx) => {
79708
80170
  if (!ctx.project) return skippedNoProject3();
79709
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and51(eq59(gbpLocations.projectId, ctx.project.id), eq59(gbpLocations.selected, true))).all();
80171
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and52(eq60(gbpLocations.projectId, ctx.project.id), eq60(gbpLocations.selected, true))).all();
79710
80172
  if (selected.length === 0) {
79711
80173
  return {
79712
80174
  status: CheckStatuses.skipped,
@@ -79766,7 +80228,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
79766
80228
  );
79767
80229
 
79768
80230
  // ../api-routes/src/doctor/checks/places.ts
79769
- import { eq as eq60 } from "drizzle-orm";
80231
+ import { eq as eq61 } from "drizzle-orm";
79770
80232
  var apiKeyCheck = {
79771
80233
  id: "gbp.places.api-key",
79772
80234
  category: CheckCategories.auth,
@@ -79811,7 +80273,7 @@ var apiKeyCheck = {
79811
80273
  details: { tier: cfg.tier }
79812
80274
  };
79813
80275
  }
79814
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq60(gbpLocations.projectId, ctx.project.id)).all();
80276
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq61(gbpLocations.projectId, ctx.project.id)).all();
79815
80277
  const selected = rows.filter((r) => r.selected);
79816
80278
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
79817
80279
  const details = {
@@ -80326,7 +80788,7 @@ var RUNTIME_STATE_CHECKS = [
80326
80788
  ];
80327
80789
 
80328
80790
  // ../api-routes/src/doctor/checks/traffic-source.ts
80329
- import { and as and52, eq as eq61, gte as gte15, inArray as inArray22, ne as ne10, sql as sql24 } from "drizzle-orm";
80791
+ import { and as and53, eq as eq62, gte as gte15, inArray as inArray23, ne as ne10, sql as sql24 } from "drizzle-orm";
80330
80792
  var RECENT_DATA_WARN_DAYS = 7;
80331
80793
  var RECENT_DATA_FAIL_DAYS = 30;
80332
80794
  function isCloudflareDirectPush(source) {
@@ -80365,8 +80827,8 @@ function skippedNoProject5() {
80365
80827
  function loadProbes(ctx) {
80366
80828
  if (!ctx.project) return [];
80367
80829
  const rows = ctx.db.select().from(trafficSources).where(
80368
- and52(
80369
- eq61(trafficSources.projectId, ctx.project.id),
80830
+ and53(
80831
+ eq62(trafficSources.projectId, ctx.project.id),
80370
80832
  ne10(trafficSources.status, TrafficSourceStatuses.archived)
80371
80833
  )
80372
80834
  ).all();
@@ -80472,27 +80934,27 @@ var recentDataCheck = {
80472
80934
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
80473
80935
  const recentCrawlers = Number(
80474
80936
  ctx.db.select({ total: sql24`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
80475
- and52(
80476
- eq61(crawlerEventsHourly.projectId, ctx.project.id),
80477
- inArray22(crawlerEventsHourly.sourceId, activeSourceIds),
80937
+ and53(
80938
+ eq62(crawlerEventsHourly.projectId, ctx.project.id),
80939
+ inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
80478
80940
  gte15(crawlerEventsHourly.tsHour, warnCutoff)
80479
80941
  )
80480
80942
  ).get()?.total ?? 0
80481
80943
  );
80482
80944
  const recentReferrals = Number(
80483
80945
  ctx.db.select({ total: sql24`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
80484
- and52(
80485
- eq61(aiReferralEventsHourly.projectId, ctx.project.id),
80486
- inArray22(aiReferralEventsHourly.sourceId, activeSourceIds),
80946
+ and53(
80947
+ eq62(aiReferralEventsHourly.projectId, ctx.project.id),
80948
+ inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
80487
80949
  gte15(aiReferralEventsHourly.tsHour, warnCutoff)
80488
80950
  )
80489
80951
  ).get()?.total ?? 0
80490
80952
  );
80491
80953
  const recentUserFetches = Number(
80492
80954
  ctx.db.select({ total: sql24`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
80493
- and52(
80494
- eq61(aiUserFetchEventsHourly.projectId, ctx.project.id),
80495
- inArray22(aiUserFetchEventsHourly.sourceId, activeSourceIds),
80955
+ and53(
80956
+ eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
80957
+ inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
80496
80958
  gte15(aiUserFetchEventsHourly.tsHour, warnCutoff)
80497
80959
  )
80498
80960
  ).get()?.total ?? 0
@@ -80516,27 +80978,27 @@ var recentDataCheck = {
80516
80978
  }
80517
80979
  const olderCrawlers = Number(
80518
80980
  ctx.db.select({ total: sql24`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
80519
- and52(
80520
- eq61(crawlerEventsHourly.projectId, ctx.project.id),
80521
- inArray22(crawlerEventsHourly.sourceId, activeSourceIds),
80981
+ and53(
80982
+ eq62(crawlerEventsHourly.projectId, ctx.project.id),
80983
+ inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
80522
80984
  gte15(crawlerEventsHourly.tsHour, failCutoff)
80523
80985
  )
80524
80986
  ).get()?.total ?? 0
80525
80987
  );
80526
80988
  const olderReferrals = Number(
80527
80989
  ctx.db.select({ total: sql24`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
80528
- and52(
80529
- eq61(aiReferralEventsHourly.projectId, ctx.project.id),
80530
- inArray22(aiReferralEventsHourly.sourceId, activeSourceIds),
80990
+ and53(
80991
+ eq62(aiReferralEventsHourly.projectId, ctx.project.id),
80992
+ inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
80531
80993
  gte15(aiReferralEventsHourly.tsHour, failCutoff)
80532
80994
  )
80533
80995
  ).get()?.total ?? 0
80534
80996
  );
80535
80997
  const olderUserFetches = Number(
80536
80998
  ctx.db.select({ total: sql24`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
80537
- and52(
80538
- eq61(aiUserFetchEventsHourly.projectId, ctx.project.id),
80539
- inArray22(aiUserFetchEventsHourly.sourceId, activeSourceIds),
80999
+ and53(
81000
+ eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
81001
+ inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
80540
81002
  gte15(aiUserFetchEventsHourly.tsHour, failCutoff)
80541
81003
  )
80542
81004
  ).get()?.total ?? 0
@@ -80601,7 +81063,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
80601
81063
  function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
80602
81064
  const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
80603
81065
  const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
80604
- const skipped = results.filter((r) => r.output.status === CheckStatuses.skipped);
81066
+ const skipped3 = results.filter((r) => r.output.status === CheckStatuses.skipped);
80605
81067
  const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
80606
81068
  const detail = {
80607
81069
  sources: results.map((r) => ({
@@ -80635,7 +81097,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
80635
81097
  return {
80636
81098
  status: CheckStatuses.ok,
80637
81099
  code: `traffic.${fallbackId}.ok`,
80638
- summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped.length > 0 ? ` (${skipped.length} skipped)` : ""}.`,
81100
+ summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped3.length > 0 ? ` (${skipped3.length} skipped)` : ""}.`,
80639
81101
  details: detail
80640
81102
  };
80641
81103
  }
@@ -80643,7 +81105,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
80643
81105
  status: CheckStatuses.skipped,
80644
81106
  code: `traffic.${fallbackId}.all-skipped`,
80645
81107
  summary: `No source-type validator was available for any of the ${results.length} connected source(s).`,
80646
- remediation: skipped.find((result) => result.output.remediation)?.output.remediation,
81108
+ remediation: skipped3.find((result) => result.output.remediation)?.output.remediation,
80647
81109
  details: detail
80648
81110
  };
80649
81111
  }
@@ -81069,7 +81531,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
81069
81531
  ];
81070
81532
  var FRESH_SNAPSHOT_WARN_DAYS = 7;
81071
81533
  var FRESH_SNAPSHOT_FAIL_DAYS = 30;
81072
- var DAY_MS2 = 24 * 60 * 60 * 1e3;
81534
+ var DAY_MS3 = 24 * 60 * 60 * 1e3;
81073
81535
  function unavailableStatus() {
81074
81536
  return {
81075
81537
  status: CheckStatuses.skipped,
@@ -81156,7 +81618,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
81156
81618
  details: { latestSnapshotAt }
81157
81619
  };
81158
81620
  }
81159
- const ageDays = (now.getTime() - snapshotMs) / DAY_MS2;
81621
+ const ageDays = (now.getTime() - snapshotMs) / DAY_MS3;
81160
81622
  const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays)) };
81161
81623
  if (ageDays < 0) {
81162
81624
  return {
@@ -81377,13 +81839,16 @@ var ALL_CHECKS = [
81377
81839
  ...BING_AUTH_CHECKS,
81378
81840
  ...WORDPRESS_PUBLISH_CHECKS,
81379
81841
  ...GA_AUTH_CHECKS,
81842
+ ...DATA_FRESHNESS_CHECKS,
81380
81843
  ...ADS_CHECKS,
81381
81844
  ...GOOGLE_MARKETING_DOCTOR_CHECKS,
81382
81845
  ...PROVIDERS_CHECKS,
81383
81846
  ...TRAFFIC_SOURCE_CHECKS,
81384
81847
  ...BACKLINKS_CHECKS,
81385
81848
  ...CONTENT_CHECKS,
81386
- ...AGENT_CHECKS
81849
+ ...AGENT_CHECKS,
81850
+ // Network probe last, so a slow or unreachable site never delays the local checks.
81851
+ ...SITE_REACHABILITY_CHECKS
81387
81852
  ];
81388
81853
 
81389
81854
  // ../api-routes/src/doctor/runner.ts
@@ -81405,6 +81870,7 @@ async function runChecks(ctx, checks, options = {}) {
81405
81870
  const projectName = ctx.project?.name ?? null;
81406
81871
  const selected = checks.filter((check2) => {
81407
81872
  if (check2.scope !== targetScope) return false;
81873
+ if (check2.optIn && filters.length === 0) return false;
81408
81874
  return matchesCheckId(check2.id, filters);
81409
81875
  });
81410
81876
  const results = [];
@@ -81515,7 +81981,7 @@ async function doctorRoutes(app, opts) {
81515
81981
 
81516
81982
  // ../api-routes/src/discovery/routes.ts
81517
81983
  import crypto46 from "crypto";
81518
- import { and as and53, desc as desc26, eq as eq62, gte as gte16, inArray as inArray23, isNull as isNull10, or as or13 } from "drizzle-orm";
81984
+ import { and as and54, desc as desc27, eq as eq63, gte as gte16, inArray as inArray24, isNull as isNull10, or as or13 } from "drizzle-orm";
81519
81985
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
81520
81986
  async function discoveryRoutes(app, opts) {
81521
81987
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -81549,13 +82015,13 @@ async function discoveryRoutes(app, opts) {
81549
82015
  const now = (/* @__PURE__ */ new Date()).toISOString();
81550
82016
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
81551
82017
  const decision = app.db.transaction((tx) => {
81552
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and53(
81553
- eq62(discoverySessions.projectId, project.id),
81554
- eq62(discoverySessions.icpDescription, icpDescription),
82018
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and54(
82019
+ eq63(discoverySessions.projectId, project.id),
82020
+ eq63(discoverySessions.icpDescription, icpDescription),
81555
82021
  // Buyer is part of session identity: it changes the seed prompt's
81556
82022
  // semantics, so a request with a different (or no) buyer must start
81557
82023
  // its own session, never adopt another buyer's probes.
81558
- parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) : eq62(discoverySessions.buyerDescription, parsed.data.buyerDescription),
82024
+ parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) : eq63(discoverySessions.buyerDescription, parsed.data.buyerDescription),
81559
82025
  // Locations are identity too: a different service-area subset seeds
81560
82026
  // and probes a different geo, so it must never reuse another geo's
81561
82027
  // session. resolveLocations is deterministic (project-config order),
@@ -81565,18 +82031,18 @@ async function discoveryRoutes(app, opts) {
81565
82031
  // locations a NULL row's subset is unknowable, so it conservatively
81566
82032
  // never reuses — a one-time, bounded (2h window) non-reuse after
81567
82033
  // upgrade, never a wrong reuse.
81568
- locations.length === 0 ? or13(isNull10(discoverySessions.locations), eq62(discoverySessions.locations, locations)) : eq62(discoverySessions.locations, locations),
82034
+ locations.length === 0 ? or13(isNull10(discoverySessions.locations), eq63(discoverySessions.locations, locations)) : eq63(discoverySessions.locations, locations),
81569
82035
  // Seed provider set is identity: a different phrasing distribution
81570
82036
  // must never reuse another set's session. Null (= Gemini-only
81571
82037
  // default, including explicit ['gemini']) matches legacy rows.
81572
- seedProviders == null ? isNull10(discoverySessions.seedProviders) : eq62(discoverySessions.seedProviders, seedProviders),
81573
- inArray23(discoverySessions.status, [
82038
+ seedProviders == null ? isNull10(discoverySessions.seedProviders) : eq63(discoverySessions.seedProviders, seedProviders),
82039
+ inArray24(discoverySessions.status, [
81574
82040
  DiscoverySessionStatuses.queued,
81575
82041
  DiscoverySessionStatuses.seeding,
81576
82042
  DiscoverySessionStatuses.probing
81577
82043
  ]),
81578
82044
  gte16(discoverySessions.createdAt, ageFloorIso)
81579
- )).orderBy(desc26(discoverySessions.createdAt)).get();
82045
+ )).orderBy(desc27(discoverySessions.createdAt)).get();
81580
82046
  if (existing && existing.runId) {
81581
82047
  return { reused: true, sessionId: existing.id, runId: existing.runId };
81582
82048
  }
@@ -81645,7 +82111,7 @@ async function discoveryRoutes(app, opts) {
81645
82111
  const project = resolveProject(app.db, request.params.name);
81646
82112
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
81647
82113
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
81648
- const rows = app.db.select().from(discoverySessions).where(eq62(discoverySessions.projectId, project.id)).orderBy(desc26(discoverySessions.createdAt)).limit(limit).all();
82114
+ const rows = app.db.select().from(discoverySessions).where(eq63(discoverySessions.projectId, project.id)).orderBy(desc27(discoverySessions.createdAt)).limit(limit).all();
81649
82115
  return reply.send(rows.map(serializeSession));
81650
82116
  }
81651
82117
  );
@@ -81653,11 +82119,11 @@ async function discoveryRoutes(app, opts) {
81653
82119
  "/projects/:name/discover/sessions/:id",
81654
82120
  async (request, reply) => {
81655
82121
  const project = resolveProject(app.db, request.params.name);
81656
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82122
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81657
82123
  if (!session || session.projectId !== project.id) {
81658
82124
  throw notFound("Discovery session", request.params.id);
81659
82125
  }
81660
- const probeRows = app.db.select().from(discoveryProbes).where(eq62(discoveryProbes.sessionId, session.id)).all();
82126
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
81661
82127
  const detail = {
81662
82128
  ...serializeSession(session),
81663
82129
  probes: probeRows.map(serializeProbe)
@@ -81669,7 +82135,7 @@ async function discoveryRoutes(app, opts) {
81669
82135
  "/projects/:name/discover/sessions/:id/harvest",
81670
82136
  async (request, reply) => {
81671
82137
  const project = resolveProject(app.db, request.params.name);
81672
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82138
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81673
82139
  if (!session || session.projectId !== project.id) {
81674
82140
  throw notFound("Discovery session", request.params.id);
81675
82141
  }
@@ -81677,7 +82143,7 @@ async function discoveryRoutes(app, opts) {
81677
82143
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
81678
82144
  const applyAnchor = request.query.anchor !== "false";
81679
82145
  const provider = session.seedProvider ?? "gemini";
81680
- const probeRows = app.db.select().from(discoveryProbes).where(eq62(discoveryProbes.sessionId, session.id)).all();
82146
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
81681
82147
  const extract = opts.harvestSearchQueries;
81682
82148
  const probesWithQueries = probeRows.map((row) => {
81683
82149
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -81688,7 +82154,7 @@ async function discoveryRoutes(app, opts) {
81688
82154
  return { searchQueries: [] };
81689
82155
  }
81690
82156
  });
81691
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq62(queries.projectId, project.id)).all().map((r) => r.query);
82157
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query);
81692
82158
  const anchorTerms = buildHarvestAnchorTerms(
81693
82159
  [session.icpDescription ?? "", ...trackedQueries],
81694
82160
  effectiveDomains(project)
@@ -81735,12 +82201,12 @@ async function discoveryRoutes(app, opts) {
81735
82201
  "/projects/:name/discover/sessions/:id/promote",
81736
82202
  async (request, reply) => {
81737
82203
  const project = resolveProject(app.db, request.params.name);
81738
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82204
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81739
82205
  if (!session || session.projectId !== project.id) {
81740
82206
  throw notFound("Discovery session", request.params.id);
81741
82207
  }
81742
- const probeRows = app.db.select().from(discoveryProbes).where(eq62(discoveryProbes.sessionId, session.id)).all();
81743
- const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq62(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
82208
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
82209
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
81744
82210
  const seenCompetitors = new Set(existingCompetitors);
81745
82211
  const cited = /* @__PURE__ */ new Set();
81746
82212
  const aspirational = /* @__PURE__ */ new Set();
@@ -81769,7 +82235,7 @@ async function discoveryRoutes(app, opts) {
81769
82235
  );
81770
82236
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
81771
82237
  const project = resolveProject(app.db, request.params.name);
81772
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82238
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81773
82239
  if (!session || session.projectId !== project.id) {
81774
82240
  throw notFound("Discovery session", request.params.id);
81775
82241
  }
@@ -81792,7 +82258,7 @@ async function discoveryRoutes(app, opts) {
81792
82258
  const bucketSet = new Set(buckets);
81793
82259
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
81794
82260
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
81795
- const probeRows = app.db.select().from(discoveryProbes).where(eq62(discoveryProbes.sessionId, session.id)).all();
82261
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
81796
82262
  const candidateQueries = /* @__PURE__ */ new Set();
81797
82263
  for (const probe of probeRows) {
81798
82264
  if (!probe.bucket) continue;
@@ -81800,7 +82266,7 @@ async function discoveryRoutes(app, opts) {
81800
82266
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
81801
82267
  }
81802
82268
  const existingQueries = new Set(
81803
- app.db.select({ query: queries.query }).from(queries).where(eq62(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
82269
+ app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
81804
82270
  );
81805
82271
  const promotedQueries = [];
81806
82272
  const skippedQueries = [];
@@ -81816,7 +82282,7 @@ async function discoveryRoutes(app, opts) {
81816
82282
  const skippedCompetitors = [];
81817
82283
  if (includeCompetitors) {
81818
82284
  const existingCompetitors = new Set(
81819
- app.db.select({ domain: competitors.domain }).from(competitors).where(eq62(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
82285
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
81820
82286
  );
81821
82287
  const competitorMap = parseCompetitorMap(session.competitorMap);
81822
82288
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -81935,7 +82401,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
81935
82401
 
81936
82402
  // ../api-routes/src/discovery/orchestrate.ts
81937
82403
  import crypto47 from "crypto";
81938
- import { eq as eq63 } from "drizzle-orm";
82404
+ import { eq as eq64 } from "drizzle-orm";
81939
82405
  var DEFAULT_MAX_PROBES = 100;
81940
82406
  var ABSOLUTE_MAX_PROBES = 500;
81941
82407
  function classifyProbeBucket(input) {
@@ -82022,7 +82488,7 @@ async function executeDiscovery(opts) {
82022
82488
  status: DiscoverySessionStatuses.seeding,
82023
82489
  dedupThreshold,
82024
82490
  startedAt
82025
- }).where(eq63(discoverySessions.id, opts.sessionId)).run();
82491
+ }).where(eq64(discoverySessions.id, opts.sessionId)).run();
82026
82492
  const seedResult = await opts.deps.seed({
82027
82493
  project: opts.project,
82028
82494
  icpDescription: opts.icpDescription,
@@ -82083,7 +82549,7 @@ async function executeDiscovery(opts) {
82083
82549
  dedupBandPairFraction: dedupStats.bandPairFraction,
82084
82550
  dedupPairsTotal: dedupStats.pairsTotal,
82085
82551
  warning
82086
- }).where(eq63(discoverySessions.id, opts.sessionId)).run();
82552
+ }).where(eq64(discoverySessions.id, opts.sessionId)).run();
82087
82553
  const probeLocation = opts.locations?.[0];
82088
82554
  const probeResults = await mapWithConcurrency(
82089
82555
  probedCanonicals,
@@ -82137,7 +82603,7 @@ async function executeDiscovery(opts) {
82137
82603
  wastedCount: buckets["wasted-surface"],
82138
82604
  competitorMap,
82139
82605
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
82140
- }).where(eq63(discoverySessions.id, opts.sessionId)).run();
82606
+ }).where(eq64(discoverySessions.id, opts.sessionId)).run();
82141
82607
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
82142
82608
  return {
82143
82609
  buckets,
@@ -82177,7 +82643,7 @@ function markSessionFailed(db, sessionId, error) {
82177
82643
  status: DiscoverySessionStatuses.failed,
82178
82644
  error,
82179
82645
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
82180
- }).where(eq63(discoverySessions.id, sessionId)).run();
82646
+ }).where(eq64(discoverySessions.id, sessionId)).run();
82181
82647
  }
82182
82648
  function dedupeStrings(input) {
82183
82649
  const seen2 = /* @__PURE__ */ new Set();
@@ -82195,7 +82661,7 @@ function dedupeStrings(input) {
82195
82661
 
82196
82662
  // ../api-routes/src/technical-aeo.ts
82197
82663
  import crypto48 from "crypto";
82198
- import { and as and54, asc as asc11, count as count2, desc as desc27, eq as eq64, inArray as inArray24, isNotNull as isNotNull5, isNull as isNull11, lt as lt10, or as or14, sql as sql25 } from "drizzle-orm";
82664
+ import { and as and55, asc as asc11, count as count2, desc as desc28, eq as eq65, inArray as inArray25, isNotNull as isNotNull5, isNull as isNull11, lt as lt10, or as or14, sql as sql25 } from "drizzle-orm";
82199
82665
  import { alias } from "drizzle-orm/sqlite-core";
82200
82666
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
82201
82667
  ...SiteCrawlFetchedStates,
@@ -82408,8 +82874,8 @@ function parseLinkKind(value) {
82408
82874
  return allowed.data;
82409
82875
  }
82410
82876
  function linkKindFilter(linkKind) {
82411
- if (linkKind === SiteHealthLinkKinds.content) return eq64(siteCrawlEdges.isTemplate, false);
82412
- if (linkKind === SiteHealthLinkKinds.template) return eq64(siteCrawlEdges.isTemplate, true);
82877
+ if (linkKind === SiteHealthLinkKinds.content) return eq65(siteCrawlEdges.isTemplate, false);
82878
+ if (linkKind === SiteHealthLinkKinds.template) return eq65(siteCrawlEdges.isTemplate, true);
82413
82879
  return void 0;
82414
82880
  }
82415
82881
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -82514,50 +82980,50 @@ function changedFields(before, after, fields) {
82514
82980
  async function technicalAeoRoutes(app, opts) {
82515
82981
  const resolveCrawl = (projectId, runId) => {
82516
82982
  const filters = [
82517
- eq64(siteCrawlSnapshots.projectId, projectId),
82518
- eq64(runs.projectId, projectId),
82519
- eq64(runs.kind, RunKinds["site-audit"]),
82520
- inArray24(runs.status, SURFACEABLE_STATUSES),
82983
+ eq65(siteCrawlSnapshots.projectId, projectId),
82984
+ eq65(runs.projectId, projectId),
82985
+ eq65(runs.kind, RunKinds["site-audit"]),
82986
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82521
82987
  notProbeRun()
82522
82988
  ];
82523
82989
  if (runId) {
82524
- filters.push(eq64(siteCrawlSnapshots.runId, runId));
82990
+ filters.push(eq65(siteCrawlSnapshots.runId, runId));
82525
82991
  } else {
82526
- filters.push(eq64(siteCrawlSnapshots.complete, true), eq64(runs.status, RunStatuses.completed));
82992
+ filters.push(eq65(siteCrawlSnapshots.complete, true), eq65(runs.status, RunStatuses.completed));
82527
82993
  }
82528
- return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq64(siteCrawlSnapshots.runId, runs.id)).where(and54(...filters)).orderBy(desc27(siteCrawlSnapshots.createdAt), desc27(siteCrawlSnapshots.runId)).limit(1).get();
82994
+ return app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(...filters)).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get();
82529
82995
  };
82530
82996
  const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
82531
82997
  const pageInScope = (scope, selector) => {
82532
82998
  if (!selector.nodeKey && !selector.url) return null;
82533
- return app.db.select().from(siteCrawlPages).where(and54(
82534
- eq64(siteCrawlPages.projectId, scope.projectId),
82535
- eq64(siteCrawlPages.runId, scope.runId),
82536
- eq64(siteCrawlPages.attemptId, scope.attemptId),
82537
- selector.nodeKey ? eq64(siteCrawlPages.nodeKey, selector.nodeKey) : eq64(siteCrawlPages.url, selector.url)
82999
+ return app.db.select().from(siteCrawlPages).where(and55(
83000
+ eq65(siteCrawlPages.projectId, scope.projectId),
83001
+ eq65(siteCrawlPages.runId, scope.runId),
83002
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83003
+ selector.nodeKey ? eq65(siteCrawlPages.nodeKey, selector.nodeKey) : eq65(siteCrawlPages.url, selector.url)
82538
83004
  )).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
82539
83005
  };
82540
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and54(
82541
- eq64(siteCrawlPages.projectId, scope.projectId),
82542
- eq64(siteCrawlPages.runId, scope.runId),
82543
- eq64(siteCrawlPages.attemptId, scope.attemptId),
82544
- eq64(siteCrawlPages.depth, 0)
83006
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and55(
83007
+ eq65(siteCrawlPages.projectId, scope.projectId),
83008
+ eq65(siteCrawlPages.runId, scope.runId),
83009
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83010
+ eq65(siteCrawlPages.depth, 0)
82545
83011
  )).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
82546
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and54(
82547
- eq64(runs.id, runId),
82548
- eq64(runs.projectId, projectId),
82549
- eq64(runs.kind, RunKinds["site-audit"]),
82550
- inArray24(runs.status, SURFACEABLE_STATUSES),
83012
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and55(
83013
+ eq65(runs.id, runId),
83014
+ eq65(runs.projectId, projectId),
83015
+ eq65(runs.kind, RunKinds["site-audit"]),
83016
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82551
83017
  notProbeRun()
82552
83018
  )).limit(1).get());
82553
83019
  const assertKnownAuditRun = (projectId, runId) => {
82554
83020
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
82555
83021
  };
82556
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq64(siteAuditSnapshots.runId, runs.id)).where(and54(
82557
- eq64(siteAuditSnapshots.projectId, projectId),
82558
- eq64(runs.projectId, projectId),
82559
- eq64(runs.kind, RunKinds["site-audit"]),
82560
- inArray24(runs.status, SURFACEABLE_STATUSES),
83022
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
83023
+ eq65(siteAuditSnapshots.projectId, projectId),
83024
+ eq65(runs.projectId, projectId),
83025
+ eq65(runs.kind, RunKinds["site-audit"]),
83026
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82561
83027
  notProbeRun()
82562
83028
  )).limit(1).get());
82563
83029
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -82578,20 +83044,20 @@ async function technicalAeoRoutes(app, opts) {
82578
83044
  app.get("/projects/:name/technical-aeo", async (request) => {
82579
83045
  const project = resolveProject(app.db, request.params.name);
82580
83046
  const baseFilters = [
82581
- eq64(siteAuditSnapshots.projectId, project.id),
82582
- eq64(runs.projectId, project.id),
82583
- eq64(runs.kind, RunKinds["site-audit"]),
82584
- inArray24(runs.status, SURFACEABLE_STATUSES),
83047
+ eq65(siteAuditSnapshots.projectId, project.id),
83048
+ eq65(runs.projectId, project.id),
83049
+ eq65(runs.kind, RunKinds["site-audit"]),
83050
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82585
83051
  notProbeRun()
82586
83052
  ];
82587
- const targetFilters = request.query.runId ? [...baseFilters, eq64(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
82588
- const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq64(siteAuditSnapshots.runId, runs.id)).where(and54(...targetFilters)).orderBy(desc27(siteAuditSnapshots.createdAt)).limit(1).get();
83053
+ const targetFilters = request.query.runId ? [...baseFilters, eq65(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
83054
+ const latest = app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(...targetFilters)).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(1).get();
82589
83055
  if (!latest) {
82590
83056
  if (request.query.runId) throw notFound("Site audit run", request.query.runId);
82591
83057
  return emptyScore(project.name);
82592
83058
  }
82593
83059
  const snap = latest.snap;
82594
- const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq64(siteAuditSnapshots.runId, runs.id)).where(and54(...baseFilters, lt10(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc27(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
83060
+ const previous = app.db.select({ snap: siteAuditSnapshots }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(...baseFilters, lt10(siteAuditSnapshots.createdAt, snap.createdAt))).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(1).get()?.snap ?? null;
82595
83061
  const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
82596
83062
  const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
82597
83063
  return {
@@ -82618,14 +83084,14 @@ async function technicalAeoRoutes(app, opts) {
82618
83084
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
82619
83085
  const project = resolveProject(app.db, request.params.name);
82620
83086
  const targetFilters = [
82621
- eq64(siteAuditSnapshots.projectId, project.id),
82622
- eq64(runs.projectId, project.id),
82623
- eq64(runs.kind, RunKinds["site-audit"]),
82624
- inArray24(runs.status, SURFACEABLE_STATUSES),
83087
+ eq65(siteAuditSnapshots.projectId, project.id),
83088
+ eq65(runs.projectId, project.id),
83089
+ eq65(runs.kind, RunKinds["site-audit"]),
83090
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82625
83091
  notProbeRun()
82626
83092
  ];
82627
- if (request.query.runId) targetFilters.push(eq64(siteAuditSnapshots.runId, request.query.runId));
82628
- const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq64(siteAuditSnapshots.runId, runs.id)).where(and54(...targetFilters)).orderBy(desc27(siteAuditSnapshots.createdAt)).limit(1).get();
83093
+ if (request.query.runId) targetFilters.push(eq65(siteAuditSnapshots.runId, request.query.runId));
83094
+ const latest = app.db.select({ runId: siteAuditSnapshots.runId, auditedAt: siteAuditSnapshots.auditedAt }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(...targetFilters)).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(1).get();
82629
83095
  if (!latest && request.query.runId) {
82630
83096
  throw notFound("Site audit run", request.query.runId);
82631
83097
  }
@@ -82633,14 +83099,14 @@ async function technicalAeoRoutes(app, opts) {
82633
83099
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
82634
83100
  }
82635
83101
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
82636
- const conds = [eq64(siteAuditPages.projectId, project.id), eq64(siteAuditPages.runId, latest.runId)];
82637
- if (statusFilter) conds.push(eq64(siteAuditPages.status, statusFilter));
82638
- const where = and54(...conds);
83102
+ const conds = [eq65(siteAuditPages.projectId, project.id), eq65(siteAuditPages.runId, latest.runId)];
83103
+ if (statusFilter) conds.push(eq65(siteAuditPages.status, statusFilter));
83104
+ const where = and55(...conds);
82639
83105
  const totalRow = app.db.select({ value: count2() }).from(siteAuditPages).where(where).get();
82640
83106
  const total = totalRow?.value ?? 0;
82641
83107
  const limit = parsePositiveInt(request.query.limit, 100, 500);
82642
83108
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
82643
- const orderBy = request.query.sort === "score-desc" ? desc27(siteAuditPages.overallScore) : request.query.sort === "url" ? asc11(siteAuditPages.url) : asc11(siteAuditPages.overallScore);
83109
+ const orderBy = request.query.sort === "score-desc" ? desc28(siteAuditPages.overallScore) : request.query.sort === "url" ? asc11(siteAuditPages.url) : asc11(siteAuditPages.overallScore);
82644
83110
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
82645
83111
  const pages = rows.map((row) => ({
82646
83112
  url: row.url,
@@ -82659,13 +83125,13 @@ async function technicalAeoRoutes(app, opts) {
82659
83125
  auditedAt: siteAuditSnapshots.auditedAt,
82660
83126
  aggregateScore: siteAuditSnapshots.aggregateScore,
82661
83127
  pagesAudited: siteAuditSnapshots.pagesAudited
82662
- }).from(siteAuditSnapshots).innerJoin(runs, eq64(siteAuditSnapshots.runId, runs.id)).where(and54(
82663
- eq64(siteAuditSnapshots.projectId, project.id),
82664
- eq64(runs.projectId, project.id),
82665
- eq64(runs.kind, RunKinds["site-audit"]),
82666
- inArray24(runs.status, SURFACEABLE_STATUSES),
83128
+ }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
83129
+ eq65(siteAuditSnapshots.projectId, project.id),
83130
+ eq65(runs.projectId, project.id),
83131
+ eq65(runs.kind, RunKinds["site-audit"]),
83132
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82667
83133
  notProbeRun()
82668
- )).orderBy(desc27(siteAuditSnapshots.createdAt)).limit(limit).all();
83134
+ )).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(limit).all();
82669
83135
  return { project: project.name, points: rows.reverse() };
82670
83136
  });
82671
83137
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -82757,10 +83223,10 @@ async function technicalAeoRoutes(app, opts) {
82757
83223
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
82758
83224
  snapshot.rootUrl
82759
83225
  )?.nodeKey ?? null;
82760
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and54(
82761
- eq64(siteCrawlGraphLayouts.projectId, project.id),
82762
- eq64(siteCrawlGraphLayouts.runId, snapshot.runId),
82763
- eq64(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
83226
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and55(
83227
+ eq65(siteCrawlGraphLayouts.projectId, project.id),
83228
+ eq65(siteCrawlGraphLayouts.runId, snapshot.runId),
83229
+ eq65(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
82764
83230
  )).limit(1).get();
82765
83231
  if (!persistedLayout) {
82766
83232
  return {
@@ -82811,9 +83277,9 @@ async function technicalAeoRoutes(app, opts) {
82811
83277
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
82812
83278
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
82813
83279
  const graphScope = [
82814
- eq64(siteCrawlGraphNodes.projectId, project.id),
82815
- eq64(siteCrawlGraphNodes.runId, snapshot.runId),
82816
- eq64(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
83280
+ eq65(siteCrawlGraphNodes.projectId, project.id),
83281
+ eq65(siteCrawlGraphNodes.runId, snapshot.runId),
83282
+ eq65(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
82817
83283
  ];
82818
83284
  const nodeRows = app.db.select({
82819
83285
  nodeKey: siteCrawlPages.nodeKey,
@@ -82832,17 +83298,17 @@ async function technicalAeoRoutes(app, opts) {
82832
83298
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
82833
83299
  x: siteCrawlGraphNodes.x,
82834
83300
  y: siteCrawlGraphNodes.y
82835
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and54(
82836
- eq64(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
82837
- eq64(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
82838
- eq64(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
82839
- eq64(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
82840
- )).where(and54(...graphScope, lt10(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc11(siteCrawlGraphNodes.sampleRank)).all();
83301
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and55(
83302
+ eq65(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
83303
+ eq65(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
83304
+ eq65(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
83305
+ eq65(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
83306
+ )).where(and55(...graphScope, lt10(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc11(siteCrawlGraphNodes.sampleRank)).all();
82841
83307
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
82842
83308
  ...row,
82843
83309
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey })
82844
83310
  }));
82845
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq64(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
83311
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq65(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
82846
83312
  const edges = app.db.select({
82847
83313
  edgeKey: siteCrawlGraphEdges.edgeKey,
82848
83314
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -82850,22 +83316,22 @@ async function technicalAeoRoutes(app, opts) {
82850
83316
  followable: siteCrawlGraphEdges.followable,
82851
83317
  occurrences: siteCrawlGraphEdges.occurrences,
82852
83318
  isTemplate: siteCrawlGraphEdges.isTemplate
82853
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and54(
82854
- eq64(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
82855
- eq64(graphSourceNode.runId, siteCrawlGraphEdges.runId),
82856
- eq64(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
82857
- eq64(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
83319
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and55(
83320
+ eq65(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
83321
+ eq65(graphSourceNode.runId, siteCrawlGraphEdges.runId),
83322
+ eq65(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
83323
+ eq65(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
82858
83324
  lt10(graphSourceNode.sampleRank, maxNodes)
82859
- )).innerJoin(graphTargetNode, and54(
82860
- eq64(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
82861
- eq64(graphTargetNode.runId, siteCrawlGraphEdges.runId),
82862
- eq64(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
82863
- eq64(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
83325
+ )).innerJoin(graphTargetNode, and55(
83326
+ eq65(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
83327
+ eq65(graphTargetNode.runId, siteCrawlGraphEdges.runId),
83328
+ eq65(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
83329
+ eq65(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
82864
83330
  lt10(graphTargetNode.sampleRank, maxNodes)
82865
- )).where(and54(
82866
- eq64(siteCrawlGraphEdges.projectId, project.id),
82867
- eq64(siteCrawlGraphEdges.runId, snapshot.runId),
82868
- eq64(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
83331
+ )).where(and55(
83332
+ eq65(siteCrawlGraphEdges.projectId, project.id),
83333
+ eq65(siteCrawlGraphEdges.runId, snapshot.runId),
83334
+ eq65(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
82869
83335
  lt10(siteCrawlGraphEdges.sampleRank, maxEdges),
82870
83336
  graphLinkKindFilter
82871
83337
  )).orderBy(asc11(siteCrawlGraphEdges.sampleRank)).all();
@@ -82959,15 +83425,15 @@ async function technicalAeoRoutes(app, opts) {
82959
83425
  let frontier = [focus.nodeKey];
82960
83426
  let queryTruncated = false;
82961
83427
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
82962
- const candidates = app.db.select().from(siteCrawlEdges).where(and54(
82963
- eq64(siteCrawlEdges.projectId, scope.projectId),
82964
- eq64(siteCrawlEdges.runId, scope.runId),
82965
- eq64(siteCrawlEdges.attemptId, scope.attemptId),
82966
- eq64(siteCrawlEdges.internal, true),
83428
+ const candidates = app.db.select().from(siteCrawlEdges).where(and55(
83429
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83430
+ eq65(siteCrawlEdges.runId, scope.runId),
83431
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83432
+ eq65(siteCrawlEdges.internal, true),
82967
83433
  isNotNull5(siteCrawlEdges.targetNodeKey),
82968
83434
  or14(
82969
- inArray24(siteCrawlEdges.sourceNodeKey, frontier),
82970
- inArray24(siteCrawlEdges.targetNodeKey, frontier)
83435
+ inArray25(siteCrawlEdges.sourceNodeKey, frontier),
83436
+ inArray25(siteCrawlEdges.targetNodeKey, frontier)
82971
83437
  )
82972
83438
  )).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
82973
83439
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
@@ -83000,22 +83466,22 @@ async function technicalAeoRoutes(app, opts) {
83000
83466
  frontier = [...next].sort();
83001
83467
  }
83002
83468
  const nodeKeys = [...distances.keys()];
83003
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and54(
83004
- eq64(siteCrawlPages.projectId, scope.projectId),
83005
- eq64(siteCrawlPages.runId, scope.runId),
83006
- eq64(siteCrawlPages.attemptId, scope.attemptId),
83007
- inArray24(siteCrawlPages.nodeKey, nodeKeys)
83469
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
83470
+ eq65(siteCrawlPages.projectId, scope.projectId),
83471
+ eq65(siteCrawlPages.runId, scope.runId),
83472
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83473
+ inArray25(siteCrawlPages.nodeKey, nodeKeys)
83008
83474
  )).all();
83009
83475
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
83010
83476
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
83011
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and54(
83012
- eq64(siteCrawlEdges.projectId, scope.projectId),
83013
- eq64(siteCrawlEdges.runId, scope.runId),
83014
- eq64(siteCrawlEdges.attemptId, scope.attemptId),
83015
- eq64(siteCrawlEdges.internal, true),
83477
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and55(
83478
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83479
+ eq65(siteCrawlEdges.runId, scope.runId),
83480
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83481
+ eq65(siteCrawlEdges.internal, true),
83016
83482
  isNotNull5(siteCrawlEdges.targetNodeKey),
83017
- inArray24(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
83018
- inArray24(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
83483
+ inArray25(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
83484
+ inArray25(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
83019
83485
  )).limit(1).get();
83020
83486
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
83021
83487
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -83110,23 +83576,23 @@ async function technicalAeoRoutes(app, opts) {
83110
83576
  let found = fromPage.nodeKey === toPage.nodeKey;
83111
83577
  let truncated = false;
83112
83578
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
83113
- const candidates = app.db.select().from(siteCrawlEdges).where(and54(
83114
- eq64(siteCrawlEdges.projectId, scope.projectId),
83115
- eq64(siteCrawlEdges.runId, scope.runId),
83116
- eq64(siteCrawlEdges.attemptId, scope.attemptId),
83117
- eq64(siteCrawlEdges.internal, true),
83118
- eq64(siteCrawlEdges.followable, true),
83119
- eq64(siteCrawlEdges.relation, "anchor"),
83579
+ const candidates = app.db.select().from(siteCrawlEdges).where(and55(
83580
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83581
+ eq65(siteCrawlEdges.runId, scope.runId),
83582
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83583
+ eq65(siteCrawlEdges.internal, true),
83584
+ eq65(siteCrawlEdges.followable, true),
83585
+ eq65(siteCrawlEdges.relation, "anchor"),
83120
83586
  isNotNull5(siteCrawlEdges.targetNodeKey),
83121
- inArray24(siteCrawlEdges.sourceNodeKey, frontier)
83587
+ inArray25(siteCrawlEdges.sourceNodeKey, frontier)
83122
83588
  )).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
83123
83589
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
83124
83590
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
83125
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and54(
83126
- eq64(siteCrawlPages.projectId, scope.projectId),
83127
- eq64(siteCrawlPages.runId, scope.runId),
83128
- eq64(siteCrawlPages.attemptId, scope.attemptId),
83129
- inArray24(siteCrawlPages.nodeKey, candidateTargetKeys)
83591
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and55(
83592
+ eq65(siteCrawlPages.projectId, scope.projectId),
83593
+ eq65(siteCrawlPages.runId, scope.runId),
83594
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83595
+ inArray25(siteCrawlPages.nodeKey, candidateTargetKeys)
83130
83596
  )).all().map((row) => row.nodeKey));
83131
83597
  const next = /* @__PURE__ */ new Set();
83132
83598
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -83168,11 +83634,11 @@ async function technicalAeoRoutes(app, opts) {
83168
83634
  pathEdges.unshift(step.edge);
83169
83635
  pathKeys.unshift(step.nodeKey);
83170
83636
  }
83171
- const pathRows = app.db.select().from(siteCrawlPages).where(and54(
83172
- eq64(siteCrawlPages.projectId, scope.projectId),
83173
- eq64(siteCrawlPages.runId, scope.runId),
83174
- eq64(siteCrawlPages.attemptId, scope.attemptId),
83175
- inArray24(siteCrawlPages.nodeKey, pathKeys)
83637
+ const pathRows = app.db.select().from(siteCrawlPages).where(and55(
83638
+ eq65(siteCrawlPages.projectId, scope.projectId),
83639
+ eq65(siteCrawlPages.runId, scope.runId),
83640
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83641
+ inArray25(siteCrawlPages.nodeKey, pathKeys)
83176
83642
  )).all();
83177
83643
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
83178
83644
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -83201,25 +83667,25 @@ async function technicalAeoRoutes(app, opts) {
83201
83667
  throw validationError("change must be all, added, removed, or changed");
83202
83668
  }
83203
83669
  const completeSnapshotFilters = [
83204
- eq64(siteCrawlSnapshots.projectId, project.id),
83205
- eq64(runs.projectId, project.id),
83206
- eq64(runs.kind, RunKinds["site-audit"]),
83207
- eq64(runs.status, RunStatuses.completed),
83208
- eq64(siteCrawlSnapshots.complete, true),
83670
+ eq65(siteCrawlSnapshots.projectId, project.id),
83671
+ eq65(runs.projectId, project.id),
83672
+ eq65(runs.kind, RunKinds["site-audit"]),
83673
+ eq65(runs.status, RunStatuses.completed),
83674
+ eq65(siteCrawlSnapshots.complete, true),
83209
83675
  notProbeRun()
83210
83676
  ];
83211
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq64(siteCrawlSnapshots.runId, runs.id)).where(and54(...completeSnapshotFilters)).orderBy(desc27(siteCrawlSnapshots.createdAt), desc27(siteCrawlSnapshots.runId)).limit(1).get();
83677
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(...completeSnapshotFilters)).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get();
83212
83678
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
83213
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq64(siteCrawlSnapshots.runId, runs.id)).where(and54(
83679
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(
83214
83680
  ...completeSnapshotFilters,
83215
83681
  or14(
83216
83682
  lt10(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
83217
- and54(
83218
- eq64(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
83683
+ and55(
83684
+ eq65(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
83219
83685
  lt10(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
83220
83686
  )
83221
83687
  )
83222
- )).orderBy(desc27(siteCrawlSnapshots.createdAt), desc27(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
83688
+ )).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
83223
83689
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
83224
83690
  if (!afterTarget) {
83225
83691
  return { project: project.name, state: "unavailable", reason: "no-crawl", fromRunId: null, toRunId: null };
@@ -83450,18 +83916,18 @@ async function technicalAeoRoutes(app, opts) {
83450
83916
  const visibleKeys = keyRows.slice(0, limit);
83451
83917
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
83452
83918
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
83453
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and54(
83454
- eq64(siteCrawlPages.projectId, scope.projectId),
83455
- eq64(siteCrawlPages.runId, scope.runId),
83456
- eq64(siteCrawlPages.attemptId, scope.attemptId),
83457
- inArray24(siteCrawlPages.nodeKey, pageKeys)
83919
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
83920
+ eq65(siteCrawlPages.projectId, scope.projectId),
83921
+ eq65(siteCrawlPages.runId, scope.runId),
83922
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83923
+ inArray25(siteCrawlPages.nodeKey, pageKeys)
83458
83924
  )).all();
83459
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and54(
83460
- eq64(siteCrawlEdges.projectId, scope.projectId),
83461
- eq64(siteCrawlEdges.runId, scope.runId),
83462
- eq64(siteCrawlEdges.attemptId, scope.attemptId),
83463
- eq64(siteCrawlEdges.internal, true),
83464
- inArray24(siteCrawlEdges.edgeKey, linkKeys)
83925
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and55(
83926
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83927
+ eq65(siteCrawlEdges.runId, scope.runId),
83928
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83929
+ eq65(siteCrawlEdges.internal, true),
83930
+ inArray25(siteCrawlEdges.edgeKey, linkKeys)
83465
83931
  )).all();
83466
83932
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
83467
83933
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -83586,30 +84052,30 @@ async function technicalAeoRoutes(app, opts) {
83586
84052
  return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, healthStateFilter: null, pages: [] };
83587
84053
  }
83588
84054
  const filters = [
83589
- eq64(siteCrawlPages.projectId, project.id),
83590
- eq64(siteCrawlPages.runId, snapshot.runId),
83591
- eq64(siteCrawlPages.attemptId, snapshot.attemptId)
84055
+ eq65(siteCrawlPages.projectId, project.id),
84056
+ eq65(siteCrawlPages.runId, snapshot.runId),
84057
+ eq65(siteCrawlPages.attemptId, snapshot.attemptId)
83592
84058
  ];
83593
- if (request.query.nodeKey) filters.push(eq64(siteCrawlPages.nodeKey, request.query.nodeKey));
84059
+ if (request.query.nodeKey) filters.push(eq65(siteCrawlPages.nodeKey, request.query.nodeKey));
83594
84060
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
83595
- if (inventoryEligible != null) filters.push(eq64(siteCrawlPages.inventoryEligible, inventoryEligible));
83596
- if (request.query.fetchState) filters.push(eq64(siteCrawlPages.fetchState, request.query.fetchState));
83597
- if (request.query.indexabilityState) filters.push(eq64(siteCrawlPages.indexabilityState, request.query.indexabilityState));
83598
- if (request.query.auditState) filters.push(eq64(siteCrawlPages.auditState, request.query.auditState));
84061
+ if (inventoryEligible != null) filters.push(eq65(siteCrawlPages.inventoryEligible, inventoryEligible));
84062
+ if (request.query.fetchState) filters.push(eq65(siteCrawlPages.fetchState, request.query.fetchState));
84063
+ if (request.query.indexabilityState) filters.push(eq65(siteCrawlPages.indexabilityState, request.query.indexabilityState));
84064
+ if (request.query.auditState) filters.push(eq65(siteCrawlPages.auditState, request.query.auditState));
83599
84065
  const healthState = parseSiteHealthState(request.query.healthState);
83600
84066
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
83601
84067
  const offset = decodeCursor(request.query.cursor);
83602
- const orderBy = request.query.sort === "score-desc" ? [desc27(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)];
84068
+ const orderBy = request.query.sort === "score-desc" ? [desc28(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)];
83603
84069
  let healthStateFilter = null;
83604
84070
  if (healthState) {
83605
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and54(
83606
- eq64(siteCrawlPages.projectId, project.id),
83607
- eq64(siteCrawlPages.runId, snapshot.runId),
83608
- eq64(siteCrawlPages.attemptId, snapshot.attemptId),
84071
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and55(
84072
+ eq65(siteCrawlPages.projectId, project.id),
84073
+ eq65(siteCrawlPages.runId, snapshot.runId),
84074
+ eq65(siteCrawlPages.attemptId, snapshot.attemptId),
83609
84075
  isNull11(siteCrawlPages.healthState)
83610
84076
  )).limit(1).get();
83611
84077
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
83612
- if (healthStateFilter === "applied") filters.push(eq64(siteCrawlPages.healthState, healthState));
84078
+ if (healthStateFilter === "applied") filters.push(eq65(siteCrawlPages.healthState, healthState));
83613
84079
  }
83614
84080
  if (healthStateFilter === "unavailable-legacy-scan") {
83615
84081
  return {
@@ -83622,7 +84088,7 @@ async function technicalAeoRoutes(app, opts) {
83622
84088
  pages: []
83623
84089
  };
83624
84090
  }
83625
- const where = and54(...filters);
84091
+ const where = and55(...filters);
83626
84092
  const total = app.db.select({ value: count2() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
83627
84093
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
83628
84094
  const nextOffset = offset + rows.length;
@@ -83653,10 +84119,10 @@ async function technicalAeoRoutes(app, opts) {
83653
84119
  url: siteCrawlPages.url,
83654
84120
  inventoryEligible: siteCrawlPages.inventoryEligible,
83655
84121
  fetchState: siteCrawlPages.fetchState
83656
- }).from(siteCrawlPages).where(and54(
83657
- eq64(siteCrawlPages.projectId, project.id),
83658
- eq64(siteCrawlPages.runId, snapshot.runId),
83659
- eq64(siteCrawlPages.attemptId, snapshot.attemptId)
84122
+ }).from(siteCrawlPages).where(and55(
84123
+ eq65(siteCrawlPages.projectId, project.id),
84124
+ eq65(siteCrawlPages.runId, snapshot.runId),
84125
+ eq65(siteCrawlPages.attemptId, snapshot.attemptId)
83660
84126
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
83661
84127
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
83662
84128
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -83735,17 +84201,17 @@ async function technicalAeoRoutes(app, opts) {
83735
84201
  };
83736
84202
  }
83737
84203
  const filters = [
83738
- eq64(siteCrawlEdges.projectId, project.id),
83739
- eq64(siteCrawlEdges.runId, snapshot.runId),
83740
- eq64(siteCrawlEdges.attemptId, snapshot.attemptId),
83741
- eq64(siteCrawlEdges.internal, true),
84204
+ eq65(siteCrawlEdges.projectId, project.id),
84205
+ eq65(siteCrawlEdges.runId, snapshot.runId),
84206
+ eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
84207
+ eq65(siteCrawlEdges.internal, true),
83742
84208
  linkKindFilter(linkKind)
83743
84209
  ];
83744
- if (request.query.sourceUrl) filters.push(eq64(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
83745
- if (request.query.targetUrl) filters.push(eq64(siteCrawlEdges.targetUrl, request.query.targetUrl));
84210
+ if (request.query.sourceUrl) filters.push(eq65(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
84211
+ if (request.query.targetUrl) filters.push(eq65(siteCrawlEdges.targetUrl, request.query.targetUrl));
83746
84212
  const followable = parseBoolean2(request.query.followable);
83747
- if (followable != null) filters.push(eq64(siteCrawlEdges.followable, followable));
83748
- const where = and54(...filters);
84213
+ if (followable != null) filters.push(eq65(siteCrawlEdges.followable, followable));
84214
+ const where = and55(...filters);
83749
84215
  const total = app.db.select({ value: count2() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
83750
84216
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
83751
84217
  const offset = decodeCursor(request.query.cursor);
@@ -83803,17 +84269,17 @@ async function technicalAeoRoutes(app, opts) {
83803
84269
  };
83804
84270
  }
83805
84271
  const scope = [
83806
- eq64(siteCrawlEdges.projectId, project.id),
83807
- eq64(siteCrawlEdges.runId, snapshot.runId),
83808
- eq64(siteCrawlEdges.attemptId, snapshot.attemptId),
83809
- eq64(siteCrawlEdges.internal, true),
84272
+ eq65(siteCrawlEdges.projectId, project.id),
84273
+ eq65(siteCrawlEdges.runId, snapshot.runId),
84274
+ eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
84275
+ eq65(siteCrawlEdges.internal, true),
83810
84276
  linkKindFilter(linkKind)
83811
84277
  ];
83812
- const inboundMatch = request.query.nodeKey && request.query.url ? or14(eq64(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq64(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq64(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq64(siteCrawlEdges.targetUrl, request.query.url);
83813
- const outboundMatch = request.query.nodeKey && request.query.url ? or14(eq64(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq64(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq64(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq64(siteCrawlEdges.sourceUrl, request.query.url);
84278
+ const inboundMatch = request.query.nodeKey && request.query.url ? or14(eq65(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq65(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq65(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq65(siteCrawlEdges.targetUrl, request.query.url);
84279
+ const outboundMatch = request.query.nodeKey && request.query.url ? or14(eq65(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq65(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq65(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq65(siteCrawlEdges.sourceUrl, request.query.url);
83814
84280
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
83815
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and54(...scope, inboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
83816
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and54(...scope, outboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
84281
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, inboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
84282
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, outboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
83817
84283
  return {
83818
84284
  project: project.name,
83819
84285
  hasCrawlData: true,
@@ -83846,11 +84312,11 @@ async function technicalAeoRoutes(app, opts) {
83846
84312
  if (!snapshot.attemptId) {
83847
84313
  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: [] };
83848
84314
  }
83849
- const where = and54(
83850
- eq64(siteCrawlFindings.projectId, project.id),
83851
- eq64(siteCrawlFindings.runId, snapshot.runId),
83852
- eq64(siteCrawlFindings.attemptId, snapshot.attemptId),
83853
- eq64(siteCrawlFindings.findingType, "dead-link"),
84315
+ const where = and55(
84316
+ eq65(siteCrawlFindings.projectId, project.id),
84317
+ eq65(siteCrawlFindings.runId, snapshot.runId),
84318
+ eq65(siteCrawlFindings.attemptId, snapshot.attemptId),
84319
+ eq65(siteCrawlFindings.findingType, "dead-link"),
83854
84320
  // Belt and braces on the client-facing surface: a row without a status
83855
84321
  // code is not evidence of a broken link, and this response is where such
83856
84322
  // a row would be read as one. Migration 140 removed every stored row that
@@ -83899,19 +84365,19 @@ async function technicalAeoRoutes(app, opts) {
83899
84365
  createdAt: runs.createdAt,
83900
84366
  startedAt: runs.startedAt,
83901
84367
  finishedAt: runs.finishedAt
83902
- }).from(runs).where(and54(
83903
- eq64(runs.projectId, project.id),
83904
- eq64(runs.kind, RunKinds["site-audit"]),
84368
+ }).from(runs).where(and55(
84369
+ eq65(runs.projectId, project.id),
84370
+ eq65(runs.kind, RunKinds["site-audit"]),
83905
84371
  notProbeRun()
83906
- )).orderBy(desc27(runs.createdAt), desc27(runs.id)).limit(limit).all();
84372
+ )).orderBy(desc28(runs.createdAt), desc28(runs.id)).limit(limit).all();
83907
84373
  const runIds = rows.map((row) => row.runId);
83908
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq64(siteCrawlSnapshots.runId, runs.id)).where(and54(
83909
- eq64(siteCrawlSnapshots.projectId, project.id),
83910
- eq64(runs.projectId, project.id),
83911
- eq64(runs.kind, RunKinds["site-audit"]),
83912
- inArray24(runs.status, SURFACEABLE_STATUSES),
84374
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(
84375
+ eq65(siteCrawlSnapshots.projectId, project.id),
84376
+ eq65(runs.projectId, project.id),
84377
+ eq65(runs.kind, RunKinds["site-audit"]),
84378
+ inArray25(runs.status, SURFACEABLE_STATUSES),
83913
84379
  notProbeRun(),
83914
- inArray24(siteCrawlSnapshots.runId, runIds)
84380
+ inArray25(siteCrawlSnapshots.runId, runIds)
83915
84381
  )).all().map((row) => row.runId));
83916
84382
  return {
83917
84383
  project: project.name,
@@ -83924,21 +84390,21 @@ async function technicalAeoRoutes(app, opts) {
83924
84390
  });
83925
84391
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
83926
84392
  const project = resolveProject(app.db, request.params.name);
83927
- const run = app.db.select().from(runs).where(and54(
83928
- eq64(runs.id, request.params.runId),
83929
- eq64(runs.projectId, project.id),
83930
- eq64(runs.kind, RunKinds["site-audit"]),
84393
+ const run = app.db.select().from(runs).where(and55(
84394
+ eq65(runs.id, request.params.runId),
84395
+ eq65(runs.projectId, project.id),
84396
+ eq65(runs.kind, RunKinds["site-audit"]),
83931
84397
  notProbeRun()
83932
84398
  )).get();
83933
84399
  if (!run) throw notFound("Site audit run", request.params.runId);
83934
- const attempt = app.db.select().from(siteCrawlAttempts).where(and54(
83935
- eq64(siteCrawlAttempts.projectId, project.id),
83936
- eq64(siteCrawlAttempts.runId, run.id)
83937
- )).orderBy(desc27(siteCrawlAttempts.attemptNumber), desc27(siteCrawlAttempts.updatedAt)).limit(1).get();
83938
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and54(
83939
- eq64(siteCrawlGraphLayouts.projectId, project.id),
83940
- eq64(siteCrawlGraphLayouts.runId, run.id),
83941
- eq64(siteCrawlGraphLayouts.attemptId, attempt.id)
84400
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and55(
84401
+ eq65(siteCrawlAttempts.projectId, project.id),
84402
+ eq65(siteCrawlAttempts.runId, run.id)
84403
+ )).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
84404
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and55(
84405
+ eq65(siteCrawlGraphLayouts.projectId, project.id),
84406
+ eq65(siteCrawlGraphLayouts.runId, run.id),
84407
+ eq65(siteCrawlGraphLayouts.attemptId, attempt.id)
83942
84408
  )).get() : void 0;
83943
84409
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
83944
84410
  return {
@@ -83969,19 +84435,19 @@ async function technicalAeoRoutes(app, opts) {
83969
84435
  };
83970
84436
  });
83971
84437
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
83972
- const project = tx.select().from(projects).where(eq64(projects.name, request.params.name)).get();
84438
+ const project = tx.select().from(projects).where(eq65(projects.name, request.params.name)).get();
83973
84439
  if (!project) throw notFound("Project", request.params.name);
83974
- const run = tx.select().from(runs).where(and54(
83975
- eq64(runs.id, request.params.runId),
83976
- eq64(runs.projectId, project.id),
83977
- eq64(runs.kind, RunKinds["site-audit"]),
84440
+ const run = tx.select().from(runs).where(and55(
84441
+ eq65(runs.id, request.params.runId),
84442
+ eq65(runs.projectId, project.id),
84443
+ eq65(runs.kind, RunKinds["site-audit"]),
83978
84444
  notProbeRun()
83979
84445
  )).get();
83980
84446
  if (!run) throw notFound("Site audit run", request.params.runId);
83981
- const attempt = tx.select().from(siteCrawlAttempts).where(and54(
83982
- eq64(siteCrawlAttempts.projectId, project.id),
83983
- eq64(siteCrawlAttempts.runId, run.id)
83984
- )).orderBy(desc27(siteCrawlAttempts.attemptNumber), desc27(siteCrawlAttempts.updatedAt)).limit(1).get();
84447
+ const attempt = tx.select().from(siteCrawlAttempts).where(and55(
84448
+ eq65(siteCrawlAttempts.projectId, project.id),
84449
+ eq65(siteCrawlAttempts.runId, run.id)
84450
+ )).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
83985
84451
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
83986
84452
  const base = {
83987
84453
  project: project.name,
@@ -83994,11 +84460,11 @@ async function technicalAeoRoutes(app, opts) {
83994
84460
  if (!attempt) {
83995
84461
  return { ...base, pagesAudited: 0, examples: [] };
83996
84462
  }
83997
- const auditedWhere = and54(
83998
- eq64(siteCrawlPages.projectId, project.id),
83999
- eq64(siteCrawlPages.runId, run.id),
84000
- eq64(siteCrawlPages.attemptId, attempt.id),
84001
- eq64(siteCrawlPages.auditState, "success")
84463
+ const auditedWhere = and55(
84464
+ eq65(siteCrawlPages.projectId, project.id),
84465
+ eq65(siteCrawlPages.runId, run.id),
84466
+ eq65(siteCrawlPages.attemptId, attempt.id),
84467
+ eq65(siteCrawlPages.auditState, "success")
84002
84468
  );
84003
84469
  const pagesAudited = tx.select({ value: count2() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
84004
84470
  if (state !== "collecting") {
@@ -84009,7 +84475,7 @@ async function technicalAeoRoutes(app, opts) {
84009
84475
  url: siteCrawlPages.url,
84010
84476
  auditScore: siteCrawlPages.auditScore,
84011
84477
  auditFields: siteCrawlPages.auditFields
84012
- }).from(siteCrawlPages).where(and54(
84478
+ }).from(siteCrawlPages).where(and55(
84013
84479
  auditedWhere,
84014
84480
  isNotNull5(siteCrawlPages.auditScore),
84015
84481
  lt10(siteCrawlPages.auditScore, 70)
@@ -84045,13 +84511,13 @@ async function technicalAeoRoutes(app, opts) {
84045
84511
  status: runs.status,
84046
84512
  identityKey: siteCrawlRunRequests.identityKey,
84047
84513
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
84048
- }).from(runs).leftJoin(siteCrawlRunRequests, and54(
84049
- eq64(siteCrawlRunRequests.projectId, runs.projectId),
84050
- eq64(siteCrawlRunRequests.runId, runs.id)
84051
- )).where(and54(
84052
- eq64(runs.projectId, project.id),
84053
- eq64(runs.kind, RunKinds["site-audit"]),
84054
- inArray24(runs.status, [RunStatuses.queued, RunStatuses.running])
84514
+ }).from(runs).leftJoin(siteCrawlRunRequests, and55(
84515
+ eq65(siteCrawlRunRequests.projectId, runs.projectId),
84516
+ eq65(siteCrawlRunRequests.runId, runs.id)
84517
+ )).where(and55(
84518
+ eq65(runs.projectId, project.id),
84519
+ eq65(runs.kind, RunKinds["site-audit"]),
84520
+ inArray25(runs.status, [RunStatuses.queued, RunStatuses.running])
84055
84521
  )).get();
84056
84522
  if (existing) {
84057
84523
  if (existing.identityKey === identityKey) {
@@ -84105,7 +84571,7 @@ async function technicalAeoRoutes(app, opts) {
84105
84571
  // ../api-routes/src/research.ts
84106
84572
  import crypto49 from "crypto";
84107
84573
  import { z as z5 } from "zod";
84108
- import { and as and55, count as count3, desc as desc28, eq as eq65, gte as gte17, lt as lt11, or as or15, sql as sql26 } from "drizzle-orm";
84574
+ import { and as and56, count as count3, desc as desc29, eq as eq66, gte as gte17, lt as lt11, or as or15, sql as sql26 } from "drizzle-orm";
84109
84575
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
84110
84576
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
84111
84577
  async function researchRoutes(app, opts) {
@@ -84123,7 +84589,7 @@ async function researchRoutes(app, opts) {
84123
84589
  const input = parsed.data;
84124
84590
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
84125
84591
  const requestHash2 = directResearchRequestHash(input);
84126
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and55(eq65(researchRuns.projectId, project.id), eq65(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
84592
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and56(eq66(researchRuns.projectId, project.id), eq66(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
84127
84593
  if (existingReceipt) {
84128
84594
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
84129
84595
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -84155,7 +84621,7 @@ async function researchRoutes(app, opts) {
84155
84621
  const initiatedBy = researchPrincipal(request);
84156
84622
  const decision = app.db.transaction((tx) => {
84157
84623
  if (input.idempotencyKey) {
84158
- const existing = tx.select().from(researchRuns).where(and55(eq65(researchRuns.projectId, project.id), eq65(researchRuns.idempotencyKey, input.idempotencyKey))).get();
84624
+ const existing = tx.select().from(researchRuns).where(and56(eq66(researchRuns.projectId, project.id), eq66(researchRuns.idempotencyKey, input.idempotencyKey))).get();
84159
84625
  if (existing) {
84160
84626
  assertSameDirectResearchRequest(existing, input, requestHash2);
84161
84627
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -84163,8 +84629,8 @@ async function researchRoutes(app, opts) {
84163
84629
  }
84164
84630
  if (initiatedBy?.limited) {
84165
84631
  const { start, end, date } = utcDayBounds(now);
84166
- const used = tx.select({ value: count3() }).from(researchRuns).where(and55(
84167
- eq65(researchRuns.projectId, project.id),
84632
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
84633
+ eq66(researchRuns.projectId, project.id),
84168
84634
  gte17(researchRuns.createdAt, start),
84169
84635
  lt11(researchRuns.createdAt, end),
84170
84636
  sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -84195,15 +84661,15 @@ async function researchRoutes(app, opts) {
84195
84661
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
84196
84662
  const requestHash2 = researchBatchRequestHash(input);
84197
84663
  const receiptKeys = input.runs.map((_run, index2) => batchChildReceiptKey(input.idempotencyKey, index2));
84198
- const firstReceipt = app.db.select().from(researchRuns).where(and55(
84199
- eq65(researchRuns.projectId, project.id),
84200
- eq65(researchRuns.idempotencyKey, receiptKeys[0])
84664
+ const firstReceipt = app.db.select().from(researchRuns).where(and56(
84665
+ eq66(researchRuns.projectId, project.id),
84666
+ eq66(researchRuns.idempotencyKey, receiptKeys[0])
84201
84667
  )).get();
84202
84668
  if (firstReceipt) {
84203
84669
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
84204
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and55(
84205
- eq65(researchRuns.projectId, project.id),
84206
- eq65(researchRuns.idempotencyKey, key)
84670
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and56(
84671
+ eq66(researchRuns.projectId, project.id),
84672
+ eq66(researchRuns.idempotencyKey, key)
84207
84673
  )).get());
84208
84674
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
84209
84675
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -84218,8 +84684,8 @@ async function researchRoutes(app, opts) {
84218
84684
  const decision = app.db.transaction((tx) => {
84219
84685
  if (initiatedBy?.limited) {
84220
84686
  const { start, end, date } = utcDayBounds(now);
84221
- const used = tx.select({ value: count3() }).from(researchRuns).where(and55(
84222
- eq65(researchRuns.projectId, project.id),
84687
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
84688
+ eq66(researchRuns.projectId, project.id),
84223
84689
  gte17(researchRuns.createdAt, start),
84224
84690
  lt11(researchRuns.createdAt, end),
84225
84691
  sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -84273,10 +84739,10 @@ async function researchRoutes(app, opts) {
84273
84739
  const requested = Number.parseInt(request.query.limit ?? "", 10);
84274
84740
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
84275
84741
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
84276
- const rows = app.db.select().from(researchRuns).where(and55(
84277
- eq65(researchRuns.projectId, project.id),
84278
- cursor ? or15(lt11(researchRuns.createdAt, cursor.createdAt), and55(eq65(researchRuns.createdAt, cursor.createdAt), lt11(researchRuns.id, cursor.id))) : void 0
84279
- )).orderBy(desc28(researchRuns.createdAt), desc28(researchRuns.id)).limit(limit + 1).all();
84742
+ const rows = app.db.select().from(researchRuns).where(and56(
84743
+ eq66(researchRuns.projectId, project.id),
84744
+ cursor ? or15(lt11(researchRuns.createdAt, cursor.createdAt), and56(eq66(researchRuns.createdAt, cursor.createdAt), lt11(researchRuns.id, cursor.id))) : void 0
84745
+ )).orderBy(desc29(researchRuns.createdAt), desc29(researchRuns.id)).limit(limit + 1).all();
84280
84746
  const runs2 = rows.slice(0, limit).map(serializeRun);
84281
84747
  const last = runs2.at(-1);
84282
84748
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -84402,9 +84868,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
84402
84868
  return stored;
84403
84869
  }
84404
84870
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
84405
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and55(
84406
- eq65(measurementQueryTemplates.projectId, projectId),
84407
- eq65(measurementQueryTemplates.id, selection.templateId)
84871
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and56(
84872
+ eq66(measurementQueryTemplates.projectId, projectId),
84873
+ eq66(measurementQueryTemplates.id, selection.templateId)
84408
84874
  )).get();
84409
84875
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
84410
84876
  const templateVersion = savedTemplate.updatedAt;
@@ -84413,7 +84879,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
84413
84879
  }
84414
84880
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
84415
84881
  if (bindingLocation) {
84416
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq65(projects.id, projectId)).get();
84882
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq66(projects.id, projectId)).get();
84417
84883
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
84418
84884
  }
84419
84885
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -84426,9 +84892,9 @@ function classifyResearchQueries(project, plan, queries2) {
84426
84892
  return queries2.map((query) => classifier?.classify(query) ?? null);
84427
84893
  }
84428
84894
  function getDetail(app, projectId, id) {
84429
- const row = app.db.select().from(researchRuns).where(and55(eq65(researchRuns.id, id), eq65(researchRuns.projectId, projectId))).get();
84895
+ const row = app.db.select().from(researchRuns).where(and56(eq66(researchRuns.id, id), eq66(researchRuns.projectId, projectId))).get();
84430
84896
  if (!row) throw notFound("Research run", id);
84431
- const queries2 = app.db.select().from(researchRunQueries).where(eq65(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
84897
+ const queries2 = app.db.select().from(researchRunQueries).where(eq66(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
84432
84898
  return { ...serializeRun(row), queries: queries2 };
84433
84899
  }
84434
84900
  function serializeRun(row) {
@@ -84458,7 +84924,7 @@ function utcDayBounds(now) {
84458
84924
 
84459
84925
  // ../api-routes/src/simple-measurement-definitions.ts
84460
84926
  import crypto50 from "crypto";
84461
- import { and as and56, eq as eq66 } from "drizzle-orm";
84927
+ import { and as and57, eq as eq67 } from "drizzle-orm";
84462
84928
  function captureSimpleMeasurementDefinition(db, input) {
84463
84929
  return db.transaction((tx) => {
84464
84930
  const run = tx.select({
@@ -84466,7 +84932,7 @@ function captureSimpleMeasurementDefinition(db, input) {
84466
84932
  trigger: runs.trigger,
84467
84933
  status: runs.status,
84468
84934
  measurementPlanVersionId: runs.measurementPlanVersionId
84469
- }).from(runs).where(and56(eq66(runs.projectId, input.projectId), eq66(runs.id, input.runId))).get();
84935
+ }).from(runs).where(and57(eq67(runs.projectId, input.projectId), eq67(runs.id, input.runId))).get();
84470
84936
  if (!run) throw notFound("Run", input.runId);
84471
84937
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
84472
84938
  return null;
@@ -84476,7 +84942,7 @@ function captureSimpleMeasurementDefinition(db, input) {
84476
84942
  if (definition.queries.some((query) => query.queryClass !== (classifier?.classify(query.queryText) ?? null))) {
84477
84943
  throw validationError("Query classes must match the captured identity and text.");
84478
84944
  }
84479
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and56(eq66(simpleMeasurementDefinitions.projectId, input.projectId), eq66(simpleMeasurementDefinitions.runId, input.runId))).get();
84945
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and57(eq67(simpleMeasurementDefinitions.projectId, input.projectId), eq67(simpleMeasurementDefinitions.runId, input.runId))).get();
84480
84946
  if (existing) {
84481
84947
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
84482
84948
  const replayDefinition = {
@@ -84497,15 +84963,15 @@ function captureSimpleMeasurementDefinition(db, input) {
84497
84963
  if (run.status !== RunStatuses.running) {
84498
84964
  throw validationError("Only a running run can capture a new measurement definition.");
84499
84965
  }
84500
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq66(querySnapshots.runId, input.runId)).limit(1).get()) {
84966
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq67(querySnapshots.runId, input.runId)).limit(1).get()) {
84501
84967
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
84502
84968
  }
84503
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq66(queries.projectId, input.projectId)).all().map((query) => query.id));
84969
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq67(queries.projectId, input.projectId)).all().map((query) => query.id));
84504
84970
  if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
84505
84971
  throw validationError("Every captured query must belong to the run project.");
84506
84972
  }
84507
84973
  if (definition.competitors !== void 0) {
84508
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq66(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
84974
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq67(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
84509
84975
  const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
84510
84976
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
84511
84977
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
@@ -85449,10 +85915,10 @@ var log = createLogger("IntelligenceService");
85449
85915
  var runChronologyKey = sql27`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
85450
85916
  function analyzableRunPredicates(projectId) {
85451
85917
  return [
85452
- eq67(runs.projectId, projectId),
85918
+ eq68(runs.projectId, projectId),
85453
85919
  // Only this kind writes query_snapshots.
85454
- eq67(runs.kind, RunKinds["answer-visibility"]),
85455
- or16(eq67(runs.status, RunStatuses.completed), eq67(runs.status, RunStatuses.partial)),
85920
+ eq68(runs.kind, RunKinds["answer-visibility"]),
85921
+ or16(eq68(runs.status, RunStatuses.completed), eq68(runs.status, RunStatuses.partial)),
85456
85922
  // Defensive: RunCoordinator already skips probes before analyzeAndPersist
85457
85923
  // is called, but a future call site invoking it directly for a probe must
85458
85924
  // still not pollute the intelligence window.
@@ -85497,16 +85963,16 @@ var IntelligenceService = class {
85497
85963
  * Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
85498
85964
  */
85499
85965
  analyzeAndPersist(runId, projectId) {
85500
- const currentRunRecord = this.db.select().from(runs).where(and57(eq67(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
85966
+ const currentRunRecord = this.db.select().from(runs).where(and58(eq68(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
85501
85967
  if (!currentRunRecord) {
85502
85968
  log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
85503
85969
  return null;
85504
85970
  }
85505
85971
  const currentLocation = currentRunRecord.location ?? null;
85506
85972
  const recentRuns = this.db.select().from(runs).where(
85507
- and57(
85973
+ and58(
85508
85974
  ...analyzableRunPredicates(projectId),
85509
- currentLocation === null ? isNull12(runs.location) : eq67(runs.location, currentLocation),
85975
+ currentLocation === null ? isNull12(runs.location) : eq68(runs.location, currentLocation),
85510
85976
  // Never look forward. The window is this run's own past, so
85511
85977
  // re-analyzing a historical run compares it against its true
85512
85978
  // predecessor rather than against sweeps that came after it.
@@ -85514,10 +85980,10 @@ var IntelligenceService = class {
85514
85980
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
85515
85981
  // Index seek on idx_snapshots_run, short-circuited on the first row.
85516
85982
  exists(
85517
- this.db.select({ one: sql27`1` }).from(querySnapshots).where(eq67(querySnapshots.runId, runs.id))
85983
+ this.db.select({ one: sql27`1` }).from(querySnapshots).where(eq68(querySnapshots.runId, runs.id))
85518
85984
  )
85519
85985
  )
85520
- ).orderBy(desc29(runs.finishedAt), desc29(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
85986
+ ).orderBy(desc30(runs.finishedAt), desc30(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
85521
85987
  const trackedCompetitors = this.loadTrackedCompetitors(projectId);
85522
85988
  const currentRun = this.buildRunData(
85523
85989
  runId,
@@ -85589,7 +86055,7 @@ var IntelligenceService = class {
85589
86055
  * Returns the persisted insights so the coordinator can count critical/high.
85590
86056
  */
85591
86057
  analyzeAndPersistGbp(runId, projectId) {
85592
- const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq67(runs.id, runId)).get();
86058
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq68(runs.id, runId)).get();
85593
86059
  if (!runRow) {
85594
86060
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
85595
86061
  this.persistGbpInsights(runId, projectId, [], []);
@@ -85597,9 +86063,9 @@ var IntelligenceService = class {
85597
86063
  }
85598
86064
  const windowStart = runRow.startedAt ?? runRow.createdAt;
85599
86065
  const windowEnd = runRow.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
85600
- const selected = this.db.select().from(gbpLocations).where(and57(
85601
- eq67(gbpLocations.projectId, projectId),
85602
- eq67(gbpLocations.selected, true),
86066
+ const selected = this.db.select().from(gbpLocations).where(and58(
86067
+ eq68(gbpLocations.projectId, projectId),
86068
+ eq68(gbpLocations.selected, true),
85603
86069
  gte18(gbpLocations.syncedAt, windowStart),
85604
86070
  lte15(gbpLocations.syncedAt, windowEnd)
85605
86071
  )).all();
@@ -85634,12 +86100,12 @@ var IntelligenceService = class {
85634
86100
  }
85635
86101
  /** Build the per-location signal bundle the GBP analyzer consumes. */
85636
86102
  buildGbpLocationSignals(projectId, locationName, displayName, fallbackDate) {
85637
- const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and57(eq67(gbpDailyMetrics.projectId, projectId), eq67(gbpDailyMetrics.locationName, locationName))).all();
85638
- const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and57(eq67(gbpPlaceActions.projectId, projectId), eq67(gbpPlaceActions.locationName, locationName))).all();
85639
- const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and57(eq67(gbpLodgingSnapshots.projectId, projectId), eq67(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc29(gbpLodgingSnapshots.syncedAt)).limit(1).get();
85640
- const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and57(eq67(gbpLocations.projectId, projectId), eq67(gbpLocations.locationName, locationName))).get();
86103
+ const metricRows = this.db.select({ metric: gbpDailyMetrics.metric, date: gbpDailyMetrics.date, value: gbpDailyMetrics.value }).from(gbpDailyMetrics).where(and58(eq68(gbpDailyMetrics.projectId, projectId), eq68(gbpDailyMetrics.locationName, locationName))).all();
86104
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and58(eq68(gbpPlaceActions.projectId, projectId), eq68(gbpPlaceActions.locationName, locationName))).all();
86105
+ const lodgingRow = this.db.select({ populatedGroupCount: gbpLodgingSnapshots.populatedGroupCount }).from(gbpLodgingSnapshots).where(and58(eq68(gbpLodgingSnapshots.projectId, projectId), eq68(gbpLodgingSnapshots.locationName, locationName))).orderBy(desc30(gbpLodgingSnapshots.syncedAt)).limit(1).get();
86106
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and58(eq68(gbpLocations.projectId, projectId), eq68(gbpLocations.locationName, locationName))).get();
85641
86107
  const descriptionMissing = !(ownerRow?.description ?? "").trim();
85642
- const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and57(eq67(gbpPlaceDetails.projectId, projectId), eq67(gbpPlaceDetails.locationName, locationName))).orderBy(desc29(gbpPlaceDetails.syncedAt)).limit(1).get();
86108
+ const placeRow = this.db.select({ attributes: gbpPlaceDetails.attributes }).from(gbpPlaceDetails).where(and58(eq68(gbpPlaceDetails.projectId, projectId), eq68(gbpPlaceDetails.locationName, locationName))).orderBy(desc30(gbpPlaceDetails.syncedAt)).limit(1).get();
85643
86109
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
85644
86110
  const summary2 = buildGbpSummary({
85645
86111
  locationName,
@@ -85675,7 +86141,7 @@ var IntelligenceService = class {
85675
86141
  /** Build the month-over-month keyword series for a location from the
85676
86142
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
85677
86143
  buildGbpKeywordTrend(projectId, locationName) {
85678
- const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and57(eq67(gbpKeywordMonthly.projectId, projectId), eq67(gbpKeywordMonthly.locationName, locationName))).all();
86144
+ const rows = this.db.select({ month: gbpKeywordMonthly.month, keyword: gbpKeywordMonthly.keyword, valueCount: gbpKeywordMonthly.valueCount }).from(gbpKeywordMonthly).where(and58(eq68(gbpKeywordMonthly.projectId, projectId), eq68(gbpKeywordMonthly.locationName, locationName))).all();
85679
86145
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
85680
86146
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
85681
86147
  const recentMonth = months[0] ?? null;
@@ -85706,7 +86172,7 @@ var IntelligenceService = class {
85706
86172
  */
85707
86173
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
85708
86174
  const covered = new Set(coveredLocationNames);
85709
- const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and57(eq67(insights.projectId, projectId), eq67(insights.provider, GBP_INSIGHT_PROVIDER))).all();
86175
+ const existing = this.db.select({ id: insights.id, dismissed: insights.dismissed }).from(insights).where(and58(eq68(insights.projectId, projectId), eq68(insights.provider, GBP_INSIGHT_PROVIDER))).all();
85710
86176
  const staleIds = [];
85711
86177
  const dismissedSlots = /* @__PURE__ */ new Set();
85712
86178
  for (const row of existing) {
@@ -85717,7 +86183,7 @@ var IntelligenceService = class {
85717
86183
  }
85718
86184
  this.db.transaction((tx) => {
85719
86185
  for (const id of staleIds) {
85720
- tx.delete(insights).where(eq67(insights.id, id)).run();
86186
+ tx.delete(insights).where(eq68(insights.id, id)).run();
85721
86187
  }
85722
86188
  for (const insight of gbpInsights) {
85723
86189
  const parsed = parseGbpInsightId(insight.id);
@@ -85803,7 +86269,7 @@ var IntelligenceService = class {
85803
86269
  * create per run + aggregate). DB is left untouched.
85804
86270
  */
85805
86271
  backfill(projectName, opts, onProgress) {
85806
- const project = this.db.select().from(projects).where(eq67(projects.name, projectName)).get();
86272
+ const project = this.db.select().from(projects).where(eq68(projects.name, projectName)).get();
85807
86273
  if (!project) {
85808
86274
  throw new Error(`Project "${projectName}" not found`);
85809
86275
  }
@@ -85815,7 +86281,7 @@ var IntelligenceService = class {
85815
86281
  }
85816
86282
  sinceTimestamp = parsed;
85817
86283
  }
85818
- const allRuns = this.db.select().from(runs).where(and57(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
86284
+ const allRuns = this.db.select().from(runs).where(and58(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
85819
86285
  const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
85820
86286
  const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
85821
86287
  let startIdx = 0;
@@ -85839,14 +86305,14 @@ var IntelligenceService = class {
85839
86305
  });
85840
86306
  }
85841
86307
  let processed = 0;
85842
- let skipped = 0;
86308
+ let skipped3 = 0;
85843
86309
  let totalInsights = 0;
85844
86310
  const isDryRun = opts?.dryRun === true;
85845
86311
  const perRunDelta = [];
85846
86312
  let wouldDeleteTotal = 0;
85847
86313
  const existingByRunId = /* @__PURE__ */ new Map();
85848
86314
  if (isDryRun && targetRuns.length > 0) {
85849
- const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray25(insights.runId, targetRuns.map((r) => r.id))).all();
86315
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray26(insights.runId, targetRuns.map((r) => r.id))).all();
85850
86316
  for (const r of rows) {
85851
86317
  if (r.runId == null) continue;
85852
86318
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -85871,14 +86337,14 @@ var IntelligenceService = class {
85871
86337
  }
85872
86338
  onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: result.insights.length });
85873
86339
  } else {
85874
- skipped++;
86340
+ skipped3++;
85875
86341
  onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: 0 });
85876
86342
  }
85877
86343
  }
85878
86344
  if (isDryRun) {
85879
86345
  return {
85880
86346
  processed,
85881
- skipped,
86347
+ skipped: skipped3,
85882
86348
  totalInsights,
85883
86349
  dryRun: true,
85884
86350
  delta: {
@@ -85889,10 +86355,10 @@ var IntelligenceService = class {
85889
86355
  }
85890
86356
  };
85891
86357
  }
85892
- return { processed, skipped, totalInsights };
86358
+ return { processed, skipped: skipped3, totalInsights };
85893
86359
  }
85894
86360
  loadTrackedCompetitors(projectId) {
85895
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq67(competitors.projectId, projectId)).all().map((r) => r.domain);
86361
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq68(competitors.projectId, projectId)).all().map((r) => r.domain);
85896
86362
  }
85897
86363
  /**
85898
86364
  * Which of `runIds` actually wrote query snapshots. One grouped read, so
@@ -85901,7 +86367,7 @@ var IntelligenceService = class {
85901
86367
  */
85902
86368
  runIdsWithSnapshots(runIds) {
85903
86369
  if (runIds.length === 0) return /* @__PURE__ */ new Set();
85904
- const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray25(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
86370
+ const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray26(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
85905
86371
  return new Set(rows.map((r) => r.runId));
85906
86372
  }
85907
86373
  /**
@@ -85923,15 +86389,15 @@ var IntelligenceService = class {
85923
86389
  }
85924
86390
  persistResult(result, runId, projectId) {
85925
86391
  const previouslyDismissed = /* @__PURE__ */ new Set();
85926
- const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq67(insights.runId, runId)).all();
86392
+ const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq68(insights.runId, runId)).all();
85927
86393
  for (const row of existingInsights) {
85928
86394
  if (row.dismissed) {
85929
86395
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
85930
86396
  }
85931
86397
  }
85932
86398
  this.db.transaction((tx) => {
85933
- tx.delete(insights).where(eq67(insights.runId, runId)).run();
85934
- tx.delete(healthSnapshots).where(eq67(healthSnapshots.runId, runId)).run();
86399
+ tx.delete(insights).where(eq68(insights.runId, runId)).run();
86400
+ tx.delete(healthSnapshots).where(eq68(healthSnapshots.runId, runId)).run();
85935
86401
  const now = (/* @__PURE__ */ new Date()).toISOString();
85936
86402
  for (const insight of result.insights) {
85937
86403
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -85978,8 +86444,8 @@ var IntelligenceService = class {
85978
86444
  clicks: gscSearchData.clicks,
85979
86445
  impressions: gscSearchData.impressions,
85980
86446
  position: gscSearchData.position
85981
- }).from(gscSearchData).where(and57(
85982
- eq67(gscSearchData.projectId, projectId),
86447
+ }).from(gscSearchData).where(and58(
86448
+ eq68(gscSearchData.projectId, projectId),
85983
86449
  gte18(gscSearchData.date, window.startDate),
85984
86450
  lte15(gscSearchData.date, window.endDate)
85985
86451
  )).all();
@@ -86032,21 +86498,21 @@ var IntelligenceService = class {
86032
86498
  const key = row.query.toLowerCase();
86033
86499
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
86034
86500
  }
86035
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq67(projects.id, projectId)).get();
86501
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq68(projects.id, projectId)).get();
86036
86502
  const locationCount = Math.max(
86037
86503
  1,
86038
86504
  (projectRow?.locations ?? []).length
86039
86505
  );
86040
86506
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
86041
86507
  const recentRunRows = this.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(
86042
- and57(
86043
- eq67(runs.projectId, projectId),
86044
- eq67(runs.kind, RunKinds["answer-visibility"]),
86045
- or16(eq67(runs.status, "completed"), eq67(runs.status, "partial")),
86508
+ and58(
86509
+ eq68(runs.projectId, projectId),
86510
+ eq68(runs.kind, RunKinds["answer-visibility"]),
86511
+ or16(eq68(runs.status, "completed"), eq68(runs.status, "partial")),
86046
86512
  // Defensive — see top of file.
86047
86513
  ne11(runs.trigger, RunTriggers.probe)
86048
86514
  )
86049
- ).orderBy(desc29(runs.createdAt), desc29(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
86515
+ ).orderBy(desc30(runs.createdAt), desc30(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
86050
86516
  const recentGroups = groupRunsByCreatedAt(recentRunRows);
86051
86517
  const recentRunIds = [];
86052
86518
  const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
@@ -86062,7 +86528,7 @@ var IntelligenceService = class {
86062
86528
  const haveHistory = recentRunIds.length > 0;
86063
86529
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
86064
86530
  if (haveHistory) {
86065
- const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and57(eq67(insights.type, "regression"), inArray25(insights.runId, recentRunIds))).all();
86531
+ const priorRows = this.db.select({ query: insights.query, provider: insights.provider, runId: insights.runId }).from(insights).where(and58(eq68(insights.type, "regression"), inArray26(insights.runId, recentRunIds))).all();
86066
86532
  const regressionGroups = /* @__PURE__ */ new Map();
86067
86533
  for (const row of priorRows) {
86068
86534
  if (!row.runId) continue;
@@ -86091,7 +86557,7 @@ var IntelligenceService = class {
86091
86557
  });
86092
86558
  }
86093
86559
  buildRunData(runId, projectId, completedAt, location = null, trackedCompetitors = []) {
86094
- const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq67(projects.id, projectId)).get();
86560
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq68(projects.id, projectId)).get();
86095
86561
  const projectDomains = projectDomainRow ? effectiveDomains({
86096
86562
  canonicalDomain: projectDomainRow.canonicalDomain,
86097
86563
  ownedDomains: projectDomainRow.ownedDomains
@@ -86109,7 +86575,7 @@ var IntelligenceService = class {
86109
86575
  citedDomains: querySnapshots.citedDomains,
86110
86576
  rawResponse: querySnapshots.rawResponse,
86111
86577
  snapshotLocation: querySnapshots.location
86112
- }).from(querySnapshots).leftJoin(queries, eq67(querySnapshots.queryId, queries.id)).where(eq67(querySnapshots.runId, runId)).all();
86578
+ }).from(querySnapshots).leftJoin(queries, eq68(querySnapshots.queryId, queries.id)).where(eq68(querySnapshots.runId, runId)).all();
86113
86579
  const snapshots = [];
86114
86580
  let orphanCount = 0;
86115
86581
  for (const r of rows) {
@@ -86170,6 +86636,7 @@ export {
86170
86636
  schedules,
86171
86637
  insightNotifyState,
86172
86638
  doctorHealthState,
86639
+ siteLivenessState,
86173
86640
  notifications,
86174
86641
  googleConnections,
86175
86642
  gscSearchData,
@@ -86318,6 +86785,9 @@ export {
86318
86785
  DEFAULT_BOT_LIST,
86319
86786
  generateWorkerScript,
86320
86787
  generateWranglerToml,
86788
+ SITE_REACHABILITY_CHECK_ID,
86789
+ SITE_REACHABILITY_CHECKS,
86790
+ runChecks,
86321
86791
  executeDiscovery,
86322
86792
  markSessionFailed,
86323
86793
  captureSimpleMeasurementDefinition,