@canonry/canonry 7.66.0 → 7.68.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 (139) hide show
  1. package/assets/agent-workspace/skills/aero/references/seo-analysis.md +12 -4
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -2
  3. package/assets/assets/{AgentSetupPage-DXFxOJ1W.js → AgentSetupPage-DyssdZ1J.js} +3 -3
  4. package/assets/assets/{AuditHistoryPanel-CS8UfTHc.js → AuditHistoryPanel-Bma4Q6p0.js} +1 -1
  5. package/assets/assets/{BacklinksPage-D5IA2tNY.js → BacklinksPage-C3GoO2Wf.js} +1 -1
  6. package/assets/assets/{HistoryPage-DsQAUkgR.js → HistoryPage-6iYvYCL7.js} +1 -1
  7. package/assets/assets/MeasurementPropertyPage-CjI5eB3b.js +4 -0
  8. package/assets/assets/ProjectPage-GYIgNPxn.js +9 -0
  9. package/assets/assets/{RunsPage-BgjN1N3t.js → RunsPage-Cw0GTxgx.js} +1 -1
  10. package/assets/assets/{SettingsPage-BQuqnEvm.js → SettingsPage-Cvg8BvZn.js} +1 -1
  11. package/assets/assets/SiteHealthSection-vejp2Igj.js +2 -0
  12. package/assets/assets/ToneBadge-c2bgjqqz.js +60 -0
  13. package/assets/assets/{TrafficPage-BBqpvwl5.js → TrafficPage-BUb0xr-b.js} +1 -1
  14. package/assets/assets/TrafficSourceDetailPage-DmrtEYs8.js +1 -0
  15. package/assets/assets/{account-context-wRthfm6K.js → account-context-qhg8esXw.js} +1 -1
  16. package/assets/assets/{card-Bq64-UB5.js → card-BNthgfAY.js} +1 -1
  17. package/assets/assets/{extract-error-message-BRDc8As2.js → extract-error-message-DuYEL09s.js} +1 -1
  18. package/assets/assets/{format-helpers-D5wFtgg2.js → format-helpers-6v-a8B3S.js} +1 -1
  19. package/assets/assets/index-BUigRGam.js +60 -0
  20. package/assets/assets/{index-C6U4u18w.css → index-CqNwsqjm.css} +1 -1
  21. package/assets/assets/{query-client-IopmAOx0.js → query-client-COFiuVJB.js} +1 -1
  22. package/assets/assets/react-query-ZdpN7cFt.js +1 -0
  23. package/assets/assets/{react-sigma_core.esm.min-d72rX0Uo.js → react-sigma_core.esm.min-EbeNATny.js} +1 -1
  24. package/assets/assets/service-DGFjEsYR.js +1 -0
  25. package/assets/index.html +8 -8
  26. package/dist/{ads-IFKZU7SC.js → ads-AHM5LMVT.js} +3 -3
  27. package/dist/{agent-3KMENHL3.js → agent-2FSGY7SD.js} +7 -7
  28. package/dist/{agent-ask-7WVQLLD5.js → agent-ask-GIK7T2HD.js} +3 -3
  29. package/dist/{agent-conversations-5LR67TBD.js → agent-conversations-JEIFZHT3.js} +3 -3
  30. package/dist/{agent-memory-GKUCCKVX.js → agent-memory-JC3JJXON.js} +3 -3
  31. package/dist/{agent-providers-TVCRM3YZ.js → agent-providers-JRL3VZA7.js} +3 -3
  32. package/dist/{agent-transcript-MUTCOOWG.js → agent-transcript-ZQDNW3IE.js} +3 -3
  33. package/dist/{analytics-2IXSG5ML.js → analytics-HMYE6EIX.js} +3 -3
  34. package/dist/{apply-7RIYK4VC.js → apply-F5CHVFC3.js} +3 -3
  35. package/dist/{backfill-VU3FV6DQ.js → backfill-JZDQKZHY.js} +9 -9
  36. package/dist/{backlinks-DLSRYEX2.js → backlinks-L7XEHBAP.js} +3 -3
  37. package/dist/{bing-4CXTZUH4.js → bing-XTMKLVRJ.js} +3 -3
  38. package/dist/{bootstrap-OOHSBSDQ.js → bootstrap-AEHBYC77.js} +10 -10
  39. package/dist/{cdp-Y53SRSRV.js → cdp-SSIHU3PU.js} +7 -7
  40. package/dist/{chunk-6ASEI24F.js → chunk-2D7O5UHJ.js} +1 -1
  41. package/dist/{chunk-LBRG6RKI.js → chunk-33LH4ZGF.js} +2 -1
  42. package/dist/{chunk-VGUZPC3I.js → chunk-4KMUJOYL.js} +4 -4
  43. package/dist/{chunk-2YBEI6LE.js → chunk-5SK27UE7.js} +1 -1
  44. package/dist/{chunk-JYOMHNRP.js → chunk-6W3ENB2X.js} +758 -467
  45. package/dist/{chunk-IHOVPHUW.js → chunk-7AXVACK6.js} +2 -2
  46. package/dist/{chunk-TJZLICMB.js → chunk-7NMCRHQL.js} +3 -3
  47. package/dist/{chunk-YRX5UOM3.js → chunk-7SGZHGZ2.js} +1 -1
  48. package/dist/{chunk-JALKQREN.js → chunk-ARIYTYOM.js} +83 -11
  49. package/dist/{chunk-PFKBQTYD.js → chunk-ARYBII44.js} +1 -1
  50. package/dist/{chunk-743NYXIK.js → chunk-CZUPNWLH.js} +2 -2
  51. package/dist/{chunk-EIMXB6UH.js → chunk-FDRZQH3Y.js} +14 -2
  52. package/dist/{chunk-OSF27DCY.js → chunk-IVBJEVHS.js} +2 -2
  53. package/dist/{chunk-BCRRAH5C.js → chunk-J4CNNYRQ.js} +1 -1
  54. package/dist/{chunk-DH5QRKFW.js → chunk-JPVG5LIE.js} +3 -3
  55. package/dist/{chunk-QRBZBJ4U.js → chunk-K2P2XO6P.js} +1 -1
  56. package/dist/{chunk-ZRSNMEB3.js → chunk-K54WGCZV.js} +409 -340
  57. package/dist/{chunk-L7DQYID4.js → chunk-LABVR6K7.js} +4 -4
  58. package/dist/{chunk-FNI3QSVD.js → chunk-LTMQ4MHF.js} +1 -1
  59. package/dist/{chunk-EYWC24VC.js → chunk-MVRPFLEL.js} +1 -1
  60. package/dist/{chunk-CKTA5374.js → chunk-PAVSKSKA.js} +1 -1
  61. package/dist/{chunk-AVQQRTMD.js → chunk-QXCQSRHL.js} +72 -33
  62. package/dist/{chunk-K2HDTMRI.js → chunk-R42VEDCB.js} +3 -3
  63. package/dist/{chunk-H37CDAZX.js → chunk-RNA6ZCM6.js} +2 -2
  64. package/dist/{chunk-FG7KMVA6.js → chunk-U3EJ5RW7.js} +12 -6
  65. package/dist/{chunk-DE2UI37M.js → chunk-V6ALEP33.js} +2 -2
  66. package/dist/{chunk-WNCOBSPN.js → chunk-VPZMSV3Q.js} +254 -9
  67. package/dist/{chunk-DVYS2RMM.js → chunk-W7PA35VJ.js} +4 -4
  68. package/dist/{chunk-YJYLFIVT.js → chunk-XMXYUP7X.js} +1 -1
  69. package/dist/{chunk-J5YZXRD5.js → chunk-Y42G2YKT.js} +8 -8
  70. package/dist/{chunk-ALQEGGYM.js → chunk-YIIIYNPW.js} +22 -4
  71. package/dist/{chunk-XC44Q5XR.js → chunk-ZD5G5QEV.js} +1 -1
  72. package/dist/{chunk-SGZWOUIZ.js → chunk-ZU6X4SN2.js} +5 -5
  73. package/dist/{chunk-IPF2S5G2.js → chunk-ZUXFHO4T.js} +4 -4
  74. package/dist/{citations-ZTQIYYDB.js → citations-NIFKIG6Y.js} +3 -3
  75. package/dist/{cli-catalog-IZJXAHFQ.js → cli-catalog-ONYERYPV.js} +3 -3
  76. package/dist/{cli-commands-XYM2VMAK.js → cli-commands-LWI2NEWW.js} +7 -7
  77. package/dist/cli.js +13 -13
  78. package/dist/{competitor-SIXAPHOM.js → competitor-NSUSTLNW.js} +3 -3
  79. package/dist/{content-XPS3D3Y6.js → content-W5VYYMAW.js} +3 -3
  80. package/dist/{dashboard-FMJDM5PA.js → dashboard-NNA3P645.js} +6 -6
  81. package/dist/{demo-QQKSHTPO.js → demo-5M4YGVV4.js} +1 -1
  82. package/dist/{demo-server-LQ3QXUAS.js → demo-server-FDR4RKLM.js} +14 -11
  83. package/dist/{discover-K5HFEU2T.js → discover-VCKMXFOR.js} +3 -3
  84. package/dist/{discover-eval-6536Z2YN.js → discover-eval-CPVP7EK5.js} +3 -3
  85. package/dist/{doctor-STNH3PHH.js → doctor-EVCP6PDM.js} +3 -3
  86. package/dist/{evidence-W2EWKBAV.js → evidence-ZWRGNZWN.js} +3 -3
  87. package/dist/{export-cmd-EGHMY3M6.js → export-cmd-AM6SIRUW.js} +3 -3
  88. package/dist/{ga-DVIG3PVW.js → ga-6J67NDMZ.js} +3 -3
  89. package/dist/{gbp-SNS4XZBO.js → gbp-ERTWK26L.js} +3 -3
  90. package/dist/{google-KGPHZG6E.js → google-O6VQFK4L.js} +4 -4
  91. package/dist/{google-marketing-VZR6W3L6.js → google-marketing-OEW2YGUR.js} +1 -1
  92. package/dist/{health-cmd-2KWO2GOM.js → health-cmd-OPGK37MK.js} +3 -3
  93. package/dist/{history-ZY27B7JE.js → history-WTU4ETCU.js} +3 -3
  94. package/dist/index.d.ts +114 -8
  95. package/dist/index.js +24 -24
  96. package/dist/{init-5TXIKII2.js → init-WTAVKFGR.js} +11 -11
  97. package/dist/{insights-MUYEOSOG.js → insights-LPEDLSEV.js} +3 -3
  98. package/dist/intelligence-service-4OO2VQ2I.js +15 -0
  99. package/dist/{journeys-YO54IRVC.js → journeys-TWVQNRLT.js} +3 -3
  100. package/dist/{keys-M4QZGYVJ.js → keys-C6FPREBZ.js} +3 -3
  101. package/dist/{keyword-MU62MGWT.js → keyword-PBSE65DK.js} +3 -3
  102. package/dist/{logs-BAQM334Q.js → logs-62RUOAKT.js} +3 -3
  103. package/dist/{mcp-LYS5P3PB.js → mcp-P4LNENKX.js} +2 -2
  104. package/dist/mcp.js +9 -9
  105. package/dist/{measurement-plan-HVSTZWTW.js → measurement-plan-HKW3IVIE.js} +5 -5
  106. package/dist/{notify-XSP5JXU3.js → notify-A3I3G7NK.js} +3 -3
  107. package/dist/{organic-evidence-4HYW4PVO.js → organic-evidence-OFUJBDCK.js} +3 -3
  108. package/dist/{overview-XH72WTZT.js → overview-NSRODN2X.js} +4 -4
  109. package/dist/{project-UKH7FTQX.js → project-B5G2HEAB.js} +3 -3
  110. package/dist/{query-RRD5M4QV.js → query-UQHJVYUJ.js} +3 -3
  111. package/dist/{research-V2IGDUKL.js → research-ZWBJDLJY.js} +3 -3
  112. package/dist/{results-export-5FC7ZT6O.js → results-export-FOJYT4MK.js} +3 -3
  113. package/dist/{run-B2A4LMWU.js → run-ZIUQ2TNN.js} +4 -4
  114. package/dist/{schedule-PLY6CHQT.js → schedule-B5CORKGT.js} +3 -3
  115. package/dist/{search-ACVK3WH5.js → search-2XCZKP6A.js} +3 -3
  116. package/dist/{serve-2LTQZPEK.js → serve-3BSLRICO.js} +25 -25
  117. package/dist/{session-IHXMPETX.js → session-PILSYOTX.js} +12 -12
  118. package/dist/{settings-ZHBNX2I5.js → settings-Y3LVGZH4.js} +3 -3
  119. package/dist/{skills-KWRTCXTF.js → skills-6E3RVQAQ.js} +2 -2
  120. package/dist/{snapshot-GBDCCV6L.js → snapshot-RAV2HCL7.js} +3 -3
  121. package/dist/{src-NOTQWGYV.js → src-VUD6WO5N.js} +6 -2
  122. package/dist/{status-FGDDGBMJ.js → status-2WCUV6C2.js} +4 -4
  123. package/dist/{technical-aeo-AK4QI63Q.js → technical-aeo-2TJEHHBR.js} +3 -3
  124. package/dist/{telemetry-6QPWYMCR.js → telemetry-LQAD26UJ.js} +4 -4
  125. package/dist/{tools-PBNTVVN7.js → tools-U6VGYZV2.js} +11 -11
  126. package/dist/{traffic-AS4PIOQL.js → traffic-3TBXSHPV.js} +5 -5
  127. package/dist/{users-BNYKF35J.js → users-2HDDA5GT.js} +3 -3
  128. package/dist/{visibility-stats-TWL3GJMD.js → visibility-stats-HSGT3OL3.js} +3 -3
  129. package/dist/{wordpress-XBWXAACD.js → wordpress-ALODIEFK.js} +3 -3
  130. package/package.json +1 -1
  131. package/assets/assets/MeasurementPropertyPage-CdCnjCaS.js +0 -4
  132. package/assets/assets/ProjectPage-D3oFKnBG.js +0 -9
  133. package/assets/assets/SiteHealthSection-C6QE9xNk.js +0 -2
  134. package/assets/assets/ToneBadge-DNva5_AM.js +0 -60
  135. package/assets/assets/TrafficSourceDetailPage-uxd0ojry.js +0 -1
  136. package/assets/assets/index-Ttz5_bL3.js +0 -60
  137. package/assets/assets/react-query-CISHjSch.js +0 -1
  138. package/assets/assets/service-DsDxSQuN.js +0 -1
  139. 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-J4CNNYRQ.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-7SGZHGZ2.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-ARIYTYOM.js";
