@canonry/canonry 7.66.0 → 7.67.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 (137) hide show
  1. package/assets/agent-workspace/skills/aero/references/seo-analysis.md +2 -2
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -2
  3. package/assets/assets/{AgentSetupPage-DXFxOJ1W.js → AgentSetupPage-ORsy3QEa.js} +3 -3
  4. package/assets/assets/{AuditHistoryPanel-CS8UfTHc.js → AuditHistoryPanel-vTHZDbfs.js} +1 -1
  5. package/assets/assets/{BacklinksPage-D5IA2tNY.js → BacklinksPage-FqADB5sO.js} +1 -1
  6. package/assets/assets/{HistoryPage-DsQAUkgR.js → HistoryPage-C4l5Toqa.js} +1 -1
  7. package/assets/assets/MeasurementPropertyPage-x8f7xanT.js +4 -0
  8. package/assets/assets/ProjectPage-CG9I2-B4.js +9 -0
  9. package/assets/assets/{RunsPage-BgjN1N3t.js → RunsPage-oyhhwAPq.js} +1 -1
  10. package/assets/assets/{SettingsPage-BQuqnEvm.js → SettingsPage-DJc4Di1M.js} +1 -1
  11. package/assets/assets/SiteHealthSection-BvqFg3rG.js +2 -0
  12. package/assets/assets/{ToneBadge-DNva5_AM.js → ToneBadge-CTWpeKu8.js} +1 -1
  13. package/assets/assets/{TrafficPage-BBqpvwl5.js → TrafficPage-DAD0FFkL.js} +1 -1
  14. package/assets/assets/TrafficSourceDetailPage-BBEzbawh.js +1 -0
  15. package/assets/assets/{account-context-wRthfm6K.js → account-context-DMHOefRt.js} +1 -1
  16. package/assets/assets/{card-Bq64-UB5.js → card-Brp6fFNG.js} +1 -1
  17. package/assets/assets/{extract-error-message-BRDc8As2.js → extract-error-message-5QpQCsGb.js} +1 -1
  18. package/assets/assets/{format-helpers-D5wFtgg2.js → format-helpers-BFM0FRjT.js} +1 -1
  19. package/assets/assets/index-BBNs-RhI.js +60 -0
  20. package/assets/assets/{query-client-IopmAOx0.js → query-client-BMTW-xss.js} +1 -1
  21. package/assets/assets/react-query-DGmY6hJa.js +1 -0
  22. package/assets/assets/{react-sigma_core.esm.min-d72rX0Uo.js → react-sigma_core.esm.min-C0VjhgDo.js} +1 -1
  23. package/assets/assets/service-DW9u_0aZ.js +1 -0
  24. package/assets/index.html +7 -7
  25. package/dist/{ads-IFKZU7SC.js → ads-BXGEL4WI.js} +3 -3
  26. package/dist/{agent-3KMENHL3.js → agent-LFX7GHWD.js} +7 -7
  27. package/dist/{agent-ask-7WVQLLD5.js → agent-ask-QJV225AT.js} +3 -3
  28. package/dist/{agent-conversations-5LR67TBD.js → agent-conversations-2TZDIGA6.js} +3 -3
  29. package/dist/{agent-memory-GKUCCKVX.js → agent-memory-XCY3ZG5C.js} +3 -3
  30. package/dist/{agent-providers-TVCRM3YZ.js → agent-providers-VS6PH55Y.js} +3 -3
  31. package/dist/{agent-transcript-MUTCOOWG.js → agent-transcript-BUUHOTZM.js} +3 -3
  32. package/dist/{analytics-2IXSG5ML.js → analytics-YWJWPUNF.js} +3 -3
  33. package/dist/{apply-7RIYK4VC.js → apply-BMCYFBEJ.js} +3 -3
  34. package/dist/{backfill-VU3FV6DQ.js → backfill-YBOKT5LT.js} +9 -9
  35. package/dist/{backlinks-DLSRYEX2.js → backlinks-QNNABB4N.js} +3 -3
  36. package/dist/{bing-4CXTZUH4.js → bing-KEMXBABP.js} +3 -3
  37. package/dist/{bootstrap-OOHSBSDQ.js → bootstrap-X2AADK4M.js} +10 -10
  38. package/dist/{cdp-Y53SRSRV.js → cdp-WVYUEV4U.js} +7 -7
  39. package/dist/{chunk-J5YZXRD5.js → chunk-2X7DFLMD.js} +8 -8
  40. package/dist/{chunk-FG7KMVA6.js → chunk-32BTTG5N.js} +5 -5
  41. package/dist/{chunk-ALQEGGYM.js → chunk-37R6GTU7.js} +4 -4
  42. package/dist/{chunk-OSF27DCY.js → chunk-5DMESZDF.js} +2 -2
  43. package/dist/{chunk-JALKQREN.js → chunk-6DJN7HCK.js} +83 -11
  44. package/dist/{chunk-ZRSNMEB3.js → chunk-AFRCTLUL.js} +342 -337
  45. package/dist/{chunk-JYOMHNRP.js → chunk-AVISYPUU.js} +702 -465
  46. package/dist/{chunk-743NYXIK.js → chunk-CBYKQRDH.js} +2 -2
  47. package/dist/{chunk-QRBZBJ4U.js → chunk-EKSXR7RJ.js} +1 -1
  48. package/dist/{chunk-K2HDTMRI.js → chunk-G6PZEDTH.js} +3 -3
  49. package/dist/{chunk-DE2UI37M.js → chunk-GTPGYFZ4.js} +2 -2
  50. package/dist/{chunk-BCRRAH5C.js → chunk-HX3WLTZU.js} +1 -1
  51. package/dist/{chunk-AVQQRTMD.js → chunk-IEBMODUI.js} +72 -33
  52. package/dist/{chunk-L7DQYID4.js → chunk-KJLICF53.js} +4 -4
  53. package/dist/{chunk-CKTA5374.js → chunk-LU3M54IY.js} +1 -1
  54. package/dist/{chunk-LBRG6RKI.js → chunk-MD5PHJBX.js} +1 -1
  55. package/dist/{chunk-6ASEI24F.js → chunk-MLOGEXIO.js} +1 -1
  56. package/dist/{chunk-XC44Q5XR.js → chunk-MOJ2XZXK.js} +1 -1
  57. package/dist/{chunk-YJYLFIVT.js → chunk-NHHZQWMI.js} +1 -1
  58. package/dist/{chunk-H37CDAZX.js → chunk-P74TPIWZ.js} +2 -2
  59. package/dist/{chunk-IPF2S5G2.js → chunk-RRS7U6HQ.js} +4 -4
  60. package/dist/{chunk-SGZWOUIZ.js → chunk-UHLWNU6I.js} +5 -5
  61. package/dist/{chunk-EYWC24VC.js → chunk-UNCOK6DA.js} +1 -1
  62. package/dist/{chunk-YRX5UOM3.js → chunk-UXTNDZKM.js} +1 -1
  63. package/dist/{chunk-VGUZPC3I.js → chunk-VR4MYSEH.js} +3 -3
  64. package/dist/{chunk-2YBEI6LE.js → chunk-WSYLDPYF.js} +1 -1
  65. package/dist/{chunk-TJZLICMB.js → chunk-WZ4LUM26.js} +3 -3
  66. package/dist/{chunk-WNCOBSPN.js → chunk-XEGYSZEV.js} +19 -3
  67. package/dist/{chunk-EIMXB6UH.js → chunk-XEPEMON2.js} +2 -2
  68. package/dist/{chunk-DVYS2RMM.js → chunk-XIA6NWIF.js} +4 -4
  69. package/dist/{chunk-PFKBQTYD.js → chunk-XMIKSCAO.js} +1 -1
  70. package/dist/{chunk-DH5QRKFW.js → chunk-Y54PMWBH.js} +3 -3
  71. package/dist/{chunk-FNI3QSVD.js → chunk-YP743QX5.js} +1 -1
  72. package/dist/{chunk-IHOVPHUW.js → chunk-ZGUR4AZM.js} +2 -2
  73. package/dist/{citations-ZTQIYYDB.js → citations-ZSI5BOGD.js} +3 -3
  74. package/dist/{cli-catalog-IZJXAHFQ.js → cli-catalog-J2HUCGJL.js} +3 -3
  75. package/dist/{cli-commands-XYM2VMAK.js → cli-commands-XFMVFREG.js} +7 -7
  76. package/dist/cli.js +13 -13
  77. package/dist/{competitor-SIXAPHOM.js → competitor-WJDN3DLP.js} +3 -3
  78. package/dist/{content-XPS3D3Y6.js → content-K52YV4BK.js} +3 -3
  79. package/dist/{dashboard-FMJDM5PA.js → dashboard-L6RKTP35.js} +6 -6
  80. package/dist/{demo-QQKSHTPO.js → demo-Y7XRD4X4.js} +1 -1
  81. package/dist/{demo-server-LQ3QXUAS.js → demo-server-QNQX5CNP.js} +13 -11
  82. package/dist/{discover-K5HFEU2T.js → discover-UO5M7HQZ.js} +3 -3
  83. package/dist/{discover-eval-6536Z2YN.js → discover-eval-DN4PS6VN.js} +3 -3
  84. package/dist/{doctor-STNH3PHH.js → doctor-7UYUXG7J.js} +3 -3
  85. package/dist/{evidence-W2EWKBAV.js → evidence-DGKN5SGQ.js} +3 -3
  86. package/dist/{export-cmd-EGHMY3M6.js → export-cmd-QAQWWIRL.js} +3 -3
  87. package/dist/{ga-DVIG3PVW.js → ga-4LGTEA6J.js} +3 -3
  88. package/dist/{gbp-SNS4XZBO.js → gbp-XOLYZ4XC.js} +3 -3
  89. package/dist/{google-KGPHZG6E.js → google-CAQDCFA6.js} +4 -4
  90. package/dist/{google-marketing-VZR6W3L6.js → google-marketing-HU5I3XPP.js} +1 -1
  91. package/dist/{health-cmd-2KWO2GOM.js → health-cmd-3PEB3M45.js} +3 -3
  92. package/dist/{history-ZY27B7JE.js → history-UGUWYPOY.js} +3 -3
  93. package/dist/index.d.ts +109 -3
  94. package/dist/index.js +24 -24
  95. package/dist/{init-5TXIKII2.js → init-DEW65BLN.js} +11 -11
  96. package/dist/{insights-MUYEOSOG.js → insights-ZC3R4ZYG.js} +3 -3
  97. package/dist/intelligence-service-XRJVXZYI.js +15 -0
  98. package/dist/{journeys-YO54IRVC.js → journeys-W6F4BCXF.js} +3 -3
  99. package/dist/{keys-M4QZGYVJ.js → keys-ME7VRF73.js} +3 -3
  100. package/dist/{keyword-MU62MGWT.js → keyword-R3PLGSO5.js} +3 -3
  101. package/dist/{logs-BAQM334Q.js → logs-UM7CWHYB.js} +3 -3
  102. package/dist/{mcp-LYS5P3PB.js → mcp-BUPP5HC6.js} +2 -2
  103. package/dist/mcp.js +9 -9
  104. package/dist/{measurement-plan-HVSTZWTW.js → measurement-plan-R4KZHJII.js} +5 -5
  105. package/dist/{notify-XSP5JXU3.js → notify-4C26D2MY.js} +3 -3
  106. package/dist/{organic-evidence-4HYW4PVO.js → organic-evidence-SLIISYHW.js} +3 -3
  107. package/dist/{overview-XH72WTZT.js → overview-JJZZOYQU.js} +4 -4
  108. package/dist/{project-UKH7FTQX.js → project-XJH2OECP.js} +3 -3
  109. package/dist/{query-RRD5M4QV.js → query-ZWAIWIYB.js} +3 -3
  110. package/dist/{research-V2IGDUKL.js → research-GTVWVZBI.js} +3 -3
  111. package/dist/{results-export-5FC7ZT6O.js → results-export-CPAWO5AV.js} +3 -3
  112. package/dist/{run-B2A4LMWU.js → run-RI2BMD7U.js} +4 -4
  113. package/dist/{schedule-PLY6CHQT.js → schedule-HDDWULBX.js} +3 -3
  114. package/dist/{search-ACVK3WH5.js → search-TT3Q2BBP.js} +3 -3
  115. package/dist/{serve-2LTQZPEK.js → serve-6JYPUU24.js} +25 -25
  116. package/dist/{session-IHXMPETX.js → session-DDESFV3N.js} +12 -12
  117. package/dist/{settings-ZHBNX2I5.js → settings-4AYXYJVA.js} +3 -3
  118. package/dist/{skills-KWRTCXTF.js → skills-UFMLMMY5.js} +2 -2
  119. package/dist/{snapshot-GBDCCV6L.js → snapshot-KLWQGZB6.js} +3 -3
  120. package/dist/{src-NOTQWGYV.js → src-6IHGEMT2.js} +6 -2
  121. package/dist/{status-FGDDGBMJ.js → status-HRIUPGUM.js} +4 -4
  122. package/dist/{technical-aeo-AK4QI63Q.js → technical-aeo-HKJQHMMS.js} +3 -3
  123. package/dist/{telemetry-6QPWYMCR.js → telemetry-IS6KZK32.js} +4 -4
  124. package/dist/{tools-PBNTVVN7.js → tools-ZNVQFEAX.js} +11 -11
  125. package/dist/{traffic-AS4PIOQL.js → traffic-PFHFGN44.js} +5 -5
  126. package/dist/{users-BNYKF35J.js → users-BSLUSU6W.js} +3 -3
  127. package/dist/{visibility-stats-TWL3GJMD.js → visibility-stats-MPRNHPZW.js} +3 -3
  128. package/dist/{wordpress-XBWXAACD.js → wordpress-U5XKHURL.js} +3 -3
  129. package/package.json +1 -1
  130. package/assets/assets/MeasurementPropertyPage-CdCnjCaS.js +0 -4
  131. package/assets/assets/ProjectPage-D3oFKnBG.js +0 -9
  132. package/assets/assets/SiteHealthSection-C6QE9xNk.js +0 -2
  133. package/assets/assets/TrafficSourceDetailPage-uxd0ojry.js +0 -1
  134. package/assets/assets/index-Ttz5_bL3.js +0 -60
  135. package/assets/assets/react-query-CISHjSch.js +0 -1
  136. package/assets/assets/service-DsDxSQuN.js +0 -1
  137. package/dist/intelligence-service-U4NSGZUH.js +0 -15
