@ainyc/canonry 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +3 -3
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
  3. package/assets/assets/{AuditHistoryPanel-D0H5TtxC.js → AuditHistoryPanel-D5cOFMKE.js} +1 -1
  4. package/assets/assets/{BacklinksPage-DRihwG5O.js → BacklinksPage-BiOj8ztV.js} +1 -1
  5. package/assets/assets/{HistoryPage-BQ2nEXly.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-Bo_Adiy0.js → RunRow-BLBAwIot.js} +1 -1
  9. package/assets/assets/{RunsPage-CDkbzjuo.js → RunsPage-BVgBbE1M.js} +1 -1
  10. package/assets/assets/{SettingsPage-CF4i_EzR.js → SettingsPage-DYtN-1_-.js} +1 -1
  11. package/assets/assets/{SiteHealthSection-BrzBV7dt.js → SiteHealthSection-ElUwcAS4.js} +3 -3
  12. package/assets/assets/{TrafficPage-BHSz1N4P.js → TrafficPage-D6QT_2hU.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-DzHLwhpy.js → TrafficSourceDetailPage-BnLzF82w.js} +1 -1
  14. package/assets/assets/{extract-error-message-WHM24Sai.js → extract-error-message-DIzjGZxl.js} +1 -1
  15. package/assets/assets/{index-5W4ZZnwg.js → index-CoQVOsWw.js} +16 -16
  16. package/assets/assets/{react-sigma_core.esm.min-BB2Lcq4W.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-CW2KPN3L.js → chunk-HOOU4NS2.js} +194 -56
  21. package/dist/{chunk-5VONCLVQ.js → chunk-MNUFMEAX.js} +1 -1
  22. package/dist/{chunk-YQNKULAD.js → chunk-QAGZWYPI.js} +815 -366
  23. package/dist/cli.js +6 -6
  24. package/dist/{demo-server-BIPT53EY.js → demo-server-Y7YWLPU7.js} +2 -2
  25. package/dist/index.js +3 -3
  26. package/dist/{intelligence-service-SJO5E7WN.js → intelligence-service-PR6T6JKY.js} +1 -1
  27. package/package.json +12 -12
  28. package/assets/assets/MeasurementPropertyPage-Bp6EURzJ.js +0 -1
  29. package/assets/assets/ProjectPage-Cd03Phwx.js +0 -8
  30. package/assets/assets/v2-overview-adapter-ebrNjcUG.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,
@@ -1387,6 +1388,18 @@ var doctorHealthState = sqliteTable("doctor_health_state", {
1387
1388
  /** When we last emitted for this (status, code). Null until first emit. */
1388
1389
  notifiedAt: text("notified_at")
1389
1390
  });
1391
+ var siteLivenessState = sqliteTable("site_liveness_state", {
1392
+ projectId: text("project_id").primaryKey(),
1393
+ /** Last graded probe: ok | fail. Skipped probes are not recorded. */
1394
+ status: text("status").notNull(),
1395
+ code: text("code").notNull(),
1396
+ summary: text("summary").notNull(),
1397
+ /** Failed passes in a row. Paging waits for two so one blip never alerts. */
1398
+ consecutiveFailures: integer("consecutive_failures").notNull().default(0),
1399
+ checkedAt: text("checked_at").notNull(),
1400
+ /** Set when an outage was actually paged; cleared on recovery. */
1401
+ notifiedAt: text("notified_at")
1402
+ });
1390
1403
  var notifications = sqliteTable("notifications", {
1391
1404
  id: text("id").primaryKey(),
1392
1405
  projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
@@ -7291,6 +7304,24 @@ var MIGRATION_VERSIONS = [
7291
7304
  `CREATE INDEX IF NOT EXISTS idx_runtime_logs_module_level_ts ON runtime_logs(module, level, ts, sequence)`,
7292
7305
  `CREATE INDEX IF NOT EXISTS idx_runtime_logs_ts ON runtime_logs(ts, sequence)`
7293
7306
  ]
7307
+ },
7308
+ {
7309
+ // Liveness gets its own row per project. See siteLivenessState in
7310
+ // schema.ts: a fast "site is up" pass must never overwrite the 6h doctor
7311
+ // state, or it would clear an unrelated outage and send a false recovery.
7312
+ version: 156,
7313
+ name: "site-liveness-state",
7314
+ statements: [
7315
+ `CREATE TABLE IF NOT EXISTS site_liveness_state (
7316
+ project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
7317
+ status TEXT NOT NULL,
7318
+ code TEXT NOT NULL,
7319
+ summary TEXT NOT NULL,
7320
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
7321
+ checked_at TEXT NOT NULL,
7322
+ notified_at TEXT
7323
+ )`
7324
+ ]
7294
7325
  }
7295
7326
  ];
7296
7327
  function addRunsMeasurementPlanVersionForeignKey(tx) {
@@ -7757,8 +7788,8 @@ function projectContext(event, nested) {
7757
7788
  const value = event[key] ?? nested[key];
7758
7789
  if (typeof value === "boolean") context[key] = value;
7759
7790
  }
7760
- const errorCode = optionalString(event.errorCode ?? nested.errorCode, 128);
7761
- if (errorCode !== void 0) context.errorCode = errorCode;
7791
+ const errorCode2 = optionalString(event.errorCode ?? nested.errorCode, 128);
7792
+ if (errorCode2 !== void 0) context.errorCode = errorCode2;
7762
7793
  return context;
7763
7794
  }
7764
7795
  var ID_FIELDS = [
@@ -43383,9 +43414,10 @@ function healthView(payload) {
43383
43414
  value: bulletList(others.map((check2) => `${check2.status} \u2014 ${check2.code}`))
43384
43415
  });
43385
43416
  }