158
160
  import {
159
161
  ADS_ACTIVATE_SCOPE,
160
162
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -366,6 +368,8 @@ import {
366
368
  SeoAdapterOutcomes,
367
369
  SeoAvailability,
368
370
  SeoCapabilities,
371
+ SeoMarketRankingSearchStatuses,
372
+ SeoMarketRankingSorts,
369
373
  SeoRunStatuses,
370
374
  SeoScopeKinds,
371
375
  SeoTargetKinds,
@@ -524,6 +528,7 @@ import {
524
528
  clusterByCosine,
525
529
  coerceSkillManifest,
526
530
  compactDateToIso,
531
+ compareMarketRankingsBy,
527
532
  compareSemver,
528
533
  compareText,
529
534
  competitorAliasBlockRequestSchema,
@@ -567,6 +572,7 @@ import {
567
572
  conversionTrackingOptionsDtoSchema,
568
573
  cosineSimilarity,
569
574
  countAdsActivationManifestEntities,
575
+ countMarketRankings,
570
576
  countedLeadEventNames,
571
577
  countsAsMention,
572
578
  countsKeyEvents,
@@ -605,7 +611,6 @@ import {
605
611
  effectiveDomains,
606
612
  emptyCitationVisibility,
607
613
  emptySentimentCounts,
608
- emptySeoResults,
609
614
  escapeLikePattern,
610
615
  escapeRegExp,
611
616
  evaluateModelPointerExposure,
@@ -697,6 +702,7 @@ import {
697
702
  googleOAuthStartResponseSchema,
698
703
  gradedReportMonths,
699
704
  groupIsoDateRanges,
705
+ groupMarketRankings,
700
706
  gscCoverageSnapshotDtoSchema,
701
707
  gscCoverageSummaryDtoSchema,
702
708
  gscDeindexedRowSchema,
@@ -771,9 +777,12 @@ import {
771
777
  makesInstallMeasurable,
772
778
  mapWithConcurrency,
773
779
  marketPinAliasClaims,
780
+ marketRankingRow,
774
781
  matchOperationTemplate,
775
782
  matchedAliasKeys,
776
783
  matcherMatchesText,
784
+ matchesMarketRankingFilter,
785
+ matchesMarketRankingText,
777
786
  matchesMeasurementTargetUrl,
778
787
  measurementChangesQuerySchema,
779
788
  measurementChangesResponseSchema,
@@ -1020,6 +1029,7 @@ import {
1020
1029
  runtimeStateMissing,
1021
1030
  schedulableRunKindSchema,
1022
1031
  scheduleDtoSchema,
1032
+ scheduleEnabledAt,
1023
1033
  scheduleExpectedUpdatedAtSchema,
1024
1034
  scheduleUpsertRequestSchema,
1025
1035
  scheduleVersionConflict,
@@ -1045,8 +1055,12 @@ import {
1045
1055
  seoDomainIntelligenceListSchema,
1046
1056
  seoEvidenceQuerySchema,
1047
1057
  seoKeywordMetricsListSchema,
1058
+ seoMarketRankingFilterSchema,
1048
1059
  seoMarketRankingListSchema,
1049
1060
  seoMarketRankingQuerySchema,
1061
+ seoMarketRankingSortSchema,
1062
+ seoMarketRankingSummaryQuerySchema,
1063
+ seoMarketRankingSummarySchema,
1050
1064
  seoOperatorTaskSchema,
1051
1065
  seoOverviewSchema,
1052
1066
  seoProfileInputSchema,
@@ -1173,7 +1187,7 @@ import {
1173
1187
  wordpressSchemaDeployResultDtoSchema,
1174
1188
  wordpressSchemaStatusResultDtoSchema,
1175
1189
  wordpressStatusDtoSchema
1176
- } from "./chunk-WNCOBSPN.js";
1190
+ } from "./chunk-VPZMSV3Q.js";
1177
1191
 
1178
1192
  // ../api-routes/src/user-session.ts
1179
1193
  import crypto2 from "crypto";
@@ -7262,9 +7276,9 @@ var SentimentService = class {
7262
7276
  const estimatedInputTokens = this.options.estimate ? inputs.reduce((sum, input) => sum + this.options.estimate(input), 0) : 0;
7263
7277
  const expiresAt = new Date(Date.parse(this.now()) + 15 * 6e4).toISOString();
7264
7278
  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 })));
7279
+ const skipped6 = source.runIds.flatMap((runId) => Object.entries(source.skippedByRun?.[runId] ?? {}).map(([reason, count10]) => ({ runId, reason, count: count10 })));
7266
7280
  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 };
7281
+ 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
7282
  }
7269
7283
  /** The receipt carries the first page of attempts; a replay of a long-running job never returns all of them. */
7270
7284
  submit(projectId, previewToken, idempotencyKey, actor2) {
@@ -8419,6 +8433,7 @@ var HEALTH_CHECK_LABELS = {
8419
8433
  "ga.auth.connection": "GA4 sign-in",
8420
8434
  "ga.data.recent-data": "GA4 data stopped",
8421
8435
  "gsc.data.recent-data": "Search Console data stopped",
8436
+ "seo.data.recent-refresh": "SEO refresh stalled",
8422
8437
  "report.sweeps": "Monthly report sweeps",
8423
8438
  "report.models": "Monthly report models",
8424
8439
  "report.daily-data": "Monthly report data gaps",
@@ -8471,7 +8486,11 @@ var HEALTH_FIXED_LINES = {
8471
8486
  "traffic.source.all-errored-recently": "Traffic syncs are working again.",
8472
8487
  "traffic.queue-backlog.remaining": "The traffic backlog is cleared.",
8473
8488
  "traffic.worker-version.stale": "The Cloudflare worker is up to date.",
8474
- "gbp.auth.no-connection": "Business Profile is connected."
8489
+ "gbp.auth.no-connection": "Business Profile is connected.",
8490
+ "seo.data.refresh-failed": "SEO refreshes complete again.",
8491
+ "seo.data.refresh-partial": "SEO refreshes complete again.",
8492
+ "seo.data.overdue": "SEO data is current again.",
8493
+ "seo.data.schedule-invalid": "The SEO refresh schedule can run again."
8475
8494
  };
8476
8495
  function firstSentence(text, max2 = 180) {
8477
8496
  if (!text) return null;
@@ -15220,7 +15239,37 @@ function runDto(job, counts2) {
15220
15239
  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
15240
  }
15222
15241
  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());
15242
+ const kept = tasks.filter((task) => task.reconciliation?.outcome !== SeoAdapterOutcomes.failed);
15243
+ return {
15244
+ keywordMetrics: kept.flatMap((task) => task.results.keywordMetrics),
15245
+ domainIntelligence: kept.flatMap((task) => task.results.domainIntelligence),
15246
+ rankedKeywords: kept.flatMap((task) => task.results.rankedKeywords),
15247
+ marketRankings: tasks.flatMap(marketRankingRows)
15248
+ };
15249
+ }
15250
+ function marketRankingRows(task) {
15251
+ const stored = task.reconciliation?.outcome === SeoAdapterOutcomes.failed ? [] : task.results.marketRankings ?? [];
15252
+ if (stored.length > 0) return stored.map((row) => ({ ...row, searchStatus: row.searchStatus ?? SeoMarketRankingSearchStatuses.completed }));
15253
+ const { search, target } = task.request;
15254
+ if (task.request.capability !== SeoCapabilities["market-rankings"] || !search || !target) return [];
15255
+ const searchStatus = task.status === SeoRunStatuses.cancelled ? SeoMarketRankingSearchStatuses.cancelled : task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"] ? SeoMarketRankingSearchStatuses.failed : null;
15256
+ if (!searchStatus) return [];
15257
+ return search.markets.map((market) => ({
15258
+ ...market.context,
15259
+ searchText: task.request.keywords[0] ?? "",
15260
+ locationLabel: search.locationLabel,
15261
+ target,
15262
+ searchStatus,
15263
+ rankOrganic: null,
15264
+ rankAbsolute: null,
15265
+ url: null,
15266
+ depth: 0,
15267
+ competitors: [],
15268
+ provenance: null
15269
+ }));
15270
+ }
15271
+ function taskSearch(task) {
15272
+ return task.request.capability === SeoCapabilities["market-rankings"] ? { searchText: task.request.keywords[0] ?? null, locationLabel: task.request.search?.locationLabel ?? null } : { searchText: null, locationLabel: null };
15224
15273
  }
15225
15274
  function compareMarketRankings(a, b) {
15226
15275
  return compareText(a.regionLabel ?? "", b.regionLabel ?? "") || compareText(a.marketLabel, b.marketLabel) || compareText(a.searchText, b.searchText) || compareText(a.queryText, b.queryText);
@@ -15434,14 +15483,20 @@ function budgetBuckets(account, projectId, policy, now) {
15434
15483
  { scope: `project-month:${projectId}`, period: now.slice(0, 7), limit: policy.projectMonthlyLimitMicros }
15435
15484
  ];
15436
15485
  }
15486
+ function skippedRefreshReason(profileId, reason) {
15487
+ return `Scheduled SEO refresh for ${profileId ? `profile "${profileId}"` : "this project"} did not start: ${reason}`;
15488
+ }
15489
+ var SKIPPED_REFRESH_PROFILE = /^Scheduled SEO refresh for profile "([^"]+)" did not start: /;
15490
+ function skippedRefreshProfile(error) {
15491
+ return error ? SKIPPED_REFRESH_PROFILE.exec(error)?.[1] ?? null : null;
15492
+ }
15437
15493
  function startScheduledSeoRefreshes(db, opts, input) {
15438
15494
  const repository = new SeoRepository(db);
15439
15495
  const principalId = `schedule:${input.scheduleId}`;
15440
15496
  const recordSkipped = (profileId, reason, detail) => {
15441
15497
  const runId = crypto17.randomUUID();
15442
15498
  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();
15499
+ 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
15500
  return { profileId, status: "skipped", runId, reason, ...detail ? { detail } : {} };
15446
15501
  };
15447
15502
  const saved = db.select().from(seoProfiles).where(eq32(seoProfiles.projectId, input.projectId)).orderBy(asc6(seoProfiles.id)).all().map(profileDto);
@@ -15493,8 +15548,8 @@ async function seoRoutes(app, opts) {
15493
15548
  if (!row) throw notFound("SEO profile", id);
15494
15549
  return profileDto(row);
15495
15550
  };
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() : [];
15551
+ const tasks = (id) => currentSeoTasks(app.db.select().from(seoTasks).where(eq32(seoTasks.jobId, id)).orderBy(asc6(seoTasks.id)).all());
15552
+ 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
15553
  const runSummary = (row) => runDto(row, taskCounts([row.id]));
15499
15554
  const job = (projectId, id) => {
15500
15555
  const row = app.db.select().from(seoJobs).where(and24(eq32(seoJobs.projectId, projectId), isNotNull3(seoJobs.runId), eq32(seoJobs.id, id))).get();
@@ -15645,7 +15700,7 @@ async function seoRoutes(app, opts) {
15645
15700
  const row = job(resolveProject(app.db, request.params.name).id, request.params.id);
15646
15701
  const rows = tasks(row.id);
15647
15702
  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) };
15703
+ 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
15704
  });
15650
15705
  app.post("/projects/:name/seo/runs/:id/cancel", { config: { paidRead: true, writeScope: SEO_RUN_SCOPE } }, async (request) => {
15651
15706
  requireScope(request, SEO_RUN_SCOPE);
@@ -15680,11 +15735,29 @@ async function seoRoutes(app, opts) {
15680
15735
  if (!parsed.success) throw validationError("Invalid SEO evidence selection.");
15681
15736
  const query = parsed.data;
15682
15737
  const row = selected(resolveProject(app.db, request.params.name).id, query, true);
15683
- const items = (row ? resultRows(tasks(row.id)).marketRankings : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey)).sort(compareMarketRankings);
15738
+ const items = (row ? resultRows(tasks(row.id)).marketRankings.map(marketRankingRow) : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey) && (query.outcome === void 0 || matchesMarketRankingFilter(item2, query.outcome)) && (query.q === void 0 || matchesMarketRankingText(item2, query.q))).sort(compareMarketRankingsBy(query.sort ?? SeoMarketRankingSorts.market));
15684
15739
  const limit = query.limit ?? 50;
15685
15740
  const offset = query.offset ?? 0;
15686
15741
  return { runId: row?.id ?? null, items: items.slice(offset, offset + limit), total: items.length, limit, offset };
15687
15742
  });
15743
+ app.get("/projects/:name/seo/market-rankings/summary", async (request) => {
15744
+ const parsed = seoMarketRankingSummaryQuerySchema.safeParse(request.query ?? {});
15745
+ if (!parsed.success) throw validationError("Invalid SEO evidence selection.");
15746
+ const query = parsed.data;
15747
+ const row = selected(resolveProject(app.db, request.params.name).id, query, true);
15748
+ const rows = (row ? resultRows(tasks(row.id)).marketRankings.map(marketRankingRow) : []).filter((item2) => (query.marketKey === void 0 || item2.marketKey === query.marketKey) && (query.regionKey === void 0 || item2.regionKey === query.regionKey));
15749
+ return {
15750
+ // The depth each search asked for; a partial search's rows say how far they reached.
15751
+ runId: row?.id ?? null,
15752
+ depth: row?.profile.rankings?.depth ?? null,
15753
+ regionKey: query.regionKey ?? null,
15754
+ marketKey: query.marketKey ?? null,
15755
+ regionLabel: query.regionKey === void 0 ? null : rows[0]?.regionLabel ?? null,
15756
+ marketLabel: query.marketKey === void 0 ? null : rows[0]?.marketLabel ?? null,
15757
+ totals: countMarketRankings(rows),
15758
+ groups: groupMarketRankings(rows, { regionKey: query.regionKey, marketKey: query.marketKey })
15759
+ };
15760
+ });
15688
15761
  }
15689
15762
 
15690
15763
  // ../api-routes/src/runs.ts
@@ -27130,7 +27203,7 @@ async function applyRoutes(app, opts) {
27130
27203
  timezone: resolvedSchedule.timezone,
27131
27204
  nextRunAt,
27132
27205
  providers: config.spec.schedule?.providers ?? [],
27133
- ...specEnabled === void 0 ? {} : { enabled: specEnabled },
27206
+ ...specEnabled === void 0 ? {} : { enabled: specEnabled, enabledAt: scheduleEnabledAt(existingSched, specEnabled, now) },
27134
27207
  updatedAt: nextScheduleUpdatedAt(existingSched.updatedAt, Date.parse(now))
27135
27208
  }).where(eq44(schedules.id, existingSched.id)).run();