@@ -9,14 +9,14 @@ import {
9
9
  normalizeCloudflareWorkerEvent,
10
10
  sessionizeJourneyRequests,
11
11
  verifyRequestSignature
12
- } from "./chunk-BCRRAH5C.js";
12
+ } from "./chunk-HX3WLTZU.js";
13
13
  import {
14
14
  currentOutcomeAttribution,
15
15
  getRequestContext,
16
16
  registerRequestContext,
17
17
  requestAuditIdentity,
18
18
  sanitizeRequestContext
19
- } from "./chunk-YRX5UOM3.js";
19
+ } from "./chunk-UXTNDZKM.js";
20
20
  import {
21
21
  SentimentIdempotencyConflict,
22
22
  SentimentRepository,
@@ -49,6 +49,8 @@ import {
49
49
  conversionTrackingContracts,
50
50
  crawlerEventsHourly,
51
51
  crawlerVerificationManifestsHourly,
52
+ currentSeoTaskCondition,
53
+ currentSeoTasks,
52
54
  dashboardSessions,
53
55
  discoveryProbes,
54
56
  discoverySessions,
@@ -154,7 +156,7 @@ import {
154
156
  userSessions,
155
157
  users,
156
158
  writeNegativeReviewMaxStars
157
- } from "./chunk-JALKQREN.js";
159
+ } from "./chunk-6DJN7HCK.js";
158
160
  import {
159
161
  ADS_ACTIVATE_SCOPE,
160
162
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -366,6 +368,7 @@ import {
366
368
  SeoAdapterOutcomes,
367
369
  SeoAvailability,
368
370
  SeoCapabilities,
371
+ SeoMarketRankingSearchStatuses,
369
372
  SeoRunStatuses,
370
373
  SeoScopeKinds,
371
374
  SeoTargetKinds,
@@ -605,7 +608,6 @@ import {
605
608
  effectiveDomains,
606
609
  emptyCitationVisibility,
607
610
  emptySentimentCounts,
608
- emptySeoResults,
609
611
  escapeLikePattern,
610
612
  escapeRegExp,
611
613
  evaluateModelPointerExposure,
@@ -1020,6 +1022,7 @@ import {
1020
1022
  runtimeStateMissing,
1021
1023
  schedulableRunKindSchema,
1022
1024
  scheduleDtoSchema,
1025
+ scheduleEnabledAt,
1023
1026
  scheduleExpectedUpdatedAtSchema,
1024
1027
  scheduleUpsertRequestSchema,
1025
1028
  scheduleVersionConflict,
@@ -1173,7 +1176,7 @@ import {
1173
1176
  wordpressSchemaDeployResultDtoSchema,
1174
1177
  wordpressSchemaStatusResultDtoSchema,
1175
1178
  wordpressStatusDtoSchema
1176
- } from "./chunk-WNCOBSPN.js";
1179
+ } from "./chunk-XEGYSZEV.js";
1177
1180
 
1178
1181
  // ../api-routes/src/user-session.ts
1179
1182
  import crypto2 from "crypto";
@@ -7262,9 +7265,9 @@ var SentimentService = class {
7262
7265
  const estimatedInputTokens = this.options.estimate ? inputs.reduce((sum, input) => sum + this.options.estimate(input), 0) : 0;
7263
7266
  const expiresAt = new Date(Date.parse(this.now()) + 15 * 6e4).toISOString();
7264
7267
  const previewToken = definitionId && settings.enabled && settings.ready && source.assessments.length ? this.sign({ projectId, selection: frozenSelection, definitionId, fingerprint: sentimentHash(source.assessments), expiresAt }) : null;
7265
- const skipped5 = source.runIds.flatMap((runId) => Object.entries(source.skippedByRun?.[runId] ?? {}).map(([reason, count10]) => ({ runId, reason, count: count10 })));
7268
+ const skipped6 = source.runIds.flatMap((runId) => Object.entries(source.skippedByRun?.[runId] ?? {}).map(([reason, count10]) => ({ runId, reason, count: count10 })));
7266
7269
  const estimateMethod = this.options.estimate ? `Adapter estimate of the full request including all questions and evidence candidates; final billed usage is recorded per attempt.${definitionId ? "" : " Sentiment is not configured for this project, so the estimate uses the current evaluator definition."}` : "Token and cost estimates unavailable on this host.";
7267
- return { previewToken, expiresAt: previewToken ? expiresAt : null, selection: frozenSelection, evaluationDefinitionId: definitionId, eligibleAssessments: source.assessments.length, alreadyClassified, skipped: skipped5, estimatedInputTokens, estimatedCostUsd: this.options.estimate ? estimatedInputTokens * 0.042 / 1e6 : null, estimateMethod };
7270
+ return { previewToken, expiresAt: previewToken ? expiresAt : null, selection: frozenSelection, evaluationDefinitionId: definitionId, eligibleAssessments: source.assessments.length, alreadyClassified, skipped: skipped6, estimatedInputTokens, estimatedCostUsd: this.options.estimate ? estimatedInputTokens * 0.042 / 1e6 : null, estimateMethod };
7268
7271
  }
7269
7272
  /** The receipt carries the first page of attempts; a replay of a long-running job never returns all of them. */
7270
7273
  submit(projectId, previewToken, idempotencyKey, actor2) {
@@ -8419,6 +8422,7 @@ var HEALTH_CHECK_LABELS = {
8419
8422
  "ga.auth.connection": "GA4 sign-in",
8420
8423
  "ga.data.recent-data": "GA4 data stopped",
8421
8424
  "gsc.data.recent-data": "Search Console data stopped",
8425
+ "seo.data.recent-refresh": "SEO refresh stalled",
8422
8426
  "report.sweeps": "Monthly report sweeps",
8423
8427
  "report.models": "Monthly report models",
8424
8428
  "report.daily-data": "Monthly report data gaps",
@@ -8471,7 +8475,11 @@ var HEALTH_FIXED_LINES = {
8471
8475
  "traffic.source.all-errored-recently": "Traffic syncs are working again.",
8472
8476
  "traffic.queue-backlog.remaining": "The traffic backlog is cleared.",
8473
8477
  "traffic.worker-version.stale": "The Cloudflare worker is up to date.",
8474
- "gbp.auth.no-connection": "Business Profile is connected."
8478
+ "gbp.auth.no-connection": "Business Profile is connected.",
8479
+ "seo.data.refresh-failed": "SEO refreshes complete again.",
8480
+ "seo.data.refresh-partial": "SEO refreshes complete again.",
8481
+ "seo.data.overdue": "SEO data is current again.",
8482
+ "seo.data.schedule-invalid": "The SEO refresh schedule can run again."
8475
8483
  };
8476
8484
  function firstSentence(text, max2 = 180) {
8477
8485
  if (!text) return null;
@@ -15220,7 +15228,37 @@ function runDto(job, counts2) {
15220
15228
  return { id: job.id, projectId: job.projectKey, profileId: job.profileId, profileRevision: job.profileRevision, locationCode: job.profile.locationCode, languageCode: job.profile.languageCode, cancelRequested: job.cancelRequested, status: job.status, capabilities: job.profile.capabilities, scope: job.profile.scope, taskCount: counts2.reduce((sum, group) => sum + group.count, 0), completedTasks: counts2.filter((group) => group.status === SeoRunStatuses.completed || group.status === SeoRunStatuses.partial).reduce((sum, group) => sum + group.count, 0), failedTasks: counts2.filter((group) => group.status === SeoRunStatuses.failed).reduce((sum, group) => sum + group.count, 0), createdAt: job.createdAt, startedAt: job.startedAt, finishedAt: job.finishedAt, error: job.error };
15221
15229
  }
15222
15230
  function resultRows(tasks) {
15223
- return tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed).reduce((results, task) => ({ keywordMetrics: results.keywordMetrics.concat(task.results.keywordMetrics), domainIntelligence: results.domainIntelligence.concat(task.results.domainIntelligence), rankedKeywords: results.rankedKeywords.concat(task.results.rankedKeywords), marketRankings: results.marketRankings.concat(task.results.marketRankings ?? []) }), emptySeoResults());
15231
+ const kept = tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed);
15232
+ return {
15233
+ keywordMetrics: kept.flatMap((task) => task.results.keywordMetrics),
15234
+ domainIntelligence: kept.flatMap((task) => task.results.domainIntelligence),
15235
+ rankedKeywords: kept.flatMap((task) => task.results.rankedKeywords),
15236
+ marketRankings: tasks.flatMap(marketRankingRows)
15237
+ };
15238
+ }
15239
+ function marketRankingRows(task) {
15240
+ const stored = task.reconciliation?.outcome === SeoAdapterOutcomes.failed ? [] : task.results.marketRankings ?? [];
15241
+ if (stored.length > 0) return stored.map((row) => ({ ...row, searchStatus: row.searchStatus ?? SeoMarketRankingSearchStatuses.completed }));
15242
+ const { search, target } = task.request;
15243
+ if (task.request.capability !== SeoCapabilities["market-rankings"] || !search || !target) return [];
15244
+ const searchStatus = task.status === SeoRunStatuses.cancelled ? SeoMarketRankingSearchStatuses.cancelled : task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"] ? SeoMarketRankingSearchStatuses.failed : null;
15245
+ if (!searchStatus) return [];
15246
+ return search.markets.map((market) => ({
15247
+ ...market.context,
15248
+ searchText: task.request.keywords[0] ?? "",
15249
+ locationLabel: search.locationLabel,
15250
+ target,
15251
+ searchStatus,
15252
+ rankOrganic: null,
15253
+ rankAbsolute: null,
15254
+ url: null,
15255
+ depth: 0,
15256
+ competitors: [],
15257
+ provenance: null
15258
+ }));
15259
+ }
15260
+ function taskSearch(task) {
15261
+ return task.request.capability === SeoCapabilities["market-rankings"] ? { searchText: task.request.keywords[0] ?? null, locationLabel: task.request.search?.locationLabel ?? null } : { searchText: null, locationLabel: null };
15224
15262
  }
15225
15263
  function compareMarketRankings(a, b) {
15226
15264
  return compareText(a.regionLabel ?? "", b.regionLabel ?? "") || compareText(a.marketLabel, b.marketLabel) || compareText(a.searchText, b.searchText) || compareText(a.queryText, b.queryText);
@@ -15434,14 +15472,20 @@ function budgetBuckets(account, projectId, policy, now) {
15434
15472
  { scope: `project-month:${projectId}`, period: now.slice(0, 7), limit: policy.projectMonthlyLimitMicros }
15435
15473
  ];
15436
15474
  }
15475
+ function skippedRefreshReason(profileId, reason) {
15476
+ return `Scheduled SEO refresh for ${profileId ? `profile "${profileId}"` : "this project"} did not start: ${reason}`;
15477
+ }
15478
+ var SKIPPED_REFRESH_PROFILE = /^Scheduled SEO refresh for profile "([^"]+)" did not start: /;
15479
+ function skippedRefreshProfile(error) {
15480
+ return error ? SKIPPED_REFRESH_PROFILE.exec(error)?.[1] ?? null : null;
15481
+ }
15437
15482
  function startScheduledSeoRefreshes(db, opts, input) {
15438
15483
  const repository = new SeoRepository(db);
15439
15484
  const principalId = `schedule:${input.scheduleId}`;
15440
15485
  const recordSkipped = (profileId, reason, detail) => {
15441
15486
  const runId = crypto17.randomUUID();
15442
15487
  const now = (/* @__PURE__ */ new Date()).toISOString();
15443
- const subject = profileId ? `profile "${profileId}"` : "this project";
15444
- db.insert(runs).values({ id: runId, projectId: input.projectId, kind: RunKinds["seo-sync"], status: RunStatuses.failed, trigger: RunTriggers.scheduled, createdAt: now, finishedAt: now, error: `Scheduled SEO refresh for ${subject} did not start: ${reason}` }).run();
15488
+ db.insert(runs).values({ id: runId, projectId: input.projectId, kind: RunKinds["seo-sync"], status: RunStatuses.failed, trigger: RunTriggers.scheduled, createdAt: now, finishedAt: now, error: skippedRefreshReason(profileId, reason) }).run();
15445
15489
  return { profileId, status: "skipped", runId, reason, ...detail ? { detail } : {} };
15446
15490
  };
15447
15491
  const saved = db.select().from(seoProfiles).where(eq32(seoProfiles.projectId, input.projectId)).orderBy(asc6(seoProfiles.id)).all().map(profileDto);
@@ -15493,8 +15537,8 @@ async function seoRoutes(app, opts) {
15493
15537
  if (!row) throw notFound("SEO profile", id);
15494
15538
  return profileDto(row);
15495
15539
  };
15496
- const tasks = (id) => app.db.select().from(seoTasks).where(eq32(seoTasks.jobId, id)).orderBy(asc6(seoTasks.id)).all();
15497
- const taskCounts = (ids) => ids.length ? app.db.select({ jobId: seoTasks.jobId, status: seoTasks.status, count: sql8`count(*)` }).from(seoTasks).where(inArray16(seoTasks.jobId, ids)).groupBy(seoTasks.jobId, seoTasks.status).all() : [];
15540
+ const tasks = (id) => currentSeoTasks(app.db.select().from(seoTasks).where(eq32(seoTasks.jobId, id)).orderBy(asc6(seoTasks.id)).all());
15541
+ const taskCounts = (ids) => ids.length ? app.db.select({ jobId: seoTasks.jobId, status: seoTasks.status, count: sql8`count(*)` }).from(seoTasks).where(and24(inArray16(seoTasks.jobId, ids), currentSeoTaskCondition())).groupBy(seoTasks.jobId, seoTasks.status).all() : [];
15498
15542
  const runSummary = (row) => runDto(row, taskCounts([row.id]));
15499
15543
  const job = (projectId, id) => {
15500
15544
  const row = app.db.select().from(seoJobs).where(and24(eq32(seoJobs.projectId, projectId), isNotNull3(seoJobs.runId), eq32(seoJobs.id, id))).get();
@@ -15645,7 +15689,7 @@ async function seoRoutes(app, opts) {
15645
15689
  const row = job(resolveProject(app.db, request.params.name).id, request.params.id);
15646
15690
  const rows = tasks(row.id);
15647
15691
  const results = resultRows(rows);
15648
- return { ...runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))), tasks: rows.map((task) => ({ id: task.id, capability: task.request.capability, target: task.request.target, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null })), results: { keywordMetrics: results.keywordMetrics.slice(0, 20), domainIntelligence: results.domainIntelligence.slice(0, 20), rankedKeywords: results.rankedKeywords.slice(0, 20), marketRankings: [...results.marketRankings].sort(compareMarketRankings).slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length, marketRankings: results.marketRankings.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
15692
+ return { ...runDto(row, rows.map((task) => ({ status: task.status, count: 1 }))), tasks: rows.map((task) => ({ id: task.id, capability: task.request.capability, target: task.request.target, status: task.status, error: task.error, remoteTaskId: task.receipts[0]?.remoteTaskId ?? null, ...taskSearch(task), retried: task.retryOf !== null })), results: { keywordMetrics: results.keywordMetrics.slice(0, 20), domainIntelligence: results.domainIntelligence.slice(0, 20), rankedKeywords: results.rankedKeywords.slice(0, 20), marketRankings: [...results.marketRankings].sort(compareMarketRankings).slice(0, 20) }, resultCounts: { keywordMetrics: results.keywordMetrics.length, domainIntelligence: results.domainIntelligence.length, rankedKeywords: results.rankedKeywords.length, marketRankings: results.marketRankings.length }, resultsTruncated: Object.values(results).some((items) => items.length > 20) };
15649
15693
  });
15650
15694
  app.post("/projects/:name/seo/runs/:id/cancel", { config: { paidRead: true, writeScope: SEO_RUN_SCOPE } }, async (request) => {
15651
15695
  requireScope(request, SEO_RUN_SCOPE);
@@ -27130,7 +27174,7 @@ async function applyRoutes(app, opts) {
27130
27174
  timezone: resolvedSchedule.timezone,
27131
27175
  nextRunAt,
27132
27176
  providers: config.spec.schedule?.providers ?? [],
27133
- ...specEnabled === void 0 ? {} : { enabled: specEnabled },
27177
+ ...specEnabled === void 0 ? {} : { enabled: specEnabled, enabledAt: scheduleEnabledAt(existingSched, specEnabled, now) },
27134
27178
  updatedAt: nextScheduleUpdatedAt(existingSched.updatedAt, Date.parse(now))
27135
27179
  }).where(eq44(schedules.id, existingSched.id)).run();
27136
27180
  } else {
@@ -27144,6 +27188,7 @@ async function applyRoutes(app, opts) {
27144
27188
  timezone: resolvedSchedule.timezone,
27145
27189
  nextRunAt,
27146
27190
  enabled: specEnabled ?? true,
27191
+ enabledAt: scheduleEnabledAt(void 0, specEnabled ?? true, now),
27147
27192
  providers: config.spec.schedule?.providers ?? [],
27148
27193
  createdAt: now,
27149
27194
  updatedAt: now
@@ -35124,7 +35169,7 @@ var routeCatalog = [
35124
35169
  path: "/api/v1/projects/{name}/seo/market-rankings",
35125
35170
  summary: "Read stored market rankings",
35126
35171
  tags: ["seo"],
35127
- description: "Stored evidence only; never calls a provider. The project's position for each tracked non-brand query, searched on Google from its market's location, ordered by region, market and search text. A null rank means the site was not in the checked results.",
35172
+ description: "Stored evidence only; never calls a provider. The project's position for each tracked non-brand query, searched on Google from its market's location, ordered by region, market and search text. searchStatus says how each search ended: in a completed or partial row a null rank means the site was not in the checked results (depth, which is lower when only part of the results came back); a failed or cancelled search keeps its rows with no rank.",
35128
35173
  parameters: [
35129
35174
  nameParameter,
35130
35175
  ...seoEvidenceParameters,
@@ -43091,6 +43136,7 @@ async function scheduleRoutes(app, opts) {
43091
43136
  providers: providers ?? [],
43092
43137
  sourceId: sourceId ?? null,
43093
43138
  enabled: enabledBool,
43139
+ enabledAt: scheduleEnabledAt(existing, enabledBool, now),
43094
43140
  updatedAt: now
43095
43141
  };
43096
43142
  if (existing) {
@@ -59835,11 +59881,11 @@ async function wordpressRoutes(app, opts) {
59835
59881
  reportMetaStep(metaWriteOutcome(result.results));
59836
59882
  const applied = result.results.filter((r) => r.status === "applied").length;
59837
59883
  const manual = result.results.filter((r) => r.status === "manual").length;
59838
- const skipped5 = result.results.filter((r) => r.status === "skipped").length;
59884
+ const skipped6 = result.results.filter((r) => r.status === "skipped").length;
59839
59885
  steps.push({
59840
59886
  name: "set-meta",
59841
59887
  status: "completed",
59842
- summary: `${applied} applied, ${manual} manual-assist, ${skipped5} skipped`
59888
+ summary: `${applied} applied, ${manual} manual-assist, ${skipped6} skipped`
59843
59889
  });
59844
59890
  }
59845
59891
  } catch (err) {
@@ -59869,11 +59915,11 @@ async function wordpressRoutes(app, opts) {
59869
59915
  reportSchemaStep(schemaDeployOutcome(result.results));
59870
59916
  const deployed = result.results.filter((r) => r.status === "deployed").length;
59871
59917
  const stripped = result.results.filter((r) => r.status === "stripped").length;
59872
- const skipped5 = result.results.filter((r) => r.status === "skipped").length;
59918
+ const skipped6 = result.results.filter((r) => r.status === "skipped").length;
59873
59919
  steps.push({
59874
59920
  name: "schema-deploy",
59875
59921
  status: "completed",
59876
- summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped5} skipped`
59922
+ summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped6} skipped`
59877
59923
  });
59878
59924
  }
59879
59925
  } catch (err) {
@@ -60261,7 +60307,7 @@ async function queryBacklinks(opts) {
60261
60307
  const reversed = opts.targets.map(reverseDomain);
60262
60308
  const targetList = reversed.map(quote).join(", ");
60263
60309
  const limitClause = opts.limitPerTarget ? `QUALIFY row_number() OVER (PARTITION BY t.target_rev_domain ORDER BY v.num_hosts DESC) <= ${Math.floor(opts.limitPerTarget)}` : "";
60264
- const sql37 = `
60310
+ const sql38 = `
60265
60311
  WITH vertices AS (
60266
60312
  SELECT * FROM read_csv(
60267
60313
  ${quote(opts.vertexPath)},
@@ -60297,7 +60343,7 @@ async function queryBacklinks(opts) {
60297
60343
  const conn = await instance.connect();
60298
60344
  let rows;
60299
60345
  try {
60300
- const reader = await conn.runAndReadAll(sql37);
60346
+ const reader = await conn.runAndReadAll(sql38);
60301
60347
  rows = reader.getRowObjects();
60302
60348
  } finally {
60303
60349
  conn.disconnectSync?.();
@@ -62904,6 +62950,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
62904
62950
  preset: null,
62905
62951
  timezone: "UTC",
62906
62952
  enabled: true,
62953
+ enabledAt: now,
62907
62954
  providers: [],
62908
62955
  sourceId,
62909
62956
  createdAt: now,
@@ -67413,6 +67460,195 @@ var gscRecentDataCheck = {
67413
67460
  };
67414
67461
  var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
67415
67462
 
67463
+ // ../api-routes/src/doctor/checks/seo-refresh.ts
67464
+ import { and as and75, asc as asc20, desc as desc42, eq as eq89, gt as gt8, inArray as inArray39, sql as sql32 } from "drizzle-orm";
67465
+ var GRACE_DAYS = 2;
67466
+ var DAY_MS5 = 24 * 60 * 60 * 1e3;
67467
+ var FIRING_SPAN_MS = 6e4;
67468
+ var REASON_LIMIT = 300;
67469
+ function skipped2(code, summary3) {
67470
+ return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
67471
+ }
67472
+ function seoSyncRuns(projectId) {
67473
+ return and75(eq89(runs.projectId, projectId), eq89(runs.kind, RunKinds["seo-sync"]));
67474
+ }
67475
+ function scheduleTiming(schedule) {
67476
+ if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
67477
+ const recurrence = calendarRecurrenceSchema.safeParse(schedule.recurrence).data;
67478
+ return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
67479
+ }
67480
+ function latestFiring(ctx, projectId, enabledSince) {
67481
+ const scheduled = and75(seoSyncRuns(projectId), eq89(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
67482
+ const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc42(runs.createdAt)).limit(1).get();
67483
+ if (!newest) return [];
67484
+ const newestMs = Date.parse(newest.createdAt);
67485
+ const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
67486
+ return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and75(scheduled, gt8(runs.createdAt, after))).orderBy(asc20(runs.createdAt), asc20(runs.id)).all();
67487
+ }
67488
+ function lastCompleted(ctx, projectId) {
67489
+ const at = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
67490
+ return ctx.db.select({ id: runs.id, at }).from(runs).where(and75(seoSyncRuns(projectId), eq89(runs.status, RunStatuses.completed))).orderBy(desc42(at)).limit(1).get() ?? null;
67491
+ }
67492
+ function isIncomplete(run) {
67493
+ return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
67494
+ }
67495
+ function runProfiles(ctx, firing) {
67496
+ const jobs = firing.length === 0 ? [] : ctx.db.select({ runId: seoJobs.runId, profileId: seoJobs.profileId }).from(seoJobs).where(inArray39(seoJobs.runId, firing.map((run) => run.id))).all();
67497
+ const byRun = new Map(jobs.flatMap((job) => job.runId ? [[job.runId, job.profileId]] : []));
67498
+ return new Map(firing.map((run) => [run.id, byRun.get(run.id) ?? skippedRefreshProfile(run.error)]));
67499
+ }
67500
+ function unsettledRuns(ctx, projectId, firing, profiles) {
67501
+ const incomplete = firing.filter(isIncomplete);
67502
+ const firedAt = firing[firing.length - 1]?.createdAt;
67503
+ if (incomplete.length === 0 || !firedAt) return incomplete;
67504
+ const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq89(seoJobs.runId, runs.id)).where(and75(seoSyncRuns(projectId), eq89(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
67505
+ return incomplete.filter((run) => {
67506
+ const profile = profiles.get(run.id) ?? null;
67507
+ return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
67508
+ });
67509
+ }
67510
+ var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
67511
+ function refreshReason(ctx, run) {
67512
+ const stored = parseRunError(run.error);
67513
+ if (stored) return formatRunErrorOneLine(stored);
67514
+ const tasks = currentSeoTasks(ctx.db.select({ id: seoTasks.id, retryOf: seoTasks.retryOf, status: seoTasks.status, error: seoTasks.error, request: seoTasks.request }).from(seoTasks).innerJoin(seoJobs, eq89(seoTasks.jobId, seoJobs.id)).where(eq89(seoJobs.runId, run.id)).all());
67515
+ const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
67516
+ const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
67517
+ const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
67518
+ const errors = [...new Set(failed.flatMap((task) => task.error ? [task.error] : []))].join(" ");
67519
+ if (tasks.length <= 1) {
67520
+ if (errors) return errors;
67521
+ if (partial > 0) return "Some SEO results could not be collected.";
67522
+ return cancelled > 0 ? "The refresh was cancelled." : "No reason was recorded.";
67523
+ }
67524
+ const unit = tasks.every((task) => task.request.capability === SeoCapabilities["market-rankings"]) ? "searches" : "requests";
67525
+ const counts2 = [
67526
+ ...failed.length > 0 ? [`${failed.length} failed`] : [],
67527
+ ...partial > 0 ? [`${partial} returned only part of their results`] : [],
67528
+ ...cancelled > 0 ? [`${cancelled} did not run`] : []
67529
+ ];
67530
+ if (counts2.length === 0) return "No reason was recorded.";
67531
+ return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
67532
+ }
67533
+ function gradeSeoRefresh(ctx, project) {
67534
+ const schedule = ctx.db.select().from(schedules).where(and75(eq89(schedules.projectId, project.id), eq89(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
67535
+ if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
67536
+ if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
67537
+ const name = project.name;
67538
+ const day = (iso) => formatIsoDateInTimeZone(iso, schedule.timezone);
67539
+ const now = Date.now();
67540
+ const timing = scheduleTiming(schedule);
67541
+ const last = lastCompleted(ctx, project.id);
67542
+ const enabledSince = schedule.enabledAt ?? schedule.createdAt;
67543
+ const reference = last && Date.parse(last.at) > Date.parse(enabledSince) ? last.at : enabledSince;
67544
+ const dueAt = timing ? nextRunFromSchedule(timing, new Date(reference)) : null;
67545
+ const deadlineAt = dueAt ? new Date(Date.parse(dueAt) + GRACE_DAYS * DAY_MS5).toISOString() : null;
67546
+ const firing = latestFiring(ctx, project.id, enabledSince);
67547
+ const profiles = runProfiles(ctx, firing);
67548
+ const details = {
67549
+ scheduleId: schedule.id,
67550
+ cronExpr: schedule.cronExpr || null,
67551
+ recurrence: schedule.recurrence ?? null,
67552
+ timezone: schedule.timezone,
67553
+ enabledSince,
67554
+ graceDays: GRACE_DAYS,
67555
+ lastCompletedAt: last?.at ?? null,
67556
+ lastCompletedRunId: last?.id ?? null,
67557
+ dueAt,
67558
+ deadlineAt,
67559
+ latestScheduledRuns: firing.map((run) => ({
67560
+ id: run.id,
67561
+ profileId: profiles.get(run.id) ?? null,
67562
+ status: run.status,
67563
+ createdAt: run.createdAt,
67564
+ reason: isIncomplete(run) ? refreshReason(ctx, run) : null
67565
+ }))
67566
+ };
67567
+ if (!timing || !dueAt || !deadlineAt) {
67568
+ return {
67569
+ status: CheckStatuses.warn,
67570
+ code: "seo.data.schedule-invalid",
67571
+ summary: "The SEO refresh schedule has no time it can run, so no scheduled SEO refresh will start.",
67572
+ remediation: `Set a schedule that can run, for example \`canonry schedule set ${name} --kind seo-sync --cron '0 7 2 * *' --timezone UTC\`.`,
67573
+ details,
67574
+ alert: {
67575
+ label: "SEO schedule invalid",
67576
+ line: "The SEO refresh schedule has no time it can run, so SEO data never refreshes.",
67577
+ next: `Set it again with canonry schedule set ${name} --kind seo-sync.`
67578
+ }
67579
+ };
67580
+ }
67581
+ const nextAt = nextRunFromSchedule(timing, new Date(now));
67582
+ const unsettled = unsettledRuns(ctx, project.id, firing, profiles);
67583
+ const newestFired = firing[firing.length - 1];
67584
+ if (newestFired && unsettled.length > 0) {
67585
+ const failed = unsettled.some((run) => run.status === RunStatuses.failed);
67586
+ const firedOn = day(newestFired.createdAt);
67587
+ const reasons = [...new Set(unsettled.map((run) => refreshReason(ctx, run)))].join(" ");
67588
+ const reason = reasons.length > REASON_LIMIT ? `${truncateUtf16(reasons, REASON_LIMIT)}...` : reasons;
67589
+ const outcome2 = firing.length === 1 ? `The scheduled SEO refresh on ${firedOn} ${failed ? "failed" : "only partly completed"}` : `${unsettled.length} of ${firing.length} profile refreshes in the scheduled SEO refresh on ${firedOn} did not fully complete`;
67590
+ const line = firing.length > 1 ? `${outcome2}.` : unsettled[0].error ? reason : `${outcome2}: ${reason}`;
67591
+ const next = nextAt ? ` on ${day(nextAt)}` : "";
67592
+ return {
67593
+ status: CheckStatuses.warn,
67594
+ code: failed ? "seo.data.refresh-failed" : "seo.data.refresh-partial",
67595
+ summary: `${outcome2}. Reason: ${reason} ${last ? `The last completed SEO refresh was on ${day(last.at)}.` : "No SEO refresh has completed yet."}`,
67596
+ remediation: `Fix what the reason names, then refresh now with \`canonry seo refresh-intent ${name} <profile-id>\` and \`canonry seo sync\`, or wait for the next scheduled refresh${next}. A completed refresh of the same profile clears this warning.`,
67597
+ details,
67598
+ alert: {
67599
+ label: failed ? "SEO refresh failed" : "SEO refresh incomplete",
67600
+ line: firstSentence(line) ?? line,
67601
+ next: `Fix the cause, then refresh by hand or wait for the next scheduled refresh${next}.`
67602
+ }
67603
+ };
67604
+ }
67605
+ if (now >= Date.parse(deadlineAt)) {
67606
+ const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and75(seoSyncRuns(project.id), inArray39(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc20(runs.createdAt)).limit(1).get();
67607
+ const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
67608
+ const missed = last ? `The scheduled refresh due ${day(dueAt)} did not complete within ${GRACE_DAYS} days.` : `Its first refresh, due ${day(dueAt)}, did not complete within ${GRACE_DAYS} days.`;
67609
+ return {
67610
+ status: CheckStatuses.warn,
67611
+ code: "seo.data.overdue",
67612
+ summary: `${since} ${missed}${active ? ` A refresh started ${day(active.createdAt)} has not finished, and scheduled refreshes wait for it.` : ""}`,
67613
+ remediation: active ? `Check that refresh with \`canonry seo run show ${name} ${active.id}\`, and cancel it with \`canonry seo run cancel ${name} ${active.id}\` if it is stuck.` : `Check that the server was running on ${day(dueAt)} and what \`canonry runs ${name} --kind seo-sync\` shows for that day. To refresh now, run \`canonry seo refresh-intent ${name} <profile-id>\`, then \`canonry seo sync\`.`,
67614
+ details: { ...details, activeRunId: active?.id ?? null },
67615
+ alert: {
67616
+ label: "SEO refresh overdue",
67617
+ line: since,
67618
+ next: active ? "Check the refresh that has not finished, and cancel it if it is stuck." : `Check that the server was up on ${day(dueAt)}, then refresh by hand.`
67619
+ }
67620
+ };
67621
+ }
67622
+ const pending = Date.parse(dueAt) <= now;
67623
+ if (!last) {
67624
+ return {
67625
+ status: CheckStatuses.ok,
67626
+ code: "seo.data.awaiting-first-refresh",
67627
+ summary: pending ? `No SEO refresh has completed yet. The first scheduled refresh, due ${day(dueAt)}, has until ${day(deadlineAt)} to complete.` : `No SEO refresh has completed yet. The first scheduled refresh is due ${day(dueAt)}.`,
67628
+ remediation: null,
67629
+ details
67630
+ };
67631
+ }
67632
+ return {
67633
+ status: CheckStatuses.ok,
67634
+ code: "seo.data.fresh",
67635
+ summary: `The last SEO refresh completed on ${day(last.at)}. ${pending ? `The scheduled refresh due ${day(dueAt)} has until ${day(deadlineAt)} to complete.` : `The next scheduled refresh is due ${day(dueAt)}.`}`,
67636
+ remediation: null,
67637
+ details
67638
+ };
67639
+ }
67640
+ var seoRecentRefreshCheck = {
67641
+ id: "seo.data.recent-refresh",
67642
+ category: CheckCategories.integrations,
67643
+ scope: CheckScopes.project,
67644
+ title: "SEO data still refreshing",
67645
+ run: (ctx) => {
67646
+ if (!ctx.project) return skipped2("seo.data.no-project", "Project context required.");
67647
+ return gradeSeoRefresh(ctx, ctx.project);
67648
+ }
67649
+ };
67650
+ var SEO_REFRESH_CHECKS = [seoRecentRefreshCheck];
67651
+
67416
67652
  // ../api-routes/src/site-reachability.ts
67417
67653
  import dns3 from "dns/promises";
67418
67654
  import http3 from "http";
@@ -67616,7 +67852,7 @@ function probeHostFromDomain(value) {
67616
67852
  if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
67617
67853
  return hostname;
67618
67854
  }
67619
- var skipped2 = (code, summary3) => ({ status: CheckStatuses.skipped, code, summary: summary3, remediation: null });
67855
+ var skipped3 = (code, summary3) => ({ status: CheckStatuses.skipped, code, summary: summary3, remediation: null });
67620
67856
  var siteReachabilityCheck = {
67621
67857
  id: SITE_REACHABILITY_CHECK_ID,
67622
67858
  category: CheckCategories.integrations,
@@ -67627,16 +67863,16 @@ var siteReachabilityCheck = {
67627
67863
  // `canonry doctor --project` must not exit 1 on a single dropped packet.
67628
67864
  optIn: true,
67629
67865
  run: async (ctx) => {
67630
- if (!ctx.project) return skipped2("site.reachability.no-project", "Project context required.");
67866
+ if (!ctx.project) return skipped3("site.reachability.no-project", "Project context required.");
67631
67867
  const host = probeHostFromDomain(ctx.project.canonicalDomain);
67632
67868
  if (!host) {
67633
- return skipped2("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
67869
+ return skipped3("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
67634
67870
  }
67635
67871
  const url = `https://${host}/`;
67636
67872
  const probe = ctx.probeSiteReachability ?? probeSiteReachability;
67637
67873
  const result = await probe(url);
67638
67874
  if (result.state === "unavailable") {
67639
- return skipped2("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
67875
+ return skipped3("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
67640
67876
  }
67641
67877
  if (result.state === "up") {
67642
67878
  return {
@@ -67677,7 +67913,7 @@ var siteReachabilityCheck = {
67677
67913
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
67678
67914
 
67679
67915
  // ../api-routes/src/doctor/checks/api-hosts.ts
67680
- import { eq as eq90 } from "drizzle-orm";
67916
+ import { eq as eq91 } from "drizzle-orm";
67681
67917
 
67682
67918
  // ../api-routes/src/api-host-probe.ts
67683
67919
  import dns4 from "dns/promises";
@@ -67783,7 +68019,7 @@ async function probeApiHost(host, options = {}) {
67783
68019
  }
67784
68020
 
67785
68021
  // ../api-routes/src/doctor/checks/integration-sync.ts
67786
- import { and as and75, count as count7, desc as desc42, eq as eq89, gte as gte23, gt as gt8, inArray as inArray39, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
68022
+ import { and as and76, count as count7, desc as desc43, eq as eq90, gte as gte23, gt as gt9, inArray as inArray40, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
67787
68023
  var SYNC_FAILURE_WINDOW = 3;
67788
68024
  var SUMMARY_ERROR_LIMIT = 300;
67789
68025
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -67804,7 +68040,7 @@ function gscConnection(ctx, project) {
67804
68040
  function gbpConnection(ctx, project) {
67805
68041
  if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
67806
68042
  if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
67807
- const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and75(eq89(gbpLocations.projectId, project.id), eq89(gbpLocations.selected, true))).limit(1).get();
68043
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and76(eq90(gbpLocations.projectId, project.id), eq90(gbpLocations.selected, true))).limit(1).get();
67808
68044
  return selected ? CONNECTED : { state: "no-selected-locations" };
67809
68045
  }
67810
68046
  function gaConnection(ctx, project) {
@@ -67861,7 +68097,7 @@ var SPECS = [
67861
68097
  causeCheckIds: ["google.auth.connection", "google.auth.property-access", "google.auth.scopes", "gsc.api.reachable"]
67862
68098
  }
67863
68099
  ];
67864
- function skipped3(code, summary3) {
68100
+ function skipped4(code, summary3) {
67865
68101
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
67866
68102
  }
67867
68103
  function latestErrorText(raw) {
@@ -67869,16 +68105,16 @@ function latestErrorText(raw) {
67869
68105
  return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
67870
68106
  }
67871
68107
  function readSyncRunState(db, projectId, kind, options = {}) {
67872
- const scope = and75(
67873
- eq89(runs.projectId, projectId),
67874
- eq89(runs.kind, kind),
67875
- ...options.sourceId ? [eq89(runs.sourceId, options.sourceId)] : [],
68108
+ const scope = and76(
68109
+ eq90(runs.projectId, projectId),
68110
+ eq90(runs.kind, kind),
68111
+ ...options.sourceId ? [eq90(runs.sourceId, options.sourceId)] : [],
67876
68112
  ...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
67877
68113
  );
67878
- const graded = options.since ? and75(scope, gte23(runs.createdAt, options.since)) : scope;
67879
- const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and75(graded, inArray39(runs.status, FINISHED_STATUSES))).orderBy(desc42(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
67880
- const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and75(graded, inArray39(runs.status, SUCCESS_STATUSES))).orderBy(desc42(runs.createdAt)).limit(1).get();
67881
- const failureCount = db.select({ count: count7() }).from(runs).where(and75(lastSuccess ? and75(graded, gt8(runs.createdAt, lastSuccess.createdAt)) : graded, eq89(runs.status, RunStatuses.failed))).get();
68114
+ const graded = options.since ? and76(scope, gte23(runs.createdAt, options.since)) : scope;
68115
+ const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and76(graded, inArray40(runs.status, FINISHED_STATUSES))).orderBy(desc43(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
68116
+ const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and76(graded, inArray40(runs.status, SUCCESS_STATUSES))).orderBy(desc43(runs.createdAt)).limit(1).get();
68117
+ const failureCount = db.select({ count: count7() }).from(runs).where(and76(lastSuccess ? and76(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq90(runs.status, RunStatuses.failed))).get();
67882
68118
  const failuresSinceSuccess = Number(failureCount?.count ?? 0);
67883
68119
  const latest = recent[0];
67884
68120
  return {
@@ -67895,21 +68131,21 @@ function readSyncRunState(db, projectId, kind, options = {}) {
67895
68131
  function gradeSyncFailures(ctx, spec) {
67896
68132
  const code = (suffix) => `${spec.prefix}.sync.${suffix}`;
67897
68133
  const project = ctx.project;
67898
- if (!project) return skipped3(code("no-project"), "Project context required.");
68134
+ if (!project) return skipped4(code("no-project"), "Project context required.");
67899
68135
  const connection = spec.connection(ctx, project);
67900
68136
  if (connection.state === "store-unavailable") {
67901
- return skipped3(code("store-unavailable"), `No ${spec.label} connection store configured for this deployment.`);
68137
+ return skipped4(code("store-unavailable"), `No ${spec.label} connection store configured for this deployment.`);
67902
68138
  }
67903
68139
  if (connection.state === "not-connected") {
67904
- return skipped3(code("not-connected"), `${spec.label} is not connected for this project.`);
68140
+ return skipped4(code("not-connected"), `${spec.label} is not connected for this project.`);
67905
68141
  }
67906
68142
  if (connection.state === "no-selected-locations") {
67907
- return skipped3(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
68143
+ return skipped4(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
67908
68144
  }
67909
68145
  const nothingToSync = spec.nothingToSyncErrors ?? [];
67910
68146
  const state = readSyncRunState(ctx.db, project.id, spec.kind, { since: connection.since, nothingToSyncErrors: nothingToSync });
67911
68147
  if (state.latestStatus === null) {
67912
- return skipped3(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
68148
+ return skipped4(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
67913
68149
  }
67914
68150
  const latestFailed = state.latestStatus === RunStatuses.failed;
67915
68151
  const details = {
@@ -67965,7 +68201,7 @@ var API_HOST_TABLE = {
67965
68201
  ads: ["api.ads.openai.com"]
67966
68202
  };
67967
68203
  var API_HOST_EXCLUDED = { gsc: [new URL(INDEXING_API_BASE).hostname] };
67968
- function skipped4(code, summary3) {
68204
+ function skipped5(code, summary3) {
67969
68205
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
67970
68206
  }
67971
68207
  function connectionFor(prefix, ctx, project) {
@@ -67975,7 +68211,7 @@ function connectionFor(prefix, ctx, project) {
67975
68211
  if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
67976
68212
  if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
67977
68213
  if (prefix === "bing") return ctx.bingConnectionStore?.getConnection(project.canonicalDomain) ? { state: "connected", since: null } : { state: "not-connected" };
67978
- return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq90(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
68214
+ return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq91(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
67979
68215
  }
67980
68216
  function syncCommand(prefix) {
67981
68217
  if (prefix === "gsc") return "google sync";
@@ -67989,9 +68225,9 @@ function syncCommand(prefix) {
67989
68225
  async function grade2(prefix, ctx) {
67990
68226
  const id = `${prefix}.api`;
67991
68227
  const checkId = `${id}.reachable`;
67992
- if (!ctx.project) return skipped4(`${id}.no-project`, "Project context required.");
68228
+ if (!ctx.project) return skipped5(`${id}.no-project`, "Project context required.");
67993
68229
  const connection = connectionFor(prefix, ctx, ctx.project);
67994
- if (connection.state !== "connected") return skipped4(`${id}.not-connected`, `${prefix} is not connected for this project.`);
68230
+ if (connection.state !== "connected") return skipped5(`${id}.not-connected`, `${prefix} is not connected for this project.`);
67995
68231
  const probe = ctx.probeApiHost ?? probeApiHost;
67996
68232
  const results = await Promise.all(API_HOST_TABLE[prefix].map((host) => probe(host)));
67997
68233
  const details = { hosts: results };
@@ -68020,7 +68256,7 @@ function definition(prefix) {
68020
68256
  var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
68021
68257
 
68022
68258
  // ../api-routes/src/doctor/checks/gbp-auth.ts
68023
- import { and as and76, eq as eq91 } from "drizzle-orm";
68259
+ import { and as and77, eq as eq92 } from "drizzle-orm";
68024
68260
  var RECENT_SYNC_WARN_DAYS = 4;
68025
68261
  var RECENT_SYNC_FAIL_DAYS = 30;
68026
68262
  function skippedNoProject3() {
@@ -68040,7 +68276,7 @@ function storeUnavailable() {
68040
68276
  };
68041
68277
  }
68042
68278
  function hasSelectedLocation(ctx, projectId) {
68043
- return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and76(eq91(gbpLocations.projectId, projectId), eq91(gbpLocations.selected, true))).limit(1).get();
68279
+ return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and77(eq92(gbpLocations.projectId, projectId), eq92(gbpLocations.selected, true))).limit(1).get();
68044
68280
  }
68045
68281
  async function resolveGbpToken(ctx) {
68046
68282
  if (!ctx.project) return { ok: false, output: skippedNoProject3() };
@@ -68300,7 +68536,7 @@ var recentSyncCheck = {
68300
68536
  title: "GBP recent sync",
68301
68537
  run: (ctx) => {
68302
68538
  if (!ctx.project) return skippedNoProject3();
68303
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and76(eq91(gbpLocations.projectId, ctx.project.id), eq91(gbpLocations.selected, true))).all();
68539
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and77(eq92(gbpLocations.projectId, ctx.project.id), eq92(gbpLocations.selected, true))).all();
68304
68540
  if (selected.length === 0) {
68305
68541
  return {
68306
68542
  status: CheckStatuses.skipped,
@@ -68360,7 +68596,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
68360
68596
  );
68361
68597
 
68362
68598
  // ../api-routes/src/doctor/checks/places.ts
68363
- import { eq as eq92 } from "drizzle-orm";
68599
+ import { eq as eq93 } from "drizzle-orm";
68364
68600
  var apiKeyCheck = {
68365
68601
  id: "gbp.places.api-key",
68366
68602
  category: CheckCategories.auth,
@@ -68405,7 +68641,7 @@ var apiKeyCheck = {
68405
68641
  details: { tier: cfg.tier }
68406
68642
  };
68407
68643
  }
68408
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq92(gbpLocations.projectId, ctx.project.id)).all();
68644
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq93(gbpLocations.projectId, ctx.project.id)).all();
68409
68645
  const selected = rows.filter((r) => r.selected);
68410
68646
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
68411
68647
  const details = {
@@ -68438,7 +68674,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
68438
68674
  );
68439
68675
 
68440
68676
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
68441
- import { and as and77, desc as desc43, eq as eq93, gte as gte24, inArray as inArray40, sql as sql32 } from "drizzle-orm";
68677
+ import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray41, sql as sql33 } from "drizzle-orm";
68442
68678
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
68443
68679
  var V4_ACCESS_REMEDIATION = "Business Profile reviews use the legacy My Business API (mybusiness.googleapis.com), which Google enables per Cloud project only on request; the API Library and `gcloud services enable` cannot turn it on. Open the enable link in the API access approval email as the approved account, or reply to the access-request thread asking Google to enable mybusiness.googleapis.com for your project number, then run `canonry gbp sync`.";
68444
68680
  var reviewsAccessCheck = {
@@ -68466,7 +68702,7 @@ var reviewsAccessCheck = {
68466
68702
  reviewsAccess: gbpLocations.reviewsAccess,
68467
68703
  reviewsAccessReason: gbpLocations.reviewsAccessReason,
68468
68704
  reviewsCheckedAt: gbpLocations.reviewsCheckedAt
68469
- }).from(gbpLocations).where(and77(eq93(gbpLocations.projectId, project.id), eq93(gbpLocations.selected, true))).all();
68705
+ }).from(gbpLocations).where(and78(eq94(gbpLocations.projectId, project.id), eq94(gbpLocations.selected, true))).all();
68470
68706
  if (locations.length === 0) {
68471
68707
  return {
68472
68708
  status: CheckStatuses.skipped,
@@ -68486,7 +68722,7 @@ var reviewsAccessCheck = {
68486
68722
  const places = ctx.getPlacesConfig?.();
68487
68723
  const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
68488
68724
  const v4Worked = new Set(
68489
- ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and77(eq93(gbpReviewRatings.projectId, project.id), eq93(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
68725
+ ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and78(eq94(gbpReviewRatings.projectId, project.id), eq94(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
68490
68726
  );
68491
68727
  const coverageOf = (loc) => {
68492
68728
  if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
@@ -68520,7 +68756,7 @@ var reviewsAccessCheck = {
68520
68756
  none > 0 ? `${none} have no review source` : null
68521
68757
  ].filter((part) => part !== null);
68522
68758
  const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
68523
- const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq93(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
68759
+ const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq94(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
68524
68760
  const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
68525
68761
  if (none > 0 && subscribed) {
68526
68762
  return {
@@ -68551,7 +68787,7 @@ var reviewDeliveryCheck = {
68551
68787
  return { status: CheckStatuses.skipped, code: "gbp.reviews.delivery.no-project", summary: "Project context required.", remediation: null };
68552
68788
  }
68553
68789
  const since = new Date(Date.now() - UNDELIVERED_ATTENTION_DAYS * 864e5).toISOString();
68554
- const lastTouched = sql32`coalesce(${reviewWebhookDeliveries.attemptedAt}, ${reviewWebhookDeliveries.createdAt})`;
68790
+ const lastTouched = sql33`coalesce(${reviewWebhookDeliveries.attemptedAt}, ${reviewWebhookDeliveries.createdAt})`;
68555
68791
  const rows = ctx.db.select({
68556
68792
  id: reviewWebhookDeliveries.id,
68557
68793
  notificationId: reviewWebhookDeliveries.notificationId,
@@ -68559,11 +68795,11 @@ var reviewDeliveryCheck = {
68559
68795
  attempts: reviewWebhookDeliveries.attempts,
68560
68796
  lastTouched,
68561
68797
  payload: reviewWebhookDeliveries.payload
68562
- }).from(reviewWebhookDeliveries).where(and77(
68563
- eq93(reviewWebhookDeliveries.projectId, ctx.project.id),
68564
- inArray40(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68798
+ }).from(reviewWebhookDeliveries).where(and78(
68799
+ eq94(reviewWebhookDeliveries.projectId, ctx.project.id),
68800
+ inArray41(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68565
68801
  gte24(lastTouched, since)
68566
- )).orderBy(desc43(lastTouched)).all();
68802
+ )).orderBy(desc44(lastTouched)).all();
68567
68803
  if (rows.length === 0) {
68568
68804
  return {
68569
68805
  status: CheckStatuses.ok,
@@ -68572,7 +68808,7 @@ var reviewDeliveryCheck = {
68572
68808
  remediation: null
68573
68809
  };
68574
68810
  }
68575
- const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq93(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
68811
+ const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq94(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
68576
68812
  const deliveries = rows.map((row) => {
68577
68813
  const review = row.payload.reviews[0];
68578
68814
  return {
@@ -69146,7 +69382,7 @@ var RUNTIME_STATE_CHECKS = [
69146
69382
  ];
69147
69383
 
69148
69384
  // ../api-routes/src/doctor/checks/traffic-source.ts
69149
- import { and as and78, desc as desc44, eq as eq94, gte as gte25, inArray as inArray41, isNotNull as isNotNull9, ne as ne16, sql as sql33 } from "drizzle-orm";
69385
+ import { and as and79, desc as desc45, eq as eq95, gte as gte25, inArray as inArray42, isNotNull as isNotNull9, ne as ne16, sql as sql34 } from "drizzle-orm";
69150
69386
  var RECENT_DATA_WARN_DAYS = 7;
69151
69387
  var RECENT_DATA_FAIL_DAYS = 30;
69152
69388
  function isCloudflareDirectPush(source) {
@@ -69185,8 +69421,8 @@ function skippedNoProject5() {
69185
69421
  function loadProbes(ctx) {
69186
69422
  if (!ctx.project) return [];
69187
69423
  const rows = ctx.db.select().from(trafficSources).where(
69188
- and78(
69189
- eq94(trafficSources.projectId, ctx.project.id),
69424
+ and79(
69425
+ eq95(trafficSources.projectId, ctx.project.id),
69190
69426
  ne16(trafficSources.status, TrafficSourceStatuses.archived)
69191
69427
  )
69192
69428
  ).all();
@@ -69323,28 +69559,28 @@ var recentDataCheck = {
69323
69559
  const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
69324
69560
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
69325
69561
  const recentCrawlers = Number(
69326
- ctx.db.select({ total: sql33`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69327
- and78(
69328
- eq94(crawlerEventsHourly.projectId, ctx.project.id),
69329
- inArray41(crawlerEventsHourly.sourceId, activeSourceIds),
69562
+ ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69563
+ and79(
69564
+ eq95(crawlerEventsHourly.projectId, ctx.project.id),
69565
+ inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
69330
69566
  gte25(crawlerEventsHourly.tsHour, warnCutoff)
69331
69567
  )
69332
69568
  ).get()?.total ?? 0
69333
69569
  );
69334
69570
  const recentReferrals = Number(
69335
- ctx.db.select({ total: sql33`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69336
- and78(
69337
- eq94(aiReferralEventsHourly.projectId, ctx.project.id),
69338
- inArray41(aiReferralEventsHourly.sourceId, activeSourceIds),
69571
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69572
+ and79(
69573
+ eq95(aiReferralEventsHourly.projectId, ctx.project.id),
69574
+ inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
69339
69575
  gte25(aiReferralEventsHourly.tsHour, warnCutoff)
69340
69576
  )
69341
69577
  ).get()?.total ?? 0
69342
69578
  );
69343
69579
  const recentUserFetches = Number(
69344
- ctx.db.select({ total: sql33`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69345
- and78(
69346
- eq94(aiUserFetchEventsHourly.projectId, ctx.project.id),
69347
- inArray41(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69580
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69581
+ and79(
69582
+ eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
69583
+ inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69348
69584
  gte25(aiUserFetchEventsHourly.tsHour, warnCutoff)
69349
69585
  )
69350
69586
  ).get()?.total ?? 0
@@ -69367,28 +69603,28 @@ var recentDataCheck = {
69367
69603
  };
69368
69604
  }
69369
69605
  const olderCrawlers = Number(
69370
- ctx.db.select({ total: sql33`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69371
- and78(
69372
- eq94(crawlerEventsHourly.projectId, ctx.project.id),
69373
- inArray41(crawlerEventsHourly.sourceId, activeSourceIds),
69606
+ ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69607
+ and79(
69608
+ eq95(crawlerEventsHourly.projectId, ctx.project.id),
69609
+ inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
69374
69610
  gte25(crawlerEventsHourly.tsHour, failCutoff)
69375
69611
  )
69376
69612
  ).get()?.total ?? 0
69377
69613
  );
69378
69614
  const olderReferrals = Number(
69379
- ctx.db.select({ total: sql33`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69380
- and78(
69381
- eq94(aiReferralEventsHourly.projectId, ctx.project.id),
69382
- inArray41(aiReferralEventsHourly.sourceId, activeSourceIds),
69615
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69616
+ and79(
69617
+ eq95(aiReferralEventsHourly.projectId, ctx.project.id),
69618
+ inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
69383
69619
  gte25(aiReferralEventsHourly.tsHour, failCutoff)
69384
69620
  )
69385
69621
  ).get()?.total ?? 0
69386
69622
  );
69387
69623
  const olderUserFetches = Number(
69388
- ctx.db.select({ total: sql33`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69389
- and78(
69390
- eq94(aiUserFetchEventsHourly.projectId, ctx.project.id),
69391
- inArray41(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69624
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69625
+ and79(
69626
+ eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
69627
+ inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69392
69628
  gte25(aiUserFetchEventsHourly.tsHour, failCutoff)
69393
69629
  )
69394
69630
  ).get()?.total ?? 0
@@ -69453,7 +69689,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
69453
69689
  function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
69454
69690
  const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
69455
69691
  const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
69456
- const skipped5 = results.filter((r) => r.output.status === CheckStatuses.skipped);
69692
+ const skipped6 = results.filter((r) => r.output.status === CheckStatuses.skipped);
69457
69693
  const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
69458
69694
  const detail = {
69459
69695
  sources: results.map((r) => ({
@@ -69487,7 +69723,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
69487
69723
  return {
69488
69724
  status: CheckStatuses.ok,
69489
69725
  code: `traffic.${fallbackId}.ok`,
69490
- summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped5.length > 0 ? ` (${skipped5.length} skipped)` : ""}.`,
69726
+ summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped6.length > 0 ? ` (${skipped6.length} skipped)` : ""}.`,
69491
69727
  details: detail
69492
69728
  };
69493
69729
  }
@@ -69495,7 +69731,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
69495
69731
  status: CheckStatuses.skipped,
69496
69732
  code: `traffic.${fallbackId}.all-skipped`,
69497
69733
  summary: `No source-type validator was available for any of the ${results.length} connected source(s).`,
69498
- remediation: skipped5.find((result) => result.output.remediation)?.output.remediation,
69734
+ remediation: skipped6.find((result) => result.output.remediation)?.output.remediation,
69499
69735
  details: detail
69500
69736
  };
69501
69737
  }
@@ -69714,12 +69950,12 @@ var syncLagCheck = {
69714
69950
  }
69715
69951
  };
69716
69952
  function lastQueueStopReason(ctx, sourceId) {
69717
- const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and78(
69718
- eq94(runs.projectId, ctx.project.id),
69719
- eq94(runs.kind, RunKinds["traffic-sync"]),
69720
- eq94(runs.sourceId, sourceId),
69953
+ const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and79(
69954
+ eq95(runs.projectId, ctx.project.id),
69955
+ eq95(runs.kind, RunKinds["traffic-sync"]),
69956
+ eq95(runs.sourceId, sourceId),
69721
69957
  isNotNull9(runs.syncSummary)
69722
- )).orderBy(desc44(runs.createdAt)).limit(1).get();
69958
+ )).orderBy(desc45(runs.createdAt)).limit(1).get();
69723
69959
  return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
69724
69960
  }
69725
69961
  var QUEUE_LIMIT_STOPS = [
@@ -70009,7 +70245,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
70009
70245
  ];
70010
70246
  var FRESH_SNAPSHOT_WARN_DAYS = 7;
70011
70247
  var FRESH_SNAPSHOT_FAIL_DAYS = 30;
70012
- var DAY_MS5 = 24 * 60 * 60 * 1e3;
70248
+ var DAY_MS6 = 24 * 60 * 60 * 1e3;
70013
70249
  function unavailableStatus() {
70014
70250
  return {
70015
70251
  status: CheckStatuses.skipped,
@@ -70096,7 +70332,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
70096
70332
  details: { latestSnapshotAt }
70097
70333
  };
70098
70334
  }
70099
- const ageDays2 = (now.getTime() - snapshotMs) / DAY_MS5;
70335
+ const ageDays2 = (now.getTime() - snapshotMs) / DAY_MS6;
70100
70336
  const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays2)) };
70101
70337
  if (ageDays2 < 0) {
70102
70338
  return {
@@ -70319,6 +70555,7 @@ var ALL_CHECKS = [
70319
70555
  ...WORDPRESS_PUBLISH_CHECKS,
70320
70556
  ...GA_AUTH_CHECKS,
70321
70557
  ...DATA_FRESHNESS_CHECKS,
70558
+ ...SEO_REFRESH_CHECKS,
70322
70559
  reportSweepsCheck,
70323
70560
  reportModelsCheck,
70324
70561
  reportDailyDataCheck,
@@ -70516,7 +70753,7 @@ async function doctorRoutes(app, opts) {
70516
70753
  }
70517
70754
 
70518
70755
  // ../api-routes/src/status.ts
70519
- import { and as and79, desc as desc45, eq as eq95, isNull as isNull15 } from "drizzle-orm";
70756
+ import { and as and80, desc as desc46, eq as eq96, isNull as isNull15 } from "drizzle-orm";
70520
70757
  function ageDays(iso) {
70521
70758
  if (!iso) return null;
70522
70759
  const ms = Date.now() - Date.parse(iso);
@@ -70566,7 +70803,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
70566
70803
  };
70567
70804
  }
70568
70805
  function latestDate2(db, table, projectId) {
70569
- const row = db.select({ date: table.date }).from(table).where(eq95(table.projectId, projectId)).orderBy(desc45(table.date)).limit(1).get();
70806
+ const row = db.select({ date: table.date }).from(table).where(eq96(table.projectId, projectId)).orderBy(desc46(table.date)).limit(1).get();
70570
70807
  return row?.date ?? null;
70571
70808
  }
70572
70809
  function projectRows(opts, project) {
@@ -70581,7 +70818,7 @@ function projectRows(opts, project) {
70581
70818
  const rows = [];
70582
70819
  const gsc = gscConnection(ctx, info);
70583
70820
  if (connected(gsc)) {
70584
- const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq95(gscDataWatermarks.projectId, project.id)).limit(1).get();
70821
+ const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq96(gscDataWatermarks.projectId, project.id)).limit(1).get();
70585
70822
  const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
70586
70823
  const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
70587
70824
  rows.push(makeRow("gsc", "Search Console", RunKinds["gsc-sync"], state, SYNC_IDLE_DAYS, dataThrough, null, ["gsc.sync.recent-failures"], GSC_DATA_AGING_DAYS));
@@ -70594,12 +70831,12 @@ function projectRows(opts, project) {
70594
70831
  const gbp = gbpConnection(ctx, info);
70595
70832
  if (connected(gbp)) rows.push(makeRow("gbp", "Google Business Profile", RunKinds["gbp-sync"], readSyncRunState(opts.db, project.id, RunKinds["gbp-sync"], { nothingToSyncErrors: GBP_NOTHING_TO_SYNC_ERRORS }), RECENT_SYNC_WARN_DAYS, null, null, ["gbp.sync.recent-failures"]));
70596
70833
  if (opts.bingConnectionStore?.getConnection(project.canonicalDomain)) rows.push(makeRow("bing", "Bing Webmaster Tools", RunKinds["bing-inspect-sitemap"], readSyncRunState(opts.db, project.id, RunKinds["bing-inspect-sitemap"]), null));
70597
- const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq95(adsConnections.projectId, project.id)).limit(1).get();
70834
+ const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq96(adsConnections.projectId, project.id)).limit(1).get();
70598
70835
  if (ads) rows.push(makeRow("ads", "OpenAI Ads", RunKinds["ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["ads-sync"]), ADS_RECENT_SYNC_WARN_DAYS, null, null, ["ads.data.recent-sync"], ADS_RECENT_SYNC_WARN_DAYS, ads.lastSyncedAt));
70599
70836
  const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
70600
70837
  if (marketing?.googleAds) rows.push(makeRow("google-ads", "Google Ads", RunKinds["google-ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["google-ads-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["google-ads.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.googleAds.latestSnapshotAt));
70601
70838
  if (marketing?.gtm) rows.push(makeRow("gtm", "Google Tag Manager", RunKinds["gtm-sync"], readSyncRunState(opts.db, project.id, RunKinds["gtm-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["gtm.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.gtm.latestSnapshotAt));
70602
- const sources = opts.db.select().from(trafficSources).where(and79(eq95(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
70839
+ const sources = opts.db.select().from(trafficSources).where(and80(eq96(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
70603
70840
  for (const source of sources) {
70604
70841
  const row = makeRow("traffic", source.displayName, RunKinds["traffic-sync"], readSyncRunState(opts.db, project.id, RunKinds["traffic-sync"], { sourceId: source.id }), null, null, source.id, ["traffic.source.sync-lag", "traffic.source.recent-data"]);
70605
70842
  const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
@@ -70611,7 +70848,7 @@ function projectRows(opts, project) {
70611
70848
  }
70612
70849
  function projectStatus(opts, project) {
70613
70850
  const integrations = projectRows(opts, project);
70614
- const healthRow = opts.db.select().from(doctorHealthState).where(eq95(doctorHealthState.projectId, project.id)).limit(1).get();
70851
+ const healthRow = opts.db.select().from(doctorHealthState).where(eq96(doctorHealthState.projectId, project.id)).limit(1).get();
70615
70852
  const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
70616
70853
  const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
70617
70854
  const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
@@ -70631,7 +70868,7 @@ async function statusRoutes(app, opts) {
70631
70868
  if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
70632
70869
  return instanceStatusDtoSchema.parse(result);
70633
70870
  }
70634
- const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq95(projects.id, scopedId)) : opts.db.select().from(projects)).all();
70871
+ const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq96(projects.id, scopedId)) : opts.db.select().from(projects)).all();
70635
70872
  const output = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...opts.getMcpHealth ? { mcp: opts.getMcpHealth() } : {}, summary: { projects: projectsRows.length, ok: 0, failing: 0, warning: 0, stale: 0, neverSynced: 0 }, projects: projectsRows.map((project) => projectStatus(opts, project)) };
70636
70873
  for (const project of output.projects) {
70637
70874
  const key = project.state === "never-synced" ? "neverSynced" : project.state;
@@ -70644,7 +70881,7 @@ async function statusRoutes(app, opts) {
70644
70881
 
70645
70882
  // ../api-routes/src/discovery/routes.ts
70646
70883
  import crypto54 from "crypto";
70647
- import { and as and80, desc as desc46, eq as eq96, gte as gte26, inArray as inArray42, isNull as isNull16, or as or20 } from "drizzle-orm";
70884
+ import { and as and81, desc as desc47, eq as eq97, gte as gte26, inArray as inArray43, isNull as isNull16, or as or20 } from "drizzle-orm";
70648
70885
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
70649
70886
  async function discoveryRoutes(app, opts) {
70650
70887
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -70678,13 +70915,13 @@ async function discoveryRoutes(app, opts) {
70678
70915
  const now = (/* @__PURE__ */ new Date()).toISOString();
70679
70916
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
70680
70917
  const decision = app.db.transaction((tx) => {
70681
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and80(
70682
- eq96(discoverySessions.projectId, project.id),
70683
- eq96(discoverySessions.icpDescription, icpDescription),
70918
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and81(
70919
+ eq97(discoverySessions.projectId, project.id),
70920
+ eq97(discoverySessions.icpDescription, icpDescription),
70684
70921
  // Buyer is part of session identity: it changes the seed prompt's
70685
70922
  // semantics, so a request with a different (or no) buyer must start
70686
70923
  // its own session, never adopt another buyer's probes.
70687
- parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq96(discoverySessions.buyerDescription, parsed.data.buyerDescription),
70924
+ parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq97(discoverySessions.buyerDescription, parsed.data.buyerDescription),
70688
70925
  // Locations are identity too: a different service-area subset seeds
70689
70926
  // and probes a different geo, so it must never reuse another geo's
70690
70927
  // session. resolveLocations is deterministic (project-config order),
@@ -70694,18 +70931,18 @@ async function discoveryRoutes(app, opts) {
70694
70931
  // locations a NULL row's subset is unknowable, so it conservatively
70695
70932
  // never reuses — a one-time, bounded (2h window) non-reuse after
70696
70933
  // upgrade, never a wrong reuse.
70697
- locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq96(discoverySessions.locations, locations)) : eq96(discoverySessions.locations, locations),
70934
+ locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq97(discoverySessions.locations, locations)) : eq97(discoverySessions.locations, locations),
70698
70935
  // Seed provider set is identity: a different phrasing distribution
70699
70936
  // must never reuse another set's session. Null (= Gemini-only
70700
70937
  // default, including explicit ['gemini']) matches legacy rows.
70701
- seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq96(discoverySessions.seedProviders, seedProviders),
70702
- inArray42(discoverySessions.status, [
70938
+ seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq97(discoverySessions.seedProviders, seedProviders),
70939
+ inArray43(discoverySessions.status, [
70703
70940
  DiscoverySessionStatuses.queued,
70704
70941
  DiscoverySessionStatuses.seeding,
70705
70942
  DiscoverySessionStatuses.probing
70706
70943
  ]),
70707
70944
  gte26(discoverySessions.createdAt, ageFloorIso)
70708
- )).orderBy(desc46(discoverySessions.createdAt)).get();
70945
+ )).orderBy(desc47(discoverySessions.createdAt)).get();
70709
70946
  if (existing && existing.runId) {
70710
70947
  return { reused: true, sessionId: existing.id, runId: existing.runId };
70711
70948
  }
@@ -70774,7 +71011,7 @@ async function discoveryRoutes(app, opts) {
70774
71011
  const project = resolveProject(app.db, request.params.name);
70775
71012
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
70776
71013
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
70777
- const rows = app.db.select().from(discoverySessions).where(eq96(discoverySessions.projectId, project.id)).orderBy(desc46(discoverySessions.createdAt)).limit(limit).all();
71014
+ const rows = app.db.select().from(discoverySessions).where(eq97(discoverySessions.projectId, project.id)).orderBy(desc47(discoverySessions.createdAt)).limit(limit).all();
70778
71015
  return reply.send(rows.map(serializeSession));
70779
71016
  }
70780
71017
  );
@@ -70782,11 +71019,11 @@ async function discoveryRoutes(app, opts) {
70782
71019
  "/projects/:name/discover/sessions/:id",
70783
71020
  async (request, reply) => {
70784
71021
  const project = resolveProject(app.db, request.params.name);
70785
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71022
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70786
71023
  if (!session || session.projectId !== project.id) {
70787
71024
  throw notFound("Discovery session", request.params.id);
70788
71025
  }
70789
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71026
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70790
71027
  const detail = {
70791
71028
  ...serializeSession(session),
70792
71029
  probes: probeRows.map(serializeProbe)
@@ -70798,7 +71035,7 @@ async function discoveryRoutes(app, opts) {
70798
71035
  "/projects/:name/discover/sessions/:id/harvest",
70799
71036
  async (request, reply) => {
70800
71037
  const project = resolveProject(app.db, request.params.name);
70801
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71038
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70802
71039
  if (!session || session.projectId !== project.id) {
70803
71040
  throw notFound("Discovery session", request.params.id);
70804
71041
  }
@@ -70806,7 +71043,7 @@ async function discoveryRoutes(app, opts) {
70806
71043
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
70807
71044
  const applyAnchor = request.query.anchor !== "false";
70808
71045
  const provider = session.seedProvider ?? "gemini";
70809
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71046
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70810
71047
  const extract = opts.harvestSearchQueries;
70811
71048
  const probesWithQueries = probeRows.map((row) => {
70812
71049
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -70817,7 +71054,7 @@ async function discoveryRoutes(app, opts) {
70817
71054
  return { searchQueries: [] };
70818
71055
  }
70819
71056
  });
70820
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq96(queries.projectId, project.id)).all().map((r) => r.query);
71057
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query);
70821
71058
  const anchorTerms = buildHarvestAnchorTerms(
70822
71059
  [session.icpDescription ?? "", ...trackedQueries],
70823
71060
  effectiveDomains(project)
@@ -70864,11 +71101,11 @@ async function discoveryRoutes(app, opts) {
70864
71101
  "/projects/:name/discover/sessions/:id/promote",
70865
71102
  async (request, reply) => {
70866
71103
  const project = resolveProject(app.db, request.params.name);
70867
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71104
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70868
71105
  if (!session || session.projectId !== project.id) {
70869
71106
  throw notFound("Discovery session", request.params.id);
70870
71107
  }
70871
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71108
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70872
71109
  const cited = /* @__PURE__ */ new Set();
70873
71110
  const aspirational = /* @__PURE__ */ new Set();
70874
71111
  const wasted = /* @__PURE__ */ new Set();
@@ -70903,7 +71140,7 @@ async function discoveryRoutes(app, opts) {
70903
71140
  );
70904
71141
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
70905
71142
  const project = resolveProject(app.db, request.params.name);
70906
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71143
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70907
71144
  if (!session || session.projectId !== project.id) {
70908
71145
  throw notFound("Discovery session", request.params.id);
70909
71146
  }
@@ -70926,7 +71163,7 @@ async function discoveryRoutes(app, opts) {
70926
71163
  const bucketSet = new Set(buckets);
70927
71164
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
70928
71165
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
70929
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71166
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70930
71167
  const candidateQueries = /* @__PURE__ */ new Set();
70931
71168
  for (const probe of probeRows) {
70932
71169
  if (!probe.bucket) continue;
@@ -70934,7 +71171,7 @@ async function discoveryRoutes(app, opts) {
70934
71171
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
70935
71172
  }
70936
71173
  const existingQueries = new Set(
70937
- app.db.select({ query: queries.query }).from(queries).where(eq96(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
71174
+ app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
70938
71175
  );
70939
71176
  const promotedQueries = [];
70940
71177
  const skippedQueries = [];
@@ -71106,21 +71343,21 @@ function planCompetitorPromotion(input) {
71106
71343
  const preferred = new Set(input.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES);
71107
71344
  const eligible = groupCompetitorMap(input.competitorMap, input.probes, preferred).filter((group) => group.hits >= DISCOVERY_PROMOTE_COMPETITOR_MIN_HITS).filter((group) => !typeFilter || group.sources.some((source) => typeFilter.has(source.competitorType))).sort((a, b) => b.hits - a.hits || a.domain.localeCompare(b.domain));
71108
71345
  const tracked = new Set(input.tracked.map((row) => normalizeCompetitorDomain(row.domain)));
71109
- const skipped5 = [];
71346
+ const skipped6 = [];
71110
71347
  const candidates = [];
71111
71348
  for (const group of eligible) {
71112
71349
  const projectDomain = competitorDomainProjectClaim(group.domain, input.project.domains);
71113
71350
  const subdomains = [...new Set(group.sources.map((source) => hostOf(source.domain) ?? source.domain))].filter((host) => host !== group.domain);
71114
71351
  if (projectDomain) {
71115
- skipped5.push({
71352
+ skipped6.push({
71116
71353
  ...group,
71117
71354
  reason: DiscoveryPromoteCompetitorSkipReasons["project-domain"],
71118
71355
  message: `${group.domain} overlaps the project domain ${projectDomain}, so every citation of the project would count for it`
71119
71356
  });
71120
71357
  } else if (tracked.has(group.domain)) {
71121
- skipped5.push({ ...group, reason: DiscoveryPromoteCompetitorSkipReasons["already-tracked"], message: `${group.domain} is already tracked` });
71358
+ skipped6.push({ ...group, reason: DiscoveryPromoteCompetitorSkipReasons["already-tracked"], message: `${group.domain} is already tracked` });
71122
71359
  } else if (subdomains.length > 1) {
71123
- skipped5.push({
71360
+ skipped6.push({
71124
71361
  ...group,
71125
71362
  reason: DiscoveryPromoteCompetitorSkipReasons["shared-host"],
71126
71363
  message: `${group.domain} was cited as ${subdomains.length} different subdomains (${subdomains.join(", ")}), which may be different sites on one shared host, so it is not merged into one competitor; if they are one competitor, add it with canonry competitor add <project> ${group.domain}`
@@ -71134,7 +71371,7 @@ function planCompetitorPromotion(input) {
71134
71371
  for (const group of candidates) {
71135
71372
  const rejections = claimed.get(group.domain);
71136
71373
  if (rejections) {
71137
- skipped5.push({
71374
+ skipped6.push({
71138
71375
  ...group,
71139
71376
  reason: DiscoveryPromoteCompetitorSkipReasons["claimed-by-alias"],
71140
71377
  message: rejections.map((rejection) => `${group.domain} ${describeCompetitorAliasRejection(rejection)}`).join("; ")
@@ -71143,12 +71380,12 @@ function planCompetitorPromotion(input) {
71143
71380
  promote.push(group);
71144
71381
  }
71145
71382
  }
71146
- return { promote, skipped: skipped5 };
71383
+ return { promote, skipped: skipped6 };
71147
71384
  }
71148
71385
 
71149
71386
  // ../api-routes/src/discovery/orchestrate.ts
71150
71387
  import crypto55 from "crypto";
71151
- import { eq as eq97 } from "drizzle-orm";
71388
+ import { eq as eq98 } from "drizzle-orm";
71152
71389
  var DEFAULT_MAX_PROBES = 100;
71153
71390
  var ABSOLUTE_MAX_PROBES = 500;
71154
71391
  function classifyProbeBucket(input) {
@@ -71234,7 +71471,7 @@ async function executeDiscovery(opts) {
71234
71471
  status: DiscoverySessionStatuses.seeding,
71235
71472
  dedupThreshold,
71236
71473
  startedAt
71237
- }).where(eq97(discoverySessions.id, opts.sessionId)).run();
71474
+ }).where(eq98(discoverySessions.id, opts.sessionId)).run();
71238
71475
  const seedResult = await opts.deps.seed({
71239
71476
  project: opts.project,
71240
71477
  icpDescription: opts.icpDescription,
@@ -71295,7 +71532,7 @@ async function executeDiscovery(opts) {
71295
71532
  dedupBandPairFraction: dedupStats.bandPairFraction,
71296
71533
  dedupPairsTotal: dedupStats.pairsTotal,
71297
71534
  warning
71298
- }).where(eq97(discoverySessions.id, opts.sessionId)).run();
71535
+ }).where(eq98(discoverySessions.id, opts.sessionId)).run();
71299
71536
  const probeLocation = opts.locations?.[0];
71300
71537
  const probeResults = await mapWithConcurrency(
71301
71538
  probedCanonicals,
@@ -71349,7 +71586,7 @@ async function executeDiscovery(opts) {
71349
71586
  wastedCount: buckets["wasted-surface"],
71350
71587
  competitorMap,
71351
71588
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
71352
- }).where(eq97(discoverySessions.id, opts.sessionId)).run();
71589
+ }).where(eq98(discoverySessions.id, opts.sessionId)).run();
71353
71590
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
71354
71591
  return {
71355
71592
  buckets,
@@ -71389,7 +71626,7 @@ function markSessionFailed(db, sessionId, error) {
71389
71626
  status: DiscoverySessionStatuses.failed,
71390
71627
  error,
71391
71628
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
71392
- }).where(eq97(discoverySessions.id, sessionId)).run();
71629
+ }).where(eq98(discoverySessions.id, sessionId)).run();
71393
71630
  }
71394
71631
  function dedupeStrings(input) {
71395
71632
  const seen2 = /* @__PURE__ */ new Set();
@@ -71407,12 +71644,12 @@ function dedupeStrings(input) {
71407
71644
 
71408
71645
  // ../api-routes/src/technical-aeo.ts
71409
71646
  import crypto56 from "crypto";
71410
- import { and as and82, asc as asc21, count as count8, desc as desc47, eq as eq99, gte as gte27, inArray as inArray43, isNotNull as isNotNull10, isNull as isNull17, lt as lt14, or as or21, sql as sql35 } from "drizzle-orm";
71647
+ import { and as and83, asc as asc22, count as count8, desc as desc48, eq as eq100, gte as gte27, inArray as inArray44, isNotNull as isNotNull10, isNull as isNull17, lt as lt14, or as or21, sql as sql36 } from "drizzle-orm";
71411
71648
  import { alias } from "drizzle-orm/sqlite-core";
71412
71649
 
71413
71650
  // ../api-routes/src/technical-aeo-issues.ts
71414
71651
  import { createHash as createHash10 } from "crypto";
71415
- import { and as and81, asc as asc20, eq as eq98, gt as gt9, sql as sql34 } from "drizzle-orm";
71652
+ import { and as and82, asc as asc21, eq as eq99, gt as gt10, sql as sql35 } from "drizzle-orm";
71416
71653
  var ADVISORY_CODES = /* @__PURE__ */ new Set([
71417
71654
  "entity-consistency.title.short",
71418
71655
  "site.duplicate-title",
@@ -71557,12 +71794,12 @@ function readSiteAuditIssues(input) {
71557
71794
  }
71558
71795
  }
71559
71796
  if (snapshot.detailsAvailable && snapshot.attemptId) {
71560
- const scope = and81(
71561
- eq98(siteCrawlPages.projectId, project.id),
71562
- eq98(siteCrawlPages.runId, snapshot.runId),
71563
- eq98(siteCrawlPages.attemptId, snapshot.attemptId)
71797
+ const scope = and82(
71798
+ eq99(siteCrawlPages.projectId, project.id),
71799
+ eq99(siteCrawlPages.runId, snapshot.runId),
71800
+ eq99(siteCrawlPages.attemptId, snapshot.attemptId)
71564
71801
  );
71565
- const size = db.select({ bytes: sql34`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
71802
+ const size = db.select({ bytes: sql35`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
71566
71803
  if (size + crossBytes > MAX_EVIDENCE_BYTES) {
71567
71804
  throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
71568
71805
  }
@@ -71573,7 +71810,7 @@ function readSiteAuditIssues(input) {
71573
71810
  url: siteCrawlPages.url,
71574
71811
  auditScore: siteCrawlPages.auditScore,
71575
71812
  auditFields: siteCrawlPages.auditFields
71576
- }).from(siteCrawlPages).where(and81(scope, after === void 0 ? void 0 : gt9(siteCrawlPages.nodeKey, after))).orderBy(asc20(siteCrawlPages.nodeKey)).limit(200).all();
71813
+ }).from(siteCrawlPages).where(and82(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc21(siteCrawlPages.nodeKey)).limit(200).all();
71577
71814
  for (const page3 of pages) {
71578
71815
  evidenceHash.update(JSON.stringify(page3));
71579
71816
  const fields = record(page3.auditFields);
@@ -71935,13 +72172,13 @@ function parseLinkKind(value) {
71935
72172
  return allowed.data;
71936
72173
  }
71937
72174
  function linkKindFilter(linkKind) {
71938
- if (linkKind === SiteHealthLinkKinds.content) return eq99(siteCrawlEdges.isTemplate, false);
71939
- if (linkKind === SiteHealthLinkKinds.template) return eq99(siteCrawlEdges.isTemplate, true);
72175
+ if (linkKind === SiteHealthLinkKinds.content) return eq100(siteCrawlEdges.isTemplate, false);
72176
+ if (linkKind === SiteHealthLinkKinds.template) return eq100(siteCrawlEdges.isTemplate, true);
71940
72177
  return void 0;
71941
72178
  }
71942
72179
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
71943
72180
  var graphTargetNode = alias(siteCrawlGraphNodes, "site_crawl_graph_target_node");
71944
- var PAGE_CHANGE_PREDICATE = sql35`(
72181
+ var PAGE_CHANGE_PREDICATE = sql36`(
71945
72182
  current.url IS NOT previous.url
71946
72183
  OR current.final_url IS NOT previous.final_url
71947
72184
  OR current.path IS NOT previous.path
@@ -71963,7 +72200,7 @@ var PAGE_CHANGE_PREDICATE = sql35`(
71963
72200
  OR current.link_score_raw IS NOT previous.link_score_raw
71964
72201
  OR current.link_score_normalized IS NOT previous.link_score_normalized
71965
72202
  )`;
71966
- var LINK_CHANGE_PREDICATE = sql35`(
72203
+ var LINK_CHANGE_PREDICATE = sql36`(
71967
72204
  current.source_node_key IS NOT previous.source_node_key
71968
72205
  OR current.source_url IS NOT previous.source_url
71969
72206
  OR current.target_node_key IS NOT previous.target_node_key
@@ -72040,20 +72277,20 @@ function changedFields(before, after, fields) {
72040
72277
  }
72041
72278
  async function technicalAeoRoutes(app, opts) {
72042
72279
  const visibleCrawlFilters = (projectId) => [
72043
- eq99(siteCrawlSnapshots.projectId, projectId),
72044
- eq99(runs.projectId, projectId),
72045
- eq99(runs.kind, RunKinds["site-audit"]),
72046
- inArray43(runs.status, SURFACEABLE_STATUSES),
72280
+ eq100(siteCrawlSnapshots.projectId, projectId),
72281
+ eq100(runs.projectId, projectId),
72282
+ eq100(runs.kind, RunKinds["site-audit"]),
72283
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72047
72284
  notProbeRun()
72048
72285
  ];
72049
72286
  const availableScanDatesFor = (projectId, requestedDate) => {
72050
72287
  if (!requestedDate) return void 0;
72051
- const date = sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
72288
+ const date = sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
72052
72289
  const filters = visibleCrawlFilters(projectId);
72053
- const allDates = and82(...filters);
72054
- const matchingDates = and82(...filters, eq99(sql35`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
72055
- const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc47(date)).limit(10).all().map((row) => row.date);
72056
- const countDates = (where) => app.db.select({ value: sql35`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
72290
+ const allDates = and83(...filters);
72291
+ const matchingDates = and83(...filters, eq100(sql36`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
72292
+ const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc48(date)).limit(10).all().map((row) => row.date);
72293
+ const countDates = (where) => app.db.select({ value: sql36`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
72057
72294
  return {
72058
72295
  recentDates: readDates(allDates),
72059
72296
  totalDates: countDates(allDates),
@@ -72069,34 +72306,34 @@ async function technicalAeoRoutes(app, opts) {
72069
72306
  nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
72070
72307
  filters.push(gte27(siteCrawlSnapshots.createdAt, requestedDate), lt14(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
72071
72308
  }
72072
- const selectionFilters = scope.scoredOnly ? [...filters, sql35`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
72073
- const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(...selectionFilters, runId ? eq99(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
72074
- desc47(sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
72075
- desc47(siteCrawlSnapshots.complete),
72076
- desc47(siteCrawlSnapshots.pagesFetched),
72077
- desc47(siteCrawlSnapshots.createdAt),
72078
- desc47(siteCrawlSnapshots.runId)
72309
+ const selectionFilters = scope.scoredOnly ? [...filters, sql36`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
72310
+ const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(...selectionFilters, runId ? eq100(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
72311
+ desc48(sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
72312
+ desc48(siteCrawlSnapshots.complete),
72313
+ desc48(siteCrawlSnapshots.pagesFetched),
72314
+ desc48(siteCrawlSnapshots.createdAt),
72315
+ desc48(siteCrawlSnapshots.runId)
72079
72316
  ).limit(1).get();
72080
72317
  if (!selected) return void 0;
72081
72318
  const date = selected.snapshot.createdAt.slice(0, 10);
72082
72319
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
72083
72320
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
72084
- const sameDate = and82(
72321
+ const sameDate = and83(
72085
72322
  ...filters,
72086
72323
  gte27(siteCrawlSnapshots.createdAt, date),
72087
72324
  lt14(siteCrawlSnapshots.createdAt, nextDate.toISOString())
72088
72325
  );
72089
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72326
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72090
72327
  const candidates = app.db.select({
72091
72328
  runId: siteCrawlSnapshots.runId,
72092
72329
  createdAt: siteCrawlSnapshots.createdAt,
72093
72330
  complete: siteCrawlSnapshots.complete,
72094
72331
  pages: siteCrawlSnapshots.pagesFetched
72095
- }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
72096
- desc47(siteCrawlSnapshots.complete),
72097
- desc47(siteCrawlSnapshots.pagesFetched),
72098
- desc47(siteCrawlSnapshots.createdAt),
72099
- desc47(siteCrawlSnapshots.runId)
72332
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
72333
+ desc48(siteCrawlSnapshots.complete),
72334
+ desc48(siteCrawlSnapshots.pagesFetched),
72335
+ desc48(siteCrawlSnapshots.createdAt),
72336
+ desc48(siteCrawlSnapshots.runId)
72100
72337
  ).limit(10).all();
72101
72338
  const runSelection = {
72102
72339
  reason: runId ? "explicit-run" : scope.scoredOnly ? "latest-scored-scan" : requestedDate ? selected.snapshot.complete ? "requested-date-complete" : "requested-date-most-pages" : selected.snapshot.complete ? "latest-date-complete" : "latest-date-most-pages",
@@ -72110,20 +72347,20 @@ async function technicalAeoRoutes(app, opts) {
72110
72347
  };
72111
72348
  const resolveAudit = (projectId, runId, recordedBefore) => {
72112
72349
  const filters = [
72113
- eq99(siteAuditSnapshots.projectId, projectId),
72114
- eq99(runs.projectId, projectId),
72115
- eq99(runs.kind, RunKinds["site-audit"]),
72116
- inArray43(runs.status, SURFACEABLE_STATUSES),
72350
+ eq100(siteAuditSnapshots.projectId, projectId),
72351
+ eq100(runs.projectId, projectId),
72352
+ eq100(runs.kind, RunKinds["site-audit"]),
72353
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72117
72354
  notProbeRun(),
72118
72355
  ...recordedBefore ? [lt14(siteAuditSnapshots.createdAt, recordedBefore)] : []
72119
72356
  ];
72120
- const complete = sql35`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
72121
- const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(and82(...filters, pinnedRunId ? eq99(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
72122
- desc47(sql35`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
72123
- desc47(complete),
72124
- desc47(siteAuditSnapshots.pagesAudited),
72125
- desc47(siteAuditSnapshots.createdAt),
72126
- desc47(siteAuditSnapshots.runId)
72357
+ const complete = sql36`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
72358
+ const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(...filters, pinnedRunId ? eq100(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
72359
+ desc48(sql36`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
72360
+ desc48(complete),
72361
+ desc48(siteAuditSnapshots.pagesAudited),
72362
+ desc48(siteAuditSnapshots.createdAt),
72363
+ desc48(siteAuditSnapshots.runId)
72127
72364
  ).limit(1).get();
72128
72365
  const auditOfCrawl = (target) => {
72129
72366
  const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
@@ -72139,22 +72376,22 @@ async function technicalAeoRoutes(app, opts) {
72139
72376
  const date = selected.snap.createdAt.slice(0, 10);
72140
72377
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
72141
72378
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
72142
- const sameDate = and82(
72379
+ const sameDate = and83(
72143
72380
  ...filters,
72144
72381
  gte27(siteAuditSnapshots.createdAt, date),
72145
72382
  lt14(siteAuditSnapshots.createdAt, nextDate.toISOString())
72146
72383
  );
72147
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72384
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72148
72385
  const candidates = app.db.select({
72149
72386
  runId: siteAuditSnapshots.runId,
72150
72387
  createdAt: siteAuditSnapshots.createdAt,
72151
72388
  complete,
72152
72389
  pages: siteAuditSnapshots.pagesAudited
72153
- }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
72154
- desc47(complete),
72155
- desc47(siteAuditSnapshots.pagesAudited),
72156
- desc47(siteAuditSnapshots.createdAt),
72157
- desc47(siteAuditSnapshots.runId)
72390
+ }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
72391
+ desc48(complete),
72392
+ desc48(siteAuditSnapshots.pagesAudited),
72393
+ desc48(siteAuditSnapshots.createdAt),
72394
+ desc48(siteAuditSnapshots.runId)
72158
72395
  ).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
72159
72396
  const runSelection = {
72160
72397
  // A crawl exists here only when none stored a scorecard: this score is a fallback.
@@ -72169,9 +72406,9 @@ async function technicalAeoRoutes(app, opts) {
72169
72406
  };
72170
72407
  const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
72171
72408
  const crawlReasonGroups = (where) => {
72172
- const canonicalOther = sql35`case when ${siteCrawlPages.canonicalNodeKey} is not null
72409
+ const canonicalOther = sql36`case when ${siteCrawlPages.canonicalNodeKey} is not null
72173
72410
  and ${siteCrawlPages.canonicalNodeKey} <> ${siteCrawlPages.nodeKey} then 1 else 0 end`;
72174
- const canonicalReason = sql35`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
72411
+ const canonicalReason = sql36`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
72175
72412
  where value = ${SiteCrawlIndexabilityReasons.canonicalToOther})`;
72176
72413
  const groups = app.db.select({
72177
72414
  fetchState: siteCrawlPages.fetchState,
@@ -72180,7 +72417,7 @@ async function technicalAeoRoutes(app, opts) {
72180
72417
  canonicalOther,
72181
72418
  canonicalReason,
72182
72419
  pages: count8(),
72183
- exampleUrl: sql35`min(${siteCrawlPages.url})`
72420
+ exampleUrl: sql36`min(${siteCrawlPages.url})`
72184
72421
  }).from(siteCrawlPages).where(where).groupBy(
72185
72422
  siteCrawlPages.fetchState,
72186
72423
  siteCrawlPages.indexabilityState,
@@ -72216,10 +72453,10 @@ async function technicalAeoRoutes(app, opts) {
72216
72453
  };
72217
72454
  const inventorySummaryFor = (scope) => {
72218
72455
  if (!scope) return null;
72219
- const groups = crawlReasonGroups(and82(
72220
- eq99(siteCrawlPages.projectId, scope.projectId),
72221
- eq99(siteCrawlPages.runId, scope.runId),
72222
- eq99(siteCrawlPages.attemptId, scope.attemptId)
72456
+ const groups = crawlReasonGroups(and83(
72457
+ eq100(siteCrawlPages.projectId, scope.projectId),
72458
+ eq100(siteCrawlPages.runId, scope.runId),
72459
+ eq100(siteCrawlPages.attemptId, scope.attemptId)
72223
72460
  ));
72224
72461
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
72225
72462
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
@@ -72239,34 +72476,34 @@ async function technicalAeoRoutes(app, opts) {
72239
72476
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
72240
72477
  const pageInScope = (scope, selector) => {
72241
72478
  if (!selector.nodeKey && !selector.url) return null;
72242
- return app.db.select().from(siteCrawlPages).where(and82(
72243
- eq99(siteCrawlPages.projectId, scope.projectId),
72244
- eq99(siteCrawlPages.runId, scope.runId),
72245
- eq99(siteCrawlPages.attemptId, scope.attemptId),
72246
- selector.nodeKey ? eq99(siteCrawlPages.nodeKey, selector.nodeKey) : eq99(siteCrawlPages.url, selector.url)
72247
- )).orderBy(asc21(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
72248
- };
72249
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and82(
72250
- eq99(siteCrawlPages.projectId, scope.projectId),
72251
- eq99(siteCrawlPages.runId, scope.runId),
72252
- eq99(siteCrawlPages.attemptId, scope.attemptId),
72253
- eq99(siteCrawlPages.depth, 0)
72254
- )).orderBy(asc21(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
72255
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and82(
72256
- eq99(runs.id, runId),
72257
- eq99(runs.projectId, projectId),
72258
- eq99(runs.kind, RunKinds["site-audit"]),
72259
- inArray43(runs.status, SURFACEABLE_STATUSES),
72479
+ return app.db.select().from(siteCrawlPages).where(and83(
72480
+ eq100(siteCrawlPages.projectId, scope.projectId),
72481
+ eq100(siteCrawlPages.runId, scope.runId),
72482
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
72483
+ selector.nodeKey ? eq100(siteCrawlPages.nodeKey, selector.nodeKey) : eq100(siteCrawlPages.url, selector.url)
72484
+ )).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
72485
+ };
72486
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and83(
72487
+ eq100(siteCrawlPages.projectId, scope.projectId),
72488
+ eq100(siteCrawlPages.runId, scope.runId),
72489
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
72490
+ eq100(siteCrawlPages.depth, 0)
72491
+ )).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
72492
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and83(
72493
+ eq100(runs.id, runId),
72494
+ eq100(runs.projectId, projectId),
72495
+ eq100(runs.kind, RunKinds["site-audit"]),
72496
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72260
72497
  notProbeRun()
72261
72498
  )).limit(1).get());
72262
72499
  const assertKnownAuditRun = (projectId, runId) => {
72263
72500
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
72264
72501
  };
72265
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(and82(
72266
- eq99(siteAuditSnapshots.projectId, projectId),
72267
- eq99(runs.projectId, projectId),
72268
- eq99(runs.kind, RunKinds["site-audit"]),
72269
- inArray43(runs.status, SURFACEABLE_STATUSES),
72502
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
72503
+ eq100(siteAuditSnapshots.projectId, projectId),
72504
+ eq100(runs.projectId, projectId),
72505
+ eq100(runs.kind, RunKinds["site-audit"]),
72506
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72270
72507
  notProbeRun()
72271
72508
  )).limit(1).get());
72272
72509
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -72367,14 +72604,14 @@ async function technicalAeoRoutes(app, opts) {
72367
72604
  };
72368
72605
  }
72369
72606
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
72370
- const conds = [eq99(siteAuditPages.projectId, project.id), eq99(siteAuditPages.runId, latest.snap.runId)];
72371
- if (statusFilter) conds.push(eq99(siteAuditPages.status, statusFilter));
72372
- const where = and82(...conds);
72607
+ const conds = [eq100(siteAuditPages.projectId, project.id), eq100(siteAuditPages.runId, latest.snap.runId)];
72608
+ if (statusFilter) conds.push(eq100(siteAuditPages.status, statusFilter));
72609
+ const where = and83(...conds);
72373
72610
  const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
72374
72611
  const total = totalRow?.value ?? 0;
72375
72612
  const limit = parsePositiveInt(request.query.limit, 100, 500);
72376
72613
  const offset = parsePositiveInt(request.query.offset, 0, Number.MAX_SAFE_INTEGER);
72377
- const orderBy = request.query.sort === "score-desc" ? desc47(siteAuditPages.overallScore) : request.query.sort === "url" ? asc21(siteAuditPages.url) : asc21(siteAuditPages.overallScore);
72614
+ const orderBy = request.query.sort === "score-desc" ? desc48(siteAuditPages.overallScore) : request.query.sort === "url" ? asc22(siteAuditPages.url) : asc22(siteAuditPages.overallScore);
72378
72615
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
72379
72616
  const pages = rows.map((row) => ({
72380
72617
  url: row.url,
@@ -72393,13 +72630,13 @@ async function technicalAeoRoutes(app, opts) {
72393
72630
  auditedAt: siteAuditSnapshots.auditedAt,
72394
72631
  aggregateScore: siteAuditSnapshots.aggregateScore,
72395
72632
  pagesAudited: siteAuditSnapshots.pagesAudited
72396
- }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(and82(
72397
- eq99(siteAuditSnapshots.projectId, project.id),
72398
- eq99(runs.projectId, project.id),
72399
- eq99(runs.kind, RunKinds["site-audit"]),
72400
- inArray43(runs.status, SURFACEABLE_STATUSES),
72633
+ }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
72634
+ eq100(siteAuditSnapshots.projectId, project.id),
72635
+ eq100(runs.projectId, project.id),
72636
+ eq100(runs.kind, RunKinds["site-audit"]),
72637
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72401
72638
  notProbeRun()
72402
- )).orderBy(desc47(siteAuditSnapshots.createdAt)).limit(limit).all();
72639
+ )).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
72403
72640
  return { project: project.name, points: rows.reverse() };
72404
72641
  });
72405
72642
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -72499,10 +72736,10 @@ async function technicalAeoRoutes(app, opts) {
72499
72736
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
72500
72737
  snapshot.rootUrl
72501
72738
  )?.nodeKey ?? null;
72502
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and82(
72503
- eq99(siteCrawlGraphLayouts.projectId, project.id),
72504
- eq99(siteCrawlGraphLayouts.runId, snapshot.runId),
72505
- eq99(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
72739
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and83(
72740
+ eq100(siteCrawlGraphLayouts.projectId, project.id),
72741
+ eq100(siteCrawlGraphLayouts.runId, snapshot.runId),
72742
+ eq100(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
72506
72743
  )).limit(1).get();
72507
72744
  if (!persistedLayout) {
72508
72745
  return {
@@ -72555,9 +72792,9 @@ async function technicalAeoRoutes(app, opts) {
72555
72792
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
72556
72793
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
72557
72794
  const graphScope = [
72558
- eq99(siteCrawlGraphNodes.projectId, project.id),
72559
- eq99(siteCrawlGraphNodes.runId, snapshot.runId),
72560
- eq99(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
72795
+ eq100(siteCrawlGraphNodes.projectId, project.id),
72796
+ eq100(siteCrawlGraphNodes.runId, snapshot.runId),
72797
+ eq100(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
72561
72798
  ];
72562
72799
  const nodeRows = app.db.select({
72563
72800
  nodeKey: siteCrawlPages.nodeKey,
@@ -72576,18 +72813,18 @@ async function technicalAeoRoutes(app, opts) {
72576
72813
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
72577
72814
  x: siteCrawlGraphNodes.x,
72578
72815
  y: siteCrawlGraphNodes.y
72579
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and82(
72580
- eq99(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
72581
- eq99(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
72582
- eq99(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
72583
- eq99(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
72584
- )).where(and82(...graphScope, lt14(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc21(siteCrawlGraphNodes.sampleRank)).all();
72816
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and83(
72817
+ eq100(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
72818
+ eq100(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
72819
+ eq100(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
72820
+ eq100(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
72821
+ )).where(and83(...graphScope, lt14(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc22(siteCrawlGraphNodes.sampleRank)).all();
72585
72822
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
72586
72823
  ...row,
72587
72824
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
72588
72825
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
72589
72826
  }));
72590
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq99(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
72827
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq100(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
72591
72828
  const edges = app.db.select({
72592
72829
  edgeKey: siteCrawlGraphEdges.edgeKey,
72593
72830
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -72595,25 +72832,25 @@ async function technicalAeoRoutes(app, opts) {
72595
72832
  followable: siteCrawlGraphEdges.followable,
72596
72833
  occurrences: siteCrawlGraphEdges.occurrences,
72597
72834
  isTemplate: siteCrawlGraphEdges.isTemplate
72598
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and82(
72599
- eq99(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
72600
- eq99(graphSourceNode.runId, siteCrawlGraphEdges.runId),
72601
- eq99(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
72602
- eq99(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
72835
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and83(
72836
+ eq100(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
72837
+ eq100(graphSourceNode.runId, siteCrawlGraphEdges.runId),
72838
+ eq100(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
72839
+ eq100(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
72603
72840
  lt14(graphSourceNode.sampleRank, maxNodes)
72604
- )).innerJoin(graphTargetNode, and82(
72605
- eq99(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
72606
- eq99(graphTargetNode.runId, siteCrawlGraphEdges.runId),
72607
- eq99(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
72608
- eq99(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
72841
+ )).innerJoin(graphTargetNode, and83(
72842
+ eq100(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
72843
+ eq100(graphTargetNode.runId, siteCrawlGraphEdges.runId),
72844
+ eq100(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
72845
+ eq100(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
72609
72846
  lt14(graphTargetNode.sampleRank, maxNodes)
72610
- )).where(and82(
72611
- eq99(siteCrawlGraphEdges.projectId, project.id),
72612
- eq99(siteCrawlGraphEdges.runId, snapshot.runId),
72613
- eq99(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
72847
+ )).where(and83(
72848
+ eq100(siteCrawlGraphEdges.projectId, project.id),
72849
+ eq100(siteCrawlGraphEdges.runId, snapshot.runId),
72850
+ eq100(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
72614
72851
  lt14(siteCrawlGraphEdges.sampleRank, maxEdges),
72615
72852
  graphLinkKindFilter
72616
- )).orderBy(asc21(siteCrawlGraphEdges.sampleRank)).all();
72853
+ )).orderBy(asc22(siteCrawlGraphEdges.sampleRank)).all();
72617
72854
  const totalNodes = persistedLayout.totalNodes;
72618
72855
  const totalEdges = persistedLayout.totalEdges;
72619
72856
  const totalTemplateEdges = persistedLayout.totalTemplateEdges;
@@ -72705,17 +72942,17 @@ async function technicalAeoRoutes(app, opts) {
72705
72942
  let frontier = [focus.nodeKey];
72706
72943
  let queryTruncated = false;
72707
72944
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
72708
- const candidates = app.db.select().from(siteCrawlEdges).where(and82(
72709
- eq99(siteCrawlEdges.projectId, scope.projectId),
72710
- eq99(siteCrawlEdges.runId, scope.runId),
72711
- eq99(siteCrawlEdges.attemptId, scope.attemptId),
72712
- eq99(siteCrawlEdges.internal, true),
72945
+ const candidates = app.db.select().from(siteCrawlEdges).where(and83(
72946
+ eq100(siteCrawlEdges.projectId, scope.projectId),
72947
+ eq100(siteCrawlEdges.runId, scope.runId),
72948
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
72949
+ eq100(siteCrawlEdges.internal, true),
72713
72950
  isNotNull10(siteCrawlEdges.targetNodeKey),
72714
72951
  or21(
72715
- inArray43(siteCrawlEdges.sourceNodeKey, frontier),
72716
- inArray43(siteCrawlEdges.targetNodeKey, frontier)
72952
+ inArray44(siteCrawlEdges.sourceNodeKey, frontier),
72953
+ inArray44(siteCrawlEdges.targetNodeKey, frontier)
72717
72954
  )
72718
- )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
72955
+ )).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
72719
72956
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
72720
72957
  const next = /* @__PURE__ */ new Set();
72721
72958
  for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
@@ -72746,22 +72983,22 @@ async function technicalAeoRoutes(app, opts) {
72746
72983
  frontier = [...next].sort();
72747
72984
  }
72748
72985
  const nodeKeys = [...distances.keys()];
72749
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and82(
72750
- eq99(siteCrawlPages.projectId, scope.projectId),
72751
- eq99(siteCrawlPages.runId, scope.runId),
72752
- eq99(siteCrawlPages.attemptId, scope.attemptId),
72753
- inArray43(siteCrawlPages.nodeKey, nodeKeys)
72986
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
72987
+ eq100(siteCrawlPages.projectId, scope.projectId),
72988
+ eq100(siteCrawlPages.runId, scope.runId),
72989
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
72990
+ inArray44(siteCrawlPages.nodeKey, nodeKeys)
72754
72991
  )).all();
72755
72992
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
72756
72993
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
72757
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and82(
72758
- eq99(siteCrawlEdges.projectId, scope.projectId),
72759
- eq99(siteCrawlEdges.runId, scope.runId),
72760
- eq99(siteCrawlEdges.attemptId, scope.attemptId),
72761
- eq99(siteCrawlEdges.internal, true),
72994
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and83(
72995
+ eq100(siteCrawlEdges.projectId, scope.projectId),
72996
+ eq100(siteCrawlEdges.runId, scope.runId),
72997
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
72998
+ eq100(siteCrawlEdges.internal, true),
72762
72999
  isNotNull10(siteCrawlEdges.targetNodeKey),
72763
- inArray43(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
72764
- inArray43(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
73000
+ inArray44(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
73001
+ inArray44(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
72765
73002
  )).limit(1).get();
72766
73003
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
72767
73004
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -72854,23 +73091,23 @@ async function technicalAeoRoutes(app, opts) {
72854
73091
  let found = fromPage.nodeKey === toPage.nodeKey;
72855
73092
  let truncated = false;
72856
73093
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
72857
- const candidates = app.db.select().from(siteCrawlEdges).where(and82(
72858
- eq99(siteCrawlEdges.projectId, scope.projectId),
72859
- eq99(siteCrawlEdges.runId, scope.runId),
72860
- eq99(siteCrawlEdges.attemptId, scope.attemptId),
72861
- eq99(siteCrawlEdges.internal, true),
72862
- eq99(siteCrawlEdges.followable, true),
72863
- eq99(siteCrawlEdges.relation, "anchor"),
73094
+ const candidates = app.db.select().from(siteCrawlEdges).where(and83(
73095
+ eq100(siteCrawlEdges.projectId, scope.projectId),
73096
+ eq100(siteCrawlEdges.runId, scope.runId),
73097
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
73098
+ eq100(siteCrawlEdges.internal, true),
73099
+ eq100(siteCrawlEdges.followable, true),
73100
+ eq100(siteCrawlEdges.relation, "anchor"),
72864
73101
  isNotNull10(siteCrawlEdges.targetNodeKey),
72865
- inArray43(siteCrawlEdges.sourceNodeKey, frontier)
72866
- )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
73102
+ inArray44(siteCrawlEdges.sourceNodeKey, frontier)
73103
+ )).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
72867
73104
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
72868
73105
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
72869
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and82(
72870
- eq99(siteCrawlPages.projectId, scope.projectId),
72871
- eq99(siteCrawlPages.runId, scope.runId),
72872
- eq99(siteCrawlPages.attemptId, scope.attemptId),
72873
- inArray43(siteCrawlPages.nodeKey, candidateTargetKeys)
73106
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and83(
73107
+ eq100(siteCrawlPages.projectId, scope.projectId),
73108
+ eq100(siteCrawlPages.runId, scope.runId),
73109
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73110
+ inArray44(siteCrawlPages.nodeKey, candidateTargetKeys)
72874
73111
  )).all().map((row) => row.nodeKey));
72875
73112
  const next = /* @__PURE__ */ new Set();
72876
73113
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -72911,11 +73148,11 @@ async function technicalAeoRoutes(app, opts) {
72911
73148
  pathEdges.unshift(step.edge);
72912
73149
  pathKeys.unshift(step.nodeKey);
72913
73150
  }
72914
- const pathRows = app.db.select().from(siteCrawlPages).where(and82(
72915
- eq99(siteCrawlPages.projectId, scope.projectId),
72916
- eq99(siteCrawlPages.runId, scope.runId),
72917
- eq99(siteCrawlPages.attemptId, scope.attemptId),
72918
- inArray43(siteCrawlPages.nodeKey, pathKeys)
73151
+ const pathRows = app.db.select().from(siteCrawlPages).where(and83(
73152
+ eq100(siteCrawlPages.projectId, scope.projectId),
73153
+ eq100(siteCrawlPages.runId, scope.runId),
73154
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73155
+ inArray44(siteCrawlPages.nodeKey, pathKeys)
72919
73156
  )).all();
72920
73157
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
72921
73158
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -72943,25 +73180,25 @@ async function technicalAeoRoutes(app, opts) {
72943
73180
  throw validationError("change must be all, added, removed, or changed");
72944
73181
  }
72945
73182
  const completeSnapshotFilters = [
72946
- eq99(siteCrawlSnapshots.projectId, project.id),
72947
- eq99(runs.projectId, project.id),
72948
- eq99(runs.kind, RunKinds["site-audit"]),
72949
- eq99(runs.status, RunStatuses.completed),
72950
- eq99(siteCrawlSnapshots.complete, true),
73183
+ eq100(siteCrawlSnapshots.projectId, project.id),
73184
+ eq100(runs.projectId, project.id),
73185
+ eq100(runs.kind, RunKinds["site-audit"]),
73186
+ eq100(runs.status, RunStatuses.completed),
73187
+ eq100(siteCrawlSnapshots.complete, true),
72951
73188
  notProbeRun()
72952
73189
  ];
72953
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(...completeSnapshotFilters)).orderBy(desc47(siteCrawlSnapshots.createdAt), desc47(siteCrawlSnapshots.runId)).limit(1).get();
73190
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(...completeSnapshotFilters)).orderBy(desc48(siteCrawlSnapshots.createdAt), desc48(siteCrawlSnapshots.runId)).limit(1).get();
72954
73191
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
72955
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(
73192
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(
72956
73193
  ...completeSnapshotFilters,
72957
73194
  or21(
72958
73195
  lt14(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
72959
- and82(
72960
- eq99(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
73196
+ and83(
73197
+ eq100(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
72961
73198
  lt14(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
72962
73199
  )
72963
73200
  )
72964
- )).orderBy(desc47(siteCrawlSnapshots.createdAt), desc47(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
73201
+ )).orderBy(desc48(siteCrawlSnapshots.createdAt), desc48(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
72965
73202
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
72966
73203
  if (!afterTarget) {
72967
73204
  const latestCrawl = resolveCrawl(project.id);
@@ -73045,9 +73282,9 @@ async function technicalAeoRoutes(app, opts) {
73045
73282
  const summary4 = { pages: zero2(), links: zero2() };
73046
73283
  if (scopeFilter !== "links") {
73047
73284
  if (includeAdded || includeChanged) {
73048
- const addedExpression = includeAdded ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql35`0`;
73049
- const changedExpression = includeChanged ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${PAGE_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql35`0`;
73050
- const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql35`
73285
+ const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
73286
+ const changedExpression = includeChanged ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${PAGE_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql36`0`;
73287
+ const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
73051
73288
  SELECT ${addedExpression} AS added, ${changedExpression} AS changed
73052
73289
  FROM site_crawl_pages AS current
73053
73290
  LEFT JOIN site_crawl_pages AS previous
@@ -73063,7 +73300,7 @@ async function technicalAeoRoutes(app, opts) {
73063
73300
  summary4.pages.changed = Number(counts2.changed);
73064
73301
  }
73065
73302
  if (includeRemoved) {
73066
- const [counts2 = { removed: 0 }] = app.db.all(sql35`
73303
+ const [counts2 = { removed: 0 }] = app.db.all(sql36`
73067
73304
  SELECT COUNT(*) AS removed
73068
73305
  FROM site_crawl_pages AS previous
73069
73306
  LEFT JOIN site_crawl_pages AS current
@@ -73081,9 +73318,9 @@ async function technicalAeoRoutes(app, opts) {
73081
73318
  }
73082
73319
  if (scopeFilter !== "pages") {
73083
73320
  if (includeAdded || includeChanged) {
73084
- const addedExpression = includeAdded ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql35`0`;
73085
- const changedExpression = includeChanged ? sql35`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${LINK_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql35`0`;
73086
- const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql35`
73321
+ const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
73322
+ const changedExpression = includeChanged ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NOT NULL AND ${LINK_CHANGE_PREDICATE} THEN 1 ELSE 0 END), 0)` : sql36`0`;
73323
+ const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
73087
73324
  SELECT ${addedExpression} AS added, ${changedExpression} AS changed
73088
73325
  FROM site_crawl_edges AS current
73089
73326
  LEFT JOIN site_crawl_edges AS previous
@@ -73101,7 +73338,7 @@ async function technicalAeoRoutes(app, opts) {
73101
73338
  summary4.links.changed = Number(counts2.changed);
73102
73339
  }
73103
73340
  if (includeRemoved) {
73104
- const [counts2 = { removed: 0 }] = app.db.all(sql35`
73341
+ const [counts2 = { removed: 0 }] = app.db.all(sql36`
73105
73342
  SELECT COUNT(*) AS removed
73106
73343
  FROM site_crawl_edges AS previous
73107
73344
  LEFT JOIN site_crawl_edges AS current
@@ -73131,48 +73368,48 @@ async function technicalAeoRoutes(app, opts) {
73131
73368
  const includeRemoved = changeFilter === "all" || changeFilter === "removed";
73132
73369
  const table = entity === "page" ? "site_crawl_pages" : "site_crawl_edges";
73133
73370
  const keyColumn = entity === "page" ? "node_key" : "edge_key";
73134
- const internalCurrent = entity === "link" ? sql35`AND current.internal = 1` : sql35``;
73135
- const internalPreviousJoin = entity === "link" ? sql35`AND previous.internal = 1` : sql35``;
73136
- const internalPrevious = entity === "link" ? sql35`AND previous.internal = 1` : sql35``;
73137
- const internalCurrentJoin = entity === "link" ? sql35`AND current.internal = 1` : sql35``;
73371
+ const internalCurrent = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
73372
+ const internalPreviousJoin = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
73373
+ const internalPrevious = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
73374
+ const internalCurrentJoin = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
73138
73375
  const difference2 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
73139
- const afterKind = changeFilter === "added" ? sql35`previous.id IS NULL` : changeFilter === "changed" ? sql35`previous.id IS NOT NULL AND ${difference2}` : sql35`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference2})`;
73140
- const afterRows = includeAfter ? app.db.all(sql35`
73376
+ const afterKind = changeFilter === "added" ? sql36`previous.id IS NULL` : changeFilter === "changed" ? sql36`previous.id IS NOT NULL AND ${difference2}` : sql36`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference2})`;
73377
+ const afterRows = includeAfter ? app.db.all(sql36`
73141
73378
  SELECT ${entity} AS entity,
73142
73379
  CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
73143
- current.${sql35.raw(keyColumn)} AS key
73144
- FROM ${sql35.raw(table)} AS current
73145
- LEFT JOIN ${sql35.raw(table)} AS previous
73380
+ current.${sql36.raw(keyColumn)} AS key
73381
+ FROM ${sql36.raw(table)} AS current
73382
+ LEFT JOIN ${sql36.raw(table)} AS previous
73146
73383
  ON previous.project_id = ${beforeScope.projectId}
73147
73384
  AND previous.run_id = ${beforeScope.runId}
73148
73385
  AND previous.attempt_id = ${beforeScope.attemptId}
73149
73386
  ${internalPreviousJoin}
73150
- AND previous.${sql35.raw(keyColumn)} = current.${sql35.raw(keyColumn)}
73387
+ AND previous.${sql36.raw(keyColumn)} = current.${sql36.raw(keyColumn)}
73151
73388
  WHERE current.project_id = ${afterScope.projectId}
73152
73389
  AND current.run_id = ${afterScope.runId}
73153
73390
  AND current.attempt_id = ${afterScope.attemptId}
73154
73391
  ${internalCurrent}
73155
- AND current.${sql35.raw(keyColumn)} > ${afterKey}
73392
+ AND current.${sql36.raw(keyColumn)} > ${afterKey}
73156
73393
  AND (${afterKind})
73157
- ORDER BY current.${sql35.raw(keyColumn)} ASC
73394
+ ORDER BY current.${sql36.raw(keyColumn)} ASC
73158
73395
  LIMIT ${wanted2}
73159
73396
  `) : [];
73160
- const removedRows = includeRemoved ? app.db.all(sql35`
73161
- SELECT ${entity} AS entity, 'removed' AS change, previous.${sql35.raw(keyColumn)} AS key
73162
- FROM ${sql35.raw(table)} AS previous
73163
- LEFT JOIN ${sql35.raw(table)} AS current
73397
+ const removedRows = includeRemoved ? app.db.all(sql36`
73398
+ SELECT ${entity} AS entity, 'removed' AS change, previous.${sql36.raw(keyColumn)} AS key
73399
+ FROM ${sql36.raw(table)} AS previous
73400
+ LEFT JOIN ${sql36.raw(table)} AS current
73164
73401
  ON current.project_id = ${afterScope.projectId}
73165
73402
  AND current.run_id = ${afterScope.runId}
73166
73403
  AND current.attempt_id = ${afterScope.attemptId}
73167
73404
  ${internalCurrentJoin}
73168
- AND current.${sql35.raw(keyColumn)} = previous.${sql35.raw(keyColumn)}
73405
+ AND current.${sql36.raw(keyColumn)} = previous.${sql36.raw(keyColumn)}
73169
73406
  WHERE previous.project_id = ${beforeScope.projectId}
73170
73407
  AND previous.run_id = ${beforeScope.runId}
73171
73408
  AND previous.attempt_id = ${beforeScope.attemptId}
73172
73409
  ${internalPrevious}
73173
- AND previous.${sql35.raw(keyColumn)} > ${afterKey}
73410
+ AND previous.${sql36.raw(keyColumn)} > ${afterKey}
73174
73411
  AND current.id IS NULL
73175
- ORDER BY previous.${sql35.raw(keyColumn)} ASC
73412
+ ORDER BY previous.${sql36.raw(keyColumn)} ASC
73176
73413
  LIMIT ${wanted2}
73177
73414
  `) : [];
73178
73415
  const merged = [];
@@ -73202,18 +73439,18 @@ async function technicalAeoRoutes(app, opts) {
73202
73439
  const visibleKeys = keyRows.slice(0, limit);
73203
73440
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
73204
73441
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
73205
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and82(
73206
- eq99(siteCrawlPages.projectId, scope.projectId),
73207
- eq99(siteCrawlPages.runId, scope.runId),
73208
- eq99(siteCrawlPages.attemptId, scope.attemptId),
73209
- inArray43(siteCrawlPages.nodeKey, pageKeys)
73442
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
73443
+ eq100(siteCrawlPages.projectId, scope.projectId),
73444
+ eq100(siteCrawlPages.runId, scope.runId),
73445
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73446
+ inArray44(siteCrawlPages.nodeKey, pageKeys)
73210
73447
  )).all();
73211
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and82(
73212
- eq99(siteCrawlEdges.projectId, scope.projectId),
73213
- eq99(siteCrawlEdges.runId, scope.runId),
73214
- eq99(siteCrawlEdges.attemptId, scope.attemptId),
73215
- eq99(siteCrawlEdges.internal, true),
73216
- inArray43(siteCrawlEdges.edgeKey, linkKeys)
73448
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and83(
73449
+ eq100(siteCrawlEdges.projectId, scope.projectId),
73450
+ eq100(siteCrawlEdges.runId, scope.runId),
73451
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
73452
+ eq100(siteCrawlEdges.internal, true),
73453
+ inArray44(siteCrawlEdges.edgeKey, linkKeys)
73217
73454
  )).all();
73218
73455
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
73219
73456
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -73386,16 +73623,16 @@ async function technicalAeoRoutes(app, opts) {
73386
73623
  }
73387
73624
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
73388
73625
  const filters = [
73389
- eq99(siteCrawlPages.projectId, project.id),
73390
- eq99(siteCrawlPages.runId, snapshot.runId),
73391
- eq99(siteCrawlPages.attemptId, snapshot.attemptId)
73626
+ eq100(siteCrawlPages.projectId, project.id),
73627
+ eq100(siteCrawlPages.runId, snapshot.runId),
73628
+ eq100(siteCrawlPages.attemptId, snapshot.attemptId)
73392
73629
  ];
73393
- if (request.query.nodeKey) filters.push(eq99(siteCrawlPages.nodeKey, request.query.nodeKey));
73630
+ if (request.query.nodeKey) filters.push(eq100(siteCrawlPages.nodeKey, request.query.nodeKey));
73394
73631
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
73395
- if (inventoryEligible != null) filters.push(eq99(siteCrawlPages.inventoryEligible, inventoryEligible));
73396
- if (request.query.fetchState) filters.push(eq99(siteCrawlPages.fetchState, request.query.fetchState));
73397
- if (request.query.indexabilityState) filters.push(eq99(siteCrawlPages.indexabilityState, request.query.indexabilityState));
73398
- if (request.query.auditState) filters.push(eq99(siteCrawlPages.auditState, request.query.auditState));
73632
+ if (inventoryEligible != null) filters.push(eq100(siteCrawlPages.inventoryEligible, inventoryEligible));
73633
+ if (request.query.fetchState) filters.push(eq100(siteCrawlPages.fetchState, request.query.fetchState));
73634
+ if (request.query.indexabilityState) filters.push(eq100(siteCrawlPages.indexabilityState, request.query.indexabilityState));
73635
+ if (request.query.auditState) filters.push(eq100(siteCrawlPages.auditState, request.query.auditState));
73399
73636
  const healthState = parseSiteHealthState(request.query.healthState);
73400
73637
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
73401
73638
  const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
@@ -73413,17 +73650,17 @@ async function technicalAeoRoutes(app, opts) {
73413
73650
  sort
73414
73651
  })).digest("base64url");
73415
73652
  const offset = decodeCrawlPageCursor(request.query.cursor, selectionKey);
73416
- const orderBy = sort === "score-desc" ? [desc47(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc21(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)] : sort === "path" ? [asc21(siteCrawlPages.path), asc21(siteCrawlPages.nodeKey)] : [asc21(siteCrawlPages.url), asc21(siteCrawlPages.nodeKey)];
73653
+ const orderBy = sort === "score-desc" ? [desc48(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc22(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)] : sort === "path" ? [asc22(siteCrawlPages.path), asc22(siteCrawlPages.nodeKey)] : [asc22(siteCrawlPages.url), asc22(siteCrawlPages.nodeKey)];
73417
73654
  let healthStateFilter = null;
73418
73655
  if (healthState) {
73419
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and82(
73420
- eq99(siteCrawlPages.projectId, project.id),
73421
- eq99(siteCrawlPages.runId, snapshot.runId),
73422
- eq99(siteCrawlPages.attemptId, snapshot.attemptId),
73656
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and83(
73657
+ eq100(siteCrawlPages.projectId, project.id),
73658
+ eq100(siteCrawlPages.runId, snapshot.runId),
73659
+ eq100(siteCrawlPages.attemptId, snapshot.attemptId),
73423
73660
  isNull17(siteCrawlPages.healthState)
73424
73661
  )).limit(1).get();
73425
73662
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
73426
- if (healthStateFilter === "applied") filters.push(eq99(siteCrawlPages.healthState, healthState));
73663
+ if (healthStateFilter === "applied") filters.push(eq100(siteCrawlPages.healthState, healthState));
73427
73664
  }
73428
73665
  if (healthStateFilter === "unavailable-legacy-scan") {
73429
73666
  return {
@@ -73440,7 +73677,7 @@ async function technicalAeoRoutes(app, opts) {
73440
73677
  pages: []
73441
73678
  };
73442
73679
  }
73443
- const where = and82(...filters);
73680
+ const where = and83(...filters);
73444
73681
  const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
73445
73682
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
73446
73683
  const nextOffset = offset + rows.length;
@@ -73475,10 +73712,10 @@ async function technicalAeoRoutes(app, opts) {
73475
73712
  url: siteCrawlPages.url,
73476
73713
  inventoryEligible: siteCrawlPages.inventoryEligible,
73477
73714
  fetchState: siteCrawlPages.fetchState
73478
- }).from(siteCrawlPages).where(and82(
73479
- eq99(siteCrawlPages.projectId, project.id),
73480
- eq99(siteCrawlPages.runId, snapshot.runId),
73481
- eq99(siteCrawlPages.attemptId, snapshot.attemptId)
73715
+ }).from(siteCrawlPages).where(and83(
73716
+ eq100(siteCrawlPages.projectId, project.id),
73717
+ eq100(siteCrawlPages.runId, snapshot.runId),
73718
+ eq100(siteCrawlPages.attemptId, snapshot.attemptId)
73482
73719
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
73483
73720
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
73484
73721
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -73560,21 +73797,21 @@ async function technicalAeoRoutes(app, opts) {
73560
73797
  };
73561
73798
  }
73562
73799
  const filters = [
73563
- eq99(siteCrawlEdges.projectId, project.id),
73564
- eq99(siteCrawlEdges.runId, snapshot.runId),
73565
- eq99(siteCrawlEdges.attemptId, snapshot.attemptId),
73566
- eq99(siteCrawlEdges.internal, true),
73800
+ eq100(siteCrawlEdges.projectId, project.id),
73801
+ eq100(siteCrawlEdges.runId, snapshot.runId),
73802
+ eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
73803
+ eq100(siteCrawlEdges.internal, true),
73567
73804
  linkKindFilter(linkKind)
73568
73805
  ];
73569
- if (request.query.sourceUrl) filters.push(eq99(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
73570
- if (request.query.targetUrl) filters.push(eq99(siteCrawlEdges.targetUrl, request.query.targetUrl));
73806
+ if (request.query.sourceUrl) filters.push(eq100(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
73807
+ if (request.query.targetUrl) filters.push(eq100(siteCrawlEdges.targetUrl, request.query.targetUrl));
73571
73808
  const followable = parseBoolean2(request.query.followable);
73572
- if (followable != null) filters.push(eq99(siteCrawlEdges.followable, followable));
73573
- const where = and82(...filters);
73809
+ if (followable != null) filters.push(eq100(siteCrawlEdges.followable, followable));
73810
+ const where = and83(...filters);
73574
73811
  const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
73575
73812
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
73576
73813
  const offset = decodeCursor(request.query.cursor);
73577
- const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
73814
+ const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
73578
73815
  const nextOffset = offset + rows.length;
73579
73816
  return {
73580
73817
  project: project.name,
@@ -73631,17 +73868,17 @@ async function technicalAeoRoutes(app, opts) {
73631
73868
  };
73632
73869
  }
73633
73870
  const scope = [
73634
- eq99(siteCrawlEdges.projectId, project.id),
73635
- eq99(siteCrawlEdges.runId, snapshot.runId),
73636
- eq99(siteCrawlEdges.attemptId, snapshot.attemptId),
73637
- eq99(siteCrawlEdges.internal, true),
73871
+ eq100(siteCrawlEdges.projectId, project.id),
73872
+ eq100(siteCrawlEdges.runId, snapshot.runId),
73873
+ eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
73874
+ eq100(siteCrawlEdges.internal, true),
73638
73875
  linkKindFilter(linkKind)
73639
73876
  ];
73640
- const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq99(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq99(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq99(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq99(siteCrawlEdges.targetUrl, request.query.url);
73641
- const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq99(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq99(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq99(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq99(siteCrawlEdges.sourceUrl, request.query.url);
73877
+ const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq100(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq100(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq100(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq100(siteCrawlEdges.targetUrl, request.query.url);
73878
+ const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq100(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq100(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq100(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq100(siteCrawlEdges.sourceUrl, request.query.url);
73642
73879
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
73643
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and82(...scope, inboundMatch)).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
73644
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and82(...scope, outboundMatch)).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
73880
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, inboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
73881
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, outboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
73645
73882
  return {
73646
73883
  project: project.name,
73647
73884
  hasCrawlData: true,
@@ -73675,11 +73912,11 @@ async function technicalAeoRoutes(app, opts) {
73675
73912
  if (!snapshot.attemptId) {
73676
73913
  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: [], runSelection: target.runSelection };
73677
73914
  }
73678
- const where = and82(
73679
- eq99(siteCrawlFindings.projectId, project.id),
73680
- eq99(siteCrawlFindings.runId, snapshot.runId),
73681
- eq99(siteCrawlFindings.attemptId, snapshot.attemptId),
73682
- eq99(siteCrawlFindings.findingType, "dead-link"),
73915
+ const where = and83(
73916
+ eq100(siteCrawlFindings.projectId, project.id),
73917
+ eq100(siteCrawlFindings.runId, snapshot.runId),
73918
+ eq100(siteCrawlFindings.attemptId, snapshot.attemptId),
73919
+ eq100(siteCrawlFindings.findingType, "dead-link"),
73683
73920
  // Belt and braces on the client-facing surface: a row without a status
73684
73921
  // code is not evidence of a broken link, and this response is where such
73685
73922
  // a row would be read as one. Migration 140 removed every stored row that
@@ -73689,12 +73926,12 @@ async function technicalAeoRoutes(app, opts) {
73689
73926
  // a fabricated broken link shown to a client is not worth the saved
73690
73927
  // predicate. Applied to `total` as well as the rows, or the count would
73691
73928
  // promise a page the list cannot fill.
73692
- isNotNull10(sql35`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
73929
+ isNotNull10(sql36`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
73693
73930
  );
73694
73931
  const total = app.db.select({ value: count8() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
73695
73932
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
73696
73933
  const offset = decodeCursor(request.query.cursor);
73697
- const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc21(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
73934
+ const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc22(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
73698
73935
  const nextOffset = offset + rows.length;
73699
73936
  const deadLinks = rows.map((row) => ({
73700
73937
  findingKey: row.findingKey,
@@ -73729,19 +73966,19 @@ async function technicalAeoRoutes(app, opts) {
73729
73966
  createdAt: runs.createdAt,
73730
73967
  startedAt: runs.startedAt,
73731
73968
  finishedAt: runs.finishedAt
73732
- }).from(runs).where(and82(
73733
- eq99(runs.projectId, project.id),
73734
- eq99(runs.kind, RunKinds["site-audit"]),
73969
+ }).from(runs).where(and83(
73970
+ eq100(runs.projectId, project.id),
73971
+ eq100(runs.kind, RunKinds["site-audit"]),
73735
73972
  notProbeRun()
73736
- )).orderBy(desc47(runs.createdAt), desc47(runs.id)).limit(limit).all();
73973
+ )).orderBy(desc48(runs.createdAt), desc48(runs.id)).limit(limit).all();
73737
73974
  const runIds = rows.map((row) => row.runId);
73738
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(and82(
73739
- eq99(siteCrawlSnapshots.projectId, project.id),
73740
- eq99(runs.projectId, project.id),
73741
- eq99(runs.kind, RunKinds["site-audit"]),
73742
- inArray43(runs.status, SURFACEABLE_STATUSES),
73975
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(
73976
+ eq100(siteCrawlSnapshots.projectId, project.id),
73977
+ eq100(runs.projectId, project.id),
73978
+ eq100(runs.kind, RunKinds["site-audit"]),
73979
+ inArray44(runs.status, SURFACEABLE_STATUSES),
73743
73980
  notProbeRun(),
73744
- inArray43(siteCrawlSnapshots.runId, runIds)
73981
+ inArray44(siteCrawlSnapshots.runId, runIds)
73745
73982
  )).all().map((row) => row.runId));
73746
73983
  return {
73747
73984
  project: project.name,
@@ -73755,21 +73992,21 @@ async function technicalAeoRoutes(app, opts) {
73755
73992
  });
73756
73993
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
73757
73994
  const project = resolveProject(app.db, request.params.name);
73758
- const run = app.db.select().from(runs).where(and82(
73759
- eq99(runs.id, request.params.runId),
73760
- eq99(runs.projectId, project.id),
73761
- eq99(runs.kind, RunKinds["site-audit"]),
73995
+ const run = app.db.select().from(runs).where(and83(
73996
+ eq100(runs.id, request.params.runId),
73997
+ eq100(runs.projectId, project.id),
73998
+ eq100(runs.kind, RunKinds["site-audit"]),
73762
73999
  notProbeRun()
73763
74000
  )).get();
73764
74001
  if (!run) throw notFound("Site audit run", request.params.runId);
73765
- const attempt = app.db.select().from(siteCrawlAttempts).where(and82(
73766
- eq99(siteCrawlAttempts.projectId, project.id),
73767
- eq99(siteCrawlAttempts.runId, run.id)
73768
- )).orderBy(desc47(siteCrawlAttempts.attemptNumber), desc47(siteCrawlAttempts.updatedAt)).limit(1).get();
73769
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and82(
73770
- eq99(siteCrawlGraphLayouts.projectId, project.id),
73771
- eq99(siteCrawlGraphLayouts.runId, run.id),
73772
- eq99(siteCrawlGraphLayouts.attemptId, attempt.id)
74002
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and83(
74003
+ eq100(siteCrawlAttempts.projectId, project.id),
74004
+ eq100(siteCrawlAttempts.runId, run.id)
74005
+ )).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
74006
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and83(
74007
+ eq100(siteCrawlGraphLayouts.projectId, project.id),
74008
+ eq100(siteCrawlGraphLayouts.runId, run.id),
74009
+ eq100(siteCrawlGraphLayouts.attemptId, attempt.id)
73773
74010
  )).get() : void 0;
73774
74011
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
73775
74012
  return {
@@ -73800,19 +74037,19 @@ async function technicalAeoRoutes(app, opts) {
73800
74037
  };
73801
74038
  });
73802
74039
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
73803
- const project = tx.select().from(projects).where(eq99(projects.name, request.params.name)).get();
74040
+ const project = tx.select().from(projects).where(eq100(projects.name, request.params.name)).get();
73804
74041
  if (!project) throw notFound("Project", request.params.name);
73805
- const run = tx.select().from(runs).where(and82(
73806
- eq99(runs.id, request.params.runId),
73807
- eq99(runs.projectId, project.id),
73808
- eq99(runs.kind, RunKinds["site-audit"]),
74042
+ const run = tx.select().from(runs).where(and83(
74043
+ eq100(runs.id, request.params.runId),
74044
+ eq100(runs.projectId, project.id),
74045
+ eq100(runs.kind, RunKinds["site-audit"]),
73809
74046
  notProbeRun()
73810
74047
  )).get();
73811
74048
  if (!run) throw notFound("Site audit run", request.params.runId);
73812
- const attempt = tx.select().from(siteCrawlAttempts).where(and82(
73813
- eq99(siteCrawlAttempts.projectId, project.id),
73814
- eq99(siteCrawlAttempts.runId, run.id)
73815
- )).orderBy(desc47(siteCrawlAttempts.attemptNumber), desc47(siteCrawlAttempts.updatedAt)).limit(1).get();
74049
+ const attempt = tx.select().from(siteCrawlAttempts).where(and83(
74050
+ eq100(siteCrawlAttempts.projectId, project.id),
74051
+ eq100(siteCrawlAttempts.runId, run.id)
74052
+ )).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
73816
74053
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
73817
74054
  const base = {
73818
74055
  project: project.name,
@@ -73825,11 +74062,11 @@ async function technicalAeoRoutes(app, opts) {
73825
74062
  if (!attempt) {
73826
74063
  return { ...base, pagesAudited: 0, examples: [] };
73827
74064
  }
73828
- const auditedWhere = and82(
73829
- eq99(siteCrawlPages.projectId, project.id),
73830
- eq99(siteCrawlPages.runId, run.id),
73831
- eq99(siteCrawlPages.attemptId, attempt.id),
73832
- eq99(siteCrawlPages.auditState, "success")
74065
+ const auditedWhere = and83(
74066
+ eq100(siteCrawlPages.projectId, project.id),
74067
+ eq100(siteCrawlPages.runId, run.id),
74068
+ eq100(siteCrawlPages.attemptId, attempt.id),
74069
+ eq100(siteCrawlPages.auditState, "success")
73833
74070
  );
73834
74071
  const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
73835
74072
  if (state !== "collecting") {
@@ -73840,11 +74077,11 @@ async function technicalAeoRoutes(app, opts) {
73840
74077
  url: siteCrawlPages.url,
73841
74078
  auditScore: siteCrawlPages.auditScore,
73842
74079
  auditFields: siteCrawlPages.auditFields
73843
- }).from(siteCrawlPages).where(and82(
74080
+ }).from(siteCrawlPages).where(and83(
73844
74081
  auditedWhere,
73845
74082
  isNotNull10(siteCrawlPages.auditScore),
73846
74083
  lt14(siteCrawlPages.auditScore, 70)
73847
- )).orderBy(asc21(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
74084
+ )).orderBy(asc22(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
73848
74085
  const examples = candidates.flatMap((candidate) => {
73849
74086
  if (candidate.auditScore == null) return [];
73850
74087
  const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
@@ -73876,13 +74113,13 @@ async function technicalAeoRoutes(app, opts) {
73876
74113
  status: runs.status,
73877
74114
  identityKey: siteCrawlRunRequests.identityKey,
73878
74115
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
73879
- }).from(runs).leftJoin(siteCrawlRunRequests, and82(
73880
- eq99(siteCrawlRunRequests.projectId, runs.projectId),
73881
- eq99(siteCrawlRunRequests.runId, runs.id)
73882
- )).where(and82(
73883
- eq99(runs.projectId, project.id),
73884
- eq99(runs.kind, RunKinds["site-audit"]),
73885
- inArray43(runs.status, [RunStatuses.queued, RunStatuses.running])
74116
+ }).from(runs).leftJoin(siteCrawlRunRequests, and83(
74117
+ eq100(siteCrawlRunRequests.projectId, runs.projectId),
74118
+ eq100(siteCrawlRunRequests.runId, runs.id)
74119
+ )).where(and83(
74120
+ eq100(runs.projectId, project.id),
74121
+ eq100(runs.kind, RunKinds["site-audit"]),
74122
+ inArray44(runs.status, [RunStatuses.queued, RunStatuses.running])
73886
74123
  )).get();
73887
74124
  if (existing) {
73888
74125
  if (existing.identityKey === identityKey) {
@@ -73938,7 +74175,7 @@ async function technicalAeoRoutes(app, opts) {
73938
74175
  // ../api-routes/src/research.ts
73939
74176
  import crypto57 from "crypto";
73940
74177
  import { z as z7 } from "zod";
73941
- import { and as and83, count as count9, desc as desc48, eq as eq100, gte as gte28, lt as lt15, or as or22, sql as sql36 } from "drizzle-orm";
74178
+ import { and as and84, count as count9, desc as desc49, eq as eq101, gte as gte28, lt as lt15, or as or22, sql as sql37 } from "drizzle-orm";
73942
74179
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
73943
74180
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
73944
74181
  async function researchRoutes(app, opts) {
@@ -73957,7 +74194,7 @@ async function researchRoutes(app, opts) {
73957
74194
  const input = parsed.data;
73958
74195
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
73959
74196
  const requestHash2 = directResearchRequestHash(input);
73960
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and83(eq100(researchRuns.projectId, project.id), eq100(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
74197
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and84(eq101(researchRuns.projectId, project.id), eq101(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
73961
74198
  if (existingReceipt) {
73962
74199
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
73963
74200
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -73992,7 +74229,7 @@ async function researchRoutes(app, opts) {
73992
74229
  const initiatedBy = researchPrincipal(request);
73993
74230
  const decision = app.db.transaction((tx) => {
73994
74231
  if (input.idempotencyKey) {
73995
- const existing = tx.select().from(researchRuns).where(and83(eq100(researchRuns.projectId, project.id), eq100(researchRuns.idempotencyKey, input.idempotencyKey))).get();
74232
+ const existing = tx.select().from(researchRuns).where(and84(eq101(researchRuns.projectId, project.id), eq101(researchRuns.idempotencyKey, input.idempotencyKey))).get();
73996
74233
  if (existing) {
73997
74234
  assertSameDirectResearchRequest(existing, input, requestHash2);
73998
74235
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -74000,11 +74237,11 @@ async function researchRoutes(app, opts) {
74000
74237
  }
74001
74238
  if (initiatedBy?.limited) {
74002
74239
  const { start, end, date } = utcDayBounds(now);
74003
- const used = tx.select({ value: count9() }).from(researchRuns).where(and83(
74004
- eq100(researchRuns.projectId, project.id),
74240
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
74241
+ eq101(researchRuns.projectId, project.id),
74005
74242
  gte28(researchRuns.createdAt, start),
74006
74243
  lt15(researchRuns.createdAt, end),
74007
- sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74244
+ sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74008
74245
  )).get()?.value ?? 0;
74009
74246
  if (used >= viewerDailyRunLimit) {
74010
74247
  throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
@@ -74032,15 +74269,15 @@ async function researchRoutes(app, opts) {
74032
74269
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
74033
74270
  const requestHash2 = researchBatchRequestHash(input);
74034
74271
  const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
74035
- const firstReceipt = app.db.select().from(researchRuns).where(and83(
74036
- eq100(researchRuns.projectId, project.id),
74037
- eq100(researchRuns.idempotencyKey, receiptKeys[0])
74272
+ const firstReceipt = app.db.select().from(researchRuns).where(and84(
74273
+ eq101(researchRuns.projectId, project.id),
74274
+ eq101(researchRuns.idempotencyKey, receiptKeys[0])
74038
74275
  )).get();
74039
74276
  if (firstReceipt) {
74040
74277
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
74041
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and83(
74042
- eq100(researchRuns.projectId, project.id),
74043
- eq100(researchRuns.idempotencyKey, key)
74278
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and84(
74279
+ eq101(researchRuns.projectId, project.id),
74280
+ eq101(researchRuns.idempotencyKey, key)
74044
74281
  )).get());
74045
74282
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
74046
74283
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -74056,11 +74293,11 @@ async function researchRoutes(app, opts) {
74056
74293
  const decision = app.db.transaction((tx) => {
74057
74294
  if (initiatedBy?.limited) {
74058
74295
  const { start, end, date } = utcDayBounds(now);
74059
- const used = tx.select({ value: count9() }).from(researchRuns).where(and83(
74060
- eq100(researchRuns.projectId, project.id),
74296
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
74297
+ eq101(researchRuns.projectId, project.id),
74061
74298
  gte28(researchRuns.createdAt, start),
74062
74299
  lt15(researchRuns.createdAt, end),
74063
- sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74300
+ sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74064
74301
  )).get()?.value ?? 0;
74065
74302
  if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
74066
74303
  }
@@ -74111,10 +74348,10 @@ async function researchRoutes(app, opts) {
74111
74348
  const requested = Number.parseInt(request.query.limit ?? "", 10);
74112
74349
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
74113
74350
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
74114
- const rows = app.db.select().from(researchRuns).where(and83(
74115
- eq100(researchRuns.projectId, project.id),
74116
- cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt), and83(eq100(researchRuns.createdAt, cursor.createdAt), lt15(researchRuns.id, cursor.id))) : void 0
74117
- )).orderBy(desc48(researchRuns.createdAt), desc48(researchRuns.id)).limit(limit + 1).all();
74351
+ const rows = app.db.select().from(researchRuns).where(and84(
74352
+ eq101(researchRuns.projectId, project.id),
74353
+ cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt), and84(eq101(researchRuns.createdAt, cursor.createdAt), lt15(researchRuns.id, cursor.id))) : void 0
74354
+ )).orderBy(desc49(researchRuns.createdAt), desc49(researchRuns.id)).limit(limit + 1).all();
74118
74355
  const runs2 = rows.slice(0, limit).map(serializeRun);
74119
74356
  const last = runs2.at(-1);
74120
74357
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -74263,9 +74500,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
74263
74500
  return stored;
74264
74501
  }
74265
74502
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
74266
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and83(
74267
- eq100(measurementQueryTemplates.projectId, projectId),
74268
- eq100(measurementQueryTemplates.id, selection.templateId)
74503
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and84(
74504
+ eq101(measurementQueryTemplates.projectId, projectId),
74505
+ eq101(measurementQueryTemplates.id, selection.templateId)
74269
74506
  )).get();
74270
74507
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
74271
74508
  const templateVersion = savedTemplate.updatedAt;
@@ -74274,7 +74511,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
74274
74511
  }
74275
74512
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
74276
74513
  if (bindingLocation) {
74277
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq100(projects.id, projectId)).get();
74514
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq101(projects.id, projectId)).get();
74278
74515
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
74279
74516
  }
74280
74517
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -74287,9 +74524,9 @@ function classifyResearchQueries(project, plan, queries2) {
74287
74524
  return queries2.map((query) => classifier?.classify(query) ?? null);
74288
74525
  }
74289
74526
  function getDetail(app, projectId, id) {
74290
- const row = app.db.select().from(researchRuns).where(and83(eq100(researchRuns.id, id), eq100(researchRuns.projectId, projectId))).get();
74527
+ const row = app.db.select().from(researchRuns).where(and84(eq101(researchRuns.id, id), eq101(researchRuns.projectId, projectId))).get();
74291
74528
  if (!row) throw notFound("Research run", id);
74292
- const queries2 = app.db.select().from(researchRunQueries).where(eq100(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
74529
+ const queries2 = app.db.select().from(researchRunQueries).where(eq101(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
74293
74530
  return { ...serializeRun(row), queries: queries2 };
74294
74531
  }
74295
74532
  function serializeRun(row) {
@@ -74344,7 +74581,7 @@ function utcDayBounds(now) {
74344
74581
 
74345
74582
  // ../api-routes/src/simple-measurement-definitions.ts
74346
74583
  import crypto58 from "crypto";
74347
- import { and as and84, eq as eq101 } from "drizzle-orm";
74584
+ import { and as and85, eq as eq102 } from "drizzle-orm";
74348
74585
  function captureSimpleMeasurementDefinition(db, input) {
74349
74586
  return db.transaction((tx) => {
74350
74587
  const run = tx.select({
@@ -74352,7 +74589,7 @@ function captureSimpleMeasurementDefinition(db, input) {
74352
74589
  trigger: runs.trigger,
74353
74590
  status: runs.status,
74354
74591
  measurementPlanVersionId: runs.measurementPlanVersionId
74355
- }).from(runs).where(and84(eq101(runs.projectId, input.projectId), eq101(runs.id, input.runId))).get();
74592
+ }).from(runs).where(and85(eq102(runs.projectId, input.projectId), eq102(runs.id, input.runId))).get();
74356
74593
  if (!run) throw notFound("Run", input.runId);
74357
74594
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
74358
74595
  return null;
@@ -74373,7 +74610,7 @@ function captureSimpleMeasurementDefinition(db, input) {
74373
74610
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
74374
74611
  }
74375
74612
  }
74376
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and84(eq101(simpleMeasurementDefinitions.projectId, input.projectId), eq101(simpleMeasurementDefinitions.runId, input.runId))).get();
74613
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and85(eq102(simpleMeasurementDefinitions.projectId, input.projectId), eq102(simpleMeasurementDefinitions.runId, input.runId))).get();
74377
74614
  if (existing) {
74378
74615
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
74379
74616
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
@@ -74399,15 +74636,15 @@ function captureSimpleMeasurementDefinition(db, input) {
74399
74636
  if (run.status !== RunStatuses.running) {
74400
74637
  throw validationError("Only a running run can capture a new measurement definition.");
74401
74638
  }
74402
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq101(querySnapshots.runId, input.runId)).limit(1).get()) {
74639
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq102(querySnapshots.runId, input.runId)).limit(1).get()) {
74403
74640
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
74404
74641
  }
74405
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq101(queries.projectId, input.projectId)).all().map((query) => query.id));
74642
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq102(queries.projectId, input.projectId)).all().map((query) => query.id));
74406
74643
  if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
74407
74644
  throw validationError("Every captured query must belong to the run project.");
74408
74645
  }
74409
74646
  if (definition2.competitors !== void 0) {
74410
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq101(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
74647
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq102(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
74411
74648
  const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
74412
74649
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
74413
74650
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");