43417
+ const website = health.code.startsWith("site.reachability.");
43386
43418
  return {
43387
43419
  severity,
43388
- title: recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
43420
+ title: website ? recovered ? `${project.name} website is back up` : `${project.name} website is down` : recovered ? `${project.name} recovered` : `${project.name} \u2014 measurement degraded`,
43389
43421
  body: health.summary,
43390
43422
  fields,
43391
43423
  url: payload.dashboardUrl,
@@ -52229,14 +52261,14 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
52229
52261
  const now = (/* @__PURE__ */ new Date()).toISOString();
52230
52262
  const entityId = outcome.entity?.id ?? row.entityId;
52231
52263
  const exhausted = outcome.state === AdsOperationStates.unknown && row.reconcileAttempts >= maxAttempts;
52232
- const errorCode = exhausted ? ADS_RECONCILIATION_QUARANTINED : outcome.errorCode ?? null;
52264
+ const errorCode2 = exhausted ? ADS_RECONCILIATION_QUARANTINED : outcome.errorCode ?? null;
52233
52265
  const errorMessage = exhausted ? `Reconciliation stopped after ${maxAttempts} inconclusive attempts; manual remediation is required` : outcome.errorMessage ?? null;
52234
52266
  app.db.transaction((tx) => {
52235
52267
  const result = tx.update(adsOperations).set({
52236
52268
  state: outcome.state,
52237
52269
  entityId,
52238
52270
  upstreamUpdatedAt: outcome.entity?.updatedAt ?? row.upstreamUpdatedAt,
52239
- errorCode,
52271
+ errorCode: errorCode2,
52240
52272
  errorMessage,
52241
52273
  lastReconciledAt: now,
52242
52274
  leaseOwner: null,
@@ -52251,7 +52283,7 @@ function finishReconciliation(app, row, leaseOwner, context, outcome, maxAttempt
52251
52283
  if (result.changes > 0) {
52252
52284
  writeAuditLog(tx, reconciliationAuditEntry(row, outcome.state, context, {
52253
52285
  entityId,
52254
- errorCode
52286
+ errorCode: errorCode2
52255
52287
  }));
52256
52288
  }
52257
52289
  });
@@ -57516,11 +57548,11 @@ async function wordpressRoutes(app, opts) {
57516
57548
  const result = await bulkSetSeoMeta(connection, metaEntries);
57517
57549
  const applied = result.results.filter((r) => r.status === "applied").length;
57518
57550
  const manual = result.results.filter((r) => r.status === "manual").length;
57519
- const skipped = result.results.filter((r) => r.status === "skipped").length;
57551
+ const skipped3 = result.results.filter((r) => r.status === "skipped").length;
57520
57552
  steps.push({
57521
57553
  name: "set-meta",
57522
57554
  status: "completed",
57523
- summary: `${applied} applied, ${manual} manual-assist, ${skipped} skipped`
57555
+ summary: `${applied} applied, ${manual} manual-assist, ${skipped3} skipped`
57524
57556
  });
57525
57557
  }
57526
57558
  } catch (err) {
@@ -57542,11 +57574,11 @@ async function wordpressRoutes(app, opts) {
57542
57574
  const result = await deploySchemaFromProfile(connection, profile);
57543
57575
  const deployed = result.results.filter((r) => r.status === "deployed").length;
57544
57576
  const stripped = result.results.filter((r) => r.status === "stripped").length;
57545
- const skipped = result.results.filter((r) => r.status === "skipped").length;
57577
+ const skipped3 = result.results.filter((r) => r.status === "skipped").length;
57546
57578
  steps.push({
57547
57579
  name: "schema-deploy",
57548
57580
  status: "completed",
57549
- summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped} skipped`
57581
+ summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped3} skipped`
57550
57582
  });
57551
57583
  }
57552
57584
  } catch (err) {
@@ -77300,7 +77332,7 @@ async function trafficRoutes(app, opts) {
77300
77332
  startedAt,
77301
77333
  createdAt: startedAt
77302
77334
  }).run();
77303
- const markFailed = (msg, errorCode) => {
77335
+ const markFailed = (msg, errorCode2) => {
77304
77336
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
77305
77337
  app.db.transaction((tx) => {
77306
77338
  tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq55(runs.id, runId)).run();
@@ -77320,7 +77352,7 @@ async function trafficRoutes(app, opts) {
77320
77352
  aiUserFetchHits: 0,
77321
77353
  aiReferralHits: 0,
77322
77354
  durationMs: Date.now() - syncStartedAtMs,
77323
- errorCode
77355
+ errorCode: errorCode2
77324
77356
  });
77325
77357
  } catch {
77326
77358
  }
@@ -79472,8 +79504,417 @@ var ga4ConnectionCheck = {
79472
79504
  };
79473
79505
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
79474
79506
 
79507
+ // ../api-routes/src/doctor/checks/data-freshness.ts
79508
+ import { and as and51, desc as desc26, eq as eq59, inArray as inArray22 } from "drizzle-orm";
79509
+ var GA_DATA_AGING_DAYS = 3;
79510
+ var GA_DATA_STALE_DAYS = 5;
79511
+ var GSC_DATA_AGING_DAYS = 5;
79512
+ var GSC_DATA_STALE_DAYS = 7;
79513
+ var SYNC_IDLE_DAYS = 3;
79514
+ var GSC_REPORTING_TIME_ZONE2 = "America/Los_Angeles";
79515
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
79516
+ var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
79517
+ function utcFromIsoDate(date) {
79518
+ const match = ISO_DATE.exec(date);
79519
+ if (!match) return null;
79520
+ return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
79521
+ }
79522
+ function daysBetweenIsoDates(from, to) {
79523
+ const start = utcFromIsoDate(from);
79524
+ const end = utcFromIsoDate(to);
79525
+ if (start === null || end === null) return null;
79526
+ return Math.floor((end - start) / DAY_MS2);
79527
+ }
79528
+ function daysSinceDate(date, now = /* @__PURE__ */ new Date(), timeZone) {
79529
+ const today = timeZone ? formatIsoDateInTimeZone(now.toISOString(), timeZone) : `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`;
79530
+ return daysBetweenIsoDates(date, today);
79531
+ }
79532
+ function skipped(code, summary2) {
79533
+ return { status: CheckStatuses.skipped, code, summary: summary2, remediation: null };
79534
+ }
79535
+ function lastSyncAgeDays(ctx, kinds) {
79536
+ 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();
79537
+ if (!row?.createdAt) return null;
79538
+ const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS2);
79539
+ return Number.isFinite(age) ? age : null;
79540
+ }
79541
+ function grade(ctx, spec, newestDate) {
79542
+ const projectName = ctx.project.name;
79543
+ const code = (suffix) => `${spec.source}.data.${suffix}`;
79544
+ const sync = spec.syncCommand(projectName);
79545
+ const syncAge = lastSyncAgeDays(ctx, spec.syncKinds);
79546
+ const syncIdle = syncAge === null || syncAge >= SYNC_IDLE_DAYS;
79547
+ if (!newestDate) {
79548
+ return {
79549
+ status: CheckStatuses.warn,
79550
+ code: code("never-synced"),
79551
+ summary: `${spec.label} is connected but no daily data has been stored yet.`,
79552
+ remediation: `Run \`${sync}\`, or set a data-refresh schedule so it keeps itself current.`,
79553
+ details: { lastSyncAgeDays: syncAge }
79554
+ };
79555
+ }
79556
+ const age = daysSinceDate(newestDate, /* @__PURE__ */ new Date(), spec.timeZone);
79557
+ if (age === null) {
79558
+ return {
79559
+ status: CheckStatuses.warn,
79560
+ code: code("unreadable-date"),
79561
+ summary: `Newest stored ${spec.label} date "${newestDate}" is not a calendar date.`,
79562
+ remediation: null,
79563
+ details: { newestDate }
79564
+ };
79565
+ }
79566
+ const details = { newestDate, ageDays: age, agingDays: spec.agingDays, staleDays: spec.staleDays, lastSyncAgeDays: syncAge };
79567
+ if (age < spec.agingDays) {
79568
+ return {
79569
+ status: CheckStatuses.ok,
79570
+ code: code("fresh"),
79571
+ summary: `${spec.label} data is current through ${newestDate}.`,
79572
+ remediation: null,
79573
+ details
79574
+ };
79575
+ }
79576
+ if (syncIdle) {
79577
+ return {
79578
+ status: CheckStatuses.warn,
79579
+ code: code("not-syncing"),
79580
+ summary: `No ${spec.label} sync has completed in ${syncAge === null ? "any recorded run" : `${syncAge} days`}, and stored data ends ${newestDate}.`,
79581
+ remediation: `Run \`${sync}\`, or set a data-refresh schedule: \`canonry schedule set ${projectName} --kind data-refresh --preset daily\`.`,
79582
+ details
79583
+ };
79584
+ }
79585
+ const remediation = `${spec.quietExplanation} ${spec.brokenExplanation}`;
79586
+ return {
79587
+ status: CheckStatuses.warn,
79588
+ code: code(age >= spec.staleDays ? "stale" : "aging"),
79589
+ summary: `${spec.label} syncs are running but have stored nothing newer than ${newestDate} (${age} days).`,
79590
+ remediation,
79591
+ details
79592
+ };
79593
+ }
79594
+ var GA_SPEC = {
79595
+ source: "ga",
79596
+ label: "GA4",
79597
+ agingDays: GA_DATA_AGING_DAYS,
79598
+ staleDays: GA_DATA_STALE_DAYS,
79599
+ syncKinds: ["ga-sync"],
79600
+ syncCommand: (name) => `canonry ga sync ${name}`,
79601
+ quietExplanation: "GA4 omits days with no sessions, so a genuinely quiet period clears itself on the next visit.",
79602
+ brokenExplanation: "If the site did have visitors, check that the GA4 tag or its Tag Manager container is still on the live site."
79603
+ };
79604
+ var GSC_SPEC = {
79605
+ source: "gsc",
79606
+ label: "Search Console",
79607
+ agingDays: GSC_DATA_AGING_DAYS,
79608
+ staleDays: GSC_DATA_STALE_DAYS,
79609
+ timeZone: GSC_REPORTING_TIME_ZONE2,
79610
+ syncKinds: ["gsc-sync"],
79611
+ syncCommand: (name) => `canonry google sync ${name}`,
79612
+ quietExplanation: "Search Console omits days with no impressions and reports two to three days behind, so a quiet period clears itself.",
79613
+ brokenExplanation: "If the site did have impressions, check that the property still lists this site and the connected account still has access."
79614
+ };
79615
+ function gaConnected(ctx) {
79616
+ const project = ctx.project;
79617
+ if (!ctx.ga4CredentialStore && !ctx.googleConnectionStore) return "store-unavailable";
79618
+ if (ctx.ga4CredentialStore?.getConnection(project.name)) return "connected";
79619
+ if (ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4")) return "connected";
79620
+ return "not-connected";
79621
+ }
79622
+ var gaRecentDataCheck = {
79623
+ id: "ga.data.recent-data",
79624
+ category: CheckCategories.integrations,
79625
+ scope: CheckScopes.project,
79626
+ title: "GA4 data still arriving",
79627
+ run: (ctx) => {
79628
+ if (!ctx.project) return skipped("ga.data.no-project", "Project context required.");
79629
+ const connection = gaConnected(ctx);
79630
+ if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
79631
+ if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
79632
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq59(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gaDailyTotals.date)).limit(1).get();
79633
+ return grade(ctx, GA_SPEC, newest?.date);
79634
+ }
79635
+ };
79636
+ var gscRecentDataCheck = {
79637
+ id: "gsc.data.recent-data",
79638
+ category: CheckCategories.integrations,
79639
+ scope: CheckScopes.project,
79640
+ title: "Search Console data still arriving",
79641
+ run: (ctx) => {
79642
+ if (!ctx.project) return skipped("gsc.data.no-project", "Project context required.");
79643
+ if (!ctx.googleConnectionStore) return skipped("gsc.data.store-unavailable", "No Google connection store configured for this deployment.");
79644
+ if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
79645
+ return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
79646
+ }
79647
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq59(gscDataWatermarks.projectId, ctx.project.id)).get();
79648
+ if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
79649
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq59(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc26(gscDailyTotals.date)).limit(1).get();
79650
+ return grade(ctx, GSC_SPEC, newest?.date);
79651
+ }
79652
+ };
79653
+ var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
79654
+
79655
+ // ../api-routes/src/site-reachability.ts
79656
+ import dns3 from "dns/promises";
79657
+ import http3 from "http";
79658
+ import https3 from "https";
79659
+ import net3 from "net";
79660
+ var SITE_REACHABILITY_USER_AGENT = "Canonry site-liveness (+https://canonry.ai)";
79661
+ var DEFAULT_SITE_MAX_REDIRECTS = 10;
79662
+ var DEFAULT_TIMEOUT_MS5 = 1e4;
79663
+ var DEFAULT_RETRY_DELAY_MS = 2e3;
79664
+ var DNS_TIMEOUT_MS = 5e3;
79665
+ var MAX_ADDRESSES_PER_HOP = 4;
79666
+ var LENIENT_RETRY_CODES = /* @__PURE__ */ new Set([
79667
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
79668
+ "UNABLE_TO_GET_ISSUER_CERT",
79669
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
79670
+ "SELF_SIGNED_CERT_IN_CHAIN",
79671
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
79672
+ "ERR_TLS_CERT_ALTNAME_INVALID",
79673
+ "HPE_INVALID_HEADER_TOKEN",
79674
+ "HPE_UNEXPECTED_CONTENT_LENGTH",
79675
+ "HPE_INVALID_CONSTANT"
79676
+ ]);
79677
+ var NO_SUCH_HOST_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "ENODATA"]);
79678
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
79679
+ function errorCode(err) {
79680
+ const code = err?.code;
79681
+ return typeof code === "string" ? code : "";
79682
+ }
79683
+ async function lookupSiteAddresses(hostname, timeoutMs = DNS_TIMEOUT_MS) {
79684
+ const literalFamily = net3.isIPv6(hostname) ? 6 : net3.isIPv4(hostname) ? 4 : null;
79685
+ if (literalFamily) return { kind: "addresses", addresses: [{ address: hostname, family: literalFamily }] };
79686
+ let timer;
79687
+ const deadline = new Promise((resolve) => {
79688
+ timer = setTimeout(() => resolve("timeout"), timeoutMs);
79689
+ });
79690
+ try {
79691
+ const settled = await Promise.race([
79692
+ Promise.allSettled([dns3.resolve4(hostname), dns3.resolve6(hostname)]),
79693
+ deadline
79694
+ ]);
79695
+ if (settled === "timeout") return { kind: "resolver-error", reason: `DNS did not answer within ${Math.round(timeoutMs / 1e3)}s` };
79696
+ const [ipv4, ipv6] = settled;
79697
+ const found = /* @__PURE__ */ new Map();
79698
+ if (ipv4.status === "fulfilled") for (const address of ipv4.value) found.set(`4:${address}`, { address, family: 4 });
79699
+ if (ipv6.status === "fulfilled") for (const address of ipv6.value) found.set(`6:${address}`, { address, family: 6 });
79700
+ if (found.size > 0) return { kind: "addresses", addresses: [...found.values()] };
79701
+ const codes = [ipv4, ipv6].map((r) => r.status === "rejected" ? errorCode(r.reason) : "");
79702
+ if (codes.every((code) => NO_SUCH_HOST_CODES.has(code))) {
79703
+ return { kind: "no-such-host", reason: `${hostname} has no DNS address (${codes[0]})` };
79704
+ }
79705
+ const failing = codes.find((code) => code && !NO_SUCH_HOST_CODES.has(code)) ?? "unknown resolver error";
79706
+ return { kind: "resolver-error", reason: `DNS lookup for ${hostname} failed (${failing})` };
79707
+ } finally {
79708
+ if (timer) clearTimeout(timer);
79709
+ }
79710
+ }
79711
+ async function requestPinnedStatus(target, timeoutMs, options = {}) {
79712
+ const secure = target.url.protocol === "https:";
79713
+ const port = target.url.port ? Number(target.url.port) : secure ? 443 : 80;
79714
+ const requestOptions = {
79715
+ hostname: target.address,
79716
+ family: target.family,
79717
+ port,
79718
+ method: "GET",
79719
+ path: `${target.url.pathname}${target.url.search}`,
79720
+ headers: { Host: target.url.host, "User-Agent": SITE_REACHABILITY_USER_AGENT, Accept: "text/html,*/*;q=0.8" },
79721
+ insecureHTTPParser: options.lenient === true
79722
+ };
79723
+ if (secure) {
79724
+ requestOptions.servername = target.url.hostname.replace(/^\[|\]$/g, "");
79725
+ if (options.lenient) requestOptions.rejectUnauthorized = false;
79726
+ }
79727
+ return await new Promise((resolve, reject) => {
79728
+ let settled = false;
79729
+ const request = (secure ? https3.request : http3.request)(requestOptions, (response) => {
79730
+ if (settled) {
79731
+ response.destroy();
79732
+ return;
79733
+ }
79734
+ settled = true;
79735
+ clearTimeout(deadline);
79736
+ const location = typeof response.headers.location === "string" ? response.headers.location : null;
79737
+ resolve({ status: response.statusCode ?? 0, location });
79738
+ response.destroy();
79739
+ request.destroy();
79740
+ });
79741
+ const deadline = setTimeout(() => {
79742
+ if (settled) return;
79743
+ settled = true;
79744
+ request.destroy();
79745
+ reject(new Error(`no response within ${Math.round(timeoutMs / 1e3)}s`));
79746
+ }, timeoutMs);
79747
+ request.on("error", (err) => {
79748
+ if (settled) return;
79749
+ settled = true;
79750
+ clearTimeout(deadline);
79751
+ reject(err);
79752
+ });
79753
+ request.end();
79754
+ });
79755
+ }
79756
+ async function probeSiteReachability(url, options = {}) {
79757
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS5;
79758
+ const maxRedirects = options.maxRedirects ?? DEFAULT_SITE_MAX_REDIRECTS;
79759
+ const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
79760
+ const transport = options.transport ?? ((target, ms, opts) => requestPinnedStatus(target, ms, opts));
79761
+ const resolveAddresses = options.resolveAddresses ?? ((hostname) => lookupSiteAddresses(hostname));
79762
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
79763
+ const started = Date.now();
79764
+ const approveHop = async (hopUrl) => {
79765
+ let hostname;
79766
+ try {
79767
+ hostname = new URL(hopUrl).hostname.replace(/^\[|\]$/g, "");
79768
+ } catch {
79769
+ return { kind: "refused", reason: `${hopUrl} is not a valid URL` };
79770
+ }
79771
+ const lookup = await resolveAddresses(hostname);
79772
+ if (lookup.kind !== "addresses") return lookup;
79773
+ const targets = [];
79774
+ let refusal = "";
79775
+ for (const address of lookup.addresses.slice(0, MAX_ADDRESSES_PER_HOP)) {
79776
+ const approved = await resolveMeasurementSitemapTarget(hopUrl, { resolveAddresses: async () => [address] });
79777
+ if (approved.ok) targets.push(approved.target);
79778
+ else if (!refusal) refusal = approved.message;
79779
+ }
79780
+ if (targets.length === 0) return { kind: "refused", reason: refusal || `${hostname} has no address this instance may dial` };
79781
+ return { kind: "targets", targets };
79782
+ };
79783
+ const attempt = async () => {
79784
+ let current = url;
79785
+ for (let hop = 0; ; hop += 1) {
79786
+ const approved = await approveHop(current);
79787
+ if (approved.kind === "no-such-host") return { kind: "down", finalUrl: current, httpStatus: null, reason: approved.reason };
79788
+ if (approved.kind === "resolver-error") return { kind: "unavailable", reason: approved.reason };
79789
+ if (approved.kind === "refused") return { kind: "unreachable", finalUrl: current, reason: approved.reason };
79790
+ let response = null;
79791
+ let lastError = "";
79792
+ for (const target of approved.targets) {
79793
+ try {
79794
+ response = await transport(target, timeoutMs);
79795
+ break;
79796
+ } catch (err) {
79797
+ lastError = describeError(err);
79798
+ if (LENIENT_RETRY_CODES.has(errorCode(err))) {
79799
+ try {
79800
+ const lenient = await transport(target, timeoutMs, { lenient: true });
79801
+ response = { ...lenient, lenient: `${errorCode(err)} (accepted leniently)` };
79802
+ break;
79803
+ } catch (retryErr) {
79804
+ lastError = describeError(retryErr);
79805
+ }
79806
+ }
79807
+ }
79808
+ }
79809
+ if (!response) return { kind: "down", finalUrl: current, httpStatus: null, reason: lastError || "no address answered" };
79810
+ if (REDIRECT_STATUSES.has(response.status) && response.location) {
79811
+ if (hop >= maxRedirects) {
79812
+ return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `more than ${maxRedirects} redirects` };
79813
+ }
79814
+ try {
79815
+ current = new URL(response.location, current).toString();
79816
+ } catch {
79817
+ return { kind: "down", finalUrl: current, httpStatus: response.status, reason: "redirect with an invalid Location header" };
79818
+ }
79819
+ continue;
79820
+ }
79821
+ if (response.status >= 500 || response.status === 0) {
79822
+ return { kind: "down", finalUrl: current, httpStatus: response.status, reason: `HTTP ${response.status}` };
79823
+ }
79824
+ return { kind: "up", finalUrl: current, httpStatus: response.status, lenient: response.lenient };
79825
+ }
79826
+ };
79827
+ let attempts = 1;
79828
+ let outcome = await attempt();
79829
+ if (outcome.kind === "down" || outcome.kind === "unreachable") {
79830
+ await sleep(retryDelayMs);
79831
+ attempts = 2;
79832
+ outcome = await attempt();
79833
+ }
79834
+ const durationMs = Date.now() - started;
79835
+ if (outcome.kind === "unavailable") return { state: "unavailable", url, reason: outcome.reason };
79836
+ if (outcome.kind === "up") {
79837
+ return { state: "up", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, attempts, durationMs, ...outcome.lenient ? { lenient: outcome.lenient } : {} };
79838
+ }
79839
+ if (outcome.kind === "unreachable") return { state: "unreachable", url, finalUrl: outcome.finalUrl, reason: outcome.reason, attempts, durationMs };
79840
+ return { state: "down", url, finalUrl: outcome.finalUrl, httpStatus: outcome.httpStatus, reason: outcome.reason, attempts, durationMs };
79841
+ }
79842
+
79843
+ // ../api-routes/src/doctor/checks/site-reachability.ts
79844
+ var SITE_REACHABILITY_CHECK_ID = "site.reachability";
79845
+ function probeHostFromDomain(value) {
79846
+ const trimmed = (value ?? "").trim();
79847
+ if (!trimmed) return null;
79848
+ let hostname;
79849
+ try {
79850
+ hostname = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`).hostname;
79851
+ } catch {
79852
+ return null;
79853
+ }
79854
+ hostname = hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
79855
+ if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
79856
+ return hostname;
79857
+ }
79858
+ var skipped2 = (code, summary2) => ({ status: CheckStatuses.skipped, code, summary: summary2, remediation: null });
79859
+ var siteReachabilityCheck = {
79860
+ id: SITE_REACHABILITY_CHECK_ID,
79861
+ category: CheckCategories.integrations,
79862
+ scope: CheckScopes.project,
79863
+ title: "Website reachable",
79864
+ // Reaches the network, and one bad answer is not an outage: the liveness loop
79865
+ // names it explicitly and applies the two-pass debounce. An unfiltered
79866
+ // `canonry doctor --project` must not exit 1 on a single dropped packet.
79867
+ optIn: true,
79868
+ run: async (ctx) => {
79869
+ if (!ctx.project) return skipped2("site.reachability.no-project", "Project context required.");
79870
+ const host = probeHostFromDomain(ctx.project.canonicalDomain);
79871
+ if (!host) {
79872
+ return skipped2("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
79873
+ }
79874
+ const url = `https://${host}/`;
79875
+ const probe = ctx.probeSiteReachability ?? probeSiteReachability;
79876
+ const result = await probe(url);
79877
+ if (result.state === "unavailable") {
79878
+ return skipped2("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
79879
+ }
79880
+ if (result.state === "up") {
79881
+ return {
79882
+ status: CheckStatuses.ok,
79883
+ code: "site.reachability.up",
79884
+ summary: `${url} answered HTTP ${result.httpStatus} in ${result.durationMs} ms.`,
79885
+ remediation: null,
79886
+ details: {
79887
+ url,
79888
+ finalUrl: result.finalUrl,
79889
+ httpStatus: result.httpStatus,
79890
+ attempts: result.attempts,
79891
+ durationMs: result.durationMs,
79892
+ ...result.lenient ? { tlsOrParserWarning: result.lenient } : {}
79893
+ }
79894
+ };
79895
+ }
79896
+ if (result.state === "unreachable") {
79897
+ return {
79898
+ status: CheckStatuses.fail,
79899
+ code: "site.reachability.refused-address",
79900
+ summary: `${url} resolves only to addresses that cannot serve visitors: ${result.reason}.`,
79901
+ remediation: "Check the domain registration and its DNS records. A suspended or parked domain resolves this way.",
79902
+ details: { url, finalUrl: result.finalUrl, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
79903
+ };
79904
+ }
79905
+ return {
79906
+ status: CheckStatuses.fail,
79907
+ code: "site.reachability.down",
79908
+ summary: `${url} is not responding: ${result.reason}.`,
79909
+ remediation: "Open the site in a browser and check with the host. Canonry keeps checking and sends health.recovered when it answers again.",
79910
+ details: { url, finalUrl: result.finalUrl, httpStatus: result.httpStatus, reason: result.reason, attempts: result.attempts, durationMs: result.durationMs }
79911
+ };
79912
+ }
79913
+ };
79914
+ var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
79915
+
79475
79916
  // ../api-routes/src/doctor/checks/gbp-auth.ts
79476
- import { and as and51, eq as eq59 } from "drizzle-orm";
79917
+ import { and as and52, eq as eq60 } from "drizzle-orm";
79477
79918
  var RECENT_SYNC_WARN_DAYS2 = 7;
79478
79919
  var RECENT_SYNC_FAIL_DAYS2 = 30;
79479
79920
  function skippedNoProject3() {
@@ -79706,7 +80147,7 @@ var recentSyncCheck = {
79706
80147
  title: "GBP recent sync",
79707
80148
  run: (ctx) => {
79708
80149
  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();
80150
+ 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
80151
  if (selected.length === 0) {
79711
80152
  return {
79712
80153
  status: CheckStatuses.skipped,
@@ -79766,7 +80207,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
79766
80207
  );
79767
80208
 
79768
80209
  // ../api-routes/src/doctor/checks/places.ts
79769
- import { eq as eq60 } from "drizzle-orm";
80210
+ import { eq as eq61 } from "drizzle-orm";
79770
80211
  var apiKeyCheck = {
79771
80212
  id: "gbp.places.api-key",
79772
80213
  category: CheckCategories.auth,
@@ -79811,7 +80252,7 @@ var apiKeyCheck = {
79811
80252
  details: { tier: cfg.tier }
79812
80253
  };
79813
80254
  }
79814
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq60(gbpLocations.projectId, ctx.project.id)).all();
80255
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq61(gbpLocations.projectId, ctx.project.id)).all();
79815
80256
  const selected = rows.filter((r) => r.selected);
79816
80257
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
79817
80258
  const details = {
@@ -80326,7 +80767,7 @@ var RUNTIME_STATE_CHECKS = [
80326
80767
  ];
80327
80768
 
80328
80769
  // ../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";
80770
+ import { and as and53, eq as eq62, gte as gte15, inArray as inArray23, ne as ne10, sql as sql24 } from "drizzle-orm";
80330
80771
  var RECENT_DATA_WARN_DAYS = 7;
80331
80772
  var RECENT_DATA_FAIL_DAYS = 30;
80332
80773
  function isCloudflareDirectPush(source) {
@@ -80365,8 +80806,8 @@ function skippedNoProject5() {
80365
80806
  function loadProbes(ctx) {
80366
80807
  if (!ctx.project) return [];
80367
80808
  const rows = ctx.db.select().from(trafficSources).where(
80368
- and52(
80369
- eq61(trafficSources.projectId, ctx.project.id),
80809
+ and53(
80810
+ eq62(trafficSources.projectId, ctx.project.id),
80370
80811
  ne10(trafficSources.status, TrafficSourceStatuses.archived)
80371
80812
  )
80372
80813
  ).all();
@@ -80472,27 +80913,27 @@ var recentDataCheck = {
80472
80913
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
80473
80914
  const recentCrawlers = Number(
80474
80915
  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),
80916
+ and53(
80917
+ eq62(crawlerEventsHourly.projectId, ctx.project.id),
80918
+ inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
80478
80919
  gte15(crawlerEventsHourly.tsHour, warnCutoff)
80479
80920
  )
80480
80921
  ).get()?.total ?? 0
80481
80922
  );
80482
80923
  const recentReferrals = Number(
80483
80924
  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),
80925
+ and53(
80926
+ eq62(aiReferralEventsHourly.projectId, ctx.project.id),
80927
+ inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
80487
80928
  gte15(aiReferralEventsHourly.tsHour, warnCutoff)
80488
80929
  )
80489
80930
  ).get()?.total ?? 0
80490
80931
  );
80491
80932
  const recentUserFetches = Number(
80492
80933
  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),
80934
+ and53(
80935
+ eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
80936
+ inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
80496
80937
  gte15(aiUserFetchEventsHourly.tsHour, warnCutoff)
80497
80938
  )
80498
80939
  ).get()?.total ?? 0
@@ -80516,27 +80957,27 @@ var recentDataCheck = {
80516
80957
  }
80517
80958
  const olderCrawlers = Number(
80518
80959
  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),
80960
+ and53(
80961
+ eq62(crawlerEventsHourly.projectId, ctx.project.id),
80962
+ inArray23(crawlerEventsHourly.sourceId, activeSourceIds),
80522
80963
  gte15(crawlerEventsHourly.tsHour, failCutoff)
80523
80964
  )
80524
80965
  ).get()?.total ?? 0
80525
80966
  );
80526
80967
  const olderReferrals = Number(
80527
80968
  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),
80969
+ and53(
80970
+ eq62(aiReferralEventsHourly.projectId, ctx.project.id),
80971
+ inArray23(aiReferralEventsHourly.sourceId, activeSourceIds),
80531
80972
  gte15(aiReferralEventsHourly.tsHour, failCutoff)
80532
80973
  )
80533
80974
  ).get()?.total ?? 0
80534
80975
  );
80535
80976
  const olderUserFetches = Number(
80536
80977
  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),
80978
+ and53(
80979
+ eq62(aiUserFetchEventsHourly.projectId, ctx.project.id),
80980
+ inArray23(aiUserFetchEventsHourly.sourceId, activeSourceIds),
80540
80981
  gte15(aiUserFetchEventsHourly.tsHour, failCutoff)
80541
80982
  )
80542
80983
  ).get()?.total ?? 0
