@canonry/canonry 7.94.0 → 7.95.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 (140) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +6 -4
  2. package/assets/assets/{AgentSetupPage-BlPXN_oH.js → AgentSetupPage-BV0TvyWO.js} +4 -4
  3. package/assets/assets/{AuditHistoryPanel-N8Z4gng7.js → AuditHistoryPanel-CTVhEVjr.js} +1 -1
  4. package/assets/assets/{BacklinksPage-DSrgqlkj.js → BacklinksPage-CE1Bfpf6.js} +1 -1
  5. package/assets/assets/{HistoryPage-v2jbUkle.js → HistoryPage-BhqA-Udl.js} +1 -1
  6. package/assets/assets/MeasurementPropertyPage-BiJ06H7w.js +4 -0
  7. package/assets/assets/ProjectPage-BqS0rfDT.js +11 -0
  8. package/assets/assets/{RunsPage-CXZJDdnr.js → RunsPage-BII3_JsE.js} +1 -1
  9. package/assets/assets/{SettingsPage-CeLvWs_T.js → SettingsPage-DS4ivpsX.js} +1 -1
  10. package/assets/assets/SiteHealthSection-IywsGGIA.js +2 -0
  11. package/assets/assets/ToneBadge-CTZ_JZLD.js +60 -0
  12. package/assets/assets/{TrafficPage-CEsmS5N1.js → TrafficPage-Bg9JrnxC.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-D31g1FXV.js → TrafficSourceDetailPage-BuD8BlaT.js} +1 -1
  14. package/assets/assets/{account-context-CGIyok0O.js → account-context-ylqeghOr.js} +1 -1
  15. package/assets/assets/{agent-trust-copy-B-3akxrd.js → agent-trust-copy-DxpavxIA.js} +1 -1
  16. package/assets/assets/{card-BMjN_ofC.js → card-rZ7BA4sl.js} +1 -1
  17. package/assets/assets/{format-helpers-Blu0QNcd.js → format-helpers-PdOXn5Yf.js} +1 -1
  18. package/assets/assets/index-CU7tAhKY.js +60 -0
  19. package/assets/assets/{query-client-DdwTc28X.js → query-client-Cq4kP41m.js} +1 -1
  20. package/assets/assets/react-query-DVctucqC.js +1 -0
  21. package/assets/assets/{react-sigma_core.esm.min-5sP0Pb_F.js → react-sigma_core.esm.min-DYhAEr2t.js} +1 -1
  22. package/assets/assets/{server-traffic-403Q1CKm.js → server-traffic-Owp6jMwT.js} +1 -1
  23. package/assets/assets/{service-ChEbo8oM.js → service-y3nQY3Ue.js} +1 -1
  24. package/assets/index.html +7 -7
  25. package/dist/{ads-6RVS23I5.js → ads-GN7QSEUV.js} +3 -3
  26. package/dist/{agent-N5LH7QNA.js → agent-J46OIQHH.js} +7 -7
  27. package/dist/{agent-ask-IJV2PWPN.js → agent-ask-755DZ4YB.js} +3 -3
  28. package/dist/{agent-conversations-STISIDWB.js → agent-conversations-QO7C6F6L.js} +3 -3
  29. package/dist/{agent-journeys-VCP5MD2W.js → agent-journeys-SVI6LWKM.js} +3 -3
  30. package/dist/{agent-memory-XQQNXRY6.js → agent-memory-V3ZJZT5W.js} +3 -3
  31. package/dist/{agent-providers-IW2Q6WPP.js → agent-providers-QIHZ222U.js} +3 -3
  32. package/dist/{agent-transcript-5XUCHOOT.js → agent-transcript-6TS3ZOCA.js} +3 -3
  33. package/dist/{analytics-RZZZBC4H.js → analytics-LFT5OAOR.js} +3 -3
  34. package/dist/{apply-X5ZTHGSJ.js → apply-3XXMVLBK.js} +3 -3
  35. package/dist/{backfill-ZF6RX27O.js → backfill-F62Y47XV.js} +10 -10
  36. package/dist/{backlinks-WZ7YUKHH.js → backlinks-AOA5KHHB.js} +3 -3
  37. package/dist/{bing-Z4A5NHOC.js → bing-75F4W4E2.js} +3 -3
  38. package/dist/{bootstrap-OCJ7EIXK.js → bootstrap-CDGVAICZ.js} +10 -10
  39. package/dist/{cdp-YGZFLTFT.js → cdp-YBCWRWY4.js} +7 -7
  40. package/dist/{chunk-YRCKRY3Q.js → chunk-46YXUJSW.js} +2 -2
  41. package/dist/{chunk-YGHI3W4D.js → chunk-5TPLZD4I.js} +1 -1
  42. package/dist/{chunk-SFNSSHIN.js → chunk-6RDBULDN.js} +1 -1
  43. package/dist/{chunk-N6MXZ5SC.js → chunk-6S6XEEGX.js} +5 -5
  44. package/dist/{chunk-XIHIB7VP.js → chunk-73AUPWSG.js} +6 -6
  45. package/dist/{chunk-N7PGV6QQ.js → chunk-7PUUMJXY.js} +922 -770
  46. package/dist/{chunk-JDOAULVX.js → chunk-A6O6X6FU.js} +1 -1
  47. package/dist/{chunk-D6KTO5TO.js → chunk-BUZBBVDZ.js} +1 -1
  48. package/dist/{chunk-PR5FCYNN.js → chunk-C6UU2PDJ.js} +21 -21
  49. package/dist/{chunk-NSUB2S4V.js → chunk-CVCUADCM.js} +14 -11
  50. package/dist/{chunk-CBNDEKI6.js → chunk-EWSVH5TU.js} +1 -1
  51. package/dist/{chunk-3D4VEWDJ.js → chunk-FESPLVBO.js} +6 -6
  52. package/dist/{chunk-B5JKQXRN.js → chunk-FWY5HUSB.js} +2 -2
  53. package/dist/{chunk-S6KJYLOV.js → chunk-GUVN5OSX.js} +3 -3
  54. package/dist/{chunk-K63XX7VY.js → chunk-IATTIKXF.js} +3 -3
  55. package/dist/{chunk-BL4CZJYJ.js → chunk-J3NS6NGM.js} +115 -3
  56. package/dist/{chunk-IUDXV6PH.js → chunk-JVOOKVCW.js} +2 -2
  57. package/dist/{chunk-FM74QGW5.js → chunk-KK7NWOGH.js} +7 -7
  58. package/dist/{chunk-BJAOD3NW.js → chunk-KMT7I5KZ.js} +5 -5
  59. package/dist/{chunk-K2JCPF2W.js → chunk-L7W72K4X.js} +367 -367
  60. package/dist/{chunk-55EFIQP7.js → chunk-LHU3KIKG.js} +2 -2
  61. package/dist/{chunk-QDGW75MP.js → chunk-MKLAX5KY.js} +1 -1
  62. package/dist/{chunk-LXJAXAJD.js → chunk-NJG6WFRK.js} +1 -1
  63. package/dist/{chunk-XPBEDIXW.js → chunk-NMUL63XG.js} +2 -2
  64. package/dist/{chunk-UPBQHUE6.js → chunk-NVWTGVZ4.js} +3 -3
  65. package/dist/{chunk-LDY66O7A.js → chunk-PHOMW3R4.js} +4 -4
  66. package/dist/{chunk-M6DYUCNB.js → chunk-Q6WHXY7D.js} +1 -1
  67. package/dist/{chunk-VGAQH7KI.js → chunk-RPPB4H4D.js} +1 -1
  68. package/dist/{chunk-RJLSGCOI.js → chunk-T3FYWUQL.js} +2 -2
  69. package/dist/{chunk-ECUJEKIO.js → chunk-TG5D4E2Y.js} +1 -1
  70. package/dist/{chunk-32N7BGTD.js → chunk-TS5Q63NE.js} +3 -3
  71. package/dist/{chunk-RSBIHKHH.js → chunk-TXW2FPNJ.js} +4 -4
  72. package/dist/{chunk-7JQM46YO.js → chunk-VOP74COR.js} +2 -2
  73. package/dist/{chunk-NUBADAA4.js → chunk-WK6IPTTM.js} +4 -4
  74. package/dist/{chunk-R7BX2ZSQ.js → chunk-YTRXALSU.js} +1 -1
  75. package/dist/{citations-IUITVPHO.js → citations-SQYZTJQ6.js} +3 -3
  76. package/dist/{cli-catalog-IAPKU2RG.js → cli-catalog-N3JLEU77.js} +3 -3
  77. package/dist/{cli-commands-FICINBUR.js → cli-commands-QOJJMX2X.js} +7 -7
  78. package/dist/cli.js +13 -13
  79. package/dist/{competitor-U5DMMCIA.js → competitor-C5JAHHR7.js} +3 -3
  80. package/dist/{content-ZQIZUNF5.js → content-RVPODQI6.js} +3 -3
  81. package/dist/{dashboard-KPGX4EQT.js → dashboard-3NAY2SX6.js} +6 -6
  82. package/dist/{demo-NEWVCEXD.js → demo-OSYK452E.js} +1 -1
  83. package/dist/{demo-server-5GJEGG5K.js → demo-server-GO4V5UXK.js} +14 -14
  84. package/dist/{discover-HSPVFHKK.js → discover-U2DNK2TO.js} +3 -3
  85. package/dist/{discover-eval-BYKPJ3DL.js → discover-eval-ESLS56AH.js} +3 -3
  86. package/dist/{doctor-CK4BTTWH.js → doctor-SBF54WXE.js} +3 -3
  87. package/dist/{evidence-BX3CWPCT.js → evidence-WHDCM7Z5.js} +3 -3
  88. package/dist/{export-cmd-QMCRDMPZ.js → export-cmd-7CHS6K3S.js} +3 -3
  89. package/dist/{ga-RMTUMSEJ.js → ga-XRASDAVX.js} +4 -4
  90. package/dist/{gbp-JK67SYLW.js → gbp-FEOFMTBH.js} +3 -3
  91. package/dist/{google-PJSK6IJV.js → google-MWJ4HAUB.js} +4 -4
  92. package/dist/{google-marketing-UQ2FONXU.js → google-marketing-GELTQ32D.js} +1 -1
  93. package/dist/{health-cmd-ZDXFD53P.js → health-cmd-IO7YMTRQ.js} +3 -3
  94. package/dist/{history-OYL7SPEY.js → history-I74D3NTT.js} +3 -3
  95. package/dist/index.d.ts +194 -16
  96. package/dist/index.js +25 -25
  97. package/dist/{init-DZUNAQBN.js → init-ZLJMCJ3Q.js} +12 -12
  98. package/dist/{insights-AIBLDPX6.js → insights-EAMVEB5Y.js} +3 -3
  99. package/dist/intelligence-service-GSQ7YAYU.js +16 -0
  100. package/dist/{journeys-EDEET2EC.js → journeys-E373TZZS.js} +3 -3
  101. package/dist/{keys-SEZQTYGJ.js → keys-27JRRWKZ.js} +3 -3
  102. package/dist/{keyword-6FJIWTHW.js → keyword-45H2TBRG.js} +3 -3
  103. package/dist/{logs-EML2KKOP.js → logs-K4K4OJDY.js} +3 -3
  104. package/dist/{mcp-AA5QUNBZ.js → mcp-734WXNIA.js} +4 -4
  105. package/dist/mcp.js +9 -9
  106. package/dist/{measurement-plan-ALHRGRPH.js → measurement-plan-XZXK3YLO.js} +5 -5
  107. package/dist/{notify-EVTI3SWQ.js → notify-ZGLVTSVC.js} +3 -3
  108. package/dist/{organic-evidence-EXQQ6R7H.js → organic-evidence-CRTYFYAN.js} +3 -3
  109. package/dist/{overview-5CIFLSDG.js → overview-5URBJKI4.js} +4 -4
  110. package/dist/{project-DNDENHRU.js → project-R5W4Y4L4.js} +3 -3
  111. package/dist/{query-ETMHFVKZ.js → query-LPGNI3ZR.js} +3 -3
  112. package/dist/{research-Q5X46CR5.js → research-GTVKPIO2.js} +3 -3
  113. package/dist/{results-export-DZZANVYY.js → results-export-UNQQHELR.js} +3 -3
  114. package/dist/{run-OQJXRJ42.js → run-DLAFXVF4.js} +4 -4
  115. package/dist/{schedule-6AI3J2AB.js → schedule-7I5VIJFS.js} +3 -3
  116. package/dist/{search-7TAJBSHE.js → search-MUS7FSOJ.js} +3 -3
  117. package/dist/{serve-WD44V3TK.js → serve-5TV26VAH.js} +26 -26
  118. package/dist/{session-LTKCS763.js → session-FWALDL3F.js} +13 -13
  119. package/dist/{settings-EUMYGNIV.js → settings-EB433IDI.js} +3 -3
  120. package/dist/{sitemaps-I4QPLAZW.js → sitemaps-5EVGGGQ5.js} +3 -3
  121. package/dist/{skills-PTYYU3FR.js → skills-2HS4MYNT.js} +2 -2
  122. package/dist/{snapshot-62FF4GEA.js → snapshot-4DMW7AK2.js} +3 -3
  123. package/dist/{src-LRYXRHLW.js → src-OYMKXD6L.js} +4 -2
  124. package/dist/{status-VRWPYTY3.js → status-2CRLY5B6.js} +4 -4
  125. package/dist/{technical-aeo-OKZW2GAX.js → technical-aeo-HIQJCEML.js} +3 -3
  126. package/dist/{telemetry-RRTHKD6W.js → telemetry-H3SGPKZC.js} +4 -4
  127. package/dist/{tools-F7WCCKZW.js → tools-PHYD6WYF.js} +11 -11
  128. package/dist/{traffic-H6RDZWGG.js → traffic-47L2EV6Q.js} +5 -5
  129. package/dist/{trends-RZOQW2LE.js → trends-CCWE7MF6.js} +3 -3
  130. package/dist/{users-KFBCBA3V.js → users-BGIGSKB2.js} +3 -3
  131. package/dist/{visibility-stats-53OZR3UN.js → visibility-stats-XVF64BMX.js} +3 -3
  132. package/dist/{wordpress-2BXU6FDI.js → wordpress-DQ6DAQPS.js} +3 -3
  133. package/package.json +1 -1
  134. package/assets/assets/MeasurementPropertyPage-C1eIWJPD.js +0 -4
  135. package/assets/assets/ProjectPage-Ho61hE8f.js +0 -11
  136. package/assets/assets/SiteHealthSection-C2Ys7rzL.js +0 -2
  137. package/assets/assets/ToneBadge-C49669Ol.js +0 -60
  138. package/assets/assets/index-B-t8djQ-.js +0 -60
  139. package/assets/assets/react-query-dnbkXy7A.js +0 -1
  140. package/dist/intelligence-service-YLNZTJQA.js +0 -16
