@canonry/canonry 7.63.1 → 7.64.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/assets/agent-workspace/skills/aero/references/ai-traffic-tracking.md +13 -9
  2. package/assets/agent-workspace/skills/canonry/references/ai-traffic-tracking.md +13 -9
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +24 -3
  4. package/assets/assets/{AgentSetupPage-D6lCJ1G1.js → AgentSetupPage-C9VnIbMS.js} +1 -1
  5. package/assets/assets/{AuditHistoryPanel-DlgI3o-U.js → AuditHistoryPanel-DLa6Ca5t.js} +1 -1
  6. package/assets/assets/{BacklinksPage-ByiMqbSI.js → BacklinksPage-Dxnz-sUy.js} +1 -1
  7. package/assets/assets/{HistoryPage-DclVeuLO.js → HistoryPage-PU7UNO3T.js} +1 -1
  8. package/assets/assets/{MeasurementPropertyPage-DDHybtHa.js → MeasurementPropertyPage-Bo4xKbWQ.js} +1 -1
  9. package/assets/assets/{ProjectPage-vBu6oWFi.js → ProjectPage-DuW9zWT9.js} +1 -1
  10. package/assets/assets/{RunsPage-CYBJ6pXX.js → RunsPage-Cr0QSE4Q.js} +1 -1
  11. package/assets/assets/{SettingsPage-w0GmXir0.js → SettingsPage-K6tGAVHo.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-DDTjGZ05.js → SiteHealthSection-DxhIqNDZ.js} +2 -2
  13. package/assets/assets/ToneBadge-Cvv6Tkk7.js +60 -0
  14. package/assets/assets/{TrafficPage-hUWYv-HT.js → TrafficPage-lNcR4TvT.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-CGHgPxs8.js → TrafficSourceDetailPage-D47VIFDK.js} +1 -1
  16. package/assets/assets/{account-context-qX-Es0RK.js → account-context-BL8j43iU.js} +1 -1
  17. package/assets/assets/{card-CzROANT5.js → card-BKud0olB.js} +1 -1
  18. package/assets/assets/{extract-error-message-BY1STLiX.js → extract-error-message-CvEgflI1.js} +1 -1
  19. package/assets/assets/{format-helpers-BNQR1HNK.js → format-helpers-Ce3RoH9T.js} +1 -1
  20. package/assets/assets/{index-LmB57HOs.js → index-C2t4yW4t.js} +4 -4
  21. package/assets/assets/{query-client-Gz37p16p.js → query-client-Bf5Rr6po.js} +1 -1
  22. package/assets/assets/{react-query-C6DtBAYQ.js → react-query-CsFtWT8r.js} +1 -1
  23. package/assets/assets/{react-sigma_core.esm.min-CgxuyPPF.js → react-sigma_core.esm.min-D-dyJflK.js} +1 -1
  24. package/assets/assets/{service-DyHZWnMe.js → service-C0U5Ude4.js} +1 -1
  25. package/assets/index.html +7 -7
  26. package/dist/{ads-FOUJNHP4.js → ads-EFXCND5Z.js} +3 -3
  27. package/dist/{agent-UVDVH7LK.js → agent-2MAONYLR.js} +7 -7
  28. package/dist/{agent-ask-UEYN2SQX.js → agent-ask-5555LQQL.js} +3 -3
  29. package/dist/{agent-conversations-F54376Q2.js → agent-conversations-LYWOS4X3.js} +3 -3
  30. package/dist/{agent-memory-SOHBTJ4U.js → agent-memory-HOYIIO66.js} +3 -3
  31. package/dist/{agent-providers-MMEK2H6V.js → agent-providers-EYXB2DMX.js} +3 -3
  32. package/dist/{agent-transcript-5IJ3VAEV.js → agent-transcript-BWDOJXPV.js} +3 -3
  33. package/dist/{analytics-LDWO5IW6.js → analytics-46VKY4SP.js} +3 -3
  34. package/dist/{apply-PSGFDJIC.js → apply-GII5J3QB.js} +3 -3
  35. package/dist/{backfill-2BVFKQ6J.js → backfill-WSIEPRES.js} +9 -9
  36. package/dist/{backlinks-X6DXIL2O.js → backlinks-GRHTLP5Z.js} +3 -3
  37. package/dist/{bing-3HMFUEOO.js → bing-5QDDHK57.js} +3 -3
  38. package/dist/{bootstrap-ZUSSLXCI.js → bootstrap-GWDFCGEQ.js} +10 -10
  39. package/dist/{cdp-FH627HJ6.js → cdp-YMIJN52Z.js} +7 -7
  40. package/dist/{chunk-SN5A767O.js → chunk-2BJDLTDI.js} +1 -1
  41. package/dist/{chunk-TKLE2Y6H.js → chunk-4PRXS5JH.js} +103 -4
  42. package/dist/{chunk-2LAL5W6J.js → chunk-6AYOWQJ3.js} +3 -3
  43. package/dist/{chunk-6ABOEN5D.js → chunk-6ZA23GRE.js} +8 -8
  44. package/dist/{chunk-PH7RTAWW.js → chunk-7LVZATRE.js} +1 -1
  45. package/dist/{chunk-V3QMOGZE.js → chunk-7STLCYWL.js} +5 -5
  46. package/dist/{chunk-AGSQWIE4.js → chunk-7U2SFOJN.js} +20 -20
  47. package/dist/{chunk-L2QPAUTQ.js → chunk-7V6DJCZO.js} +1 -1
  48. package/dist/{chunk-LBD7YXVD.js → chunk-AUIHVRJ3.js} +2 -2
  49. package/dist/{chunk-ZJTU73GW.js → chunk-BAQOH3LE.js} +3 -3
  50. package/dist/{chunk-QNFX7ZCT.js → chunk-BUED4RWE.js} +1 -1
  51. package/dist/{chunk-NOQCIXM6.js → chunk-CLMJSIZU.js} +1 -1
  52. package/dist/{chunk-UBP3AAKA.js → chunk-CNPYR2CQ.js} +1 -1
  53. package/dist/{chunk-SQYYAYH6.js → chunk-DKVY6HFW.js} +3 -3
  54. package/dist/{chunk-LEG7F3DN.js → chunk-DYDLK3YY.js} +2 -2
  55. package/dist/{chunk-CWMJQ2JV.js → chunk-I62VYS4B.js} +4 -4
  56. package/dist/{chunk-GRWZC3GQ.js → chunk-ITYUIHKO.js} +1 -1
  57. package/dist/{chunk-5VAC4KQ2.js → chunk-JP4Q5FEW.js} +3 -3
  58. package/dist/{chunk-32KNDPD3.js → chunk-K5S5HXAK.js} +348 -336
  59. package/dist/{chunk-UYPBR3SF.js → chunk-L6IQN2Y5.js} +2 -2
  60. package/dist/{chunk-ERJ54U2I.js → chunk-M6QGMJAQ.js} +1 -1
  61. package/dist/{chunk-OIHJJHJR.js → chunk-MXQB7AAP.js} +1 -1
  62. package/dist/{chunk-5GMYD7UZ.js → chunk-NFZXB5ZV.js} +5 -5
  63. package/dist/{chunk-22ABGRB7.js → chunk-TX4HBLFP.js} +206 -115
  64. package/dist/{chunk-SG2OPR7J.js → chunk-UQABYWAX.js} +1 -1
  65. package/dist/{chunk-67262NJL.js → chunk-V23ALG2D.js} +2 -2
  66. package/dist/{chunk-H22RCHYH.js → chunk-VLOWXBJS.js} +3 -3
  67. package/dist/{chunk-IDQZTPRG.js → chunk-WFOIIHAK.js} +2 -2
  68. package/dist/{chunk-IRRONWF7.js → chunk-WLCYVJGN.js} +4 -4
  69. package/dist/{chunk-EHTSCLEL.js → chunk-XR3R5PDK.js} +1 -1
  70. package/dist/{chunk-A57HYHJ3.js → chunk-Y3VMSYEG.js} +1 -1
  71. package/dist/{chunk-5YC6IU44.js → chunk-ZGLLSPBB.js} +2 -2
  72. package/dist/{chunk-JM6OFCLX.js → chunk-ZUH7GDQ4.js} +4 -4
  73. package/dist/{chunk-HNZQVIPS.js → chunk-ZYE7D2OC.js} +23 -1
  74. package/dist/{citations-BKWLP5S3.js → citations-AMOXXK5R.js} +3 -3
  75. package/dist/{cli-catalog-VRJS66GR.js → cli-catalog-K5EQZBOO.js} +3 -3
  76. package/dist/{cli-commands-ONFE63OB.js → cli-commands-CCGP2A2P.js} +7 -7
  77. package/dist/cli.js +13 -13
  78. package/dist/{competitor-VELMM2TJ.js → competitor-YQBDZE6A.js} +3 -3
  79. package/dist/{content-I525MITL.js → content-DFZ56U3J.js} +3 -3
  80. package/dist/{dashboard-BC4NKAO2.js → dashboard-LIPZS22H.js} +6 -6
  81. package/dist/{demo-ZRFZ5EBH.js → demo-XZJKCQRO.js} +1 -1
  82. package/dist/{demo-server-7R7NGWJA.js → demo-server-3BCUBH4H.js} +11 -11
  83. package/dist/{discover-E2CV5ZP7.js → discover-JDJ2O6D6.js} +3 -3
  84. package/dist/{discover-eval-ESRCWRHP.js → discover-eval-5GST6BOJ.js} +3 -3
  85. package/dist/{doctor-BQDVERII.js → doctor-2LJGHCIZ.js} +3 -3
  86. package/dist/{evidence-NUULK5XD.js → evidence-FQKL6ULX.js} +3 -3
  87. package/dist/{export-cmd-K324RTN4.js → export-cmd-KNGYT4JH.js} +3 -3
  88. package/dist/{ga-PQ36UFTK.js → ga-UH4RUHX2.js} +25 -3
  89. package/dist/{gbp-KTYA6KHR.js → gbp-IPMROSVV.js} +3 -3
  90. package/dist/{google-3D4E7TJX.js → google-QU5M737H.js} +4 -4
  91. package/dist/{google-marketing-4FRO4PRN.js → google-marketing-QCLVAP25.js} +1 -1
  92. package/dist/{health-cmd-CZJOZUAQ.js → health-cmd-7UFZ5MT6.js} +3 -3
  93. package/dist/{history-JNH34GAM.js → history-3H3CKHVR.js} +3 -3
  94. package/dist/index.d.ts +64 -5
  95. package/dist/index.js +24 -24
  96. package/dist/{init-TU4VSGVU.js → init-HNPTJIOS.js} +11 -11
  97. package/dist/{insights-STYJSZBB.js → insights-LEVVXDBW.js} +3 -3
  98. package/dist/intelligence-service-GQQNTMUX.js +15 -0
  99. package/dist/{journeys-POTSSBOM.js → journeys-JMQBUF2E.js} +3 -3
  100. package/dist/{keys-BPCSPX2O.js → keys-LF2UMTN6.js} +3 -3
  101. package/dist/{keyword-6SML434W.js → keyword-JSJYNWN2.js} +3 -3
  102. package/dist/{logs-WTKKH4UJ.js → logs-RWUB42V3.js} +3 -3
  103. package/dist/{mcp-HTAHMVRC.js → mcp-FWQXTWAR.js} +2 -2
  104. package/dist/mcp.js +9 -9
  105. package/dist/{measurement-plan-LJHKKRZ7.js → measurement-plan-4WXYE6CZ.js} +5 -5
  106. package/dist/{notify-EARS4LMG.js → notify-FBS43GY5.js} +3 -3
  107. package/dist/{organic-evidence-W7ZQQKPL.js → organic-evidence-TTXXAHFX.js} +3 -3
  108. package/dist/{overview-C7KQD4NR.js → overview-AB5JTMWR.js} +4 -4
  109. package/dist/{project-YU2FIL4B.js → project-GSQMF736.js} +7 -4
  110. package/dist/{query-SYFLWHFA.js → query-JITDUO3B.js} +3 -3
  111. package/dist/{research-UOUXMFBV.js → research-UU5W2XPU.js} +3 -3
  112. package/dist/{results-export-VYPG2Z6S.js → results-export-ZX62A645.js} +3 -3
  113. package/dist/{run-GZD3MQGS.js → run-HXNS5CB3.js} +4 -4
  114. package/dist/{schedule-PAA2NLGF.js → schedule-Q4XMBZAH.js} +3 -3
  115. package/dist/{search-6RWCUEEY.js → search-GC4RYLYD.js} +3 -3
  116. package/dist/{serve-VIEGFGSK.js → serve-ECMM256O.js} +25 -25
  117. package/dist/{session-JLH72BF5.js → session-JI4YODZS.js} +12 -12
  118. package/dist/{settings-TTA775EJ.js → settings-5GFLK4DY.js} +3 -3
  119. package/dist/{skills-GA35KKOV.js → skills-B7Z5S3IT.js} +2 -2
  120. package/dist/{snapshot-QVQVAMMN.js → snapshot-2JOQFYJE.js} +3 -3
  121. package/dist/{src-CTHVFVLF.js → src-2UNG3E4M.js} +2 -2
  122. package/dist/{status-E7B726S6.js → status-NKK5MYNE.js} +4 -4
  123. package/dist/{technical-aeo-SMPN3QDQ.js → technical-aeo-MQLGHVIX.js} +3 -3
  124. package/dist/{telemetry-CTBFPMWD.js → telemetry-J5F2H7OJ.js} +4 -4
  125. package/dist/{tools-YOV2KHF2.js → tools-CXANMI23.js} +11 -11
  126. package/dist/{traffic-XACOSYYM.js → traffic-BVKW6QDD.js} +5 -5
  127. package/dist/{users-WJC7SGRL.js → users-2EKLHWPH.js} +3 -3
  128. package/dist/{visibility-stats-H3ZNFV5M.js → visibility-stats-HVQ6VUEB.js} +3 -3
  129. package/dist/{wordpress-72YMWF7F.js → wordpress-EEYBM634.js} +3 -3
  130. package/package.json +1 -1
  131. package/assets/assets/ToneBadge-MKA1xeYc.js +0 -60
  132. package/dist/intelligence-service-MWTHUE7S.js +0 -15