@@ -80601,7 +81042,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
80601
81042
  function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
80602
81043
  const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
80603
81044
  const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
80604
- const skipped = results.filter((r) => r.output.status === CheckStatuses.skipped);
81045
+ const skipped3 = results.filter((r) => r.output.status === CheckStatuses.skipped);
80605
81046
  const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
80606
81047
  const detail = {
80607
81048
  sources: results.map((r) => ({
@@ -80635,7 +81076,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
80635
81076
  return {
80636
81077
  status: CheckStatuses.ok,
80637
81078
  code: `traffic.${fallbackId}.ok`,
80638
- summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped.length > 0 ? ` (${skipped.length} skipped)` : ""}.`,
81079
+ summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped3.length > 0 ? ` (${skipped3.length} skipped)` : ""}.`,
80639
81080
  details: detail
80640
81081
  };
80641
81082
  }
@@ -80643,7 +81084,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
80643
81084
  status: CheckStatuses.skipped,
80644
81085
  code: `traffic.${fallbackId}.all-skipped`,
80645
81086
  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,
81087
+ remediation: skipped3.find((result) => result.output.remediation)?.output.remediation,
80647
81088
  details: detail
80648
81089
  };
80649
81090
  }
@@ -81069,7 +81510,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
81069
81510
  ];
81070
81511
  var FRESH_SNAPSHOT_WARN_DAYS = 7;
81071
81512
  var FRESH_SNAPSHOT_FAIL_DAYS = 30;
81072
- var DAY_MS2 = 24 * 60 * 60 * 1e3;
81513
+ var DAY_MS3 = 24 * 60 * 60 * 1e3;
81073
81514
  function unavailableStatus() {
81074
81515
  return {
81075
81516
  status: CheckStatuses.skipped,
@@ -81156,7 +81597,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
81156
81597
  details: { latestSnapshotAt }
81157
81598
  };
81158
81599
  }
81159
- const ageDays = (now.getTime() - snapshotMs) / DAY_MS2;
81600
+ const ageDays = (now.getTime() - snapshotMs) / DAY_MS3;
81160
81601
  const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays)) };