27136
27209
  } else {
@@ -27144,6 +27217,7 @@ async function applyRoutes(app, opts) {
27144
27217
  timezone: resolvedSchedule.timezone,
27145
27218
  nextRunAt,
27146
27219
  enabled: specEnabled ?? true,
27220
+ enabledAt: scheduleEnabledAt(void 0, specEnabled ?? true, now),
27147
27221
  providers: config.spec.schedule?.providers ?? [],
27148
27222
  createdAt: now,
27149
27223
  updatedAt: now
@@ -33892,6 +33966,7 @@ var SCHEMA_TABLE = {
33892
33966
  SeoDomainIntelligenceList: seoDomainIntelligenceListSchema,
33893
33967
  SeoRankedKeywordList: seoRankedKeywordListSchema,
33894
33968
  SeoMarketRankingList: seoMarketRankingListSchema,
33969
+ SeoMarketRankingSummary: seoMarketRankingSummarySchema,
33895
33970
  SentimentSummary: sentimentSummarySchema,
33896
33971
  SentimentSettings: sentimentSettingsSchema,
33897
33972
  SentimentSettingsUpdate: sentimentSettingsUpdateSchema,
@@ -35124,12 +35199,15 @@ var routeCatalog = [
35124
35199
  path: "/api/v1/projects/{name}/seo/market-rankings",
35125
35200
  summary: "Read stored market rankings",
35126
35201
  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.",
35202
+ 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. 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. outcome says where each row landed (a partial search without the site is partly-checked, never not-ranked) and rivalAhead whether a tracked competitor ranks above the site.",
35128
35203
  parameters: [
35129
35204
  nameParameter,
35130
35205
  ...seoEvidenceParameters,
35131
35206
  { name: "marketKey", in: "query", description: "Only rows for this market (an Advanced market key or a Simple location label).", schema: stringSchema },
35132
- { name: "regionKey", in: "query", description: "Only rows whose market sits under this top-level group.", schema: stringSchema }
35207
+ { name: "regionKey", in: "query", description: "Only rows whose market sits under this top-level group.", schema: stringSchema },
35208
+ { name: "outcome", in: "query", description: "Only rows with this outcome. `partly-checked` is a partial search that did not reach the site, so ranks past its depth are unknown; `needs-work` is ranks 11 and deeper, not ranked or partly checked; `rival-ahead` is rows where a tracked competitor ranks above the site.", schema: { type: "string", enum: [...seoMarketRankingFilterSchema.options] } },
35209
+ { name: "q", in: "query", description: "Only rows whose query, searched text or market label contains this text, ignoring case.", schema: { type: "string", minLength: 1, maxLength: 200 } },
35210
+ { name: "sort", in: "query", description: "`market` (default): region, market, search text. `rank`: best rank first, then partly checked rows, then rows not in the results with the strongest rival first, then searches with no result. `-rank`: rows not in the results first, then partly checked rows, then the worst rank. `query` / `-query`: by query text.", schema: { type: "string", enum: [...seoMarketRankingSortSchema.options], default: "market" } }
35133
35211
  ],
35134
35212
  responses: {
35135
35213
  200: jsonResponse("SEO response.", "SeoMarketRankingList"),
@@ -35139,6 +35217,27 @@ var routeCatalog = [
35139
35217
  409: errorResponse("Selection, intent or execution identity changed.")
35140
35218
  }
35141
35219
  },
35220
+ {
35221
+ method: "get",
35222
+ path: "/api/v1/projects/{name}/seo/market-rankings/summary",
35223
+ summary: "Summarize stored market rankings by outcome",
35224
+ tags: ["seo"],
35225
+ description: "Stored evidence only; never calls a provider. Outcome counts for the selected scope (the whole profile, one region or one market) and for the groups under it: regions for the whole profile, markets within one region, none within one market. Groups come in needs-work order: most ranks 11-20, then most ranks 21 and deeper.",
35226
+ parameters: [
35227
+ nameParameter,
35228
+ { name: "profileId", in: "query", description: "Exact saved profile. Required when multiple profiles exist.", schema: stringSchema },
35229
+ { name: "runId", in: "query", description: "Exact historical refresh. Omit for latest saved evidence for the selected profile.", schema: stringSchema },
35230
+ { name: "marketKey", in: "query", description: "Summarize one market (an Advanced market key or a Simple location label).", schema: stringSchema },
35231
+ { name: "regionKey", in: "query", description: "Summarize one top-level group and the markets under it.", schema: stringSchema }
35232
+ ],
35233
+ responses: {
35234
+ 200: jsonResponse("SEO response.", "SeoMarketRankingSummary"),
35235
+ 400: errorResponse("Invalid scope or input."),
35236
+ 403: errorResponse("Insufficient authority."),
35237
+ 404: errorResponse("Project or selected evidence not found."),
35238
+ 409: errorResponse("Selection, intent or execution identity changed.")
35239
+ }
35240
+ },
35142
35241
  {
35143
35242
  method: "put",
35144
35243
  path: "/api/v1/projects/{name}/seo/profiles/{id}",
@@ -43091,6 +43190,7 @@ async function scheduleRoutes(app, opts) {
43091
43190
  providers: providers ?? [],
43092
43191
  sourceId: sourceId ?? null,
43093
43192
  enabled: enabledBool,
43193
+ enabledAt: scheduleEnabledAt(existing, enabledBool, now),
43094
43194
  updatedAt: now
43095
43195
  };
43096
43196
  if (existing) {
@@ -59835,11 +59935,11 @@ async function wordpressRoutes(app, opts) {
59835
59935
  reportMetaStep(metaWriteOutcome(result.results));
59836
59936
  const applied = result.results.filter((r) => r.status === "applied").length;
59837
59937
  const manual = result.results.filter((r) => r.status === "manual").length;
59838
- const skipped5 = result.results.filter((r) => r.status === "skipped").length;
59938
+ const skipped6 = result.results.filter((r) => r.status === "skipped").length;
59839
59939
  steps.push({
59840
59940
  name: "set-meta",
59841
59941
  status: "completed",
59842
- summary: `${applied} applied, ${manual} manual-assist, ${skipped5} skipped`
59942
+ summary: `${applied} applied, ${manual} manual-assist, ${skipped6} skipped`
59843
59943
  });
59844
59944
  }
59845
59945
  } catch (err) {
@@ -59869,11 +59969,11 @@ async function wordpressRoutes(app, opts) {
59869
59969
  reportSchemaStep(schemaDeployOutcome(result.results));
59870
59970
  const deployed = result.results.filter((r) => r.status === "deployed").length;
59871
59971
  const stripped = result.results.filter((r) => r.status === "stripped").length;
59872
- const skipped5 = result.results.filter((r) => r.status === "skipped").length;
59972
+ const skipped6 = result.results.filter((r) => r.status === "skipped").length;
59873
59973
  steps.push({
59874
59974
  name: "schema-deploy",
59875
59975
  status: "completed",
59876
- summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped5} skipped`
59976
+ summary: `${deployed} deployed, ${stripped} stripped (manual-assist), ${skipped6} skipped`
59877
59977
  });
59878
59978
  }
59879
59979
  } catch (err) {
@@ -60261,7 +60361,7 @@ async function queryBacklinks(opts) {
60261
60361
  const reversed = opts.targets.map(reverseDomain);
60262
60362
  const targetList = reversed.map(quote).join(", ");
60263
60363
  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 = `
60364
+ const sql38 = `
60265
60365
  WITH vertices AS (
60266
60366
  SELECT * FROM read_csv(
60267
60367
  ${quote(opts.vertexPath)},
@@ -60297,7 +60397,7 @@ async function queryBacklinks(opts) {
60297
60397
  const conn = await instance.connect();
60298
60398
  let rows;
60299
60399
  try {
60300
- const reader = await conn.runAndReadAll(sql37);
60400
+ const reader = await conn.runAndReadAll(sql38);
60301
60401
  rows = reader.getRowObjects();
60302
60402
  } finally {
60303
60403
  conn.disconnectSync?.();
@@ -62904,6 +63004,7 @@ function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing =
62904
63004
  preset: null,
62905
63005
  timezone: "UTC",
62906
63006
  enabled: true,
63007
+ enabledAt: now,
62907
63008
  providers: [],
62908
63009
  sourceId,
62909
63010
  createdAt: now,
@@ -67413,6 +67514,195 @@ var gscRecentDataCheck = {
67413
67514
  };
67414
67515
  var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
67415
67516
 
67517
+ // ../api-routes/src/doctor/checks/seo-refresh.ts
67518
+ 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";
67519
+ var GRACE_DAYS = 2;
67520
+ var DAY_MS5 = 24 * 60 * 60 * 1e3;
67521
+ var FIRING_SPAN_MS = 6e4;
67522
+ var REASON_LIMIT = 300;
67523
+ function skipped2(code, summary3) {
67524
+ return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
67525
+ }
67526
+ function seoSyncRuns(projectId) {
67527
+ return and75(eq89(runs.projectId, projectId), eq89(runs.kind, RunKinds["seo-sync"]));
67528
+ }
67529
+ function scheduleTiming(schedule) {
67530
+ if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
67531
+ const recurrence = calendarRecurrenceSchema.safeParse(schedule.recurrence).data;
67532
+ return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
67533
+ }
67534
+ function latestFiring(ctx, projectId, enabledSince) {
67535
+ const scheduled = and75(seoSyncRuns(projectId), eq89(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
67536
+ const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc42(runs.createdAt)).limit(1).get();
67537
+ if (!newest) return [];
67538
+ const newestMs = Date.parse(newest.createdAt);
67539
+ const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
67540
+ 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();
67541
+ }
67542
+ function lastCompleted(ctx, projectId) {
67543
+ const at = sql32`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
67544
+ 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;
67545
+ }
67546
+ function isIncomplete(run) {
67547
+ return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
67548
+ }
67549
+ function runProfiles(ctx, firing) {
67550
+ 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();
67551
+ const byRun = new Map(jobs.flatMap((job) => job.runId ? [[job.runId, job.profileId]] : []));
67552
+ return new Map(firing.map((run) => [run.id, byRun.get(run.id) ?? skippedRefreshProfile(run.error)]));
67553
+ }
67554
+ function unsettledRuns(ctx, projectId, firing, profiles) {
67555
+ const incomplete = firing.filter(isIncomplete);
67556
+ const firedAt = firing[firing.length - 1]?.createdAt;
67557
+ if (incomplete.length === 0 || !firedAt) return incomplete;
67558
+ 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));
67559
+ return incomplete.filter((run) => {
67560
+ const profile = profiles.get(run.id) ?? null;
67561
+ return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
67562
+ });
67563
+ }
67564
+ var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
67565
+ function refreshReason(ctx, run) {
67566
+ const stored = parseRunError(run.error);
67567
+ if (stored) return formatRunErrorOneLine(stored);
67568
+ 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());
67569
+ const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
67570
+ const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
67571
+ const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
67572
+ const errors = [...new Set(failed.flatMap((task) => task.error ? [task.error] : []))].join(" ");
67573
+ if (tasks.length <= 1) {
67574
+ if (errors) return errors;
67575
+ if (partial > 0) return "Some SEO results could not be collected.";
67576
+ return cancelled > 0 ? "The refresh was cancelled." : "No reason was recorded.";
67577
+ }
67578
+ const unit = tasks.every((task) => task.request.capability === SeoCapabilities["market-rankings"]) ? "searches" : "requests";
67579
+ const counts2 = [
67580
+ ...failed.length > 0 ? [`${failed.length} failed`] : [],
67581
+ ...partial > 0 ? [`${partial} returned only part of their results`] : [],
67582
+ ...cancelled > 0 ? [`${cancelled} did not run`] : []
67583
+ ];
67584
+ if (counts2.length === 0) return "No reason was recorded.";
67585
+ return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
67586
+ }
67587
+ function gradeSeoRefresh(ctx, project) {
67588
+ const schedule = ctx.db.select().from(schedules).where(and75(eq89(schedules.projectId, project.id), eq89(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
67589
+ if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
67590
+ if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
67591
+ const name = project.name;
67592
+ const day = (iso) => formatIsoDateInTimeZone(iso, schedule.timezone);
67593
+ const now = Date.now();
67594
+ const timing = scheduleTiming(schedule);
67595
+ const last = lastCompleted(ctx, project.id);
67596
+ const enabledSince = schedule.enabledAt ?? schedule.createdAt;
67597
+ const reference = last && Date.parse(last.at) > Date.parse(enabledSince) ? last.at : enabledSince;
67598
+ const dueAt = timing ? nextRunFromSchedule(timing, new Date(reference)) : null;
67599
+ const deadlineAt = dueAt ? new Date(Date.parse(dueAt) + GRACE_DAYS * DAY_MS5).toISOString() : null;
67600
+ const firing = latestFiring(ctx, project.id, enabledSince);
67601
+ const profiles = runProfiles(ctx, firing);
67602
+ const details = {
67603
+ scheduleId: schedule.id,
67604
+ cronExpr: schedule.cronExpr || null,
67605
+ recurrence: schedule.recurrence ?? null,
67606
+ timezone: schedule.timezone,
67607
+ enabledSince,
67608
+ graceDays: GRACE_DAYS,
67609
+ lastCompletedAt: last?.at ?? null,
67610
+ lastCompletedRunId: last?.id ?? null,
67611
+ dueAt,
67612
+ deadlineAt,
67613
+ latestScheduledRuns: firing.map((run) => ({
67614
+ id: run.id,
67615
+ profileId: profiles.get(run.id) ?? null,
67616
+ status: run.status,
67617
+ createdAt: run.createdAt,
67618
+ reason: isIncomplete(run) ? refreshReason(ctx, run) : null
67619
+ }))
67620
+ };
67621
+ if (!timing || !dueAt || !deadlineAt) {
67622
+ return {
67623
+ status: CheckStatuses.warn,
67624
+ code: "seo.data.schedule-invalid",
67625
+ summary: "The SEO refresh schedule has no time it can run, so no scheduled SEO refresh will start.",
67626
+ remediation: `Set a schedule that can run, for example \`canonry schedule set ${name} --kind seo-sync --cron '0 7 2 * *' --timezone UTC\`.`,
67627
+ details,
67628
+ alert: {
67629
+ label: "SEO schedule invalid",
67630
+ line: "The SEO refresh schedule has no time it can run, so SEO data never refreshes.",
67631
+ next: `Set it again with canonry schedule set ${name} --kind seo-sync.`
67632
+ }
67633
+ };
67634
+ }
67635
+ const nextAt = nextRunFromSchedule(timing, new Date(now));
67636
+ const unsettled = unsettledRuns(ctx, project.id, firing, profiles);
67637
+ const newestFired = firing[firing.length - 1];
67638
+ if (newestFired && unsettled.length > 0) {
67639
+ const failed = unsettled.some((run) => run.status === RunStatuses.failed);
67640
+ const firedOn = day(newestFired.createdAt);
67641
+ const reasons = [...new Set(unsettled.map((run) => refreshReason(ctx, run)))].join(" ");
67642
+ const reason = reasons.length > REASON_LIMIT ? `${truncateUtf16(reasons, REASON_LIMIT)}...` : reasons;
67643
+ 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`;
67644
+ const line = firing.length > 1 ? `${outcome2}.` : unsettled[0].error ? reason : `${outcome2}: ${reason}`;
67645
+ const next = nextAt ? ` on ${day(nextAt)}` : "";
67646
+ return {
67647
+ status: CheckStatuses.warn,
67648
+ code: failed ? "seo.data.refresh-failed" : "seo.data.refresh-partial",
67649
+ summary: `${outcome2}. Reason: ${reason} ${last ? `The last completed SEO refresh was on ${day(last.at)}.` : "No SEO refresh has completed yet."}`,
67650
+ 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.`,
67651
+ details,
67652
+ alert: {
67653
+ label: failed ? "SEO refresh failed" : "SEO refresh incomplete",
67654
+ line: firstSentence(line) ?? line,
67655
+ next: `Fix the cause, then refresh by hand or wait for the next scheduled refresh${next}.`
67656
+ }
67657
+ };
67658
+ }
67659
+ if (now >= Date.parse(deadlineAt)) {
67660
+ 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();
67661
+ const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
67662
+ 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.`;
67663
+ return {
67664
+ status: CheckStatuses.warn,
67665
+ code: "seo.data.overdue",
67666
+ summary: `${since} ${missed}${active ? ` A refresh started ${day(active.createdAt)} has not finished, and scheduled refreshes wait for it.` : ""}`,
67667
+ 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\`.`,
67668
+ details: { ...details, activeRunId: active?.id ?? null },
67669
+ alert: {
67670
+ label: "SEO refresh overdue",
67671
+ line: since,
67672
+ 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.`
67673
+ }
67674
+ };
67675
+ }
67676
+ const pending = Date.parse(dueAt) <= now;
67677
+ if (!last) {
67678
+ return {
67679
+ status: CheckStatuses.ok,
67680
+ code: "seo.data.awaiting-first-refresh",
67681
+ 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)}.`,
67682
+ remediation: null,
67683
+ details
67684
+ };
67685
+ }
67686
+ return {
67687
+ status: CheckStatuses.ok,
67688
+ code: "seo.data.fresh",
67689
+ 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)}.`}`,
67690
+ remediation: null,
67691
+ details
67692
+ };
67693
+ }
67694
+ var seoRecentRefreshCheck = {
67695
+ id: "seo.data.recent-refresh",
67696
+ category: CheckCategories.integrations,
67697
+ scope: CheckScopes.project,
67698
+ title: "SEO data still refreshing",
67699
+ run: (ctx) => {
67700
+ if (!ctx.project) return skipped2("seo.data.no-project", "Project context required.");
67701
+ return gradeSeoRefresh(ctx, ctx.project);
67702
+ }
67703
+ };
67704
+ var SEO_REFRESH_CHECKS = [seoRecentRefreshCheck];
67705
+
67416
67706
  // ../api-routes/src/site-reachability.ts
67417
67707
  import dns3 from "dns/promises";
67418
67708
  import http3 from "http";
@@ -67616,7 +67906,7 @@ function probeHostFromDomain(value) {
67616
67906
  if (!hostname || !hostname.includes(".") || hostname.endsWith(".localhost")) return null;
67617
67907
  return hostname;
67618
67908
  }
67619
- var skipped2 = (code, summary3) => ({ status: CheckStatuses.skipped, code, summary: summary3, remediation: null });
67909
+ var skipped3 = (code, summary3) => ({ status: CheckStatuses.skipped, code, summary: summary3, remediation: null });
67620
67910
  var siteReachabilityCheck = {
67621
67911
  id: SITE_REACHABILITY_CHECK_ID,
67622
67912
  category: CheckCategories.integrations,
@@ -67627,16 +67917,16 @@ var siteReachabilityCheck = {
67627
67917
  // `canonry doctor --project` must not exit 1 on a single dropped packet.
67628
67918
  optIn: true,
67629
67919
  run: async (ctx) => {
67630
- if (!ctx.project) return skipped2("site.reachability.no-project", "Project context required.");
67920
+ if (!ctx.project) return skipped3("site.reachability.no-project", "Project context required.");
67631
67921
  const host = probeHostFromDomain(ctx.project.canonicalDomain);
67632
67922
  if (!host) {
67633
- return skipped2("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
67923
+ return skipped3("site.reachability.no-domain", `Project domain "${(ctx.project.canonicalDomain ?? "").trim()}" is not a probeable hostname.`);
67634
67924
  }
67635
67925
  const url = `https://${host}/`;
67636
67926
  const probe = ctx.probeSiteReachability ?? probeSiteReachability;
67637
67927
  const result = await probe(url);
67638
67928
  if (result.state === "unavailable") {
67639
- return skipped2("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
67929
+ return skipped3("site.reachability.probe-unavailable", `Could not probe ${url} from this host: ${result.reason}.`);
67640
67930
  }
67641
67931
  if (result.state === "up") {
67642
67932
  return {
@@ -67677,7 +67967,7 @@ var siteReachabilityCheck = {
67677
67967
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
67678
67968
 
67679
67969
  // ../api-routes/src/doctor/checks/api-hosts.ts
67680
- import { eq as eq90 } from "drizzle-orm";
67970
+ import { eq as eq91 } from "drizzle-orm";
67681
67971
 
67682
67972
  // ../api-routes/src/api-host-probe.ts
67683
67973
  import dns4 from "dns/promises";
@@ -67783,7 +68073,7 @@ async function probeApiHost(host, options = {}) {
67783
68073
  }
67784
68074
 
67785
68075
  // ../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";
68076
+ 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
68077
  var SYNC_FAILURE_WINDOW = 3;
67788
68078
  var SUMMARY_ERROR_LIMIT = 300;
67789
68079
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -67804,7 +68094,7 @@ function gscConnection(ctx, project) {
67804
68094
  function gbpConnection(ctx, project) {
67805
68095
  if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
67806
68096
  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();
68097
+ 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
68098
  return selected ? CONNECTED : { state: "no-selected-locations" };
67809
68099
  }
67810
68100
  function gaConnection(ctx, project) {
@@ -67861,7 +68151,7 @@ var SPECS = [
67861
68151
  causeCheckIds: ["google.auth.connection", "google.auth.property-access", "google.auth.scopes", "gsc.api.reachable"]
67862
68152
  }
67863
68153
  ];
67864
- function skipped3(code, summary3) {
68154
+ function skipped4(code, summary3) {
67865
68155
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
67866
68156
  }
67867
68157
  function latestErrorText(raw) {
@@ -67869,16 +68159,16 @@ function latestErrorText(raw) {
67869
68159
  return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
67870
68160
  }
67871
68161
  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)] : [],
68162
+ const scope = and76(
68163
+ eq90(runs.projectId, projectId),
68164
+ eq90(runs.kind, kind),
68165
+ ...options.sourceId ? [eq90(runs.sourceId, options.sourceId)] : [],
67876
68166
  ...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
67877
68167
  );
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();
68168
+ const graded = options.since ? and76(scope, gte23(runs.createdAt, options.since)) : scope;
68169
+ 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();
68170
+ 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();
68171
+ 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
68172
  const failuresSinceSuccess = Number(failureCount?.count ?? 0);
67883
68173
  const latest = recent[0];
67884
68174
  return {
@@ -67895,21 +68185,21 @@ function readSyncRunState(db, projectId, kind, options = {}) {
67895
68185
  function gradeSyncFailures(ctx, spec) {
67896
68186
  const code = (suffix) => `${spec.prefix}.sync.${suffix}`;
67897
68187
  const project = ctx.project;
67898
- if (!project) return skipped3(code("no-project"), "Project context required.");
68188
+ if (!project) return skipped4(code("no-project"), "Project context required.");
67899
68189
  const connection = spec.connection(ctx, project);
67900
68190
  if (connection.state === "store-unavailable") {
67901
- return skipped3(code("store-unavailable"), `No ${spec.label} connection store configured for this deployment.`);
68191
+ return skipped4(code("store-unavailable"), `No ${spec.label} connection store configured for this deployment.`);
67902
68192
  }
67903
68193
  if (connection.state === "not-connected") {
67904
- return skipped3(code("not-connected"), `${spec.label} is not connected for this project.`);
68194
+ return skipped4(code("not-connected"), `${spec.label} is not connected for this project.`);
67905
68195
  }
67906
68196
  if (connection.state === "no-selected-locations") {
67907
- return skipped3(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
68197
+ return skipped4(code("no-selected-locations"), `No ${spec.label} locations are selected for this project.`);
67908
68198
  }
67909
68199
  const nothingToSync = spec.nothingToSyncErrors ?? [];
67910
68200
  const state = readSyncRunState(ctx.db, project.id, spec.kind, { since: connection.since, nothingToSyncErrors: nothingToSync });
67911
68201
  if (state.latestStatus === null) {
67912
- return skipped3(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
68202
+ return skipped4(code("no-runs"), `${spec.label} is connected but no sync has finished yet.`);
67913
68203
  }
67914
68204
  const latestFailed = state.latestStatus === RunStatuses.failed;
67915
68205
  const details = {
@@ -67965,7 +68255,7 @@ var API_HOST_TABLE = {
67965
68255
  ads: ["api.ads.openai.com"]
67966
68256
  };
67967
68257
  var API_HOST_EXCLUDED = { gsc: [new URL(INDEXING_API_BASE).hostname] };
67968
- function skipped4(code, summary3) {
68258
+ function skipped5(code, summary3) {
67969
68259
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
67970
68260
  }
67971
68261
  function connectionFor(prefix, ctx, project) {
@@ -67975,7 +68265,7 @@ function connectionFor(prefix, ctx, project) {
67975
68265
  if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
67976
68266
  if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
67977
68267
  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" };
68268
+ 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
68269
  }
67980
68270
  function syncCommand(prefix) {
67981
68271
  if (prefix === "gsc") return "google sync";
@@ -67989,9 +68279,9 @@ function syncCommand(prefix) {
67989
68279
  async function grade2(prefix, ctx) {
67990
68280
  const id = `${prefix}.api`;
67991
68281
  const checkId = `${id}.reachable`;
67992
- if (!ctx.project) return skipped4(`${id}.no-project`, "Project context required.");
68282
+ if (!ctx.project) return skipped5(`${id}.no-project`, "Project context required.");
67993
68283
  const connection = connectionFor(prefix, ctx, ctx.project);
67994
- if (connection.state !== "connected") return skipped4(`${id}.not-connected`, `${prefix} is not connected for this project.`);
68284
+ if (connection.state !== "connected") return skipped5(`${id}.not-connected`, `${prefix} is not connected for this project.`);
67995
68285
  const probe = ctx.probeApiHost ?? probeApiHost;
67996
68286
  const results = await Promise.all(API_HOST_TABLE[prefix].map((host) => probe(host)));
67997
68287
  const details = { hosts: results };
@@ -68020,7 +68310,7 @@ function definition(prefix) {
68020
68310
  var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
68021
68311
 
68022
68312
  // ../api-routes/src/doctor/checks/gbp-auth.ts
68023
- import { and as and76, eq as eq91 } from "drizzle-orm";
68313
+ import { and as and77, eq as eq92 } from "drizzle-orm";
68024
68314
  var RECENT_SYNC_WARN_DAYS = 4;
68025
68315
  var RECENT_SYNC_FAIL_DAYS = 30;
68026
68316
  function skippedNoProject3() {
@@ -68040,7 +68330,7 @@ function storeUnavailable() {
68040
68330
  };
68041
68331
  }
68042
68332
  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();
68333
+ return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and77(eq92(gbpLocations.projectId, projectId), eq92(gbpLocations.selected, true))).limit(1).get();
68044
68334
  }
68045
68335
  async function resolveGbpToken(ctx) {
68046
68336
  if (!ctx.project) return { ok: false, output: skippedNoProject3() };
@@ -68300,7 +68590,7 @@ var recentSyncCheck = {
68300
68590
  title: "GBP recent sync",
68301
68591
  run: (ctx) => {
68302
68592
  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();
68593
+ 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
68594
  if (selected.length === 0) {
68305
68595
  return {
68306
68596
  status: CheckStatuses.skipped,
@@ -68360,7 +68650,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
68360
68650
  );
68361
68651
 
68362
68652
  // ../api-routes/src/doctor/checks/places.ts
68363
- import { eq as eq92 } from "drizzle-orm";
68653
+ import { eq as eq93 } from "drizzle-orm";
68364
68654
  var apiKeyCheck = {
68365
68655
  id: "gbp.places.api-key",
68366
68656
  category: CheckCategories.auth,
@@ -68405,7 +68695,7 @@ var apiKeyCheck = {
68405
68695
  details: { tier: cfg.tier }
68406
68696
  };
68407
68697
  }
68408
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq92(gbpLocations.projectId, ctx.project.id)).all();
68698
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq93(gbpLocations.projectId, ctx.project.id)).all();
68409
68699
  const selected = rows.filter((r) => r.selected);
68410
68700
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
68411
68701
  const details = {
@@ -68438,7 +68728,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
68438
68728
  );
68439
68729
 
68440
68730
  // ../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";
68731
+ import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray41, sql as sql33 } from "drizzle-orm";
68442
68732
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
68443
68733
  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
68734
  var reviewsAccessCheck = {
@@ -68466,7 +68756,7 @@ var reviewsAccessCheck = {
68466
68756
  reviewsAccess: gbpLocations.reviewsAccess,
68467
68757
  reviewsAccessReason: gbpLocations.reviewsAccessReason,
68468
68758
  reviewsCheckedAt: gbpLocations.reviewsCheckedAt
68469
- }).from(gbpLocations).where(and77(eq93(gbpLocations.projectId, project.id), eq93(gbpLocations.selected, true))).all();
68759
+ }).from(gbpLocations).where(and78(eq94(gbpLocations.projectId, project.id), eq94(gbpLocations.selected, true))).all();
68470
68760
  if (locations.length === 0) {
68471
68761
  return {
68472
68762
  status: CheckStatuses.skipped,
@@ -68486,7 +68776,7 @@ var reviewsAccessCheck = {
68486
68776
  const places = ctx.getPlacesConfig?.();
68487
68777
  const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
68488
68778
  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)
68779
+ 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
68780
  );
68491
68781
  const coverageOf = (loc) => {
68492
68782
  if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
@@ -68520,7 +68810,7 @@ var reviewsAccessCheck = {
68520
68810
  none > 0 ? `${none} have no review source` : null
68521
68811
  ].filter((part) => part !== null);
68522
68812
  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)));
68813
+ 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
68814
  const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
68525
68815
  if (none > 0 && subscribed) {
68526
68816
  return {
@@ -68551,7 +68841,7 @@ var reviewDeliveryCheck = {
68551
68841
  return { status: CheckStatuses.skipped, code: "gbp.reviews.delivery.no-project", summary: "Project context required.", remediation: null };
68552
68842
  }
68553
68843
  const since = new Date(Date.now() - UNDELIVERED_ATTENTION_DAYS * 864e5).toISOString();
68554
- const lastTouched = sql32`coalesce(${reviewWebhookDeliveries.attemptedAt}, ${reviewWebhookDeliveries.createdAt})`;
68844
+ const lastTouched = sql33`coalesce(${reviewWebhookDeliveries.attemptedAt}, ${reviewWebhookDeliveries.createdAt})`;
68555
68845
  const rows = ctx.db.select({
68556
68846
  id: reviewWebhookDeliveries.id,
68557
68847
  notificationId: reviewWebhookDeliveries.notificationId,
@@ -68559,11 +68849,11 @@ var reviewDeliveryCheck = {
68559
68849
  attempts: reviewWebhookDeliveries.attempts,
68560
68850
  lastTouched,
68561
68851
  payload: reviewWebhookDeliveries.payload
68562
- }).from(reviewWebhookDeliveries).where(and77(
68563
- eq93(reviewWebhookDeliveries.projectId, ctx.project.id),
68564
- inArray40(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68852
+ }).from(reviewWebhookDeliveries).where(and78(
68853
+ eq94(reviewWebhookDeliveries.projectId, ctx.project.id),
68854
+ inArray41(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68565
68855
  gte24(lastTouched, since)
68566
- )).orderBy(desc43(lastTouched)).all();
68856
+ )).orderBy(desc44(lastTouched)).all();
68567
68857
  if (rows.length === 0) {
68568
68858
  return {
68569
68859
  status: CheckStatuses.ok,
@@ -68572,7 +68862,7 @@ var reviewDeliveryCheck = {
68572
68862
  remediation: null
68573
68863
  };
68574
68864
  }
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]));
68865
+ 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
68866
  const deliveries = rows.map((row) => {
68577
68867
  const review = row.payload.reviews[0];
68578
68868
  return {
@@ -69146,7 +69436,7 @@ var RUNTIME_STATE_CHECKS = [
69146
69436
  ];
69147
69437
 
69148
69438
  // ../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";
69439
+ 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
69440
  var RECENT_DATA_WARN_DAYS = 7;
69151
69441
  var RECENT_DATA_FAIL_DAYS = 30;
69152
69442
  function isCloudflareDirectPush(source) {
@@ -69185,8 +69475,8 @@ function skippedNoProject5() {
69185
69475
  function loadProbes(ctx) {
69186
69476
  if (!ctx.project) return [];
69187
69477
  const rows = ctx.db.select().from(trafficSources).where(
69188
- and78(
69189
- eq94(trafficSources.projectId, ctx.project.id),
69478
+ and79(
69479
+ eq95(trafficSources.projectId, ctx.project.id),
69190
69480
  ne16(trafficSources.status, TrafficSourceStatuses.archived)
69191
69481
  )
69192
69482
  ).all();
@@ -69323,28 +69613,28 @@ var recentDataCheck = {
69323
69613
  const warnCutoff = new Date(now.getTime() - RECENT_DATA_WARN_DAYS * 24 * 60 * 6e4).toISOString();
69324
69614
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
69325
69615
  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),
69616
+ ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69617
+ and79(
69618
+ eq95(crawlerEventsHourly.projectId, ctx.project.id),
69619
+ inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
69330
69620
  gte25(crawlerEventsHourly.tsHour, warnCutoff)
69331
69621
  )
69332
69622
  ).get()?.total ?? 0
69333
69623
  );
69334
69624
  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),
69625
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69626
+ and79(
69627
+ eq95(aiReferralEventsHourly.projectId, ctx.project.id),
69628
+ inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
69339
69629
  gte25(aiReferralEventsHourly.tsHour, warnCutoff)
69340
69630
  )
69341
69631
  ).get()?.total ?? 0
69342
69632
  );
69343
69633
  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),
69634
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69635
+ and79(
69636
+ eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
69637
+ inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69348
69638
  gte25(aiUserFetchEventsHourly.tsHour, warnCutoff)
69349
69639
  )
69350
69640
  ).get()?.total ?? 0
@@ -69367,28 +69657,28 @@ var recentDataCheck = {
69367
69657
  };
69368
69658
  }
69369
69659
  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),
69660
+ ctx.db.select({ total: sql34`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69661
+ and79(
69662
+ eq95(crawlerEventsHourly.projectId, ctx.project.id),
69663
+ inArray42(crawlerEventsHourly.sourceId, activeSourceIds),
69374
69664
  gte25(crawlerEventsHourly.tsHour, failCutoff)
69375
69665
  )
69376
69666
  ).get()?.total ?? 0
69377
69667
  );
69378
69668
  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),
69669
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69670
+ and79(
69671
+ eq95(aiReferralEventsHourly.projectId, ctx.project.id),
69672
+ inArray42(aiReferralEventsHourly.sourceId, activeSourceIds),
69383
69673
  gte25(aiReferralEventsHourly.tsHour, failCutoff)
69384
69674
  )
69385
69675
  ).get()?.total ?? 0
69386
69676
  );
69387
69677
  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),
69678
+ ctx.db.select({ total: sql34`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69679
+ and79(
69680
+ eq95(aiUserFetchEventsHourly.projectId, ctx.project.id),
69681
+ inArray42(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69392
69682
  gte25(aiUserFetchEventsHourly.tsHour, failCutoff)
69393
69683
  )
69394
69684
  ).get()?.total ?? 0
@@ -69453,7 +69743,7 @@ async function runValidator(source, validator, fallbackId, fallbackLabel) {
69453
69743
  function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
69454
69744
  const failed = results.filter((r) => r.output.status === CheckStatuses.fail);
69455
69745
  const warned = results.filter((r) => r.output.status === CheckStatuses.warn);
69456
- const skipped5 = results.filter((r) => r.output.status === CheckStatuses.skipped);
69746
+ const skipped6 = results.filter((r) => r.output.status === CheckStatuses.skipped);
69457
69747
  const ok = results.filter((r) => r.output.status === CheckStatuses.ok);
69458
69748
  const detail = {
69459
69749
  sources: results.map((r) => ({
@@ -69487,7 +69777,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
69487
69777
  return {
69488
69778
  status: CheckStatuses.ok,
69489
69779
  code: `traffic.${fallbackId}.ok`,
69490
- summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped5.length > 0 ? ` (${skipped5.length} skipped)` : ""}.`,
69780
+ summary: `${ok.length} source(s) passed ${fallbackLabel} validation${skipped6.length > 0 ? ` (${skipped6.length} skipped)` : ""}.`,
69491
69781
  details: detail
69492
69782
  };
69493
69783
  }
@@ -69495,7 +69785,7 @@ function summarizePerSourceResults(fallbackId, fallbackLabel, results) {
69495
69785
  status: CheckStatuses.skipped,
69496
69786
  code: `traffic.${fallbackId}.all-skipped`,
69497
69787
  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,
69788
+ remediation: skipped6.find((result) => result.output.remediation)?.output.remediation,
69499
69789
  details: detail
69500
69790
  };
69501
69791
  }
@@ -69714,12 +70004,12 @@ var syncLagCheck = {
69714
70004
  }
69715
70005
  };
69716
70006
  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),
70007
+ const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and79(
70008
+ eq95(runs.projectId, ctx.project.id),
70009
+ eq95(runs.kind, RunKinds["traffic-sync"]),
70010
+ eq95(runs.sourceId, sourceId),
69721
70011
  isNotNull9(runs.syncSummary)
69722
- )).orderBy(desc44(runs.createdAt)).limit(1).get();
70012
+ )).orderBy(desc45(runs.createdAt)).limit(1).get();
69723
70013
  return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
69724
70014
  }
69725
70015
  var QUEUE_LIMIT_STOPS = [
@@ -70009,7 +70299,7 @@ var GTM_REQUIRED_OAUTH_SCOPES = [
70009
70299
  ];
70010
70300
  var FRESH_SNAPSHOT_WARN_DAYS = 7;
70011
70301
  var FRESH_SNAPSHOT_FAIL_DAYS = 30;
70012
- var DAY_MS5 = 24 * 60 * 60 * 1e3;
70302
+ var DAY_MS6 = 24 * 60 * 60 * 1e3;
70013
70303
  function unavailableStatus() {
70014
70304
  return {
70015
70305
  status: CheckStatuses.skipped,
@@ -70096,7 +70386,7 @@ function snapshotOutput(provider, latestSnapshotAt, now) {
70096
70386
  details: { latestSnapshotAt }
70097
70387
  };
70098
70388
  }
70099
- const ageDays2 = (now.getTime() - snapshotMs) / DAY_MS5;
70389
+ const ageDays2 = (now.getTime() - snapshotMs) / DAY_MS6;
70100
70390
  const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays2)) };
70101
70391
  if (ageDays2 < 0) {
70102
70392
  return {
@@ -70319,6 +70609,7 @@ var ALL_CHECKS = [
70319
70609
  ...WORDPRESS_PUBLISH_CHECKS,
70320
70610
  ...GA_AUTH_CHECKS,
70321
70611
  ...DATA_FRESHNESS_CHECKS,
70612
+ ...SEO_REFRESH_CHECKS,
70322
70613
  reportSweepsCheck,
70323
70614
  reportModelsCheck,
70324
70615
  reportDailyDataCheck,
@@ -70516,7 +70807,7 @@ async function doctorRoutes(app, opts) {
70516
70807
  }
70517
70808
 
70518
70809
  // ../api-routes/src/status.ts
70519
- import { and as and79, desc as desc45, eq as eq95, isNull as isNull15 } from "drizzle-orm";
70810
+ import { and as and80, desc as desc46, eq as eq96, isNull as isNull15 } from "drizzle-orm";
70520
70811
  function ageDays(iso) {
70521
70812
  if (!iso) return null;
70522
70813
  const ms = Date.now() - Date.parse(iso);
@@ -70566,7 +70857,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
70566
70857
  };
70567
70858
  }
70568
70859
  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();
70860
+ const row = db.select({ date: table.date }).from(table).where(eq96(table.projectId, projectId)).orderBy(desc46(table.date)).limit(1).get();
70570
70861
  return row?.date ?? null;
70571
70862
  }
70572
70863
  function projectRows(opts, project) {
@@ -70581,7 +70872,7 @@ function projectRows(opts, project) {
70581
70872
  const rows = [];
70582
70873
  const gsc = gscConnection(ctx, info);
70583
70874
  if (connected(gsc)) {
70584
- const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq95(gscDataWatermarks.projectId, project.id)).limit(1).get();
70875
+ const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq96(gscDataWatermarks.projectId, project.id)).limit(1).get();
70585
70876
  const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
70586
70877
  const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
70587
70878
  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 +70885,12 @@ function projectRows(opts, project) {
70594
70885
  const gbp = gbpConnection(ctx, info);
70595
70886
  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
70887
  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();
70888
+ const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq96(adsConnections.projectId, project.id)).limit(1).get();
70598
70889
  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
70890
  const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
70600
70891
  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
70892
  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();
70893
+ const sources = opts.db.select().from(trafficSources).where(and80(eq96(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
70603
70894
  for (const source of sources) {
70604
70895
  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
70896
  const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
@@ -70611,7 +70902,7 @@ function projectRows(opts, project) {
70611
70902
  }
70612
70903
  function projectStatus(opts, project) {
70613
70904
  const integrations = projectRows(opts, project);
70614
- const healthRow = opts.db.select().from(doctorHealthState).where(eq95(doctorHealthState.projectId, project.id)).limit(1).get();
70905
+ const healthRow = opts.db.select().from(doctorHealthState).where(eq96(doctorHealthState.projectId, project.id)).limit(1).get();
70615
70906
  const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
70616
70907
  const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
70617
70908
  const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
@@ -70631,7 +70922,7 @@ async function statusRoutes(app, opts) {
70631
70922
  if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
70632
70923
  return instanceStatusDtoSchema.parse(result);
70633
70924
  }
70634
- const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq95(projects.id, scopedId)) : opts.db.select().from(projects)).all();
70925
+ const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq96(projects.id, scopedId)) : opts.db.select().from(projects)).all();
70635
70926
  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
70927
  for (const project of output.projects) {
70637
70928
  const key = project.state === "never-synced" ? "neverSynced" : project.state;
@@ -70644,7 +70935,7 @@ async function statusRoutes(app, opts) {
70644
70935
 
70645
70936
  // ../api-routes/src/discovery/routes.ts
70646
70937
  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";
70938
+ 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
70939
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
70649
70940
  async function discoveryRoutes(app, opts) {
70650
70941
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -70678,13 +70969,13 @@ async function discoveryRoutes(app, opts) {
70678
70969
  const now = (/* @__PURE__ */ new Date()).toISOString();
70679
70970
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
70680
70971
  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),
70972
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and81(
70973
+ eq97(discoverySessions.projectId, project.id),
70974
+ eq97(discoverySessions.icpDescription, icpDescription),
70684
70975
  // Buyer is part of session identity: it changes the seed prompt's
70685
70976
  // semantics, so a request with a different (or no) buyer must start
70686
70977
  // its own session, never adopt another buyer's probes.
70687
- parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq96(discoverySessions.buyerDescription, parsed.data.buyerDescription),
70978
+ parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq97(discoverySessions.buyerDescription, parsed.data.buyerDescription),
70688
70979
  // Locations are identity too: a different service-area subset seeds
70689
70980
  // and probes a different geo, so it must never reuse another geo's
70690
70981
  // session. resolveLocations is deterministic (project-config order),
@@ -70694,18 +70985,18 @@ async function discoveryRoutes(app, opts) {
70694
70985
  // locations a NULL row's subset is unknowable, so it conservatively
70695
70986
  // never reuses — a one-time, bounded (2h window) non-reuse after
70696
70987
  // upgrade, never a wrong reuse.
70697
- locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq96(discoverySessions.locations, locations)) : eq96(discoverySessions.locations, locations),
70988
+ locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq97(discoverySessions.locations, locations)) : eq97(discoverySessions.locations, locations),
70698
70989
  // Seed provider set is identity: a different phrasing distribution
70699
70990
  // must never reuse another set's session. Null (= Gemini-only
70700
70991
  // default, including explicit ['gemini']) matches legacy rows.
70701
- seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq96(discoverySessions.seedProviders, seedProviders),
70702
- inArray42(discoverySessions.status, [
70992
+ seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq97(discoverySessions.seedProviders, seedProviders),
70993
+ inArray43(discoverySessions.status, [
70703
70994
  DiscoverySessionStatuses.queued,
70704
70995
  DiscoverySessionStatuses.seeding,
70705
70996
  DiscoverySessionStatuses.probing
70706
70997
  ]),
70707
70998
  gte26(discoverySessions.createdAt, ageFloorIso)
70708
- )).orderBy(desc46(discoverySessions.createdAt)).get();
70999
+ )).orderBy(desc47(discoverySessions.createdAt)).get();
70709
71000
  if (existing && existing.runId) {
70710
71001
  return { reused: true, sessionId: existing.id, runId: existing.runId };
70711
71002
  }
@@ -70774,7 +71065,7 @@ async function discoveryRoutes(app, opts) {
70774
71065
  const project = resolveProject(app.db, request.params.name);
70775
71066
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
70776
71067
  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();
71068
+ const rows = app.db.select().from(discoverySessions).where(eq97(discoverySessions.projectId, project.id)).orderBy(desc47(discoverySessions.createdAt)).limit(limit).all();
70778
71069
  return reply.send(rows.map(serializeSession));
70779
71070
  }
70780
71071
  );
@@ -70782,11 +71073,11 @@ async function discoveryRoutes(app, opts) {
70782
71073
  "/projects/:name/discover/sessions/:id",
70783
71074
  async (request, reply) => {
70784
71075
  const project = resolveProject(app.db, request.params.name);
70785
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71076
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70786
71077
  if (!session || session.projectId !== project.id) {
70787
71078
  throw notFound("Discovery session", request.params.id);
70788
71079
  }
70789
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71080
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70790
71081
  const detail = {
70791
71082
  ...serializeSession(session),
70792
71083
  probes: probeRows.map(serializeProbe)
@@ -70798,7 +71089,7 @@ async function discoveryRoutes(app, opts) {
70798
71089
  "/projects/:name/discover/sessions/:id/harvest",
70799
71090
  async (request, reply) => {
70800
71091
  const project = resolveProject(app.db, request.params.name);
70801
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71092
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70802
71093
  if (!session || session.projectId !== project.id) {
70803
71094
  throw notFound("Discovery session", request.params.id);
70804
71095
  }
@@ -70806,7 +71097,7 @@ async function discoveryRoutes(app, opts) {
70806
71097
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
70807
71098
  const applyAnchor = request.query.anchor !== "false";
70808
71099
  const provider = session.seedProvider ?? "gemini";
70809
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71100
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70810
71101
  const extract = opts.harvestSearchQueries;
70811
71102
  const probesWithQueries = probeRows.map((row) => {
70812
71103
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -70817,7 +71108,7 @@ async function discoveryRoutes(app, opts) {
70817
71108
  return { searchQueries: [] };
70818
71109
  }
70819
71110
  });
70820
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq96(queries.projectId, project.id)).all().map((r) => r.query);
71111
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query);
70821
71112
  const anchorTerms = buildHarvestAnchorTerms(
70822
71113
  [session.icpDescription ?? "", ...trackedQueries],
70823
71114
  effectiveDomains(project)
@@ -70864,11 +71155,11 @@ async function discoveryRoutes(app, opts) {
70864
71155
  "/projects/:name/discover/sessions/:id/promote",
70865
71156
  async (request, reply) => {
70866
71157
  const project = resolveProject(app.db, request.params.name);
70867
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71158
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70868
71159
  if (!session || session.projectId !== project.id) {
70869
71160
  throw notFound("Discovery session", request.params.id);
70870
71161
  }
70871
- const probeRows = app.db.select().from(discoveryProbes).where(eq96(discoveryProbes.sessionId, session.id)).all();
71162
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70872
71163
  const cited = /* @__PURE__ */ new Set();
70873
71164
  const aspirational = /* @__PURE__ */ new Set();
70874
71165
  const wasted = /* @__PURE__ */ new Set();
@@ -70903,7 +71194,7 @@ async function discoveryRoutes(app, opts) {
70903
71194
  );
70904
71195
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
70905
71196
  const project = resolveProject(app.db, request.params.name);
70906
- const session = app.db.select().from(discoverySessions).where(eq96(discoverySessions.id, request.params.id)).get();
71197
+ const session = app.db.select().from(discoverySessions).where(eq97(discoverySessions.id, request.params.id)).get();
70907
71198
  if (!session || session.projectId !== project.id) {
70908
71199
  throw notFound("Discovery session", request.params.id);
70909
71200
  }
@@ -70926,7 +71217,7 @@ async function discoveryRoutes(app, opts) {
70926
71217
  const bucketSet = new Set(buckets);
70927
71218
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
70928
71219
  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();
71220
+ const probeRows = app.db.select().from(discoveryProbes).where(eq97(discoveryProbes.sessionId, session.id)).all();
70930
71221
  const candidateQueries = /* @__PURE__ */ new Set();
70931
71222
  for (const probe of probeRows) {
70932
71223
  if (!probe.bucket) continue;
@@ -70934,7 +71225,7 @@ async function discoveryRoutes(app, opts) {
70934
71225
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
70935
71226
  }
70936
71227
  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())
71228
+ app.db.select({ query: queries.query }).from(queries).where(eq97(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
70938
71229
  );
70939
71230
  const promotedQueries = [];
70940
71231
  const skippedQueries = [];
@@ -71106,21 +71397,21 @@ function planCompetitorPromotion(input) {
71106
71397
  const preferred = new Set(input.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES);
71107
71398
  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
71399
  const tracked = new Set(input.tracked.map((row) => normalizeCompetitorDomain(row.domain)));
71109
- const skipped5 = [];
71400
+ const skipped6 = [];
71110
71401
  const candidates = [];
71111
71402
  for (const group of eligible) {
71112
71403
  const projectDomain = competitorDomainProjectClaim(group.domain, input.project.domains);
71113
71404
  const subdomains = [...new Set(group.sources.map((source) => hostOf(source.domain) ?? source.domain))].filter((host) => host !== group.domain);
71114
71405
  if (projectDomain) {
71115
- skipped5.push({
71406
+ skipped6.push({
71116
71407
  ...group,
71117
71408
  reason: DiscoveryPromoteCompetitorSkipReasons["project-domain"],
71118
71409
  message: `${group.domain} overlaps the project domain ${projectDomain}, so every citation of the project would count for it`
71119
71410
  });
71120
71411
  } else if (tracked.has(group.domain)) {
71121
- skipped5.push({ ...group, reason: DiscoveryPromoteCompetitorSkipReasons["already-tracked"], message: `${group.domain} is already tracked` });
71412
+ skipped6.push({ ...group, reason: DiscoveryPromoteCompetitorSkipReasons["already-tracked"], message: `${group.domain} is already tracked` });
71122
71413
  } else if (subdomains.length > 1) {
71123
- skipped5.push({
71414
+ skipped6.push({
71124
71415
  ...group,
71125
71416
  reason: DiscoveryPromoteCompetitorSkipReasons["shared-host"],
71126
71417
  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 +71425,7 @@ function planCompetitorPromotion(input) {
71134
71425
  for (const group of candidates) {
71135
71426
  const rejections = claimed.get(group.domain);
71136
71427
  if (rejections) {
71137
- skipped5.push({
71428
+ skipped6.push({
71138
71429
  ...group,
71139
71430
  reason: DiscoveryPromoteCompetitorSkipReasons["claimed-by-alias"],
71140
71431
  message: rejections.map((rejection) => `${group.domain} ${describeCompetitorAliasRejection(rejection)}`).join("; ")
@@ -71143,12 +71434,12 @@ function planCompetitorPromotion(input) {
71143
71434
  promote.push(group);
71144
71435
  }
71145
71436
  }
71146
- return { promote, skipped: skipped5 };
71437
+ return { promote, skipped: skipped6 };
71147
71438
  }
71148
71439
 
71149
71440
  // ../api-routes/src/discovery/orchestrate.ts
71150
71441
  import crypto55 from "crypto";
71151
- import { eq as eq97 } from "drizzle-orm";
71442
+ import { eq as eq98 } from "drizzle-orm";
71152
71443
  var DEFAULT_MAX_PROBES = 100;
71153
71444
  var ABSOLUTE_MAX_PROBES = 500;
71154
71445
  function classifyProbeBucket(input) {
@@ -71234,7 +71525,7 @@ async function executeDiscovery(opts) {
71234
71525
  status: DiscoverySessionStatuses.seeding,
71235
71526
  dedupThreshold,
71236
71527
  startedAt
71237
- }).where(eq97(discoverySessions.id, opts.sessionId)).run();
71528
+ }).where(eq98(discoverySessions.id, opts.sessionId)).run();
71238
71529
  const seedResult = await opts.deps.seed({
71239
71530
  project: opts.project,
71240
71531
  icpDescription: opts.icpDescription,
@@ -71295,7 +71586,7 @@ async function executeDiscovery(opts) {
71295
71586
  dedupBandPairFraction: dedupStats.bandPairFraction,
71296
71587
  dedupPairsTotal: dedupStats.pairsTotal,
71297
71588
  warning
71298
- }).where(eq97(discoverySessions.id, opts.sessionId)).run();
71589
+ }).where(eq98(discoverySessions.id, opts.sessionId)).run();
71299
71590
  const probeLocation = opts.locations?.[0];
71300
71591
  const probeResults = await mapWithConcurrency(
71301
71592
  probedCanonicals,
@@ -71349,7 +71640,7 @@ async function executeDiscovery(opts) {
71349
71640
  wastedCount: buckets["wasted-surface"],
71350
71641
  competitorMap,
71351
71642
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
71352
- }).where(eq97(discoverySessions.id, opts.sessionId)).run();
71643
+ }).where(eq98(discoverySessions.id, opts.sessionId)).run();
71353
71644
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
71354
71645
  return {
71355
71646
  buckets,
@@ -71389,7 +71680,7 @@ function markSessionFailed(db, sessionId, error) {
71389
71680
  status: DiscoverySessionStatuses.failed,
71390
71681
  error,
71391
71682
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
71392
- }).where(eq97(discoverySessions.id, sessionId)).run();
71683
+ }).where(eq98(discoverySessions.id, sessionId)).run();
71393
71684
  }
71394
71685
  function dedupeStrings(input) {
71395
71686
  const seen2 = /* @__PURE__ */ new Set();
@@ -71407,12 +71698,12 @@ function dedupeStrings(input) {
71407
71698
 
71408
71699
  // ../api-routes/src/technical-aeo.ts
71409
71700
  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";
71701
+ 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
71702
  import { alias } from "drizzle-orm/sqlite-core";
71412
71703
 
71413
71704
  // ../api-routes/src/technical-aeo-issues.ts
71414
71705
  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";
71706
+ import { and as and82, asc as asc21, eq as eq99, gt as gt10, sql as sql35 } from "drizzle-orm";
71416
71707
  var ADVISORY_CODES = /* @__PURE__ */ new Set([
71417
71708
  "entity-consistency.title.short",
71418
71709
  "site.duplicate-title",
@@ -71557,12 +71848,12 @@ function readSiteAuditIssues(input) {
71557
71848
  }
71558
71849
  }
71559
71850
  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)
71851
+ const scope = and82(
71852
+ eq99(siteCrawlPages.projectId, project.id),
71853
+ eq99(siteCrawlPages.runId, snapshot.runId),
71854
+ eq99(siteCrawlPages.attemptId, snapshot.attemptId)
71564
71855
  );
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;
71856
+ 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
71857
  if (size + crossBytes > MAX_EVIDENCE_BYTES) {
71567
71858
  throw validationError("Stored audit exceeds the 128 MiB issue-evidence read budget");
71568
71859
  }
@@ -71573,7 +71864,7 @@ function readSiteAuditIssues(input) {
71573
71864
  url: siteCrawlPages.url,
71574
71865
  auditScore: siteCrawlPages.auditScore,
71575
71866
  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();
71867
+ }).from(siteCrawlPages).where(and82(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc21(siteCrawlPages.nodeKey)).limit(200).all();
71577
71868
  for (const page3 of pages) {
71578
71869
  evidenceHash.update(JSON.stringify(page3));
71579
71870
  const fields = record(page3.auditFields);
@@ -71935,13 +72226,13 @@ function parseLinkKind(value) {
71935
72226
  return allowed.data;
71936
72227
  }
71937
72228
  function linkKindFilter(linkKind) {
71938
- if (linkKind === SiteHealthLinkKinds.content) return eq99(siteCrawlEdges.isTemplate, false);
71939
- if (linkKind === SiteHealthLinkKinds.template) return eq99(siteCrawlEdges.isTemplate, true);
72229
+ if (linkKind === SiteHealthLinkKinds.content) return eq100(siteCrawlEdges.isTemplate, false);
72230
+ if (linkKind === SiteHealthLinkKinds.template) return eq100(siteCrawlEdges.isTemplate, true);
71940
72231
  return void 0;
71941
72232
  }
71942
72233
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
71943
72234
  var graphTargetNode = alias(siteCrawlGraphNodes, "site_crawl_graph_target_node");
71944
- var PAGE_CHANGE_PREDICATE = sql35`(
72235
+ var PAGE_CHANGE_PREDICATE = sql36`(
71945
72236
  current.url IS NOT previous.url
71946
72237
  OR current.final_url IS NOT previous.final_url
71947
72238
  OR current.path IS NOT previous.path
@@ -71963,7 +72254,7 @@ var PAGE_CHANGE_PREDICATE = sql35`(
71963
72254
  OR current.link_score_raw IS NOT previous.link_score_raw
71964
72255
  OR current.link_score_normalized IS NOT previous.link_score_normalized
71965
72256
  )`;
71966
- var LINK_CHANGE_PREDICATE = sql35`(
72257
+ var LINK_CHANGE_PREDICATE = sql36`(
71967
72258
  current.source_node_key IS NOT previous.source_node_key
71968
72259
  OR current.source_url IS NOT previous.source_url
71969
72260
  OR current.target_node_key IS NOT previous.target_node_key
@@ -72040,20 +72331,20 @@ function changedFields(before, after, fields) {
72040
72331
  }
72041
72332
  async function technicalAeoRoutes(app, opts) {
72042
72333
  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),
72334
+ eq100(siteCrawlSnapshots.projectId, projectId),
72335
+ eq100(runs.projectId, projectId),
72336
+ eq100(runs.kind, RunKinds["site-audit"]),
72337
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72047
72338
  notProbeRun()
72048
72339
  ];
72049
72340
  const availableScanDatesFor = (projectId, requestedDate) => {
72050
72341
  if (!requestedDate) return void 0;
72051
- const date = sql35`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
72342
+ const date = sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
72052
72343
  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;
72344
+ const allDates = and83(...filters);
72345
+ const matchingDates = and83(...filters, eq100(sql36`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
72346
+ 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);
72347
+ 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
72348
  return {
72058
72349
  recentDates: readDates(allDates),
72059
72350
  totalDates: countDates(allDates),
@@ -72069,34 +72360,34 @@ async function technicalAeoRoutes(app, opts) {
72069
72360
  nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
72070
72361
  filters.push(gte27(siteCrawlSnapshots.createdAt, requestedDate), lt14(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
72071
72362
  }
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)
72363
+ const selectionFilters = scope.scoredOnly ? [...filters, sql36`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
72364
+ 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(
72365
+ desc48(sql36`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
72366
+ desc48(siteCrawlSnapshots.complete),
72367
+ desc48(siteCrawlSnapshots.pagesFetched),
72368
+ desc48(siteCrawlSnapshots.createdAt),
72369
+ desc48(siteCrawlSnapshots.runId)
72079
72370
  ).limit(1).get();
72080
72371
  if (!selected) return void 0;
72081
72372
  const date = selected.snapshot.createdAt.slice(0, 10);
72082
72373
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
72083
72374
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
72084
- const sameDate = and82(
72375
+ const sameDate = and83(
72085
72376
  ...filters,
72086
72377
  gte27(siteCrawlSnapshots.createdAt, date),
72087
72378
  lt14(siteCrawlSnapshots.createdAt, nextDate.toISOString())
72088
72379
  );
72089
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq99(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72380
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72090
72381
  const candidates = app.db.select({
72091
72382
  runId: siteCrawlSnapshots.runId,
72092
72383
  createdAt: siteCrawlSnapshots.createdAt,
72093
72384
  complete: siteCrawlSnapshots.complete,
72094
72385
  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)
72386
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
72387
+ desc48(siteCrawlSnapshots.complete),
72388
+ desc48(siteCrawlSnapshots.pagesFetched),
72389
+ desc48(siteCrawlSnapshots.createdAt),
72390
+ desc48(siteCrawlSnapshots.runId)
72100
72391
  ).limit(10).all();
72101
72392
  const runSelection = {
72102
72393
  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 +72401,20 @@ async function technicalAeoRoutes(app, opts) {
72110
72401
  };
72111
72402
  const resolveAudit = (projectId, runId, recordedBefore) => {
72112
72403
  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),
72404
+ eq100(siteAuditSnapshots.projectId, projectId),
72405
+ eq100(runs.projectId, projectId),
72406
+ eq100(runs.kind, RunKinds["site-audit"]),
72407
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72117
72408
  notProbeRun(),
72118
72409
  ...recordedBefore ? [lt14(siteAuditSnapshots.createdAt, recordedBefore)] : []
72119
72410
  ];
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)
72411
+ const complete = sql36`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
72412
+ 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(
72413
+ desc48(sql36`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
72414
+ desc48(complete),
72415
+ desc48(siteAuditSnapshots.pagesAudited),
72416
+ desc48(siteAuditSnapshots.createdAt),
72417
+ desc48(siteAuditSnapshots.runId)
72127
72418
  ).limit(1).get();
72128
72419
  const auditOfCrawl = (target) => {
72129
72420
  const selected2 = target ? selectAudit(target.snapshot.runId) : void 0;
@@ -72139,22 +72430,22 @@ async function technicalAeoRoutes(app, opts) {
72139
72430
  const date = selected.snap.createdAt.slice(0, 10);
72140
72431
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
72141
72432
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
72142
- const sameDate = and82(
72433
+ const sameDate = and83(
72143
72434
  ...filters,
72144
72435
  gte27(siteAuditSnapshots.createdAt, date),
72145
72436
  lt14(siteAuditSnapshots.createdAt, nextDate.toISOString())
72146
72437
  );
72147
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq99(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72438
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
72148
72439
  const candidates = app.db.select({
72149
72440
  runId: siteAuditSnapshots.runId,
72150
72441
  createdAt: siteAuditSnapshots.createdAt,
72151
72442
  complete,
72152
72443
  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)
72444
+ }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
72445
+ desc48(complete),
72446
+ desc48(siteAuditSnapshots.pagesAudited),
72447
+ desc48(siteAuditSnapshots.createdAt),
72448
+ desc48(siteAuditSnapshots.runId)
72158
72449
  ).limit(10).all().map((candidate) => ({ ...candidate, complete: Boolean(candidate.complete) }));
72159
72450
  const runSelection = {
72160
72451
  // A crawl exists here only when none stored a scorecard: this score is a fallback.
@@ -72169,9 +72460,9 @@ async function technicalAeoRoutes(app, opts) {
72169
72460
  };
72170
72461
  const detailScopeFor = (projectId, snapshot) => snapshot.detailsAvailable && snapshot.attemptId ? { projectId, runId: snapshot.runId, attemptId: snapshot.attemptId } : null;
72171
72462
  const crawlReasonGroups = (where) => {
72172
- const canonicalOther = sql35`case when ${siteCrawlPages.canonicalNodeKey} is not null
72463
+ const canonicalOther = sql36`case when ${siteCrawlPages.canonicalNodeKey} is not null
72173
72464
  and ${siteCrawlPages.canonicalNodeKey} <> ${siteCrawlPages.nodeKey} then 1 else 0 end`;
72174
- const canonicalReason = sql35`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
72465
+ const canonicalReason = sql36`exists (select 1 from json_each(${siteCrawlPages.indexabilityReasons})
72175
72466
  where value = ${SiteCrawlIndexabilityReasons.canonicalToOther})`;
72176
72467
  const groups = app.db.select({
72177
72468
  fetchState: siteCrawlPages.fetchState,
@@ -72180,7 +72471,7 @@ async function technicalAeoRoutes(app, opts) {
72180
72471
  canonicalOther,
72181
72472
  canonicalReason,
72182
72473
  pages: count8(),
72183
- exampleUrl: sql35`min(${siteCrawlPages.url})`
72474
+ exampleUrl: sql36`min(${siteCrawlPages.url})`
72184
72475
  }).from(siteCrawlPages).where(where).groupBy(
72185
72476
  siteCrawlPages.fetchState,
72186
72477
  siteCrawlPages.indexabilityState,
@@ -72216,10 +72507,10 @@ async function technicalAeoRoutes(app, opts) {
72216
72507
  };
72217
72508
  const inventorySummaryFor = (scope) => {
72218
72509
  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)
72510
+ const groups = crawlReasonGroups(and83(
72511
+ eq100(siteCrawlPages.projectId, scope.projectId),
72512
+ eq100(siteCrawlPages.runId, scope.runId),
72513
+ eq100(siteCrawlPages.attemptId, scope.attemptId)
72223
72514
  ));
72224
72515
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
72225
72516
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
@@ -72239,34 +72530,34 @@ async function technicalAeoRoutes(app, opts) {
72239
72530
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
72240
72531
  const pageInScope = (scope, selector) => {
72241
72532
  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),
72533
+ return app.db.select().from(siteCrawlPages).where(and83(
72534
+ eq100(siteCrawlPages.projectId, scope.projectId),
72535
+ eq100(siteCrawlPages.runId, scope.runId),
72536
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
72537
+ selector.nodeKey ? eq100(siteCrawlPages.nodeKey, selector.nodeKey) : eq100(siteCrawlPages.url, selector.url)
72538
+ )).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
72539
+ };
72540
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and83(
72541
+ eq100(siteCrawlPages.projectId, scope.projectId),
72542
+ eq100(siteCrawlPages.runId, scope.runId),
72543
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
72544
+ eq100(siteCrawlPages.depth, 0)
72545
+ )).orderBy(asc22(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
72546
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and83(
72547
+ eq100(runs.id, runId),
72548
+ eq100(runs.projectId, projectId),
72549
+ eq100(runs.kind, RunKinds["site-audit"]),
72550
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72260
72551
  notProbeRun()
72261
72552
  )).limit(1).get());
72262
72553
  const assertKnownAuditRun = (projectId, runId) => {
72263
72554
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
72264
72555
  };
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),
72556
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
72557
+ eq100(siteAuditSnapshots.projectId, projectId),
72558
+ eq100(runs.projectId, projectId),
72559
+ eq100(runs.kind, RunKinds["site-audit"]),
72560
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72270
72561
  notProbeRun()
72271
72562
  )).limit(1).get());
72272
72563
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -72367,14 +72658,14 @@ async function technicalAeoRoutes(app, opts) {
72367
72658
  };
72368
72659
  }
72369
72660
  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);
72661
+ const conds = [eq100(siteAuditPages.projectId, project.id), eq100(siteAuditPages.runId, latest.snap.runId)];
72662
+ if (statusFilter) conds.push(eq100(siteAuditPages.status, statusFilter));
72663
+ const where = and83(...conds);
72373
72664
  const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
72374
72665
  const total = totalRow?.value ?? 0;
72375
72666
  const limit = parsePositiveInt(request.query.limit, 100, 500);
72376
72667
  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);
72668
+ const orderBy = request.query.sort === "score-desc" ? desc48(siteAuditPages.overallScore) : request.query.sort === "url" ? asc22(siteAuditPages.url) : asc22(siteAuditPages.overallScore);
72378
72669
  const rows = app.db.select().from(siteAuditPages).where(where).orderBy(orderBy).limit(limit).offset(offset).all();
72379
72670
  const pages = rows.map((row) => ({
72380
72671
  url: row.url,
@@ -72393,13 +72684,13 @@ async function technicalAeoRoutes(app, opts) {
72393
72684
  auditedAt: siteAuditSnapshots.auditedAt,
72394
72685
  aggregateScore: siteAuditSnapshots.aggregateScore,
72395
72686
  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),
72687
+ }).from(siteAuditSnapshots).innerJoin(runs, eq100(siteAuditSnapshots.runId, runs.id)).where(and83(
72688
+ eq100(siteAuditSnapshots.projectId, project.id),
72689
+ eq100(runs.projectId, project.id),
72690
+ eq100(runs.kind, RunKinds["site-audit"]),
72691
+ inArray44(runs.status, SURFACEABLE_STATUSES),
72401
72692
  notProbeRun()
72402
- )).orderBy(desc47(siteAuditSnapshots.createdAt)).limit(limit).all();
72693
+ )).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
72403
72694
  return { project: project.name, points: rows.reverse() };
72404
72695
  });
72405
72696
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -72499,10 +72790,10 @@ async function technicalAeoRoutes(app, opts) {
72499
72790
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
72500
72791
  snapshot.rootUrl
72501
72792
  )?.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)
72793
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and83(
72794
+ eq100(siteCrawlGraphLayouts.projectId, project.id),
72795
+ eq100(siteCrawlGraphLayouts.runId, snapshot.runId),
72796
+ eq100(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
72506
72797
  )).limit(1).get();
72507
72798
  if (!persistedLayout) {
72508
72799
  return {
@@ -72555,9 +72846,9 @@ async function technicalAeoRoutes(app, opts) {
72555
72846
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
72556
72847
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
72557
72848
  const graphScope = [
72558
- eq99(siteCrawlGraphNodes.projectId, project.id),
72559
- eq99(siteCrawlGraphNodes.runId, snapshot.runId),
72560
- eq99(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
72849
+ eq100(siteCrawlGraphNodes.projectId, project.id),
72850
+ eq100(siteCrawlGraphNodes.runId, snapshot.runId),
72851
+ eq100(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
72561
72852
  ];
72562
72853
  const nodeRows = app.db.select({
72563
72854
  nodeKey: siteCrawlPages.nodeKey,
@@ -72576,18 +72867,18 @@ async function technicalAeoRoutes(app, opts) {
72576
72867
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
72577
72868
  x: siteCrawlGraphNodes.x,
72578
72869
  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();
72870
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and83(
72871
+ eq100(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
72872
+ eq100(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
72873
+ eq100(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
72874
+ eq100(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
72875
+ )).where(and83(...graphScope, lt14(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc22(siteCrawlGraphNodes.sampleRank)).all();
72585
72876
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
72586
72877
  ...row,
72587
72878
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
72588
72879
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
72589
72880
  }));
72590
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq99(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
72881
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq100(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
72591
72882
  const edges = app.db.select({
72592
72883
  edgeKey: siteCrawlGraphEdges.edgeKey,
72593
72884
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -72595,25 +72886,25 @@ async function technicalAeoRoutes(app, opts) {
72595
72886
  followable: siteCrawlGraphEdges.followable,
72596
72887
  occurrences: siteCrawlGraphEdges.occurrences,
72597
72888
  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),
72889
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and83(
72890
+ eq100(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
72891
+ eq100(graphSourceNode.runId, siteCrawlGraphEdges.runId),
72892
+ eq100(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
72893
+ eq100(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
72603
72894
  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),
72895
+ )).innerJoin(graphTargetNode, and83(
72896
+ eq100(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
72897
+ eq100(graphTargetNode.runId, siteCrawlGraphEdges.runId),
72898
+ eq100(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
72899
+ eq100(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
72609
72900
  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),
72901
+ )).where(and83(
72902
+ eq100(siteCrawlGraphEdges.projectId, project.id),
72903
+ eq100(siteCrawlGraphEdges.runId, snapshot.runId),
72904
+ eq100(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
72614
72905
  lt14(siteCrawlGraphEdges.sampleRank, maxEdges),
72615
72906
  graphLinkKindFilter
72616
- )).orderBy(asc21(siteCrawlGraphEdges.sampleRank)).all();
72907
+ )).orderBy(asc22(siteCrawlGraphEdges.sampleRank)).all();
72617
72908
  const totalNodes = persistedLayout.totalNodes;
72618
72909
  const totalEdges = persistedLayout.totalEdges;
72619
72910
  const totalTemplateEdges = persistedLayout.totalTemplateEdges;
@@ -72705,17 +72996,17 @@ async function technicalAeoRoutes(app, opts) {
72705
72996
  let frontier = [focus.nodeKey];
72706
72997
  let queryTruncated = false;
72707
72998
  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),
72999
+ const candidates = app.db.select().from(siteCrawlEdges).where(and83(
73000
+ eq100(siteCrawlEdges.projectId, scope.projectId),
73001
+ eq100(siteCrawlEdges.runId, scope.runId),
73002
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
73003
+ eq100(siteCrawlEdges.internal, true),
72713
73004
  isNotNull10(siteCrawlEdges.targetNodeKey),
72714
73005
  or21(
72715
- inArray43(siteCrawlEdges.sourceNodeKey, frontier),
72716
- inArray43(siteCrawlEdges.targetNodeKey, frontier)
73006
+ inArray44(siteCrawlEdges.sourceNodeKey, frontier),
73007
+ inArray44(siteCrawlEdges.targetNodeKey, frontier)
72717
73008
  )
72718
- )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
73009
+ )).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
72719
73010
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
72720
73011
  const next = /* @__PURE__ */ new Set();
72721
73012
  for (const edge of candidates.slice(0, maxEdges + maxNodes)) {
@@ -72746,22 +73037,22 @@ async function technicalAeoRoutes(app, opts) {
72746
73037
  frontier = [...next].sort();
72747
73038
  }
72748
73039
  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)
73040
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
73041
+ eq100(siteCrawlPages.projectId, scope.projectId),
73042
+ eq100(siteCrawlPages.runId, scope.runId),
73043
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73044
+ inArray44(siteCrawlPages.nodeKey, nodeKeys)
72754
73045
  )).all();
72755
73046
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
72756
73047
  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),
73048
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and83(
73049
+ eq100(siteCrawlEdges.projectId, scope.projectId),
73050
+ eq100(siteCrawlEdges.runId, scope.runId),
73051
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
73052
+ eq100(siteCrawlEdges.internal, true),
72762
73053
  isNotNull10(siteCrawlEdges.targetNodeKey),
72763
- inArray43(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
72764
- inArray43(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
73054
+ inArray44(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
73055
+ inArray44(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
72765
73056
  )).limit(1).get();
72766
73057
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
72767
73058
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -72854,23 +73145,23 @@ async function technicalAeoRoutes(app, opts) {
72854
73145
  let found = fromPage.nodeKey === toPage.nodeKey;
72855
73146
  let truncated = false;
72856
73147
  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"),
73148
+ const candidates = app.db.select().from(siteCrawlEdges).where(and83(
73149
+ eq100(siteCrawlEdges.projectId, scope.projectId),
73150
+ eq100(siteCrawlEdges.runId, scope.runId),
73151
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
73152
+ eq100(siteCrawlEdges.internal, true),
73153
+ eq100(siteCrawlEdges.followable, true),
73154
+ eq100(siteCrawlEdges.relation, "anchor"),
72864
73155
  isNotNull10(siteCrawlEdges.targetNodeKey),
72865
- inArray43(siteCrawlEdges.sourceNodeKey, frontier)
72866
- )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
73156
+ inArray44(siteCrawlEdges.sourceNodeKey, frontier)
73157
+ )).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
72867
73158
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
72868
73159
  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)
73160
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and83(
73161
+ eq100(siteCrawlPages.projectId, scope.projectId),
73162
+ eq100(siteCrawlPages.runId, scope.runId),
73163
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73164
+ inArray44(siteCrawlPages.nodeKey, candidateTargetKeys)
72874
73165
  )).all().map((row) => row.nodeKey));
72875
73166
  const next = /* @__PURE__ */ new Set();
72876
73167
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -72911,11 +73202,11 @@ async function technicalAeoRoutes(app, opts) {
72911
73202
  pathEdges.unshift(step.edge);
72912
73203
  pathKeys.unshift(step.nodeKey);
72913
73204
  }
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)
73205
+ const pathRows = app.db.select().from(siteCrawlPages).where(and83(
73206
+ eq100(siteCrawlPages.projectId, scope.projectId),
73207
+ eq100(siteCrawlPages.runId, scope.runId),
73208
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73209
+ inArray44(siteCrawlPages.nodeKey, pathKeys)
72919
73210
  )).all();
72920
73211
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
72921
73212
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -72943,25 +73234,25 @@ async function technicalAeoRoutes(app, opts) {
72943
73234
  throw validationError("change must be all, added, removed, or changed");
72944
73235
  }
72945
73236
  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),
73237
+ eq100(siteCrawlSnapshots.projectId, project.id),
73238
+ eq100(runs.projectId, project.id),
73239
+ eq100(runs.kind, RunKinds["site-audit"]),
73240
+ eq100(runs.status, RunStatuses.completed),
73241
+ eq100(siteCrawlSnapshots.complete, true),
72951
73242
  notProbeRun()
72952
73243
  ];
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();
73244
+ 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
73245
  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(
73246
+ 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
73247
  ...completeSnapshotFilters,
72957
73248
  or21(
72958
73249
  lt14(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
72959
- and82(
72960
- eq99(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
73250
+ and83(
73251
+ eq100(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
72961
73252
  lt14(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
72962
73253
  )
72963
73254
  )
72964
- )).orderBy(desc47(siteCrawlSnapshots.createdAt), desc47(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
73255
+ )).orderBy(desc48(siteCrawlSnapshots.createdAt), desc48(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
72965
73256
  if (request.query.fromRunId && !beforeTarget) throw notFound("Site crawl run", request.query.fromRunId);
72966
73257
  if (!afterTarget) {
72967
73258
  const latestCrawl = resolveCrawl(project.id);
@@ -73045,9 +73336,9 @@ async function technicalAeoRoutes(app, opts) {
73045
73336
  const summary4 = { pages: zero2(), links: zero2() };
73046
73337
  if (scopeFilter !== "links") {
73047
73338
  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`
73339
+ const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
73340
+ 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`;
73341
+ const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
73051
73342
  SELECT ${addedExpression} AS added, ${changedExpression} AS changed
73052
73343
  FROM site_crawl_pages AS current
73053
73344
  LEFT JOIN site_crawl_pages AS previous
@@ -73063,7 +73354,7 @@ async function technicalAeoRoutes(app, opts) {
73063
73354
  summary4.pages.changed = Number(counts2.changed);
73064
73355
  }
73065
73356
  if (includeRemoved) {
73066
- const [counts2 = { removed: 0 }] = app.db.all(sql35`
73357
+ const [counts2 = { removed: 0 }] = app.db.all(sql36`
73067
73358
  SELECT COUNT(*) AS removed
73068
73359
  FROM site_crawl_pages AS previous
73069
73360
  LEFT JOIN site_crawl_pages AS current
@@ -73081,9 +73372,9 @@ async function technicalAeoRoutes(app, opts) {
73081
73372
  }
73082
73373
  if (scopeFilter !== "pages") {
73083
73374
  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`
73375
+ const addedExpression = includeAdded ? sql36`COALESCE(SUM(CASE WHEN previous.id IS NULL THEN 1 ELSE 0 END), 0)` : sql36`0`;
73376
+ 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`;
73377
+ const [counts2 = { added: 0, changed: 0 }] = app.db.all(sql36`
73087
73378
  SELECT ${addedExpression} AS added, ${changedExpression} AS changed
73088
73379
  FROM site_crawl_edges AS current
73089
73380
  LEFT JOIN site_crawl_edges AS previous
@@ -73101,7 +73392,7 @@ async function technicalAeoRoutes(app, opts) {
73101
73392
  summary4.links.changed = Number(counts2.changed);
73102
73393
  }
73103
73394
  if (includeRemoved) {
73104
- const [counts2 = { removed: 0 }] = app.db.all(sql35`
73395
+ const [counts2 = { removed: 0 }] = app.db.all(sql36`
73105
73396
  SELECT COUNT(*) AS removed
73106
73397
  FROM site_crawl_edges AS previous
73107
73398
  LEFT JOIN site_crawl_edges AS current
@@ -73131,48 +73422,48 @@ async function technicalAeoRoutes(app, opts) {
73131
73422
  const includeRemoved = changeFilter === "all" || changeFilter === "removed";
73132
73423
  const table = entity === "page" ? "site_crawl_pages" : "site_crawl_edges";
73133
73424
  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``;
73425
+ const internalCurrent = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
73426
+ const internalPreviousJoin = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
73427
+ const internalPrevious = entity === "link" ? sql36`AND previous.internal = 1` : sql36``;
73428
+ const internalCurrentJoin = entity === "link" ? sql36`AND current.internal = 1` : sql36``;
73138
73429
  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`
73430
+ 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})`;
73431
+ const afterRows = includeAfter ? app.db.all(sql36`
73141
73432
  SELECT ${entity} AS entity,
73142
73433
  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
73434
+ current.${sql36.raw(keyColumn)} AS key
73435
+ FROM ${sql36.raw(table)} AS current
73436
+ LEFT JOIN ${sql36.raw(table)} AS previous
73146
73437
  ON previous.project_id = ${beforeScope.projectId}
73147
73438
  AND previous.run_id = ${beforeScope.runId}
73148
73439
  AND previous.attempt_id = ${beforeScope.attemptId}
73149
73440
  ${internalPreviousJoin}
73150
- AND previous.${sql35.raw(keyColumn)} = current.${sql35.raw(keyColumn)}
73441
+ AND previous.${sql36.raw(keyColumn)} = current.${sql36.raw(keyColumn)}
73151
73442
  WHERE current.project_id = ${afterScope.projectId}
73152
73443
  AND current.run_id = ${afterScope.runId}
73153
73444
  AND current.attempt_id = ${afterScope.attemptId}
73154
73445
  ${internalCurrent}
73155
- AND current.${sql35.raw(keyColumn)} > ${afterKey}
73446
+ AND current.${sql36.raw(keyColumn)} > ${afterKey}
73156
73447
  AND (${afterKind})
73157
- ORDER BY current.${sql35.raw(keyColumn)} ASC
73448
+ ORDER BY current.${sql36.raw(keyColumn)} ASC
73158
73449
  LIMIT ${wanted2}
73159
73450
  `) : [];
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
73451
+ const removedRows = includeRemoved ? app.db.all(sql36`
73452
+ SELECT ${entity} AS entity, 'removed' AS change, previous.${sql36.raw(keyColumn)} AS key
73453
+ FROM ${sql36.raw(table)} AS previous
73454
+ LEFT JOIN ${sql36.raw(table)} AS current
73164
73455
  ON current.project_id = ${afterScope.projectId}
73165
73456
  AND current.run_id = ${afterScope.runId}
73166
73457
  AND current.attempt_id = ${afterScope.attemptId}
73167
73458
  ${internalCurrentJoin}
73168
- AND current.${sql35.raw(keyColumn)} = previous.${sql35.raw(keyColumn)}
73459
+ AND current.${sql36.raw(keyColumn)} = previous.${sql36.raw(keyColumn)}
73169
73460
  WHERE previous.project_id = ${beforeScope.projectId}
73170
73461
  AND previous.run_id = ${beforeScope.runId}
73171
73462
  AND previous.attempt_id = ${beforeScope.attemptId}
73172
73463
  ${internalPrevious}
73173
- AND previous.${sql35.raw(keyColumn)} > ${afterKey}
73464
+ AND previous.${sql36.raw(keyColumn)} > ${afterKey}
73174
73465
  AND current.id IS NULL
73175
- ORDER BY previous.${sql35.raw(keyColumn)} ASC
73466
+ ORDER BY previous.${sql36.raw(keyColumn)} ASC
73176
73467
  LIMIT ${wanted2}
73177
73468
  `) : [];
73178
73469
  const merged = [];
@@ -73202,18 +73493,18 @@ async function technicalAeoRoutes(app, opts) {
73202
73493
  const visibleKeys = keyRows.slice(0, limit);
73203
73494
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
73204
73495
  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)
73496
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and83(
73497
+ eq100(siteCrawlPages.projectId, scope.projectId),
73498
+ eq100(siteCrawlPages.runId, scope.runId),
73499
+ eq100(siteCrawlPages.attemptId, scope.attemptId),
73500
+ inArray44(siteCrawlPages.nodeKey, pageKeys)
73210
73501
  )).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)
73502
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and83(
73503
+ eq100(siteCrawlEdges.projectId, scope.projectId),
73504
+ eq100(siteCrawlEdges.runId, scope.runId),
73505
+ eq100(siteCrawlEdges.attemptId, scope.attemptId),
73506
+ eq100(siteCrawlEdges.internal, true),
73507
+ inArray44(siteCrawlEdges.edgeKey, linkKeys)
73217
73508
  )).all();
73218
73509
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
73219
73510
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -73386,16 +73677,16 @@ async function technicalAeoRoutes(app, opts) {
73386
73677
  }
73387
73678
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
73388
73679
  const filters = [
73389
- eq99(siteCrawlPages.projectId, project.id),
73390
- eq99(siteCrawlPages.runId, snapshot.runId),
73391
- eq99(siteCrawlPages.attemptId, snapshot.attemptId)
73680
+ eq100(siteCrawlPages.projectId, project.id),
73681
+ eq100(siteCrawlPages.runId, snapshot.runId),
73682
+ eq100(siteCrawlPages.attemptId, snapshot.attemptId)
73392
73683
  ];
73393
- if (request.query.nodeKey) filters.push(eq99(siteCrawlPages.nodeKey, request.query.nodeKey));
73684
+ if (request.query.nodeKey) filters.push(eq100(siteCrawlPages.nodeKey, request.query.nodeKey));
73394
73685
  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));
73686
+ if (inventoryEligible != null) filters.push(eq100(siteCrawlPages.inventoryEligible, inventoryEligible));
73687
+ if (request.query.fetchState) filters.push(eq100(siteCrawlPages.fetchState, request.query.fetchState));
73688
+ if (request.query.indexabilityState) filters.push(eq100(siteCrawlPages.indexabilityState, request.query.indexabilityState));
73689
+ if (request.query.auditState) filters.push(eq100(siteCrawlPages.auditState, request.query.auditState));
73399
73690
  const healthState = parseSiteHealthState(request.query.healthState);
73400
73691
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
73401
73692
  const sort = ["score-desc", "score-asc", "path"].includes(request.query.sort ?? "") ? request.query.sort : "url";
@@ -73413,17 +73704,17 @@ async function technicalAeoRoutes(app, opts) {
73413
73704
  sort
73414
73705
  })).digest("base64url");
73415
73706
  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)];
73707
+ 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
73708
  let healthStateFilter = null;
73418
73709
  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),
73710
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and83(
73711
+ eq100(siteCrawlPages.projectId, project.id),
73712
+ eq100(siteCrawlPages.runId, snapshot.runId),
73713
+ eq100(siteCrawlPages.attemptId, snapshot.attemptId),
73423
73714
  isNull17(siteCrawlPages.healthState)
73424
73715
  )).limit(1).get();
73425
73716
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
73426
- if (healthStateFilter === "applied") filters.push(eq99(siteCrawlPages.healthState, healthState));
73717
+ if (healthStateFilter === "applied") filters.push(eq100(siteCrawlPages.healthState, healthState));
73427
73718
  }
73428
73719
  if (healthStateFilter === "unavailable-legacy-scan") {
73429
73720
  return {
@@ -73440,7 +73731,7 @@ async function technicalAeoRoutes(app, opts) {
73440
73731
  pages: []
73441
73732
  };
73442
73733
  }
73443
- const where = and82(...filters);
73734
+ const where = and83(...filters);
73444
73735
  const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
73445
73736
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
73446
73737
  const nextOffset = offset + rows.length;
@@ -73475,10 +73766,10 @@ async function technicalAeoRoutes(app, opts) {
73475
73766
  url: siteCrawlPages.url,
73476
73767
  inventoryEligible: siteCrawlPages.inventoryEligible,
73477
73768
  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)
73769
+ }).from(siteCrawlPages).where(and83(
73770
+ eq100(siteCrawlPages.projectId, project.id),
73771
+ eq100(siteCrawlPages.runId, snapshot.runId),
73772
+ eq100(siteCrawlPages.attemptId, snapshot.attemptId)
73482
73773
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
73483
73774
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
73484
73775
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -73560,21 +73851,21 @@ async function technicalAeoRoutes(app, opts) {
73560
73851
  };
73561
73852
  }
73562
73853
  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),
73854
+ eq100(siteCrawlEdges.projectId, project.id),
73855
+ eq100(siteCrawlEdges.runId, snapshot.runId),
73856
+ eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
73857
+ eq100(siteCrawlEdges.internal, true),
73567
73858
  linkKindFilter(linkKind)
73568
73859
  ];
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));
73860
+ if (request.query.sourceUrl) filters.push(eq100(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
73861
+ if (request.query.targetUrl) filters.push(eq100(siteCrawlEdges.targetUrl, request.query.targetUrl));
73571
73862
  const followable = parseBoolean2(request.query.followable);
73572
- if (followable != null) filters.push(eq99(siteCrawlEdges.followable, followable));
73573
- const where = and82(...filters);
73863
+ if (followable != null) filters.push(eq100(siteCrawlEdges.followable, followable));
73864
+ const where = and83(...filters);
73574
73865
  const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
73575
73866
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
73576
73867
  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();
73868
+ const rows = app.db.select().from(siteCrawlEdges).where(where).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit).offset(offset).all();
73578
73869
  const nextOffset = offset + rows.length;
73579
73870
  return {
73580
73871
  project: project.name,
@@ -73631,17 +73922,17 @@ async function technicalAeoRoutes(app, opts) {
73631
73922
  };
73632
73923
  }
73633
73924
  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),
73925
+ eq100(siteCrawlEdges.projectId, project.id),
73926
+ eq100(siteCrawlEdges.runId, snapshot.runId),
73927
+ eq100(siteCrawlEdges.attemptId, snapshot.attemptId),
73928
+ eq100(siteCrawlEdges.internal, true),
73638
73929
  linkKindFilter(linkKind)
73639
73930
  ];
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);
73931
+ 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);
73932
+ 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
73933
  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();
73934
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, inboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
73935
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and83(...scope, outboundMatch)).orderBy(asc22(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
73645
73936
  return {
73646
73937
  project: project.name,
73647
73938
  hasCrawlData: true,
@@ -73675,11 +73966,11 @@ async function technicalAeoRoutes(app, opts) {
73675
73966
  if (!snapshot.attemptId) {
73676
73967
  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
73968
  }
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"),
73969
+ const where = and83(
73970
+ eq100(siteCrawlFindings.projectId, project.id),
73971
+ eq100(siteCrawlFindings.runId, snapshot.runId),
73972
+ eq100(siteCrawlFindings.attemptId, snapshot.attemptId),
73973
+ eq100(siteCrawlFindings.findingType, "dead-link"),
73683
73974
  // Belt and braces on the client-facing surface: a row without a status
73684
73975
  // code is not evidence of a broken link, and this response is where such
73685
73976
  // a row would be read as one. Migration 140 removed every stored row that
@@ -73689,12 +73980,12 @@ async function technicalAeoRoutes(app, opts) {
73689
73980
  // a fabricated broken link shown to a client is not worth the saved
73690
73981
  // predicate. Applied to `total` as well as the rows, or the count would
73691
73982
  // promise a page the list cannot fill.
73692
- isNotNull10(sql35`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
73983
+ isNotNull10(sql36`json_extract(${siteCrawlFindings.evidence}, '$.statusCode')`)
73693
73984
  );
73694
73985
  const total = app.db.select({ value: count8() }).from(siteCrawlFindings).where(where).get()?.value ?? 0;
73695
73986
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
73696
73987
  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();
73988
+ const rows = app.db.select().from(siteCrawlFindings).where(where).orderBy(asc22(siteCrawlFindings.findingKey)).limit(limit).offset(offset).all();
73698
73989
  const nextOffset = offset + rows.length;
73699
73990
  const deadLinks = rows.map((row) => ({
73700
73991
  findingKey: row.findingKey,
@@ -73729,19 +74020,19 @@ async function technicalAeoRoutes(app, opts) {
73729
74020
  createdAt: runs.createdAt,
73730
74021
  startedAt: runs.startedAt,
73731
74022
  finishedAt: runs.finishedAt
73732
- }).from(runs).where(and82(
73733
- eq99(runs.projectId, project.id),
73734
- eq99(runs.kind, RunKinds["site-audit"]),
74023
+ }).from(runs).where(and83(
74024
+ eq100(runs.projectId, project.id),
74025
+ eq100(runs.kind, RunKinds["site-audit"]),
73735
74026
  notProbeRun()
73736
- )).orderBy(desc47(runs.createdAt), desc47(runs.id)).limit(limit).all();
74027
+ )).orderBy(desc48(runs.createdAt), desc48(runs.id)).limit(limit).all();
73737
74028
  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),
74029
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq100(siteCrawlSnapshots.runId, runs.id)).where(and83(
74030
+ eq100(siteCrawlSnapshots.projectId, project.id),
74031
+ eq100(runs.projectId, project.id),
74032
+ eq100(runs.kind, RunKinds["site-audit"]),
74033
+ inArray44(runs.status, SURFACEABLE_STATUSES),
73743
74034
  notProbeRun(),
73744
- inArray43(siteCrawlSnapshots.runId, runIds)
74035
+ inArray44(siteCrawlSnapshots.runId, runIds)
73745
74036
  )).all().map((row) => row.runId));
73746
74037
  return {
73747
74038
  project: project.name,
@@ -73755,21 +74046,21 @@ async function technicalAeoRoutes(app, opts) {
73755
74046
  });
73756
74047
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
73757
74048
  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"]),
74049
+ const run = app.db.select().from(runs).where(and83(
74050
+ eq100(runs.id, request.params.runId),
74051
+ eq100(runs.projectId, project.id),
74052
+ eq100(runs.kind, RunKinds["site-audit"]),
73762
74053
  notProbeRun()
73763
74054
  )).get();
73764
74055
  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)
74056
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and83(
74057
+ eq100(siteCrawlAttempts.projectId, project.id),
74058
+ eq100(siteCrawlAttempts.runId, run.id)
74059
+ )).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
74060
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and83(
74061
+ eq100(siteCrawlGraphLayouts.projectId, project.id),
74062
+ eq100(siteCrawlGraphLayouts.runId, run.id),
74063
+ eq100(siteCrawlGraphLayouts.attemptId, attempt.id)
73773
74064
  )).get() : void 0;
73774
74065
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
73775
74066
  return {
@@ -73800,19 +74091,19 @@ async function technicalAeoRoutes(app, opts) {
73800
74091
  };
73801
74092
  });
73802
74093
  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();
74094
+ const project = tx.select().from(projects).where(eq100(projects.name, request.params.name)).get();
73804
74095
  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"]),
74096
+ const run = tx.select().from(runs).where(and83(
74097
+ eq100(runs.id, request.params.runId),
74098
+ eq100(runs.projectId, project.id),
74099
+ eq100(runs.kind, RunKinds["site-audit"]),
73809
74100
  notProbeRun()
73810
74101
  )).get();
73811
74102
  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();
74103
+ const attempt = tx.select().from(siteCrawlAttempts).where(and83(
74104
+ eq100(siteCrawlAttempts.projectId, project.id),
74105
+ eq100(siteCrawlAttempts.runId, run.id)
74106
+ )).orderBy(desc48(siteCrawlAttempts.attemptNumber), desc48(siteCrawlAttempts.updatedAt)).limit(1).get();
73816
74107
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
73817
74108
  const base = {
73818
74109
  project: project.name,
@@ -73825,11 +74116,11 @@ async function technicalAeoRoutes(app, opts) {
73825
74116
  if (!attempt) {
73826
74117
  return { ...base, pagesAudited: 0, examples: [] };
73827
74118
  }
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")
74119
+ const auditedWhere = and83(
74120
+ eq100(siteCrawlPages.projectId, project.id),
74121
+ eq100(siteCrawlPages.runId, run.id),
74122
+ eq100(siteCrawlPages.attemptId, attempt.id),
74123
+ eq100(siteCrawlPages.auditState, "success")
73833
74124
  );
73834
74125
  const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
73835
74126
  if (state !== "collecting") {
@@ -73840,11 +74131,11 @@ async function technicalAeoRoutes(app, opts) {
73840
74131
  url: siteCrawlPages.url,
73841
74132
  auditScore: siteCrawlPages.auditScore,
73842
74133
  auditFields: siteCrawlPages.auditFields
73843
- }).from(siteCrawlPages).where(and82(
74134
+ }).from(siteCrawlPages).where(and83(
73844
74135
  auditedWhere,
73845
74136
  isNotNull10(siteCrawlPages.auditScore),
73846
74137
  lt14(siteCrawlPages.auditScore, 70)
73847
- )).orderBy(asc21(siteCrawlPages.auditScore), asc21(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
74138
+ )).orderBy(asc22(siteCrawlPages.auditScore), asc22(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
73848
74139
  const examples = candidates.flatMap((candidate) => {
73849
74140
  if (candidate.auditScore == null) return [];
73850
74141
  const checksNeedingAttention = countLivePageHealthChecks(candidate.auditFields);
@@ -73876,13 +74167,13 @@ async function technicalAeoRoutes(app, opts) {
73876
74167
  status: runs.status,
73877
74168
  identityKey: siteCrawlRunRequests.identityKey,
73878
74169
  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])
74170
+ }).from(runs).leftJoin(siteCrawlRunRequests, and83(
74171
+ eq100(siteCrawlRunRequests.projectId, runs.projectId),
74172
+ eq100(siteCrawlRunRequests.runId, runs.id)
74173
+ )).where(and83(
74174
+ eq100(runs.projectId, project.id),
74175
+ eq100(runs.kind, RunKinds["site-audit"]),
74176
+ inArray44(runs.status, [RunStatuses.queued, RunStatuses.running])
73886
74177
  )).get();
73887
74178
  if (existing) {
73888
74179
  if (existing.identityKey === identityKey) {
@@ -73938,7 +74229,7 @@ async function technicalAeoRoutes(app, opts) {
73938
74229
  // ../api-routes/src/research.ts
73939
74230
  import crypto57 from "crypto";
73940
74231
  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";
74232
+ 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
74233
  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
74234
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
73944
74235
  async function researchRoutes(app, opts) {
@@ -73957,7 +74248,7 @@ async function researchRoutes(app, opts) {
73957
74248
  const input = parsed.data;
73958
74249
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
73959
74250
  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;
74251
+ 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
74252
  if (existingReceipt) {
73962
74253
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
73963
74254
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -73992,7 +74283,7 @@ async function researchRoutes(app, opts) {
73992
74283
  const initiatedBy = researchPrincipal(request);
73993
74284
  const decision = app.db.transaction((tx) => {
73994
74285
  if (input.idempotencyKey) {
73995
- const existing = tx.select().from(researchRuns).where(and83(eq100(researchRuns.projectId, project.id), eq100(researchRuns.idempotencyKey, input.idempotencyKey))).get();
74286
+ const existing = tx.select().from(researchRuns).where(and84(eq101(researchRuns.projectId, project.id), eq101(researchRuns.idempotencyKey, input.idempotencyKey))).get();
73996
74287
  if (existing) {
73997
74288
  assertSameDirectResearchRequest(existing, input, requestHash2);
73998
74289
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -74000,11 +74291,11 @@ async function researchRoutes(app, opts) {
74000
74291
  }
74001
74292
  if (initiatedBy?.limited) {
74002
74293
  const { start, end, date } = utcDayBounds(now);
74003
- const used = tx.select({ value: count9() }).from(researchRuns).where(and83(
74004
- eq100(researchRuns.projectId, project.id),
74294
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
74295
+ eq101(researchRuns.projectId, project.id),
74005
74296
  gte28(researchRuns.createdAt, start),
74006
74297
  lt15(researchRuns.createdAt, end),
74007
- sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74298
+ sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74008
74299
  )).get()?.value ?? 0;
74009
74300
  if (used >= viewerDailyRunLimit) {
74010
74301
  throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
@@ -74032,15 +74323,15 @@ async function researchRoutes(app, opts) {
74032
74323
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
74033
74324
  const requestHash2 = researchBatchRequestHash(input);
74034
74325
  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])
74326
+ const firstReceipt = app.db.select().from(researchRuns).where(and84(
74327
+ eq101(researchRuns.projectId, project.id),
74328
+ eq101(researchRuns.idempotencyKey, receiptKeys[0])
74038
74329
  )).get();
74039
74330
  if (firstReceipt) {
74040
74331
  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)
74332
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and84(
74333
+ eq101(researchRuns.projectId, project.id),
74334
+ eq101(researchRuns.idempotencyKey, key)
74044
74335
  )).get());
74045
74336
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
74046
74337
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -74056,11 +74347,11 @@ async function researchRoutes(app, opts) {
74056
74347
  const decision = app.db.transaction((tx) => {
74057
74348
  if (initiatedBy?.limited) {
74058
74349
  const { start, end, date } = utcDayBounds(now);
74059
- const used = tx.select({ value: count9() }).from(researchRuns).where(and83(
74060
- eq100(researchRuns.projectId, project.id),
74350
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and84(
74351
+ eq101(researchRuns.projectId, project.id),
74061
74352
  gte28(researchRuns.createdAt, start),
74062
74353
  lt15(researchRuns.createdAt, end),
74063
- sql36`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74354
+ sql37`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
74064
74355
  )).get()?.value ?? 0;
74065
74356
  if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
74066
74357
  }
@@ -74111,10 +74402,10 @@ async function researchRoutes(app, opts) {
74111
74402
  const requested = Number.parseInt(request.query.limit ?? "", 10);
74112
74403
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
74113
74404
  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();
74405
+ const rows = app.db.select().from(researchRuns).where(and84(
74406
+ eq101(researchRuns.projectId, project.id),
74407
+ cursor ? or22(lt15(researchRuns.createdAt, cursor.createdAt), and84(eq101(researchRuns.createdAt, cursor.createdAt), lt15(researchRuns.id, cursor.id))) : void 0
74408
+ )).orderBy(desc49(researchRuns.createdAt), desc49(researchRuns.id)).limit(limit + 1).all();
74118
74409
  const runs2 = rows.slice(0, limit).map(serializeRun);
74119
74410
  const last = runs2.at(-1);
74120
74411
  const nextCursor = rows.length > limit && last ? Buffer.from(JSON.stringify({ projectId: project.id, createdAt: last.createdAt, id: last.id })).toString("base64url") : null;
@@ -74263,9 +74554,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
74263
74554
  return stored;
74264
74555
  }
74265
74556
  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)
74557
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and84(
74558
+ eq101(measurementQueryTemplates.projectId, projectId),
74559
+ eq101(measurementQueryTemplates.id, selection.templateId)
74269
74560
  )).get();
74270
74561
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
74271
74562
  const templateVersion = savedTemplate.updatedAt;
@@ -74274,7 +74565,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
74274
74565
  }
74275
74566
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
74276
74567
  if (bindingLocation) {
74277
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq100(projects.id, projectId)).get();
74568
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq101(projects.id, projectId)).get();
74278
74569
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
74279
74570
  }
74280
74571
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -74287,9 +74578,9 @@ function classifyResearchQueries(project, plan, queries2) {
74287
74578
  return queries2.map((query) => classifier?.classify(query) ?? null);
74288
74579
  }
74289
74580
  function getDetail(app, projectId, id) {
74290
- const row = app.db.select().from(researchRuns).where(and83(eq100(researchRuns.id, id), eq100(researchRuns.projectId, projectId))).get();
74581
+ const row = app.db.select().from(researchRuns).where(and84(eq101(researchRuns.id, id), eq101(researchRuns.projectId, projectId))).get();
74291
74582
  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);
74583
+ const queries2 = app.db.select().from(researchRunQueries).where(eq101(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
74293
74584
  return { ...serializeRun(row), queries: queries2 };
74294
74585
  }
74295
74586
  function serializeRun(row) {
@@ -74344,7 +74635,7 @@ function utcDayBounds(now) {
74344
74635
 
74345
74636
  // ../api-routes/src/simple-measurement-definitions.ts
74346
74637
  import crypto58 from "crypto";
74347
- import { and as and84, eq as eq101 } from "drizzle-orm";
74638
+ import { and as and85, eq as eq102 } from "drizzle-orm";
74348
74639
  function captureSimpleMeasurementDefinition(db, input) {
74349
74640
  return db.transaction((tx) => {
74350
74641
  const run = tx.select({
@@ -74352,7 +74643,7 @@ function captureSimpleMeasurementDefinition(db, input) {
74352
74643
  trigger: runs.trigger,
74353
74644
  status: runs.status,
74354
74645
  measurementPlanVersionId: runs.measurementPlanVersionId
74355
- }).from(runs).where(and84(eq101(runs.projectId, input.projectId), eq101(runs.id, input.runId))).get();
74646
+ }).from(runs).where(and85(eq102(runs.projectId, input.projectId), eq102(runs.id, input.runId))).get();
74356
74647
  if (!run) throw notFound("Run", input.runId);
74357
74648
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
74358
74649
  return null;
@@ -74373,7 +74664,7 @@ function captureSimpleMeasurementDefinition(db, input) {
74373
74664
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
74374
74665
  }
74375
74666
  }
74376
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and84(eq101(simpleMeasurementDefinitions.projectId, input.projectId), eq101(simpleMeasurementDefinitions.runId, input.runId))).get();
74667
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and85(eq102(simpleMeasurementDefinitions.projectId, input.projectId), eq102(simpleMeasurementDefinitions.runId, input.runId))).get();
74377
74668
  if (existing) {
74378
74669
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
74379
74670
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
@@ -74399,15 +74690,15 @@ function captureSimpleMeasurementDefinition(db, input) {
74399
74690
  if (run.status !== RunStatuses.running) {
74400
74691
  throw validationError("Only a running run can capture a new measurement definition.");
74401
74692
  }
74402
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq101(querySnapshots.runId, input.runId)).limit(1).get()) {
74693
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq102(querySnapshots.runId, input.runId)).limit(1).get()) {
74403
74694
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
74404
74695
  }
74405
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq101(queries.projectId, input.projectId)).all().map((query) => query.id));
74696
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq102(queries.projectId, input.projectId)).all().map((query) => query.id));
74406
74697
  if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
74407
74698
  throw validationError("Every captured query must belong to the run project.");
74408
74699
  }
74409
74700
  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));
74701
+ 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
74702
  const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
74412
74703
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
74413
74704
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");