@@ -9,14 +9,14 @@ import {
9
9
  normalizeCloudflareWorkerEvent,
10
10
  sessionizeJourneyRequests,
11
11
  verifyRequestSignature
12
- } from "./chunk-SG2OPR7J.js";
12
+ } from "./chunk-UQABYWAX.js";
13
13
  import {
14
14
  currentOutcomeAttribution,
15
15
  getRequestContext,
16
16
  registerRequestContext,
17
17
  requestAuditIdentity,
18
18
  sanitizeRequestContext
19
- } from "./chunk-QNFX7ZCT.js";
19
+ } from "./chunk-BUED4RWE.js";
20
20
  import {
21
21
  SentimentIdempotencyConflict,
22
22
  SentimentRepository,
@@ -154,7 +154,7 @@ import {
154
154
  userSessions,
155
155
  users,
156
156
  writeNegativeReviewMaxStars
157
- } from "./chunk-HNZQVIPS.js";
157
+ } from "./chunk-ZYE7D2OC.js";
158
158
  import {
159
159
  ADS_ACTIVATE_SCOPE,
160
160
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -565,7 +565,9 @@ import {
565
565
  conversionTrackingOptionsDtoSchema,
566
566
  cosineSimilarity,
567
567
  countAdsActivationManifestEntities,
568
+ countedLeadEventNames,
568
569
  countsAsMention,
570
+ countsKeyEvents,
569
571
  createApiKeyRequestSchema,
570
572
  createAutoAliasAccumulator,
571
573
  createSentimentEvaluationDefinition,
@@ -720,6 +722,7 @@ import {
720
722
  hostMatchesAnyDomain,
721
723
  hostMatchesDomain,
722
724
  hostOf,
725
+ ignoredKeyEventNames,
723
726
  inclusiveDayCount,
724
727
  indexingRequestResponseDtoSchema,
725
728
  insightActionGroupByType,
@@ -1163,7 +1166,7 @@ import {
1163
1166
  wordpressSchemaDeployResultDtoSchema,
1164
1167
  wordpressSchemaStatusResultDtoSchema,
1165
1168
  wordpressStatusDtoSchema
1166
- } from "./chunk-TKLE2Y6H.js";
1169
+ } from "./chunk-4PRXS5JH.js";
1167
1170
 
1168
1171
  // ../api-routes/src/user-session.ts
1169
1172
  import crypto2 from "crypto";
@@ -11732,7 +11735,9 @@ async function projectRoutes(app, opts) {
11732
11735
  measurement: {
11733
11736
  marketingHosts: patch.measurement.marketingHosts ?? existing.measurement.marketingHosts,
11734
11737
  brandTerms: patch.measurement.brandTerms ?? existing.measurement.brandTerms,
11735
- leadEventNames: patch.measurement.leadEventNames ?? existing.measurement.leadEventNames
11738
+ leadEventNames: patch.measurement.leadEventNames ?? existing.measurement.leadEventNames,
11739
+ // Omitted stays omitted, which means on.
11740
+ ...(patch.measurement.countKeyEvents ?? existing.measurement.countKeyEvents) !== void 0 ? { countKeyEvents: patch.measurement.countKeyEvents ?? existing.measurement.countKeyEvents } : {}
11736
11741
  }
11737
11742
  } : {},
11738
11743
  ...patch.autoExtractBacklinks !== void 0 ? { autoExtractBacklinks: patch.autoExtractBacklinks } : {},
@@ -26627,6 +26632,7 @@ function readProjectConfigState(db, name, options = {}) {
26627
26632
  "measurement.marketingHosts": project.measurement.marketingHosts,
26628
26633
  "measurement.brandTerms": project.measurement.brandTerms,
26629
26634
  "measurement.leadEventNames": project.measurement.leadEventNames,
26635
+ "measurement.countKeyEvents": countsKeyEvents(project.measurement),
26630
26636
  locations: project.locations,
26631
26637
  defaultLocation: project.defaultLocation,
26632
26638
  autoExtractBacklinks: project.autoExtractBacklinks,
@@ -29830,7 +29836,7 @@ function literalPathPrefixCondition(path8, prefix) {
29830
29836
  }
29831
29837
 
29832
29838
  // ../api-routes/src/ga-measurement-analysis.ts
29833
- import { and as and44, eq as eq52, gte as gte12, lte as lte11, or as or11, sql as sql18 } from "drizzle-orm";
29839
+ import { and as and44, eq as eq52, gte as gte12, inArray as inArray28, lte as lte11, or as or11, sql as sql18 } from "drizzle-orm";
29834
29840
  var UNKNOWN_LANDING_PAGE = "(not set)";
29835
29841
  function addDays(date, offset) {
29836
29842
  const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
@@ -30131,8 +30137,16 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30131
30137
  };
30132
30138
  const acquisitionScope = scopedConditions(gaAcquisitionDaily.hostName, gaAcquisitionDaily.landingPageNormalized, gaAcquisitionDaily.landingPage);
30133
30139
  const leadLandingScope = scopedConditions(gaLeadEventsDaily.hostName, gaLeadEventsDaily.landingPageNormalized, gaLeadEventsDaily.landingPage);
30140
+ const state = db.select().from(gaMeasurementSyncStates).where(eq52(gaMeasurementSyncStates.projectId, project.id)).get();
30141
+ const configuredLeadEvents = [...project.measurement.leadEventNames];
30142
+ const keyEventsEnabled = countsKeyEvents(project.measurement);
30143
+ const keyEventNames = keyEventsEnabled ? state?.keyEventNames ?? null : null;
30144
+ const countedEventNames = countedLeadEventNames(configuredLeadEvents, keyEventNames ?? []);
30145
+ const measuredEventSet = new Set(state?.leadEventNames ?? configuredLeadEvents);
30146
+ const reportedEventNames = countedEventNames.filter((name) => measuredEventSet.has(name));
30147
+ const leadEventCondition = reportedEventNames.length > 0 ? inArray28(gaLeadEventsDaily.eventName, reportedEventNames) : null;
30134
30148
  const acquisitionAnchor = db.select({ date: sql18`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(and44(eq52(gaAcquisitionDaily.projectId, project.id), ...acquisitionScope)).get()?.date ?? null;
30135
- const leadAnchor = db.select({ date: sql18`max(${gaLeadEventsDaily.date})` }).from(gaLeadEventsDaily).where(and44(eq52(gaLeadEventsDaily.projectId, project.id), or11(
30149
+ const leadAnchor = leadEventCondition === null ? null : db.select({ date: sql18`max(${gaLeadEventsDaily.date})` }).from(gaLeadEventsDaily).where(and44(eq52(gaLeadEventsDaily.projectId, project.id), leadEventCondition, or11(
30136
30150
  eq52(gaLeadEventsDaily.attributionScope, "channel"),
30137
30151
  and44(eq52(gaLeadEventsDaily.attributionScope, "landing-page"), ...leadLandingScope)
30138
30152
  ))).get()?.date ?? null;
@@ -30140,7 +30154,7 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30140
30154
  const gaPeriods = gaAnchor ? buildPeriods(gaAnchor, days) : [];
30141
30155
  const gaStartDate = gaPeriods[0]?.startDate;
30142
30156
  const acquisitionRows = gaStartDate ? db.select().from(gaAcquisitionDaily).where(and44(eq52(gaAcquisitionDaily.projectId, project.id), gte12(gaAcquisitionDaily.date, gaStartDate), lte11(gaAcquisitionDaily.date, gaAnchor))).all() : [];
30143
- const leadRows = gaStartDate ? db.select().from(gaLeadEventsDaily).where(and44(eq52(gaLeadEventsDaily.projectId, project.id), gte12(gaLeadEventsDaily.date, gaStartDate), lte11(gaLeadEventsDaily.date, gaAnchor))).all() : [];
30157
+ const leadRows = gaStartDate && leadEventCondition !== null ? db.select().from(gaLeadEventsDaily).where(and44(eq52(gaLeadEventsDaily.projectId, project.id), leadEventCondition, gte12(gaLeadEventsDaily.date, gaStartDate), lte11(gaLeadEventsDaily.date, gaAnchor))).all() : [];
30144
30158
  const engagementAnchor = db.select({ date: sql18`max(${gaDailyTotals.date})` }).from(gaDailyTotals).where(eq52(gaDailyTotals.projectId, project.id)).get()?.date ?? null;
30145
30159
  const engagementPeriodWindow = engagementAnchor ? buildPeriods(engagementAnchor, days) : [];
30146
30160
  const engagementStartDate = engagementPeriodWindow[0]?.startDate;
@@ -30164,7 +30178,6 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30164
30178
  const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and44(eq52(gscDailyTotals.projectId, project.id), gte12(gscDailyTotals.date, gscStartDate), lte11(gscDailyTotals.date, gscAnchor))).all() : [];
30165
30179
  const queryRows2 = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and44(eq52(gscQueryDailyTotals.projectId, project.id), gte12(gscQueryDailyTotals.date, gscStartDate), lte11(gscQueryDailyTotals.date, gscAnchor))).all() : [];
30166
30180
  const rawPageRows = gscStartDate ? db.select().from(gscSearchData).where(and44(eq52(gscSearchData.projectId, project.id), gte12(gscSearchData.date, gscStartDate), lte11(gscSearchData.date, gscAnchor))).all() : [];
30167
- const state = db.select().from(gaMeasurementSyncStates).where(eq52(gaMeasurementSyncStates.projectId, project.id)).get();
30168
30181
  const acquisition = acquisitionRows.filter((row) => {
30169
30182
  const landingPage = normalizeLandingPage(row.landingPageNormalized ?? row.landingPage);
30170
30183
  return hostIsIncluded(row.hostName) && pageIsIncluded(landingPage);
@@ -30211,6 +30224,19 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30211
30224
  (row) => row.channelGroup,
30212
30225
  (row) => row.eventCount
30213
30226
  );
30227
+ const leadEvents = aggregateByKey(
30228
+ leads,
30229
+ gaPeriods,
30230
+ (row) => row.eventName,
30231
+ (row) => row.eventCount
30232
+ );
30233
+ for (const eventName of reportedEventNames) {
30234
+ if (!leadEvents.has(eventName)) leadEvents.set(eventName, gaPeriods.map(() => 0));
30235
+ }
30236
+ const unmeasuredEventNames = countedEventNames.filter((name) => !measuredEventSet.has(name));
30237
+ const configuredEventSet = new Set(configuredLeadEvents);
30238
+ const keyEventSet = new Set(keyEventNames ?? []);
30239
+ const eventOrigin = (eventName) => configuredEventSet.has(eventName) && keyEventSet.has(eventName) ? "both" : keyEventSet.has(eventName) ? "key-event" : "configured";
30214
30240
  const filtersNarrowSessions = parsedHostScope.data === "marketing" || pathPrefix !== null && pathPrefix !== "/";
30215
30241
  const hasAiEngineLeadData = gaAnchor !== null && hasLeadTimeline;
30216
30242
  const aiEngineRows = attributeAiEngineRows({
@@ -30338,7 +30364,28 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30338
30364
  channelGroup,
30339
30365
  periods: eventPeriods(gaPeriods, values)
30340
30366
  })),
30341
- aiEngines: aiEngineLeads
30367
+ aiEngines: aiEngineLeads,
30368
+ countedEvents: {
30369
+ configured: configuredLeadEvents,
30370
+ keyEventsEnabled,
30371
+ keyEvents: keyEventNames,
30372
+ keyEventsError: keyEventsEnabled ? state?.keyEventsError ?? null : null,
30373
+ ignoredKeyEvents: ignoredKeyEventNames(configuredLeadEvents, keyEventNames ?? [])
30374
+ },
30375
+ events: [
30376
+ ...gaAnchor && hasLeadTimeline ? rankEntries(leadEvents).map(([eventName, values]) => ({
30377
+ eventName,
30378
+ origin: eventOrigin(eventName),
30379
+ measured: true,
30380
+ periods: eventPeriods(gaPeriods, values)
30381
+ })) : [],
30382
+ ...unmeasuredEventNames.map((eventName) => ({
30383
+ eventName,
30384
+ origin: eventOrigin(eventName),
30385
+ measured: false,
30386
+ periods: []
30387
+ }))
30388
+ ]
30342
30389
  },
30343
30390
  engagement: {
30344
30391
  status: engagementAnchor === null ? "unavailable" : "ready",
@@ -30921,7 +30968,7 @@ async function organicEvidenceRoutes(app) {
30921
30968
  }
30922
30969
 
30923
30970
  // ../api-routes/src/citations.ts
30924
- import { and as and46, eq as eq54, inArray as inArray28 } from "drizzle-orm";
30971
+ import { and as and46, eq as eq54, inArray as inArray29 } from "drizzle-orm";
30925
30972
  async function citationRoutes(app) {
30926
30973
  app.get("/projects/:name/citations/visibility", async (request, reply) => {
30927
30974
  const project = resolveProject(app.db, request.params.name);
@@ -30945,7 +30992,7 @@ async function citationRoutes(app) {
30945
30992
  competitorOverlap: querySnapshots.competitorOverlap,
30946
30993
  answerMentioned: querySnapshots.answerMentioned,
30947
30994
  createdAt: querySnapshots.createdAt
30948
- }).from(querySnapshots).where(inArray28(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
30995
+ }).from(querySnapshots).where(inArray29(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
30949
30996
  if (rawSnapshots.length === 0) {
30950
30997
  return reply.send(emptyCitationVisibility("no-runs-yet"));
30951
30998
  }
@@ -31082,7 +31129,7 @@ function computeCitationVisibility(input) {
31082
31129
  }
31083
31130
 
31084
31131
  // ../api-routes/src/visibility-stats.ts
31085
- import { and as and47, desc as desc26, eq as eq55, inArray as inArray29 } from "drizzle-orm";
31132
+ import { and as and47, desc as desc26, eq as eq55, inArray as inArray30 } from "drizzle-orm";
31086
31133
 
31087
31134
  // ../api-routes/src/visibility-compare.ts
31088
31135
  var VISIBILITY_COMPARE_MIN_RUNS = 5;
@@ -31569,7 +31616,7 @@ async function visibilityStatsRoutes(app) {
31569
31616
  citationState: querySnapshots.citationState,
31570
31617
  answerMentioned: querySnapshots.answerMentioned,
31571
31618
  createdAt: querySnapshots.createdAt
31572
- }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31619
+ }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31573
31620
  const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
31574
31621
  const queryAttribution = buildQueryAttribution(projectQueries);
31575
31622
  let shareOfVoice;
@@ -31580,7 +31627,7 @@ async function visibilityStatsRoutes(app) {
31580
31627
  queryText: querySnapshots.queryText,
31581
31628
  answerMentioned: querySnapshots.answerMentioned,
31582
31629
  answerText: querySnapshots.answerText
31583
- }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31630
+ }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31584
31631
  const attributedSovSnapshots = sovSnapshots.filter((s) => resolveCurrentQuery(queryAttribution, s) !== void 0);
31585
31632
  const advanced = activeMeasurementPlan(app.db, project.id)?.plan.schemaVersion === 2;
31586
31633
  const queryClass = advanced || projectQueryClassifier(project) ? requestedQueryClass : "all";
@@ -31678,7 +31725,7 @@ function readVisibilityCompare(db, projectName, query) {
31678
31725
  answerText: querySnapshots.answerText,
31679
31726
  citedDomains: querySnapshots.citedDomains,
31680
31727
  location: querySnapshots.location
31681
- }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31728
+ }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31682
31729
  return {
31683
31730
  runCount: runIds.length,
31684
31731
  snapshots: snapshots.filter((snapshot) => (providerKey2 === void 0 || normalizeIdentityText(snapshot.provider) === providerKey2) && (locationKey2 === void 0 || (locationKey2 === "none" ? snapshot.location === null : snapshot.location !== null && normalizeIdentityText(snapshot.location) === locationKey2)))
@@ -31797,7 +31844,7 @@ function readVisibilityContinuity(db, projectName, months) {
31797
31844
  const trackedQueries = db.select({ id: queries.id, query: queries.query }).from(queries).where(eq55(queries.projectId, project.id)).all();
31798
31845
  const loadProjectMonth = (bounds) => {
31799
31846
  const runIds = legacyCompareRunIds(db, project.id, bounds);
31800
- const rows = runIds.length > 0 && trackedQueries.length > 0 ? db.select({ queryId: querySnapshots.queryId, queryText: querySnapshots.queryText, provider: querySnapshots.provider, model: querySnapshots.model }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31847
+ const rows = runIds.length > 0 && trackedQueries.length > 0 ? db.select({ queryId: querySnapshots.queryId, queryText: querySnapshots.queryText, provider: querySnapshots.provider, model: querySnapshots.model }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31801
31848
  return { runCount: runIds.length, snapshots: rows.map(bare) };
31802
31849
  };
31803
31850
  const frames = [{ frame: "project", continuity: gate(trackedQueries, loadProjectMonth(fromBounds), loadProjectMonth(toBounds)) }];
@@ -32057,7 +32104,7 @@ async function resultsExportRoutes(app) {
32057
32104
  }
32058
32105
 
32059
32106
  // ../api-routes/src/composites.ts
32060
- import { eq as eq57, and as and49, desc as desc27, sql as sql20, like as like5, or as or13, inArray as inArray30 } from "drizzle-orm";
32107
+ import { eq as eq57, and as and49, desc as desc27, sql as sql20, like as like5, or as or13, inArray as inArray31 } from "drizzle-orm";
32061
32108
  var TOP_INSIGHT_LIMIT = 5;
32062
32109
  var SEARCH_HIT_HARD_LIMIT = 50;
32063
32110
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -32358,7 +32405,7 @@ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
32358
32405
  rawResponse: querySnapshots.rawResponse,
32359
32406
  competitorOverlap: querySnapshots.competitorOverlap,
32360
32407
  citedDomains: querySnapshots.citedDomains
32361
- }).from(querySnapshots).where(inArray30(querySnapshots.runId, [...runIds])).all();
32408
+ }).from(querySnapshots).where(inArray31(querySnapshots.runId, [...runIds])).all();
32362
32409
  for (const row of rows) {
32363
32410
  const queryText = row.queryText?.trim() || null;
32364
32411
  let queryId;
@@ -32798,7 +32845,7 @@ import crypto29 from "crypto";
32798
32845
  import { and as and51, desc as desc29, eq as eq59 } from "drizzle-orm";
32799
32846
 
32800
32847
  // ../api-routes/src/content-data.ts
32801
- import { and as and50, eq as eq58, desc as desc28, gte as gte15, inArray as inArray31, lte as lte14 } from "drizzle-orm";
32848
+ import { and as and50, eq as eq58, desc as desc28, gte as gte15, inArray as inArray32, lte as lte14 } from "drizzle-orm";
32802
32849
  var RECENT_RUNS_WINDOW = 5;
32803
32850
  function loadOrchestratorInput(db, project, locationFilter = void 0, gscWindowDays = DEFAULT_CONTENT_GSC_WINDOW_DAYS) {
32804
32851
  const projectId = project.id;
@@ -32938,7 +32985,7 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
32938
32985
  // Queued/running/failed/cancelled runs may have partial or no
32939
32986
  // snapshots; including them risks pointing latestRunId at a run with
32940
32987
  // no usable evidence.
32941
- inArray31(runs.status, [RunStatuses.completed, RunStatuses.partial]),
32988
+ inArray32(runs.status, [RunStatuses.completed, RunStatuses.partial]),
32942
32989
  // Probe runs are operator/agent test runs; they must not poison the
32943
32990
  // recent-runs window the content engine uses to recommend actions.
32944
32991
  notProbeRun()
@@ -33006,7 +33053,7 @@ function buildCandidateQueries(opts) {
33006
33053
  const queryRows2 = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq58(queries.projectId, opts.projectId)).all();
33007
33054
  const queryIdByText = new Map(queryRows2.map((r) => [r.text, r.id]));
33008
33055
  const candidateQueryIds = opts.candidateQueryStrings.map((q) => queryIdByText.get(q)).filter((id) => Boolean(id));
33009
- const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray31(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
33056
+ const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray32(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
33010
33057
  const snapshotsByQuery = /* @__PURE__ */ new Map();
33011
33058
  for (const row of snapshotRows) {
33012
33059
  const list = snapshotsByQuery.get(row.queryId) ?? [];
@@ -45064,7 +45111,7 @@ async function gbpLocalPostsRoutes(app, options) {
45064
45111
  }
45065
45112
 
45066
45113
  // ../api-routes/src/google.ts
45067
- import { eq as eq66, and as and54, desc as desc31, sql as sql21, inArray as inArray32 } from "drizzle-orm";
45114
+ import { eq as eq66, and as and54, desc as desc31, sql as sql21, inArray as inArray33 } from "drizzle-orm";
45068
45115
 
45069
45116
  // ../api-routes/src/gsc-period-comparison.ts
45070
45117
  var DAY_MS2 = 864e5;
@@ -45937,39 +45984,7 @@ async function listProperties(accessToken) {
45937
45984
  headers: { "Authorization": `Bearer ${accessToken}` },
45938
45985
  signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
45939
45986
  });
45940
- if (!res.ok) {
45941
- const raw = await res.text().catch(() => "");
45942
- let detail = "";
45943
- let quotaReason = false;
45944
- try {
45945
- const parsed = JSON.parse(raw);
45946
- if (parsed.error?.status === "SERVICE_DISABLED") {
45947
- detail = " The Google Analytics Admin API is not enabled for this GCP project. Enable it at: https://console.developers.google.com/apis/api/analyticsadmin.googleapis.com/overview";
45948
- } else if (parsed.error?.message) {
45949
- detail = ` ${parsed.error.message}`;
45950
- }
45951
- quotaReason = (parsed.error?.errors ?? []).some(
45952
- (e) => e.reason === "rateLimitExceeded" || e.reason === "quotaExceeded" || e.reason === "userRateLimitExceeded"
45953
- ) || parsed.error?.status === "RESOURCE_EXHAUSTED";
45954
- } catch {
45955
- if (raw.length < 200) detail = ` ${raw}`;
45956
- }
45957
- const sanitized = detail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
45958
- const retryAfterSeconds2 = res.status === 429 || res.status >= 500 || res.status === 403 && quotaReason ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
45959
- ga4Log("error", "admin.account-summaries-failed", {
45960
- httpStatus: res.status,
45961
- retryAfterSeconds: retryAfterSeconds2,
45962
- quotaReason
45963
- });
45964
- throw new GA4ApiError(
45965
- `Failed to list GA4 properties.${sanitized}`,
45966
- // A quota 403 is surfaced as 429 so every downstream consumer — the
45967
- // retry predicate and the route's error mapper — reads it as rate
45968
- // limiting rather than as a permission denial.
45969
- res.status === 403 && quotaReason ? 429 : res.status,
45970
- retryAfterSeconds2
45971
- );
45972
- }
45987
+ if (!res.ok) throw await adminApiError(res, accessToken, "admin.account-summaries-failed", "Failed to list GA4 properties.");
45973
45988
  return await res.json();
45974
45989
  }, "GA4 accountSummaries"));
45975
45990
  for (const account of body.accountSummaries ?? []) {
@@ -45987,6 +46002,53 @@ async function listProperties(accessToken) {
45987
46002
  } while (pageToken);
45988
46003
  return properties;
45989
46004
  }
46005
+ async function adminApiError(res, accessToken, logAction, message) {
46006
+ const raw = await res.text().catch(() => "");
46007
+ let detail = "";
46008
+ let quotaReason = false;
46009
+ try {
46010
+ const parsed = JSON.parse(raw);
46011
+ if (parsed.error?.status === "SERVICE_DISABLED") {
46012
+ detail = " The Google Analytics Admin API is not enabled for this GCP project. Enable it at: https://console.developers.google.com/apis/api/analyticsadmin.googleapis.com/overview";
46013
+ } else if (parsed.error?.message) {
46014
+ detail = ` ${parsed.error.message}`;
46015
+ }
46016
+ quotaReason = (parsed.error?.errors ?? []).some(
46017
+ (e) => e.reason === "rateLimitExceeded" || e.reason === "quotaExceeded" || e.reason === "userRateLimitExceeded"
46018
+ ) || parsed.error?.status === "RESOURCE_EXHAUSTED";
46019
+ } catch {
46020
+ if (raw.length < 200) detail = ` ${raw}`;
46021
+ }
46022
+ const sanitized = detail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
46023
+ const retryAfterSeconds2 = res.status === 429 || res.status >= 500 || res.status === 403 && quotaReason ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
46024
+ ga4Log("error", logAction, { httpStatus: res.status, retryAfterSeconds: retryAfterSeconds2, quotaReason });
46025
+ return new GA4ApiError(`${message}${sanitized}`, res.status === 403 && quotaReason ? 429 : res.status, retryAfterSeconds2);
46026
+ }
46027
+ async function fetchKeyEventNames(accessToken, propertyId) {
46028
+ validateAccessToken2(accessToken);
46029
+ validatePropertyId(propertyId);
46030
+ const names = /* @__PURE__ */ new Set();
46031
+ let pageToken;
46032
+ do {
46033
+ const url = new URL(`${GA4_ADMIN_API_BASE}/properties/${propertyId}/keyEvents`);
46034
+ url.searchParams.set("pageSize", "200");
46035
+ if (pageToken) url.searchParams.set("pageToken", pageToken);
46036
+ const body = await withGa4Limit(() => withGa4Retry(async () => {
46037
+ const res = await fetchWithContext(url, {
46038
+ headers: { "Authorization": `Bearer ${accessToken}` },
46039
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
46040
+ });
46041
+ if (!res.ok) throw await adminApiError(res, accessToken, "admin.key-events-failed", "Failed to list GA4 key events.");
46042
+ return await res.json();
46043
+ }, "GA4 keyEvents"));
46044
+ for (const keyEvent of body.keyEvents ?? []) {
46045
+ const name = keyEvent.eventName?.trim();
46046
+ if (name) names.add(name);
46047
+ }
46048
+ pageToken = body.nextPageToken;
46049
+ } while (pageToken);
46050
+ return [...names].sort();
46051
+ }
45990
46052
  async function fetchAggregateSummary(accessToken, propertyId, days) {
45991
46053
  validateAccessToken2(accessToken);
45992
46054
  validatePropertyId(propertyId);
@@ -48304,10 +48366,10 @@ async function googleRoutes(app, opts) {
48304
48366
  locationProfiles: []
48305
48367
  });
48306
48368
  }
48307
- const metricRows = app.db.select().from(gbpDailyMetrics).where(and54(eq66(gbpDailyMetrics.projectId, project.id), inArray32(gbpDailyMetrics.locationName, locationNames))).all();
48308
- const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and54(eq66(gbpKeywordImpressions.projectId, project.id), inArray32(gbpKeywordImpressions.locationName, locationNames))).all();
48309
- const placeActionRows = app.db.select().from(gbpPlaceActions).where(and54(eq66(gbpPlaceActions.projectId, project.id), inArray32(gbpPlaceActions.locationName, locationNames))).all();
48310
- const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and54(eq66(gbpLodgingSnapshots.projectId, project.id), inArray32(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc31(gbpLodgingSnapshots.syncedAt)).all();
48369
+ const metricRows = app.db.select().from(gbpDailyMetrics).where(and54(eq66(gbpDailyMetrics.projectId, project.id), inArray33(gbpDailyMetrics.locationName, locationNames))).all();
48370
+ const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and54(eq66(gbpKeywordImpressions.projectId, project.id), inArray33(gbpKeywordImpressions.locationName, locationNames))).all();
48371
+ const placeActionRows = app.db.select().from(gbpPlaceActions).where(and54(eq66(gbpPlaceActions.projectId, project.id), inArray33(gbpPlaceActions.locationName, locationNames))).all();
48372
+ const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and54(eq66(gbpLodgingSnapshots.projectId, project.id), inArray33(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc31(gbpLodgingSnapshots.syncedAt)).all();
48311
48373
  const latestLodgingByLocation = /* @__PURE__ */ new Map();
48312
48374
  for (const row of lodgingRows) {
48313
48375
  if (!latestLodgingByLocation.has(row.locationName)) {
@@ -48321,7 +48383,7 @@ async function googleRoutes(app, opts) {
48321
48383
  regularHours: gbpLocations.regularHours,
48322
48384
  primaryPhone: gbpLocations.primaryPhone,
48323
48385
  openStatus: gbpLocations.openStatus
48324
- }).from(gbpLocations).where(and54(eq66(gbpLocations.projectId, project.id), inArray32(gbpLocations.locationName, locationNames))).all();
48386
+ }).from(gbpLocations).where(and54(eq66(gbpLocations.projectId, project.id), inArray33(gbpLocations.locationName, locationNames))).all();
48325
48387
  return buildGbpSummary({
48326
48388
  locationName,
48327
48389
  locationCount: locationNames.length,
@@ -49991,7 +50053,7 @@ async function googleMarketingRoutes(app, opts) {
49991
50053
 
49992
50054
  // ../api-routes/src/ads.ts
49993
50055
  import crypto42 from "crypto";
49994
- import { eq as eq69, and as and57, asc as asc15, desc as desc33, gt as gt6, gte as gte16, lt as lt10, lte as lte16, ne as ne13, inArray as inArray33, or as or16, isNull as isNull12, isNotNull as isNotNull7, sql as sql24 } from "drizzle-orm";
50056
+ import { eq as eq69, and as and57, asc as asc15, desc as desc33, gt as gt6, gte as gte16, lt as lt10, lte as lte16, ne as ne13, inArray as inArray34, or as or16, isNull as isNull12, isNotNull as isNotNull7, sql as sql24 } from "drizzle-orm";
49995
50057
 
49996
50058
  // ../api-routes/src/ads-activation.ts
49997
50059
  import crypto40 from "crypto";
@@ -53089,7 +53151,7 @@ function semanticallyMatchesMaterializedEntity(app, input) {
53089
53151
  eq69(adsOperations.projectId, input.projectId),
53090
53152
  eq69(adsOperations.entityType, input.entityType),
53091
53153
  eq69(adsOperations.entityId, input.entityId),
53092
- inArray33(adsOperations.kind, [createKind, updateKind])
53154
+ inArray34(adsOperations.kind, [createKind, updateKind])
53093
53155
  )).orderBy(asc15(adsOperations.createdAt), asc15(adsOperations.id)).all();
53094
53156
  if (rows.some((row) => row.state === AdsOperationStates.pending || row.state === AdsOperationStates.reconciling || row.state === AdsOperationStates.unknown || row.kind === updateKind && row.state === AdsOperationStates.succeeded)) {
53095
53157
  return false;
@@ -53537,7 +53599,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
53537
53599
  eq69(adsOperations.id, row.id),
53538
53600
  eq69(adsOperations.projectId, row.projectId),
53539
53601
  gte16(adsOperations.reconcileAttempts, policy.maxAttempts),
53540
- inArray33(adsOperations.state, [
53602
+ inArray34(adsOperations.state, [
53541
53603
  AdsOperationStates.pending,
53542
53604
  AdsOperationStates.unknown,
53543
53605
  AdsOperationStates.reconciling
@@ -53781,7 +53843,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
53781
53843
  eq69(adsOperations.reconcileStrategy, AdsReconcileStrategies.create_fingerprint),
53782
53844
  or16(
53783
53845
  isNotNull7(adsOperations.entityId),
53784
- inArray33(adsOperations.state, [
53846
+ inArray34(adsOperations.state, [
53785
53847
  AdsOperationStates.pending,
53786
53848
  AdsOperationStates.reconciling
53787
53849
  ])
@@ -53832,7 +53894,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
53832
53894
  const eligibleProjectIds = [...credentialsByProjectId.keys()];
53833
53895
  if (eligibleProjectIds.length === 0) return;
53834
53896
  const rows = app.db.select().from(adsOperations).where(and57(
53835
- inArray33(adsOperations.projectId, eligibleProjectIds),
53897
+ inArray34(adsOperations.projectId, eligibleProjectIds),
53836
53898
  staleReconcileWhere
53837
53899
  )).orderBy(asc15(adsOperations.updatedAt)).limit(config.batchSize).all();
53838
53900
  for (const row of rows) {
@@ -54158,7 +54220,7 @@ async function adsRoutes(app, opts) {
54158
54220
  const cursor = parsed.data.cursor ? decodeAdsOperationCursor(parsed.data.cursor, project.id, parsed.data.state) : void 0;
54159
54221
  const rows = app.db.select().from(adsOperations).where(and57(
54160
54222
  eq69(adsOperations.projectId, project.id),
54161
- inArray33(adsOperations.state, parsed.data.state),
54223
+ inArray34(adsOperations.state, parsed.data.state),
54162
54224
  cursor ? or16(
54163
54225
  gt6(adsOperations.createdAt, cursor.createdAt),
54164
54226
  and57(
@@ -54592,7 +54654,7 @@ async function adsRoutes(app, opts) {
54592
54654
  const inFlight2 = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and57(
54593
54655
  eq69(runs.projectId, project.id),
54594
54656
  eq69(runs.kind, RunKinds["ads-sync"]),
54595
- inArray33(runs.status, [RunStatuses.queued, RunStatuses.running])
54657
+ inArray34(runs.status, [RunStatuses.queued, RunStatuses.running])
54596
54658
  )).get();
54597
54659
  if (inFlight2) {
54598
54660
  const existing = { runId: inFlight2.id, status: inFlight2.status };
@@ -56623,6 +56685,7 @@ function persistLeadMeasurement(db, input) {
56623
56685
  leadError: null,
56624
56686
  leadSyncedAt: input.syncedAt,
56625
56687
  leadAttributionScope: input.report.attributionScope,
56688
+ leadEventNames: input.eventNames,
56626
56689
  updatedAt: input.syncedAt
56627
56690
  }).onConflictDoUpdate({
56628
56691
  target: gaMeasurementSyncStates.projectId,
@@ -56631,6 +56694,7 @@ function persistLeadMeasurement(db, input) {
56631
56694
  leadError: null,
56632
56695
  leadSyncedAt: input.syncedAt,
56633
56696
  leadAttributionScope: input.report.attributionScope,
56697
+ leadEventNames: input.eventNames,
56634
56698
  updatedAt: input.syncedAt
56635
56699
  }
56636
56700
  }).run();
@@ -56639,12 +56703,23 @@ function persistLeadMeasurement(db, input) {
56639
56703
  function resetLeadMeasurement(db, projectId, updatedAt) {
56640
56704
  db.transaction((tx) => {
56641
56705
  tx.delete(gaLeadEventsDaily).where(eq73(gaLeadEventsDaily.projectId, projectId)).run();
56642
- tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, updatedAt }).onConflictDoUpdate({
56706
+ tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }).onConflictDoUpdate({
56643
56707
  target: gaMeasurementSyncStates.projectId,
56644
- set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, updatedAt }
56708
+ set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }
56645
56709
  }).run();
56646
56710
  });
56647
56711
  }
56712
+ function persistKeyEventRead(db, projectId, read, updatedAt) {
56713
+ if (read.status === "disabled") return;
56714
+ const set = read.status === "read" ? { keyEventNames: read.names, keyEventsError: null, updatedAt } : { keyEventsError: read.error ?? "Key event read failed", updatedAt };
56715
+ db.insert(gaMeasurementSyncStates).values({ projectId, ...set }).onConflictDoUpdate({
56716
+ target: gaMeasurementSyncStates.projectId,
56717
+ set
56718
+ }).run();
56719
+ }
56720
+ function sameEventNames(left, right) {
56721
+ return left.length === right.length && left.every((name, index) => name === right[index]);
56722
+ }
56648
56723
  function persistMeasurementError(db, projectId, component, message, updatedAt) {
56649
56724
  if (component === "acquisition") {
56650
56725
  db.insert(gaMeasurementSyncStates).values({
@@ -56994,8 +57069,9 @@ async function ga4Routes(app, opts) {
56994
57069
  const syncSearchLanding = !only || only === "search-landing";
56995
57070
  const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq73(gaMeasurementSyncStates.projectId, project.id)).get();
56996
57071
  const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
56997
- const leadDays = measurementState?.leadSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
56998
- const leadEventNames = project.measurement.leadEventNames;
57072
+ let leadDays = GA4_MAX_SYNC_DAYS;
57073
+ let leadEventNames = [];
57074
+ let keyEventRead = { status: "disabled", names: [] };
56999
57075
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
57000
57076
  const runId = crypto47.randomUUID();
57001
57077
  app.db.insert(runs).values({
@@ -57013,6 +57089,18 @@ async function ga4Routes(app, opts) {
57013
57089
  try {
57014
57090
  const { accessToken, propertyId } = await resolveGa4AccessToken(opts, project.name, project.canonicalDomain);
57015
57091
  credentialsResolved = true;
57092
+ if (countsKeyEvents(project.measurement)) {
57093
+ try {
57094
+ keyEventRead = { status: "read", names: await fetchKeyEventNames(accessToken, propertyId) };
57095
+ } catch (error) {
57096
+ keyEventRead = { status: "error", names: measurementState?.keyEventNames ?? [], error: describeError(error) };
57097
+ }
57098
+ persistKeyEventRead(app.db, project.id, keyEventRead, (/* @__PURE__ */ new Date()).toISOString());
57099
+ }
57100
+ leadEventNames = countedLeadEventNames(project.measurement.leadEventNames, keyEventRead.names);
57101
+ const previousLeadEvents = measurementState?.leadEventNames ?? null;
57102
+ const leadEventsChanged = previousLeadEvents === null || !sameEventNames(previousLeadEvents, leadEventNames);
57103
+ leadDays = measurementState?.leadSyncedAt != null && !leadEventsChanged ? days : GA4_MAX_SYNC_DAYS;
57016
57104
  let rows = [];
57017
57105
  let aiReferrals = [];
57018
57106
  let socialReferrals = [];
@@ -57276,13 +57364,16 @@ async function ga4Routes(app, opts) {
57276
57364
  projectId: project.id,
57277
57365
  runId,
57278
57366
  syncedAt: measurementNow,
57279
- report: leadsSettled.value
57367
+ report: leadsSettled.value,
57368
+ eventNames: leadEventNames
57280
57369
  });
57281
57370
  return {
57282
57371
  status: "ready",
57283
57372
  days: leadDays,
57284
57373
  rowCount: leadsSettled.value.rows.length,
57285
- attributionScope: leadsSettled.value.attributionScope
57374
+ attributionScope: leadsSettled.value.attributionScope,
57375
+ eventNames: leadEventNames,
57376
+ keyEvents: keyEventRead
57286
57377
  };
57287
57378
  } catch (error) {
57288
57379
  return recordMeasurementFailure("leads", error);
@@ -60441,7 +60532,7 @@ async function backlinksRoutes(app, opts) {
60441
60532
  import crypto52 from "crypto";
60442
60533
  import { isIP } from "net";
60443
60534
  import { isDeepStrictEqual } from "util";
60444
- import { and as and69, desc as desc39, eq as eq80, gt as gt7, gte as gte19, inArray as inArray34, lt as lt13, lte as lte20, sql as sql30 } from "drizzle-orm";
60535
+ import { and as and69, desc as desc39, eq as eq80, gt as gt7, gte as gte19, inArray as inArray35, lt as lt13, lte as lte20, sql as sql30 } from "drizzle-orm";
60445
60536
 
60446
60537
  // ../api-routes/src/traffic-limits.ts
60447
60538
  var VERCEL_MAX_SYNC_WINDOW_MS = 24 * 60 * 6e4;
@@ -65390,7 +65481,7 @@ async function trafficRoutes(app, opts) {
65390
65481
  const listed = useContentList ? contentPaths : infraPaths;
65391
65482
  const IN_LIST_CAP = 2e4;
65392
65483
  const listFitsInSql = listed.length <= IN_LIST_CAP;
65393
- const contentExpr = listed.length === 0 ? useContentList ? sql30`0` : sql30`${crawlerEventsHourly.hits}` : useContentList ? sql30`CASE WHEN ${inArray34(crawlerEventsHourly.pathNormalized, listed)} THEN ${crawlerEventsHourly.hits} ELSE 0 END` : sql30`CASE WHEN ${inArray34(crawlerEventsHourly.pathNormalized, listed)} THEN 0 ELSE ${crawlerEventsHourly.hits} END`;
65484
+ const contentExpr = listed.length === 0 ? useContentList ? sql30`0` : sql30`${crawlerEventsHourly.hits}` : useContentList ? sql30`CASE WHEN ${inArray35(crawlerEventsHourly.pathNormalized, listed)} THEN ${crawlerEventsHourly.hits} ELSE 0 END` : sql30`CASE WHEN ${inArray35(crawlerEventsHourly.pathNormalized, listed)} THEN 0 ELSE ${crawlerEventsHourly.hits} END`;
65394
65485
  if (listFitsInSql) {
65395
65486
  const crawlerSeries = app.db.select({
65396
65487
  bucket: crawlerSeriesBucket,
@@ -65779,7 +65870,7 @@ var reportSweepsCheck = {
65779
65870
  };
65780
65871
 
65781
65872
  // ../api-routes/src/doctor/checks/report-models.ts
65782
- import { and as and71, eq as eq82, gte as gte21, inArray as inArray35, lte as lte22 } from "drizzle-orm";
65873
+ import { and as and71, eq as eq82, gte as gte21, inArray as inArray36, lte as lte22 } from "drizzle-orm";
65783
65874
  var reportModelsCheck = {
65784
65875
  id: "report.models",
65785
65876
  category: CheckCategories.providers,
@@ -65794,7 +65885,7 @@ var reportModelsCheck = {
65794
65885
  const months = reportMonths.flatMap((month) => {
65795
65886
  const bounds = calendarMonthBounds(month);
65796
65887
  const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
65797
- const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq82(runs.id, querySnapshots.runId)).where(and71(eq82(runs.projectId, ctx.project.id), eq82(runs.kind, RunKinds["answer-visibility"]), inArray35(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte21(runs.createdAt, bounds.since), lte22(runs.createdAt, bounds.until))).all();
65888
+ const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq82(runs.id, querySnapshots.runId)).where(and71(eq82(runs.projectId, ctx.project.id), eq82(runs.kind, RunKinds["answer-visibility"]), inArray36(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte21(runs.createdAt, bounds.since), lte22(runs.createdAt, bounds.until))).all();
65798
65889
  return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
65799
65890
  month,
65800
65891
  previousMonth: previous,
@@ -66371,7 +66462,7 @@ var BACKLINKS_CHECKS = [
66371
66462
  ];
66372
66463
 
66373
66464
  // ../api-routes/src/doctor/checks/bing-auth.ts
66374
- import { and as and73, desc as desc40, eq as eq85, inArray as inArray36 } from "drizzle-orm";
66465
+ import { and as and73, desc as desc40, eq as eq85, inArray as inArray37 } from "drizzle-orm";
66375
66466
  var BING_CONNECTION_ENTITY = "bing_connection";
66376
66467
  var BING_CONNECTED_ACTION = "bing.connected";
66377
66468
  var BING_DISCONNECTED_ACTION = "bing.disconnected";
@@ -66382,7 +66473,7 @@ function lostBingConnection(ctx, project) {
66382
66473
  const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and73(
66383
66474
  eq85(auditLog.entityType, BING_CONNECTION_ENTITY),
66384
66475
  eq85(auditLog.entityId, project.canonicalDomain),
66385
- inArray36(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
66476
+ inArray37(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
66386
66477
  )).orderBy(desc40(auditLog.createdAt)).limit(1).get();
66387
66478
  return lastChange?.action !== BING_DISCONNECTED_ACTION;
66388
66479
  }
@@ -66928,7 +67019,7 @@ var ga4ConnectionCheck = {
66928
67019
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
66929
67020
 
66930
67021
  // ../api-routes/src/doctor/checks/data-freshness.ts
66931
- import { and as and74, desc as desc41, eq as eq88, inArray as inArray37 } from "drizzle-orm";
67022
+ import { and as and74, desc as desc41, eq as eq88, inArray as inArray38 } from "drizzle-orm";
66932
67023
  var GA_DATA_AGING_DAYS = 3;
66933
67024
  var GA_DATA_STALE_DAYS = 5;
66934
67025
  var GSC_DATA_AGING_DAYS = 5;
@@ -66957,7 +67048,7 @@ function skipped(code, summary3) {
66957
67048
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
66958
67049
  }
66959
67050
  function lastSyncAgeDays(ctx, kinds) {
66960
- const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and74(eq88(runs.projectId, ctx.project.id), inArray37(runs.kind, kinds), eq88(runs.status, "completed"))).orderBy(desc41(runs.createdAt)).limit(1).get();
67051
+ const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and74(eq88(runs.projectId, ctx.project.id), inArray38(runs.kind, kinds), eq88(runs.status, "completed"))).orderBy(desc41(runs.createdAt)).limit(1).get();
66961
67052
  if (!row?.createdAt) return null;
66962
67053
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS4);
66963
67054
  return Number.isFinite(age) ? age : null;
@@ -67452,7 +67543,7 @@ async function probeApiHost(host, options = {}) {
67452
67543
  }
67453
67544
 
67454
67545
  // ../api-routes/src/doctor/checks/integration-sync.ts
67455
- import { and as and75, count as count7, desc as desc42, eq as eq89, gte as gte22, gt as gt8, inArray as inArray38, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
67546
+ import { and as and75, count as count7, desc as desc42, eq as eq89, gte as gte22, gt as gt8, inArray as inArray39, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
67456
67547
  var SYNC_FAILURE_WINDOW = 3;
67457
67548
  var SUMMARY_ERROR_LIMIT = 300;
67458
67549
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -67545,8 +67636,8 @@ function readSyncRunState(db, projectId, kind, options = {}) {
67545
67636
  ...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
67546
67637
  );
67547
67638
  const graded = options.since ? and75(scope, gte22(runs.createdAt, options.since)) : scope;
67548
- const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and75(graded, inArray38(runs.status, FINISHED_STATUSES))).orderBy(desc42(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
67549
- const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and75(graded, inArray38(runs.status, SUCCESS_STATUSES))).orderBy(desc42(runs.createdAt)).limit(1).get();
67639
+ const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and75(graded, inArray39(runs.status, FINISHED_STATUSES))).orderBy(desc42(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
67640
+ const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and75(graded, inArray39(runs.status, SUCCESS_STATUSES))).orderBy(desc42(runs.createdAt)).limit(1).get();
67550
67641
  const failureCount = db.select({ count: count7() }).from(runs).where(and75(lastSuccess ? and75(graded, gt8(runs.createdAt, lastSuccess.createdAt)) : graded, eq89(runs.status, RunStatuses.failed))).get();
67551
67642
  const failuresSinceSuccess = Number(failureCount?.count ?? 0);
67552
67643
  const latest = recent[0];
@@ -68107,7 +68198,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
68107
68198
  );
68108
68199
 
68109
68200
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
68110
- import { and as and77, desc as desc43, eq as eq93, gte as gte23, inArray as inArray39, sql as sql31 } from "drizzle-orm";
68201
+ import { and as and77, desc as desc43, eq as eq93, gte as gte23, inArray as inArray40, sql as sql31 } from "drizzle-orm";
68111
68202
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
68112
68203
  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`.";
68113
68204
  var reviewsAccessCheck = {
@@ -68230,7 +68321,7 @@ var reviewDeliveryCheck = {
68230
68321
  payload: reviewWebhookDeliveries.payload
68231
68322
  }).from(reviewWebhookDeliveries).where(and77(
68232
68323
  eq93(reviewWebhookDeliveries.projectId, ctx.project.id),
68233
- inArray39(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68324
+ inArray40(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68234
68325
  gte23(lastTouched, since)
68235
68326
  )).orderBy(desc43(lastTouched)).all();
68236
68327
  if (rows.length === 0) {
@@ -68815,7 +68906,7 @@ var RUNTIME_STATE_CHECKS = [
68815
68906
  ];
68816
68907
 
68817
68908
  // ../api-routes/src/doctor/checks/traffic-source.ts
68818
- import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray40, isNotNull as isNotNull9, ne as ne16, sql as sql32 } from "drizzle-orm";
68909
+ import { and as and78, desc as desc44, eq as eq94, gte as gte24, inArray as inArray41, isNotNull as isNotNull9, ne as ne16, sql as sql32 } from "drizzle-orm";
68819
68910
  var RECENT_DATA_WARN_DAYS = 7;
68820
68911
  var RECENT_DATA_FAIL_DAYS = 30;
68821
68912
  function isCloudflareDirectPush(source) {
@@ -68995,7 +69086,7 @@ var recentDataCheck = {
68995
69086
  ctx.db.select({ total: sql32`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
68996
69087
  and78(
68997
69088
  eq94(crawlerEventsHourly.projectId, ctx.project.id),
68998
- inArray40(crawlerEventsHourly.sourceId, activeSourceIds),
69089
+ inArray41(crawlerEventsHourly.sourceId, activeSourceIds),
68999
69090
  gte24(crawlerEventsHourly.tsHour, warnCutoff)
69000
69091
  )
69001
69092
  ).get()?.total ?? 0
@@ -69004,7 +69095,7 @@ var recentDataCheck = {
69004
69095
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69005
69096
  and78(
69006
69097
  eq94(aiReferralEventsHourly.projectId, ctx.project.id),
69007
- inArray40(aiReferralEventsHourly.sourceId, activeSourceIds),
69098
+ inArray41(aiReferralEventsHourly.sourceId, activeSourceIds),
69008
69099
  gte24(aiReferralEventsHourly.tsHour, warnCutoff)
69009
69100
  )
69010
69101
  ).get()?.total ?? 0
@@ -69013,7 +69104,7 @@ var recentDataCheck = {
69013
69104
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69014
69105
  and78(
69015
69106
  eq94(aiUserFetchEventsHourly.projectId, ctx.project.id),
69016
- inArray40(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69107
+ inArray41(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69017
69108
  gte24(aiUserFetchEventsHourly.tsHour, warnCutoff)
69018
69109
  )
69019
69110
  ).get()?.total ?? 0
@@ -69039,7 +69130,7 @@ var recentDataCheck = {
69039
69130
  ctx.db.select({ total: sql32`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
69040
69131
  and78(
69041
69132
  eq94(crawlerEventsHourly.projectId, ctx.project.id),
69042
- inArray40(crawlerEventsHourly.sourceId, activeSourceIds),
69133
+ inArray41(crawlerEventsHourly.sourceId, activeSourceIds),
69043
69134
  gte24(crawlerEventsHourly.tsHour, failCutoff)
69044
69135
  )
69045
69136
  ).get()?.total ?? 0
@@ -69048,7 +69139,7 @@ var recentDataCheck = {
69048
69139
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
69049
69140
  and78(
69050
69141
  eq94(aiReferralEventsHourly.projectId, ctx.project.id),
69051
- inArray40(aiReferralEventsHourly.sourceId, activeSourceIds),
69142
+ inArray41(aiReferralEventsHourly.sourceId, activeSourceIds),
69052
69143
  gte24(aiReferralEventsHourly.tsHour, failCutoff)
69053
69144
  )
69054
69145
  ).get()?.total ?? 0
@@ -69057,7 +69148,7 @@ var recentDataCheck = {
69057
69148
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
69058
69149
  and78(
69059
69150
  eq94(aiUserFetchEventsHourly.projectId, ctx.project.id),
69060
- inArray40(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69151
+ inArray41(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69061
69152
  gte24(aiUserFetchEventsHourly.tsHour, failCutoff)
69062
69153
  )
69063
69154
  ).get()?.total ?? 0
@@ -70313,7 +70404,7 @@ async function statusRoutes(app, opts) {
70313
70404
 
70314
70405
  // ../api-routes/src/discovery/routes.ts
70315
70406
  import crypto54 from "crypto";
70316
- import { and as and80, desc as desc46, eq as eq96, gte as gte25, inArray as inArray41, isNull as isNull16, or as or20 } from "drizzle-orm";
70407
+ import { and as and80, desc as desc46, eq as eq96, gte as gte25, inArray as inArray42, isNull as isNull16, or as or20 } from "drizzle-orm";
70317
70408
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
70318
70409
  async function discoveryRoutes(app, opts) {
70319
70410
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -70368,7 +70459,7 @@ async function discoveryRoutes(app, opts) {
70368
70459
  // must never reuse another set's session. Null (= Gemini-only
70369
70460
  // default, including explicit ['gemini']) matches legacy rows.
70370
70461
  seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq96(discoverySessions.seedProviders, seedProviders),
70371
- inArray41(discoverySessions.status, [
70462
+ inArray42(discoverySessions.status, [
70372
70463
  DiscoverySessionStatuses.queued,
70373
70464
  DiscoverySessionStatuses.seeding,
70374
70465
  DiscoverySessionStatuses.probing
@@ -71076,7 +71167,7 @@ function dedupeStrings(input) {
71076
71167
 
71077
71168
  // ../api-routes/src/technical-aeo.ts
71078
71169
  import crypto56 from "crypto";
71079
- import { and as and82, asc as asc21, count as count8, desc as desc47, eq as eq99, gte as gte26, inArray as inArray42, isNotNull as isNotNull10, isNull as isNull17, lt as lt14, or as or21, sql as sql34 } from "drizzle-orm";
71170
+ import { and as and82, asc as asc21, count as count8, desc as desc47, eq as eq99, gte as gte26, inArray as inArray43, isNotNull as isNotNull10, isNull as isNull17, lt as lt14, or as or21, sql as sql34 } from "drizzle-orm";
71080
71171
  import { alias } from "drizzle-orm/sqlite-core";
71081
71172
 
71082
71173
  // ../api-routes/src/technical-aeo-issues.ts
@@ -71712,7 +71803,7 @@ async function technicalAeoRoutes(app, opts) {
71712
71803
  eq99(siteCrawlSnapshots.projectId, projectId),
71713
71804
  eq99(runs.projectId, projectId),
71714
71805
  eq99(runs.kind, RunKinds["site-audit"]),
71715
- inArray42(runs.status, SURFACEABLE_STATUSES),
71806
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71716
71807
  notProbeRun()
71717
71808
  ];
71718
71809
  const availableScanDatesFor = (projectId, requestedDate) => {
@@ -71782,7 +71873,7 @@ async function technicalAeoRoutes(app, opts) {
71782
71873
  eq99(siteAuditSnapshots.projectId, projectId),
71783
71874
  eq99(runs.projectId, projectId),
71784
71875
  eq99(runs.kind, RunKinds["site-audit"]),
71785
- inArray42(runs.status, SURFACEABLE_STATUSES),
71876
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71786
71877
  notProbeRun(),
71787
71878
  ...recordedBefore ? [lt14(siteAuditSnapshots.createdAt, recordedBefore)] : []
71788
71879
  ];
@@ -71925,7 +72016,7 @@ async function technicalAeoRoutes(app, opts) {
71925
72016
  eq99(runs.id, runId),
71926
72017
  eq99(runs.projectId, projectId),
71927
72018
  eq99(runs.kind, RunKinds["site-audit"]),
71928
- inArray42(runs.status, SURFACEABLE_STATUSES),
72019
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71929
72020
  notProbeRun()
71930
72021
  )).limit(1).get());
71931
72022
  const assertKnownAuditRun = (projectId, runId) => {
@@ -71935,7 +72026,7 @@ async function technicalAeoRoutes(app, opts) {
71935
72026
  eq99(siteAuditSnapshots.projectId, projectId),
71936
72027
  eq99(runs.projectId, projectId),
71937
72028
  eq99(runs.kind, RunKinds["site-audit"]),
71938
- inArray42(runs.status, SURFACEABLE_STATUSES),
72029
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71939
72030
  notProbeRun()
71940
72031
  )).limit(1).get());
71941
72032
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -72066,7 +72157,7 @@ async function technicalAeoRoutes(app, opts) {
72066
72157
  eq99(siteAuditSnapshots.projectId, project.id),
72067
72158
  eq99(runs.projectId, project.id),
72068
72159
  eq99(runs.kind, RunKinds["site-audit"]),
72069
- inArray42(runs.status, SURFACEABLE_STATUSES),
72160
+ inArray43(runs.status, SURFACEABLE_STATUSES),
72070
72161
  notProbeRun()
72071
72162
  )).orderBy(desc47(siteAuditSnapshots.createdAt)).limit(limit).all();
72072
72163
  return { project: project.name, points: rows.reverse() };
@@ -72381,8 +72472,8 @@ async function technicalAeoRoutes(app, opts) {
72381
72472
  eq99(siteCrawlEdges.internal, true),
72382
72473
  isNotNull10(siteCrawlEdges.targetNodeKey),
72383
72474
  or21(
72384
- inArray42(siteCrawlEdges.sourceNodeKey, frontier),
72385
- inArray42(siteCrawlEdges.targetNodeKey, frontier)
72475
+ inArray43(siteCrawlEdges.sourceNodeKey, frontier),
72476
+ inArray43(siteCrawlEdges.targetNodeKey, frontier)
72386
72477
  )
72387
72478
  )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
72388
72479
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
@@ -72419,7 +72510,7 @@ async function technicalAeoRoutes(app, opts) {
72419
72510
  eq99(siteCrawlPages.projectId, scope.projectId),
72420
72511
  eq99(siteCrawlPages.runId, scope.runId),
72421
72512
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72422
- inArray42(siteCrawlPages.nodeKey, nodeKeys)
72513
+ inArray43(siteCrawlPages.nodeKey, nodeKeys)
72423
72514
  )).all();
72424
72515
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
72425
72516
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
@@ -72429,8 +72520,8 @@ async function technicalAeoRoutes(app, opts) {
72429
72520
  eq99(siteCrawlEdges.attemptId, scope.attemptId),
72430
72521
  eq99(siteCrawlEdges.internal, true),
72431
72522
  isNotNull10(siteCrawlEdges.targetNodeKey),
72432
- inArray42(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
72433
- inArray42(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
72523
+ inArray43(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
72524
+ inArray43(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
72434
72525
  )).limit(1).get();
72435
72526
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
72436
72527
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -72531,7 +72622,7 @@ async function technicalAeoRoutes(app, opts) {
72531
72622
  eq99(siteCrawlEdges.followable, true),
72532
72623
  eq99(siteCrawlEdges.relation, "anchor"),
72533
72624
  isNotNull10(siteCrawlEdges.targetNodeKey),
72534
- inArray42(siteCrawlEdges.sourceNodeKey, frontier)
72625
+ inArray43(siteCrawlEdges.sourceNodeKey, frontier)
72535
72626
  )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
72536
72627
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
72537
72628
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
@@ -72539,7 +72630,7 @@ async function technicalAeoRoutes(app, opts) {
72539
72630
  eq99(siteCrawlPages.projectId, scope.projectId),
72540
72631
  eq99(siteCrawlPages.runId, scope.runId),
72541
72632
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72542
- inArray42(siteCrawlPages.nodeKey, candidateTargetKeys)
72633
+ inArray43(siteCrawlPages.nodeKey, candidateTargetKeys)
72543
72634
  )).all().map((row) => row.nodeKey));
72544
72635
  const next = /* @__PURE__ */ new Set();
72545
72636
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -72584,7 +72675,7 @@ async function technicalAeoRoutes(app, opts) {
72584
72675
  eq99(siteCrawlPages.projectId, scope.projectId),
72585
72676
  eq99(siteCrawlPages.runId, scope.runId),
72586
72677
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72587
- inArray42(siteCrawlPages.nodeKey, pathKeys)
72678
+ inArray43(siteCrawlPages.nodeKey, pathKeys)
72588
72679
  )).all();
72589
72680
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
72590
72681
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -72875,14 +72966,14 @@ async function technicalAeoRoutes(app, opts) {
72875
72966
  eq99(siteCrawlPages.projectId, scope.projectId),
72876
72967
  eq99(siteCrawlPages.runId, scope.runId),
72877
72968
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72878
- inArray42(siteCrawlPages.nodeKey, pageKeys)
72969
+ inArray43(siteCrawlPages.nodeKey, pageKeys)
72879
72970
  )).all();
72880
72971
  const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and82(
72881
72972
  eq99(siteCrawlEdges.projectId, scope.projectId),
72882
72973
  eq99(siteCrawlEdges.runId, scope.runId),
72883
72974
  eq99(siteCrawlEdges.attemptId, scope.attemptId),
72884
72975
  eq99(siteCrawlEdges.internal, true),
72885
- inArray42(siteCrawlEdges.edgeKey, linkKeys)
72976
+ inArray43(siteCrawlEdges.edgeKey, linkKeys)
72886
72977
  )).all();
72887
72978
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
72888
72979
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -73408,9 +73499,9 @@ async function technicalAeoRoutes(app, opts) {
73408
73499
  eq99(siteCrawlSnapshots.projectId, project.id),
73409
73500
  eq99(runs.projectId, project.id),
73410
73501
  eq99(runs.kind, RunKinds["site-audit"]),
73411
- inArray42(runs.status, SURFACEABLE_STATUSES),
73502
+ inArray43(runs.status, SURFACEABLE_STATUSES),
73412
73503
  notProbeRun(),
73413
- inArray42(siteCrawlSnapshots.runId, runIds)
73504
+ inArray43(siteCrawlSnapshots.runId, runIds)
73414
73505
  )).all().map((row) => row.runId));
73415
73506
  return {
73416
73507
  project: project.name,
@@ -73551,7 +73642,7 @@ async function technicalAeoRoutes(app, opts) {
73551
73642
  )).where(and82(
73552
73643
  eq99(runs.projectId, project.id),
73553
73644
  eq99(runs.kind, RunKinds["site-audit"]),
73554
- inArray42(runs.status, [RunStatuses.queued, RunStatuses.running])
73645
+ inArray43(runs.status, [RunStatuses.queued, RunStatuses.running])
73555
73646
  )).get();
73556
73647
  if (existing) {
73557
73648
  if (existing.identityKey === identityKey) {