81161
81602
  if (ageDays < 0) {
81162
81603
  return {
@@ -81377,13 +81818,16 @@ var ALL_CHECKS = [
81377
81818
  ...BING_AUTH_CHECKS,
81378
81819
  ...WORDPRESS_PUBLISH_CHECKS,
81379
81820
  ...GA_AUTH_CHECKS,
81821
+ ...DATA_FRESHNESS_CHECKS,
81380
81822
  ...ADS_CHECKS,
81381
81823
  ...GOOGLE_MARKETING_DOCTOR_CHECKS,
81382
81824
  ...PROVIDERS_CHECKS,
81383
81825
  ...TRAFFIC_SOURCE_CHECKS,
81384
81826
  ...BACKLINKS_CHECKS,
81385
81827
  ...CONTENT_CHECKS,
81386
- ...AGENT_CHECKS
81828
+ ...AGENT_CHECKS,
81829
+ // Network probe last, so a slow or unreachable site never delays the local checks.
81830
+ ...SITE_REACHABILITY_CHECKS
81387
81831
  ];
81388
81832
 
81389
81833
  // ../api-routes/src/doctor/runner.ts
@@ -81405,6 +81849,7 @@ async function runChecks(ctx, checks, options = {}) {
81405
81849
  const projectName = ctx.project?.name ?? null;
81406
81850
  const selected = checks.filter((check2) => {
81407
81851
  if (check2.scope !== targetScope) return false;
81852
+ if (check2.optIn && filters.length === 0) return false;
81408
81853
  return matchesCheckId(check2.id, filters);
81409
81854
  });
81410
81855
  const results = [];
@@ -81515,7 +81960,7 @@ async function doctorRoutes(app, opts) {
81515
81960
 
81516
81961
  // ../api-routes/src/discovery/routes.ts
81517
81962
  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";
81963
+ 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
81964
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
81520
81965
  async function discoveryRoutes(app, opts) {
81521
81966
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -81549,13 +81994,13 @@ async function discoveryRoutes(app, opts) {
81549
81994
  const now = (/* @__PURE__ */ new Date()).toISOString();
81550
81995
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
81551
81996
  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),
81997
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and54(
81998
+ eq63(discoverySessions.projectId, project.id),
81999
+ eq63(discoverySessions.icpDescription, icpDescription),
81555
82000
  // Buyer is part of session identity: it changes the seed prompt's
81556
82001
  // semantics, so a request with a different (or no) buyer must start
81557
82002
  // its own session, never adopt another buyer's probes.
81558
- parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) : eq62(discoverySessions.buyerDescription, parsed.data.buyerDescription),
82003
+ parsed.data.buyerDescription == null ? isNull10(discoverySessions.buyerDescription) : eq63(discoverySessions.buyerDescription, parsed.data.buyerDescription),
81559
82004
  // Locations are identity too: a different service-area subset seeds
81560
82005
  // and probes a different geo, so it must never reuse another geo's
81561
82006
  // session. resolveLocations is deterministic (project-config order),
@@ -81565,18 +82010,18 @@ async function discoveryRoutes(app, opts) {
81565
82010
  // locations a NULL row's subset is unknowable, so it conservatively
81566
82011
  // never reuses — a one-time, bounded (2h window) non-reuse after
81567
82012
  // upgrade, never a wrong reuse.
81568
- locations.length === 0 ? or13(isNull10(discoverySessions.locations), eq62(discoverySessions.locations, locations)) : eq62(discoverySessions.locations, locations),
82013
+ locations.length === 0 ? or13(isNull10(discoverySessions.locations), eq63(discoverySessions.locations, locations)) : eq63(discoverySessions.locations, locations),
81569
82014
  // Seed provider set is identity: a different phrasing distribution
81570
82015
  // must never reuse another set's session. Null (= Gemini-only
81571
82016
  // default, including explicit ['gemini']) matches legacy rows.
81572
- seedProviders == null ? isNull10(discoverySessions.seedProviders) : eq62(discoverySessions.seedProviders, seedProviders),
81573
- inArray23(discoverySessions.status, [
82017
+ seedProviders == null ? isNull10(discoverySessions.seedProviders) : eq63(discoverySessions.seedProviders, seedProviders),
82018
+ inArray24(discoverySessions.status, [
81574
82019
  DiscoverySessionStatuses.queued,
81575
82020
  DiscoverySessionStatuses.seeding,
81576
82021
  DiscoverySessionStatuses.probing
81577
82022
  ]),
81578
82023
  gte16(discoverySessions.createdAt, ageFloorIso)
81579
- )).orderBy(desc26(discoverySessions.createdAt)).get();
82024
+ )).orderBy(desc27(discoverySessions.createdAt)).get();
81580
82025
  if (existing && existing.runId) {
81581
82026
  return { reused: true, sessionId: existing.id, runId: existing.runId };
81582
82027
  }
@@ -81645,7 +82090,7 @@ async function discoveryRoutes(app, opts) {
81645
82090
  const project = resolveProject(app.db, request.params.name);
81646
82091
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
81647
82092
  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();
82093
+ const rows = app.db.select().from(discoverySessions).where(eq63(discoverySessions.projectId, project.id)).orderBy(desc27(discoverySessions.createdAt)).limit(limit).all();
81649
82094
  return reply.send(rows.map(serializeSession));
81650
82095
  }
81651
82096
  );
@@ -81653,11 +82098,11 @@ async function discoveryRoutes(app, opts) {
81653
82098
  "/projects/:name/discover/sessions/:id",
81654
82099
  async (request, reply) => {
81655
82100
  const project = resolveProject(app.db, request.params.name);
81656
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82101
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81657
82102
  if (!session || session.projectId !== project.id) {
81658
82103
  throw notFound("Discovery session", request.params.id);
81659
82104
  }
81660
- const probeRows = app.db.select().from(discoveryProbes).where(eq62(discoveryProbes.sessionId, session.id)).all();
82105
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
81661
82106
  const detail = {
81662
82107
  ...serializeSession(session),
81663
82108
  probes: probeRows.map(serializeProbe)
@@ -81669,7 +82114,7 @@ async function discoveryRoutes(app, opts) {
81669
82114
  "/projects/:name/discover/sessions/:id/harvest",
81670
82115
  async (request, reply) => {
81671
82116
  const project = resolveProject(app.db, request.params.name);
81672
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82117
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81673
82118
  if (!session || session.projectId !== project.id) {
81674
82119
  throw notFound("Discovery session", request.params.id);
81675
82120
  }
@@ -81677,7 +82122,7 @@ async function discoveryRoutes(app, opts) {
81677
82122
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
81678
82123
  const applyAnchor = request.query.anchor !== "false";
81679
82124
  const provider = session.seedProvider ?? "gemini";
81680
- const probeRows = app.db.select().from(discoveryProbes).where(eq62(discoveryProbes.sessionId, session.id)).all();
82125
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
81681
82126
  const extract = opts.harvestSearchQueries;
81682
82127
  const probesWithQueries = probeRows.map((row) => {
81683
82128
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -81688,7 +82133,7 @@ async function discoveryRoutes(app, opts) {
81688
82133
  return { searchQueries: [] };
81689
82134
  }
81690
82135
  });
81691
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq62(queries.projectId, project.id)).all().map((r) => r.query);
82136
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query);
81692
82137
  const anchorTerms = buildHarvestAnchorTerms(
81693
82138
  [session.icpDescription ?? "", ...trackedQueries],
81694
82139
  effectiveDomains(project)
@@ -81735,12 +82180,12 @@ async function discoveryRoutes(app, opts) {
81735
82180
  "/projects/:name/discover/sessions/:id/promote",
81736
82181
  async (request, reply) => {
81737
82182
  const project = resolveProject(app.db, request.params.name);
81738
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82183
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81739
82184
  if (!session || session.projectId !== project.id) {
81740
82185
  throw notFound("Discovery session", request.params.id);
81741
82186
  }
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());
82187
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
82188
+ const existingCompetitors = app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase());
81744
82189
  const seenCompetitors = new Set(existingCompetitors);
81745
82190
  const cited = /* @__PURE__ */ new Set();
81746
82191
  const aspirational = /* @__PURE__ */ new Set();
@@ -81769,7 +82214,7 @@ async function discoveryRoutes(app, opts) {
81769
82214
  );
81770
82215
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
81771
82216
  const project = resolveProject(app.db, request.params.name);
81772
- const session = app.db.select().from(discoverySessions).where(eq62(discoverySessions.id, request.params.id)).get();
82217
+ const session = app.db.select().from(discoverySessions).where(eq63(discoverySessions.id, request.params.id)).get();
81773
82218
  if (!session || session.projectId !== project.id) {
81774
82219
  throw notFound("Discovery session", request.params.id);
81775
82220
  }
@@ -81792,7 +82237,7 @@ async function discoveryRoutes(app, opts) {
81792
82237
  const bucketSet = new Set(buckets);
81793
82238
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
81794
82239
  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();
82240
+ const probeRows = app.db.select().from(discoveryProbes).where(eq63(discoveryProbes.sessionId, session.id)).all();
81796
82241
  const candidateQueries = /* @__PURE__ */ new Set();
81797
82242
  for (const probe of probeRows) {
81798
82243
  if (!probe.bucket) continue;
@@ -81800,7 +82245,7 @@ async function discoveryRoutes(app, opts) {
81800
82245
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
81801
82246
  }
81802
82247
  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())
82248
+ app.db.select({ query: queries.query }).from(queries).where(eq63(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
81804
82249
  );
81805
82250
  const promotedQueries = [];
81806
82251
  const skippedQueries = [];
@@ -81816,7 +82261,7 @@ async function discoveryRoutes(app, opts) {
81816
82261
  const skippedCompetitors = [];
81817
82262
  if (includeCompetitors) {
81818
82263
  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())
82264
+ app.db.select({ domain: competitors.domain }).from(competitors).where(eq63(competitors.projectId, project.id)).all().map((r) => r.domain.toLowerCase())
81820
82265
  );
81821
82266
  const competitorMap = parseCompetitorMap(session.competitorMap);
81822
82267
  for (const entry of selectEligibleCompetitors(competitorMap, competitorTypes)) {
@@ -81935,7 +82380,7 @@ function selectEligibleCompetitors(competitorMap, competitorTypes) {
81935
82380
 
81936
82381
  // ../api-routes/src/discovery/orchestrate.ts
81937
82382
  import crypto47 from "crypto";
81938
- import { eq as eq63 } from "drizzle-orm";
82383
+ import { eq as eq64 } from "drizzle-orm";
81939
82384
  var DEFAULT_MAX_PROBES = 100;
81940
82385
  var ABSOLUTE_MAX_PROBES = 500;
81941
82386
  function classifyProbeBucket(input) {
@@ -82022,7 +82467,7 @@ async function executeDiscovery(opts) {
82022
82467
  status: DiscoverySessionStatuses.seeding,
82023
82468
  dedupThreshold,
82024
82469
  startedAt
82025
- }).where(eq63(discoverySessions.id, opts.sessionId)).run();
82470
+ }).where(eq64(discoverySessions.id, opts.sessionId)).run();
82026
82471
  const seedResult = await opts.deps.seed({
82027
82472
  project: opts.project,
82028
82473
  icpDescription: opts.icpDescription,
@@ -82083,7 +82528,7 @@ async function executeDiscovery(opts) {
82083
82528
  dedupBandPairFraction: dedupStats.bandPairFraction,
82084
82529
  dedupPairsTotal: dedupStats.pairsTotal,
82085
82530
  warning
82086
- }).where(eq63(discoverySessions.id, opts.sessionId)).run();
82531
+ }).where(eq64(discoverySessions.id, opts.sessionId)).run();
82087
82532
  const probeLocation = opts.locations?.[0];
82088
82533
  const probeResults = await mapWithConcurrency(
82089
82534
  probedCanonicals,
@@ -82137,7 +82582,7 @@ async function executeDiscovery(opts) {
82137
82582
  wastedCount: buckets["wasted-surface"],
82138
82583
  competitorMap,
82139
82584
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
82140
- }).where(eq63(discoverySessions.id, opts.sessionId)).run();
82585
+ }).where(eq64(discoverySessions.id, opts.sessionId)).run();
82141
82586
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
82142
82587
  return {
82143
82588
  buckets,
@@ -82177,7 +82622,7 @@ function markSessionFailed(db, sessionId, error) {
82177
82622
  status: DiscoverySessionStatuses.failed,
82178
82623
  error,
82179
82624
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
82180
- }).where(eq63(discoverySessions.id, sessionId)).run();
82625
+ }).where(eq64(discoverySessions.id, sessionId)).run();
82181
82626
  }
82182
82627
  function dedupeStrings(input) {
82183
82628
  const seen2 = /* @__PURE__ */ new Set();
@@ -82195,7 +82640,7 @@ function dedupeStrings(input) {
82195
82640
 
82196
82641
  // ../api-routes/src/technical-aeo.ts
82197
82642
  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";
82643
+ 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
82644
  import { alias } from "drizzle-orm/sqlite-core";
82200
82645
  var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
82201
82646
  ...SiteCrawlFetchedStates,
@@ -82408,8 +82853,8 @@ function parseLinkKind(value) {
82408
82853
  return allowed.data;
82409
82854
  }
82410
82855
  function linkKindFilter(linkKind) {
82411
- if (linkKind === SiteHealthLinkKinds.content) return eq64(siteCrawlEdges.isTemplate, false);
82412
- if (linkKind === SiteHealthLinkKinds.template) return eq64(siteCrawlEdges.isTemplate, true);
82856
+ if (linkKind === SiteHealthLinkKinds.content) return eq65(siteCrawlEdges.isTemplate, false);
82857
+ if (linkKind === SiteHealthLinkKinds.template) return eq65(siteCrawlEdges.isTemplate, true);
82413
82858
  return void 0;
82414
82859
  }
82415
82860
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -82514,50 +82959,50 @@ function changedFields(before, after, fields) {
82514
82959
  async function technicalAeoRoutes(app, opts) {
82515
82960
  const resolveCrawl = (projectId, runId) => {
82516
82961
  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),
82962
+ eq65(siteCrawlSnapshots.projectId, projectId),
82963
+ eq65(runs.projectId, projectId),
82964
+ eq65(runs.kind, RunKinds["site-audit"]),
82965
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82521
82966
  notProbeRun()
82522
82967
  ];
82523
82968
  if (runId) {
82524
- filters.push(eq64(siteCrawlSnapshots.runId, runId));
82969
+ filters.push(eq65(siteCrawlSnapshots.runId, runId));
82525
82970
  } else {
82526
- filters.push(eq64(siteCrawlSnapshots.complete, true), eq64(runs.status, RunStatuses.completed));
82971
+ filters.push(eq65(siteCrawlSnapshots.complete, true), eq65(runs.status, RunStatuses.completed));
82527
82972
  }
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();
82973
+ 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
82974
  };
82530
82975
  const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
82531
82976
  const pageInScope = (scope, selector) => {
82532
82977
  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)
82978
+ return app.db.select().from(siteCrawlPages).where(and55(
82979
+ eq65(siteCrawlPages.projectId, scope.projectId),
82980
+ eq65(siteCrawlPages.runId, scope.runId),
82981
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
82982
+ selector.nodeKey ? eq65(siteCrawlPages.nodeKey, selector.nodeKey) : eq65(siteCrawlPages.url, selector.url)
82538
82983
  )).orderBy(asc11(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
82539
82984
  };
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)
82985
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and55(
82986
+ eq65(siteCrawlPages.projectId, scope.projectId),
82987
+ eq65(siteCrawlPages.runId, scope.runId),
82988
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
82989
+ eq65(siteCrawlPages.depth, 0)
82545
82990
  )).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),
82991
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and55(
82992
+ eq65(runs.id, runId),
82993
+ eq65(runs.projectId, projectId),
82994
+ eq65(runs.kind, RunKinds["site-audit"]),
82995
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82551
82996
  notProbeRun()
82552
82997
  )).limit(1).get());
82553
82998
  const assertKnownAuditRun = (projectId, runId) => {
82554
82999
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
82555
83000
  };
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),
83001
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
83002
+ eq65(siteAuditSnapshots.projectId, projectId),
83003
+ eq65(runs.projectId, projectId),
83004
+ eq65(runs.kind, RunKinds["site-audit"]),
83005
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82561
83006
  notProbeRun()
82562
83007
  )).limit(1).get());