@@ -17,14 +17,14 @@ import {
17
17
  normalizeTrafficPathPattern,
18
18
  sessionizeJourneyRequests,
19
19
  verifyRequestSignature
20
- } from "./chunk-QDGW75MP.js";
20
+ } from "./chunk-MKLAX5KY.js";
21
21
  import {
22
22
  currentOutcomeAttribution,
23
23
  getRequestContext,
24
24
  registerRequestContext,
25
25
  requestAuditIdentity,
26
26
  sanitizeRequestContext
27
- } from "./chunk-CBNDEKI6.js";
27
+ } from "./chunk-EWSVH5TU.js";
28
28
  import {
29
29
  RawEventSampleEventTypes,
30
30
  SentimentIdempotencyConflict,
@@ -87,6 +87,7 @@ import {
87
87
  gaSearchLandingPages,
88
88
  gaSearchLandingWindows,
89
89
  gaSocialReferrals,
90
+ gaSyncCoverage,
90
91
  gaTrafficSnapshots,
91
92
  gaTrafficSummaries,
92
93
  gaTrafficWindowSummaries,
@@ -183,7 +184,7 @@ import {
183
184
  userSessions,
184
185
  users,
185
186
  writeNegativeReviewMaxStars
186
- } from "./chunk-BL4CZJYJ.js";
187
+ } from "./chunk-J3NS6NGM.js";
187
188
  import {
188
189
  ADS_ACTIVATE_SCOPE,
189
190
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -274,6 +275,8 @@ import {
274
275
  GA4_AI_ASSISTANT_CHANNEL_GROUP,
275
276
  GA_OVERVIEW_HISTORY_DAYS,
276
277
  GA_OVERVIEW_METRIC_ORDER,
278
+ GA_OVERVIEW_WINDOWS,
279
+ GA_OVERVIEW_WINDOW_DAYS,
277
280
  GA_SEARCH_LANDING_DEFAULT_LIMIT,
278
281
  GA_SEARCH_LANDING_DEFAULT_WINDOW,
279
282
  GA_SEARCH_LANDING_MAX_LIMIT,
@@ -1280,7 +1283,7 @@ import {
1280
1283
  wordpressSchemaDeployResultDtoSchema,
1281
1284
  wordpressSchemaStatusResultDtoSchema,
1282
1285
  wordpressStatusDtoSchema
1283
- } from "./chunk-NSUB2S4V.js";
1286
+ } from "./chunk-CVCUADCM.js";
1284
1287
 
1285
1288
  // ../api-routes/src/user-session.ts
1286
1289
  import crypto2 from "crypto";
@@ -36493,11 +36496,15 @@ var analyticsWindowParameter = {
36493
36496
  description: "Time window for analytics queries. An unrecognised value is rejected with 400; it is never widened to the full history.",
36494
36497
  schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
36495
36498
  };
36499
+ var gaTrafficWindowParameter = {
36500
+ ...analyticsWindowParameter,
36501
+ schema: { type: "string", enum: [...WINDOW_SETS.gaTraffic] }
36502
+ };
36496
36503
  var gaOverviewWindowParameter = {
36497
36504
  name: "window",
36498
36505
  in: "query",
36499
36506
  description: 'Overview window: the complete days ending yesterday, as GA4 counts "Last 30 days". Defaults to 30d. Any other value is rejected with 400.',
36500
- schema: { type: "string", enum: ["7d", "30d", "90d"] }
36507
+ schema: { type: "string", enum: [...WINDOW_SETS.gaOverview] }
36501
36508
  };
36502
36509
  var competitorLandscapeGroupKeyParameter = {
36503
36510
  name: "groupKey",
@@ -42757,7 +42764,7 @@ var routeCatalog = [
42757
42764
  {
42758
42765
  name: "window",
42759
42766
  in: "query",
42760
- description: `Stored window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
42767
+ description: `Stored 7, 28, 90 or 180-day window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
42761
42768
  schema: { type: "string", enum: [...gaSearchLandingWindowSchema.options], default: "28d" }
42762
42769
  },
42763
42770
  {
@@ -42812,7 +42819,7 @@ var routeCatalog = [
42812
42819
  summary: "Get GA4 landing page traffic, channel breakdown, and AI referral landing pages",
42813
42820
  description: "Every figure is measured over one window (windowStart to windowEnd). Each AI and social referral row carries share, its fraction of that table's sessions, so a table's shares add up to 1; each top page carries organicShare, its organic sessions over its own sessions.",
42814
42821
  tags: ["ga4"],
42815
- parameters: [nameParameter, limitQueryParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42822
+ parameters: [nameParameter, limitQueryParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42816
42823
  responses: {
42817
42824
  200: jsonResponse("GA4 traffic data returned.", "GaTrafficResponse"),
42818
42825
  400: errorResponse("GA4 is not connected."),
@@ -42825,7 +42832,7 @@ var routeCatalog = [
42825
42832
  summary: "Get raw AI referral detail rows per day, landing page, and attribution dimension",
42826
42833
  description: "Detail rows, not totals. One row per landing page per attribution dimension, so a single day of one source is many rows and each is commonly worth one session. Use /ga/ai-referral-daily for per-date or per-source session counts.",
42827
42834
  tags: ["ga4"],
42828
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42835
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42829
42836
  responses: {
42830
42837
  200: jsonArrayResponse("AI referral history returned.", "GA4AiReferralHistoryEntry"),
42831
42838
  400: errorResponse("GA4 is not connected."),
@@ -42838,7 +42845,7 @@ var routeCatalog = [
42838
42845
  summary: "Get AI referral sessions per day and per source",
42839
42846
  description: "Sums landing pages within one attribution dimension and never across dimensions, so totalSessions equals the aiSessionsDeduped reported by /ga/traffic for the same window. Sessions only: GA counts users as a distinct count at the grain requested, so an AI-referral user total cannot be summed from these rows and no un-dimensioned AI-referral fetch exists to supply one.",
42840
42847
  tags: ["ga4"],
42841
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42848
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42842
42849
  responses: {
42843
42850
  200: jsonResponse("AI referral daily series returned.", "GA4AiReferralDailyDto"),
42844
42851
  400: errorResponse("GA4 is not connected."),
@@ -42850,7 +42857,7 @@ var routeCatalog = [
42850
42857
  path: "/api/v1/projects/{name}/ga/social-referral-history",
42851
42858
  summary: "Get social media referral sessions per day grouped by source",
42852
42859
  tags: ["ga4"],
42853
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42860
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42854
42861
  responses: {
42855
42862
  200: jsonArrayResponse("Social referral history returned.", "GA4SocialReferralHistoryEntry"),
42856
42863
  400: errorResponse("GA4 is not connected."),
@@ -42891,7 +42898,7 @@ var routeCatalog = [
42891
42898
  parameters: [nameParameter, gaOverviewWindowParameter],
42892
42899
  responses: {
42893
42900
  200: jsonResponse("GA4 overview returned.", "GaOverviewResponse"),
42894
- 400: errorResponse("GA4 is not connected, or the window is not 7d, 30d or 90d."),
42901
+ 400: errorResponse("GA4 is not connected, or the window is not 7d, 30d, 90d or 180d."),
42895
42902
  404: errorResponse("Project not found.")
42896
42903
  }
42897
42904
  },
@@ -42900,7 +42907,7 @@ var routeCatalog = [
42900
42907
  path: "/api/v1/projects/{name}/ga/session-history",
42901
42908
  summary: "Get total sessions per day for the project",
42902
42909
  tags: ["ga4"],
42903
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42910
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42904
42911
  responses: {
42905
42912
  200: jsonArrayResponse("Session history returned.", "GA4SessionHistoryEntry"),
42906
42913
  400: errorResponse("GA4 is not connected."),
@@ -47697,6 +47704,7 @@ var GA_PROPERTY_TABLES = [
47697
47704
  gaTrafficWindowSummaries,
47698
47705
  gaWindowMetrics,
47699
47706
  gaDailyTotals,
47707
+ gaSyncCoverage,
47700
47708
  gaAiReferrals,
47701
47709
  gaSocialReferrals,
47702
47710
  gaAcquisitionDaily,
@@ -47781,7 +47789,7 @@ var GA4_ADMIN_API_BASE = "https://analyticsadmin.googleapis.com/v1beta";
47781
47789
  var GA4_SCOPE = "https://www.googleapis.com/auth/analytics.readonly";
47782
47790
  var GOOGLE_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
47783
47791
  var GA4_DEFAULT_SYNC_DAYS = 30;
47784
- var GA4_MAX_SYNC_DAYS = 90;
47792
+ var GA4_MAX_SYNC_DAYS = 180;
47785
47793
  function resolveGa4SyncDays(days) {
47786
47794
  const requestedDays = days != null && Number.isFinite(days) ? Math.trunc(days) : GA4_DEFAULT_SYNC_DAYS;
47787
47795
  const effectiveDays = Math.min(Math.max(1, requestedDays), GA4_MAX_SYNC_DAYS);
@@ -47859,6 +47867,7 @@ var GA4_METRICS = {
47859
47867
  };
47860
47868
  var GA4_SEARCH_LANDING_MAX_ROWS = 1e4;
47861
47869
  var GA4_SEARCH_LANDING_PAGE_SIZE = 1e4;
47870
+ var GA4_SEARCH_LANDING_REPORT_DEFINITION = "search-landing-v1";
47862
47871
 
47863
47872
  // ../integration-google-analytics/src/types.ts
47864
47873
  var GA4ApiError = class extends Error {
@@ -48163,7 +48172,12 @@ function reportTimeZone(...responses) {
48163
48172
  }
48164
48173
  return null;
48165
48174
  }
48166
- function measurementDateRange(days) {
48175
+ function measurementDateRange(days, requestedRange, maxDays = GA4_MAX_SYNC_DAYS) {
48176
+ if (requestedRange) {
48177
+ const count10 = inclusiveDayCount(requestedRange.startDate, requestedRange.endDate);
48178
+ if (count10 === null || count10 > maxDays + 1) throw new GA4ApiError("Invalid GA4 sync date range", 400);
48179
+ return { syncDays: count10, dateRange: requestedRange };
48180
+ }
48167
48181
  const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48168
48182
  const endDate = /* @__PURE__ */ new Date();
48169
48183
  const startDate = /* @__PURE__ */ new Date();
@@ -48211,7 +48225,7 @@ async function fetchPagedReportWithTotals(accessToken, propertyId, baseRequest,
48211
48225
  async function fetchAcquisitionByChannel(accessToken, propertyId, days, options) {
48212
48226
  validateAccessToken2(accessToken);
48213
48227
  validatePropertyId(propertyId);
48214
- const { syncDays, dateRange } = measurementDateRange(days);
48228
+ const { syncDays, dateRange } = measurementDateRange(days, options?.dateRange);
48215
48229
  const rows = await fetchPagedReport(accessToken, propertyId, {
48216
48230
  dateRanges: [dateRange],
48217
48231
  dimensions: [
@@ -48238,10 +48252,10 @@ async function fetchAcquisitionByChannel(accessToken, propertyId, days, options)
48238
48252
  function isLandingPageDimensionIncompatibility(error) {
48239
48253
  return error instanceof GA4ApiError && error.status === 400 && /landingPagePlusQueryString/i.test(error.message) && /incompatib/i.test(error.message);
48240
48254
  }
48241
- async function fetchLeadEvents(accessToken, propertyId, eventNames, days) {
48255
+ async function fetchLeadEvents(accessToken, propertyId, eventNames, days, dateRangeOverride) {
48242
48256
  validateAccessToken2(accessToken);
48243
48257
  validatePropertyId(propertyId);
48244
- const { dateRange } = measurementDateRange(days);
48258
+ const { dateRange } = measurementDateRange(days, dateRangeOverride);
48245
48259
  const filter = { orGroup: { expressions: eventNames.map((value) => ({ filter: {
48246
48260
  fieldName: GA4_DIMENSIONS.eventName,
48247
48261
  stringFilter: { matchType: "EXACT", value }
@@ -48327,13 +48341,10 @@ var AI_REFERRAL_PASSES = [
48327
48341
  function isInvalidGa4Request(error) {
48328
48342
  return error instanceof GA4ApiError && error.status === 400;
48329
48343
  }
48330
- async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48344
+ async function fetchTrafficByLandingPage(accessToken, propertyId, days, dateRangeOverride) {
48331
48345
  validateAccessToken2(accessToken);
48332
48346
  validatePropertyId(propertyId);
48333
- const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48334
- const endDate = /* @__PURE__ */ new Date();
48335
- const startDate = /* @__PURE__ */ new Date();
48336
- startDate.setDate(startDate.getDate() - syncDays);
48347
+ const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
48337
48348
  ga4Log("info", "fetch-traffic.start", { propertyId, days: syncDays });
48338
48349
  const PAGE_SIZE = 1e4;
48339
48350
  const rows = [];
@@ -48342,7 +48353,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48342
48353
  while (pageCount < GA4_MAX_PAGES) {
48343
48354
  pageCount++;
48344
48355
  const request = {
48345
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48356
+ dateRanges: [dateRange],
48346
48357
  dimensions: [
48347
48358
  { name: GA4_DIMENSIONS.date },
48348
48359
  { name: GA4_DIMENSIONS.landingPagePlusQueryString }
@@ -48367,9 +48378,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48367
48378
  users: parseInt(row.metricValues[1].value, 10) || 0
48368
48379
  }));
48369
48380
  rows.push(...pageRows);
48370
- const totalRows = response.rowCount ?? 0;
48381
+ const totalRows = response.rowCount;
48371
48382
  offset += pageRows.length;
48372
- if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
48383
+ if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
48384
+ if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
48373
48385
  }
48374
48386
  const organicMap = /* @__PURE__ */ new Map();
48375
48387
  let organicOffset = 0;
@@ -48377,7 +48389,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48377
48389
  while (organicPageCount < GA4_MAX_PAGES) {
48378
48390
  organicPageCount++;
48379
48391
  const organicRequest = {
48380
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48392
+ dateRanges: [dateRange],
48381
48393
  dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
48382
48394
  metrics: [{ name: GA4_METRICS.sessions }],
48383
48395
  dimensionFilter: {
@@ -48395,9 +48407,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48395
48407
  const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
48396
48408
  organicMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
48397
48409
  }
48398
- const total = organicResponse.rowCount ?? 0;
48410
+ const total = organicResponse.rowCount;
48399
48411
  organicOffset += (organicResponse.rows ?? []).length;
48400
- if ((organicResponse.rows ?? []).length < 1e4 || organicOffset >= total) break;
48412
+ if ((organicResponse.rows ?? []).length < 1e4 || total !== void 0 && organicOffset >= total) break;
48413
+ if (organicPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 organic report truncated at the page limit", 502);
48401
48414
  }
48402
48415
  const directMap = /* @__PURE__ */ new Map();
48403
48416
  let directOffset = 0;
@@ -48405,7 +48418,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48405
48418
  while (directPageCount < GA4_MAX_PAGES) {
48406
48419
  directPageCount++;
48407
48420
  const directRequest = {
48408
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48421
+ dateRanges: [dateRange],
48409
48422
  dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
48410
48423
  metrics: [{ name: GA4_METRICS.sessions }],
48411
48424
  dimensionFilter: {
@@ -48423,9 +48436,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48423
48436
  const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
48424
48437
  directMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
48425
48438
  }
48426
- const total = directResponse.rowCount ?? 0;
48439
+ const total = directResponse.rowCount;
48427
48440
  directOffset += (directResponse.rows ?? []).length;
48428
- if ((directResponse.rows ?? []).length < 1e4 || directOffset >= total) break;
48441
+ if ((directResponse.rows ?? []).length < 1e4 || total !== void 0 && directOffset >= total) break;
48442
+ if (directPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 direct report truncated at the page limit", 502);
48429
48443
  }
48430
48444
  for (const row of rows) {
48431
48445
  const key = `${row.date}::${row.landingPage}`;
@@ -48592,7 +48606,8 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48592
48606
  validatePropertyId(propertyId);
48593
48607
  const historyDays = Math.min(Math.max(1, Number.isFinite(days) ? Math.trunc(days) : GA_OVERVIEW_HISTORY_DAYS), GA_OVERVIEW_HISTORY_DAYS);
48594
48608
  const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
48595
- const dateRange = { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
48609
+ const dateRange = options.dateRange ? measurementDateRange(days, options.dateRange, GA_OVERVIEW_HISTORY_DAYS).dateRange : { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
48610
+ const rowLimit = options.dateRange ? inclusiveDayCount(dateRange.startDate, dateRange.endDate) : historyDays + 1;
48596
48611
  ga4Log("info", "fetch-daily-totals.start", { propertyId, days: historyDays });
48597
48612
  const [totalsRes, organicRes] = await batchRunReports(accessToken, propertyId, [
48598
48613
  {
@@ -48610,7 +48625,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48610
48625
  { name: GA4_METRICS.userEngagementDuration },
48611
48626
  { name: GA4_METRICS.bounceRate }
48612
48627
  ],
48613
- limit: historyDays + 1
48628
+ limit: rowLimit
48614
48629
  },
48615
48630
  {
48616
48631
  dateRanges: [dateRange],
@@ -48622,7 +48637,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48622
48637
  stringFilter: { matchType: "EXACT", value: "Organic Search" }
48623
48638
  }
48624
48639
  },
48625
- limit: historyDays + 1
48640
+ limit: rowLimit
48626
48641
  }
48627
48642
  ]);
48628
48643
  const organicByDate = /* @__PURE__ */ new Map();
@@ -48652,18 +48667,17 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48652
48667
  const timeZone = reportTimeZone(totalsRes, organicRes) ?? "UTC";
48653
48668
  ga4Log("info", "fetch-daily-totals.done", { propertyId, days: historyDays, timeZone, rows: rows.length });
48654
48669
  return {
48655
- startDate: isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
48656
- endDate: isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
48670
+ startDate: options.dateRange?.startDate ?? isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
48671
+ endDate: options.dateRange?.endDate ?? isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
48657
48672
  rows
48658
48673
  };
48659
48674
  }
48660
- var WINDOW_DAYS2 = { "7d": 7, "30d": 30, "90d": 90 };
48661
48675
  async function fetchWindowSummary(accessToken, propertyId, windowKey) {
48662
48676
  validateAccessToken2(accessToken);
48663
48677
  validatePropertyId(propertyId);
48664
- const days = WINDOW_DAYS2[windowKey];
48678
+ const days = GA_OVERVIEW_WINDOW_DAYS[windowKey];
48665
48679
  if (!days) {
48666
- throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, or 90d`, 400);
48680
+ throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, 90d, or 180d`, 400);
48667
48681
  }
48668
48682
  const endDate = /* @__PURE__ */ new Date();
48669
48683
  const startDate = /* @__PURE__ */ new Date();
@@ -48793,16 +48807,12 @@ async function fetchWindowMetrics(accessToken, propertyId, windowKey, options =
48793
48807
  previous: period2(COMPARISON_RANGE_NAMES.previous, ranges.previous)
48794
48808
  };
48795
48809
  ga4Log("info", "fetch-window-metrics.done", { propertyId, windowKey, timeZone });
48796
- return result;
48810
+ return { ...result, timeZone };
48797
48811
  }
48798
- async function fetchAiReferrals(accessToken, propertyId, days) {
48812
+ async function fetchAiReferrals(accessToken, propertyId, days, dateRangeOverride) {
48799
48813
  validateAccessToken2(accessToken);
48800
48814
  validatePropertyId(propertyId);
48801
- const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48802
- const endDate = /* @__PURE__ */ new Date();
48803
- const startDate = /* @__PURE__ */ new Date();
48804
- startDate.setDate(startDate.getDate() - syncDays);
48805
- const dateRange = { startDate: formatDate(startDate), endDate: formatDate(endDate) };
48815
+ const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
48806
48816
  ga4Log("info", "fetch-ai-referrals.start", { propertyId, days: syncDays });
48807
48817
  const rows = [];
48808
48818
  for (const pass of AI_REFERRAL_PASSES) {
@@ -48900,20 +48910,18 @@ async function fetchAiReferralPass(accessToken, propertyId, dateRange, pass, cha
48900
48910
  };
48901
48911
  });
48902
48912
  rows.push(...pageRows);
48903
- const totalRows = response.rowCount ?? 0;
48913
+ const totalRows = response.rowCount;
48904
48914
  offset += pageRows.length;
48905
- if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
48915
+ if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
48916
+ if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
48906
48917
  }
48907
48918
  return rows;
48908
48919
  }
48909
48920
  var SOCIAL_CHANNEL_GROUPS = ["Organic Social", "Paid Social"];
48910
- async function fetchSocialReferrals(accessToken, propertyId, days) {
48921
+ async function fetchSocialReferrals(accessToken, propertyId, days, dateRangeOverride) {
48911
48922
  validateAccessToken2(accessToken);
48912
48923
  validatePropertyId(propertyId);
48913
- const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48914
- const endDate = /* @__PURE__ */ new Date();
48915
- const startDate = /* @__PURE__ */ new Date();
48916
- startDate.setDate(startDate.getDate() - syncDays);
48924
+ const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
48917
48925
  ga4Log("info", "fetch-social-referrals.start", { propertyId, days: syncDays });
48918
48926
  const PAGE_SIZE = 1e3;
48919
48927
  const rows = [];
@@ -48921,7 +48929,7 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
48921
48929
  let pageCount = 0;
48922
48930
  while (pageCount < GA4_MAX_PAGES) {
48923
48931
  const request = {
48924
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48932
+ dateRanges: [dateRange],
48925
48933
  dimensions: [
48926
48934
  { name: GA4_DIMENSIONS.date },
48927
48935
  { name: GA4_DIMENSIONS.sessionSource },
@@ -48956,10 +48964,11 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
48956
48964
  channelGroup: row.dimensionValues[3].value
48957
48965
  }));
48958
48966
  rows.push(...pageRows);
48959
- const totalRows = response.rowCount ?? 0;
48967
+ const totalRows = response.rowCount;
48960
48968
  offset += pageRows.length;
48961
48969
  pageCount += 1;
48962
- if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
48970
+ if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
48971
+ if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
48963
48972
  }
48964
48973
  for (const row of rows) {
48965
48974
  row.date = compactDateToIso(row.date);
@@ -49012,10 +49021,14 @@ function isSearchConsoleUnavailable(error) {
49012
49021
  }
49013
49022
  async function fetchSearchLandingWindow(accessToken, propertyId, window2, options) {
49014
49023
  const days = GA_SEARCH_LANDING_WINDOW_DAYS[window2];
49024
+ const explicitRange = options.timeZone === void 0 ? null : {
49025
+ startDate: isoDateDaysBeforeInTimeZone(options.requestedAt, days, options.timeZone),
49026
+ endDate: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, options.timeZone)
49027
+ };
49015
49028
  const { rows: rawRows, firstPage, reportRowCount, capped } = await fetchPagedReportWithTotals(accessToken, propertyId, {
49016
- // Relative dates resolve in the property's reporting time zone, which is
49017
- // how GA4's own "Last N days" ranges end: yesterday, local to the property.
49018
- dateRanges: [{ startDate: `${days}daysAgo`, endDate: "yesterday" }],
49029
+ // A known reporting zone pins the planner's calendar bounds across I/O.
49030
+ // Otherwise GA4 resolves the relative range in its own reporting zone.
49031
+ dateRanges: [explicitRange ?? { startDate: `${days}daysAgo`, endDate: "yesterday" }],
49019
49032
  dimensions: [{ name: GA4_DIMENSIONS.landingPagePlusQueryString }],
49020
49033
  metrics: SEARCH_LANDING_METRIC_NAMES.map((name) => ({ name })),
49021
49034
  metricAggregations: ["TOTAL"],
@@ -49047,8 +49060,8 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
49047
49060
  const zone = timeZone ?? "UTC";
49048
49061
  return {
49049
49062
  window: window2,
49050
- periodStart: isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
49051
- periodEnd: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
49063
+ periodStart: explicitRange?.startDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
49064
+ periodEnd: explicitRange?.endDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
49052
49065
  timeZone,
49053
49066
  total,
49054
49067
  rows,
@@ -49062,13 +49075,17 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
49062
49075
  async function fetchSearchLandingPages(accessToken, propertyId, options = {}) {
49063
49076
  validateAccessToken2(accessToken);
49064
49077
  validatePropertyId(propertyId);
49078
+ const requestedWindows = options.windows ?? GA_SEARCH_LANDING_WINDOWS;
49079
+ if (requestedWindows.length === 0 || new Set(requestedWindows).size !== requestedWindows.length || requestedWindows.some((window2) => !GA_SEARCH_LANDING_WINDOWS.includes(window2))) {
49080
+ throw new GA4ApiError("Search landing windows must be a non-empty selection of unique supported windows", 400);
49081
+ }
49065
49082
  const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
49066
49083
  const pageSize = options.pageSize ?? GA4_SEARCH_LANDING_PAGE_SIZE;
49067
49084
  const maxRows = options.maxRows ?? GA4_SEARCH_LANDING_MAX_ROWS;
49068
49085
  ga4Log("info", "fetch-search-landing.start", { propertyId });
49069
49086
  let windows;
49070
49087
  try {
49071
- windows = await Promise.all(GA_SEARCH_LANDING_WINDOWS.map((window2) => fetchSearchLandingWindow(accessToken, propertyId, window2, { requestedAt, pageSize, maxRows })));
49088
+ windows = await Promise.all(requestedWindows.map((window2) => fetchSearchLandingWindow(accessToken, propertyId, window2, { requestedAt, pageSize, maxRows, timeZone: options.timeZone })));
49072
49089
  } catch (error) {
49073
49090
  if (!isSearchConsoleUnavailable(error)) throw error;
49074
49091
  ga4Log("info", "fetch-search-landing.unavailable", { propertyId, httpStatus: 400 });
@@ -59608,7 +59625,7 @@ async function cdpRoutes(app, opts) {
59608
59625
 
59609
59626
  // ../api-routes/src/ga.ts
59610
59627
  import crypto51 from "crypto";
59611
- import { eq as eq85, desc as desc38, and as and73, sql as sql37 } from "drizzle-orm";
59628
+ import { eq as eq86, desc as desc38, and as and74, sql as sql37 } from "drizzle-orm";
59612
59629
 
59613
59630
  // ../api-routes/src/ga-session-history.ts
59614
59631
  function buildSessionHistory(landingPageDays, dailyTotals) {
@@ -59628,7 +59645,7 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
59628
59645
  // ../api-routes/src/ga-overview.ts
59629
59646
  function parseGaOverviewWindow(value) {
59630
59647
  if (value === void 0 || value === "") return "30d";
59631
- if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d or 90d.`);
59648
+ if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d, 90d or 180d.`);
59632
59649
  return value;
59633
59650
  }
59634
59651
  var NO_VALUES = {
@@ -59771,13 +59788,93 @@ function findBiggestMover(current, prior) {
59771
59788
  return mover;
59772
59789
  }
59773
59790
 
59791
+ // ../api-routes/src/ga-sync-coverage.ts
59792
+ import { and as and72, eq as eq84, lt as lt17 } from "drizzle-orm";
59793
+ function planGaSyncCoverage(db, input, now) {
59794
+ const definition2 = input.definition ?? "v1";
59795
+ const refreshStart = shiftIsoCalendarDate(input.requested.endDate, -13);
59796
+ const reconcileAfter = new Date(Date.parse(now) - 30 * 864e5).toISOString();
59797
+ const covered = new Set(db.select().from(gaSyncCoverage).where(and72(
59798
+ eq84(gaSyncCoverage.projectId, input.projectId),
59799
+ eq84(gaSyncCoverage.propertyId, input.propertyId),
59800
+ eq84(gaSyncCoverage.component, input.component),
59801
+ eq84(gaSyncCoverage.definition, definition2)
59802
+ )).all().filter((row) => row.settled && row.syncedAt > reconcileAfter).map((row) => row.date));
59803
+ const missing = calendarDateRange(input.requested.startDate, input.requested.endDate).filter((date) => date >= refreshStart || !covered.has(date));
59804
+ return {
59805
+ ...input,
59806
+ definition: definition2,
59807
+ refreshStart,
59808
+ ranges: groupIsoDateRanges(missing).map((range) => ({ startDate: range.start, endDate: range.end }))
59809
+ };
59810
+ }
59811
+ function saveGaSyncCoverage(tx, plan, ranges, syncedAt, syncRunId) {
59812
+ for (const range of ranges) {
59813
+ for (const date of calendarDateRange(range.startDate, range.endDate)) {
59814
+ const values = {
59815
+ projectId: plan.projectId,
59816
+ propertyId: plan.propertyId,
59817
+ component: plan.component,
59818
+ definition: plan.definition,
59819
+ date,
59820
+ settled: date < plan.refreshStart,
59821
+ syncedAt,
59822
+ syncRunId
59823
+ };
59824
+ tx.insert(gaSyncCoverage).values(values).onConflictDoUpdate({
59825
+ target: [gaSyncCoverage.projectId, gaSyncCoverage.component, gaSyncCoverage.date],
59826
+ set: values
59827
+ }).run();
59828
+ }
59829
+ }
59830
+ tx.delete(gaSyncCoverage).where(and72(
59831
+ eq84(gaSyncCoverage.projectId, plan.projectId),
59832
+ lt17(gaSyncCoverage.date, shiftIsoCalendarDate(plan.requested.endDate, -362))
59833
+ )).run();
59834
+ }
59835
+ async function fetchGaSyncRanges(plan, fetchRange) {
59836
+ const results = [];
59837
+ for (const range of plan.ranges) {
59838
+ results.push({ range, report: await fetchRange(range) });
59839
+ }
59840
+ return results;
59841
+ }
59842
+
59774
59843
  // ../api-routes/src/ga-search-landing-pages.ts
59775
59844
  import crypto50 from "crypto";
59776
- import { and as and72, asc as asc21, count as count5, desc as desc37, eq as eq84, gte as gte23, lte as lte23, sql as sql36 } from "drizzle-orm";
59845
+ import { and as and73, asc as asc21, count as count5, desc as desc37, eq as eq85, gte as gte23, lte as lte23, sql as sql36 } from "drizzle-orm";
59846
+ function searchLandingWindowsToFetch(db, projectId, propertyId, timeZone, requestedAt) {
59847
+ const state = db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, projectId)).get();
59848
+ if (!timeZone || state?.searchLandingStatus !== "ready") return [...GA_SEARCH_LANDING_WINDOWS];
59849
+ const stored = db.select().from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
59850
+ const now = Date.parse(requestedAt);
59851
+ const end = isoDateDaysBeforeInTimeZone(requestedAt, 1, timeZone);
59852
+ return GA_SEARCH_LANDING_WINDOWS.filter((window2) => {
59853
+ const snapshot = stored.find((row) => row.windowKey === window2);
59854
+ if (!snapshot) return true;
59855
+ const age = now - Date.parse(snapshot.syncedAt);
59856
+ return snapshot.propertyId !== propertyId || snapshot.reportDefinition !== GA4_SEARCH_LANDING_REPORT_DEFINITION || snapshot.timeZone !== timeZone || snapshot.periodStart !== isoDateDaysBeforeInTimeZone(requestedAt, GA_SEARCH_LANDING_WINDOW_DAYS[window2], timeZone) || snapshot.periodEnd !== end || !(age >= 0 && age < 60 * 6e4);
59857
+ });
59858
+ }
59777
59859
  function persistSearchLandingSnapshot(db, input) {
59778
59860
  db.transaction((tx) => {
59779
- deleteSearchLandingRows(tx, input.projectId);
59861
+ if (input.reuse.windows.length > 0) {
59862
+ const missing = searchLandingWindowsToFetch(tx, input.projectId, input.propertyId, input.reuse.timeZone, input.reuse.requestedAt);
59863
+ if (missing.some((window2) => input.reuse.windows.includes(window2))) {
59864
+ throw new Error("Search landing snapshots changed during sync; retry to refresh the missing windows");
59865
+ }
59866
+ }
59867
+ const otherProperty = tx.select({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, input.projectId)).all().some((row) => row.propertyId !== input.propertyId);
59868
+ if (otherProperty) deleteSearchLandingRows(tx, input.projectId);
59780
59869
  for (const window2 of input.windows) {
59870
+ tx.delete(gaSearchLandingPages).where(and73(
59871
+ eq85(gaSearchLandingPages.projectId, input.projectId),
59872
+ eq85(gaSearchLandingPages.windowKey, window2.window)
59873
+ )).run();
59874
+ tx.delete(gaSearchLandingWindows).where(and73(
59875
+ eq85(gaSearchLandingWindows.projectId, input.projectId),
59876
+ eq85(gaSearchLandingWindows.windowKey, window2.window)
59877
+ )).run();
59781
59878
  tx.insert(gaSearchLandingWindows).values({
59782
59879
  id: crypto50.randomUUID(),
59783
59880
  projectId: input.projectId,
@@ -59786,6 +59883,7 @@ function persistSearchLandingSnapshot(db, input) {
59786
59883
  periodStart: window2.periodStart,
59787
59884
  periodEnd: window2.periodEnd,
59788
59885
  timeZone: window2.timeZone,
59886
+ reportDefinition: GA4_SEARCH_LANDING_REPORT_DEFINITION,
59789
59887
  totalClicks: window2.total.clicks,
59790
59888
  totalImpressions: window2.total.impressions,
59791
59889
  totalCtr: window2.total.ctr,
@@ -59819,7 +59917,7 @@ function persistSearchLandingSnapshot(db, input) {
59819
59917
  upsertSearchLandingState(tx, input.projectId, {
59820
59918
  searchLandingStatus: "ready",
59821
59919
  searchLandingError: null,
59822
- searchLandingSyncedAt: input.syncedAt,
59920
+ ...input.windows.length > 0 ? { searchLandingSyncedAt: input.syncedAt } : {},
59823
59921
  searchLandingAttemptedAt: input.syncedAt
59824
59922
  }, input.syncedAt);
59825
59923
  });
@@ -59837,7 +59935,7 @@ function persistSearchLandingSnapshot(db, input) {
59837
59935
  }
59838
59936
  function persistSearchLandingFailure(db, projectId, propertyId, status, message, attemptedAt) {
59839
59937
  db.transaction((tx) => {
59840
- const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq84(gaSearchLandingWindows.projectId, projectId)).all();
59938
+ const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
59841
59939
  const otherProperty = storedProperties.some((stored) => stored.propertyId !== propertyId);
59842
59940
  if (otherProperty) deleteSearchLandingRows(tx, projectId);
59843
59941
  upsertSearchLandingState(tx, projectId, {
@@ -59849,8 +59947,8 @@ function persistSearchLandingFailure(db, projectId, propertyId, status, message,
59849
59947
  });
59850
59948
  }
59851
59949
  function deleteSearchLandingRows(db, projectId) {
59852
- db.delete(gaSearchLandingPages).where(eq84(gaSearchLandingPages.projectId, projectId)).run();
59853
- db.delete(gaSearchLandingWindows).where(eq84(gaSearchLandingWindows.projectId, projectId)).run();
59950
+ db.delete(gaSearchLandingPages).where(eq85(gaSearchLandingPages.projectId, projectId)).run();
59951
+ db.delete(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).run();
59854
59952
  }
59855
59953
  function upsertSearchLandingState(db, projectId, fields, updatedAt) {
59856
59954
  db.insert(gaMeasurementSyncStates).values({ projectId, ...fields, updatedAt }).onConflictDoUpdate({
@@ -59882,11 +59980,11 @@ function buildGaSearchLandingPages(db, projectName, query2, connections) {
59882
59980
  throw validationError('"offset" must be a non-negative integer');
59883
59981
  }
59884
59982
  const project = resolveProject(db, projectName);
59885
- const storedWindow = db.select().from(gaSearchLandingWindows).where(and72(eq84(gaSearchLandingWindows.projectId, project.id), eq84(gaSearchLandingWindows.windowKey, window2))).get();
59983
+ const storedWindow = db.select().from(gaSearchLandingWindows).where(and73(eq85(gaSearchLandingWindows.projectId, project.id), eq85(gaSearchLandingWindows.windowKey, window2))).get();
59886
59984
  const otherProperty = storedWindow !== void 0 && storedWindow.propertyId !== resolveCurrentGa4PropertyId(connections, project.name, project.canonicalDomain);
59887
59985
  const stored = otherProperty ? void 0 : storedWindow;
59888
- const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(eq84(gaMeasurementSyncStates.projectId, project.id)).get();
59889
- const pageScope = and72(eq84(gaSearchLandingPages.projectId, project.id), eq84(gaSearchLandingPages.windowKey, window2));
59986
+ const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, project.id)).get();
59987
+ const pageScope = and73(eq85(gaSearchLandingPages.projectId, project.id), eq85(gaSearchLandingPages.windowKey, window2));
59890
59988
  const totalRows = otherProperty ? 0 : db.select({ value: count5() }).from(gaSearchLandingPages).where(pageScope).get()?.value ?? 0;
59891
59989
  const rows = otherProperty ? [] : db.select().from(gaSearchLandingPages).where(pageScope).orderBy(desc37(gaSearchLandingPages.clicks), desc37(gaSearchLandingPages.impressions), asc21(gaSearchLandingPages.landingPage)).limit(limit).offset(offset).all();
59892
59990
  const total = stored ? toWireMetrics({
@@ -59926,7 +60024,7 @@ function searchConsoleLinkFor(db, projectId, total, windowStart, windowEnd) {
59926
60024
  const stored = db.select({
59927
60025
  days: count5(),
59928
60026
  impressions: sql36`sum(${gscDailyTotals.impressions})`
59929
- }).from(gscDailyTotals).where(and72(eq84(gscDailyTotals.projectId, projectId), gte23(gscDailyTotals.date, windowStart), lte23(gscDailyTotals.date, windowEnd))).get();
60027
+ }).from(gscDailyTotals).where(and73(eq85(gscDailyTotals.projectId, projectId), gte23(gscDailyTotals.date, windowStart), lte23(gscDailyTotals.date, windowEnd))).get();
59930
60028
  const searchConsoleImpressions = stored && stored.days > 0 ? Number(stored.impressions ?? 0) : null;
59931
60029
  if (gaSearchLandingHasSearchData(total)) return { state: "has-data", searchConsoleImpressions };
59932
60030
  if (searchConsoleImpressions === null) return { state: "unknown", searchConsoleImpressions };
@@ -60025,8 +60123,8 @@ function keepWinningDimensionRows(rows, groupKey) {
60025
60123
  function persistAcquisitionMeasurement(db, input) {
60026
60124
  const { startDate, endDate } = input.report;
60027
60125
  db.transaction((tx) => {
60028
- tx.delete(gaAcquisitionDaily).where(and73(
60029
- eq85(gaAcquisitionDaily.projectId, input.projectId),
60126
+ tx.delete(gaAcquisitionDaily).where(and74(
60127
+ eq86(gaAcquisitionDaily.projectId, input.projectId),
60030
60128
  sql37`${gaAcquisitionDaily.date} >= ${startDate}`,
60031
60129
  sql37`${gaAcquisitionDaily.date} <= ${endDate}`
60032
60130
  )).run();
@@ -60067,8 +60165,8 @@ function persistAcquisitionMeasurement(db, input) {
60067
60165
  function persistLeadMeasurement(db, input) {
60068
60166
  const { startDate, endDate } = input.report;
60069
60167
  db.transaction((tx) => {
60070
- tx.delete(gaLeadEventsDaily).where(and73(
60071
- eq85(gaLeadEventsDaily.projectId, input.projectId),
60168
+ tx.delete(gaLeadEventsDaily).where(and74(
60169
+ eq86(gaLeadEventsDaily.projectId, input.projectId),
60072
60170
  sql37`${gaLeadEventsDaily.date} >= ${startDate}`,
60073
60171
  sql37`${gaLeadEventsDaily.date} <= ${endDate}`
60074
60172
  )).run();
@@ -60114,7 +60212,8 @@ function persistLeadMeasurement(db, input) {
60114
60212
  }
60115
60213
  function resetLeadMeasurement(db, projectId, updatedAt) {
60116
60214
  db.transaction((tx) => {
60117
- tx.delete(gaLeadEventsDaily).where(eq85(gaLeadEventsDaily.projectId, projectId)).run();
60215
+ tx.delete(gaLeadEventsDaily).where(eq86(gaLeadEventsDaily.projectId, projectId)).run();
60216
+ tx.delete(gaSyncCoverage).where(and74(eq86(gaSyncCoverage.projectId, projectId), eq86(gaSyncCoverage.component, "leads"))).run();
60118
60217
  tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }).onConflictDoUpdate({
60119
60218
  target: gaMeasurementSyncStates.projectId,
60120
60219
  set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }
@@ -60456,7 +60555,7 @@ async function ga4Routes(app, opts) {
60456
60555
  if (!connected2) {
60457
60556
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
60458
60557
  }
60459
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
60558
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
60460
60559
  return {
60461
60560
  connected: true,
60462
60561
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -60479,7 +60578,7 @@ async function ga4Routes(app, opts) {
60479
60578
  const syncAi = !only || only === "ai";
60480
60579
  const syncSocial = !only || only === "social";
60481
60580
  const syncSearchLanding = !only || only === "search-landing";
60482
- const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, project.id)).get();
60581
+ const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq86(gaMeasurementSyncStates.projectId, project.id)).get();
60483
60582
  const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
60484
60583
  let leadDays = GA4_MAX_SYNC_DAYS;
60485
60584
  let leadEventNames = [];
@@ -60516,47 +60615,72 @@ async function ga4Routes(app, opts) {
60516
60615
  let rows = [];
60517
60616
  let aiReferrals = [];
60518
60617
  let socialReferrals = [];
60519
- const WINDOW_KEYS = ["7d", "30d", "90d"];
60520
- const fetches = [
60618
+ const WINDOW_KEYS = GA_OVERVIEW_WINDOWS;
60619
+ const [summary3, windowSummaries, windowMetrics] = await Promise.all([
60521
60620
  fetchAggregateSummary(accessToken, propertyId, days),
60522
- ...WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w)),
60523
- fetchDailyTotals(accessToken, propertyId),
60524
- ...WINDOW_KEYS.map((w) => fetchWindowMetrics(accessToken, propertyId, w))
60525
- ];
60526
- if (syncTraffic) fetches.push(fetchTrafficByLandingPage(accessToken, propertyId, days));
60527
- if (syncAi) fetches.push(fetchAiReferrals(accessToken, propertyId, days));
60528
- if (syncSocial) fetches.push(fetchSocialReferrals(accessToken, propertyId, days));
60621
+ Promise.all(WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w))),
60622
+ Promise.all(WINDOW_KEYS.map((w) => fetchWindowMetrics(accessToken, propertyId, w)))
60623
+ ]);
60624
+ const requested = { startDate: summary3.periodStart, endDate: summary3.periodEnd };
60625
+ const definition2 = JSON.stringify(["v1", windowMetrics[0].timeZone ?? "UTC"]);
60626
+ const planInput = { projectId: project.id, propertyId, requested, definition: definition2 };
60627
+ const trafficPlan = planGaSyncCoverage(app.db, { ...planInput, component: "traffic" }, startedAt);
60628
+ const aiPlan = planGaSyncCoverage(app.db, { ...planInput, component: "ai" }, startedAt);
60629
+ const socialPlan = planGaSyncCoverage(app.db, { ...planInput, component: "social" }, startedAt);
60630
+ const longestWindow = windowMetrics.at(-1);
60631
+ const dailyPlan = planGaSyncCoverage(app.db, {
60632
+ ...planInput,
60633
+ component: "daily",
60634
+ requested: { startDate: longestWindow.previous.startDate, endDate: shiftIsoCalendarDate(longestWindow.current.endDate, 1) }
60635
+ }, startedAt);
60636
+ const acquisitionPlan = planGaSyncCoverage(app.db, {
60637
+ ...planInput,
60638
+ component: "acquisition",
60639
+ requested: { startDate: shiftIsoCalendarDate(requested.endDate, -acquisitionDays), endDate: requested.endDate }
60640
+ }, startedAt);
60641
+ const leadPlan = planGaSyncCoverage(app.db, {
60642
+ ...planInput,
60643
+ component: "leads",
60644
+ definition: JSON.stringify([definition2, ...leadEventNames]),
60645
+ requested: { startDate: shiftIsoCalendarDate(requested.endDate, -leadDays), endDate: requested.endDate }
60646
+ }, startedAt);
60647
+ const searchRequestedAt = /* @__PURE__ */ new Date();
60648
+ const searchTimeZone = windowMetrics[0].timeZone ?? null;
60649
+ const searchWindows = syncSearchLanding ? searchLandingWindowsToFetch(app.db, project.id, propertyId, searchTimeZone, searchRequestedAt.toISOString()) : [];
60529
60650
  const measurementFetch = Promise.allSettled([
60530
- fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays),
60531
- leadEventNames.length > 0 ? fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays) : Promise.resolve(null),
60532
- // Fails soft like the components above: its own state, never the run.
60533
- syncSearchLanding ? fetchSearchLandingPages(accessToken, propertyId) : Promise.resolve(null)
60651
+ fetchGaSyncRanges(acquisitionPlan, (range) => fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays, { dateRange: range })),
60652
+ leadEventNames.length > 0 ? fetchGaSyncRanges(leadPlan, (range) => fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays, range)) : Promise.resolve(null),
60653
+ syncSearchLanding ? searchWindows.length > 0 ? fetchSearchLandingPages(accessToken, propertyId, {
60654
+ windows: searchWindows,
60655
+ now: searchRequestedAt,
60656
+ timeZone: searchTimeZone ?? void 0
60657
+ }) : Promise.resolve({ status: "ready", windows: [] }) : Promise.resolve(null)
60534
60658
  ]);
60535
- const results = await Promise.all(fetches);
60536
- const summary3 = results[0];
60537
- const windowSummaries = results.slice(1, 1 + WINDOW_KEYS.length);
60538
- const dailyTotals = results[1 + WINDOW_KEYS.length];
60539
- const windowMetrics = results.slice(2 + WINDOW_KEYS.length, 2 + 2 * WINDOW_KEYS.length);
60540
- let idx = 2 + 2 * WINDOW_KEYS.length;
60541
- if (syncTraffic) {
60542
- rows = results[idx++];
60543
- }
60544
- if (syncAi) {
60545
- aiReferrals = results[idx++];
60546
- }
60547
- if (syncSocial) {
60548
- socialReferrals = results[idx++];
60549
- }
60659
+ const [dailyReports, trafficReports, aiReports, socialReports] = await Promise.all([
60660
+ fetchGaSyncRanges(dailyPlan, (range) => fetchDailyTotals(accessToken, propertyId, void 0, { dateRange: range })),
60661
+ fetchGaSyncRanges(trafficPlan, (range) => fetchTrafficByLandingPage(accessToken, propertyId, days, range)),
60662
+ syncAi ? fetchGaSyncRanges(aiPlan, (range) => fetchAiReferrals(accessToken, propertyId, days, range)) : Promise.resolve([]),
60663
+ syncSocial ? fetchGaSyncRanges(socialPlan, (range) => fetchSocialReferrals(accessToken, propertyId, days, range)) : Promise.resolve([])
60664
+ ]);
60665
+ rows = trafficReports.flatMap((result) => result.report);
60666
+ aiReferrals = aiReports.flatMap((result) => result.report);
60667
+ socialReferrals = socialReports.flatMap((result) => result.report);
60668
+ const dailyTotals = { rows: dailyReports.flatMap((result) => result.report.rows) };
60550
60669
  const now = (/* @__PURE__ */ new Date()).toISOString();
60670
+ if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
60671
+ throw validationError("GA4 property changed during sync; retry against the current connection");
60672
+ }
60551
60673
  app.db.transaction((tx) => {
60552
60674
  if (syncTraffic) {
60553
- tx.delete(gaTrafficSnapshots).where(
60554
- and73(
60555
- eq85(gaTrafficSnapshots.projectId, project.id),
60556
- sql37`${gaTrafficSnapshots.date} >= ${summary3.periodStart}`,
60557
- sql37`${gaTrafficSnapshots.date} <= ${summary3.periodEnd}`
60558
- )
60559
- ).run();
60675
+ for (const { range } of trafficReports) {
60676
+ tx.delete(gaTrafficSnapshots).where(
60677
+ and74(
60678
+ eq86(gaTrafficSnapshots.projectId, project.id),
60679
+ sql37`${gaTrafficSnapshots.date} >= ${range.startDate}`,
60680
+ sql37`${gaTrafficSnapshots.date} <= ${range.endDate}`
60681
+ )
60682
+ ).run();
60683
+ }
60560
60684
  for (const row of rows) {
60561
60685
  tx.insert(gaTrafficSnapshots).values({
60562
60686
  id: crypto51.randomUUID(),
@@ -60573,13 +60697,15 @@ async function ga4Routes(app, opts) {
60573
60697
  }).run();
60574
60698
  }
60575
60699
  }
60576
- tx.delete(gaDailyTotals).where(
60577
- and73(
60578
- eq85(gaDailyTotals.projectId, project.id),
60579
- sql37`${gaDailyTotals.date} >= ${dailyTotals.startDate}`,
60580
- sql37`${gaDailyTotals.date} <= ${dailyTotals.endDate}`
60581
- )
60582
- ).run();
60700
+ for (const { report } of dailyReports) {
60701
+ tx.delete(gaDailyTotals).where(
60702
+ and74(
60703
+ eq86(gaDailyTotals.projectId, project.id),
60704
+ sql37`${gaDailyTotals.date} >= ${report.startDate}`,
60705
+ sql37`${gaDailyTotals.date} <= ${report.endDate}`
60706
+ )
60707
+ ).run();
60708
+ }
60583
60709
  for (const row of dailyTotals.rows) {
60584
60710
  tx.insert(gaDailyTotals).values({
60585
60711
  id: crypto51.randomUUID(),
@@ -60605,7 +60731,7 @@ async function ga4Routes(app, opts) {
60605
60731
  createdAt: now
60606
60732
  }).run();
60607
60733
  }
60608
- tx.delete(gaWindowMetrics).where(eq85(gaWindowMetrics.projectId, project.id)).run();
60734
+ tx.delete(gaWindowMetrics).where(eq86(gaWindowMetrics.projectId, project.id)).run();
60609
60735
  for (const windowMetric of windowMetrics) {
60610
60736
  for (const [period2, totals] of [
60611
60737
  [GaOverviewPeriods.current, windowMetric.current],
@@ -60635,13 +60761,15 @@ async function ga4Routes(app, opts) {
60635
60761
  }
60636
60762
  }
60637
60763
  if (syncAi) {
60638
- tx.delete(gaAiReferrals).where(
60639
- and73(
60640
- eq85(gaAiReferrals.projectId, project.id),
60641
- sql37`${gaAiReferrals.date} >= ${summary3.periodStart}`,
60642
- sql37`${gaAiReferrals.date} <= ${summary3.periodEnd}`
60643
- )
60644
- ).run();
60764
+ for (const { range } of aiReports) {
60765
+ tx.delete(gaAiReferrals).where(
60766
+ and74(
60767
+ eq86(gaAiReferrals.projectId, project.id),
60768
+ sql37`${gaAiReferrals.date} >= ${range.startDate}`,
60769
+ sql37`${gaAiReferrals.date} <= ${range.endDate}`
60770
+ )
60771
+ ).run();
60772
+ }
60645
60773
  for (const row of aiReferrals) {
60646
60774
  tx.insert(gaAiReferrals).values({
60647
60775
  id: crypto51.randomUUID(),
@@ -60667,13 +60795,15 @@ async function ga4Routes(app, opts) {
60667
60795
  }
60668
60796
  }
60669
60797
  if (syncSocial) {
60670
- tx.delete(gaSocialReferrals).where(
60671
- and73(
60672
- eq85(gaSocialReferrals.projectId, project.id),
60673
- sql37`${gaSocialReferrals.date} >= ${summary3.periodStart}`,
60674
- sql37`${gaSocialReferrals.date} <= ${summary3.periodEnd}`
60675
- )
60676
- ).run();
60798
+ for (const { range } of socialReports) {
60799
+ tx.delete(gaSocialReferrals).where(
60800
+ and74(
60801
+ eq86(gaSocialReferrals.projectId, project.id),
60802
+ sql37`${gaSocialReferrals.date} >= ${range.startDate}`,
60803
+ sql37`${gaSocialReferrals.date} <= ${range.endDate}`
60804
+ )
60805
+ ).run();
60806
+ }
60677
60807
  for (const row of socialReferrals) {
60678
60808
  tx.insert(gaSocialReferrals).values({
60679
60809
  id: crypto51.randomUUID(),
@@ -60690,7 +60820,7 @@ async function ga4Routes(app, opts) {
60690
60820
  }
60691
60821
  }
60692
60822
  if (syncSummary) {
60693
- tx.delete(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).run();
60823
+ tx.delete(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).run();
60694
60824
  tx.insert(gaTrafficSummaries).values({
60695
60825
  id: crypto51.randomUUID(),
60696
60826
  projectId: project.id,
@@ -60702,7 +60832,7 @@ async function ga4Routes(app, opts) {
60702
60832
  syncedAt: now,
60703
60833
  syncRunId: runId
60704
60834
  }).run();
60705
- tx.delete(gaTrafficWindowSummaries).where(eq85(gaTrafficWindowSummaries.projectId, project.id)).run();
60835
+ tx.delete(gaTrafficWindowSummaries).where(eq86(gaTrafficWindowSummaries.projectId, project.id)).run();
60706
60836
  for (const ws of windowSummaries) {
60707
60837
  tx.insert(gaTrafficWindowSummaries).values({
60708
60838
  id: crypto51.randomUUID(),
@@ -60719,9 +60849,16 @@ async function ga4Routes(app, opts) {
60719
60849
  }).run();
60720
60850
  }
60721
60851
  }
60852
+ saveGaSyncCoverage(tx, trafficPlan, trafficReports.map((result) => result.range), now, runId);
60853
+ saveGaSyncCoverage(tx, dailyPlan, dailyReports.map((result) => result.report), now, runId);
60854
+ if (syncAi) saveGaSyncCoverage(tx, aiPlan, aiReports.map((result) => result.range), now, runId);
60855
+ if (syncSocial) saveGaSyncCoverage(tx, socialPlan, socialReports.map((result) => result.range), now, runId);
60722
60856
  });
60723
60857
  const [acquisitionSettled, leadsSettled, searchLandingSettled] = await measurementFetch;
60724
60858
  const measurementNow = (/* @__PURE__ */ new Date()).toISOString();
60859
+ if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
60860
+ throw validationError("GA4 property changed during sync; retry against the current connection");
60861
+ }
60725
60862
  const recordMeasurementFailure = (component, reason) => {
60726
60863
  componentFailure ??= { error: reason };
60727
60864
  const message = describeError(reason);
@@ -60748,16 +60885,21 @@ async function ga4Routes(app, opts) {
60748
60885
  return recordMeasurementFailure("acquisition", acquisitionSettled.reason);
60749
60886
  }
60750
60887
  try {
60751
- persistAcquisitionMeasurement(app.db, {
60752
- projectId: project.id,
60753
- runId,
60754
- syncedAt: measurementNow,
60755
- report: acquisitionSettled.value
60888
+ app.db.transaction((tx) => {
60889
+ for (const { report } of acquisitionSettled.value) {
60890
+ persistAcquisitionMeasurement(tx, {
60891
+ projectId: project.id,
60892
+ runId,
60893
+ syncedAt: measurementNow,
60894
+ report
60895
+ });
60896
+ }
60897
+ saveGaSyncCoverage(tx, acquisitionPlan, acquisitionSettled.value.map((result) => result.report), measurementNow, runId);
60756
60898
  });
60757
60899
  return {
60758
60900
  status: "ready",
60759
60901
  days: acquisitionDays,
60760
- rowCount: acquisitionSettled.value.rows.length
60902
+ rowCount: acquisitionSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0)
60761
60903
  };
60762
60904
  } catch (error) {
60763
60905
  return recordMeasurementFailure("acquisition", error);
@@ -60772,18 +60914,23 @@ async function ga4Routes(app, opts) {
60772
60914
  return { status: "not-configured", days: 0, rowCount: 0 };
60773
60915
  }
60774
60916
  try {
60775
- persistLeadMeasurement(app.db, {
60776
- projectId: project.id,
60777
- runId,
60778
- syncedAt: measurementNow,
60779
- report: leadsSettled.value,
60780
- eventNames: leadEventNames
60917
+ app.db.transaction((tx) => {
60918
+ for (const { report } of leadsSettled.value) {
60919
+ persistLeadMeasurement(tx, {
60920
+ projectId: project.id,
60921
+ runId,
60922
+ syncedAt: measurementNow,
60923
+ report,
60924
+ eventNames: leadEventNames
60925
+ });
60926
+ }
60927
+ saveGaSyncCoverage(tx, leadPlan, leadsSettled.value.map((result) => result.report), measurementNow, runId);
60781
60928
  });
60782
60929
  return {
60783
60930
  status: "ready",
60784
60931
  days: leadDays,
60785
- rowCount: leadsSettled.value.rows.length,
60786
- attributionScope: leadsSettled.value.attributionScope,
60932
+ rowCount: leadsSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0),
60933
+ attributionScope: leadsSettled.value.at(-1).report.attributionScope,
60787
60934
  eventNames: leadEventNames,
60788
60935
  keyEvents: keyEventRead
60789
60936
  };
@@ -60826,19 +60973,24 @@ async function ga4Routes(app, opts) {
60826
60973
  propertyId,
60827
60974
  runId,
60828
60975
  syncedAt: measurementNow,
60829
- windows: report.windows
60976
+ windows: report.windows,
60977
+ reuse: {
60978
+ windows: GA_SEARCH_LANDING_WINDOWS.filter((window2) => !searchWindows.includes(window2)),
60979
+ timeZone: searchTimeZone,
60980
+ requestedAt: searchRequestedAt.toISOString()
60981
+ }
60830
60982
  });
60831
60983
  } catch (error) {
60832
60984
  return recordSearchLandingFailure("error", error);
60833
60985
  }
60834
60986
  })();
60835
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq85(runs.id, runId)).run();
60987
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq86(runs.id, runId)).run();
60836
60988
  const syncedComponents = only ? [
60837
60989
  ...syncTraffic ? ["traffic"] : [],
60838
60990
  ...syncSummary ? ["summary"] : [],
60839
60991
  ...syncAi ? ["ai"] : [],
60840
60992
  ...syncSocial ? ["social"] : [],
60841
- ...searchLandingResult?.status === "ready" ? ["search-landing"] : []
60993
+ ...searchLandingResult?.status === "ready" && searchLandingResult.windows.length > 0 ? ["search-landing"] : []
60842
60994
  ] : void 0;
60843
60995
  gaLog("info", "sync.complete", {
60844
60996
  projectId: project.id,
@@ -60878,7 +61030,7 @@ async function ga4Routes(app, opts) {
60878
61030
  } catch (e) {
60879
61031
  const msg = describeError(e);
60880
61032
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
60881
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq85(runs.id, runId)).run();
61033
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq86(runs.id, runId)).run();
60882
61034
  outcome2.report({ status: OutcomeStatuses.failed, ...ga4SyncFailure(e, credentialsResolved, opts, project) });
60883
61035
  throw e;
60884
61036
  }
@@ -60887,7 +61039,7 @@ async function ga4Routes(app, opts) {
60887
61039
  const project = resolveProject(app.db, request.params.name);
60888
61040
  requireGa4Connection(opts, project.name, project.canonicalDomain);
60889
61041
  const limit = Math.max(1, Math.min(parseInt(request.query.limit ?? "50", 10) || 50, 500));
60890
- const range = resolveDateRange(request.query);
61042
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
60891
61043
  const windowSummaryRow = range.startDate && !range.explicitDates ? app.db.select({
60892
61044
  periodStart: gaTrafficWindowSummaries.periodStart,
60893
61045
  periodEnd: gaTrafficWindowSummaries.periodEnd,
@@ -60896,9 +61048,9 @@ async function ga4Routes(app, opts) {
60896
61048
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
60897
61049
  totalUsers: gaTrafficWindowSummaries.totalUsers
60898
61050
  }).from(gaTrafficWindowSummaries).where(
60899
- and73(
60900
- eq85(gaTrafficWindowSummaries.projectId, project.id),
60901
- eq85(gaTrafficWindowSummaries.windowKey, range.window)
61051
+ and74(
61052
+ eq86(gaTrafficWindowSummaries.projectId, project.id),
61053
+ eq86(gaTrafficWindowSummaries.windowKey, range.window)
60902
61054
  )
60903
61055
  ).get() : null;
60904
61056
  const projectSummaryRow = app.db.select({
@@ -60907,11 +61059,11 @@ async function ga4Routes(app, opts) {
60907
61059
  totalSessions: gaTrafficSummaries.totalSessions,
60908
61060
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
60909
61061
  totalUsers: gaTrafficSummaries.totalUsers
60910
- }).from(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).get();
61062
+ }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).get();
60911
61063
  const retainedBounds = [
60912
- app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq85(gaTrafficSnapshots.projectId, project.id)).get(),
60913
- app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq85(gaAiReferrals.projectId, project.id)).get(),
60914
- app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(eq85(gaSocialReferrals.projectId, project.id)).get()
61064
+ app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).get(),
61065
+ app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get(),
61066
+ app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(eq86(gaSocialReferrals.projectId, project.id)).get()
60915
61067
  ];
60916
61068
  const retainedDates = retainedBounds.flatMap((bound) => [bound?.min, bound?.max]).filter((date) => Boolean(date));
60917
61069
  const projectSummaryCoversAll = Boolean(
@@ -60920,15 +61072,15 @@ async function ga4Routes(app, opts) {
60920
61072
  const denominatorRow = windowSummaryRow ?? (range.window === "all" && projectSummaryCoversAll ? projectSummaryRow : null);
60921
61073
  const denominatorPeriod = denominatorRow?.periodStart && denominatorRow.periodEnd ? { startDate: denominatorRow.periodStart, endDate: denominatorRow.periodEnd } : null;
60922
61074
  const measuredRange = denominatorPeriod ? { ...range, ...denominatorPeriod } : range;
60923
- const snapshotConditions = [eq85(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, measuredRange)];
60924
- const aiConditions = [eq85(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, measuredRange)];
60925
- const socialConditions = [eq85(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, measuredRange)];
61075
+ const snapshotConditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, measuredRange)];
61076
+ const aiConditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, measuredRange)];
61077
+ const socialConditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, measuredRange)];
60926
61078
  const snapshotTotalsRow = !denominatorRow ? (() => {
60927
61079
  const summed = app.db.select({
60928
61080
  totalSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
60929
61081
  totalOrganicSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
60930
61082
  totalUsers: sql37`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
60931
- }).from(gaTrafficSnapshots).where(and73(...snapshotConditions)).get();
61083
+ }).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
60932
61084
  return {
60933
61085
  totalSessions: summed?.totalSessions ?? 0,
60934
61086
  totalOrganicSessions: summed?.totalOrganicSessions ?? 0,
@@ -60941,21 +61093,21 @@ async function ga4Routes(app, opts) {
60941
61093
  const summaryRow = denominatorRow ?? snapshotTotalsRow;
60942
61094
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
60943
61095
  totalDirectSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
60944
- }).from(gaTrafficSnapshots).where(and73(...snapshotConditions)).get();
61096
+ }).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
60945
61097
  const rows = app.db.select({
60946
61098
  landingPage: sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
60947
61099
  sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
60948
61100
  organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
60949
61101
  directSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
60950
61102
  users: sql37`SUM(${gaTrafficSnapshots.users})`
60951
- }).from(gaTrafficSnapshots).where(and73(...snapshotConditions)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
61103
+ }).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
60952
61104
  const aiReferralRows = app.db.select({
60953
61105
  source: gaAiReferrals.source,
60954
61106
  medium: gaAiReferrals.medium,
60955
61107
  trafficClass: gaAiReferrals.trafficClass,
60956
61108
  sourceDimension: gaAiReferrals.sourceDimension,
60957
61109
  sessions: sql37`SUM(${gaAiReferrals.sessions})`
60958
- }).from(gaAiReferrals).where(and73(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
61110
+ }).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
60959
61111
  const aiReferralLandingPageRows = app.db.select({
60960
61112
  source: gaAiReferrals.source,
60961
61113
  medium: gaAiReferrals.medium,
@@ -60963,7 +61115,7 @@ async function ga4Routes(app, opts) {
60963
61115
  sourceDimension: gaAiReferrals.sourceDimension,
60964
61116
  landingPage: sql37`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
60965
61117
  sessions: sql37`SUM(${gaAiReferrals.sessions})`
60966
- }).from(gaAiReferrals).where(and73(...aiConditions)).groupBy(
61118
+ }).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(
60967
61119
  gaAiReferrals.source,
60968
61120
  gaAiReferrals.medium,
60969
61121
  gaAiReferrals.trafficClass,
@@ -60978,7 +61130,7 @@ async function ga4Routes(app, opts) {
60978
61130
  sourceDimension: gaAiReferrals.sourceDimension,
60979
61131
  channelGroup: gaAiReferrals.channelGroup,
60980
61132
  sessions: gaAiReferrals.sessions
60981
- }).from(gaAiReferrals).where(and73(...aiConditions)).all();
61133
+ }).from(gaAiReferrals).where(and74(...aiConditions)).all();
60982
61134
  const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
60983
61135
  const aiReferralLandingPages = keepWinningDimensionRows(
60984
61136
  aiReferralLandingPageRows,
@@ -60995,16 +61147,16 @@ async function ga4Routes(app, opts) {
60995
61147
  // dates counts a returning visitor once per day they returned. Same
60996
61148
  // conclusion the AI-referral surfaces reached in 4.135.0.
60997
61149
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
60998
- }).from(gaSocialReferrals).where(and73(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql37`SUM(${gaSocialReferrals.sessions}) DESC`).all();
61150
+ }).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql37`SUM(${gaSocialReferrals.sessions}) DESC`).all();
60999
61151
  const socialDailyRows = app.db.select({
61000
61152
  date: gaSocialReferrals.date,
61001
61153
  source: gaSocialReferrals.source,
61002
61154
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61003
- }).from(gaSocialReferrals).where(and73(...socialConditions)).groupBy(gaSocialReferrals.date, gaSocialReferrals.source).all();
61155
+ }).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.date, gaSocialReferrals.source).all();
61004
61156
  const socialTotals = app.db.select({
61005
61157
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61006
- }).from(gaSocialReferrals).where(and73(...socialConditions)).get();
61007
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
61158
+ }).from(gaSocialReferrals).where(and74(...socialConditions)).get();
61159
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
61008
61160
  const total = summaryRow?.totalSessions ?? 0;
61009
61161
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
61010
61162
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -61126,8 +61278,8 @@ async function ga4Routes(app, opts) {
61126
61278
  app.get("/projects/:name/ga/ai-referral-history", async (request, _reply) => {
61127
61279
  const project = resolveProject(app.db, request.params.name);
61128
61280
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61129
- const range = resolveDateRange(request.query);
61130
- const conditions = [eq85(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61281
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61282
+ const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61131
61283
  const rows = app.db.select({
61132
61284
  date: gaAiReferrals.date,
61133
61285
  source: gaAiReferrals.source,
@@ -61140,7 +61292,7 @@ async function ga4Routes(app, opts) {
61140
61292
  // added up. ga4AiReferralDtoSchema.users was deprecated for this in
61141
61293
  // 4.135.0; the history endpoint kept emitting one.
61142
61294
  sessions: sql37`SUM(${gaAiReferrals.sessions})`
61143
- }).from(gaAiReferrals).where(and73(...conditions)).groupBy(
61295
+ }).from(gaAiReferrals).where(and74(...conditions)).groupBy(
61144
61296
  gaAiReferrals.date,
61145
61297
  gaAiReferrals.source,
61146
61298
  gaAiReferrals.medium,
@@ -61153,8 +61305,8 @@ async function ga4Routes(app, opts) {
61153
61305
  app.get("/projects/:name/ga/ai-referral-daily", async (request, _reply) => {
61154
61306
  const project = resolveProject(app.db, request.params.name);
61155
61307
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61156
- const range = resolveDateRange(request.query);
61157
- const conditions = [eq85(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61308
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61309
+ const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61158
61310
  const rows = app.db.select({
61159
61311
  date: gaAiReferrals.date,
61160
61312
  source: gaAiReferrals.source,
@@ -61163,17 +61315,17 @@ async function ga4Routes(app, opts) {
61163
61315
  sourceDimension: gaAiReferrals.sourceDimension,
61164
61316
  channelGroup: gaAiReferrals.channelGroup,
61165
61317
  sessions: gaAiReferrals.sessions
61166
- }).from(gaAiReferrals).where(and73(...conditions)).all();
61318
+ }).from(gaAiReferrals).where(and74(...conditions)).all();
61167
61319
  const series = buildAiReferralDailySeries(rows);
61168
- const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(and73(eq85(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range))).all().map((row) => row.date);
61169
- const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq85(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
61320
+ const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(and74(eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range))).all().map((row) => row.date);
61321
+ const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
61170
61322
  return { ...series, trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, /* @__PURE__ */ new Date()) };
61171
61323
  });
61172
61324
  app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
61173
61325
  const project = resolveProject(app.db, request.params.name);
61174
61326
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61175
- const range = resolveDateRange(request.query);
61176
- const conditions = [eq85(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, range)];
61327
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61328
+ const conditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, range)];
61177
61329
  const rows = app.db.select({
61178
61330
  date: gaSocialReferrals.date,
61179
61331
  source: gaSocialReferrals.source,
@@ -61181,7 +61333,7 @@ async function ga4Routes(app, opts) {
61181
61333
  channelGroup: gaSocialReferrals.channelGroup,
61182
61334
  sessions: gaSocialReferrals.sessions,
61183
61335
  users: gaSocialReferrals.users
61184
- }).from(gaSocialReferrals).where(and73(...conditions)).orderBy(gaSocialReferrals.date).all();
61336
+ }).from(gaSocialReferrals).where(and74(...conditions)).orderBy(gaSocialReferrals.date).all();
61185
61337
  return rows;
61186
61338
  });
61187
61339
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -61194,8 +61346,8 @@ async function ga4Routes(app, opts) {
61194
61346
  d.setDate(d.getDate() - n);
61195
61347
  return fmt(d);
61196
61348
  };
61197
- const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and73(
61198
- eq85(gaSocialReferrals.projectId, project.id),
61349
+ const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(
61350
+ eq86(gaSocialReferrals.projectId, project.id),
61199
61351
  sql37`${gaSocialReferrals.date} >= ${from}`,
61200
61352
  sql37`${gaSocialReferrals.date} < ${to}`
61201
61353
  )).get();
@@ -61206,16 +61358,16 @@ async function ga4Routes(app, opts) {
61206
61358
  const sourceCurrent = app.db.select({
61207
61359
  source: gaSocialReferrals.source,
61208
61360
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61209
- }).from(gaSocialReferrals).where(and73(
61210
- eq85(gaSocialReferrals.projectId, project.id),
61361
+ }).from(gaSocialReferrals).where(and74(
61362
+ eq86(gaSocialReferrals.projectId, project.id),
61211
61363
  sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
61212
61364
  sql37`${gaSocialReferrals.date} < ${fmt(today)}`
61213
61365
  )).groupBy(gaSocialReferrals.source).all();
61214
61366
  const sourcePrev = app.db.select({
61215
61367
  source: gaSocialReferrals.source,
61216
61368
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61217
- }).from(gaSocialReferrals).where(and73(
61218
- eq85(gaSocialReferrals.projectId, project.id),
61369
+ }).from(gaSocialReferrals).where(and74(
61370
+ eq86(gaSocialReferrals.projectId, project.id),
61219
61371
  sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
61220
61372
  sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`
61221
61373
  )).groupBy(gaSocialReferrals.source).all();
@@ -61240,16 +61392,16 @@ async function ga4Routes(app, opts) {
61240
61392
  d.setDate(d.getDate() - n);
61241
61393
  return fmt(d);
61242
61394
  };
61243
- const sumTotal = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and73(eq85(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61244
- const sumOrganic = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and73(eq85(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61245
- const sumDirect = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and73(eq85(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61246
- const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and73(
61247
- eq85(gaAiReferrals.projectId, project.id),
61395
+ const sumTotal = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61396
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61397
+ const sumDirect = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61398
+ const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
61399
+ eq86(gaAiReferrals.projectId, project.id),
61248
61400
  sql37`${gaAiReferrals.date} >= ${from}`,
61249
61401
  sql37`${gaAiReferrals.date} < ${to}`,
61250
- eq85(gaAiReferrals.sourceDimension, "session")
61402
+ eq86(gaAiReferrals.sourceDimension, "session")
61251
61403
  )).get();
61252
- const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and73(eq85(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${from}`, sql37`${gaSocialReferrals.date} < ${to}`)).get();
61404
+ const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${from}`, sql37`${gaSocialReferrals.date} < ${to}`)).get();
61253
61405
  const todayStr = fmt(today);
61254
61406
  const buildTrend = (sum) => {
61255
61407
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -61258,20 +61410,20 @@ async function ga4Routes(app, opts) {
61258
61410
  const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
61259
61411
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: deltaPercent(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: deltaPercent(c30, p30) };
61260
61412
  };
61261
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and73(
61262
- eq85(gaAiReferrals.projectId, project.id),
61413
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
61414
+ eq86(gaAiReferrals.projectId, project.id),
61263
61415
  sql37`${gaAiReferrals.date} >= ${daysAgo(7)}`,
61264
61416
  sql37`${gaAiReferrals.date} < ${todayStr}`,
61265
- eq85(gaAiReferrals.sourceDimension, "session")
61417
+ eq86(gaAiReferrals.sourceDimension, "session")
61266
61418
  )).groupBy(gaAiReferrals.source).all();
61267
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and73(
61268
- eq85(gaAiReferrals.projectId, project.id),
61419
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
61420
+ eq86(gaAiReferrals.projectId, project.id),
61269
61421
  sql37`${gaAiReferrals.date} >= ${daysAgo(14)}`,
61270
61422
  sql37`${gaAiReferrals.date} < ${daysAgo(7)}`,
61271
- eq85(gaAiReferrals.sourceDimension, "session")
61423
+ eq86(gaAiReferrals.sourceDimension, "session")
61272
61424
  )).groupBy(gaAiReferrals.source).all();
61273
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and73(eq85(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql37`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
61274
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and73(eq85(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
61425
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql37`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
61426
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
61275
61427
  const response = {
61276
61428
  total: buildTrend(sumTotal),
61277
61429
  organic: buildTrend(sumOrganic),
@@ -61287,14 +61439,14 @@ async function ga4Routes(app, opts) {
61287
61439
  const project = resolveProject(app.db, request.params.name);
61288
61440
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61289
61441
  const window2 = parseGaOverviewWindow(request.query.window);
61290
- const periodRows = app.db.select().from(gaWindowMetrics).where(and73(eq85(gaWindowMetrics.projectId, project.id), eq85(gaWindowMetrics.windowKey, window2))).all();
61442
+ const periodRows = app.db.select().from(gaWindowMetrics).where(and74(eq86(gaWindowMetrics.projectId, project.id), eq86(gaWindowMetrics.windowKey, window2))).all();
61291
61443
  const current = periodRows.find((row) => row.period === GaOverviewPeriods.current) ?? null;
61292
61444
  const previous = periodRows.find((row) => row.period === GaOverviewPeriods.previous) ?? null;
61293
61445
  const fallback = gaOverviewComparisonRanges(window2);
61294
61446
  const from = previous?.startDate ?? fallback.previous.startDate;
61295
61447
  const to = current?.endDate ?? fallback.current.endDate;
61296
- const daily = app.db.select().from(gaDailyTotals).where(and73(
61297
- eq85(gaDailyTotals.projectId, project.id),
61448
+ const daily = app.db.select().from(gaDailyTotals).where(and74(
61449
+ eq86(gaDailyTotals.projectId, project.id),
61298
61450
  sql37`${gaDailyTotals.date} >= ${from}`,
61299
61451
  sql37`${gaDailyTotals.date} <= ${to}`
61300
61452
  )).all();
@@ -61303,16 +61455,16 @@ async function ga4Routes(app, opts) {
61303
61455
  app.get("/projects/:name/ga/session-history", async (request, _reply) => {
61304
61456
  const project = resolveProject(app.db, request.params.name);
61305
61457
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61306
- const range = resolveDateRange(request.query);
61307
- const conditions = [eq85(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, range)];
61458
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61459
+ const conditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, range)];
61308
61460
  const rows = app.db.select({
61309
61461
  date: gaTrafficSnapshots.date,
61310
61462
  sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
61311
61463
  organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
61312
61464
  users: sql37`SUM(${gaTrafficSnapshots.users})`
61313
- }).from(gaTrafficSnapshots).where(and73(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
61314
- const totalConditions = [eq85(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range)];
61315
- const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and73(...totalConditions)).all();
61465
+ }).from(gaTrafficSnapshots).where(and74(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
61466
+ const totalConditions = [eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range)];
61467
+ const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and74(...totalConditions)).all();
61316
61468
  return buildSessionHistory(
61317
61469
  rows.map((r) => ({
61318
61470
  date: r.date,
@@ -61331,7 +61483,7 @@ async function ga4Routes(app, opts) {
61331
61483
  sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
61332
61484
  organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
61333
61485
  users: sql37`SUM(${gaTrafficSnapshots.users})`
61334
- }).from(gaTrafficSnapshots).where(eq85(gaTrafficSnapshots.projectId, project.id)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
61486
+ }).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
61335
61487
  return {
61336
61488
  pages: trafficPages.map((r) => ({
61337
61489
  landingPage: r.landingPage,
@@ -61344,7 +61496,7 @@ async function ga4Routes(app, opts) {
61344
61496
  }
61345
61497
 
61346
61498
  // ../api-routes/src/ga-acquisition.ts
61347
- import { and as and74, asc as asc22, count as count6, desc as desc39, eq as eq86, gte as gte24, lte as lte24, sql as sql38 } from "drizzle-orm";
61499
+ import { and as and75, asc as asc22, count as count6, desc as desc39, eq as eq87, gte as gte24, lte as lte24, sql as sql38 } from "drizzle-orm";
61348
61500
  async function gaAcquisitionRoutes(app) {
61349
61501
  app.get("/projects/:name/ga/acquisition", async (request) => {
61350
61502
  const project = resolveProject(app.db, request.params.name);
@@ -61367,14 +61519,14 @@ async function gaAcquisitionRoutes(app) {
61367
61519
  hostName: gaAcquisitionDaily.hostName,
61368
61520
  landingPage
61369
61521
  };
61370
- const windowFilters = [eq86(gaAcquisitionDaily.projectId, project.id)];
61522
+ const windowFilters = [eq87(gaAcquisitionDaily.projectId, project.id)];
61371
61523
  if (range.startDate) windowFilters.push(gte24(gaAcquisitionDaily.date, range.startDate));
61372
61524
  if (range.endDate) windowFilters.push(lte24(gaAcquisitionDaily.date, range.endDate));
61373
61525
  const filters = [...windowFilters];
61374
61526
  if (query2.channel !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.channelGroup}) = lower(${query2.channel})`);
61375
61527
  if (query2.hostName !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.hostName}) = lower(${query2.hostName})`);
61376
- if (query2.source !== void 0) filters.push(eq86(gaAcquisitionDaily.source, query2.source));
61377
- if (query2.medium !== void 0) filters.push(eq86(gaAcquisitionDaily.medium, query2.medium));
61528
+ if (query2.source !== void 0) filters.push(eq87(gaAcquisitionDaily.source, query2.source));
61529
+ if (query2.medium !== void 0) filters.push(eq87(gaAcquisitionDaily.medium, query2.medium));
61378
61530
  const selectedLandingPage = query2.landingPage === void 0 ? void 0 : normalizeUrlPath(query2.landingPage) ?? query2.landingPage;
61379
61531
  if (selectedLandingPage !== void 0) filters.push(sql38`${landingPage} = ${selectedLandingPage}`);
61380
61532
  const pathPrefix = normalizePathPrefix(query2.pathPrefix);
@@ -61382,7 +61534,7 @@ async function gaAcquisitionRoutes(app) {
61382
61534
  const pathname = sql38`case when instr(${landingPage}, '?') = 0 then ${landingPage} else substr(${landingPage}, 1, instr(${landingPage}, '?') - 1) end`;
61383
61535
  filters.push(literalPathPrefixCondition(pathname, pathPrefix));
61384
61536
  }
61385
- const where = and74(...filters);
61537
+ const where = and75(...filters);
61386
61538
  const sessions = sql38`coalesce(sum(${gaAcquisitionDaily.sessions}), 0)`.mapWith(Number);
61387
61539
  const selected = Object.fromEntries(groupBy.map((key) => [key, dimensions[key]]));
61388
61540
  const grouped = app.db.select({ ...selected, sessions }).from(gaAcquisitionDaily).where(where).groupBy(...groupBy.map((key) => dimensions[key]));
@@ -61394,9 +61546,9 @@ async function gaAcquisitionRoutes(app) {
61394
61546
  ...groupBy.filter((key) => key !== GaAcquisitionDimensions.date).map((key) => asc22(dimensions[key]))
61395
61547
  ];
61396
61548
  const rows = grouped.orderBy(...order).limit(limit).offset(offset).all();
61397
- const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(eq86(gaAcquisitionDaily.projectId, project.id)).get();
61398
- const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(and74(...windowFilters)).orderBy(asc22(gaAcquisitionDaily.channelGroup)).all().map((row) => row.channel);
61399
- const state = app.db.select().from(gaMeasurementSyncStates).where(eq86(gaMeasurementSyncStates.projectId, project.id)).get();
61549
+ const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(eq87(gaAcquisitionDaily.projectId, project.id)).get();
61550
+ const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(and75(...windowFilters)).orderBy(asc22(gaAcquisitionDaily.channelGroup)).all().map((row) => row.channel);
61551
+ const state = app.db.select().from(gaMeasurementSyncStates).where(eq87(gaMeasurementSyncStates.projectId, project.id)).get();
61400
61552
  const response = {
61401
61553
  source: "ga4-acquisition",
61402
61554
  status: state?.acquisitionStatus ?? "never-synced",
@@ -63128,7 +63280,7 @@ async function wordpressRoutes(app, opts) {
63128
63280
 
63129
63281
  // ../api-routes/src/backlinks.ts
63130
63282
  import crypto53 from "crypto";
63131
- import { and as and77, asc as asc23, desc as desc40, eq as eq88, sql as sql39 } from "drizzle-orm";
63283
+ import { and as and78, asc as asc23, desc as desc40, eq as eq89, sql as sql39 } from "drizzle-orm";
63132
63284
 
63133
63285
  // ../integration-commoncrawl/src/constants.ts
63134
63286
  import os2 from "os";
@@ -63578,7 +63730,7 @@ function pruneCachedRelease(release, opts = {}) {
63578
63730
  }
63579
63731
 
63580
63732
  // ../api-routes/src/backlinks-filter.ts
63581
- import { and as and75, ne as ne15, notLike } from "drizzle-orm";
63733
+ import { and as and76, ne as ne15, notLike } from "drizzle-orm";
63582
63734
  var BACKLINK_FILTER_PATTERNS = [
63583
63735
  "*.google.com",
63584
63736
  "*.googleusercontent.com",
@@ -63601,23 +63753,23 @@ function backlinkCrawlerExclusionClause() {
63601
63753
  conditions.push(ne15(backlinkDomains.linkingDomain, pattern));
63602
63754
  }
63603
63755
  }
63604
- const combined = and75(...conditions);
63756
+ const combined = and76(...conditions);
63605
63757
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
63606
63758
  return combined;
63607
63759
  }
63608
63760
 
63609
63761
  // ../api-routes/src/backlinks-source-state.ts
63610
- import { and as and76, eq as eq87, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
63762
+ import { and as and77, eq as eq88, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
63611
63763
  function readCommonCrawlSourceState(db, projectId) {
63612
63764
  const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
63613
- eq87(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
63765
+ eq88(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
63614
63766
  isNotNull8(ccReleaseSyncs.queryFinishedAt)
63615
63767
  )).all();
63616
63768
  const hasReadyRelease = completedReleases.length > 0;
63617
63769
  const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
63618
- const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and76(
63619
- eq87(backlinkSummaries.projectId, projectId),
63620
- eq87(backlinkSummaries.source, BacklinkSources.commoncrawl)
63770
+ const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and77(
63771
+ eq88(backlinkSummaries.projectId, projectId),
63772
+ eq88(backlinkSummaries.source, BacklinkSources.commoncrawl)
63621
63773
  )).limit(1).get() !== void 0;
63622
63774
  return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
63623
63775
  }
@@ -63688,11 +63840,11 @@ function mapRunRow(row) {
63688
63840
  }
63689
63841
  function latestSummaryForProject(db, projectId, source, release) {
63690
63842
  const conditions = [
63691
- eq88(backlinkSummaries.projectId, projectId),
63692
- eq88(backlinkSummaries.source, source)
63843
+ eq89(backlinkSummaries.projectId, projectId),
63844
+ eq89(backlinkSummaries.source, source)
63693
63845
  ];
63694
- if (release) conditions.push(eq88(backlinkSummaries.release, release));
63695
- const rows = db.select().from(backlinkSummaries).where(and77(...conditions)).orderBy(desc40(backlinkSummaries.queriedAt)).all();
63846
+ if (release) conditions.push(eq89(backlinkSummaries.release, release));
63847
+ const rows = db.select().from(backlinkSummaries).where(and78(...conditions)).orderBy(desc40(backlinkSummaries.queriedAt)).all();
63696
63848
  return rows.reduce((latest, row) => {
63697
63849
  if (!latest) return row;
63698
63850
  const newer = isValidReleaseId(row.release) && isValidReleaseId(latest.release) && compareReleaseIds(row.release, latest.release) > 0;
@@ -63705,12 +63857,12 @@ function parseExcludeCrawlers(value) {
63705
63857
  return lower === "1" || lower === "true" || lower === "yes";
63706
63858
  }
63707
63859
  function computeFilteredSummary(db, base) {
63708
- const baseDomainCondition = and77(
63709
- eq88(backlinkDomains.projectId, base.projectId),
63710
- eq88(backlinkDomains.source, base.source),
63711
- eq88(backlinkDomains.release, base.release)
63860
+ const baseDomainCondition = and78(
63861
+ eq89(backlinkDomains.projectId, base.projectId),
63862
+ eq89(backlinkDomains.source, base.source),
63863
+ eq89(backlinkDomains.release, base.release)
63712
63864
  );
63713
- const filteredCondition = and77(baseDomainCondition, backlinkCrawlerExclusionClause());
63865
+ const filteredCondition = and78(baseDomainCondition, backlinkCrawlerExclusionClause());
63714
63866
  const unfilteredAgg = db.select({
63715
63867
  count: sql39`count(*)`,
63716
63868
  total: sql39`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -63751,7 +63903,7 @@ function computeFilteredReleaseTotals(db, projectId, source) {
63751
63903
  SELECT ${backlinkDomains.release} AS release, ${backlinkDomains.numHosts} AS num_hosts, ${kept} AS kept,
63752
63904
  ROW_NUMBER() OVER (PARTITION BY ${backlinkDomains.release}, ${kept} ORDER BY ${backlinkDomains.numHosts} DESC) AS kept_rank
63753
63905
  FROM ${backlinkDomains}
63754
- WHERE ${and77(eq88(backlinkDomains.projectId, projectId), eq88(backlinkDomains.source, source))}
63906
+ WHERE ${and78(eq89(backlinkDomains.projectId, projectId), eq89(backlinkDomains.source, source))}
63755
63907
  )
63756
63908
  GROUP BY release`);
63757
63909
  return new Map(rows.map((row) => {
@@ -63765,13 +63917,13 @@ function computeFilteredReleaseTotals(db, projectId, source) {
63765
63917
  }));
63766
63918
  }
63767
63919
  function buildSourceAvailability(db, projectId, source, connected2, excludeCrawlers) {
63768
- const summary3 = db.select().from(backlinkSummaries).where(and77(eq88(backlinkSummaries.projectId, projectId), eq88(backlinkSummaries.source, source))).orderBy(desc40(backlinkSummaries.queriedAt)).limit(1).get();
63920
+ const summary3 = db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, projectId), eq89(backlinkSummaries.source, source))).orderBy(desc40(backlinkSummaries.queriedAt)).limit(1).get();
63769
63921
  let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
63770
63922
  if (summary3 && excludeCrawlers) {
63771
- const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(and77(
63772
- eq88(backlinkDomains.projectId, projectId),
63773
- eq88(backlinkDomains.source, source),
63774
- eq88(backlinkDomains.release, summary3.release),
63923
+ const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(and78(
63924
+ eq89(backlinkDomains.projectId, projectId),
63925
+ eq89(backlinkDomains.source, source),
63926
+ eq89(backlinkDomains.release, summary3.release),
63775
63927
  backlinkCrawlerExclusionClause()
63776
63928
  )).get();
63777
63929
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -63841,7 +63993,7 @@ async function backlinksRoutes(app, opts) {
63841
63993
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
63842
63994
  );
63843
63995
  }
63844
- const existing = app.db.select().from(ccReleaseSyncs).where(eq88(ccReleaseSyncs.release, release)).get();
63996
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.release, release)).get();
63845
63997
  const now = (/* @__PURE__ */ new Date()).toISOString();
63846
63998
  if (existing) {
63847
63999
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -63852,9 +64004,9 @@ async function backlinksRoutes(app, opts) {
63852
64004
  phaseDetail: null,
63853
64005
  error: null,
63854
64006
  updatedAt: now
63855
- }).where(eq88(ccReleaseSyncs.id, existing.id)).run();
64007
+ }).where(eq89(ccReleaseSyncs.id, existing.id)).run();
63856
64008
  opts.onReleaseSyncRequested(existing.id, release);
63857
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq88(ccReleaseSyncs.id, existing.id)).get();
64009
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, existing.id)).get();
63858
64010
  return reply.status(200).send(mapSyncRow(refreshed));
63859
64011
  }
63860
64012
  const id = crypto53.randomUUID();
@@ -63866,7 +64018,7 @@ async function backlinksRoutes(app, opts) {
63866
64018
  updatedAt: now
63867
64019
  }).run();
63868
64020
  opts.onReleaseSyncRequested(id, release);
63869
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq88(ccReleaseSyncs.id, id)).get();
64021
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, id)).get();
63870
64022
  return reply.status(201).send(mapSyncRow(inserted));
63871
64023
  });
63872
64024
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -63924,7 +64076,7 @@ async function backlinksRoutes(app, opts) {
63924
64076
  createdAt: now
63925
64077
  }).run();
63926
64078
  opts.onBacklinkExtractRequested(runId, project.id, release);
63927
- const run = app.db.select().from(runs).where(eq88(runs.id, runId)).get();
64079
+ const run = app.db.select().from(runs).where(eq89(runs.id, runId)).get();
63928
64080
  return reply.status(201).send(mapRunRow(run));
63929
64081
  });
63930
64082
  app.get(
@@ -63950,12 +64102,12 @@ async function backlinksRoutes(app, opts) {
63950
64102
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
63951
64103
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
63952
64104
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
63953
- const baseDomainCondition = and77(
63954
- eq88(backlinkDomains.projectId, project.id),
63955
- eq88(backlinkDomains.source, source),
63956
- eq88(backlinkDomains.release, targetRelease)
64105
+ const baseDomainCondition = and78(
64106
+ eq89(backlinkDomains.projectId, project.id),
64107
+ eq89(backlinkDomains.source, source),
64108
+ eq89(backlinkDomains.release, targetRelease)
63957
64109
  );
63958
- const domainCondition = excludeCrawlers ? and77(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
64110
+ const domainCondition = excludeCrawlers ? and78(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
63959
64111
  const totalRow = app.db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(domainCondition).get();
63960
64112
  const rows = app.db.select({
63961
64113
  linkingDomain: backlinkDomains.linkingDomain,
@@ -63979,7 +64131,7 @@ async function backlinksRoutes(app, opts) {
63979
64131
  async (request, reply) => {
63980
64132
  const project = resolveProject(app.db, request.params.name);
63981
64133
  const source = parseSourceParam(request.query.source);
63982
- const rows = app.db.select().from(backlinkSummaries).where(and77(eq88(backlinkSummaries.projectId, project.id), eq88(backlinkSummaries.source, source))).orderBy(asc23(backlinkSummaries.queriedAt)).all();
64134
+ const rows = app.db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, project.id), eq89(backlinkSummaries.source, source))).orderBy(asc23(backlinkSummaries.queriedAt)).all();
63983
64135
  const response = rows.map((r) => ({
63984
64136
  release: r.release,
63985
64137
  totalLinkingDomains: r.totalLinkingDomains,
@@ -64006,7 +64158,7 @@ async function backlinksRoutes(app, opts) {
64006
64158
  import crypto55 from "crypto";
64007
64159
  import { isIP as isIP3 } from "net";
64008
64160
  import { isDeepStrictEqual } from "util";
64009
- import { and as and79, desc as desc41, eq as eq90, gt as gt7, gte as gte25, inArray as inArray41, lt as lt17, lte as lte26, sql as sql40 } from "drizzle-orm";
64161
+ import { and as and80, desc as desc41, eq as eq91, gt as gt7, gte as gte25, inArray as inArray41, lt as lt18, lte as lte26, sql as sql40 } from "drizzle-orm";
64010
64162
 
64011
64163
  // ../api-routes/src/traffic-limits.ts
64012
64164
  var VERCEL_MAX_SYNC_WINDOW_MS = 24 * 60 * 6e4;
@@ -65349,7 +65501,7 @@ async function ackCloudflareQueueMessages(client, options) {
65349
65501
  }
65350
65502
 
65351
65503
  // ../api-routes/src/traffic-sync-lease.ts
65352
- import { and as and78, eq as eq89, isNull as isNull13, lte as lte25, or as or18 } from "drizzle-orm";
65504
+ import { and as and79, eq as eq90, isNull as isNull13, lte as lte25, or as or18 } from "drizzle-orm";
65353
65505
  function leaseExpiresAt(now, ttlMs) {
65354
65506
  const nowMs = Date.parse(now);
65355
65507
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -65362,12 +65514,12 @@ function tryClaimTrafficSyncLease(input) {
65362
65514
  const changed = tx.update(trafficSources).set({
65363
65515
  syncLeaseOwner: input.owner,
65364
65516
  syncLeaseExpiresAt: expiresAt
65365
- }).where(and78(
65366
- eq89(trafficSources.id, input.sourceId),
65517
+ }).where(and79(
65518
+ eq90(trafficSources.id, input.sourceId),
65367
65519
  or18(
65368
65520
  isNull13(trafficSources.syncLeaseOwner),
65369
65521
  lte25(trafficSources.syncLeaseExpiresAt, input.now),
65370
- eq89(trafficSources.syncLeaseOwner, input.owner)
65522
+ eq90(trafficSources.syncLeaseOwner, input.owner)
65371
65523
  )
65372
65524
  )).run();
65373
65525
  return changed.changes === 1;
@@ -65378,9 +65530,9 @@ function releaseTrafficSyncLease(input) {
65378
65530
  const changed = tx.update(trafficSources).set({
65379
65531
  syncLeaseOwner: null,
65380
65532
  syncLeaseExpiresAt: null
65381
- }).where(and78(
65382
- eq89(trafficSources.id, input.sourceId),
65383
- eq89(trafficSources.syncLeaseOwner, input.owner)
65533
+ }).where(and79(
65534
+ eq90(trafficSources.id, input.sourceId),
65535
+ eq90(trafficSources.syncLeaseOwner, input.owner)
65384
65536
  )).run();
65385
65537
  return changed.changes === 1;
65386
65538
  }, { behavior: "immediate" });
@@ -65447,14 +65599,14 @@ async function loadQueueReceiptPolicy(db, sourceId, readRetentionSeconds, warn2)
65447
65599
  if (liveTtlMs === null) return { ttlMsAt: () => CLOUDFLARE_QUEUE_RECEIPT_TTL_MS };
65448
65600
  const ttlMs = liveTtlMs;
65449
65601
  const now = (/* @__PURE__ */ new Date()).toISOString();
65450
- const alive = and79(eq90(trafficEventReceipts.sourceId, sourceId), gt7(trafficEventReceipts.expiresAt, now));
65602
+ const alive = and80(eq91(trafficEventReceipts.sourceId, sourceId), gt7(trafficEventReceipts.expiresAt, now));
65451
65603
  const lifetimeMs = sql40`(julianday(${trafficEventReceipts.expiresAt}) - julianday(${trafficEventReceipts.receivedAt})) * ${DAY_MS5}`;
65452
65604
  const shortest = db.select({ ms: sql40`min(${lifetimeMs})` }).from(trafficEventReceipts).where(alive).get()?.ms ?? null;
65453
65605
  if (shortest !== null && shortest < ttlMs - 1e3) {
65454
65606
  const extended = sql40`strftime('%Y-%m-%dT%H:%M:%fZ', julianday(${trafficEventReceipts.receivedAt}) + ${ttlMs / DAY_MS5})`;
65455
- db.update(trafficEventReceipts).set({ expiresAt: extended }).where(and79(alive, lt17(trafficEventReceipts.expiresAt, extended))).run();
65607
+ db.update(trafficEventReceipts).set({ expiresAt: extended }).where(and80(alive, lt18(trafficEventReceipts.expiresAt, extended))).run();
65456
65608
  }
65457
- const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(and79(alive, sql40`${lifetimeMs} < ${CLOUDFLARE_QUEUE_RECEIPT_TTL_MS - 1e3}`)).get()?.at ?? null;
65609
+ const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(and80(alive, sql40`${lifetimeMs} < ${CLOUDFLARE_QUEUE_RECEIPT_TTL_MS - 1e3}`)).get()?.at ?? null;
65458
65610
  const floorMs = floorExpiry ? Date.parse(floorExpiry) : 0;
65459
65611
  return { ttlMsAt: (receivedAt) => Math.max(ttlMs, floorMs - Date.parse(receivedAt)) };
65460
65612
  }
@@ -65509,7 +65661,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
65509
65661
  const authenticated = authenticateCloudflareIngest(request, store);
65510
65662
  const params = request.params;
65511
65663
  if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
65512
- const source = db.select().from(trafficSources).where(eq90(trafficSources.id, authenticated.credential.sourceId)).get();
65664
+ const source = db.select().from(trafficSources).where(eq91(trafficSources.id, authenticated.credential.sourceId)).get();
65513
65665
  if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
65514
65666
  return `cloudflare-source:${authenticated.credential.sourceId}`;
65515
65667
  }
@@ -65752,7 +65904,7 @@ async function defaultResolveAccessToken(record2) {
65752
65904
  );
65753
65905
  }
65754
65906
  function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
65755
- return db.select().from(trafficSources).where(eq90(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
65907
+ return db.select().from(trafficSources).where(eq91(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
65756
65908
  }
65757
65909
  function isAuthoritativeTrafficSource(db, source) {
65758
65910
  return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
@@ -65779,13 +65931,13 @@ function wordpressBaseUrlIdentity(value) {
65779
65931
  }
65780
65932
  }
65781
65933
  function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
65782
- const schedule = tx.select().from(schedules).where(and79(
65783
- eq90(schedules.projectId, projectId),
65784
- eq90(schedules.kind, SchedulableRunKinds["traffic-sync"])
65934
+ const schedule = tx.select().from(schedules).where(and80(
65935
+ eq91(schedules.projectId, projectId),
65936
+ eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
65785
65937
  )).get();
65786
65938
  if (schedule) {
65787
65939
  if (schedule.sourceId === sourceId) return { changed: false, created: false };
65788
- tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq90(schedules.id, schedule.id)).run();
65940
+ tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq91(schedules.id, schedule.id)).run();
65789
65941
  return { changed: true, created: false };
65790
65942
  }
65791
65943
  if (!createIfMissing) return { changed: false, created: false };
@@ -65827,7 +65979,7 @@ async function runBackfillTask(options) {
65827
65979
  };
65828
65980
  const renewLease2 = setInterval(() => {
65829
65981
  try {
65830
- const latest = app.db.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
65982
+ const latest = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65831
65983
  if (!latest || !isAuthoritativeTrafficSource(app.db, latest) || !isSameTrafficSourceGeneration(latest, sourceRow2)) {
65832
65984
  clearInterval(renewLease2);
65833
65985
  stopped.abort();
@@ -65875,10 +66027,10 @@ async function runBackfillTaskHoldingLease(options, signal) {
65875
66027
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
65876
66028
  try {
65877
66029
  app.db.transaction((tx) => {
65878
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq90(runs.id, runId)).run();
65879
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66030
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
66031
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65880
66032
  if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
65881
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq90(trafficSources.id, sourceRow2.id)).run();
66033
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
65882
66034
  }
65883
66035
  });
65884
66036
  } catch {
@@ -65910,7 +66062,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
65910
66062
  let emptyOutcome;
65911
66063
  try {
65912
66064
  const completed = app.db.transaction((tx) => {
65913
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66065
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65914
66066
  const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner);
65915
66067
  if (stillAuthoritative) {
65916
66068
  enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
@@ -65919,7 +66071,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
65919
66071
  status: RunStatuses.failed,
65920
66072
  error: "Traffic source was deactivated or reconfigured during backfill",
65921
66073
  finishedAt: finishedAt2
65922
- }).where(eq90(runs.id, runId)).run();
66074
+ }).where(eq91(runs.id, runId)).run();
65923
66075
  return Boolean(stillAuthoritative);
65924
66076
  });
65925
66077
  emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
@@ -65951,13 +66103,13 @@ async function runBackfillTaskHoldingLease(options, signal) {
65951
66103
  const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
65952
66104
  try {
65953
66105
  const commitOutcome = app.db.transaction((tx) => {
65954
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66106
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65955
66107
  if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
65956
66108
  tx.update(runs).set({
65957
66109
  status: RunStatuses.failed,
65958
66110
  error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
65959
66111
  finishedAt
65960
- }).where(eq90(runs.id, runId)).run();
66112
+ }).where(eq91(runs.id, runId)).run();
65961
66113
  return "wordpress-backfill-unsupported";
65962
66114
  }
65963
66115
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
@@ -65965,7 +66117,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
65965
66117
  status: RunStatuses.failed,
65966
66118
  error: "Traffic source was deactivated or reconfigured during backfill",
65967
66119
  finishedAt
65968
- }).where(eq90(runs.id, runId)).run();
66120
+ }).where(eq91(runs.id, runId)).run();
65969
66121
  return "source-inactive";
65970
66122
  }
65971
66123
  const rawSampleCutoff = enforceRawEventSampleRetention(
@@ -65974,37 +66126,37 @@ async function runBackfillTaskHoldingLease(options, signal) {
65974
66126
  finishedAt
65975
66127
  );
65976
66128
  tx.delete(crawlerEventsHourly).where(
65977
- and79(
65978
- eq90(crawlerEventsHourly.sourceId, sourceRow2.id),
66129
+ and80(
66130
+ eq91(crawlerEventsHourly.sourceId, sourceRow2.id),
65979
66131
  gte25(crawlerEventsHourly.tsHour, windowStartIso),
65980
66132
  lte26(crawlerEventsHourly.tsHour, windowEndIso)
65981
66133
  )
65982
66134
  ).run();
65983
66135
  tx.delete(aiUserFetchEventsHourly).where(
65984
- and79(
65985
- eq90(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
66136
+ and80(
66137
+ eq91(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
65986
66138
  gte25(aiUserFetchEventsHourly.tsHour, windowStartIso),
65987
66139
  lte26(aiUserFetchEventsHourly.tsHour, windowEndIso)
65988
66140
  )
65989
66141
  ).run();
65990
66142
  clearLikelyFakeAgentHits(tx, { sourceId: sourceRow2.id, windowStart: windowStartIso, windowEnd: windowEndIso });
65991
66143
  tx.delete(aiReferralEventsHourly).where(
65992
- and79(
65993
- eq90(aiReferralEventsHourly.sourceId, sourceRow2.id),
66144
+ and80(
66145
+ eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
65994
66146
  gte25(aiReferralEventsHourly.tsHour, windowStartIso),
65995
66147
  lte26(aiReferralEventsHourly.tsHour, windowEndIso)
65996
66148
  )
65997
66149
  ).run();
65998
66150
  tx.delete(rawEventSamples).where(
65999
- and79(
66000
- eq90(rawEventSamples.sourceId, sourceRow2.id),
66151
+ and80(
66152
+ eq91(rawEventSamples.sourceId, sourceRow2.id),
66001
66153
  gte25(rawEventSamples.ts, windowStartIso),
66002
66154
  lte26(rawEventSamples.ts, windowEndIso)
66003
66155
  )
66004
66156
  ).run();
66005
66157
  tx.delete(unrecognizedAgentEventsHourly).where(
66006
- and79(
66007
- eq90(unrecognizedAgentEventsHourly.sourceId, sourceRow2.id),
66158
+ and80(
66159
+ eq91(unrecognizedAgentEventsHourly.sourceId, sourceRow2.id),
66008
66160
  gte25(unrecognizedAgentEventsHourly.tsHour, windowStartIso),
66009
66161
  lte26(unrecognizedAgentEventsHourly.tsHour, windowEndIso)
66010
66162
  )
@@ -66212,9 +66364,9 @@ async function runBackfillTaskHoldingLease(options, signal) {
66212
66364
  FROM _migrations WHERE version = 207
66213
66365
  `);
66214
66366
  if (ruleChange?.appliedAt) {
66215
- const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(and79(
66216
- eq90(aiReferralEventsHourly.sourceId, sourceRow2.id),
66217
- lt17(aiReferralEventsHourly.createdAt, ruleChange.appliedAt)
66367
+ const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(and80(
66368
+ eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
66369
+ lt18(aiReferralEventsHourly.createdAt, ruleChange.appliedAt)
66218
66370
  )).limit(1).get();
66219
66371
  if (!olderReferral) olderCountUntil = null;
66220
66372
  }
@@ -66227,8 +66379,8 @@ async function runBackfillTaskHoldingLease(options, signal) {
66227
66379
  aiReferralOlderCountUntil: olderCountUntil,
66228
66380
  ...skipRecovered ? { skippedThroughAt: null } : {},
66229
66381
  updatedAt: finishedAt
66230
- }).where(eq90(trafficSources.id, sourceRow2.id)).run();
66231
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq90(runs.id, runId)).run();
66382
+ }).where(eq91(trafficSources.id, sourceRow2.id)).run();
66383
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
66232
66384
  return "committed";
66233
66385
  });
66234
66386
  reportBackfill(commitOutcome === "committed" ? {
@@ -66401,7 +66553,7 @@ async function trafficRoutes(app, opts) {
66401
66553
  };
66402
66554
  const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
66403
66555
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
66404
- const activeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
66556
+ const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
66405
66557
  const sourceId = activeSource?.id ?? crypto55.randomUUID();
66406
66558
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
66407
66559
  if (activeSource) {
@@ -66411,7 +66563,7 @@ async function trafficRoutes(app, opts) {
66411
66563
  lastError: null,
66412
66564
  configJson: config,
66413
66565
  updatedAt: now
66414
- }).where(eq90(trafficSources.id, sourceId)).run();
66566
+ }).where(eq91(trafficSources.id, sourceId)).run();
66415
66567
  } else {
66416
66568
  tx.insert(trafficSources).values({
66417
66569
  id: sourceId,
@@ -66437,7 +66589,7 @@ async function trafficRoutes(app, opts) {
66437
66589
  entityId: sourceId
66438
66590
  });
66439
66591
  return {
66440
- sourceRow: tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get(),
66592
+ sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
66441
66593
  scheduleChanged: scheduleBinding.changed
66442
66594
  };
66443
66595
  }, { behavior: "immediate" });
@@ -66492,7 +66644,7 @@ async function trafficRoutes(app, opts) {
66492
66644
  const config = { baseUrl, username };
66493
66645
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
66494
66646
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
66495
- const activeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
66647
+ const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
66496
66648
  const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
66497
66649
  const sourceId = endpointChanged ? crypto55.randomUUID() : activeSource?.id ?? crypto55.randomUUID();
66498
66650
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
@@ -66503,14 +66655,14 @@ async function trafficRoutes(app, opts) {
66503
66655
  lastError: null,
66504
66656
  configJson: config,
66505
66657
  updatedAt: now
66506
- }).where(eq90(trafficSources.id, sourceId)).run();
66658
+ }).where(eq91(trafficSources.id, sourceId)).run();
66507
66659
  } else {
66508
66660
  if (activeSource) {
66509
66661
  tx.update(trafficSources).set({
66510
66662
  status: TrafficSourceStatuses.archived,
66511
66663
  archivedAt: now,
66512
66664
  updatedAt: now
66513
- }).where(eq90(trafficSources.id, activeSource.id)).run();
66665
+ }).where(eq91(trafficSources.id, activeSource.id)).run();
66514
66666
  }
66515
66667
  tx.insert(trafficSources).values({
66516
66668
  id: sourceId,
@@ -66536,7 +66688,7 @@ async function trafficRoutes(app, opts) {
66536
66688
  entityId: sourceId
66537
66689
  });
66538
66690
  return {
66539
- sourceRow: tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get(),
66691
+ sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
66540
66692
  scheduleChanged: scheduleBinding.changed
66541
66693
  };
66542
66694
  }, { behavior: "immediate" });
@@ -66593,7 +66745,7 @@ async function trafficRoutes(app, opts) {
66593
66745
  const config = { projectId, teamId, environment };
66594
66746
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
66595
66747
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
66596
- const activeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
66748
+ const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
66597
66749
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
66598
66750
  let row;
66599
66751
  if (activeSource) {
@@ -66603,8 +66755,8 @@ async function trafficRoutes(app, opts) {
66603
66755
  lastError: null,
66604
66756
  configJson: config,
66605
66757
  updatedAt: now
66606
- }).where(eq90(trafficSources.id, activeSource.id)).run();
66607
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, activeSource.id)).get();
66758
+ }).where(eq91(trafficSources.id, activeSource.id)).run();
66759
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, activeSource.id)).get();
66608
66760
  } else {
66609
66761
  const newId = crypto55.randomUUID();
66610
66762
  tx.insert(trafficSources).values({
@@ -66629,7 +66781,7 @@ async function trafficRoutes(app, opts) {
66629
66781
  createdAt: now,
66630
66782
  updatedAt: now
66631
66783
  }).run();
66632
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, newId)).get();
66784
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, newId)).get();
66633
66785
  }
66634
66786
  const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
66635
66787
  writeAuditLog(tx, {
@@ -66673,7 +66825,7 @@ async function trafficRoutes(app, opts) {
66673
66825
  const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
66674
66826
  const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
66675
66827
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
66676
- const cloudflareSources = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
66828
+ const cloudflareSources = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
66677
66829
  const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
66678
66830
  if (sameModeSource) attempt.update({ action: "reauth" });
66679
66831
  const sourceId2 = sameModeSource?.id ?? crypto55.randomUUID();
@@ -66727,7 +66879,7 @@ async function trafficRoutes(app, opts) {
66727
66879
  let scheduleChanged = false;
66728
66880
  try {
66729
66881
  const result = app.db.transaction((tx) => {
66730
- const currentSameModeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
66882
+ const currentSameModeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
66731
66883
  if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
66732
66884
  throw operationInProgress("Cloudflare Queue source changed during connect; retry");
66733
66885
  }
@@ -66745,7 +66897,7 @@ async function trafficRoutes(app, opts) {
66745
66897
  queueBacklogObservedAt: null
66746
66898
  } : {},
66747
66899
  updatedAt: now2
66748
- }).where(eq90(trafficSources.id, sourceId2)).run();
66900
+ }).where(eq91(trafficSources.id, sourceId2)).run();
66749
66901
  } else {
66750
66902
  tx.insert(trafficSources).values({
66751
66903
  id: sourceId2,
@@ -66765,7 +66917,7 @@ async function trafficRoutes(app, opts) {
66765
66917
  }
66766
66918
  const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
66767
66919
  return {
66768
- row: tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId2)).get(),
66920
+ row: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId2)).get(),
66769
66921
  scheduleChanged: changed
66770
66922
  };
66771
66923
  }, { behavior: "immediate" });
@@ -66815,7 +66967,7 @@ async function trafficRoutes(app, opts) {
66815
66967
  const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
66816
66968
  const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
66817
66969
  const now = (/* @__PURE__ */ new Date()).toISOString();
66818
- const activeSource = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
66970
+ const activeSource = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
66819
66971
  if (activeSource) attempt.update({ action: "reauth" });
66820
66972
  const sourceId = activeSource?.id ?? crypto55.randomUUID();
66821
66973
  const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
@@ -66883,7 +67035,7 @@ async function trafficRoutes(app, opts) {
66883
67035
  let scheduleRemoved = false;
66884
67036
  try {
66885
67037
  const result = app.db.transaction((tx) => {
66886
- const currentActiveSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
67038
+ const currentActiveSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
66887
67039
  if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
66888
67040
  throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
66889
67041
  }
@@ -66897,8 +67049,8 @@ async function trafficRoutes(app, opts) {
66897
67049
  configJson: config,
66898
67050
  ingestTokenHash,
66899
67051
  updatedAt: now
66900
- }).where(eq90(trafficSources.id, currentActiveSource.id)).run();
66901
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, currentActiveSource.id)).get();
67052
+ }).where(eq91(trafficSources.id, currentActiveSource.id)).run();
67053
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, currentActiveSource.id)).get();
66902
67054
  } else {
66903
67055
  tx.insert(trafficSources).values({
66904
67056
  id: sourceId,
@@ -66918,16 +67070,16 @@ async function trafficRoutes(app, opts) {
66918
67070
  createdAt: now,
66919
67071
  updatedAt: now
66920
67072
  }).run();
66921
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get();
67073
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
66922
67074
  }
66923
67075
  let removed = false;
66924
67076
  if (row.status === TrafficSourceStatuses.connected) {
66925
- const schedule = tx.select().from(schedules).where(and79(
66926
- eq90(schedules.projectId, project.id),
66927
- eq90(schedules.kind, SchedulableRunKinds["traffic-sync"])
67077
+ const schedule = tx.select().from(schedules).where(and80(
67078
+ eq91(schedules.projectId, project.id),
67079
+ eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
66928
67080
  )).get();
66929
67081
  if (schedule) {
66930
- tx.delete(schedules).where(eq90(schedules.id, schedule.id)).run();
67082
+ tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
66931
67083
  removed = true;
66932
67084
  }
66933
67085
  }
@@ -66983,47 +67135,47 @@ async function trafficRoutes(app, opts) {
66983
67135
  };
66984
67136
  }));
66985
67137
  app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
66986
- const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
67138
+ const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
66987
67139
  const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
66988
67140
  return integration ? { integration, action: "select" } : void 0;
66989
67141
  }, async (request) => {
66990
67142
  const project = resolveProject(app.db, request.params.name);
66991
67143
  const now = (/* @__PURE__ */ new Date()).toISOString();
66992
67144
  const result = app.db.transaction((tx) => {
66993
- const target = tx.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
67145
+ const target = tx.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
66994
67146
  if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
66995
67147
  throw notFound("Traffic source", request.params.id);
66996
67148
  }
66997
67149
  const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
66998
- const siblings = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all();
67150
+ const siblings = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all();
66999
67151
  for (const sibling of siblings) {
67000
67152
  if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
67001
67153
  tx.update(trafficSources).set({
67002
67154
  status: TrafficSourceStatuses.paused,
67003
67155
  updatedAt: now
67004
- }).where(eq90(trafficSources.id, sibling.id)).run();
67156
+ }).where(eq91(trafficSources.id, sibling.id)).run();
67005
67157
  }
67006
67158
  tx.update(trafficSources).set({
67007
67159
  status: TrafficSourceStatuses.connected,
67008
67160
  lastError: null,
67009
67161
  archivedAt: null,
67010
67162
  updatedAt: now
67011
- }).where(eq90(trafficSources.id, target.id)).run();
67163
+ }).where(eq91(trafficSources.id, target.id)).run();
67012
67164
  let scheduleAction = null;
67013
67165
  if (usesPullSchedule) {
67014
67166
  bindTrafficSyncSchedule(tx, project.id, target.id, now);
67015
67167
  scheduleAction = "upsert";
67016
67168
  } else {
67017
- const schedule = tx.select().from(schedules).where(and79(
67018
- eq90(schedules.projectId, project.id),
67019
- eq90(schedules.kind, SchedulableRunKinds["traffic-sync"])
67169
+ const schedule = tx.select().from(schedules).where(and80(
67170
+ eq91(schedules.projectId, project.id),
67171
+ eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
67020
67172
  )).get();
67021
67173
  if (schedule) {
67022
- tx.delete(schedules).where(eq90(schedules.id, schedule.id)).run();
67174
+ tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
67023
67175
  scheduleAction = "delete";
67024
67176
  }
67025
67177
  }
67026
- const source = tx.select().from(trafficSources).where(eq90(trafficSources.id, target.id)).get();
67178
+ const source = tx.select().from(trafficSources).where(eq91(trafficSources.id, target.id)).get();
67027
67179
  writeAuditLog(tx, {
67028
67180
  projectId: project.id,
67029
67181
  actor: "api",
@@ -67091,7 +67243,7 @@ async function trafficRoutes(app, opts) {
67091
67243
  }
67092
67244
  const { bearerHash, credential } = authenticated;
67093
67245
  const sourceId = credential.sourceId;
67094
- const sourceRow2 = app.db.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get();
67246
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
67095
67247
  if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
67096
67248
  throw authRequired();
67097
67249
  }
@@ -67169,7 +67321,7 @@ async function trafficRoutes(app, opts) {
67169
67321
  });
67170
67322
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
67171
67323
  const project = resolveProject(app.db, request.params.name);
67172
- const initialSourceRow = app.db.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
67324
+ const initialSourceRow = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
67173
67325
  if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
67174
67326
  throw notFound("Traffic source", request.params.id);
67175
67327
  }
@@ -67242,7 +67394,7 @@ async function trafficRoutes(app, opts) {
67242
67394
  });
67243
67395
  const finishSync = (status, error, stopReason) => {
67244
67396
  const queueDrain = drainSummary(stopReason);
67245
- app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq90(runs.id, runId)).run();
67397
+ app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq91(runs.id, runId)).run();
67246
67398
  writeAuditLog(app.db, {
67247
67399
  projectId: project.id,
67248
67400
  actor: "api",
@@ -67417,7 +67569,7 @@ async function trafficRoutes(app, opts) {
67417
67569
  now: (/* @__PURE__ */ new Date()).toISOString(),
67418
67570
  ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
67419
67571
  })) throw new Error("Queue sync lease was lost before acknowledgement");
67420
- const latestSource = app.db.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67572
+ const latestSource = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67421
67573
  if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
67422
67574
  throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
67423
67575
  }
@@ -67506,13 +67658,13 @@ async function trafficRoutes(app, opts) {
67506
67658
  error: safeError,
67507
67659
  finishedAt: failedAt,
67508
67660
  syncSummary: drainSummary(CloudflareQueueDrainStopReasons.error)
67509
- }).where(eq90(runs.id, runId)).run();
67510
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67661
+ }).where(eq91(runs.id, runId)).run();
67662
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67511
67663
  if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
67512
- tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and79(
67513
- eq90(trafficSources.id, sourceRow2.id),
67514
- eq90(trafficSources.status, TrafficSourceStatuses.connected),
67515
- eq90(trafficSources.syncLeaseOwner, leaseOwner)
67664
+ tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and80(
67665
+ eq91(trafficSources.id, sourceRow2.id),
67666
+ eq91(trafficSources.status, TrafficSourceStatuses.connected),
67667
+ eq91(trafficSources.syncLeaseOwner, leaseOwner)
67516
67668
  )).run();
67517
67669
  }
67518
67670
  });
@@ -67531,7 +67683,7 @@ async function trafficRoutes(app, opts) {
67531
67683
  `Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
67532
67684
  );
67533
67685
  }
67534
- const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
67686
+ const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
67535
67687
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
67536
67688
  throw validationError("Traffic source must be active before it can sync");
67537
67689
  }
@@ -67568,10 +67720,10 @@ async function trafficRoutes(app, opts) {
67568
67720
  const markFailed = (msg, errorCode2) => {
67569
67721
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
67570
67722
  app.db.transaction((tx) => {
67571
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq90(runs.id, runId)).run();
67572
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67723
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
67724
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67573
67725
  if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && latestSource.syncLeaseOwner === syncLeaseOwner) {
67574
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq90(trafficSources.id, sourceRow2.id)).run();
67726
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
67575
67727
  }
67576
67728
  });
67577
67729
  try {
@@ -67665,7 +67817,7 @@ async function trafficRoutes(app, opts) {
67665
67817
  }
67666
67818
  const credential = credentialStore.getConnection(project.name);
67667
67819
  if (!credential) {
67668
- app.db.delete(runs).where(eq90(runs.id, runId)).run();
67820
+ app.db.delete(runs).where(eq91(runs.id, runId)).run();
67669
67821
  throw validationError(
67670
67822
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
67671
67823
  );
@@ -67699,14 +67851,14 @@ async function trafficRoutes(app, opts) {
67699
67851
  const windowEndIso2 = windowEnd.toISOString();
67700
67852
  const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
67701
67853
  const reservedSource = app.db.transaction((tx) => {
67702
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67854
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67703
67855
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== syncLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
67704
67856
  tx.update(trafficSources).set({
67705
67857
  lastSyncedAt: windowStartIso2,
67706
67858
  wordpressPendingUntil: windowEndIso2,
67707
67859
  updatedAt: reservedAt
67708
- }).where(eq90(trafficSources.id, sourceRow2.id)).run();
67709
- return tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67860
+ }).where(eq91(trafficSources.id, sourceRow2.id)).run();
67861
+ return tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67710
67862
  }, { behavior: "immediate" });
67711
67863
  if (!reservedSource) {
67712
67864
  const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
@@ -67766,12 +67918,12 @@ async function trafficRoutes(app, opts) {
67766
67918
  auditAction = "traffic.vercel.synced";
67767
67919
  const credentialStore = opts.vercelTrafficCredentialStore;
67768
67920
  if (!credentialStore) {
67769
- app.db.delete(runs).where(eq90(runs.id, runId)).run();
67921
+ app.db.delete(runs).where(eq91(runs.id, runId)).run();
67770
67922
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
67771
67923
  }
67772
67924
  const credential = credentialStore.getConnection(project.name);
67773
67925
  if (!credential) {
67774
- app.db.delete(runs).where(eq90(runs.id, runId)).run();
67926
+ app.db.delete(runs).where(eq91(runs.id, runId)).run();
67775
67927
  throw validationError(
67776
67928
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
67777
67929
  );
@@ -67789,7 +67941,7 @@ async function trafficRoutes(app, opts) {
67789
67941
  if (cappedStartMs > clampedStartMs) {
67790
67942
  const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
67791
67943
  const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
67792
- app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq90(trafficSources.id, sourceRow2.id)).run();
67944
+ app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq91(trafficSources.id, sourceRow2.id)).run();
67793
67945
  request.log.warn(
67794
67946
  {
67795
67947
  sourceId: sourceRow2.id,
@@ -67883,15 +68035,15 @@ async function trafficRoutes(app, opts) {
67883
68035
  throw providerError(`WordPress sync failed: ${msg}`);
67884
68036
  }
67885
68037
  const commitOutcome = app.db.transaction((tx) => {
67886
- const latestRow = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67887
- const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
68038
+ const latestRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68039
+ const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
67888
68040
  if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || latestRow.syncLeaseOwner !== syncLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
67889
68041
  const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
67890
68042
  tx.update(runs).set({
67891
68043
  status: RunStatuses.failed,
67892
68044
  error: "Traffic source was deactivated or reconfigured during sync",
67893
68045
  finishedAt: abortedAt
67894
- }).where(eq90(runs.id, runId)).run();
68046
+ }).where(eq91(runs.id, runId)).run();
67895
68047
  return "source-inactive";
67896
68048
  }
67897
68049
  const previousIds = latestRow.lastEventIds ?? [];
@@ -68177,8 +68329,8 @@ async function trafficRoutes(app, opts) {
68177
68329
  sourceUpdate.lastCursor = nextCursor ?? null;
68178
68330
  sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
68179
68331
  }
68180
- tx.update(trafficSources).set(sourceUpdate).where(eq90(trafficSources.id, sourceRow2.id)).run();
68181
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq90(runs.id, runId)).run();
68332
+ tx.update(trafficSources).set(sourceUpdate).where(eq91(trafficSources.id, sourceRow2.id)).run();
68333
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
68182
68334
  return "committed";
68183
68335
  }, { behavior: "immediate" });
68184
68336
  if (commitOutcome === "source-inactive") {
@@ -68247,7 +68399,7 @@ async function trafficRoutes(app, opts) {
68247
68399
  });
68248
68400
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
68249
68401
  const project = resolveProject(app.db, request.params.name);
68250
- const sourceRow2 = app.db.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
68402
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
68251
68403
  if (!sourceRow2 || sourceRow2.projectId !== project.id) {
68252
68404
  throw notFound("Traffic source", request.params.id);
68253
68405
  }
@@ -68372,7 +68524,7 @@ async function trafficRoutes(app, opts) {
68372
68524
  }
68373
68525
  try {
68374
68526
  app.db.transaction((tx) => {
68375
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
68527
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68376
68528
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
68377
68529
  throw validationError("Traffic source must remain active and unchanged before it can backfill");
68378
68530
  }
@@ -68422,8 +68574,8 @@ async function trafficRoutes(app, opts) {
68422
68574
  pathNormalized: crawlerEventsHourly.pathNormalized,
68423
68575
  hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
68424
68576
  }).from(crawlerEventsHourly).where(
68425
- and79(
68426
- eq90(crawlerEventsHourly.sourceId, row.id),
68577
+ and80(
68578
+ eq91(crawlerEventsHourly.sourceId, row.id),
68427
68579
  gte25(crawlerEventsHourly.tsHour, since)
68428
68580
  )
68429
68581
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
@@ -68432,14 +68584,14 @@ async function trafficRoutes(app, opts) {
68432
68584
  );
68433
68585
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
68434
68586
  const aiUserFetchTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
68435
- and79(
68436
- eq90(aiUserFetchEventsHourly.sourceId, row.id),
68587
+ and80(
68588
+ eq91(aiUserFetchEventsHourly.sourceId, row.id),
68437
68589
  gte25(aiUserFetchEventsHourly.tsHour, since)
68438
68590
  )
68439
68591
  ).get();
68440
68592
  const likelyFakeTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchLikelyFakeHourly.hits}), 0)` }).from(aiUserFetchLikelyFakeHourly).where(
68441
- and79(
68442
- eq90(aiUserFetchLikelyFakeHourly.sourceId, row.id),
68593
+ and80(
68594
+ eq91(aiUserFetchLikelyFakeHourly.sourceId, row.id),
68443
68595
  gte25(aiUserFetchLikelyFakeHourly.tsHour, since)
68444
68596
  )
68445
68597
  ).get();
@@ -68448,22 +68600,22 @@ async function trafficRoutes(app, opts) {
68448
68600
  nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
68449
68601
  landed: sql40`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
68450
68602
  }).from(aiReferralEventsHourly).where(
68451
- and79(
68452
- eq90(aiReferralEventsHourly.sourceId, row.id),
68603
+ and80(
68604
+ eq91(aiReferralEventsHourly.sourceId, row.id),
68453
68605
  gte25(aiReferralEventsHourly.tsHour, since)
68454
68606
  )
68455
68607
  ).get();
68456
68608
  const sampleTotals = app.db.select({ total: sql40`COUNT(*)` }).from(rawEventSamples).where(
68457
- and79(
68458
- eq90(rawEventSamples.sourceId, row.id),
68609
+ and80(
68610
+ eq91(rawEventSamples.sourceId, row.id),
68459
68611
  gte25(rawEventSamples.ts, since)
68460
68612
  )
68461
68613
  ).get();
68462
68614
  const latestRun = app.db.select().from(runs).where(
68463
- and79(
68464
- eq90(runs.projectId, projectId),
68465
- eq90(runs.kind, RunKinds["traffic-sync"]),
68466
- eq90(runs.sourceId, row.id)
68615
+ and80(
68616
+ eq91(runs.projectId, projectId),
68617
+ eq91(runs.kind, RunKinds["traffic-sync"]),
68618
+ eq91(runs.sourceId, row.id)
68467
68619
  )
68468
68620
  ).orderBy(desc41(runs.startedAt)).limit(1).get();
68469
68621
  return {
@@ -68501,7 +68653,7 @@ async function trafficRoutes(app, opts) {
68501
68653
  const now = (/* @__PURE__ */ new Date()).toISOString();
68502
68654
  let updatedRow;
68503
68655
  app.db.transaction((tx) => {
68504
- const sourceRow2 = tx.select().from(trafficSources).where(and79(eq90(trafficSources.projectId, project.id), eq90(trafficSources.id, request.params.id))).get();
68656
+ const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
68505
68657
  if (!sourceRow2) {
68506
68658
  throw notFound("traffic source", request.params.id);
68507
68659
  }
@@ -68515,7 +68667,7 @@ async function trafficRoutes(app, opts) {
68515
68667
  `Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
68516
68668
  );
68517
68669
  }
68518
- const hasConnectedSibling = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
68670
+ const hasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
68519
68671
  if (hasConnectedSibling) {
68520
68672
  throw validationError(
68521
68673
  `Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
@@ -68532,7 +68684,7 @@ async function trafficRoutes(app, opts) {
68532
68684
  status: TrafficSourceStatuses.connected,
68533
68685
  lastError: null,
68534
68686
  updatedAt: now
68535
- }).where(eq90(trafficSources.id, sourceRow2.id)).run();
68687
+ }).where(eq91(trafficSources.id, sourceRow2.id)).run();
68536
68688
  writeAuditLog(tx, auditFromRequest(request, {
68537
68689
  projectId: project.id,
68538
68690
  actor: "api",
@@ -68540,7 +68692,7 @@ async function trafficRoutes(app, opts) {
68540
68692
  entityType: "traffic_source",
68541
68693
  entityId: sourceRow2.id
68542
68694
  }));
68543
- updatedRow = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
68695
+ updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68544
68696
  });
68545
68697
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
68546
68698
  }));
@@ -68552,7 +68704,7 @@ async function trafficRoutes(app, opts) {
68552
68704
  }
68553
68705
  let updatedRow;
68554
68706
  app.db.transaction((tx) => {
68555
- const sourceRow2 = tx.select().from(trafficSources).where(and79(eq90(trafficSources.projectId, project.id), eq90(trafficSources.id, request.params.id))).get();
68707
+ const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
68556
68708
  if (!sourceRow2) {
68557
68709
  throw notFound("traffic source", request.params.id);
68558
68710
  }
@@ -68565,7 +68717,7 @@ async function trafficRoutes(app, opts) {
68565
68717
  );
68566
68718
  }
68567
68719
  const now = (/* @__PURE__ */ new Date()).toISOString();
68568
- tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq90(trafficSources.id, sourceRow2.id)).run();
68720
+ tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq91(trafficSources.id, sourceRow2.id)).run();
68569
68721
  writeAuditLog(tx, auditFromRequest(request, {
68570
68722
  projectId: project.id,
68571
68723
  actor: "api",
@@ -68574,20 +68726,20 @@ async function trafficRoutes(app, opts) {
68574
68726
  entityId: sourceRow2.id,
68575
68727
  diff: { skippedThroughAt: sourceRow2.skippedThroughAt, sourceType: sourceRow2.sourceType }
68576
68728
  }));
68577
- updatedRow = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
68729
+ updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68578
68730
  });
68579
68731
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
68580
68732
  });
68581
68733
  app.get("/projects/:name/traffic/sources", async (request) => {
68582
68734
  const project = resolveProject(app.db, request.params.name);
68583
- const rows = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68735
+ const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68584
68736
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
68585
68737
  const response = { sources };
68586
68738
  return response;
68587
68739
  });
68588
68740
  app.get("/projects/:name/traffic/status", async (request) => {
68589
68741
  const project = resolveProject(app.db, request.params.name);
68590
- const rows = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68742
+ const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68591
68743
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
68592
68744
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
68593
68745
  const response = { sources };
@@ -68597,7 +68749,7 @@ async function trafficRoutes(app, opts) {
68597
68749
  "/projects/:name/traffic/sources/:id",
68598
68750
  async (request) => {
68599
68751
  const project = resolveProject(app.db, request.params.name);
68600
- const row = app.db.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
68752
+ const row = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
68601
68753
  if (!row || row.projectId !== project.id) {
68602
68754
  throw notFound("Traffic source", request.params.id);
68603
68755
  }
@@ -68665,12 +68817,12 @@ async function trafficRoutes(app, opts) {
68665
68817
  const seriesByBucket = /* @__PURE__ */ new Map();
68666
68818
  if (kinds.has(TrafficEventKinds.crawler)) {
68667
68819
  const crawlerFilters = [
68668
- eq90(crawlerEventsHourly.projectId, project.id),
68820
+ eq91(crawlerEventsHourly.projectId, project.id),
68669
68821
  gte25(crawlerEventsHourly.tsHour, sinceIso),
68670
68822
  lte26(crawlerEventsHourly.tsHour, untilIso)
68671
68823
  ];
68672
- if (sourceIdParam) crawlerFilters.push(eq90(crawlerEventsHourly.sourceId, sourceIdParam));
68673
- const crawlerWhere = and79(...crawlerFilters);
68824
+ if (sourceIdParam) crawlerFilters.push(eq91(crawlerEventsHourly.sourceId, sourceIdParam));
68825
+ const crawlerWhere = and80(...crawlerFilters);
68674
68826
  const pathTotals = app.db.select({
68675
68827
  pathNormalized: crawlerEventsHourly.pathNormalized,
68676
68828
  hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
@@ -68740,12 +68892,12 @@ async function trafficRoutes(app, opts) {
68740
68892
  }
68741
68893
  if (kinds.has(TrafficEventKinds["ai-user-fetch"])) {
68742
68894
  const userFetchFilters = [
68743
- eq90(aiUserFetchEventsHourly.projectId, project.id),
68895
+ eq91(aiUserFetchEventsHourly.projectId, project.id),
68744
68896
  gte25(aiUserFetchEventsHourly.tsHour, sinceIso),
68745
68897
  lte26(aiUserFetchEventsHourly.tsHour, untilIso)
68746
68898
  ];
68747
- if (sourceIdParam) userFetchFilters.push(eq90(aiUserFetchEventsHourly.sourceId, sourceIdParam));
68748
- const userFetchWhere = and79(...userFetchFilters);
68899
+ if (sourceIdParam) userFetchFilters.push(eq91(aiUserFetchEventsHourly.sourceId, sourceIdParam));
68900
+ const userFetchWhere = and80(...userFetchFilters);
68749
68901
  const total = app.db.select({
68750
68902
  total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
68751
68903
  rows: sql40`COUNT(*)`
@@ -68779,12 +68931,12 @@ async function trafficRoutes(app, opts) {
68779
68931
  }
68780
68932
  if (kinds.has(TrafficEventKinds["ai-referral"])) {
68781
68933
  const aiFilters = [
68782
- eq90(aiReferralEventsHourly.projectId, project.id),
68934
+ eq91(aiReferralEventsHourly.projectId, project.id),
68783
68935
  gte25(aiReferralEventsHourly.tsHour, sinceIso),
68784
68936
  lte26(aiReferralEventsHourly.tsHour, untilIso)
68785
68937
  ];
68786
- if (sourceIdParam) aiFilters.push(eq90(aiReferralEventsHourly.sourceId, sourceIdParam));
68787
- const aiWhere = and79(...aiFilters);
68938
+ if (sourceIdParam) aiFilters.push(eq91(aiReferralEventsHourly.sourceId, sourceIdParam));
68939
+ const aiWhere = and80(...aiFilters);
68788
68940
  const total = app.db.select({
68789
68941
  total: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
68790
68942
  nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
@@ -68859,7 +69011,7 @@ async function trafficRoutes(app, opts) {
68859
69011
  windowEnd: untilIso,
68860
69012
  series: (() => {
68861
69013
  const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
68862
- const coverageScope = (col, srcCol) => sourceIdParam ? and79(eq90(col, project.id), eq90(srcCol, sourceIdParam)) : eq90(col, project.id);
69014
+ const coverageScope = (col, srcCol) => sourceIdParam ? and80(eq91(col, project.id), eq91(srcCol, sourceIdParam)) : eq91(col, project.id);
68863
69015
  const firsts = [
68864
69016
  app.db.select({ v: sql40`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
68865
69017
  app.db.select({ v: sql40`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
@@ -68946,7 +69098,7 @@ async function trafficRoutes(app, opts) {
68946
69098
  }
68947
69099
 
68948
69100
  // ../api-routes/src/doctor/checks/report-sweeps.ts
68949
- import { and as and80, asc as asc24, eq as eq91, gte as gte26, lte as lte27 } from "drizzle-orm";
69101
+ import { and as and81, asc as asc24, eq as eq92, gte as gte26, lte as lte27 } from "drizzle-orm";
68950
69102
 
68951
69103
  // ../api-routes/src/visibility-attribution.ts
68952
69104
  function buildQueryAttribution2(projectQueries) {
@@ -68980,11 +69132,11 @@ function inspectSweep(ctx, run) {
68980
69132
  answerMentioned: querySnapshots.answerMentioned,
68981
69133
  answerText: querySnapshots.answerText,
68982
69134
  measurementExecutionId: querySnapshots.measurementExecutionId
68983
- }).from(querySnapshots).where(eq91(querySnapshots.runId, run.id)).all();
69135
+ }).from(querySnapshots).where(eq92(querySnapshots.runId, run.id)).all();
68984
69136
  if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
68985
69137
  try {
68986
69138
  if (run.measurementPlanVersionId) {
68987
- const version = ctx.db.select().from(measurementPlanVersions).where(and80(eq91(measurementPlanVersions.projectId, ctx.project.id), eq91(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
69139
+ const version = ctx.db.select().from(measurementPlanVersions).where(and81(eq92(measurementPlanVersions.projectId, ctx.project.id), eq92(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
68988
69140
  if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
68989
69141
  const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
68990
69142
  const manifest = measurementRunExpectedSlots(run, plan);
@@ -69016,10 +69168,10 @@ function inspectSweep(ctx, run) {
69016
69168
  };
69017
69169
  return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
69018
69170
  }
69019
- const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq91(simpleMeasurementDefinitions.runId, run.id)).get();
69171
+ const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq92(simpleMeasurementDefinitions.runId, run.id)).get();
69020
69172
  const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
69021
- const project = ctx.db.select().from(projects).where(eq91(projects.id, ctx.project.id)).get();
69022
- const basket = frozen ? frozen.queries.map((query2) => ({ id: query2.queryId, query: query2.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq91(queries.projectId, project.id)).all();
69173
+ const project = ctx.db.select().from(projects).where(eq92(projects.id, ctx.project.id)).get();
69174
+ const basket = frozen ? frozen.queries.map((query2) => ({ id: query2.queryId, query: query2.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq92(queries.projectId, project.id)).all();
69023
69175
  const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
69024
69176
  const attribution = buildQueryAttribution2(basket);
69025
69177
  const expected = new Set(basket.flatMap((query2) => providers.map((provider) => JSON.stringify([query2.id, provider.trim().toLowerCase()]))));
@@ -69068,7 +69220,7 @@ var reportSweepsCheck = {
69068
69220
  const graded = gradedReportMonths(reportMonths, now);
69069
69221
  const months = reportMonths.map((month) => {
69070
69222
  const bounds = calendarMonthBounds(month);
69071
- const monthRuns = ctx.db.select().from(runs).where(and80(eq91(runs.projectId, ctx.project.id), eq91(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte26(runs.createdAt, bounds.since), lte27(runs.createdAt, bounds.until))).orderBy(asc24(runs.createdAt), asc24(runs.id)).all();
69223
+ const monthRuns = ctx.db.select().from(runs).where(and81(eq92(runs.projectId, ctx.project.id), eq92(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte26(runs.createdAt, bounds.since), lte27(runs.createdAt, bounds.until))).orderBy(asc24(runs.createdAt), asc24(runs.id)).all();
69072
69224
  const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
69073
69225
  return {
69074
69226
  month,
@@ -69091,7 +69243,7 @@ var reportSweepsCheck = {
69091
69243
  };
69092
69244
 
69093
69245
  // ../api-routes/src/doctor/checks/report-models.ts
69094
- import { and as and81, eq as eq92, gte as gte27, inArray as inArray42, lte as lte28 } from "drizzle-orm";
69246
+ import { and as and82, eq as eq93, gte as gte27, inArray as inArray42, lte as lte28 } from "drizzle-orm";
69095
69247
  var reportModelsCheck = {
69096
69248
  id: "report.models",
69097
69249
  category: CheckCategories.providers,
@@ -69106,7 +69258,7 @@ var reportModelsCheck = {
69106
69258
  const months = reportMonths.flatMap((month) => {
69107
69259
  const bounds = calendarMonthBounds(month);
69108
69260
  const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
69109
- const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq92(runs.id, querySnapshots.runId)).where(and81(eq92(runs.projectId, ctx.project.id), eq92(runs.kind, RunKinds["answer-visibility"]), inArray42(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte27(runs.createdAt, bounds.since), lte28(runs.createdAt, bounds.until))).all();
69261
+ const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq93(runs.id, querySnapshots.runId)).where(and82(eq93(runs.projectId, ctx.project.id), eq93(runs.kind, RunKinds["answer-visibility"]), inArray42(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte27(runs.createdAt, bounds.since), lte28(runs.createdAt, bounds.until))).all();
69110
69262
  return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
69111
69263
  month,
69112
69264
  previousMonth: previous,
@@ -69147,7 +69299,7 @@ var reportModelsCheck = {
69147
69299
  };
69148
69300
 
69149
69301
  // ../api-routes/src/doctor/checks/report-daily-data.ts
69150
- import { eq as eq93 } from "drizzle-orm";
69302
+ import { eq as eq94 } from "drizzle-orm";
69151
69303
  var REPORTING_LAG_DAYS = 3;
69152
69304
  function dateSpan(dates) {
69153
69305
  const sorted = [...dates].sort();
@@ -69156,31 +69308,31 @@ function dateSpan(dates) {
69156
69308
  function latestSyncRange(ctx, source) {
69157
69309
  const projectId = ctx.project.id;
69158
69310
  if (source === "ga") {
69159
- const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq93(gaDailyTotals.projectId, projectId)).all();
69311
+ const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, projectId)).all();
69160
69312
  const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
69161
- const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq93(gaTrafficSummaries.projectId, projectId)).get();
69313
+ const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq94(gaTrafficSummaries.projectId, projectId)).get();
69162
69314
  if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
69163
69315
  const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
69164
69316
  return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
69165
69317
  }
69166
- const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq93(gscDailyTotals.projectId, projectId)).all();
69318
+ const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, projectId)).all();
69167
69319
  const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
69168
69320
  const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
69169
69321
  if (!span || latest === null) return null;
69170
- const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq93(gscDataWatermarks.projectId, projectId)).get();
69322
+ const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq94(gscDataWatermarks.projectId, projectId)).get();
69171
69323
  return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
69172
69324
  }
69173
69325
  function dailyCoverage(ctx, month, source) {
69174
69326
  const project = ctx.project;
69175
69327
  const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
69176
- const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq93(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq93(gscDailyTotals.projectId, project.id)).all();
69328
+ const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, project.id)).all();
69177
69329
  if (!connection && stored.length === 0) return null;
69178
69330
  const bounds = calendarMonthBounds(month);
69179
69331
  const start = bounds.since.slice(0, 10);
69180
69332
  const end = bounds.until.slice(0, 10);
69181
69333
  const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
69182
69334
  const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
69183
- const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq93(projects.id, project.id)).get().date.slice(0, 10);
69335
+ const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq94(projects.id, project.id)).get().date.slice(0, 10);
69184
69336
  const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
69185
69337
  const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
69186
69338
  const byDate = new Map(stored.map((row) => [row.date, row.count]));
@@ -69262,7 +69414,7 @@ var reportDailyDataCheck = {
69262
69414
  };
69263
69415
 
69264
69416
  // ../api-routes/src/doctor/checks/referral-assessment.ts
69265
- import { and as and82, eq as eq94, ne as ne16 } from "drizzle-orm";
69417
+ import { and as and83, eq as eq95, ne as ne16 } from "drizzle-orm";
69266
69418
  var REFERRAL_ASSESSMENT_CHECKS = [{
69267
69419
  id: "report.ai-referral-bursts",
69268
69420
  category: CheckCategories.integrations,
@@ -69271,7 +69423,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
69271
69423
  notificationPolicy: CheckNotificationPolicies.silent,
69272
69424
  run: (ctx) => {
69273
69425
  if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
69274
- const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and82(eq94(trafficSources.projectId, ctx.project.id), ne16(trafficSources.status, TrafficSourceStatuses.archived))).get();
69426
+ const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and83(eq95(trafficSources.projectId, ctx.project.id), ne16(trafficSources.status, TrafficSourceStatuses.archived))).get();
69275
69427
  if (!configured) {
69276
69428
  return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
69277
69429
  }
@@ -69683,17 +69835,17 @@ var BACKLINKS_CHECKS = [
69683
69835
  ];
69684
69836
 
69685
69837
  // ../api-routes/src/doctor/checks/bing-auth.ts
69686
- import { and as and83, desc as desc42, eq as eq95, inArray as inArray43 } from "drizzle-orm";
69838
+ import { and as and84, desc as desc42, eq as eq96, inArray as inArray43 } from "drizzle-orm";
69687
69839
  var BING_CONNECTION_ENTITY = "bing_connection";
69688
69840
  var BING_CONNECTED_ACTION = "bing.connected";
69689
69841
  var BING_DISCONNECTED_ACTION = "bing.disconnected";
69690
69842
  function lostBingConnection(ctx, project) {
69691
69843
  const hasRow = (row) => row !== void 0;
69692
- const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq95(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq95(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq95(bingKeywordStats.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingTrafficDaily.id }).from(bingTrafficDaily).where(eq95(bingTrafficDaily.projectId, project.id)).limit(1).get());
69844
+ const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq96(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq96(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq96(bingKeywordStats.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingTrafficDaily.id }).from(bingTrafficDaily).where(eq96(bingTrafficDaily.projectId, project.id)).limit(1).get());
69693
69845
  if (!storedData) return false;
69694
- const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and83(
69695
- eq95(auditLog.entityType, BING_CONNECTION_ENTITY),
69696
- eq95(auditLog.entityId, project.canonicalDomain),
69846
+ const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and84(
69847
+ eq96(auditLog.entityType, BING_CONNECTION_ENTITY),
69848
+ eq96(auditLog.entityId, project.canonicalDomain),
69697
69849
  inArray43(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
69698
69850
  )).orderBy(desc42(auditLog.createdAt)).limit(1).get();
69699
69851
  return lastChange?.action !== BING_DISCONNECTED_ACTION;
@@ -69852,7 +70004,7 @@ var BING_AUTH_CHECKS = [
69852
70004
  ];
69853
70005
 
69854
70006
  // ../api-routes/src/doctor/checks/content.ts
69855
- import { eq as eq96 } from "drizzle-orm";
70007
+ import { eq as eq97 } from "drizzle-orm";
69856
70008
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
69857
70009
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
69858
70010
  function skippedNoProject2() {
@@ -69865,7 +70017,7 @@ function skippedNoProject2() {
69865
70017
  }
69866
70018
  function loadProject(ctx) {
69867
70019
  if (!ctx.project) return null;
69868
- return ctx.db.select().from(projects).where(eq96(projects.id, ctx.project.id)).get() ?? null;
70020
+ return ctx.db.select().from(projects).where(eq97(projects.id, ctx.project.id)).get() ?? null;
69869
70021
  }
69870
70022
  var winnabilityCoverageCheck = {
69871
70023
  id: "content.winnability.coverage",
@@ -69958,7 +70110,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
69958
70110
  );
69959
70111
 
69960
70112
  // ../api-routes/src/doctor/checks/ads.ts
69961
- import { eq as eq97 } from "drizzle-orm";
70113
+ import { eq as eq98 } from "drizzle-orm";
69962
70114
  var ADS_RECENT_SYNC_WARN_DAYS = 7;
69963
70115
  var ADS_RECENT_SYNC_FAIL_DAYS = 30;
69964
70116
  var adsConnectionCheck = {
@@ -69975,7 +70127,7 @@ var adsConnectionCheck = {
69975
70127
  remediation: null
69976
70128
  };
69977
70129
  }
69978
- const row = ctx.db.select().from(adsConnections).where(eq97(adsConnections.projectId, ctx.project.id)).get();
70130
+ const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
69979
70131
  if (!row) {
69980
70132
  return {
69981
70133
  status: CheckStatuses.skipped,
@@ -70025,7 +70177,7 @@ var adsRecentSyncCheck = {
70025
70177
  remediation: null
70026
70178
  };
70027
70179
  }
70028
- const row = ctx.db.select().from(adsConnections).where(eq97(adsConnections.projectId, ctx.project.id)).get();
70180
+ const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
70029
70181
  if (!row) {
70030
70182
  return {
70031
70183
  status: CheckStatuses.skipped,
@@ -70240,7 +70392,7 @@ var ga4ConnectionCheck = {
70240
70392
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
70241
70393
 
70242
70394
  // ../api-routes/src/doctor/checks/data-freshness.ts
70243
- import { and as and84, desc as desc43, eq as eq98, inArray as inArray44 } from "drizzle-orm";
70395
+ import { and as and85, desc as desc43, eq as eq99, inArray as inArray44 } from "drizzle-orm";
70244
70396
  var GA_DATA_AGING_DAYS = 3;
70245
70397
  var GA_DATA_STALE_DAYS = 5;
70246
70398
  var GSC_DATA_AGING_DAYS = 5;
@@ -70270,7 +70422,7 @@ function skipped(code, summary3) {
70270
70422
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
70271
70423
  }
70272
70424
  function lastSyncAgeDays(ctx, kinds) {
70273
- const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and84(eq98(runs.projectId, ctx.project.id), inArray44(runs.kind, kinds), eq98(runs.status, "completed"))).orderBy(desc43(runs.createdAt)).limit(1).get();
70425
+ const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and85(eq99(runs.projectId, ctx.project.id), inArray44(runs.kind, kinds), eq99(runs.status, "completed"))).orderBy(desc43(runs.createdAt)).limit(1).get();
70274
70426
  if (!row?.createdAt) return null;
70275
70427
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS6);
70276
70428
  return Number.isFinite(age) ? age : null;
@@ -70372,7 +70524,7 @@ var gaRecentDataCheck = {
70372
70524
  const connection = gaConnected(ctx);
70373
70525
  if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
70374
70526
  if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
70375
- const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq98(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gaDailyTotals.date)).limit(1).get();
70527
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq99(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gaDailyTotals.date)).limit(1).get();
70376
70528
  return grade(ctx, GA_SPEC, newest?.date);
70377
70529
  }
70378
70530
  };
@@ -70387,16 +70539,16 @@ var gscRecentDataCheck = {
70387
70539
  if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
70388
70540
  return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
70389
70541
  }
70390
- const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq98(gscDataWatermarks.projectId, ctx.project.id)).get();
70542
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq99(gscDataWatermarks.projectId, ctx.project.id)).get();
70391
70543
  if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
70392
- const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq98(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gscDailyTotals.date)).limit(1).get();
70544
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq99(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gscDailyTotals.date)).limit(1).get();
70393
70545
  return grade(ctx, GSC_SPEC, newest?.date);
70394
70546
  }
70395
70547
  };
70396
70548
  var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
70397
70549
 
70398
70550
  // ../api-routes/src/doctor/checks/seo-refresh.ts
70399
- import { and as and85, asc as asc25, desc as desc44, eq as eq99, gt as gt8, inArray as inArray45, sql as sql41 } from "drizzle-orm";
70551
+ import { and as and86, asc as asc25, desc as desc44, eq as eq100, gt as gt8, inArray as inArray45, sql as sql41 } from "drizzle-orm";
70400
70552
  var GRACE_DAYS = 2;
70401
70553
  var DAY_MS7 = 24 * 60 * 60 * 1e3;
70402
70554
  var FIRING_SPAN_MS = 6e4;
@@ -70405,7 +70557,7 @@ function skipped2(code, summary3) {
70405
70557
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
70406
70558
  }
70407
70559
  function seoSyncRuns(projectId) {
70408
- return and85(eq99(runs.projectId, projectId), eq99(runs.kind, RunKinds["seo-sync"]));
70560
+ return and86(eq100(runs.projectId, projectId), eq100(runs.kind, RunKinds["seo-sync"]));
70409
70561
  }
70410
70562
  function scheduleTiming(schedule) {
70411
70563
  if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
@@ -70413,16 +70565,16 @@ function scheduleTiming(schedule) {
70413
70565
  return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
70414
70566
  }
70415
70567
  function latestFiring(ctx, projectId, enabledSince) {
70416
- const scheduled = and85(seoSyncRuns(projectId), eq99(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
70568
+ const scheduled = and86(seoSyncRuns(projectId), eq100(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
70417
70569
  const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc44(runs.createdAt)).limit(1).get();
70418
70570
  if (!newest) return [];
70419
70571
  const newestMs = Date.parse(newest.createdAt);
70420
70572
  const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
70421
- return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and85(scheduled, gt8(runs.createdAt, after))).orderBy(asc25(runs.createdAt), asc25(runs.id)).all();
70573
+ return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and86(scheduled, gt8(runs.createdAt, after))).orderBy(asc25(runs.createdAt), asc25(runs.id)).all();
70422
70574
  }
70423
70575
  function lastCompleted(ctx, projectId) {
70424
70576
  const at = sql41`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
70425
- return ctx.db.select({ id: runs.id, at }).from(runs).where(and85(seoSyncRuns(projectId), eq99(runs.status, RunStatuses.completed))).orderBy(desc44(at)).limit(1).get() ?? null;
70577
+ return ctx.db.select({ id: runs.id, at }).from(runs).where(and86(seoSyncRuns(projectId), eq100(runs.status, RunStatuses.completed))).orderBy(desc44(at)).limit(1).get() ?? null;
70426
70578
  }
70427
70579
  function isIncomplete(run) {
70428
70580
  return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
@@ -70436,7 +70588,7 @@ function unsettledRuns(ctx, projectId, firing, profiles) {
70436
70588
  const incomplete = firing.filter(isIncomplete);
70437
70589
  const firedAt = firing[firing.length - 1]?.createdAt;
70438
70590
  if (incomplete.length === 0 || !firedAt) return incomplete;
70439
- const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq99(seoJobs.runId, runs.id)).where(and85(seoSyncRuns(projectId), eq99(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
70591
+ const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq100(seoJobs.runId, runs.id)).where(and86(seoSyncRuns(projectId), eq100(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
70440
70592
  return incomplete.filter((run) => {
70441
70593
  const profile = profiles.get(run.id) ?? null;
70442
70594
  return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
@@ -70446,7 +70598,7 @@ var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction"
70446
70598
  function refreshReason(ctx, run) {
70447
70599
  const stored = parseRunError(run.error);
70448
70600
  if (stored) return formatRunErrorOneLine(stored);
70449
- 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, eq99(seoTasks.jobId, seoJobs.id)).where(eq99(seoJobs.runId, run.id)).all());
70601
+ 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, eq100(seoTasks.jobId, seoJobs.id)).where(eq100(seoJobs.runId, run.id)).all());
70450
70602
  const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
70451
70603
  const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
70452
70604
  const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
@@ -70466,7 +70618,7 @@ function refreshReason(ctx, run) {
70466
70618
  return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
70467
70619
  }
70468
70620
  function gradeSeoRefresh(ctx, project) {
70469
- const schedule = ctx.db.select().from(schedules).where(and85(eq99(schedules.projectId, project.id), eq99(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
70621
+ const schedule = ctx.db.select().from(schedules).where(and86(eq100(schedules.projectId, project.id), eq100(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
70470
70622
  if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
70471
70623
  if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
70472
70624
  const name = project.name;
@@ -70538,7 +70690,7 @@ function gradeSeoRefresh(ctx, project) {
70538
70690
  };
70539
70691
  }
70540
70692
  if (now >= Date.parse(deadlineAt)) {
70541
- const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and85(seoSyncRuns(project.id), inArray45(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc25(runs.createdAt)).limit(1).get();
70693
+ const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and86(seoSyncRuns(project.id), inArray45(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc25(runs.createdAt)).limit(1).get();
70542
70694
  const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
70543
70695
  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.`;
70544
70696
  return {
@@ -70848,7 +71000,7 @@ var siteReachabilityCheck = {
70848
71000
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
70849
71001
 
70850
71002
  // ../api-routes/src/doctor/checks/api-hosts.ts
70851
- import { eq as eq101 } from "drizzle-orm";
71003
+ import { eq as eq102 } from "drizzle-orm";
70852
71004
 
70853
71005
  // ../api-routes/src/api-host-probe.ts
70854
71006
  import dns4 from "dns/promises";
@@ -70954,7 +71106,7 @@ async function probeApiHost(host, options = {}) {
70954
71106
  }
70955
71107
 
70956
71108
  // ../api-routes/src/doctor/checks/integration-sync.ts
70957
- import { and as and86, count as count7, desc as desc45, eq as eq100, gte as gte28, gt as gt9, inArray as inArray46, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
71109
+ import { and as and87, count as count7, desc as desc45, eq as eq101, gte as gte28, gt as gt9, inArray as inArray46, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
70958
71110
  var SYNC_FAILURE_WINDOW = 3;
70959
71111
  var SUMMARY_ERROR_LIMIT = 300;
70960
71112
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -70981,7 +71133,7 @@ function gscConnection(ctx, project) {
70981
71133
  function gbpConnection(ctx, project) {
70982
71134
  if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
70983
71135
  if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
70984
- const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and86(eq100(gbpLocations.projectId, project.id), eq100(gbpLocations.selected, true))).limit(1).get();
71136
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and87(eq101(gbpLocations.projectId, project.id), eq101(gbpLocations.selected, true))).limit(1).get();
70985
71137
  return selected ? CONNECTED : { state: "no-selected-locations" };
70986
71138
  }
70987
71139
  function gaConnection(ctx, project) {
@@ -71054,16 +71206,16 @@ function latestErrorText(raw) {
71054
71206
  return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
71055
71207
  }
71056
71208
  function readSyncRunState(db, projectId, kind, options = {}) {
71057
- const scope = and86(
71058
- eq100(runs.projectId, projectId),
71059
- eq100(runs.kind, kind),
71060
- ...options.sourceId ? [eq100(runs.sourceId, options.sourceId)] : [],
71209
+ const scope = and87(
71210
+ eq101(runs.projectId, projectId),
71211
+ eq101(runs.kind, kind),
71212
+ ...options.sourceId ? [eq101(runs.sourceId, options.sourceId)] : [],
71061
71213
  ...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
71062
71214
  );
71063
- const graded = options.since ? and86(scope, gte28(runs.createdAt, options.since)) : scope;
71064
- const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and86(graded, inArray46(runs.status, FINISHED_STATUSES))).orderBy(desc45(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
71065
- const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and86(graded, inArray46(runs.status, SUCCESS_STATUSES))).orderBy(desc45(runs.createdAt)).limit(1).get();
71066
- const failureCount = db.select({ count: count7() }).from(runs).where(and86(lastSuccess ? and86(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq100(runs.status, RunStatuses.failed))).get();
71215
+ const graded = options.since ? and87(scope, gte28(runs.createdAt, options.since)) : scope;
71216
+ const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and87(graded, inArray46(runs.status, FINISHED_STATUSES))).orderBy(desc45(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
71217
+ const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and87(graded, inArray46(runs.status, SUCCESS_STATUSES))).orderBy(desc45(runs.createdAt)).limit(1).get();
71218
+ const failureCount = db.select({ count: count7() }).from(runs).where(and87(lastSuccess ? and87(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq101(runs.status, RunStatuses.failed))).get();
71067
71219
  const failuresSinceSuccess = Number(failureCount?.count ?? 0);
71068
71220
  const latest = recent[0];
71069
71221
  return {
@@ -71160,7 +71312,7 @@ function connectionFor(prefix, ctx, project) {
71160
71312
  if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
71161
71313
  if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
71162
71314
  if (prefix === "bing") return ctx.bingConnectionStore?.getConnection(project.canonicalDomain) ? { state: "connected", since: null } : { state: "not-connected" };
71163
- return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq101(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
71315
+ return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq102(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
71164
71316
  }
71165
71317
  function syncCommand(prefix) {
71166
71318
  if (prefix === "gsc") return "google sync";
@@ -71205,7 +71357,7 @@ function definition(prefix) {
71205
71357
  var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
71206
71358
 
71207
71359
  // ../api-routes/src/doctor/checks/gbp-auth.ts
71208
- import { and as and87, eq as eq102 } from "drizzle-orm";
71360
+ import { and as and88, eq as eq103 } from "drizzle-orm";
71209
71361
  var RECENT_SYNC_WARN_DAYS = 4;
71210
71362
  var RECENT_SYNC_FAIL_DAYS = 30;
71211
71363
  function skippedNoProject3() {
@@ -71225,7 +71377,7 @@ function storeUnavailable() {
71225
71377
  };
71226
71378
  }
71227
71379
  function hasSelectedLocation(ctx, projectId) {
71228
- return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and87(eq102(gbpLocations.projectId, projectId), eq102(gbpLocations.selected, true))).limit(1).get();
71380
+ return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, projectId), eq103(gbpLocations.selected, true))).limit(1).get();
71229
71381
  }
71230
71382
  async function resolveGbpToken(ctx) {
71231
71383
  if (!ctx.project) return { ok: false, output: skippedNoProject3() };
@@ -71485,7 +71637,7 @@ var recentSyncCheck = {
71485
71637
  title: "GBP recent sync",
71486
71638
  run: (ctx) => {
71487
71639
  if (!ctx.project) return skippedNoProject3();
71488
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and87(eq102(gbpLocations.projectId, ctx.project.id), eq102(gbpLocations.selected, true))).all();
71640
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, ctx.project.id), eq103(gbpLocations.selected, true))).all();
71489
71641
  if (selected.length === 0) {
71490
71642
  return {
71491
71643
  status: CheckStatuses.skipped,
@@ -71545,7 +71697,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
71545
71697
  );
71546
71698
 
71547
71699
  // ../api-routes/src/doctor/checks/places.ts
71548
- import { eq as eq103 } from "drizzle-orm";
71700
+ import { eq as eq104 } from "drizzle-orm";
71549
71701
  var apiKeyCheck = {
71550
71702
  id: "gbp.places.api-key",
71551
71703
  category: CheckCategories.auth,
@@ -71590,7 +71742,7 @@ var apiKeyCheck = {
71590
71742
  details: { tier: cfg.tier }
71591
71743
  };
71592
71744
  }
71593
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq103(gbpLocations.projectId, ctx.project.id)).all();
71745
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq104(gbpLocations.projectId, ctx.project.id)).all();
71594
71746
  const selected = rows.filter((r) => r.selected);
71595
71747
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
71596
71748
  const details = {
@@ -71623,7 +71775,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
71623
71775
  );
71624
71776
 
71625
71777
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
71626
- import { and as and88, desc as desc46, eq as eq104, gte as gte29, inArray as inArray47, sql as sql42 } from "drizzle-orm";
71778
+ import { and as and89, desc as desc46, eq as eq105, gte as gte29, inArray as inArray47, sql as sql42 } from "drizzle-orm";
71627
71779
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
71628
71780
  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`.";
71629
71781
  var reviewsAccessCheck = {
@@ -71651,7 +71803,7 @@ var reviewsAccessCheck = {
71651
71803
  reviewsAccess: gbpLocations.reviewsAccess,
71652
71804
  reviewsAccessReason: gbpLocations.reviewsAccessReason,
71653
71805
  reviewsCheckedAt: gbpLocations.reviewsCheckedAt
71654
- }).from(gbpLocations).where(and88(eq104(gbpLocations.projectId, project.id), eq104(gbpLocations.selected, true))).all();
71806
+ }).from(gbpLocations).where(and89(eq105(gbpLocations.projectId, project.id), eq105(gbpLocations.selected, true))).all();
71655
71807
  if (locations.length === 0) {
71656
71808
  return {
71657
71809
  status: CheckStatuses.skipped,
@@ -71671,7 +71823,7 @@ var reviewsAccessCheck = {
71671
71823
  const places = ctx.getPlacesConfig?.();
71672
71824
  const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
71673
71825
  const v4Worked = new Set(
71674
- ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and88(eq104(gbpReviewRatings.projectId, project.id), eq104(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
71826
+ ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and89(eq105(gbpReviewRatings.projectId, project.id), eq105(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
71675
71827
  );
71676
71828
  const coverageOf = (loc) => {
71677
71829
  if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
@@ -71705,7 +71857,7 @@ var reviewsAccessCheck = {
71705
71857
  none > 0 ? `${none} have no review source` : null
71706
71858
  ].filter((part) => part !== null);
71707
71859
  const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
71708
- const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq104(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
71860
+ const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq105(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
71709
71861
  const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
71710
71862
  if (none > 0 && subscribed) {
71711
71863
  return {
@@ -71744,8 +71896,8 @@ var reviewDeliveryCheck = {
71744
71896
  attempts: reviewWebhookDeliveries.attempts,
71745
71897
  lastTouched,
71746
71898
  payload: reviewWebhookDeliveries.payload
71747
- }).from(reviewWebhookDeliveries).where(and88(
71748
- eq104(reviewWebhookDeliveries.projectId, ctx.project.id),
71899
+ }).from(reviewWebhookDeliveries).where(and89(
71900
+ eq105(reviewWebhookDeliveries.projectId, ctx.project.id),
71749
71901
  inArray47(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
71750
71902
  gte29(lastTouched, since)
71751
71903
  )).orderBy(desc46(lastTouched)).all();
@@ -71757,7 +71909,7 @@ var reviewDeliveryCheck = {
71757
71909
  remediation: null
71758
71910
  };
71759
71911
  }
71760
- const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq104(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
71912
+ const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq105(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
71761
71913
  const deliveries = rows.map((row) => {
71762
71914
  const review = row.payload.reviews[0];
71763
71915
  return {
@@ -72331,7 +72483,7 @@ var RUNTIME_STATE_CHECKS = [
72331
72483
  ];
72332
72484
 
72333
72485
  // ../api-routes/src/doctor/checks/traffic-source.ts
72334
- import { and as and89, desc as desc47, eq as eq105, gte as gte30, inArray as inArray48, isNotNull as isNotNull9, ne as ne17, sql as sql43 } from "drizzle-orm";
72486
+ import { and as and90, desc as desc47, eq as eq106, gte as gte30, inArray as inArray48, isNotNull as isNotNull9, ne as ne17, sql as sql43 } from "drizzle-orm";
72335
72487
  var RECENT_DATA_WARN_DAYS = 7;
72336
72488
  var RECENT_DATA_FAIL_DAYS = 30;
72337
72489
  function isCloudflareDirectPush(source) {
@@ -72370,8 +72522,8 @@ function skippedNoProject5() {
72370
72522
  function loadProbes(ctx) {
72371
72523
  if (!ctx.project) return [];
72372
72524
  const rows = ctx.db.select().from(trafficSources).where(
72373
- and89(
72374
- eq105(trafficSources.projectId, ctx.project.id),
72525
+ and90(
72526
+ eq106(trafficSources.projectId, ctx.project.id),
72375
72527
  ne17(trafficSources.status, TrafficSourceStatuses.archived)
72376
72528
  )
72377
72529
  ).all();
@@ -72509,8 +72661,8 @@ var recentDataCheck = {
72509
72661
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
72510
72662
  const recentCrawlers = Number(
72511
72663
  ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
72512
- and89(
72513
- eq105(crawlerEventsHourly.projectId, ctx.project.id),
72664
+ and90(
72665
+ eq106(crawlerEventsHourly.projectId, ctx.project.id),
72514
72666
  inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
72515
72667
  gte30(crawlerEventsHourly.tsHour, warnCutoff)
72516
72668
  )
@@ -72518,8 +72670,8 @@ var recentDataCheck = {
72518
72670
  );
72519
72671
  const recentReferrals = Number(
72520
72672
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
72521
- and89(
72522
- eq105(aiReferralEventsHourly.projectId, ctx.project.id),
72673
+ and90(
72674
+ eq106(aiReferralEventsHourly.projectId, ctx.project.id),
72523
72675
  inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
72524
72676
  gte30(aiReferralEventsHourly.tsHour, warnCutoff)
72525
72677
  )
@@ -72527,8 +72679,8 @@ var recentDataCheck = {
72527
72679
  );
72528
72680
  const recentUserFetches = Number(
72529
72681
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72530
- and89(
72531
- eq105(aiUserFetchEventsHourly.projectId, ctx.project.id),
72682
+ and90(
72683
+ eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
72532
72684
  inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
72533
72685
  gte30(aiUserFetchEventsHourly.tsHour, warnCutoff)
72534
72686
  )
@@ -72553,8 +72705,8 @@ var recentDataCheck = {
72553
72705
  }
72554
72706
  const olderCrawlers = Number(
72555
72707
  ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
72556
- and89(
72557
- eq105(crawlerEventsHourly.projectId, ctx.project.id),
72708
+ and90(
72709
+ eq106(crawlerEventsHourly.projectId, ctx.project.id),
72558
72710
  inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
72559
72711
  gte30(crawlerEventsHourly.tsHour, failCutoff)
72560
72712
  )
@@ -72562,8 +72714,8 @@ var recentDataCheck = {
72562
72714
  );
72563
72715
  const olderReferrals = Number(
72564
72716
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
72565
- and89(
72566
- eq105(aiReferralEventsHourly.projectId, ctx.project.id),
72717
+ and90(
72718
+ eq106(aiReferralEventsHourly.projectId, ctx.project.id),
72567
72719
  inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
72568
72720
  gte30(aiReferralEventsHourly.tsHour, failCutoff)
72569
72721
  )
@@ -72571,8 +72723,8 @@ var recentDataCheck = {
72571
72723
  );
72572
72724
  const olderUserFetches = Number(
72573
72725
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72574
- and89(
72575
- eq105(aiUserFetchEventsHourly.projectId, ctx.project.id),
72726
+ and90(
72727
+ eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
72576
72728
  inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
72577
72729
  gte30(aiUserFetchEventsHourly.tsHour, failCutoff)
72578
72730
  )
@@ -72899,10 +73051,10 @@ var syncLagCheck = {
72899
73051
  }
72900
73052
  };
72901
73053
  function lastQueueStopReason(ctx, sourceId) {
72902
- const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and89(
72903
- eq105(runs.projectId, ctx.project.id),
72904
- eq105(runs.kind, RunKinds["traffic-sync"]),
72905
- eq105(runs.sourceId, sourceId),
73054
+ const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and90(
73055
+ eq106(runs.projectId, ctx.project.id),
73056
+ eq106(runs.kind, RunKinds["traffic-sync"]),
73057
+ eq106(runs.sourceId, sourceId),
72906
73058
  isNotNull9(runs.syncSummary)
72907
73059
  )).orderBy(desc47(runs.createdAt)).limit(1).get();
72908
73060
  return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
@@ -73702,7 +73854,7 @@ async function doctorRoutes(app, opts) {
73702
73854
  }
73703
73855
 
73704
73856
  // ../api-routes/src/status.ts
73705
- import { and as and90, desc as desc48, eq as eq106, isNull as isNull15 } from "drizzle-orm";
73857
+ import { and as and91, desc as desc48, eq as eq107, isNull as isNull15 } from "drizzle-orm";
73706
73858
  function ageDays(iso2) {
73707
73859
  if (!iso2) return null;
73708
73860
  const ms = Date.now() - Date.parse(iso2);
@@ -73752,7 +73904,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
73752
73904
  };
73753
73905
  }
73754
73906
  function latestDate2(db, table, projectId) {
73755
- const row = db.select({ date: table.date }).from(table).where(eq106(table.projectId, projectId)).orderBy(desc48(table.date)).limit(1).get();
73907
+ const row = db.select({ date: table.date }).from(table).where(eq107(table.projectId, projectId)).orderBy(desc48(table.date)).limit(1).get();
73756
73908
  return row?.date ?? null;
73757
73909
  }
73758
73910
  function projectRows(opts, project) {
@@ -73767,7 +73919,7 @@ function projectRows(opts, project) {
73767
73919
  const rows = [];
73768
73920
  const gsc = gscConnection(ctx, info);
73769
73921
  if (connected(gsc)) {
73770
- const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq106(gscDataWatermarks.projectId, project.id)).limit(1).get();
73922
+ const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq107(gscDataWatermarks.projectId, project.id)).limit(1).get();
73771
73923
  const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
73772
73924
  const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
73773
73925
  rows.push(makeRow("gsc", "Search Console", RunKinds["gsc-sync"], state, SYNC_IDLE_DAYS, dataThrough, null, ["gsc.sync.recent-failures"], GSC_DATA_AGING_DAYS));
@@ -73783,12 +73935,12 @@ function projectRows(opts, project) {
73783
73935
  rows.push(makeRow("bing", "Bing Webmaster Tools", RunKinds["bing-inspect-sitemap"], readSyncRunState(opts.db, project.id, RunKinds["bing-inspect-sitemap"]), null));
73784
73936
  rows.push(makeRow("bing-search", "Bing search data", RunKinds["bing-sync"], readSyncRunState(opts.db, project.id, RunKinds["bing-sync"]), SYNC_IDLE_DAYS, latestDate2(opts.db, bingTrafficDaily, project.id), null, ["bing.sync.recent-failures"], BING_DATA_AGING_DAYS));
73785
73937
  }
73786
- const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq106(adsConnections.projectId, project.id)).limit(1).get();
73938
+ const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq107(adsConnections.projectId, project.id)).limit(1).get();
73787
73939
  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));
73788
73940
  const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
73789
73941
  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));
73790
73942
  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));
73791
- const sources = opts.db.select().from(trafficSources).where(and90(eq106(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
73943
+ const sources = opts.db.select().from(trafficSources).where(and91(eq107(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
73792
73944
  for (const source of sources) {
73793
73945
  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"]);
73794
73946
  const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
@@ -73800,7 +73952,7 @@ function projectRows(opts, project) {
73800
73952
  }
73801
73953
  function projectStatus(opts, project) {
73802
73954
  const integrations = projectRows(opts, project);
73803
- const healthRow = opts.db.select().from(doctorHealthState).where(eq106(doctorHealthState.projectId, project.id)).limit(1).get();
73955
+ const healthRow = opts.db.select().from(doctorHealthState).where(eq107(doctorHealthState.projectId, project.id)).limit(1).get();
73804
73956
  const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
73805
73957
  const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
73806
73958
  const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
@@ -73820,7 +73972,7 @@ async function statusRoutes(app, opts) {
73820
73972
  if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
73821
73973
  return instanceStatusDtoSchema.parse(result);
73822
73974
  }
73823
- const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq106(projects.id, scopedId)) : opts.db.select().from(projects)).all();
73975
+ const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq107(projects.id, scopedId)) : opts.db.select().from(projects)).all();
73824
73976
  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)) };
73825
73977
  for (const project of output.projects) {
73826
73978
  const key = project.state === "never-synced" ? "neverSynced" : project.state;
@@ -73833,7 +73985,7 @@ async function statusRoutes(app, opts) {
73833
73985
 
73834
73986
  // ../api-routes/src/discovery/routes.ts
73835
73987
  import crypto57 from "crypto";
73836
- import { and as and91, desc as desc49, eq as eq107, gte as gte31, inArray as inArray49, isNull as isNull16, or as or20 } from "drizzle-orm";
73988
+ import { and as and92, desc as desc49, eq as eq108, gte as gte31, inArray as inArray49, isNull as isNull16, or as or20 } from "drizzle-orm";
73837
73989
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
73838
73990
  async function discoveryRoutes(app, opts) {
73839
73991
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -73867,13 +74019,13 @@ async function discoveryRoutes(app, opts) {
73867
74019
  const now = (/* @__PURE__ */ new Date()).toISOString();
73868
74020
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
73869
74021
  const decision = app.db.transaction((tx) => {
73870
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and91(
73871
- eq107(discoverySessions.projectId, project.id),
73872
- eq107(discoverySessions.icpDescription, icpDescription),
74022
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and92(
74023
+ eq108(discoverySessions.projectId, project.id),
74024
+ eq108(discoverySessions.icpDescription, icpDescription),
73873
74025
  // Buyer is part of session identity: it changes the seed prompt's
73874
74026
  // semantics, so a request with a different (or no) buyer must start
73875
74027
  // its own session, never adopt another buyer's probes.
73876
- parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq107(discoverySessions.buyerDescription, parsed.data.buyerDescription),
74028
+ parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq108(discoverySessions.buyerDescription, parsed.data.buyerDescription),
73877
74029
  // Locations are identity too: a different service-area subset seeds
73878
74030
  // and probes a different geo, so it must never reuse another geo's
73879
74031
  // session. resolveLocations is deterministic (project-config order),
@@ -73883,11 +74035,11 @@ async function discoveryRoutes(app, opts) {
73883
74035
  // locations a NULL row's subset is unknowable, so it conservatively
73884
74036
  // never reuses — a one-time, bounded (2h window) non-reuse after
73885
74037
  // upgrade, never a wrong reuse.
73886
- locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq107(discoverySessions.locations, locations)) : eq107(discoverySessions.locations, locations),
74038
+ locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq108(discoverySessions.locations, locations)) : eq108(discoverySessions.locations, locations),
73887
74039
  // Seed provider set is identity: a different phrasing distribution
73888
74040
  // must never reuse another set's session. Null (= Gemini-only
73889
74041
  // default, including explicit ['gemini']) matches legacy rows.
73890
- seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq107(discoverySessions.seedProviders, seedProviders),
74042
+ seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq108(discoverySessions.seedProviders, seedProviders),
73891
74043
  inArray49(discoverySessions.status, [
73892
74044
  DiscoverySessionStatuses.queued,
73893
74045
  DiscoverySessionStatuses.seeding,
@@ -73963,7 +74115,7 @@ async function discoveryRoutes(app, opts) {
73963
74115
  const project = resolveProject(app.db, request.params.name);
73964
74116
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
73965
74117
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
73966
- const rows = app.db.select().from(discoverySessions).where(eq107(discoverySessions.projectId, project.id)).orderBy(desc49(discoverySessions.createdAt)).limit(limit).all();
74118
+ const rows = app.db.select().from(discoverySessions).where(eq108(discoverySessions.projectId, project.id)).orderBy(desc49(discoverySessions.createdAt)).limit(limit).all();
73967
74119
  return reply.send(rows.map(serializeSession));
73968
74120
  }
73969
74121
  );
@@ -73971,11 +74123,11 @@ async function discoveryRoutes(app, opts) {
73971
74123
  "/projects/:name/discover/sessions/:id",
73972
74124
  async (request, reply) => {
73973
74125
  const project = resolveProject(app.db, request.params.name);
73974
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74126
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
73975
74127
  if (!session || session.projectId !== project.id) {
73976
74128
  throw notFound("Discovery session", request.params.id);
73977
74129
  }
73978
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74130
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
73979
74131
  const detail = {
73980
74132
  ...serializeSession(session),
73981
74133
  probes: probeRows.map(serializeProbe)
@@ -73987,7 +74139,7 @@ async function discoveryRoutes(app, opts) {
73987
74139
  "/projects/:name/discover/sessions/:id/harvest",
73988
74140
  async (request, reply) => {
73989
74141
  const project = resolveProject(app.db, request.params.name);
73990
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74142
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
73991
74143
  if (!session || session.projectId !== project.id) {
73992
74144
  throw notFound("Discovery session", request.params.id);
73993
74145
  }
@@ -73995,7 +74147,7 @@ async function discoveryRoutes(app, opts) {
73995
74147
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
73996
74148
  const applyAnchor = request.query.anchor !== "false";
73997
74149
  const provider = session.seedProvider ?? "gemini";
73998
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74150
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
73999
74151
  const extract = opts.harvestSearchQueries;
74000
74152
  const probesWithQueries = probeRows.map((row) => {
74001
74153
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -74006,7 +74158,7 @@ async function discoveryRoutes(app, opts) {
74006
74158
  return { searchQueries: [] };
74007
74159
  }
74008
74160
  });
74009
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq107(queries.projectId, project.id)).all().map((r) => r.query);
74161
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query);
74010
74162
  const anchorTerms = buildHarvestAnchorTerms(
74011
74163
  [session.icpDescription ?? "", ...trackedQueries],
74012
74164
  effectiveDomains(project)
@@ -74053,11 +74205,11 @@ async function discoveryRoutes(app, opts) {
74053
74205
  "/projects/:name/discover/sessions/:id/promote",
74054
74206
  async (request, reply) => {
74055
74207
  const project = resolveProject(app.db, request.params.name);
74056
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74208
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
74057
74209
  if (!session || session.projectId !== project.id) {
74058
74210
  throw notFound("Discovery session", request.params.id);
74059
74211
  }
74060
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74212
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
74061
74213
  const cited = /* @__PURE__ */ new Set();
74062
74214
  const aspirational = /* @__PURE__ */ new Set();
74063
74215
  const wasted = /* @__PURE__ */ new Set();
@@ -74092,7 +74244,7 @@ async function discoveryRoutes(app, opts) {
74092
74244
  );
74093
74245
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
74094
74246
  const project = resolveProject(app.db, request.params.name);
74095
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74247
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
74096
74248
  if (!session || session.projectId !== project.id) {
74097
74249
  throw notFound("Discovery session", request.params.id);
74098
74250
  }
@@ -74115,7 +74267,7 @@ async function discoveryRoutes(app, opts) {
74115
74267
  const bucketSet = new Set(buckets);
74116
74268
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
74117
74269
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
74118
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74270
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
74119
74271
  const candidateQueries = /* @__PURE__ */ new Set();
74120
74272
  for (const probe of probeRows) {
74121
74273
  if (!probe.bucket) continue;
@@ -74123,7 +74275,7 @@ async function discoveryRoutes(app, opts) {
74123
74275
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
74124
74276
  }
74125
74277
  const existingQueries = new Set(
74126
- app.db.select({ query: queries.query }).from(queries).where(eq107(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
74278
+ app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
74127
74279
  );
74128
74280
  const promotedQueries = [];
74129
74281
  const skippedQueries = [];
@@ -74337,7 +74489,7 @@ function planCompetitorPromotion(input) {
74337
74489
 
74338
74490
  // ../api-routes/src/discovery/orchestrate.ts
74339
74491
  import crypto58 from "crypto";
74340
- import { eq as eq108 } from "drizzle-orm";
74492
+ import { eq as eq109 } from "drizzle-orm";
74341
74493
  var DEFAULT_MAX_PROBES = 100;
74342
74494
  var ABSOLUTE_MAX_PROBES = 500;
74343
74495
  function classifyProbeBucket(input) {
@@ -74423,7 +74575,7 @@ async function executeDiscovery(opts) {
74423
74575
  status: DiscoverySessionStatuses.seeding,
74424
74576
  dedupThreshold,
74425
74577
  startedAt
74426
- }).where(eq108(discoverySessions.id, opts.sessionId)).run();
74578
+ }).where(eq109(discoverySessions.id, opts.sessionId)).run();
74427
74579
  const seedResult = await opts.deps.seed({
74428
74580
  project: opts.project,
74429
74581
  icpDescription: opts.icpDescription,
@@ -74484,7 +74636,7 @@ async function executeDiscovery(opts) {
74484
74636
  dedupBandPairFraction: dedupStats.bandPairFraction,
74485
74637
  dedupPairsTotal: dedupStats.pairsTotal,
74486
74638
  warning
74487
- }).where(eq108(discoverySessions.id, opts.sessionId)).run();
74639
+ }).where(eq109(discoverySessions.id, opts.sessionId)).run();
74488
74640
  const probeLocation = opts.locations?.[0];
74489
74641
  const probeResults = await mapWithConcurrency(
74490
74642
  probedCanonicals,
@@ -74538,7 +74690,7 @@ async function executeDiscovery(opts) {
74538
74690
  wastedCount: buckets["wasted-surface"],
74539
74691
  competitorMap,
74540
74692
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
74541
- }).where(eq108(discoverySessions.id, opts.sessionId)).run();
74693
+ }).where(eq109(discoverySessions.id, opts.sessionId)).run();
74542
74694
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
74543
74695
  return {
74544
74696
  buckets,
@@ -74578,7 +74730,7 @@ function markSessionFailed(db, sessionId, error) {
74578
74730
  status: DiscoverySessionStatuses.failed,
74579
74731
  error,
74580
74732
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
74581
- }).where(eq108(discoverySessions.id, sessionId)).run();
74733
+ }).where(eq109(discoverySessions.id, sessionId)).run();
74582
74734
  }
74583
74735
  function dedupeStrings(input) {
74584
74736
  const seen2 = /* @__PURE__ */ new Set();
@@ -74596,12 +74748,12 @@ function dedupeStrings(input) {
74596
74748
 
74597
74749
  // ../api-routes/src/technical-aeo.ts
74598
74750
  import crypto59 from "crypto";
74599
- import { and as and93, asc as asc27, count as count8, desc as desc50, eq as eq110, gte as gte32, inArray as inArray50, isNotNull as isNotNull10, isNull as isNull17, lt as lt18, or as or21, sql as sql45 } from "drizzle-orm";
74751
+ import { and as and94, asc as asc27, count as count8, desc as desc50, eq as eq111, gte as gte32, inArray as inArray50, isNotNull as isNotNull10, isNull as isNull17, lt as lt19, or as or21, sql as sql45 } from "drizzle-orm";
74600
74752
  import { alias } from "drizzle-orm/sqlite-core";
74601
74753
 
74602
74754
  // ../api-routes/src/technical-aeo-issues.ts
74603
74755
  import { createHash as createHash10 } from "crypto";
74604
- import { and as and92, asc as asc26, eq as eq109, gt as gt10, sql as sql44 } from "drizzle-orm";
74756
+ import { and as and93, asc as asc26, eq as eq110, gt as gt10, sql as sql44 } from "drizzle-orm";
74605
74757
  var ADVISORY_CODES = /* @__PURE__ */ new Set([
74606
74758
  "entity-consistency.title.short",
74607
74759
  "site.duplicate-title",
@@ -74746,10 +74898,10 @@ function readSiteAuditIssues(input) {
74746
74898
  }
74747
74899
  }
74748
74900
  if (snapshot.detailsAvailable && snapshot.attemptId) {
74749
- const scope = and92(
74750
- eq109(siteCrawlPages.projectId, project.id),
74751
- eq109(siteCrawlPages.runId, snapshot.runId),
74752
- eq109(siteCrawlPages.attemptId, snapshot.attemptId)
74901
+ const scope = and93(
74902
+ eq110(siteCrawlPages.projectId, project.id),
74903
+ eq110(siteCrawlPages.runId, snapshot.runId),
74904
+ eq110(siteCrawlPages.attemptId, snapshot.attemptId)
74753
74905
  );
74754
74906
  const size = db.select({ bytes: sql44`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
74755
74907
  if (size + crossBytes > MAX_EVIDENCE_BYTES) {
@@ -74762,7 +74914,7 @@ function readSiteAuditIssues(input) {
74762
74914
  url: siteCrawlPages.url,
74763
74915
  auditScore: siteCrawlPages.auditScore,
74764
74916
  auditFields: siteCrawlPages.auditFields
74765
- }).from(siteCrawlPages).where(and92(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc26(siteCrawlPages.nodeKey)).limit(200).all();
74917
+ }).from(siteCrawlPages).where(and93(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc26(siteCrawlPages.nodeKey)).limit(200).all();
74766
74918
  for (const page2 of pages) {
74767
74919
  evidenceHash.update(JSON.stringify(page2));
74768
74920
  const fields = record(page2.auditFields);
@@ -74899,9 +75051,9 @@ var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
74899
75051
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
74900
75052
  function surfaceableSiteAuditFilters(projectId) {
74901
75053
  return [
74902
- eq110(siteAuditSnapshots.projectId, projectId),
74903
- eq110(runs.projectId, projectId),
74904
- eq110(runs.kind, RunKinds["site-audit"]),
75054
+ eq111(siteAuditSnapshots.projectId, projectId),
75055
+ eq111(runs.projectId, projectId),
75056
+ eq111(runs.kind, RunKinds["site-audit"]),
74905
75057
  inArray50(runs.status, SURFACEABLE_STATUSES),
74906
75058
  notProbeRun()
74907
75059
  ];
@@ -75142,8 +75294,8 @@ function parseLinkKind(value) {
75142
75294
  return allowed.data;
75143
75295
  }
75144
75296
  function linkKindFilter(linkKind) {
75145
- if (linkKind === SiteHealthLinkKinds.content) return eq110(siteCrawlEdges.isTemplate, false);
75146
- if (linkKind === SiteHealthLinkKinds.template) return eq110(siteCrawlEdges.isTemplate, true);
75297
+ if (linkKind === SiteHealthLinkKinds.content) return eq111(siteCrawlEdges.isTemplate, false);
75298
+ if (linkKind === SiteHealthLinkKinds.template) return eq111(siteCrawlEdges.isTemplate, true);
75147
75299
  return void 0;
75148
75300
  }
75149
75301
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -75247,9 +75399,9 @@ function changedFields(before, after, fields) {
75247
75399
  }
75248
75400
  async function technicalAeoRoutes(app, opts) {
75249
75401
  const visibleCrawlFilters = (projectId) => [
75250
- eq110(siteCrawlSnapshots.projectId, projectId),
75251
- eq110(runs.projectId, projectId),
75252
- eq110(runs.kind, RunKinds["site-audit"]),
75402
+ eq111(siteCrawlSnapshots.projectId, projectId),
75403
+ eq111(runs.projectId, projectId),
75404
+ eq111(runs.kind, RunKinds["site-audit"]),
75253
75405
  inArray50(runs.status, SURFACEABLE_STATUSES),
75254
75406
  notProbeRun()
75255
75407
  ];
@@ -75257,10 +75409,10 @@ async function technicalAeoRoutes(app, opts) {
75257
75409
  if (!requestedDate) return void 0;
75258
75410
  const date = sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
75259
75411
  const filters = visibleCrawlFilters(projectId);
75260
- const allDates = and93(...filters);
75261
- const matchingDates = and93(...filters, eq110(sql45`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
75262
- const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc50(date)).limit(10).all().map((row) => row.date);
75263
- const countDates = (where) => app.db.select({ value: sql45`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
75412
+ const allDates = and94(...filters);
75413
+ const matchingDates = and94(...filters, eq111(sql45`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
75414
+ const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc50(date)).limit(10).all().map((row) => row.date);
75415
+ const countDates = (where) => app.db.select({ value: sql45`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
75264
75416
  return {
75265
75417
  recentDates: readDates(allDates),
75266
75418
  totalDates: countDates(allDates),
@@ -75270,15 +75422,15 @@ async function technicalAeoRoutes(app, opts) {
75270
75422
  };
75271
75423
  const resolveCrawl = (projectId, runId, requestedDate, scope = {}) => {
75272
75424
  const filters = visibleCrawlFilters(projectId);
75273
- if (scope.recordedBefore) filters.push(lt18(siteCrawlSnapshots.createdAt, scope.recordedBefore));
75425
+ if (scope.recordedBefore) filters.push(lt19(siteCrawlSnapshots.createdAt, scope.recordedBefore));
75274
75426
  const newestIfComplete = sql45`case when ${siteCrawlSnapshots.complete} then ${siteCrawlSnapshots.createdAt} end`;
75275
75427
  if (requestedDate) {
75276
75428
  const nextDate2 = /* @__PURE__ */ new Date(`${requestedDate}T00:00:00.000Z`);
75277
75429
  nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
75278
- filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate), lt18(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
75430
+ filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate), lt19(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
75279
75431
  }
75280
75432
  const selectionFilters = scope.scoredOnly ? [...filters, sql45`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
75281
- const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(...selectionFilters, runId ? eq110(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
75433
+ const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(...selectionFilters, runId ? eq111(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
75282
75434
  desc50(sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
75283
75435
  desc50(siteCrawlSnapshots.complete),
75284
75436
  desc50(newestIfComplete),
@@ -75290,18 +75442,18 @@ async function technicalAeoRoutes(app, opts) {
75290
75442
  const date = selected.snapshot.createdAt.slice(0, 10);
75291
75443
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
75292
75444
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
75293
- const sameDate = and93(
75445
+ const sameDate = and94(
75294
75446
  ...filters,
75295
75447
  gte32(siteCrawlSnapshots.createdAt, date),
75296
- lt18(siteCrawlSnapshots.createdAt, nextDate.toISOString())
75448
+ lt19(siteCrawlSnapshots.createdAt, nextDate.toISOString())
75297
75449
  );
75298
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75450
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75299
75451
  const candidates = app.db.select({
75300
75452
  runId: siteCrawlSnapshots.runId,
75301
75453
  createdAt: siteCrawlSnapshots.createdAt,
75302
75454
  complete: siteCrawlSnapshots.complete,
75303
75455
  pages: siteCrawlSnapshots.pagesFetched
75304
- }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
75456
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
75305
75457
  desc50(siteCrawlSnapshots.complete),
75306
75458
  desc50(newestIfComplete),
75307
75459
  desc50(siteCrawlSnapshots.pagesFetched),
@@ -75321,11 +75473,11 @@ async function technicalAeoRoutes(app, opts) {
75321
75473
  const resolveAudit = (projectId, runId, recordedBefore) => {
75322
75474
  const filters = [
75323
75475
  ...surfaceableSiteAuditFilters(projectId),
75324
- ...recordedBefore ? [lt18(siteAuditSnapshots.createdAt, recordedBefore)] : []
75476
+ ...recordedBefore ? [lt19(siteAuditSnapshots.createdAt, recordedBefore)] : []
75325
75477
  ];
75326
75478
  const complete = sql45`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
75327
75479
  const newestIfComplete = sql45`case when ${runs.status} = ${RunStatuses.completed} then ${siteAuditSnapshots.createdAt} end`;
75328
- const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(and93(...filters, pinnedRunId ? eq110(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
75480
+ const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...filters, pinnedRunId ? eq111(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
75329
75481
  desc50(sql45`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
75330
75482
  desc50(complete),
75331
75483
  desc50(newestIfComplete),
@@ -75347,18 +75499,18 @@ async function technicalAeoRoutes(app, opts) {
75347
75499
  const date = selected.snap.createdAt.slice(0, 10);
75348
75500
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
75349
75501
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
75350
- const sameDate = and93(
75502
+ const sameDate = and94(
75351
75503
  ...filters,
75352
75504
  gte32(siteAuditSnapshots.createdAt, date),
75353
- lt18(siteAuditSnapshots.createdAt, nextDate.toISOString())
75505
+ lt19(siteAuditSnapshots.createdAt, nextDate.toISOString())
75354
75506
  );
75355
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75507
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75356
75508
  const candidates = app.db.select({
75357
75509
  runId: siteAuditSnapshots.runId,
75358
75510
  createdAt: siteAuditSnapshots.createdAt,
75359
75511
  complete,
75360
75512
  pages: siteAuditSnapshots.pagesAudited
75361
- }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
75513
+ }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
75362
75514
  desc50(complete),
75363
75515
  desc50(newestIfComplete),
75364
75516
  desc50(siteAuditSnapshots.pagesAudited),
@@ -75425,19 +75577,19 @@ async function technicalAeoRoutes(app, opts) {
75425
75577
  };
75426
75578
  const inventorySummaryFor = (scope) => {
75427
75579
  if (!scope) return null;
75428
- const inScope = and93(
75429
- eq110(siteCrawlPages.projectId, scope.projectId),
75430
- eq110(siteCrawlPages.runId, scope.runId),
75431
- eq110(siteCrawlPages.attemptId, scope.attemptId)
75580
+ const inScope = and94(
75581
+ eq111(siteCrawlPages.projectId, scope.projectId),
75582
+ eq111(siteCrawlPages.runId, scope.runId),
75583
+ eq111(siteCrawlPages.attemptId, scope.attemptId)
75432
75584
  );
75433
75585
  const groups = crawlReasonGroups(inScope);
75434
75586
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
75435
75587
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
75436
- const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(and93(inScope, isNotNull10(siteCrawlPages.urlVersionOf))).get()?.value ?? 0;
75437
- const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(and93(
75438
- eq110(siteCrawlAttempts.projectId, scope.projectId),
75439
- eq110(siteCrawlAttempts.runId, scope.runId),
75440
- eq110(siteCrawlAttempts.id, scope.attemptId)
75588
+ const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(and94(inScope, isNotNull10(siteCrawlPages.urlVersionOf))).get()?.value ?? 0;
75589
+ const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(and94(
75590
+ eq111(siteCrawlAttempts.projectId, scope.projectId),
75591
+ eq111(siteCrawlAttempts.runId, scope.runId),
75592
+ eq111(siteCrawlAttempts.id, scope.attemptId)
75441
75593
  )).get()?.value ?? 0;
75442
75594
  return {
75443
75595
  scope: "selected-snapshot",
@@ -75457,33 +75609,33 @@ async function technicalAeoRoutes(app, opts) {
75457
75609
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
75458
75610
  const pageInScope = (scope, selector) => {
75459
75611
  if (!selector.nodeKey && !selector.url) return null;
75460
- return app.db.select().from(siteCrawlPages).where(and93(
75461
- eq110(siteCrawlPages.projectId, scope.projectId),
75462
- eq110(siteCrawlPages.runId, scope.runId),
75463
- eq110(siteCrawlPages.attemptId, scope.attemptId),
75464
- selector.nodeKey ? eq110(siteCrawlPages.nodeKey, selector.nodeKey) : eq110(siteCrawlPages.url, selector.url)
75612
+ return app.db.select().from(siteCrawlPages).where(and94(
75613
+ eq111(siteCrawlPages.projectId, scope.projectId),
75614
+ eq111(siteCrawlPages.runId, scope.runId),
75615
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
75616
+ selector.nodeKey ? eq111(siteCrawlPages.nodeKey, selector.nodeKey) : eq111(siteCrawlPages.url, selector.url)
75465
75617
  )).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
75466
75618
  };
75467
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and93(
75468
- eq110(siteCrawlPages.projectId, scope.projectId),
75469
- eq110(siteCrawlPages.runId, scope.runId),
75470
- eq110(siteCrawlPages.attemptId, scope.attemptId),
75471
- eq110(siteCrawlPages.depth, 0)
75619
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and94(
75620
+ eq111(siteCrawlPages.projectId, scope.projectId),
75621
+ eq111(siteCrawlPages.runId, scope.runId),
75622
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
75623
+ eq111(siteCrawlPages.depth, 0)
75472
75624
  )).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
75473
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and93(
75474
- eq110(runs.id, runId),
75475
- eq110(runs.projectId, projectId),
75476
- eq110(runs.kind, RunKinds["site-audit"]),
75625
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and94(
75626
+ eq111(runs.id, runId),
75627
+ eq111(runs.projectId, projectId),
75628
+ eq111(runs.kind, RunKinds["site-audit"]),
75477
75629
  inArray50(runs.status, SURFACEABLE_STATUSES),
75478
75630
  notProbeRun()
75479
75631
  )).limit(1).get());
75480
75632
  const assertKnownAuditRun = (projectId, runId) => {
75481
75633
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
75482
75634
  };
75483
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(and93(
75484
- eq110(siteAuditSnapshots.projectId, projectId),
75485
- eq110(runs.projectId, projectId),
75486
- eq110(runs.kind, RunKinds["site-audit"]),
75635
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(
75636
+ eq111(siteAuditSnapshots.projectId, projectId),
75637
+ eq111(runs.projectId, projectId),
75638
+ eq111(runs.kind, RunKinds["site-audit"]),
75487
75639
  inArray50(runs.status, SURFACEABLE_STATUSES),
75488
75640
  notProbeRun()
75489
75641
  )).limit(1).get());
@@ -75585,9 +75737,9 @@ async function technicalAeoRoutes(app, opts) {
75585
75737
  };
75586
75738
  }
75587
75739
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
75588
- const conds = [eq110(siteAuditPages.projectId, project.id), eq110(siteAuditPages.runId, latest.snap.runId)];
75589
- if (statusFilter) conds.push(eq110(siteAuditPages.status, statusFilter));
75590
- const where = and93(...conds);
75740
+ const conds = [eq111(siteAuditPages.projectId, project.id), eq111(siteAuditPages.runId, latest.snap.runId)];
75741
+ if (statusFilter) conds.push(eq111(siteAuditPages.status, statusFilter));
75742
+ const where = and94(...conds);
75591
75743
  const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
75592
75744
  const total = totalRow?.value ?? 0;
75593
75745
  const limit = parsePositiveInt(request.query.limit, 100, 500);
@@ -75611,7 +75763,7 @@ async function technicalAeoRoutes(app, opts) {
75611
75763
  auditedAt: siteAuditSnapshots.auditedAt,
75612
75764
  aggregateScore: siteAuditSnapshots.aggregateScore,
75613
75765
  pagesAudited: siteAuditSnapshots.pagesAudited
75614
- }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(and93(...surfaceableSiteAuditFilters(project.id))).orderBy(desc50(siteAuditSnapshots.createdAt)).limit(limit).all();
75766
+ }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...surfaceableSiteAuditFilters(project.id))).orderBy(desc50(siteAuditSnapshots.createdAt)).limit(limit).all();
75615
75767
  return { project: project.name, points: rows.reverse() };
75616
75768
  });
75617
75769
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -75711,10 +75863,10 @@ async function technicalAeoRoutes(app, opts) {
75711
75863
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
75712
75864
  snapshot.rootUrl
75713
75865
  )?.nodeKey ?? null;
75714
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and93(
75715
- eq110(siteCrawlGraphLayouts.projectId, project.id),
75716
- eq110(siteCrawlGraphLayouts.runId, snapshot.runId),
75717
- eq110(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
75866
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and94(
75867
+ eq111(siteCrawlGraphLayouts.projectId, project.id),
75868
+ eq111(siteCrawlGraphLayouts.runId, snapshot.runId),
75869
+ eq111(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
75718
75870
  )).limit(1).get();
75719
75871
  if (!persistedLayout) {
75720
75872
  return {
@@ -75767,9 +75919,9 @@ async function technicalAeoRoutes(app, opts) {
75767
75919
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
75768
75920
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
75769
75921
  const graphScope = [
75770
- eq110(siteCrawlGraphNodes.projectId, project.id),
75771
- eq110(siteCrawlGraphNodes.runId, snapshot.runId),
75772
- eq110(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
75922
+ eq111(siteCrawlGraphNodes.projectId, project.id),
75923
+ eq111(siteCrawlGraphNodes.runId, snapshot.runId),
75924
+ eq111(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
75773
75925
  ];
75774
75926
  const nodeRows = app.db.select({
75775
75927
  nodeKey: siteCrawlPages.nodeKey,
@@ -75788,18 +75940,18 @@ async function technicalAeoRoutes(app, opts) {
75788
75940
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
75789
75941
  x: siteCrawlGraphNodes.x,
75790
75942
  y: siteCrawlGraphNodes.y
75791
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and93(
75792
- eq110(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
75793
- eq110(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
75794
- eq110(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
75795
- eq110(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
75796
- )).where(and93(...graphScope, lt18(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc27(siteCrawlGraphNodes.sampleRank)).all();
75943
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and94(
75944
+ eq111(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
75945
+ eq111(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
75946
+ eq111(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
75947
+ eq111(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
75948
+ )).where(and94(...graphScope, lt19(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc27(siteCrawlGraphNodes.sampleRank)).all();
75797
75949
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
75798
75950
  ...row,
75799
75951
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
75800
75952
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
75801
75953
  }));
75802
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq110(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
75954
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq111(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
75803
75955
  const edges = app.db.select({
75804
75956
  edgeKey: siteCrawlGraphEdges.edgeKey,
75805
75957
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -75807,23 +75959,23 @@ async function technicalAeoRoutes(app, opts) {
75807
75959
  followable: siteCrawlGraphEdges.followable,
75808
75960
  occurrences: siteCrawlGraphEdges.occurrences,
75809
75961
  isTemplate: siteCrawlGraphEdges.isTemplate
75810
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and93(
75811
- eq110(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
75812
- eq110(graphSourceNode.runId, siteCrawlGraphEdges.runId),
75813
- eq110(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
75814
- eq110(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
75815
- lt18(graphSourceNode.sampleRank, maxNodes)
75816
- )).innerJoin(graphTargetNode, and93(
75817
- eq110(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
75818
- eq110(graphTargetNode.runId, siteCrawlGraphEdges.runId),
75819
- eq110(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
75820
- eq110(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
75821
- lt18(graphTargetNode.sampleRank, maxNodes)
75822
- )).where(and93(
75823
- eq110(siteCrawlGraphEdges.projectId, project.id),
75824
- eq110(siteCrawlGraphEdges.runId, snapshot.runId),
75825
- eq110(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
75826
- lt18(siteCrawlGraphEdges.sampleRank, maxEdges),
75962
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and94(
75963
+ eq111(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
75964
+ eq111(graphSourceNode.runId, siteCrawlGraphEdges.runId),
75965
+ eq111(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
75966
+ eq111(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
75967
+ lt19(graphSourceNode.sampleRank, maxNodes)
75968
+ )).innerJoin(graphTargetNode, and94(
75969
+ eq111(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
75970
+ eq111(graphTargetNode.runId, siteCrawlGraphEdges.runId),
75971
+ eq111(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
75972
+ eq111(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
75973
+ lt19(graphTargetNode.sampleRank, maxNodes)
75974
+ )).where(and94(
75975
+ eq111(siteCrawlGraphEdges.projectId, project.id),
75976
+ eq111(siteCrawlGraphEdges.runId, snapshot.runId),
75977
+ eq111(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
75978
+ lt19(siteCrawlGraphEdges.sampleRank, maxEdges),
75827
75979
  graphLinkKindFilter
75828
75980
  )).orderBy(asc27(siteCrawlGraphEdges.sampleRank)).all();
75829
75981
  const totalNodes = persistedLayout.totalNodes;
@@ -75917,11 +76069,11 @@ async function technicalAeoRoutes(app, opts) {
75917
76069
  let frontier = [focus.nodeKey];
75918
76070
  let queryTruncated = false;
75919
76071
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
75920
- const candidates = app.db.select().from(siteCrawlEdges).where(and93(
75921
- eq110(siteCrawlEdges.projectId, scope.projectId),
75922
- eq110(siteCrawlEdges.runId, scope.runId),
75923
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
75924
- eq110(siteCrawlEdges.internal, true),
76072
+ const candidates = app.db.select().from(siteCrawlEdges).where(and94(
76073
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76074
+ eq111(siteCrawlEdges.runId, scope.runId),
76075
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76076
+ eq111(siteCrawlEdges.internal, true),
75925
76077
  isNotNull10(siteCrawlEdges.targetNodeKey),
75926
76078
  or21(
75927
76079
  inArray50(siteCrawlEdges.sourceNodeKey, frontier),
@@ -75958,19 +76110,19 @@ async function technicalAeoRoutes(app, opts) {
75958
76110
  frontier = [...next].sort();
75959
76111
  }
75960
76112
  const nodeKeys = [...distances.keys()];
75961
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and93(
75962
- eq110(siteCrawlPages.projectId, scope.projectId),
75963
- eq110(siteCrawlPages.runId, scope.runId),
75964
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76113
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
76114
+ eq111(siteCrawlPages.projectId, scope.projectId),
76115
+ eq111(siteCrawlPages.runId, scope.runId),
76116
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
75965
76117
  inArray50(siteCrawlPages.nodeKey, nodeKeys)
75966
76118
  )).all();
75967
76119
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
75968
76120
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
75969
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and93(
75970
- eq110(siteCrawlEdges.projectId, scope.projectId),
75971
- eq110(siteCrawlEdges.runId, scope.runId),
75972
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
75973
- eq110(siteCrawlEdges.internal, true),
76121
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and94(
76122
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76123
+ eq111(siteCrawlEdges.runId, scope.runId),
76124
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76125
+ eq111(siteCrawlEdges.internal, true),
75974
76126
  isNotNull10(siteCrawlEdges.targetNodeKey),
75975
76127
  inArray50(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
75976
76128
  inArray50(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
@@ -76066,22 +76218,22 @@ async function technicalAeoRoutes(app, opts) {
76066
76218
  let found = fromPage.nodeKey === toPage.nodeKey;
76067
76219
  let truncated = false;
76068
76220
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
76069
- const candidates = app.db.select().from(siteCrawlEdges).where(and93(
76070
- eq110(siteCrawlEdges.projectId, scope.projectId),
76071
- eq110(siteCrawlEdges.runId, scope.runId),
76072
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
76073
- eq110(siteCrawlEdges.internal, true),
76074
- eq110(siteCrawlEdges.followable, true),
76075
- eq110(siteCrawlEdges.relation, "anchor"),
76221
+ const candidates = app.db.select().from(siteCrawlEdges).where(and94(
76222
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76223
+ eq111(siteCrawlEdges.runId, scope.runId),
76224
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76225
+ eq111(siteCrawlEdges.internal, true),
76226
+ eq111(siteCrawlEdges.followable, true),
76227
+ eq111(siteCrawlEdges.relation, "anchor"),
76076
76228
  isNotNull10(siteCrawlEdges.targetNodeKey),
76077
76229
  inArray50(siteCrawlEdges.sourceNodeKey, frontier)
76078
76230
  )).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
76079
76231
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
76080
76232
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
76081
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and93(
76082
- eq110(siteCrawlPages.projectId, scope.projectId),
76083
- eq110(siteCrawlPages.runId, scope.runId),
76084
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76233
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and94(
76234
+ eq111(siteCrawlPages.projectId, scope.projectId),
76235
+ eq111(siteCrawlPages.runId, scope.runId),
76236
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
76085
76237
  inArray50(siteCrawlPages.nodeKey, candidateTargetKeys)
76086
76238
  )).all().map((row) => row.nodeKey));
76087
76239
  const next = /* @__PURE__ */ new Set();
@@ -76123,10 +76275,10 @@ async function technicalAeoRoutes(app, opts) {
76123
76275
  pathEdges.unshift(step.edge);
76124
76276
  pathKeys.unshift(step.nodeKey);
76125
76277
  }
76126
- const pathRows = app.db.select().from(siteCrawlPages).where(and93(
76127
- eq110(siteCrawlPages.projectId, scope.projectId),
76128
- eq110(siteCrawlPages.runId, scope.runId),
76129
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76278
+ const pathRows = app.db.select().from(siteCrawlPages).where(and94(
76279
+ eq111(siteCrawlPages.projectId, scope.projectId),
76280
+ eq111(siteCrawlPages.runId, scope.runId),
76281
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
76130
76282
  inArray50(siteCrawlPages.nodeKey, pathKeys)
76131
76283
  )).all();
76132
76284
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
@@ -76155,22 +76307,22 @@ async function technicalAeoRoutes(app, opts) {
76155
76307
  throw validationError("change must be all, added, removed, or changed");
76156
76308
  }
76157
76309
  const completeSnapshotFilters = [
76158
- eq110(siteCrawlSnapshots.projectId, project.id),
76159
- eq110(runs.projectId, project.id),
76160
- eq110(runs.kind, RunKinds["site-audit"]),
76161
- eq110(runs.status, RunStatuses.completed),
76162
- eq110(siteCrawlSnapshots.complete, true),
76310
+ eq111(siteCrawlSnapshots.projectId, project.id),
76311
+ eq111(runs.projectId, project.id),
76312
+ eq111(runs.kind, RunKinds["site-audit"]),
76313
+ eq111(runs.status, RunStatuses.completed),
76314
+ eq111(siteCrawlSnapshots.complete, true),
76163
76315
  notProbeRun()
76164
76316
  ];
76165
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(...completeSnapshotFilters)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get();
76317
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(...completeSnapshotFilters)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get();
76166
76318
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
76167
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(
76319
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
76168
76320
  ...completeSnapshotFilters,
76169
76321
  or21(
76170
- lt18(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76171
- and93(
76172
- eq110(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76173
- lt18(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
76322
+ lt19(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76323
+ and94(
76324
+ eq111(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76325
+ lt19(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
76174
76326
  )
76175
76327
  )
76176
76328
  )).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
@@ -76414,17 +76566,17 @@ async function technicalAeoRoutes(app, opts) {
76414
76566
  const visibleKeys = keyRows.slice(0, limit);
76415
76567
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
76416
76568
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
76417
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and93(
76418
- eq110(siteCrawlPages.projectId, scope.projectId),
76419
- eq110(siteCrawlPages.runId, scope.runId),
76420
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76569
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
76570
+ eq111(siteCrawlPages.projectId, scope.projectId),
76571
+ eq111(siteCrawlPages.runId, scope.runId),
76572
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
76421
76573
  inArray50(siteCrawlPages.nodeKey, pageKeys)
76422
76574
  )).all();
76423
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and93(
76424
- eq110(siteCrawlEdges.projectId, scope.projectId),
76425
- eq110(siteCrawlEdges.runId, scope.runId),
76426
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
76427
- eq110(siteCrawlEdges.internal, true),
76575
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and94(
76576
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76577
+ eq111(siteCrawlEdges.runId, scope.runId),
76578
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76579
+ eq111(siteCrawlEdges.internal, true),
76428
76580
  inArray50(siteCrawlEdges.edgeKey, linkKeys)
76429
76581
  )).all();
76430
76582
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -76598,16 +76750,16 @@ async function technicalAeoRoutes(app, opts) {
76598
76750
  }
76599
76751
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
76600
76752
  const filters = [
76601
- eq110(siteCrawlPages.projectId, project.id),
76602
- eq110(siteCrawlPages.runId, snapshot.runId),
76603
- eq110(siteCrawlPages.attemptId, snapshot.attemptId)
76753
+ eq111(siteCrawlPages.projectId, project.id),
76754
+ eq111(siteCrawlPages.runId, snapshot.runId),
76755
+ eq111(siteCrawlPages.attemptId, snapshot.attemptId)
76604
76756
  ];
76605
- if (request.query.nodeKey) filters.push(eq110(siteCrawlPages.nodeKey, request.query.nodeKey));
76757
+ if (request.query.nodeKey) filters.push(eq111(siteCrawlPages.nodeKey, request.query.nodeKey));
76606
76758
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
76607
- if (inventoryEligible != null) filters.push(eq110(siteCrawlPages.inventoryEligible, inventoryEligible));
76608
- if (request.query.fetchState) filters.push(eq110(siteCrawlPages.fetchState, request.query.fetchState));
76609
- if (request.query.indexabilityState) filters.push(eq110(siteCrawlPages.indexabilityState, request.query.indexabilityState));
76610
- if (request.query.auditState) filters.push(eq110(siteCrawlPages.auditState, request.query.auditState));
76759
+ if (inventoryEligible != null) filters.push(eq111(siteCrawlPages.inventoryEligible, inventoryEligible));
76760
+ if (request.query.fetchState) filters.push(eq111(siteCrawlPages.fetchState, request.query.fetchState));
76761
+ if (request.query.indexabilityState) filters.push(eq111(siteCrawlPages.indexabilityState, request.query.indexabilityState));
76762
+ if (request.query.auditState) filters.push(eq111(siteCrawlPages.auditState, request.query.auditState));
76611
76763
  const urlVersions = parseUrlVersionFilter(request.query.urlVersions);
76612
76764
  if (urlVersions === SiteCrawlUrlVersionFilters.exclude) filters.push(isNull17(siteCrawlPages.urlVersionOf));
76613
76765
  if (urlVersions === SiteCrawlUrlVersionFilters.only) filters.push(isNotNull10(siteCrawlPages.urlVersionOf));
@@ -76633,14 +76785,14 @@ async function technicalAeoRoutes(app, opts) {
76633
76785
  const orderBy = sort === "score-desc" ? [desc50(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)] : sort === "path" ? [asc27(siteCrawlPages.path), asc27(siteCrawlPages.nodeKey)] : [asc27(siteCrawlPages.url), asc27(siteCrawlPages.nodeKey)];
76634
76786
  let healthStateFilter = null;
76635
76787
  if (healthState) {
76636
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and93(
76637
- eq110(siteCrawlPages.projectId, project.id),
76638
- eq110(siteCrawlPages.runId, snapshot.runId),
76639
- eq110(siteCrawlPages.attemptId, snapshot.attemptId),
76788
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and94(
76789
+ eq111(siteCrawlPages.projectId, project.id),
76790
+ eq111(siteCrawlPages.runId, snapshot.runId),
76791
+ eq111(siteCrawlPages.attemptId, snapshot.attemptId),
76640
76792
  isNull17(siteCrawlPages.healthState)
76641
76793
  )).limit(1).get();
76642
76794
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
76643
- if (healthStateFilter === "applied") filters.push(eq110(siteCrawlPages.healthState, healthState));
76795
+ if (healthStateFilter === "applied") filters.push(eq111(siteCrawlPages.healthState, healthState));
76644
76796
  }
76645
76797
  if (healthStateFilter === "unavailable-legacy-scan") {
76646
76798
  return {
@@ -76657,7 +76809,7 @@ async function technicalAeoRoutes(app, opts) {
76657
76809
  pages: []
76658
76810
  };
76659
76811
  }
76660
- const where = and93(...filters);
76812
+ const where = and94(...filters);
76661
76813
  const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
76662
76814
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
76663
76815
  const nextOffset = offset + rows.length;
@@ -76692,10 +76844,10 @@ async function technicalAeoRoutes(app, opts) {
76692
76844
  url: siteCrawlPages.url,
76693
76845
  inventoryEligible: siteCrawlPages.inventoryEligible,
76694
76846
  fetchState: siteCrawlPages.fetchState
76695
- }).from(siteCrawlPages).where(and93(
76696
- eq110(siteCrawlPages.projectId, project.id),
76697
- eq110(siteCrawlPages.runId, snapshot.runId),
76698
- eq110(siteCrawlPages.attemptId, snapshot.attemptId)
76847
+ }).from(siteCrawlPages).where(and94(
76848
+ eq111(siteCrawlPages.projectId, project.id),
76849
+ eq111(siteCrawlPages.runId, snapshot.runId),
76850
+ eq111(siteCrawlPages.attemptId, snapshot.attemptId)
76699
76851
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
76700
76852
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
76701
76853
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -76777,17 +76929,17 @@ async function technicalAeoRoutes(app, opts) {
76777
76929
  };
76778
76930
  }
76779
76931
  const filters = [
76780
- eq110(siteCrawlEdges.projectId, project.id),
76781
- eq110(siteCrawlEdges.runId, snapshot.runId),
76782
- eq110(siteCrawlEdges.attemptId, snapshot.attemptId),
76783
- eq110(siteCrawlEdges.internal, true),
76932
+ eq111(siteCrawlEdges.projectId, project.id),
76933
+ eq111(siteCrawlEdges.runId, snapshot.runId),
76934
+ eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
76935
+ eq111(siteCrawlEdges.internal, true),
76784
76936
  linkKindFilter(linkKind)
76785
76937
  ];
76786
- if (request.query.sourceUrl) filters.push(eq110(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
76787
- if (request.query.targetUrl) filters.push(eq110(siteCrawlEdges.targetUrl, request.query.targetUrl));
76938
+ if (request.query.sourceUrl) filters.push(eq111(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
76939
+ if (request.query.targetUrl) filters.push(eq111(siteCrawlEdges.targetUrl, request.query.targetUrl));
76788
76940
  const followable = parseBoolean2(request.query.followable);
76789
- if (followable != null) filters.push(eq110(siteCrawlEdges.followable, followable));
76790
- const where = and93(...filters);
76941
+ if (followable != null) filters.push(eq111(siteCrawlEdges.followable, followable));
76942
+ const where = and94(...filters);
76791
76943
  const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
76792
76944
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
76793
76945
  const offset = decodeCursor(request.query.cursor);
@@ -76848,17 +77000,17 @@ async function technicalAeoRoutes(app, opts) {
76848
77000
  };
76849
77001
  }
76850
77002
  const scope = [
76851
- eq110(siteCrawlEdges.projectId, project.id),
76852
- eq110(siteCrawlEdges.runId, snapshot.runId),
76853
- eq110(siteCrawlEdges.attemptId, snapshot.attemptId),
76854
- eq110(siteCrawlEdges.internal, true),
77003
+ eq111(siteCrawlEdges.projectId, project.id),
77004
+ eq111(siteCrawlEdges.runId, snapshot.runId),
77005
+ eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
77006
+ eq111(siteCrawlEdges.internal, true),
76855
77007
  linkKindFilter(linkKind)
76856
77008
  ];
76857
- const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq110(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq110(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq110(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq110(siteCrawlEdges.targetUrl, request.query.url);
76858
- const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq110(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq110(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq110(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq110(siteCrawlEdges.sourceUrl, request.query.url);
77009
+ const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq111(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq111(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq111(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq111(siteCrawlEdges.targetUrl, request.query.url);
77010
+ const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq111(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq111(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq111(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq111(siteCrawlEdges.sourceUrl, request.query.url);
76859
77011
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
76860
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and93(...scope, inboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
76861
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and93(...scope, outboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
77012
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, inboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
77013
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, outboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
76862
77014
  return {
76863
77015
  project: project.name,
76864
77016
  hasCrawlData: true,
@@ -76892,11 +77044,11 @@ async function technicalAeoRoutes(app, opts) {
76892
77044
  if (!snapshot.attemptId) {
76893
77045
  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 };
76894
77046
  }
76895
- const where = and93(
76896
- eq110(siteCrawlFindings.projectId, project.id),
76897
- eq110(siteCrawlFindings.runId, snapshot.runId),
76898
- eq110(siteCrawlFindings.attemptId, snapshot.attemptId),
76899
- eq110(siteCrawlFindings.findingType, "dead-link"),
77047
+ const where = and94(
77048
+ eq111(siteCrawlFindings.projectId, project.id),
77049
+ eq111(siteCrawlFindings.runId, snapshot.runId),
77050
+ eq111(siteCrawlFindings.attemptId, snapshot.attemptId),
77051
+ eq111(siteCrawlFindings.findingType, "dead-link"),
76900
77052
  // Belt and braces on the client-facing surface: a row without a status
76901
77053
  // code is not evidence of a broken link, and this response is where such
76902
77054
  // a row would be read as one. Migration 140 removed every stored row that
@@ -76946,16 +77098,16 @@ async function technicalAeoRoutes(app, opts) {
76946
77098
  createdAt: runs.createdAt,
76947
77099
  startedAt: runs.startedAt,
76948
77100
  finishedAt: runs.finishedAt
76949
- }).from(runs).where(and93(
76950
- eq110(runs.projectId, project.id),
76951
- eq110(runs.kind, RunKinds["site-audit"]),
77101
+ }).from(runs).where(and94(
77102
+ eq111(runs.projectId, project.id),
77103
+ eq111(runs.kind, RunKinds["site-audit"]),
76952
77104
  notProbeRun()
76953
77105
  )).orderBy(desc50(runs.createdAt), desc50(runs.id)).limit(limit).all();
76954
77106
  const runIds = rows.map((row) => row.runId);
76955
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(
76956
- eq110(siteCrawlSnapshots.projectId, project.id),
76957
- eq110(runs.projectId, project.id),
76958
- eq110(runs.kind, RunKinds["site-audit"]),
77107
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
77108
+ eq111(siteCrawlSnapshots.projectId, project.id),
77109
+ eq111(runs.projectId, project.id),
77110
+ eq111(runs.kind, RunKinds["site-audit"]),
76959
77111
  inArray50(runs.status, SURFACEABLE_STATUSES),
76960
77112
  notProbeRun(),
76961
77113
  inArray50(siteCrawlSnapshots.runId, runIds)
@@ -76972,21 +77124,21 @@ async function technicalAeoRoutes(app, opts) {
76972
77124
  });
76973
77125
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
76974
77126
  const project = resolveProject(app.db, request.params.name);
76975
- const run = app.db.select().from(runs).where(and93(
76976
- eq110(runs.id, request.params.runId),
76977
- eq110(runs.projectId, project.id),
76978
- eq110(runs.kind, RunKinds["site-audit"]),
77127
+ const run = app.db.select().from(runs).where(and94(
77128
+ eq111(runs.id, request.params.runId),
77129
+ eq111(runs.projectId, project.id),
77130
+ eq111(runs.kind, RunKinds["site-audit"]),
76979
77131
  notProbeRun()
76980
77132
  )).get();
76981
77133
  if (!run) throw notFound("Site audit run", request.params.runId);
76982
- const attempt = app.db.select().from(siteCrawlAttempts).where(and93(
76983
- eq110(siteCrawlAttempts.projectId, project.id),
76984
- eq110(siteCrawlAttempts.runId, run.id)
77134
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and94(
77135
+ eq111(siteCrawlAttempts.projectId, project.id),
77136
+ eq111(siteCrawlAttempts.runId, run.id)
76985
77137
  )).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
76986
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and93(
76987
- eq110(siteCrawlGraphLayouts.projectId, project.id),
76988
- eq110(siteCrawlGraphLayouts.runId, run.id),
76989
- eq110(siteCrawlGraphLayouts.attemptId, attempt.id)
77138
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and94(
77139
+ eq111(siteCrawlGraphLayouts.projectId, project.id),
77140
+ eq111(siteCrawlGraphLayouts.runId, run.id),
77141
+ eq111(siteCrawlGraphLayouts.attemptId, attempt.id)
76990
77142
  )).get() : void 0;
76991
77143
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
76992
77144
  return {
@@ -77017,18 +77169,18 @@ async function technicalAeoRoutes(app, opts) {
77017
77169
  };
77018
77170
  });
77019
77171
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
77020
- const project = tx.select().from(projects).where(eq110(projects.name, request.params.name)).get();
77172
+ const project = tx.select().from(projects).where(eq111(projects.name, request.params.name)).get();
77021
77173
  if (!project) throw notFound("Project", request.params.name);
77022
- const run = tx.select().from(runs).where(and93(
77023
- eq110(runs.id, request.params.runId),
77024
- eq110(runs.projectId, project.id),
77025
- eq110(runs.kind, RunKinds["site-audit"]),
77174
+ const run = tx.select().from(runs).where(and94(
77175
+ eq111(runs.id, request.params.runId),
77176
+ eq111(runs.projectId, project.id),
77177
+ eq111(runs.kind, RunKinds["site-audit"]),
77026
77178
  notProbeRun()
77027
77179
  )).get();
77028
77180
  if (!run) throw notFound("Site audit run", request.params.runId);
77029
- const attempt = tx.select().from(siteCrawlAttempts).where(and93(
77030
- eq110(siteCrawlAttempts.projectId, project.id),
77031
- eq110(siteCrawlAttempts.runId, run.id)
77181
+ const attempt = tx.select().from(siteCrawlAttempts).where(and94(
77182
+ eq111(siteCrawlAttempts.projectId, project.id),
77183
+ eq111(siteCrawlAttempts.runId, run.id)
77032
77184
  )).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
77033
77185
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
77034
77186
  const base = {
@@ -77042,11 +77194,11 @@ async function technicalAeoRoutes(app, opts) {
77042
77194
  if (!attempt) {
77043
77195
  return { ...base, pagesAudited: 0, examples: [] };
77044
77196
  }
77045
- const auditedWhere = and93(
77046
- eq110(siteCrawlPages.projectId, project.id),
77047
- eq110(siteCrawlPages.runId, run.id),
77048
- eq110(siteCrawlPages.attemptId, attempt.id),
77049
- eq110(siteCrawlPages.auditState, "success")
77197
+ const auditedWhere = and94(
77198
+ eq111(siteCrawlPages.projectId, project.id),
77199
+ eq111(siteCrawlPages.runId, run.id),
77200
+ eq111(siteCrawlPages.attemptId, attempt.id),
77201
+ eq111(siteCrawlPages.auditState, "success")
77050
77202
  );
77051
77203
  const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
77052
77204
  if (state !== "collecting") {
@@ -77057,10 +77209,10 @@ async function technicalAeoRoutes(app, opts) {
77057
77209
  url: siteCrawlPages.url,
77058
77210
  auditScore: siteCrawlPages.auditScore,
77059
77211
  auditFields: siteCrawlPages.auditFields
77060
- }).from(siteCrawlPages).where(and93(
77212
+ }).from(siteCrawlPages).where(and94(
77061
77213
  auditedWhere,
77062
77214
  isNotNull10(siteCrawlPages.auditScore),
77063
- lt18(siteCrawlPages.auditScore, 70)
77215
+ lt19(siteCrawlPages.auditScore, 70)
77064
77216
  )).orderBy(asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
77065
77217
  const examples = candidates.flatMap((candidate) => {
77066
77218
  if (candidate.auditScore == null) return [];
@@ -77093,12 +77245,12 @@ async function technicalAeoRoutes(app, opts) {
77093
77245
  status: runs.status,
77094
77246
  identityKey: siteCrawlRunRequests.identityKey,
77095
77247
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
77096
- }).from(runs).leftJoin(siteCrawlRunRequests, and93(
77097
- eq110(siteCrawlRunRequests.projectId, runs.projectId),
77098
- eq110(siteCrawlRunRequests.runId, runs.id)
77099
- )).where(and93(
77100
- eq110(runs.projectId, project.id),
77101
- eq110(runs.kind, RunKinds["site-audit"]),
77248
+ }).from(runs).leftJoin(siteCrawlRunRequests, and94(
77249
+ eq111(siteCrawlRunRequests.projectId, runs.projectId),
77250
+ eq111(siteCrawlRunRequests.runId, runs.id)
77251
+ )).where(and94(
77252
+ eq111(runs.projectId, project.id),
77253
+ eq111(runs.kind, RunKinds["site-audit"]),
77102
77254
  inArray50(runs.status, [RunStatuses.queued, RunStatuses.running])
77103
77255
  )).get();
77104
77256
  if (existing) {
@@ -77153,7 +77305,7 @@ async function technicalAeoRoutes(app, opts) {
77153
77305
  }
77154
77306
 
77155
77307
  // ../api-routes/src/trends.ts
77156
- import { and as and94, asc as asc28, eq as eq111 } from "drizzle-orm";
77308
+ import { and as and95, asc as asc28, eq as eq112 } from "drizzle-orm";
77157
77309
  function parseSurface(value) {
77158
77310
  const parsed = trendSurfaceSchema.safeParse(value);
77159
77311
  if (!parsed.success) {
@@ -77209,8 +77361,8 @@ async function trendsRoutes(app) {
77209
77361
  function readSurface(app, projectId, surface) {
77210
77362
  switch (surface) {
77211
77363
  case TrendSurfaces["index-coverage"]: {
77212
- const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(eq111(gscCoverageSnapshots.projectId, projectId)).orderBy(asc28(gscCoverageSnapshots.date)).all();
77213
- const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(eq111(bingCoverageSnapshots.projectId, projectId)).orderBy(asc28(bingCoverageSnapshots.date)).all();
77364
+ const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(eq112(gscCoverageSnapshots.projectId, projectId)).orderBy(asc28(gscCoverageSnapshots.date)).all();
77365
+ const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(eq112(bingCoverageSnapshots.projectId, projectId)).orderBy(asc28(bingCoverageSnapshots.date)).all();
77214
77366
  const coverage = (key, label, rows) => ({
77215
77367
  key,
77216
77368
  label,
@@ -77222,7 +77374,7 @@ function readSurface(app, projectId, surface) {
77222
77374
  return [coverage("google", "Google", google), coverage("bing", "Bing", bing)];
77223
77375
  }
77224
77376
  case TrendSurfaces["site-health"]: {
77225
- const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...surfaceableSiteAuditFilters(projectId))).orderBy(asc28(siteAuditSnapshots.auditedAt)).all();
77377
+ const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs, eq112(siteAuditSnapshots.runId, runs.id)).where(and95(...surfaceableSiteAuditFilters(projectId))).orderBy(asc28(siteAuditSnapshots.auditedAt)).all();
77226
77378
  return [{
77227
77379
  key: "site-health",
77228
77380
  label: "Site Health",
@@ -77234,7 +77386,7 @@ function readSurface(app, projectId, surface) {
77234
77386
  }
77235
77387
  case TrendSurfaces.backlinks: {
77236
77388
  const totals = computeFilteredReleaseTotals(app.db, projectId, BacklinkSources.commoncrawl);
77237
- const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(and94(eq111(backlinkSummaries.projectId, projectId), eq111(backlinkSummaries.source, BacklinkSources.commoncrawl))).all().sort((a, b) => compareReleaseIds(a.release, b.release)).map((row) => {
77389
+ const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(and95(eq112(backlinkSummaries.projectId, projectId), eq112(backlinkSummaries.source, BacklinkSources.commoncrawl))).all().sort((a, b) => compareReleaseIds(a.release, b.release)).map((row) => {
77238
77390
  const filtered = totals.get(row.release);
77239
77391
  const known = (filtered?.domainRows ?? 0) > 0 || row.totalLinkingDomains === 0;
77240
77392
  const windowEnd = releaseWindowEndMonth(row.release);
@@ -77264,7 +77416,7 @@ function readSurface(app, projectId, surface) {
77264
77416
  // ../api-routes/src/research.ts
77265
77417
  import crypto60 from "crypto";
77266
77418
  import { z as z7 } from "zod";
77267
- import { and as and95, count as count9, desc as desc51, eq as eq112, gte as gte33, lt as lt19, or as or22, sql as sql46 } from "drizzle-orm";
77419
+ import { and as and96, count as count9, desc as desc51, eq as eq113, gte as gte33, lt as lt20, or as or22, sql as sql46 } from "drizzle-orm";
77268
77420
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
77269
77421
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
77270
77422
  async function researchRoutes(app, opts) {
@@ -77283,7 +77435,7 @@ async function researchRoutes(app, opts) {
77283
77435
  const input = parsed.data;
77284
77436
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
77285
77437
  const requestHash2 = directResearchRequestHash(input);
77286
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and95(eq112(researchRuns.projectId, project.id), eq112(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
77438
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
77287
77439
  if (existingReceipt) {
77288
77440
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
77289
77441
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -77318,7 +77470,7 @@ async function researchRoutes(app, opts) {
77318
77470
  const initiatedBy = researchPrincipal(request);
77319
77471
  const decision = app.db.transaction((tx) => {
77320
77472
  if (input.idempotencyKey) {
77321
- const existing = tx.select().from(researchRuns).where(and95(eq112(researchRuns.projectId, project.id), eq112(researchRuns.idempotencyKey, input.idempotencyKey))).get();
77473
+ const existing = tx.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get();
77322
77474
  if (existing) {
77323
77475
  assertSameDirectResearchRequest(existing, input, requestHash2);
77324
77476
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -77326,10 +77478,10 @@ async function researchRoutes(app, opts) {
77326
77478
  }
77327
77479
  if (initiatedBy?.limited) {
77328
77480
  const { start, end, date } = utcDayBounds(now);
77329
- const used = tx.select({ value: count9() }).from(researchRuns).where(and95(
77330
- eq112(researchRuns.projectId, project.id),
77481
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
77482
+ eq113(researchRuns.projectId, project.id),
77331
77483
  gte33(researchRuns.createdAt, start),
77332
- lt19(researchRuns.createdAt, end),
77484
+ lt20(researchRuns.createdAt, end),
77333
77485
  sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
77334
77486
  )).get()?.value ?? 0;
77335
77487
  if (used >= viewerDailyRunLimit) {
@@ -77358,15 +77510,15 @@ async function researchRoutes(app, opts) {
77358
77510
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
77359
77511
  const requestHash2 = researchBatchRequestHash(input);
77360
77512
  const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
77361
- const firstReceipt = app.db.select().from(researchRuns).where(and95(
77362
- eq112(researchRuns.projectId, project.id),
77363
- eq112(researchRuns.idempotencyKey, receiptKeys[0])
77513
+ const firstReceipt = app.db.select().from(researchRuns).where(and96(
77514
+ eq113(researchRuns.projectId, project.id),
77515
+ eq113(researchRuns.idempotencyKey, receiptKeys[0])
77364
77516
  )).get();
77365
77517
  if (firstReceipt) {
77366
77518
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
77367
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and95(
77368
- eq112(researchRuns.projectId, project.id),
77369
- eq112(researchRuns.idempotencyKey, key)
77519
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and96(
77520
+ eq113(researchRuns.projectId, project.id),
77521
+ eq113(researchRuns.idempotencyKey, key)
77370
77522
  )).get());
77371
77523
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
77372
77524
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -77382,10 +77534,10 @@ async function researchRoutes(app, opts) {
77382
77534
  const decision = app.db.transaction((tx) => {
77383
77535
  if (initiatedBy?.limited) {
77384
77536
  const { start, end, date } = utcDayBounds(now);
77385
- const used = tx.select({ value: count9() }).from(researchRuns).where(and95(
77386
- eq112(researchRuns.projectId, project.id),
77537
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
77538
+ eq113(researchRuns.projectId, project.id),
77387
77539
  gte33(researchRuns.createdAt, start),
77388
- lt19(researchRuns.createdAt, end),
77540
+ lt20(researchRuns.createdAt, end),
77389
77541
  sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
77390
77542
  )).get()?.value ?? 0;
77391
77543
  if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
@@ -77437,9 +77589,9 @@ async function researchRoutes(app, opts) {
77437
77589
  const requested = Number.parseInt(request.query.limit ?? "", 10);
77438
77590
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
77439
77591
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
77440
- const rows = app.db.select().from(researchRuns).where(and95(
77441
- eq112(researchRuns.projectId, project.id),
77442
- cursor ? or22(lt19(researchRuns.createdAt, cursor.createdAt), and95(eq112(researchRuns.createdAt, cursor.createdAt), lt19(researchRuns.id, cursor.id))) : void 0
77592
+ const rows = app.db.select().from(researchRuns).where(and96(
77593
+ eq113(researchRuns.projectId, project.id),
77594
+ cursor ? or22(lt20(researchRuns.createdAt, cursor.createdAt), and96(eq113(researchRuns.createdAt, cursor.createdAt), lt20(researchRuns.id, cursor.id))) : void 0
77443
77595
  )).orderBy(desc51(researchRuns.createdAt), desc51(researchRuns.id)).limit(limit + 1).all();
77444
77596
  const runs2 = rows.slice(0, limit).map(serializeRun);
77445
77597
  const last = runs2.at(-1);
@@ -77589,9 +77741,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
77589
77741
  return stored;
77590
77742
  }
77591
77743
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
77592
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and95(
77593
- eq112(measurementQueryTemplates.projectId, projectId),
77594
- eq112(measurementQueryTemplates.id, selection.templateId)
77744
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and96(
77745
+ eq113(measurementQueryTemplates.projectId, projectId),
77746
+ eq113(measurementQueryTemplates.id, selection.templateId)
77595
77747
  )).get();
77596
77748
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
77597
77749
  const templateVersion = savedTemplate.updatedAt;
@@ -77600,7 +77752,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
77600
77752
  }
77601
77753
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
77602
77754
  if (bindingLocation) {
77603
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq112(projects.id, projectId)).get();
77755
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq113(projects.id, projectId)).get();
77604
77756
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
77605
77757
  }
77606
77758
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -77613,9 +77765,9 @@ function classifyResearchQueries(project, plan, queries2) {
77613
77765
  return queries2.map((query2) => classifier?.classify(query2) ?? null);
77614
77766
  }
77615
77767
  function getDetail(app, projectId, id) {
77616
- const row = app.db.select().from(researchRuns).where(and95(eq112(researchRuns.id, id), eq112(researchRuns.projectId, projectId))).get();
77768
+ const row = app.db.select().from(researchRuns).where(and96(eq113(researchRuns.id, id), eq113(researchRuns.projectId, projectId))).get();
77617
77769
  if (!row) throw notFound("Research run", id);
77618
- const queries2 = app.db.select().from(researchRunQueries).where(eq112(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
77770
+ const queries2 = app.db.select().from(researchRunQueries).where(eq113(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
77619
77771
  return { ...serializeRun(row), queries: queries2 };
77620
77772
  }
77621
77773
  function serializeRun(row) {
@@ -77670,7 +77822,7 @@ function utcDayBounds(now) {
77670
77822
 
77671
77823
  // ../api-routes/src/simple-measurement-definitions.ts
77672
77824
  import crypto61 from "crypto";
77673
- import { and as and96, eq as eq113 } from "drizzle-orm";
77825
+ import { and as and97, eq as eq114 } from "drizzle-orm";
77674
77826
  function captureSimpleMeasurementDefinition(db, input) {
77675
77827
  return db.transaction((tx) => {
77676
77828
  const run = tx.select({
@@ -77678,7 +77830,7 @@ function captureSimpleMeasurementDefinition(db, input) {
77678
77830
  trigger: runs.trigger,
77679
77831
  status: runs.status,
77680
77832
  measurementPlanVersionId: runs.measurementPlanVersionId
77681
- }).from(runs).where(and96(eq113(runs.projectId, input.projectId), eq113(runs.id, input.runId))).get();
77833
+ }).from(runs).where(and97(eq114(runs.projectId, input.projectId), eq114(runs.id, input.runId))).get();
77682
77834
  if (!run) throw notFound("Run", input.runId);
77683
77835
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
77684
77836
  return null;
@@ -77699,7 +77851,7 @@ function captureSimpleMeasurementDefinition(db, input) {
77699
77851
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
77700
77852
  }
77701
77853
  }
77702
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and96(eq113(simpleMeasurementDefinitions.projectId, input.projectId), eq113(simpleMeasurementDefinitions.runId, input.runId))).get();
77854
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and97(eq114(simpleMeasurementDefinitions.projectId, input.projectId), eq114(simpleMeasurementDefinitions.runId, input.runId))).get();
77703
77855
  if (existing) {
77704
77856
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
77705
77857
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
@@ -77725,15 +77877,15 @@ function captureSimpleMeasurementDefinition(db, input) {
77725
77877
  if (run.status !== RunStatuses.running) {
77726
77878
  throw validationError("Only a running run can capture a new measurement definition.");
77727
77879
  }
77728
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq113(querySnapshots.runId, input.runId)).limit(1).get()) {
77880
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq114(querySnapshots.runId, input.runId)).limit(1).get()) {
77729
77881
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
77730
77882
  }
77731
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq113(queries.projectId, input.projectId)).all().map((query2) => query2.id));
77883
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq114(queries.projectId, input.projectId)).all().map((query2) => query2.id));
77732
77884
  if (definition2.queries.some((query2) => !projectQueryIds.has(query2.queryId))) {
77733
77885
  throw validationError("Every captured query must belong to the run project.");
77734
77886
  }
77735
77887
  if (definition2.competitors !== void 0) {
77736
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq113(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
77888
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq114(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
77737
77889
  const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
77738
77890
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
77739
77891
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");