82563
83008
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -82578,20 +83023,20 @@ async function technicalAeoRoutes(app, opts) {
82578
83023
  app.get("/projects/:name/technical-aeo", async (request) => {
82579
83024
  const project = resolveProject(app.db, request.params.name);
82580
83025
  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),
83026
+ eq65(siteAuditSnapshots.projectId, project.id),
83027
+ eq65(runs.projectId, project.id),
83028
+ eq65(runs.kind, RunKinds["site-audit"]),
83029
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82585
83030
  notProbeRun()
82586
83031
  ];
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();
83032
+ const targetFilters = request.query.runId ? [...baseFilters, eq65(siteAuditSnapshots.runId, request.query.runId)] : baseFilters;
83033
+ 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
83034
  if (!latest) {
82590
83035
  if (request.query.runId) throw notFound("Site audit run", request.query.runId);
82591
83036
  return emptyScore(project.name);
82592
83037
  }
82593
83038
  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;
83039
+ 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
83040
  const deltaScore = previous ? snap.aggregateScore - previous.aggregateScore : null;
82596
83041
  const trend = deltaScore == null ? null : deltaScore > 0 ? SiteAuditTrendDirections.up : deltaScore < 0 ? SiteAuditTrendDirections.down : SiteAuditTrendDirections.flat;
82597
83042
  return {
@@ -82618,14 +83063,14 @@ async function technicalAeoRoutes(app, opts) {
82618
83063
  app.get("/projects/:name/technical-aeo/pages", async (request) => {
82619
83064
  const project = resolveProject(app.db, request.params.name);
82620
83065
  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),
83066
+ eq65(siteAuditSnapshots.projectId, project.id),
83067
+ eq65(runs.projectId, project.id),
83068
+ eq65(runs.kind, RunKinds["site-audit"]),
83069
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82625
83070
  notProbeRun()
82626
83071
  ];
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();
83072
+ if (request.query.runId) targetFilters.push(eq65(siteAuditSnapshots.runId, request.query.runId));
83073
+ 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
83074
  if (!latest && request.query.runId) {
82630
83075
  throw notFound("Site audit run", request.query.runId);
82631
83076
  }
@@ -82633,14 +83078,14 @@ async function technicalAeoRoutes(app, opts) {
82633
83078
  return { project: project.name, runId: null, auditedAt: null, total: 0, pages: [] };
82634
83079
  }
82635
83080
  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);
83081
+ const conds = [eq65(siteAuditPages.projectId, project.id), eq65(siteAuditPages.runId, latest.runId)];
83082
+ if (statusFilter) conds.push(eq65(siteAuditPages.status, statusFilter));
83083
+ const where = and55(...conds);
82639
83084
  const totalRow = app.db.select({ value: count2() }).from(siteAuditPages).where(where).get();
82640
83085
  const total = totalRow?.value ?? 0;
82641
83086
  const limit = parsePositiveInt(request.query.limit, 100, 500);
82642
83087
  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);
83088
+ const orderBy = request.query.sort === "score-desc" ? desc28(siteAuditPages.overallScore) : request.query.sort === "url" ? asc11(siteAuditPages.url) : asc11(siteAuditPages.overallScore);
82644
83089
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
82645
83090
  const pages = rows.map((row) => ({
82646
83091
  url: row.url,
@@ -82659,13 +83104,13 @@ async function technicalAeoRoutes(app, opts) {
82659
83104
  auditedAt: siteAuditSnapshots.auditedAt,
82660
83105
  aggregateScore: siteAuditSnapshots.aggregateScore,
82661
83106
  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),
83107
+ }).from(siteAuditSnapshots).innerJoin(runs, eq65(siteAuditSnapshots.runId, runs.id)).where(and55(
83108
+ eq65(siteAuditSnapshots.projectId, project.id),
83109
+ eq65(runs.projectId, project.id),
83110
+ eq65(runs.kind, RunKinds["site-audit"]),
83111
+ inArray25(runs.status, SURFACEABLE_STATUSES),
82667
83112
  notProbeRun()
82668
- )).orderBy(desc27(siteAuditSnapshots.createdAt)).limit(limit).all();
83113
+ )).orderBy(desc28(siteAuditSnapshots.createdAt)).limit(limit).all();
82669
83114
  return { project: project.name, points: rows.reverse() };
82670
83115
  });
82671
83116
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -82757,10 +83202,10 @@ async function technicalAeoRoutes(app, opts) {
82757
83202
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
82758
83203
  snapshot.rootUrl
82759
83204
  )?.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)
83205
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and55(
83206
+ eq65(siteCrawlGraphLayouts.projectId, project.id),
83207
+ eq65(siteCrawlGraphLayouts.runId, snapshot.runId),
83208
+ eq65(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
82764
83209
  )).limit(1).get();
82765
83210
  if (!persistedLayout) {
82766
83211
  return {
@@ -82811,9 +83256,9 @@ async function technicalAeoRoutes(app, opts) {
82811
83256
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
82812
83257
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
82813
83258
  const graphScope = [
82814
- eq64(siteCrawlGraphNodes.projectId, project.id),
82815
- eq64(siteCrawlGraphNodes.runId, snapshot.runId),
82816
- eq64(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
83259
+ eq65(siteCrawlGraphNodes.projectId, project.id),
83260
+ eq65(siteCrawlGraphNodes.runId, snapshot.runId),
83261
+ eq65(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
82817
83262
  ];
82818
83263
  const nodeRows = app.db.select({
82819
83264
  nodeKey: siteCrawlPages.nodeKey,
@@ -82832,17 +83277,17 @@ async function technicalAeoRoutes(app, opts) {
82832
83277
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
82833
83278
  x: siteCrawlGraphNodes.x,
82834
83279
  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();
83280
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and55(
83281
+ eq65(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
83282
+ eq65(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
83283
+ eq65(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
83284
+ eq65(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
83285
+ )).where(and55(...graphScope, lt10(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc11(siteCrawlGraphNodes.sampleRank)).all();
82841
83286
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
82842
83287
  ...row,
82843
83288
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey })
82844
83289
  }));
82845
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq64(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
83290
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq65(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
82846
83291
  const edges = app.db.select({
82847
83292
  edgeKey: siteCrawlGraphEdges.edgeKey,
82848
83293
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -82850,22 +83295,22 @@ async function technicalAeoRoutes(app, opts) {
82850
83295
  followable: siteCrawlGraphEdges.followable,
82851
83296
  occurrences: siteCrawlGraphEdges.occurrences,
82852
83297
  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),
83298
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and55(
83299
+ eq65(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
83300
+ eq65(graphSourceNode.runId, siteCrawlGraphEdges.runId),
83301
+ eq65(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
83302
+ eq65(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
82858
83303
  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),
83304
+ )).innerJoin(graphTargetNode, and55(
83305
+ eq65(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
83306
+ eq65(graphTargetNode.runId, siteCrawlGraphEdges.runId),
83307
+ eq65(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
83308
+ eq65(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
82864
83309
  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),
83310
+ )).where(and55(
83311
+ eq65(siteCrawlGraphEdges.projectId, project.id),
83312
+ eq65(siteCrawlGraphEdges.runId, snapshot.runId),
83313
+ eq65(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
82869
83314
  lt10(siteCrawlGraphEdges.sampleRank, maxEdges),
82870
83315
  graphLinkKindFilter
82871
83316
  )).orderBy(asc11(siteCrawlGraphEdges.sampleRank)).all();
@@ -82959,15 +83404,15 @@ async function technicalAeoRoutes(app, opts) {
82959
83404
  let frontier = [focus.nodeKey];
82960
83405
  let queryTruncated = false;
82961
83406
  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),
83407
+ const candidates = app.db.select().from(siteCrawlEdges).where(and55(
83408
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83409
+ eq65(siteCrawlEdges.runId, scope.runId),
83410
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83411
+ eq65(siteCrawlEdges.internal, true),
82967
83412
  isNotNull5(siteCrawlEdges.targetNodeKey),
82968
83413
  or14(
82969
- inArray24(siteCrawlEdges.sourceNodeKey, frontier),
82970
- inArray24(siteCrawlEdges.targetNodeKey, frontier)
83414
+ inArray25(siteCrawlEdges.sourceNodeKey, frontier),
83415
+ inArray25(siteCrawlEdges.targetNodeKey, frontier)
82971
83416
  )
82972
83417
  )).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
82973
83418
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
@@ -83000,22 +83445,22 @@ async function technicalAeoRoutes(app, opts) {
83000
83445
  frontier = [...next].sort();
83001
83446
  }
83002
83447
  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)
83448
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
83449
+ eq65(siteCrawlPages.projectId, scope.projectId),
83450
+ eq65(siteCrawlPages.runId, scope.runId),
83451
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83452
+ inArray25(siteCrawlPages.nodeKey, nodeKeys)
83008
83453
  )).all();
83009
83454
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
83010
83455
  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),
83456
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and55(
83457
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83458
+ eq65(siteCrawlEdges.runId, scope.runId),
83459
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83460
+ eq65(siteCrawlEdges.internal, true),
83016
83461
  isNotNull5(siteCrawlEdges.targetNodeKey),
83017
- inArray24(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
83018
- inArray24(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
83462
+ inArray25(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
83463
+ inArray25(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
83019
83464
  )).limit(1).get();
83020
83465
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
83021
83466
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -83110,23 +83555,23 @@ async function technicalAeoRoutes(app, opts) {
83110
83555
  let found = fromPage.nodeKey === toPage.nodeKey;
83111
83556
  let truncated = false;
83112
83557
  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"),
83558
+ const candidates = app.db.select().from(siteCrawlEdges).where(and55(
83559
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83560
+ eq65(siteCrawlEdges.runId, scope.runId),
83561
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83562
+ eq65(siteCrawlEdges.internal, true),
83563
+ eq65(siteCrawlEdges.followable, true),
83564
+ eq65(siteCrawlEdges.relation, "anchor"),
83120
83565
  isNotNull5(siteCrawlEdges.targetNodeKey),
83121
- inArray24(siteCrawlEdges.sourceNodeKey, frontier)
83566
+ inArray25(siteCrawlEdges.sourceNodeKey, frontier)
83122
83567
  )).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
83123
83568
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
83124
83569
  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)
83570
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and55(
83571
+ eq65(siteCrawlPages.projectId, scope.projectId),
83572
+ eq65(siteCrawlPages.runId, scope.runId),
83573
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83574
+ inArray25(siteCrawlPages.nodeKey, candidateTargetKeys)
83130
83575
  )).all().map((row) => row.nodeKey));
83131
83576
  const next = /* @__PURE__ */ new Set();
83132
83577
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -83168,11 +83613,11 @@ async function technicalAeoRoutes(app, opts) {
83168
83613
  pathEdges.unshift(step.edge);
83169
83614
  pathKeys.unshift(step.nodeKey);
83170
83615
  }
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)
83616
+ const pathRows = app.db.select().from(siteCrawlPages).where(and55(
83617
+ eq65(siteCrawlPages.projectId, scope.projectId),
83618
+ eq65(siteCrawlPages.runId, scope.runId),
83619
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83620
+ inArray25(siteCrawlPages.nodeKey, pathKeys)
83176
83621
  )).all();
83177
83622
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
83178
83623
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -83201,25 +83646,25 @@ async function technicalAeoRoutes(app, opts) {
83201
83646
  throw validationError("change must be all, added, removed, or changed");
83202
83647
  }
83203
83648
  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),
83649
+ eq65(siteCrawlSnapshots.projectId, project.id),
83650
+ eq65(runs.projectId, project.id),
83651
+ eq65(runs.kind, RunKinds["site-audit"]),
83652
+ eq65(runs.status, RunStatuses.completed),
83653
+ eq65(siteCrawlSnapshots.complete, true),
83209
83654
  notProbeRun()
83210
83655
  ];
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();
83656
+ 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
83657
  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(
83658
+ 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
83659
  ...completeSnapshotFilters,
83215
83660
  or14(
83216
83661
  lt10(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
83217
- and54(
83218
- eq64(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
83662
+ and55(
83663
+ eq65(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
83219
83664
  lt10(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
83220
83665
  )
83221
83666
  )
83222
- )).orderBy(desc27(siteCrawlSnapshots.createdAt), desc27(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
83667
+ )).orderBy(desc28(siteCrawlSnapshots.createdAt), desc28(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
83223
83668
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
83224
83669
  if (!afterTarget) {
83225
83670
  return { project: project.name, state: "unavailable", reason: "no-crawl", fromRunId: null, toRunId: null };
@@ -83450,18 +83895,18 @@ async function technicalAeoRoutes(app, opts) {
83450
83895
  const visibleKeys = keyRows.slice(0, limit);
83451
83896
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
83452
83897
  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)
83898
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and55(
83899
+ eq65(siteCrawlPages.projectId, scope.projectId),
83900
+ eq65(siteCrawlPages.runId, scope.runId),
83901
+ eq65(siteCrawlPages.attemptId, scope.attemptId),
83902
+ inArray25(siteCrawlPages.nodeKey, pageKeys)
83458
83903
  )).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)
83904
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and55(
83905
+ eq65(siteCrawlEdges.projectId, scope.projectId),
83906
+ eq65(siteCrawlEdges.runId, scope.runId),
83907
+ eq65(siteCrawlEdges.attemptId, scope.attemptId),
83908
+ eq65(siteCrawlEdges.internal, true),
83909
+ inArray25(siteCrawlEdges.edgeKey, linkKeys)
83465
83910
  )).all();
83466
83911
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
83467
83912
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -83586,30 +84031,30 @@ async function technicalAeoRoutes(app, opts) {
83586
84031
  return { project: project.name, hasCrawlData: true, runId: snapshot.runId, total: 0, nextCursor: null, healthStateFilter: null, pages: [] };
83587
84032
  }
83588
84033
  const filters = [
83589
- eq64(siteCrawlPages.projectId, project.id),
83590
- eq64(siteCrawlPages.runId, snapshot.runId),
83591
- eq64(siteCrawlPages.attemptId, snapshot.attemptId)
84034
+ eq65(siteCrawlPages.projectId, project.id),
84035
+ eq65(siteCrawlPages.runId, snapshot.runId),
84036
+ eq65(siteCrawlPages.attemptId, snapshot.attemptId)
83592
84037
  ];
83593
- if (request.query.nodeKey) filters.push(eq64(siteCrawlPages.nodeKey, request.query.nodeKey));
84038
+ if (request.query.nodeKey) filters.push(eq65(siteCrawlPages.nodeKey, request.query.nodeKey));
83594
84039
  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));
84040
+ if (inventoryEligible != null) filters.push(eq65(siteCrawlPages.inventoryEligible, inventoryEligible));
84041
+ if (request.query.fetchState) filters.push(eq65(siteCrawlPages.fetchState, request.query.fetchState));
84042
+ if (request.query.indexabilityState) filters.push(eq65(siteCrawlPages.indexabilityState, request.query.indexabilityState));
84043
+ if (request.query.auditState) filters.push(eq65(siteCrawlPages.auditState, request.query.auditState));
83599
84044
  const healthState = parseSiteHealthState(request.query.healthState);
83600
84045
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
83601
84046
  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)];
84047
+ 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
84048
  let healthStateFilter = null;
83604
84049
  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),
84050
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and55(
84051
+ eq65(siteCrawlPages.projectId, project.id),
84052
+ eq65(siteCrawlPages.runId, snapshot.runId),
84053
+ eq65(siteCrawlPages.attemptId, snapshot.attemptId),
83609
84054
  isNull11(siteCrawlPages.healthState)
83610
84055
  )).limit(1).get();
83611
84056
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
83612
- if (healthStateFilter === "applied") filters.push(eq64(siteCrawlPages.healthState, healthState));
84057
+ if (healthStateFilter === "applied") filters.push(eq65(siteCrawlPages.healthState, healthState));
83613
84058
  }
83614
84059
  if (healthStateFilter === "unavailable-legacy-scan") {
83615
84060
  return {
@@ -83622,7 +84067,7 @@ async function technicalAeoRoutes(app, opts) {
83622
84067
  pages: []
83623
84068
  };
83624
84069
  }
83625
- const where = and54(...filters);
84070
+ const where = and55(...filters);
83626
84071
  const total = app.db.select({ value: count2() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
83627
84072
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
83628
84073
  const nextOffset = offset + rows.length;
@@ -83653,10 +84098,10 @@ async function technicalAeoRoutes(app, opts) {
83653
84098
  url: siteCrawlPages.url,
83654
84099
  inventoryEligible: siteCrawlPages.inventoryEligible,
83655
84100
  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)
84101
+ }).from(siteCrawlPages).where(and55(
84102
+ eq65(siteCrawlPages.projectId, project.id),
84103
+ eq65(siteCrawlPages.runId, snapshot.runId),
84104
+ eq65(siteCrawlPages.attemptId, snapshot.attemptId)
83660
84105
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
83661
84106
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
83662
84107
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -83735,17 +84180,17 @@ async function technicalAeoRoutes(app, opts) {
83735
84180
  };
83736
84181
  }
83737
84182
  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),
84183
+ eq65(siteCrawlEdges.projectId, project.id),
84184
+ eq65(siteCrawlEdges.runId, snapshot.runId),
84185
+ eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
84186
+ eq65(siteCrawlEdges.internal, true),
83742
84187
  linkKindFilter(linkKind)
83743
84188
  ];
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));
84189
+ if (request.query.sourceUrl) filters.push(eq65(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
84190
+ if (request.query.targetUrl) filters.push(eq65(siteCrawlEdges.targetUrl, request.query.targetUrl));
83746
84191
  const followable = parseBoolean2(request.query.followable);
83747
- if (followable != null) filters.push(eq64(siteCrawlEdges.followable, followable));
83748
- const where = and54(...filters);
84192
+ if (followable != null) filters.push(eq65(siteCrawlEdges.followable, followable));
84193
+ const where = and55(...filters);
83749
84194
  const total = app.db.select({ value: count2() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
83750
84195
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
83751
84196
  const offset = decodeCursor(request.query.cursor);
@@ -83803,17 +84248,17 @@ async function technicalAeoRoutes(app, opts) {
83803
84248
  };
83804
84249
  }
83805
84250
  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),
84251
+ eq65(siteCrawlEdges.projectId, project.id),
84252
+ eq65(siteCrawlEdges.runId, snapshot.runId),
84253
+ eq65(siteCrawlEdges.attemptId, snapshot.attemptId),
84254
+ eq65(siteCrawlEdges.internal, true),
83810
84255
  linkKindFilter(linkKind)
83811
84256
  ];
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);
84257
+ 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);
84258
+ 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
84259
  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();
84260
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, inboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
84261
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and55(...scope, outboundMatch)).orderBy(asc11(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
83817
84262
  return {
83818
84263
  project: project.name,
83819
84264
  hasCrawlData: true,
@@ -83846,11 +84291,11 @@ async function technicalAeoRoutes(app, opts) {
83846
84291
  if (!snapshot.attemptId) {
83847
84292
  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
84293
  }
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"),
84294
+ const where = and55(
84295
+ eq65(siteCrawlFindings.projectId, project.id),
84296
+ eq65(siteCrawlFindings.runId, snapshot.runId),
84297
+ eq65(siteCrawlFindings.attemptId, snapshot.attemptId),
84298
+ eq65(siteCrawlFindings.findingType, "dead-link"),
83854
84299
  // Belt and braces on the client-facing surface: a row without a status
83855
84300
  // code is not evidence of a broken link, and this response is where such
83856
84301
  // a row would be read as one. Migration 140 removed every stored row that
@@ -83899,19 +84344,19 @@ async function technicalAeoRoutes(app, opts) {
83899
84344
  createdAt: runs.createdAt,
83900
84345
  startedAt: runs.startedAt,
83901
84346
  finishedAt: runs.finishedAt
83902
- }).from(runs).where(and54(
83903
- eq64(runs.projectId, project.id),
83904
- eq64(runs.kind, RunKinds["site-audit"]),
84347
+ }).from(runs).where(and55(
84348
+ eq65(runs.projectId, project.id),
84349
+ eq65(runs.kind, RunKinds["site-audit"]),
83905
84350
  notProbeRun()
83906
- )).orderBy(desc27(runs.createdAt), desc27(runs.id)).limit(limit).all();
84351
+ )).orderBy(desc28(runs.createdAt), desc28(runs.id)).limit(limit).all();
83907
84352
  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),
84353
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq65(siteCrawlSnapshots.runId, runs.id)).where(and55(
84354
+ eq65(siteCrawlSnapshots.projectId, project.id),
84355
+ eq65(runs.projectId, project.id),
84356
+ eq65(runs.kind, RunKinds["site-audit"]),
84357
+ inArray25(runs.status, SURFACEABLE_STATUSES),
83913
84358
  notProbeRun(),
83914
- inArray24(siteCrawlSnapshots.runId, runIds)
84359
+ inArray25(siteCrawlSnapshots.runId, runIds)
83915
84360
  )).all().map((row) => row.runId));
83916
84361
  return {
83917
84362
  project: project.name,
@@ -83924,21 +84369,21 @@ async function technicalAeoRoutes(app, opts) {
83924
84369
  });
83925
84370
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
83926
84371
  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"]),
84372
+ const run = app.db.select().from(runs).where(and55(
84373
+ eq65(runs.id, request.params.runId),
84374
+ eq65(runs.projectId, project.id),
84375
+ eq65(runs.kind, RunKinds["site-audit"]),
83931
84376
  notProbeRun()
83932
84377
  )).get();
83933
84378
  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)
84379
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and55(
84380
+ eq65(siteCrawlAttempts.projectId, project.id),
84381
+ eq65(siteCrawlAttempts.runId, run.id)
84382
+ )).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
84383
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and55(
84384
+ eq65(siteCrawlGraphLayouts.projectId, project.id),
84385
+ eq65(siteCrawlGraphLayouts.runId, run.id),
84386
+ eq65(siteCrawlGraphLayouts.attemptId, attempt.id)
83942
84387
  )).get() : void 0;
83943
84388
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
83944
84389
  return {
@@ -83969,19 +84414,19 @@ async function technicalAeoRoutes(app, opts) {
83969
84414
  };
83970
84415
  });
83971
84416
  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();
84417
+ const project = tx.select().from(projects).where(eq65(projects.name, request.params.name)).get();
83973
84418
  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"]),
84419
+ const run = tx.select().from(runs).where(and55(
84420
+ eq65(runs.id, request.params.runId),
84421
+ eq65(runs.projectId, project.id),
84422
+ eq65(runs.kind, RunKinds["site-audit"]),
83978
84423
  notProbeRun()
83979
84424
  )).get();
83980
84425
  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();
84426
+ const attempt = tx.select().from(siteCrawlAttempts).where(and55(
84427
+ eq65(siteCrawlAttempts.projectId, project.id),
84428
+ eq65(siteCrawlAttempts.runId, run.id)
84429
+ )).orderBy(desc28(siteCrawlAttempts.attemptNumber), desc28(siteCrawlAttempts.updatedAt)).limit(1).get();
83985
84430
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
83986
84431
  const base = {
83987
84432
  project: project.name,
@@ -83994,11 +84439,11 @@ async function technicalAeoRoutes(app, opts) {
83994
84439
  if (!attempt) {
83995
84440
  return { ...base, pagesAudited: 0, examples: [] };
83996
84441
  }
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")
84442
+ const auditedWhere = and55(
84443
+ eq65(siteCrawlPages.projectId, project.id),
84444
+ eq65(siteCrawlPages.runId, run.id),
84445
+ eq65(siteCrawlPages.attemptId, attempt.id),
84446
+ eq65(siteCrawlPages.auditState, "success")
84002
84447
  );
84003
84448
  const pagesAudited = tx.select({ value: count2() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
84004
84449
  if (state !== "collecting") {
@@ -84009,7 +84454,7 @@ async function technicalAeoRoutes(app, opts) {
84009
84454
  url: siteCrawlPages.url,
84010
84455
  auditScore: siteCrawlPages.auditScore,
84011
84456
  auditFields: siteCrawlPages.auditFields
84012
- }).from(siteCrawlPages).where(and54(
84457
+ }).from(siteCrawlPages).where(and55(
84013
84458
  auditedWhere,
84014
84459
  isNotNull5(siteCrawlPages.auditScore),
84015
84460
  lt10(siteCrawlPages.auditScore, 70)
@@ -84045,13 +84490,13 @@ async function technicalAeoRoutes(app, opts) {
84045
84490
  status: runs.status,
84046
84491
  identityKey: siteCrawlRunRequests.identityKey,
84047
84492
  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])
84493
+ }).from(runs).leftJoin(siteCrawlRunRequests, and55(
84494
+ eq65(siteCrawlRunRequests.projectId, runs.projectId),
84495
+ eq65(siteCrawlRunRequests.runId, runs.id)
84496
+ )).where(and55(
84497
+ eq65(runs.projectId, project.id),
84498
+ eq65(runs.kind, RunKinds["site-audit"]),
84499
+ inArray25(runs.status, [RunStatuses.queued, RunStatuses.running])
84055
84500
  )).get();
84056
84501
  if (existing) {
84057
84502
  if (existing.identityKey === identityKey) {
@@ -84105,7 +84550,7 @@ async function technicalAeoRoutes(app, opts) {
84105
84550
  // ../api-routes/src/research.ts
84106
84551
  import crypto49 from "crypto";
84107
84552
  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";
84553
+ 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
84554
  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
84555
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
84111
84556
  async function researchRoutes(app, opts) {
@@ -84123,7 +84568,7 @@ async function researchRoutes(app, opts) {
84123
84568
  const input = parsed.data;
84124
84569
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
84125
84570
  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;
84571
+ 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
84572
  if (existingReceipt) {
84128
84573
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
84129
84574
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -84155,7 +84600,7 @@ async function researchRoutes(app, opts) {
84155
84600
  const initiatedBy = researchPrincipal(request);
84156
84601
  const decision = app.db.transaction((tx) => {
84157
84602
  if (input.idempotencyKey) {
84158
- const existing = tx.select().from(researchRuns).where(and55(eq65(researchRuns.projectId, project.id), eq65(researchRuns.idempotencyKey, input.idempotencyKey))).get();
84603
+ const existing = tx.select().from(researchRuns).where(and56(eq66(researchRuns.projectId, project.id), eq66(researchRuns.idempotencyKey, input.idempotencyKey))).get();
84159
84604
  if (existing) {
84160
84605
  assertSameDirectResearchRequest(existing, input, requestHash2);
84161
84606
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -84163,8 +84608,8 @@ async function researchRoutes(app, opts) {
84163
84608
  }
84164
84609
  if (initiatedBy?.limited) {
84165
84610
  const { start, end, date } = utcDayBounds(now);
84166
- const used = tx.select({ value: count3() }).from(researchRuns).where(and55(
84167
- eq65(researchRuns.projectId, project.id),
84611
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
84612
+ eq66(researchRuns.projectId, project.id),
84168
84613
  gte17(researchRuns.createdAt, start),
84169
84614
  lt11(researchRuns.createdAt, end),
84170
84615
  sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -84195,15 +84640,15 @@ async function researchRoutes(app, opts) {
84195
84640
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
84196
84641
  const requestHash2 = researchBatchRequestHash(input);
84197
84642
  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])
84643
+ const firstReceipt = app.db.select().from(researchRuns).where(and56(
84644
+ eq66(researchRuns.projectId, project.id),
84645
+ eq66(researchRuns.idempotencyKey, receiptKeys[0])
84201
84646
  )).get();
84202
84647
  if (firstReceipt) {
84203
84648
  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)
84649
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and56(
84650
+ eq66(researchRuns.projectId, project.id),
84651
+ eq66(researchRuns.idempotencyKey, key)
84207
84652
  )).get());
84208
84653
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
84209
84654
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -84218,8 +84663,8 @@ async function researchRoutes(app, opts) {
84218
84663
  const decision = app.db.transaction((tx) => {
84219
84664
  if (initiatedBy?.limited) {
84220
84665
  const { start, end, date } = utcDayBounds(now);
84221
- const used = tx.select({ value: count3() }).from(researchRuns).where(and55(
84222
- eq65(researchRuns.projectId, project.id),
84666
+ const used = tx.select({ value: count3() }).from(researchRuns).where(and56(
84667
+ eq66(researchRuns.projectId, project.id),
84223
84668
  gte17(researchRuns.createdAt, start),
84224
84669
  lt11(researchRuns.createdAt, end),
84225
84670
  sql26`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
@@ -84273,10 +84718,10 @@ async function researchRoutes(app, opts) {
84273
84718
  const requested = Number.parseInt(request.query.limit ?? "", 10);
84274
84719
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
84275
84720
  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();
84721
+ const rows = app.db.select().from(researchRuns).where(and56(
84722
+ eq66(researchRuns.projectId, project.id),
84723
+ cursor ? or15(lt11(researchRuns.createdAt, cursor.createdAt), and56(eq66(researchRuns.createdAt, cursor.createdAt), lt11(researchRuns.id, cursor.id))) : void 0
84724
+ )).orderBy(desc29(researchRuns.createdAt), desc29(researchRuns.id)).limit(limit + 1).all();
84280
84725
  const runs2 = rows.slice(0, limit).map(serializeRun);
84281
84726
  const last = runs2.at(-1);
84282
84727
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -84402,9 +84847,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
84402
84847
  return stored;
84403
84848
  }
84404
84849
  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)
84850
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and56(
84851
+ eq66(measurementQueryTemplates.projectId, projectId),
84852
+ eq66(measurementQueryTemplates.id, selection.templateId)
84408
84853
  )).get();
84409
84854
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
84410
84855
  const templateVersion = savedTemplate.updatedAt;
@@ -84413,7 +84858,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
84413
84858
  }
84414
84859
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
84415
84860
  if (bindingLocation) {
84416
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq65(projects.id, projectId)).get();
84861
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq66(projects.id, projectId)).get();
84417
84862
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
84418
84863
  }
84419
84864
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -84426,9 +84871,9 @@ function classifyResearchQueries(project, plan, queries2) {
84426
84871
  return queries2.map((query) => classifier?.classify(query) ?? null);
84427
84872
  }
84428
84873
  function getDetail(app, projectId, id) {
84429
- const row = app.db.select().from(researchRuns).where(and55(eq65(researchRuns.id, id), eq65(researchRuns.projectId, projectId))).get();
84874
+ const row = app.db.select().from(researchRuns).where(and56(eq66(researchRuns.id, id), eq66(researchRuns.projectId, projectId))).get();
84430
84875
  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);
84876
+ const queries2 = app.db.select().from(researchRunQueries).where(eq66(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
84432
84877
  return { ...serializeRun(row), queries: queries2 };
84433
84878
  }
84434
84879
  function serializeRun(row) {
@@ -84458,7 +84903,7 @@ function utcDayBounds(now) {
84458
84903
 
84459
84904
  // ../api-routes/src/simple-measurement-definitions.ts
84460
84905
  import crypto50 from "crypto";
84461
- import { and as and56, eq as eq66 } from "drizzle-orm";
84906
+ import { and as and57, eq as eq67 } from "drizzle-orm";
84462
84907
  function captureSimpleMeasurementDefinition(db, input) {
84463
84908
  return db.transaction((tx) => {
84464
84909
  const run = tx.select({
@@ -84466,7 +84911,7 @@ function captureSimpleMeasurementDefinition(db, input) {
84466
84911
  trigger: runs.trigger,
84467
84912
  status: runs.status,
84468
84913
  measurementPlanVersionId: runs.measurementPlanVersionId
84469
- }).from(runs).where(and56(eq66(runs.projectId, input.projectId), eq66(runs.id, input.runId))).get();
84914
+ }).from(runs).where(and57(eq67(runs.projectId, input.projectId), eq67(runs.id, input.runId))).get();
84470
84915
  if (!run) throw notFound("Run", input.runId);
84471
84916
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
84472
84917
  return null;
@@ -84476,7 +84921,7 @@ function captureSimpleMeasurementDefinition(db, input) {
84476
84921
  if (definition.queries.some((query) => query.queryClass !== (classifier?.classify(query.queryText) ?? null))) {
84477
84922
  throw validationError("Query classes must match the captured identity and text.");
84478
84923
  }
84479
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and56(eq66(simpleMeasurementDefinitions.projectId, input.projectId), eq66(simpleMeasurementDefinitions.runId, input.runId))).get();
84924
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and57(eq67(simpleMeasurementDefinitions.projectId, input.projectId), eq67(simpleMeasurementDefinitions.runId, input.runId))).get();
84480
84925
  if (existing) {
84481
84926
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
84482
84927
  const replayDefinition = {
@@ -84497,15 +84942,15 @@ function captureSimpleMeasurementDefinition(db, input) {
84497
84942
  if (run.status !== RunStatuses.running) {
84498
84943
  throw validationError("Only a running run can capture a new measurement definition.");
84499
84944
  }
84500
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq66(querySnapshots.runId, input.runId)).limit(1).get()) {
84945
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq67(querySnapshots.runId, input.runId)).limit(1).get()) {
84501
84946
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
84502
84947
  }
84503
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq66(queries.projectId, input.projectId)).all().map((query) => query.id));
84948
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq67(queries.projectId, input.projectId)).all().map((query) => query.id));
84504
84949
  if (definition.queries.some((query) => !projectQueryIds.has(query.queryId))) {
84505
84950
  throw validationError("Every captured query must belong to the run project.");
84506
84951
  }
84507
84952
  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));
84953
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq67(competitors.projectId, input.projectId)).all().map((competitor) => competitor.domain));
84509
84954
  const capturedDomains = new Set(definition.competitors.map((competitor) => competitor.domain));
84510
84955
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
84511
84956
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
@@ -85449,10 +85894,10 @@ var log = createLogger("IntelligenceService");
85449
85894
  var runChronologyKey = sql27`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
85450
85895
  function analyzableRunPredicates(projectId) {
85451
85896
  return [
85452
- eq67(runs.projectId, projectId),
85897
+ eq68(runs.projectId, projectId),
85453
85898
  // 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)),
85899
+ eq68(runs.kind, RunKinds["answer-visibility"]),
85900
+ or16(eq68(runs.status, RunStatuses.completed), eq68(runs.status, RunStatuses.partial)),
85456
85901
  // Defensive: RunCoordinator already skips probes before analyzeAndPersist
85457
85902
  // is called, but a future call site invoking it directly for a probe must
85458
85903
  // still not pollute the intelligence window.
@@ -85497,16 +85942,16 @@ var IntelligenceService = class {
85497
85942
  * Returns the analysis result for the coordinator to inspect (e.g. for webhook dispatch).
85498
85943
  */
85499
85944
  analyzeAndPersist(runId, projectId) {
85500
- const currentRunRecord = this.db.select().from(runs).where(and57(eq67(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
85945
+ const currentRunRecord = this.db.select().from(runs).where(and58(eq68(runs.id, runId), ...analyzableRunPredicates(projectId))).get();
85501
85946
  if (!currentRunRecord) {
85502
85947
  log.info("intelligence.skip", { runId, reason: "run not eligible for analysis" });
85503
85948
  return null;
85504
85949
  }
85505
85950
  const currentLocation = currentRunRecord.location ?? null;
85506
85951
  const recentRuns = this.db.select().from(runs).where(
85507
- and57(
85952
+ and58(
85508
85953
  ...analyzableRunPredicates(projectId),
85509
- currentLocation === null ? isNull12(runs.location) : eq67(runs.location, currentLocation),
85954
+ currentLocation === null ? isNull12(runs.location) : eq68(runs.location, currentLocation),
85510
85955
  // Never look forward. The window is this run's own past, so
85511
85956
  // re-analyzing a historical run compares it against its true
85512
85957
  // predecessor rather than against sweeps that came after it.
@@ -85514,10 +85959,10 @@ var IntelligenceService = class {
85514
85959
  // MEASUREMENT: only runs that actually wrote snapshots — see above.
85515
85960
  // Index seek on idx_snapshots_run, short-circuited on the first row.
85516
85961
  exists(
85517
- this.db.select({ one: sql27`1` }).from(querySnapshots).where(eq67(querySnapshots.runId, runs.id))
85962
+ this.db.select({ one: sql27`1` }).from(querySnapshots).where(eq68(querySnapshots.runId, runs.id))
85518
85963
  )
85519
85964
  )
85520
- ).orderBy(desc29(runs.finishedAt), desc29(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
85965
+ ).orderBy(desc30(runs.finishedAt), desc30(runs.createdAt)).limit(HISTORY_WINDOW_RUNS).all();
85521
85966
  const trackedCompetitors = this.loadTrackedCompetitors(projectId);
85522
85967
  const currentRun = this.buildRunData(
85523
85968
  runId,
@@ -85589,7 +86034,7 @@ var IntelligenceService = class {
85589
86034
  * Returns the persisted insights so the coordinator can count critical/high.
85590
86035
  */
85591
86036
  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();
86037
+ const runRow = this.db.select({ createdAt: runs.createdAt, startedAt: runs.startedAt, finishedAt: runs.finishedAt }).from(runs).where(eq68(runs.id, runId)).get();
85593
86038
  if (!runRow) {
85594
86039
  log.info("gbp-intelligence.skip", { runId, reason: "run not found" });
85595
86040
  this.persistGbpInsights(runId, projectId, [], []);
@@ -85597,9 +86042,9 @@ var IntelligenceService = class {
85597
86042
  }
85598
86043
  const windowStart = runRow.startedAt ?? runRow.createdAt;
85599
86044
  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),
86045
+ const selected = this.db.select().from(gbpLocations).where(and58(
86046
+ eq68(gbpLocations.projectId, projectId),
86047
+ eq68(gbpLocations.selected, true),
85603
86048
  gte18(gbpLocations.syncedAt, windowStart),
85604
86049
  lte15(gbpLocations.syncedAt, windowEnd)
85605
86050
  )).all();
@@ -85634,12 +86079,12 @@ var IntelligenceService = class {
85634
86079
  }
85635
86080
  /** Build the per-location signal bundle the GBP analyzer consumes. */
85636
86081
  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();
86082
+ 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();
86083
+ const placeActionRows = this.db.select({ placeActionType: gbpPlaceActions.placeActionType, providerType: gbpPlaceActions.providerType }).from(gbpPlaceActions).where(and58(eq68(gbpPlaceActions.projectId, projectId), eq68(gbpPlaceActions.locationName, locationName))).all();
86084
+ 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();
86085
+ const ownerRow = this.db.select({ description: gbpLocations.description }).from(gbpLocations).where(and58(eq68(gbpLocations.projectId, projectId), eq68(gbpLocations.locationName, locationName))).get();
85641
86086
  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();
86087
+ 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
86088
  const placesAmenities = placeRow ? extractPlaceAmenities(placeRow.attributes) : [];
85644
86089
  const summary2 = buildGbpSummary({
85645
86090
  locationName,
@@ -85675,7 +86120,7 @@ var IntelligenceService = class {
85675
86120
  /** Build the month-over-month keyword series for a location from the
85676
86121
  * accumulating gbp_keyword_monthly table (latest complete month vs prior). */
85677
86122
  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();
86123
+ 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
86124
  if (rows.length === 0) return { recentMonth: null, priorMonth: null, points: [] };
85680
86125
  const months = [...new Set(rows.map((r) => r.month))].sort().reverse();
85681
86126
  const recentMonth = months[0] ?? null;
@@ -85706,7 +86151,7 @@ var IntelligenceService = class {
85706
86151
  */
85707
86152
  persistGbpInsights(runId, projectId, gbpInsights, coveredLocationNames) {
85708
86153
  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();
86154
+ 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
86155
  const staleIds = [];
85711
86156
  const dismissedSlots = /* @__PURE__ */ new Set();
85712
86157
  for (const row of existing) {
@@ -85717,7 +86162,7 @@ var IntelligenceService = class {
85717
86162
  }
85718
86163
  this.db.transaction((tx) => {
85719
86164
  for (const id of staleIds) {
85720
- tx.delete(insights).where(eq67(insights.id, id)).run();
86165
+ tx.delete(insights).where(eq68(insights.id, id)).run();
85721
86166
  }
85722
86167
  for (const insight of gbpInsights) {
85723
86168
  const parsed = parseGbpInsightId(insight.id);
@@ -85803,7 +86248,7 @@ var IntelligenceService = class {
85803
86248
  * create per run + aggregate). DB is left untouched.
85804
86249
  */
85805
86250
  backfill(projectName, opts, onProgress) {
85806
- const project = this.db.select().from(projects).where(eq67(projects.name, projectName)).get();
86251
+ const project = this.db.select().from(projects).where(eq68(projects.name, projectName)).get();
85807
86252
  if (!project) {
85808
86253
  throw new Error(`Project "${projectName}" not found`);
85809
86254
  }
@@ -85815,7 +86260,7 @@ var IntelligenceService = class {
85815
86260
  }
85816
86261
  sinceTimestamp = parsed;
85817
86262
  }
85818
- const allRuns = this.db.select().from(runs).where(and57(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
86263
+ const allRuns = this.db.select().from(runs).where(and58(...analyzableRunPredicates(project.id))).orderBy(asc12(runs.finishedAt)).all();
85819
86264
  const measuredRunIds = this.runIdsWithSnapshots(allRuns.map((r) => r.id));
85820
86265
  const measuredRuns = allRuns.filter((r) => measuredRunIds.has(r.id));
85821
86266
  let startIdx = 0;
@@ -85839,14 +86284,14 @@ var IntelligenceService = class {
85839
86284
  });
85840
86285
  }
85841
86286
  let processed = 0;
85842
- let skipped = 0;
86287
+ let skipped3 = 0;
85843
86288
  let totalInsights = 0;
85844
86289
  const isDryRun = opts?.dryRun === true;
85845
86290
  const perRunDelta = [];
85846
86291
  let wouldDeleteTotal = 0;
85847
86292
  const existingByRunId = /* @__PURE__ */ new Map();
85848
86293
  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();
86294
+ const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray26(insights.runId, targetRuns.map((r) => r.id))).all();
85850
86295
  for (const r of rows) {
85851
86296
  if (r.runId == null) continue;
85852
86297
  existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
@@ -85871,14 +86316,14 @@ var IntelligenceService = class {
85871
86316
  }
85872
86317
  onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: result.insights.length });
85873
86318
  } else {
85874
- skipped++;
86319
+ skipped3++;
85875
86320
  onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: 0 });
85876
86321
  }
85877
86322
  }
85878
86323
  if (isDryRun) {
85879
86324
  return {
85880
86325
  processed,
85881
- skipped,
86326
+ skipped: skipped3,
85882
86327
  totalInsights,
85883
86328
  dryRun: true,
85884
86329
  delta: {
@@ -85889,10 +86334,10 @@ var IntelligenceService = class {
85889
86334
  }
85890
86335
  };
85891
86336
  }
85892
- return { processed, skipped, totalInsights };
86337
+ return { processed, skipped: skipped3, totalInsights };
85893
86338
  }
85894
86339
  loadTrackedCompetitors(projectId) {
85895
- return this.db.select({ domain: competitors.domain }).from(competitors).where(eq67(competitors.projectId, projectId)).all().map((r) => r.domain);
86340
+ return this.db.select({ domain: competitors.domain }).from(competitors).where(eq68(competitors.projectId, projectId)).all().map((r) => r.domain);
85896
86341
  }
85897
86342
  /**
85898
86343
  * Which of `runIds` actually wrote query snapshots. One grouped read, so
@@ -85901,7 +86346,7 @@ var IntelligenceService = class {
85901
86346
  */
85902
86347
  runIdsWithSnapshots(runIds) {
85903
86348
  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();
86349
+ const rows = this.db.select({ runId: querySnapshots.runId }).from(querySnapshots).where(inArray26(querySnapshots.runId, [...runIds])).groupBy(querySnapshots.runId).all();
85905
86350
  return new Set(rows.map((r) => r.runId));
85906
86351
  }
85907
86352
  /**
@@ -85923,15 +86368,15 @@ var IntelligenceService = class {
85923
86368
  }
85924
86369
  persistResult(result, runId, projectId) {
85925
86370
  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();
86371
+ 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
86372
  for (const row of existingInsights) {
85928
86373
  if (row.dismissed) {
85929
86374
  previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
85930
86375
  }
85931
86376
  }
85932
86377
  this.db.transaction((tx) => {
85933
- tx.delete(insights).where(eq67(insights.runId, runId)).run();
85934
- tx.delete(healthSnapshots).where(eq67(healthSnapshots.runId, runId)).run();
86378
+ tx.delete(insights).where(eq68(insights.runId, runId)).run();
86379
+ tx.delete(healthSnapshots).where(eq68(healthSnapshots.runId, runId)).run();
85935
86380
  const now = (/* @__PURE__ */ new Date()).toISOString();
85936
86381
  for (const insight of result.insights) {
85937
86382
  const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
@@ -85978,8 +86423,8 @@ var IntelligenceService = class {
85978
86423
  clicks: gscSearchData.clicks,
85979
86424
  impressions: gscSearchData.impressions,
85980
86425
  position: gscSearchData.position
85981
- }).from(gscSearchData).where(and57(
85982
- eq67(gscSearchData.projectId, projectId),
86426
+ }).from(gscSearchData).where(and58(
86427
+ eq68(gscSearchData.projectId, projectId),
85983
86428
  gte18(gscSearchData.date, window.startDate),
85984
86429
  lte15(gscSearchData.date, window.endDate)
85985
86430
  )).all();
@@ -86032,21 +86477,21 @@ var IntelligenceService = class {
86032
86477
  const key = row.query.toLowerCase();
86033
86478
  gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
86034
86479
  }
86035
- const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq67(projects.id, projectId)).get();
86480
+ const projectRow = this.db.select({ locations: projects.locations }).from(projects).where(eq68(projects.id, projectId)).get();
86036
86481
  const locationCount = Math.max(
86037
86482
  1,
86038
86483
  (projectRow?.locations ?? []).length
86039
86484
  );
86040
86485
  const ROWS_PER_GROUP_BUDGET = Math.max(2, locationCount);
86041
86486
  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")),
86487
+ and58(
86488
+ eq68(runs.projectId, projectId),
86489
+ eq68(runs.kind, RunKinds["answer-visibility"]),
86490
+ or16(eq68(runs.status, "completed"), eq68(runs.status, "partial")),
86046
86491
  // Defensive — see top of file.
86047
86492
  ne11(runs.trigger, RunTriggers.probe)
86048
86493
  )
86049
- ).orderBy(desc29(runs.createdAt), desc29(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
86494
+ ).orderBy(desc30(runs.createdAt), desc30(runs.id)).limit((RECURRENCE_LOOKBACK_RUNS + 1) * ROWS_PER_GROUP_BUDGET).all();
86050
86495
  const recentGroups = groupRunsByCreatedAt(recentRunRows);
86051
86496
  const recentRunIds = [];
86052
86497
  const recentRunIdToCreatedAt = /* @__PURE__ */ new Map();
@@ -86062,7 +86507,7 @@ var IntelligenceService = class {
86062
86507
  const haveHistory = recentRunIds.length > 0;
86063
86508
  const priorRegressionsByPair = /* @__PURE__ */ new Map();
86064
86509
  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();
86510
+ 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
86511
  const regressionGroups = /* @__PURE__ */ new Map();
86067
86512
  for (const row of priorRows) {
86068
86513
  if (!row.runId) continue;
@@ -86091,7 +86536,7 @@ var IntelligenceService = class {
86091
86536
  });
86092
86537
  }
86093
86538
  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();
86539
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq68(projects.id, projectId)).get();
86095
86540
  const projectDomains = projectDomainRow ? effectiveDomains({
86096
86541
  canonicalDomain: projectDomainRow.canonicalDomain,
86097
86542
  ownedDomains: projectDomainRow.ownedDomains
@@ -86109,7 +86554,7 @@ var IntelligenceService = class {
86109
86554
  citedDomains: querySnapshots.citedDomains,
86110
86555
  rawResponse: querySnapshots.rawResponse,
86111
86556
  snapshotLocation: querySnapshots.location
86112
- }).from(querySnapshots).leftJoin(queries, eq67(querySnapshots.queryId, queries.id)).where(eq67(querySnapshots.runId, runId)).all();
86557
+ }).from(querySnapshots).leftJoin(queries, eq68(querySnapshots.queryId, queries.id)).where(eq68(querySnapshots.runId, runId)).all();
86113
86558
  const snapshots = [];
86114
86559
  let orphanCount = 0;
86115
86560
  for (const r of rows) {
@@ -86170,6 +86615,7 @@ export {
86170
86615
  schedules,
86171
86616
  insightNotifyState,
86172
86617
  doctorHealthState,
86618
+ siteLivenessState,
86173
86619
  notifications,
86174
86620
  googleConnections,
86175
86621
  gscSearchData,
@@ -86318,6 +86764,9 @@ export {
86318
86764
  DEFAULT_BOT_LIST,
86319
86765
  generateWorkerScript,
86320
86766
  generateWranglerToml,
86767
+ SITE_REACHABILITY_CHECK_ID,
86768
+ SITE_REACHABILITY_CHECKS,
86769
+ runChecks,
86321
86770
  executeDiscovery,
86322
86771
  markSessionFailed,
86323
86772
  captureSimpleMeasurementDefinition,