@canonry/canonry 7.63.0 → 7.64.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 (132) hide show
  1. package/assets/agent-workspace/skills/aero/references/ai-traffic-tracking.md +12 -9
  2. package/assets/agent-workspace/skills/canonry/references/ai-traffic-tracking.md +12 -9
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +20 -3
  4. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +10 -0
  5. package/assets/assets/{AgentSetupPage-D6lCJ1G1.js → AgentSetupPage-C1e8ca-F.js} +1 -1
  6. package/assets/assets/{AuditHistoryPanel-DlgI3o-U.js → AuditHistoryPanel-w8m0gCI_.js} +1 -1
  7. package/assets/assets/{BacklinksPage-ByiMqbSI.js → BacklinksPage-5KLau65-.js} +1 -1
  8. package/assets/assets/{HistoryPage-DclVeuLO.js → HistoryPage-BDT8bwji.js} +1 -1
  9. package/assets/assets/{MeasurementPropertyPage-DDHybtHa.js → MeasurementPropertyPage-BXeUhCMT.js} +1 -1
  10. package/assets/assets/{ProjectPage-vBu6oWFi.js → ProjectPage-DpkuLS-J.js} +1 -1
  11. package/assets/assets/{RunsPage-CYBJ6pXX.js → RunsPage-sX82BCc6.js} +1 -1
  12. package/assets/assets/{SettingsPage-w0GmXir0.js → SettingsPage-Ckpdf_CN.js} +1 -1
  13. package/assets/assets/{SiteHealthSection-DDTjGZ05.js → SiteHealthSection-BXzwqMPd.js} +2 -2
  14. package/assets/assets/{ToneBadge-MKA1xeYc.js → ToneBadge-DX3v0ZMs.js} +1 -1
  15. package/assets/assets/{TrafficPage-hUWYv-HT.js → TrafficPage-DXFWGVO1.js} +1 -1
  16. package/assets/assets/{TrafficSourceDetailPage-CGHgPxs8.js → TrafficSourceDetailPage-C5fkng5v.js} +1 -1
  17. package/assets/assets/{account-context-qX-Es0RK.js → account-context-CeU5Z9QZ.js} +1 -1
  18. package/assets/assets/{card-CzROANT5.js → card-BBGm0dhg.js} +1 -1
  19. package/assets/assets/{extract-error-message-BY1STLiX.js → extract-error-message-CfVZrmn7.js} +1 -1
  20. package/assets/assets/{format-helpers-BNQR1HNK.js → format-helpers-D7251Obw.js} +1 -1
  21. package/assets/assets/{index-LmB57HOs.js → index-DkJdIncY.js} +4 -4
  22. package/assets/assets/{query-client-Gz37p16p.js → query-client-D0E1gVp6.js} +1 -1
  23. package/assets/assets/{react-query-C6DtBAYQ.js → react-query-D23hl-0E.js} +1 -1
  24. package/assets/assets/{react-sigma_core.esm.min-CgxuyPPF.js → react-sigma_core.esm.min-00xBdRUm.js} +1 -1
  25. package/assets/assets/{service-DyHZWnMe.js → service-DgWa71E6.js} +1 -1
  26. package/assets/index.html +7 -7
  27. package/dist/{ads-FOUJNHP4.js → ads-KYINOV7M.js} +3 -3
  28. package/dist/{agent-UVDVH7LK.js → agent-KUYZWQDM.js} +7 -7
  29. package/dist/{agent-ask-UEYN2SQX.js → agent-ask-MMQJ676U.js} +3 -3
  30. package/dist/{agent-conversations-F54376Q2.js → agent-conversations-HHBEVWSA.js} +3 -3
  31. package/dist/{agent-memory-SOHBTJ4U.js → agent-memory-4GVGUXS5.js} +3 -3
  32. package/dist/{agent-providers-MMEK2H6V.js → agent-providers-NZHQGXOD.js} +3 -3
  33. package/dist/{agent-transcript-5IJ3VAEV.js → agent-transcript-DX6OH72V.js} +3 -3
  34. package/dist/{analytics-LDWO5IW6.js → analytics-ZB3WSJJ7.js} +3 -3
  35. package/dist/{apply-PSGFDJIC.js → apply-KINEQ47G.js} +3 -3
  36. package/dist/{backfill-HZU3BBOH.js → backfill-6BNBH7ZO.js} +9 -9
  37. package/dist/{backlinks-X6DXIL2O.js → backlinks-H3HTXZDY.js} +3 -3
  38. package/dist/{bing-3HMFUEOO.js → bing-F4LWNUZV.js} +3 -3
  39. package/dist/{bootstrap-ZUSSLXCI.js → bootstrap-KBV763E2.js} +10 -10
  40. package/dist/{cdp-FH627HJ6.js → cdp-LN3ZPTDI.js} +7 -7
  41. package/dist/{chunk-HNZQVIPS.js → chunk-3I3TZNDR.js} +23 -1
  42. package/dist/{chunk-DBA374EM.js → chunk-3VHU332P.js} +24 -20
  43. package/dist/{chunk-A57HYHJ3.js → chunk-4MLRY4FJ.js} +1 -1
  44. package/dist/{chunk-PH7RTAWW.js → chunk-52VCSAX7.js} +1 -1
  45. package/dist/{chunk-5VAC4KQ2.js → chunk-7TJGQHY6.js} +3 -3
  46. package/dist/{chunk-LEG7F3DN.js → chunk-B23LQ2IZ.js} +2 -2
  47. package/dist/{chunk-5YC6IU44.js → chunk-C5ZK4IHK.js} +2 -2
  48. package/dist/{chunk-SN5A767O.js → chunk-CXZGOSWD.js} +1 -1
  49. package/dist/{chunk-2LAL5W6J.js → chunk-D7IDW3TO.js} +3 -3
  50. package/dist/{chunk-ZJTU73GW.js → chunk-DBP7MBK7.js} +3 -3
  51. package/dist/{chunk-GRWZC3GQ.js → chunk-DYCCEDZF.js} +1 -1
  52. package/dist/{chunk-CWMJQ2JV.js → chunk-E2K4HLIX.js} +4 -4
  53. package/dist/{chunk-UYPBR3SF.js → chunk-EGWLPPTB.js} +2 -2
  54. package/dist/{chunk-ERJ54U2I.js → chunk-GDQBZMDZ.js} +1 -1
  55. package/dist/{chunk-OIHJJHJR.js → chunk-GQILYY3G.js} +1 -1
  56. package/dist/{chunk-L2QPAUTQ.js → chunk-H7WFBL5L.js} +1 -1
  57. package/dist/{chunk-SG2OPR7J.js → chunk-HOPQMZOC.js} +1 -1
  58. package/dist/{chunk-WYTRXYUR.js → chunk-IGITZQRK.js} +5 -5
  59. package/dist/{chunk-TDWNONBU.js → chunk-IZ74LHLX.js} +5 -5
  60. package/dist/{chunk-NOQCIXM6.js → chunk-JUQBI2Y2.js} +1 -1
  61. package/dist/{chunk-QPTSZDHE.js → chunk-KH7443NX.js} +4 -4
  62. package/dist/{chunk-Q4U2GJME.js → chunk-L6QDD6F5.js} +316 -158
  63. package/dist/{chunk-EHTSCLEL.js → chunk-LJZ7GIGO.js} +1 -1
  64. package/dist/{chunk-UBP3AAKA.js → chunk-LVT6WRLB.js} +1 -1
  65. package/dist/{chunk-I5QSS3CY.js → chunk-NSGCU5X6.js} +348 -336
  66. package/dist/{chunk-QNFX7ZCT.js → chunk-S67ZCRDF.js} +1 -1
  67. package/dist/{chunk-H22RCHYH.js → chunk-SQXSMO35.js} +3 -3
  68. package/dist/{chunk-67262NJL.js → chunk-STI6DRNG.js} +2 -2
  69. package/dist/{chunk-LBD7YXVD.js → chunk-T42EHWAR.js} +2 -2
  70. package/dist/{chunk-IDQZTPRG.js → chunk-TWUBHLRB.js} +2 -2
  71. package/dist/{chunk-SQYYAYH6.js → chunk-U2QS52HS.js} +3 -3
  72. package/dist/{chunk-W5TIRS4U.js → chunk-V4SAX4MI.js} +4 -4
  73. package/dist/{chunk-TKLE2Y6H.js → chunk-VCMF4TX5.js} +63 -3
  74. package/dist/{chunk-XWDCLX55.js → chunk-WJHOTZV3.js} +8 -8
  75. package/dist/{citations-BKWLP5S3.js → citations-OBRSEU4U.js} +3 -3
  76. package/dist/{cli-catalog-VRJS66GR.js → cli-catalog-2PSBZUIA.js} +3 -3
  77. package/dist/{cli-commands-6LZ5OIIP.js → cli-commands-44OZDNWA.js} +7 -7
  78. package/dist/cli.js +13 -13
  79. package/dist/{competitor-VELMM2TJ.js → competitor-6CMPUGOO.js} +3 -3
  80. package/dist/{content-I525MITL.js → content-ZTWFVZEH.js} +3 -3
  81. package/dist/{dashboard-XJJ6NZ2L.js → dashboard-6M7TS53W.js} +6 -6
  82. package/dist/{demo-X7LQ24GS.js → demo-6NSR6BVC.js} +1 -1
  83. package/dist/{demo-server-MBPBTJXG.js → demo-server-3YD53OJ6.js} +11 -11
  84. package/dist/{discover-E2CV5ZP7.js → discover-2SD6PX63.js} +3 -3
  85. package/dist/{discover-eval-ESRCWRHP.js → discover-eval-N34ZXEGG.js} +3 -3
  86. package/dist/{doctor-BQDVERII.js → doctor-BKLHIVSH.js} +3 -3
  87. package/dist/{evidence-NUULK5XD.js → evidence-DFBKZAYQ.js} +3 -3
  88. package/dist/{export-cmd-K324RTN4.js → export-cmd-U43VCFQA.js} +3 -3
  89. package/dist/{ga-PQ36UFTK.js → ga-6SHEXO27.js} +22 -3
  90. package/dist/{gbp-KTYA6KHR.js → gbp-TMU3ASMH.js} +3 -3
  91. package/dist/{google-3D4E7TJX.js → google-RTDYVX35.js} +4 -4
  92. package/dist/{google-marketing-4FRO4PRN.js → google-marketing-VR7CZD3V.js} +1 -1
  93. package/dist/{health-cmd-CZJOZUAQ.js → health-cmd-R4CR5JSV.js} +3 -3
  94. package/dist/{history-JNH34GAM.js → history-76HXCNM7.js} +3 -3
  95. package/dist/index.d.ts +59 -0
  96. package/dist/index.js +24 -24
  97. package/dist/{init-JRGRHLYU.js → init-J6BADMB7.js} +11 -11
  98. package/dist/{insights-STYJSZBB.js → insights-JQ7ZUTEB.js} +3 -3
  99. package/dist/intelligence-service-XTCLJSAP.js +15 -0
  100. package/dist/{journeys-POTSSBOM.js → journeys-GPOCEKPM.js} +3 -3
  101. package/dist/{keys-BPCSPX2O.js → keys-CEPK4BP5.js} +3 -3
  102. package/dist/{keyword-6SML434W.js → keyword-6NCXMIFC.js} +3 -3
  103. package/dist/{logs-WTKKH4UJ.js → logs-IIGC4WKI.js} +3 -3
  104. package/dist/{mcp-HTAHMVRC.js → mcp-RC2LU65U.js} +2 -2
  105. package/dist/mcp.js +9 -9
  106. package/dist/{measurement-plan-LJHKKRZ7.js → measurement-plan-BH3ZXRLG.js} +5 -5
  107. package/dist/{notify-EARS4LMG.js → notify-JVL7ZQWC.js} +3 -3
  108. package/dist/{organic-evidence-W7ZQQKPL.js → organic-evidence-FU2254CJ.js} +3 -3
  109. package/dist/{overview-C7KQD4NR.js → overview-CQOJKYB3.js} +4 -4
  110. package/dist/{project-YU2FIL4B.js → project-CHBBMK7T.js} +7 -4
  111. package/dist/{query-SYFLWHFA.js → query-FIUQXVX4.js} +3 -3
  112. package/dist/{research-UOUXMFBV.js → research-N3CSBKTG.js} +3 -3
  113. package/dist/{results-export-VYPG2Z6S.js → results-export-5RCAVBD4.js} +3 -3
  114. package/dist/{run-GZD3MQGS.js → run-6GQF3LBR.js} +4 -4
  115. package/dist/{schedule-PAA2NLGF.js → schedule-Z36TCNV2.js} +3 -3
  116. package/dist/{search-6RWCUEEY.js → search-ON4HP22R.js} +3 -3
  117. package/dist/{serve-FTAFEEJD.js → serve-GWGRQVKA.js} +25 -25
  118. package/dist/{session-E4V5MRUE.js → session-XHMABUUK.js} +12 -12
  119. package/dist/{settings-TTA775EJ.js → settings-LVZDNSTS.js} +3 -3
  120. package/dist/{skills-GA35KKOV.js → skills-3DOCO57E.js} +2 -2
  121. package/dist/{snapshot-QVQVAMMN.js → snapshot-JVUWV4MQ.js} +3 -3
  122. package/dist/{src-CTHVFVLF.js → src-QCQT7JBJ.js} +2 -2
  123. package/dist/{status-E7B726S6.js → status-BAAI3UGB.js} +4 -4
  124. package/dist/{technical-aeo-SMPN3QDQ.js → technical-aeo-RF3KVZNQ.js} +3 -3
  125. package/dist/{telemetry-CTBFPMWD.js → telemetry-67H5UO2X.js} +4 -4
  126. package/dist/{tools-XKGEC56T.js → tools-65JSRP7T.js} +11 -11
  127. package/dist/{traffic-XACOSYYM.js → traffic-OZMUJLQG.js} +5 -5
  128. package/dist/{users-WJC7SGRL.js → users-5Q6PLQTL.js} +3 -3
  129. package/dist/{visibility-stats-H3ZNFV5M.js → visibility-stats-L35IYACG.js} +3 -3
  130. package/dist/{wordpress-72YMWF7F.js → wordpress-I64CCIJA.js} +3 -3
  131. package/package.json +1 -1
  132. package/dist/intelligence-service-7LCTJQDL.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-HOPQMZOC.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-S67ZCRDF.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-3I3TZNDR.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,
@@ -1163,7 +1165,7 @@ import {
1163
1165
  wordpressSchemaDeployResultDtoSchema,
1164
1166
  wordpressSchemaStatusResultDtoSchema,
1165
1167
  wordpressStatusDtoSchema
1166
- } from "./chunk-TKLE2Y6H.js";
1168
+ } from "./chunk-VCMF4TX5.js";
1167
1169
 
1168
1170
  // ../api-routes/src/user-session.ts
1169
1171
  import crypto2 from "crypto";
@@ -11732,7 +11734,9 @@ async function projectRoutes(app, opts) {
11732
11734
  measurement: {
11733
11735
  marketingHosts: patch.measurement.marketingHosts ?? existing.measurement.marketingHosts,
11734
11736
  brandTerms: patch.measurement.brandTerms ?? existing.measurement.brandTerms,
11735
- leadEventNames: patch.measurement.leadEventNames ?? existing.measurement.leadEventNames
11737
+ leadEventNames: patch.measurement.leadEventNames ?? existing.measurement.leadEventNames,
11738
+ // Omitted stays omitted, which means on.
11739
+ ...(patch.measurement.countKeyEvents ?? existing.measurement.countKeyEvents) !== void 0 ? { countKeyEvents: patch.measurement.countKeyEvents ?? existing.measurement.countKeyEvents } : {}
11736
11740
  }
11737
11741
  } : {},
11738
11742
  ...patch.autoExtractBacklinks !== void 0 ? { autoExtractBacklinks: patch.autoExtractBacklinks } : {},
@@ -26627,6 +26631,7 @@ function readProjectConfigState(db, name, options = {}) {
26627
26631
  "measurement.marketingHosts": project.measurement.marketingHosts,
26628
26632
  "measurement.brandTerms": project.measurement.brandTerms,
26629
26633
  "measurement.leadEventNames": project.measurement.leadEventNames,
26634
+ "measurement.countKeyEvents": countsKeyEvents(project.measurement),
26630
26635
  locations: project.locations,
26631
26636
  defaultLocation: project.defaultLocation,
26632
26637
  autoExtractBacklinks: project.autoExtractBacklinks,
@@ -29830,7 +29835,7 @@ function literalPathPrefixCondition(path8, prefix) {
29830
29835
  }
29831
29836
 
29832
29837
  // ../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";
29838
+ 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
29839
  var UNKNOWN_LANDING_PAGE = "(not set)";
29835
29840
  function addDays(date, offset) {
29836
29841
  const value = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
@@ -30131,8 +30136,16 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30131
30136
  };
30132
30137
  const acquisitionScope = scopedConditions(gaAcquisitionDaily.hostName, gaAcquisitionDaily.landingPageNormalized, gaAcquisitionDaily.landingPage);
30133
30138
  const leadLandingScope = scopedConditions(gaLeadEventsDaily.hostName, gaLeadEventsDaily.landingPageNormalized, gaLeadEventsDaily.landingPage);
30139
+ const state = db.select().from(gaMeasurementSyncStates).where(eq52(gaMeasurementSyncStates.projectId, project.id)).get();
30140
+ const configuredLeadEvents = [...project.measurement.leadEventNames];
30141
+ const keyEventsEnabled = countsKeyEvents(project.measurement);
30142
+ const keyEventNames = keyEventsEnabled ? state?.keyEventNames ?? null : null;
30143
+ const countedEventNames = countedLeadEventNames(configuredLeadEvents, keyEventNames ?? []);
30144
+ const measuredEventSet = new Set(state?.leadEventNames ?? configuredLeadEvents);
30145
+ const reportedEventNames = countedEventNames.filter((name) => measuredEventSet.has(name));
30146
+ const leadEventCondition = reportedEventNames.length > 0 ? inArray28(gaLeadEventsDaily.eventName, reportedEventNames) : null;
30134
30147
  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(
30148
+ const leadAnchor = leadEventCondition === null ? null : db.select({ date: sql18`max(${gaLeadEventsDaily.date})` }).from(gaLeadEventsDaily).where(and44(eq52(gaLeadEventsDaily.projectId, project.id), leadEventCondition, or11(
30136
30149
  eq52(gaLeadEventsDaily.attributionScope, "channel"),
30137
30150
  and44(eq52(gaLeadEventsDaily.attributionScope, "landing-page"), ...leadLandingScope)
30138
30151
  ))).get()?.date ?? null;
@@ -30140,7 +30153,7 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30140
30153
  const gaPeriods = gaAnchor ? buildPeriods(gaAnchor, days) : [];
30141
30154
  const gaStartDate = gaPeriods[0]?.startDate;
30142
30155
  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() : [];
30156
+ 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
30157
  const engagementAnchor = db.select({ date: sql18`max(${gaDailyTotals.date})` }).from(gaDailyTotals).where(eq52(gaDailyTotals.projectId, project.id)).get()?.date ?? null;
30145
30158
  const engagementPeriodWindow = engagementAnchor ? buildPeriods(engagementAnchor, days) : [];
30146
30159
  const engagementStartDate = engagementPeriodWindow[0]?.startDate;
@@ -30164,7 +30177,6 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30164
30177
  const propertyRows = gscStartDate ? db.select().from(gscDailyTotals).where(and44(eq52(gscDailyTotals.projectId, project.id), gte12(gscDailyTotals.date, gscStartDate), lte11(gscDailyTotals.date, gscAnchor))).all() : [];
30165
30178
  const queryRows2 = gscStartDate ? db.select().from(gscQueryDailyTotals).where(and44(eq52(gscQueryDailyTotals.projectId, project.id), gte12(gscQueryDailyTotals.date, gscStartDate), lte11(gscQueryDailyTotals.date, gscAnchor))).all() : [];
30166
30179
  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
30180
  const acquisition = acquisitionRows.filter((row) => {
30169
30181
  const landingPage = normalizeLandingPage(row.landingPageNormalized ?? row.landingPage);
30170
30182
  return hostIsIncluded(row.hostName) && pageIsIncluded(landingPage);
@@ -30211,6 +30223,19 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30211
30223
  (row) => row.channelGroup,
30212
30224
  (row) => row.eventCount
30213
30225
  );
30226
+ const leadEvents = aggregateByKey(
30227
+ leads,
30228
+ gaPeriods,
30229
+ (row) => row.eventName,
30230
+ (row) => row.eventCount
30231
+ );
30232
+ for (const eventName of reportedEventNames) {
30233
+ if (!leadEvents.has(eventName)) leadEvents.set(eventName, gaPeriods.map(() => 0));
30234
+ }
30235
+ const unmeasuredEventNames = countedEventNames.filter((name) => !measuredEventSet.has(name));
30236
+ const configuredEventSet = new Set(configuredLeadEvents);
30237
+ const keyEventSet = new Set(keyEventNames ?? []);
30238
+ const eventOrigin = (eventName) => configuredEventSet.has(eventName) && keyEventSet.has(eventName) ? "both" : keyEventSet.has(eventName) ? "key-event" : "configured";
30214
30239
  const filtersNarrowSessions = parsedHostScope.data === "marketing" || pathPrefix !== null && pathPrefix !== "/";
30215
30240
  const hasAiEngineLeadData = gaAnchor !== null && hasLeadTimeline;
30216
30241
  const aiEngineRows = attributeAiEngineRows({
@@ -30338,7 +30363,27 @@ function buildGaMeasurementAnalysis(db, projectName, options = {}) {
30338
30363
  channelGroup,
30339
30364
  periods: eventPeriods(gaPeriods, values)
30340
30365
  })),
30341
- aiEngines: aiEngineLeads
30366
+ aiEngines: aiEngineLeads,
30367
+ countedEvents: {
30368
+ configured: configuredLeadEvents,
30369
+ keyEventsEnabled,
30370
+ keyEvents: keyEventNames,
30371
+ keyEventsError: keyEventsEnabled ? state?.keyEventsError ?? null : null
30372
+ },
30373
+ events: [
30374
+ ...gaAnchor && hasLeadTimeline ? rankEntries(leadEvents).map(([eventName, values]) => ({
30375
+ eventName,
30376
+ origin: eventOrigin(eventName),
30377
+ measured: true,
30378
+ periods: eventPeriods(gaPeriods, values)
30379
+ })) : [],
30380
+ ...unmeasuredEventNames.map((eventName) => ({
30381
+ eventName,
30382
+ origin: eventOrigin(eventName),
30383
+ measured: false,
30384
+ periods: []
30385
+ }))
30386
+ ]
30342
30387
  },
30343
30388
  engagement: {
30344
30389
  status: engagementAnchor === null ? "unavailable" : "ready",
@@ -30921,7 +30966,7 @@ async function organicEvidenceRoutes(app) {
30921
30966
  }
30922
30967
 
30923
30968
  // ../api-routes/src/citations.ts
30924
- import { and as and46, eq as eq54, inArray as inArray28 } from "drizzle-orm";
30969
+ import { and as and46, eq as eq54, inArray as inArray29 } from "drizzle-orm";
30925
30970
  async function citationRoutes(app) {
30926
30971
  app.get("/projects/:name/citations/visibility", async (request, reply) => {
30927
30972
  const project = resolveProject(app.db, request.params.name);
@@ -30945,7 +30990,7 @@ async function citationRoutes(app) {
30945
30990
  competitorOverlap: querySnapshots.competitorOverlap,
30946
30991
  answerMentioned: querySnapshots.answerMentioned,
30947
30992
  createdAt: querySnapshots.createdAt
30948
- }).from(querySnapshots).where(inArray28(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
30993
+ }).from(querySnapshots).where(inArray29(querySnapshots.runId, projectRuns.map((r) => r.id))).all();
30949
30994
  if (rawSnapshots.length === 0) {
30950
30995
  return reply.send(emptyCitationVisibility("no-runs-yet"));
30951
30996
  }
@@ -31082,7 +31127,7 @@ function computeCitationVisibility(input) {
31082
31127
  }
31083
31128
 
31084
31129
  // ../api-routes/src/visibility-stats.ts
31085
- import { and as and47, desc as desc26, eq as eq55, inArray as inArray29 } from "drizzle-orm";
31130
+ import { and as and47, desc as desc26, eq as eq55, inArray as inArray30 } from "drizzle-orm";
31086
31131
 
31087
31132
  // ../api-routes/src/visibility-compare.ts
31088
31133
  var VISIBILITY_COMPARE_MIN_RUNS = 5;
@@ -31569,7 +31614,7 @@ async function visibilityStatsRoutes(app) {
31569
31614
  citationState: querySnapshots.citationState,
31570
31615
  answerMentioned: querySnapshots.answerMentioned,
31571
31616
  createdAt: querySnapshots.createdAt
31572
- }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31617
+ }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31573
31618
  const stats = computeVisibilityStats({ queries: projectQueries, snapshots, groupBy });
31574
31619
  const queryAttribution = buildQueryAttribution(projectQueries);
31575
31620
  let shareOfVoice;
@@ -31580,7 +31625,7 @@ async function visibilityStatsRoutes(app) {
31580
31625
  queryText: querySnapshots.queryText,
31581
31626
  answerMentioned: querySnapshots.answerMentioned,
31582
31627
  answerText: querySnapshots.answerText
31583
- }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31628
+ }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31584
31629
  const attributedSovSnapshots = sovSnapshots.filter((s) => resolveCurrentQuery(queryAttribution, s) !== void 0);
31585
31630
  const advanced = activeMeasurementPlan(app.db, project.id)?.plan.schemaVersion === 2;
31586
31631
  const queryClass = advanced || projectQueryClassifier(project) ? requestedQueryClass : "all";
@@ -31678,7 +31723,7 @@ function readVisibilityCompare(db, projectName, query) {
31678
31723
  answerText: querySnapshots.answerText,
31679
31724
  citedDomains: querySnapshots.citedDomains,
31680
31725
  location: querySnapshots.location
31681
- }).from(querySnapshots).where(inArray29(querySnapshots.runId, runIds)).all() : [];
31726
+ }).from(querySnapshots).where(inArray30(querySnapshots.runId, runIds)).all() : [];
31682
31727
  return {
31683
31728
  runCount: runIds.length,
31684
31729
  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 +31842,7 @@ function readVisibilityContinuity(db, projectName, months) {
31797
31842
  const trackedQueries = db.select({ id: queries.id, query: queries.query }).from(queries).where(eq55(queries.projectId, project.id)).all();
31798
31843
  const loadProjectMonth = (bounds) => {
31799
31844
  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() : [];
31845
+ 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
31846
  return { runCount: runIds.length, snapshots: rows.map(bare) };
31802
31847
  };
31803
31848
  const frames = [{ frame: "project", continuity: gate(trackedQueries, loadProjectMonth(fromBounds), loadProjectMonth(toBounds)) }];
@@ -32057,7 +32102,7 @@ async function resultsExportRoutes(app) {
32057
32102
  }
32058
32103
 
32059
32104
  // ../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";
32105
+ 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
32106
  var TOP_INSIGHT_LIMIT = 5;
32062
32107
  var SEARCH_HIT_HARD_LIMIT = 50;
32063
32108
  var SEARCH_SNIPPET_RADIUS = 80;
@@ -32358,7 +32403,7 @@ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
32358
32403
  rawResponse: querySnapshots.rawResponse,
32359
32404
  competitorOverlap: querySnapshots.competitorOverlap,
32360
32405
  citedDomains: querySnapshots.citedDomains
32361
- }).from(querySnapshots).where(inArray30(querySnapshots.runId, [...runIds])).all();
32406
+ }).from(querySnapshots).where(inArray31(querySnapshots.runId, [...runIds])).all();
32362
32407
  for (const row of rows) {
32363
32408
  const queryText = row.queryText?.trim() || null;
32364
32409
  let queryId;
@@ -32798,7 +32843,7 @@ import crypto29 from "crypto";
32798
32843
  import { and as and51, desc as desc29, eq as eq59 } from "drizzle-orm";
32799
32844
 
32800
32845
  // ../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";
32846
+ import { and as and50, eq as eq58, desc as desc28, gte as gte15, inArray as inArray32, lte as lte14 } from "drizzle-orm";
32802
32847
  var RECENT_RUNS_WINDOW = 5;
32803
32848
  function loadOrchestratorInput(db, project, locationFilter = void 0, gscWindowDays = DEFAULT_CONTENT_GSC_WINDOW_DAYS) {
32804
32849
  const projectId = project.id;
@@ -32938,7 +32983,7 @@ function listRecentAnswerVisibilityRunIds(db, projectId, limit, locationFilter)
32938
32983
  // Queued/running/failed/cancelled runs may have partial or no
32939
32984
  // snapshots; including them risks pointing latestRunId at a run with
32940
32985
  // no usable evidence.
32941
- inArray31(runs.status, [RunStatuses.completed, RunStatuses.partial]),
32986
+ inArray32(runs.status, [RunStatuses.completed, RunStatuses.partial]),
32942
32987
  // Probe runs are operator/agent test runs; they must not poison the
32943
32988
  // recent-runs window the content engine uses to recommend actions.
32944
32989
  notProbeRun()
@@ -33006,7 +33051,7 @@ function buildCandidateQueries(opts) {
33006
33051
  const queryRows2 = opts.db.select({ id: queries.id, text: queries.query }).from(queries).where(eq58(queries.projectId, opts.projectId)).all();
33007
33052
  const queryIdByText = new Map(queryRows2.map((r) => [r.text, r.id]));
33008
33053
  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));
33054
+ const snapshotRows = filterTrackedSnapshots(opts.db.select().from(querySnapshots).where(inArray32(querySnapshots.runId, opts.recentRunIds)).all()).filter((r) => candidateQueryIds.includes(r.queryId));
33010
33055
  const snapshotsByQuery = /* @__PURE__ */ new Map();
33011
33056
  for (const row of snapshotRows) {
33012
33057
  const list = snapshotsByQuery.get(row.queryId) ?? [];
@@ -45064,7 +45109,7 @@ async function gbpLocalPostsRoutes(app, options) {
45064
45109
  }
45065
45110
 
45066
45111
  // ../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";
45112
+ import { eq as eq66, and as and54, desc as desc31, sql as sql21, inArray as inArray33 } from "drizzle-orm";
45068
45113
 
45069
45114
  // ../api-routes/src/gsc-period-comparison.ts
45070
45115
  var DAY_MS2 = 864e5;
@@ -45937,39 +45982,7 @@ async function listProperties(accessToken) {
45937
45982
  headers: { "Authorization": `Bearer ${accessToken}` },
45938
45983
  signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
45939
45984
  });
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
- }
45985
+ if (!res.ok) throw await adminApiError(res, accessToken, "admin.account-summaries-failed", "Failed to list GA4 properties.");
45973
45986
  return await res.json();
45974
45987
  }, "GA4 accountSummaries"));
45975
45988
  for (const account of body.accountSummaries ?? []) {
@@ -45987,6 +46000,53 @@ async function listProperties(accessToken) {
45987
46000
  } while (pageToken);
45988
46001
  return properties;
45989
46002
  }
46003
+ async function adminApiError(res, accessToken, logAction, message) {
46004
+ const raw = await res.text().catch(() => "");
46005
+ let detail = "";
46006
+ let quotaReason = false;
46007
+ try {
46008
+ const parsed = JSON.parse(raw);
46009
+ if (parsed.error?.status === "SERVICE_DISABLED") {
46010
+ 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";
46011
+ } else if (parsed.error?.message) {
46012
+ detail = ` ${parsed.error.message}`;
46013
+ }
46014
+ quotaReason = (parsed.error?.errors ?? []).some(
46015
+ (e) => e.reason === "rateLimitExceeded" || e.reason === "quotaExceeded" || e.reason === "userRateLimitExceeded"
46016
+ ) || parsed.error?.status === "RESOURCE_EXHAUSTED";
46017
+ } catch {
46018
+ if (raw.length < 200) detail = ` ${raw}`;
46019
+ }
46020
+ const sanitized = detail.replace(new RegExp(escapeRegExp(accessToken), "g"), "***");
46021
+ const retryAfterSeconds2 = res.status === 429 || res.status >= 500 || res.status === 403 && quotaReason ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
46022
+ ga4Log("error", logAction, { httpStatus: res.status, retryAfterSeconds: retryAfterSeconds2, quotaReason });
46023
+ return new GA4ApiError(`${message}${sanitized}`, res.status === 403 && quotaReason ? 429 : res.status, retryAfterSeconds2);
46024
+ }
46025
+ async function fetchKeyEventNames(accessToken, propertyId) {
46026
+ validateAccessToken2(accessToken);
46027
+ validatePropertyId(propertyId);
46028
+ const names = /* @__PURE__ */ new Set();
46029
+ let pageToken;
46030
+ do {
46031
+ const url = new URL(`${GA4_ADMIN_API_BASE}/properties/${propertyId}/keyEvents`);
46032
+ url.searchParams.set("pageSize", "200");
46033
+ if (pageToken) url.searchParams.set("pageToken", pageToken);
46034
+ const body = await withGa4Limit(() => withGa4Retry(async () => {
46035
+ const res = await fetchWithContext(url, {
46036
+ headers: { "Authorization": `Bearer ${accessToken}` },
46037
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
46038
+ });
46039
+ if (!res.ok) throw await adminApiError(res, accessToken, "admin.key-events-failed", "Failed to list GA4 key events.");
46040
+ return await res.json();
46041
+ }, "GA4 keyEvents"));
46042
+ for (const keyEvent of body.keyEvents ?? []) {
46043
+ const name = keyEvent.eventName?.trim();
46044
+ if (name) names.add(name);
46045
+ }
46046
+ pageToken = body.nextPageToken;
46047
+ } while (pageToken);
46048
+ return [...names].sort();
46049
+ }
45990
46050
  async function fetchAggregateSummary(accessToken, propertyId, days) {
45991
46051
  validateAccessToken2(accessToken);
45992
46052
  validatePropertyId(propertyId);
@@ -48304,10 +48364,10 @@ async function googleRoutes(app, opts) {
48304
48364
  locationProfiles: []
48305
48365
  });
48306
48366
  }
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();
48367
+ const metricRows = app.db.select().from(gbpDailyMetrics).where(and54(eq66(gbpDailyMetrics.projectId, project.id), inArray33(gbpDailyMetrics.locationName, locationNames))).all();
48368
+ const keywordRows = app.db.select().from(gbpKeywordImpressions).where(and54(eq66(gbpKeywordImpressions.projectId, project.id), inArray33(gbpKeywordImpressions.locationName, locationNames))).all();
48369
+ const placeActionRows = app.db.select().from(gbpPlaceActions).where(and54(eq66(gbpPlaceActions.projectId, project.id), inArray33(gbpPlaceActions.locationName, locationNames))).all();
48370
+ const lodgingRows = app.db.select().from(gbpLodgingSnapshots).where(and54(eq66(gbpLodgingSnapshots.projectId, project.id), inArray33(gbpLodgingSnapshots.locationName, locationNames))).orderBy(desc31(gbpLodgingSnapshots.syncedAt)).all();
48311
48371
  const latestLodgingByLocation = /* @__PURE__ */ new Map();
48312
48372
  for (const row of lodgingRows) {
48313
48373
  if (!latestLodgingByLocation.has(row.locationName)) {
@@ -48321,7 +48381,7 @@ async function googleRoutes(app, opts) {
48321
48381
  regularHours: gbpLocations.regularHours,
48322
48382
  primaryPhone: gbpLocations.primaryPhone,
48323
48383
  openStatus: gbpLocations.openStatus
48324
- }).from(gbpLocations).where(and54(eq66(gbpLocations.projectId, project.id), inArray32(gbpLocations.locationName, locationNames))).all();
48384
+ }).from(gbpLocations).where(and54(eq66(gbpLocations.projectId, project.id), inArray33(gbpLocations.locationName, locationNames))).all();
48325
48385
  return buildGbpSummary({
48326
48386
  locationName,
48327
48387
  locationCount: locationNames.length,
@@ -49991,7 +50051,7 @@ async function googleMarketingRoutes(app, opts) {
49991
50051
 
49992
50052
  // ../api-routes/src/ads.ts
49993
50053
  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";
50054
+ 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
50055
 
49996
50056
  // ../api-routes/src/ads-activation.ts
49997
50057
  import crypto40 from "crypto";
@@ -53089,7 +53149,7 @@ function semanticallyMatchesMaterializedEntity(app, input) {
53089
53149
  eq69(adsOperations.projectId, input.projectId),
53090
53150
  eq69(adsOperations.entityType, input.entityType),
53091
53151
  eq69(adsOperations.entityId, input.entityId),
53092
- inArray33(adsOperations.kind, [createKind, updateKind])
53152
+ inArray34(adsOperations.kind, [createKind, updateKind])
53093
53153
  )).orderBy(asc15(adsOperations.createdAt), asc15(adsOperations.id)).all();
53094
53154
  if (rows.some((row) => row.state === AdsOperationStates.pending || row.state === AdsOperationStates.reconciling || row.state === AdsOperationStates.unknown || row.kind === updateKind && row.state === AdsOperationStates.succeeded)) {
53095
53155
  return false;
@@ -53537,7 +53597,7 @@ function quarantineExhaustedReconciliation(app, row, now, policy, context) {
53537
53597
  eq69(adsOperations.id, row.id),
53538
53598
  eq69(adsOperations.projectId, row.projectId),
53539
53599
  gte16(adsOperations.reconcileAttempts, policy.maxAttempts),
53540
- inArray33(adsOperations.state, [
53600
+ inArray34(adsOperations.state, [
53541
53601
  AdsOperationStates.pending,
53542
53602
  AdsOperationStates.unknown,
53543
53603
  AdsOperationStates.reconciling
@@ -53781,7 +53841,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
53781
53841
  eq69(adsOperations.reconcileStrategy, AdsReconcileStrategies.create_fingerprint),
53782
53842
  or16(
53783
53843
  isNotNull7(adsOperations.entityId),
53784
- inArray33(adsOperations.state, [
53844
+ inArray34(adsOperations.state, [
53785
53845
  AdsOperationStates.pending,
53786
53846
  AdsOperationStates.reconciling
53787
53847
  ])
@@ -53832,7 +53892,7 @@ async function sweepAdsOperationsOnce(app, opts, config) {
53832
53892
  const eligibleProjectIds = [...credentialsByProjectId.keys()];
53833
53893
  if (eligibleProjectIds.length === 0) return;
53834
53894
  const rows = app.db.select().from(adsOperations).where(and57(
53835
- inArray33(adsOperations.projectId, eligibleProjectIds),
53895
+ inArray34(adsOperations.projectId, eligibleProjectIds),
53836
53896
  staleReconcileWhere
53837
53897
  )).orderBy(asc15(adsOperations.updatedAt)).limit(config.batchSize).all();
53838
53898
  for (const row of rows) {
@@ -54158,7 +54218,7 @@ async function adsRoutes(app, opts) {
54158
54218
  const cursor = parsed.data.cursor ? decodeAdsOperationCursor(parsed.data.cursor, project.id, parsed.data.state) : void 0;
54159
54219
  const rows = app.db.select().from(adsOperations).where(and57(
54160
54220
  eq69(adsOperations.projectId, project.id),
54161
- inArray33(adsOperations.state, parsed.data.state),
54221
+ inArray34(adsOperations.state, parsed.data.state),
54162
54222
  cursor ? or16(
54163
54223
  gt6(adsOperations.createdAt, cursor.createdAt),
54164
54224
  and57(
@@ -54592,7 +54652,7 @@ async function adsRoutes(app, opts) {
54592
54652
  const inFlight2 = app.db.select({ id: runs.id, status: runs.status }).from(runs).where(and57(
54593
54653
  eq69(runs.projectId, project.id),
54594
54654
  eq69(runs.kind, RunKinds["ads-sync"]),
54595
- inArray33(runs.status, [RunStatuses.queued, RunStatuses.running])
54655
+ inArray34(runs.status, [RunStatuses.queued, RunStatuses.running])
54596
54656
  )).get();
54597
54657
  if (inFlight2) {
54598
54658
  const existing = { runId: inFlight2.id, status: inFlight2.status };
@@ -56623,6 +56683,7 @@ function persistLeadMeasurement(db, input) {
56623
56683
  leadError: null,
56624
56684
  leadSyncedAt: input.syncedAt,
56625
56685
  leadAttributionScope: input.report.attributionScope,
56686
+ leadEventNames: input.eventNames,
56626
56687
  updatedAt: input.syncedAt
56627
56688
  }).onConflictDoUpdate({
56628
56689
  target: gaMeasurementSyncStates.projectId,
@@ -56631,6 +56692,7 @@ function persistLeadMeasurement(db, input) {
56631
56692
  leadError: null,
56632
56693
  leadSyncedAt: input.syncedAt,
56633
56694
  leadAttributionScope: input.report.attributionScope,
56695
+ leadEventNames: input.eventNames,
56634
56696
  updatedAt: input.syncedAt
56635
56697
  }
56636
56698
  }).run();
@@ -56639,12 +56701,23 @@ function persistLeadMeasurement(db, input) {
56639
56701
  function resetLeadMeasurement(db, projectId, updatedAt) {
56640
56702
  db.transaction((tx) => {
56641
56703
  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({
56704
+ tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }).onConflictDoUpdate({
56643
56705
  target: gaMeasurementSyncStates.projectId,
56644
- set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, updatedAt }
56706
+ set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }
56645
56707
  }).run();
56646
56708
  });
56647
56709
  }
56710
+ function persistKeyEventRead(db, projectId, read, updatedAt) {
56711
+ if (read.status === "disabled") return;
56712
+ const set = read.status === "read" ? { keyEventNames: read.names, keyEventsError: null, updatedAt } : { keyEventsError: read.error ?? "Key event read failed", updatedAt };
56713
+ db.insert(gaMeasurementSyncStates).values({ projectId, ...set }).onConflictDoUpdate({
56714
+ target: gaMeasurementSyncStates.projectId,
56715
+ set
56716
+ }).run();
56717
+ }
56718
+ function sameEventNames(left, right) {
56719
+ return left.length === right.length && left.every((name, index) => name === right[index]);
56720
+ }
56648
56721
  function persistMeasurementError(db, projectId, component, message, updatedAt) {
56649
56722
  if (component === "acquisition") {
56650
56723
  db.insert(gaMeasurementSyncStates).values({
@@ -56994,8 +57067,9 @@ async function ga4Routes(app, opts) {
56994
57067
  const syncSearchLanding = !only || only === "search-landing";
56995
57068
  const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq73(gaMeasurementSyncStates.projectId, project.id)).get();
56996
57069
  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;
57070
+ let leadDays = GA4_MAX_SYNC_DAYS;
57071
+ let leadEventNames = [];
57072
+ let keyEventRead = { status: "disabled", names: [] };
56999
57073
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
57000
57074
  const runId = crypto47.randomUUID();
57001
57075
  app.db.insert(runs).values({
@@ -57013,6 +57087,18 @@ async function ga4Routes(app, opts) {
57013
57087
  try {
57014
57088
  const { accessToken, propertyId } = await resolveGa4AccessToken(opts, project.name, project.canonicalDomain);
57015
57089
  credentialsResolved = true;
57090
+ if (countsKeyEvents(project.measurement)) {
57091
+ try {
57092
+ keyEventRead = { status: "read", names: await fetchKeyEventNames(accessToken, propertyId) };
57093
+ } catch (error) {
57094
+ keyEventRead = { status: "error", names: measurementState?.keyEventNames ?? [], error: describeError(error) };
57095
+ }
57096
+ persistKeyEventRead(app.db, project.id, keyEventRead, (/* @__PURE__ */ new Date()).toISOString());
57097
+ }
57098
+ leadEventNames = countedLeadEventNames(project.measurement.leadEventNames, keyEventRead.names);
57099
+ const previousLeadEvents = measurementState?.leadEventNames ?? null;
57100
+ const leadEventsChanged = previousLeadEvents === null || !sameEventNames(previousLeadEvents, leadEventNames);
57101
+ leadDays = measurementState?.leadSyncedAt != null && !leadEventsChanged ? days : GA4_MAX_SYNC_DAYS;
57016
57102
  let rows = [];
57017
57103
  let aiReferrals = [];
57018
57104
  let socialReferrals = [];
@@ -57276,13 +57362,16 @@ async function ga4Routes(app, opts) {
57276
57362
  projectId: project.id,
57277
57363
  runId,
57278
57364
  syncedAt: measurementNow,
57279
- report: leadsSettled.value
57365
+ report: leadsSettled.value,
57366
+ eventNames: leadEventNames
57280
57367
  });
57281
57368
  return {
57282
57369
  status: "ready",
57283
57370
  days: leadDays,
57284
57371
  rowCount: leadsSettled.value.rows.length,
57285
- attributionScope: leadsSettled.value.attributionScope
57372
+ attributionScope: leadsSettled.value.attributionScope,
57373
+ eventNames: leadEventNames,
57374
+ keyEvents: keyEventRead
57286
57375
  };
57287
57376
  } catch (error) {
57288
57377
  return recordMeasurementFailure("leads", error);
@@ -60441,7 +60530,7 @@ async function backlinksRoutes(app, opts) {
60441
60530
  import crypto52 from "crypto";
60442
60531
  import { isIP } from "net";
60443
60532
  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";
60533
+ 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
60534
 
60446
60535
  // ../api-routes/src/traffic-limits.ts
60447
60536
  var VERCEL_MAX_SYNC_WINDOW_MS = 24 * 60 * 6e4;
@@ -62187,6 +62276,9 @@ var CLOUDFLARE_QUEUE_RETENTION_READ_TIMEOUT_MS = 1e4;
62187
62276
  var CLOUDFLARE_QUEUE_VISIBILITY_TIMEOUT_MS = 5 * 6e4;
62188
62277
  var CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS = 5 * 6e4;
62189
62278
  var WORDPRESS_SYNC_LEASE_TTL_MS = 5 * 6e4;
62279
+ var PULL_SYNC_LEASE_TTL_MS = 10 * 6e4;
62280
+ var TRAFFIC_BACKFILL_LEASE_TTL_MS = 5 * 6e4;
62281
+ var TRAFFIC_BACKFILL_LEASE_RENEW_MS = 6e4;
62190
62282
  var DEFAULT_CLOUDFLARE_INGEST_RATE_LIMIT_MAX = 6e3;
62191
62283
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
62192
62284
  function sha256Hex3(value) {
@@ -62533,6 +62625,9 @@ function isAuthoritativeTrafficSource(db, source) {
62533
62625
  function trafficConnectStatus(tx, projectId, existingSource) {
62534
62626
  return existingSource?.status === TrafficSourceStatuses.paused || hasConnectedTrafficSourceSibling(tx, projectId, existingSource?.id ?? "") ? TrafficSourceStatuses.paused : TrafficSourceStatuses.connected;
62535
62627
  }
62628
+ function isSameBackfillSource(current, started, leaseOwner) {
62629
+ return current.syncLeaseOwner === leaseOwner && isSameTrafficSourceGeneration(current, started);
62630
+ }
62536
62631
  function isSameTrafficSourceGeneration(current, started) {
62537
62632
  return isDeepStrictEqual(current.configJson, started.configJson) && current.updatedAt === started.updatedAt && current.lastSyncedAt === started.lastSyncedAt && current.lastCursor === started.lastCursor && current.wordpressPendingUntil === started.wordpressPendingUntil;
62538
62633
  }
@@ -62586,6 +62681,42 @@ function vercelRetentionClampError(requestedStartMs, effectiveStartMs) {
62586
62681
  return new VercelRetentionClampError(requestedStartMs, effectiveStartMs);
62587
62682
  }
62588
62683
  async function runBackfillTask(options) {
62684
+ const { app, sourceRow: sourceRow2, leaseOwner } = options;
62685
+ const stopped = new AbortController();
62686
+ const releaseLease = () => {
62687
+ try {
62688
+ releaseTrafficSyncLease({ db: app.db, sourceId: sourceRow2.id, owner: leaseOwner, now: (/* @__PURE__ */ new Date()).toISOString() });
62689
+ } catch {
62690
+ }
62691
+ };
62692
+ const renewLease2 = setInterval(() => {
62693
+ try {
62694
+ const latest = app.db.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
62695
+ if (!latest || !isAuthoritativeTrafficSource(app.db, latest) || !isSameTrafficSourceGeneration(latest, sourceRow2)) {
62696
+ clearInterval(renewLease2);
62697
+ stopped.abort();
62698
+ releaseLease();
62699
+ return;
62700
+ }
62701
+ tryClaimTrafficSyncLease({
62702
+ db: app.db,
62703
+ sourceId: sourceRow2.id,
62704
+ owner: leaseOwner,
62705
+ now: (/* @__PURE__ */ new Date()).toISOString(),
62706
+ ttlMs: TRAFFIC_BACKFILL_LEASE_TTL_MS
62707
+ });
62708
+ } catch {
62709
+ }
62710
+ }, options.leaseRenewMs);
62711
+ renewLease2.unref?.();
62712
+ try {
62713
+ await runBackfillTaskHoldingLease(options, stopped.signal);
62714
+ } finally {
62715
+ clearInterval(renewLease2);
62716
+ releaseLease();
62717
+ }
62718
+ }
62719
+ async function runBackfillTaskHoldingLease(options, signal) {
62589
62720
  const {
62590
62721
  app,
62591
62722
  runId,
@@ -62595,7 +62726,8 @@ async function runBackfillTask(options) {
62595
62726
  windowEnd,
62596
62727
  journeyKeySecret,
62597
62728
  pullForBackfill,
62598
- pullErrorPrefix
62729
+ pullErrorPrefix,
62730
+ leaseOwner
62599
62731
  } = options;
62600
62732
  const backfillStartedAt = Date.now();
62601
62733
  const target = { feature: "server_traffic", operation: "backfill", attribution: currentOutcomeAttribution() };
@@ -62609,7 +62741,7 @@ async function runBackfillTask(options) {
62609
62741
  app.db.transaction((tx) => {
62610
62742
  tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq80(runs.id, runId)).run();
62611
62743
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
62612
- if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
62744
+ if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
62613
62745
  tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq80(trafficSources.id, sourceRow2.id)).run();
62614
62746
  }
62615
62747
  });
@@ -62623,8 +62755,13 @@ async function runBackfillTask(options) {
62623
62755
  }
62624
62756
  let allEvents;
62625
62757
  try {
62626
- allEvents = await pullForBackfill();
62758
+ allEvents = await pullForBackfill(signal);
62627
62759
  } catch (e) {
62760
+ if (signal.aborted) {
62761
+ markFailed("Backfill stopped: the traffic source was reset, reconnected or deactivated while it ran", true);
62762
+ reportBackfill({ status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED });
62763
+ return;
62764
+ }
62628
62765
  markFailed(
62629
62766
  `${pullErrorPrefix}: ${describeError(e)}`,
62630
62767
  e instanceof VercelRetentionClampError
@@ -62638,7 +62775,7 @@ async function runBackfillTask(options) {
62638
62775
  try {
62639
62776
  const completed = app.db.transaction((tx) => {
62640
62777
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
62641
- const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameTrafficSourceGeneration(latestSource, sourceRow2);
62778
+ const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner);
62642
62779
  if (stillAuthoritative) {
62643
62780
  enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
62644
62781
  }
@@ -62681,7 +62818,7 @@ async function runBackfillTask(options) {
62681
62818
  }).where(eq80(runs.id, runId)).run();
62682
62819
  return "wordpress-backfill-unsupported";
62683
62820
  }
62684
- if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
62821
+ if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
62685
62822
  tx.update(runs).set({
62686
62823
  status: RunStatuses.failed,
62687
62824
  error: "Traffic source was deactivated or reconfigured during backfill",
@@ -64205,15 +64342,19 @@ async function trafficRoutes(app, opts) {
64205
64342
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
64206
64343
  throw validationError("Traffic source must be active before it can sync");
64207
64344
  }
64208
- const wordpressLeaseOwner = sourceRow2.sourceType === TrafficSourceTypes.wordpress ? crypto52.randomUUID() : void 0;
64209
- if (wordpressLeaseOwner && !tryClaimTrafficSyncLease({
64345
+ const isWordpressSource = sourceRow2.sourceType === TrafficSourceTypes.wordpress;
64346
+ const syncLeaseOwner = crypto52.randomUUID();
64347
+ if (!tryClaimTrafficSyncLease({
64210
64348
  db: app.db,
64211
64349
  sourceId: sourceRow2.id,
64212
- owner: wordpressLeaseOwner,
64350
+ owner: syncLeaseOwner,
64213
64351
  now: (/* @__PURE__ */ new Date()).toISOString(),
64214
- ttlMs: WORDPRESS_SYNC_LEASE_TTL_MS
64352
+ ttlMs: isWordpressSource ? WORDPRESS_SYNC_LEASE_TTL_MS : PULL_SYNC_LEASE_TTL_MS
64215
64353
  })) {
64216
- throw operationInProgress("WordPress source sync is already in progress", { sourceId: sourceRow2.id });
64354
+ throw operationInProgress(
64355
+ isWordpressSource ? "WordPress source sync is already in progress" : "A sync or backfill is already running for this traffic source",
64356
+ { sourceId: sourceRow2.id }
64357
+ );
64217
64358
  }
64218
64359
  try {
64219
64360
  const syncWindowEnd = /* @__PURE__ */ new Date();
@@ -64236,7 +64377,7 @@ async function trafficRoutes(app, opts) {
64236
64377
  app.db.transaction((tx) => {
64237
64378
  tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq80(runs.id, runId)).run();
64238
64379
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
64239
- if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && (wordpressLeaseOwner === void 0 || latestSource.syncLeaseOwner === wordpressLeaseOwner)) {
64380
+ if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && latestSource.syncLeaseOwner === syncLeaseOwner) {
64240
64381
  tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq80(trafficSources.id, sourceRow2.id)).run();
64241
64382
  }
64242
64383
  });
@@ -64257,11 +64398,11 @@ async function trafficRoutes(app, opts) {
64257
64398
  }
64258
64399
  };
64259
64400
  const renewWordpressLease = () => {
64260
- if (!wordpressLeaseOwner) return;
64401
+ if (!isWordpressSource) return;
64261
64402
  if (!tryClaimTrafficSyncLease({
64262
64403
  db: app.db,
64263
64404
  sourceId: sourceRow2.id,
64264
- owner: wordpressLeaseOwner,
64405
+ owner: syncLeaseOwner,
64265
64406
  now: (/* @__PURE__ */ new Date()).toISOString(),
64266
64407
  ttlMs: WORDPRESS_SYNC_LEASE_TTL_MS
64267
64408
  })) {
@@ -64366,7 +64507,7 @@ async function trafficRoutes(app, opts) {
64366
64507
  const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
64367
64508
  const reservedSource = app.db.transaction((tx) => {
64368
64509
  const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
64369
- if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== wordpressLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
64510
+ if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== syncLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
64370
64511
  tx.update(trafficSources).set({
64371
64512
  lastSyncedAt: windowStartIso2,
64372
64513
  wordpressPendingUntil: windowEndIso2,
@@ -64550,7 +64691,7 @@ async function trafficRoutes(app, opts) {
64550
64691
  const commitOutcome = app.db.transaction((tx) => {
64551
64692
  const latestRow = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
64552
64693
  const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq80(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
64553
- if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || wordpressLeaseOwner !== void 0 && latestRow.syncLeaseOwner !== wordpressLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
64694
+ if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || latestRow.syncLeaseOwner !== syncLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
64554
64695
  const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
64555
64696
  tx.update(runs).set({
64556
64697
  status: RunStatuses.failed,
@@ -64880,17 +65021,15 @@ async function trafficRoutes(app, opts) {
64880
65021
  };
64881
65022
  return response;
64882
65023
  } finally {
64883
- if (wordpressLeaseOwner) {
64884
- try {
64885
- releaseTrafficSyncLease({
64886
- db: app.db,
64887
- sourceId: sourceRow2.id,
64888
- owner: wordpressLeaseOwner,
64889
- now: (/* @__PURE__ */ new Date()).toISOString()
64890
- });
64891
- } catch (error) {
64892
- request.log.warn({ sourceId: sourceRow2.id, error: describeError(error) }, "Failed to release WordPress sync lease");
64893
- }
65024
+ try {
65025
+ releaseTrafficSyncLease({
65026
+ db: app.db,
65027
+ sourceId: sourceRow2.id,
65028
+ owner: syncLeaseOwner,
65029
+ now: (/* @__PURE__ */ new Date()).toISOString()
65030
+ });
65031
+ } catch (error) {
65032
+ request.log.warn({ sourceId: sourceRow2.id, error: describeError(error) }, "Failed to release traffic sync lease");
64894
65033
  }
64895
65034
  }
64896
65035
  });
@@ -64976,11 +65115,12 @@ async function trafficRoutes(app, opts) {
64976
65115
  const vercelTeamId = config.teamId ?? credential.teamId;
64977
65116
  const vercelEnvironment = config.environment ?? credential.environment;
64978
65117
  pullErrorPrefix = "Vercel pull failed";
64979
- pullForBackfill = async () => {
65118
+ pullForBackfill = async (signal) => {
64980
65119
  const collected = [];
64981
65120
  const seenEventIds = /* @__PURE__ */ new Set();
64982
65121
  const backfillEndMs = windowEnd.getTime();
64983
65122
  for (let chunkStartMs = windowStart.getTime(); chunkStartMs < backfillEndMs; chunkStartMs += VERCEL_BACKFILL_CHUNK_MS) {
65123
+ signal.throwIfAborted();
64984
65124
  const chunkEndMs = Math.min(chunkStartMs + VERCEL_BACKFILL_CHUNK_MS, backfillEndMs);
64985
65125
  const drained = await drainVercelTrafficEvents({
64986
65126
  pull: pullVercelEvents,
@@ -65008,25 +65148,42 @@ async function trafficRoutes(app, opts) {
65008
65148
  }
65009
65149
  const startedAt = windowEnd.toISOString();
65010
65150
  const runId = crypto52.randomUUID();
65011
- app.db.transaction((tx) => {
65012
- const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
65013
- if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
65014
- throw validationError("Traffic source must remain active and unchanged before it can backfill");
65015
- }
65016
- tx.insert(runs).values({
65017
- id: runId,
65018
- projectId: project.id,
65019
- kind: RunKinds["traffic-sync"],
65020
- status: RunStatuses.running,
65021
- trigger: RunTriggers.backfill,
65022
- sourceId: sourceRow2.id,
65023
- startedAt,
65024
- createdAt: startedAt
65025
- }).run();
65026
- });
65151
+ const leaseOwner = crypto52.randomUUID();
65152
+ if (!tryClaimTrafficSyncLease({
65153
+ db: app.db,
65154
+ sourceId: sourceRow2.id,
65155
+ owner: leaseOwner,
65156
+ now: (/* @__PURE__ */ new Date()).toISOString(),
65157
+ ttlMs: TRAFFIC_BACKFILL_LEASE_TTL_MS
65158
+ })) {
65159
+ throw operationInProgress("A sync or backfill is already running for this traffic source; retry when it finishes", { sourceId: sourceRow2.id });
65160
+ }
65161
+ try {
65162
+ app.db.transaction((tx) => {
65163
+ const latestSource = tx.select().from(trafficSources).where(eq80(trafficSources.id, sourceRow2.id)).get();
65164
+ if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
65165
+ throw validationError("Traffic source must remain active and unchanged before it can backfill");
65166
+ }
65167
+ tx.insert(runs).values({
65168
+ id: runId,
65169
+ projectId: project.id,
65170
+ kind: RunKinds["traffic-sync"],
65171
+ status: RunStatuses.running,
65172
+ trigger: RunTriggers.backfill,
65173
+ sourceId: sourceRow2.id,
65174
+ startedAt,
65175
+ createdAt: startedAt
65176
+ }).run();
65177
+ });
65178
+ } catch (error) {
65179
+ releaseTrafficSyncLease({ db: app.db, sourceId: sourceRow2.id, owner: leaseOwner, now: (/* @__PURE__ */ new Date()).toISOString() });
65180
+ throw error;
65181
+ }
65027
65182
  void runBackfillTask({
65028
65183
  app,
65029
65184
  runId,
65185
+ leaseOwner,
65186
+ leaseRenewMs: opts.trafficBackfillLeaseRenewMs ?? TRAFFIC_BACKFILL_LEASE_RENEW_MS,
65030
65187
  project,
65031
65188
  sourceRow: sourceRow2,
65032
65189
  journeyKeySecret: opts.journeyKeySecret,
@@ -65322,7 +65479,7 @@ async function trafficRoutes(app, opts) {
65322
65479
  const listed = useContentList ? contentPaths : infraPaths;
65323
65480
  const IN_LIST_CAP = 2e4;
65324
65481
  const listFitsInSql = listed.length <= IN_LIST_CAP;
65325
- 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`;
65482
+ 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`;
65326
65483
  if (listFitsInSql) {
65327
65484
  const crawlerSeries = app.db.select({
65328
65485
  bucket: crawlerSeriesBucket,
@@ -65711,7 +65868,7 @@ var reportSweepsCheck = {
65711
65868
  };
65712
65869
 
65713
65870
  // ../api-routes/src/doctor/checks/report-models.ts
65714
- import { and as and71, eq as eq82, gte as gte21, inArray as inArray35, lte as lte22 } from "drizzle-orm";
65871
+ import { and as and71, eq as eq82, gte as gte21, inArray as inArray36, lte as lte22 } from "drizzle-orm";
65715
65872
  var reportModelsCheck = {
65716
65873
  id: "report.models",
65717
65874
  category: CheckCategories.providers,
@@ -65726,7 +65883,7 @@ var reportModelsCheck = {
65726
65883
  const months = reportMonths.flatMap((month) => {
65727
65884
  const bounds = calendarMonthBounds(month);
65728
65885
  const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
65729
- 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();
65886
+ 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();
65730
65887
  return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
65731
65888
  month,
65732
65889
  previousMonth: previous,
@@ -66303,7 +66460,7 @@ var BACKLINKS_CHECKS = [
66303
66460
  ];
66304
66461
 
66305
66462
  // ../api-routes/src/doctor/checks/bing-auth.ts
66306
- import { and as and73, desc as desc40, eq as eq85, inArray as inArray36 } from "drizzle-orm";
66463
+ import { and as and73, desc as desc40, eq as eq85, inArray as inArray37 } from "drizzle-orm";
66307
66464
  var BING_CONNECTION_ENTITY = "bing_connection";
66308
66465
  var BING_CONNECTED_ACTION = "bing.connected";
66309
66466
  var BING_DISCONNECTED_ACTION = "bing.disconnected";
@@ -66314,7 +66471,7 @@ function lostBingConnection(ctx, project) {
66314
66471
  const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and73(
66315
66472
  eq85(auditLog.entityType, BING_CONNECTION_ENTITY),
66316
66473
  eq85(auditLog.entityId, project.canonicalDomain),
66317
- inArray36(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
66474
+ inArray37(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
66318
66475
  )).orderBy(desc40(auditLog.createdAt)).limit(1).get();
66319
66476
  return lastChange?.action !== BING_DISCONNECTED_ACTION;
66320
66477
  }
@@ -66860,7 +67017,7 @@ var ga4ConnectionCheck = {
66860
67017
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
66861
67018
 
66862
67019
  // ../api-routes/src/doctor/checks/data-freshness.ts
66863
- import { and as and74, desc as desc41, eq as eq88, inArray as inArray37 } from "drizzle-orm";
67020
+ import { and as and74, desc as desc41, eq as eq88, inArray as inArray38 } from "drizzle-orm";
66864
67021
  var GA_DATA_AGING_DAYS = 3;
66865
67022
  var GA_DATA_STALE_DAYS = 5;
66866
67023
  var GSC_DATA_AGING_DAYS = 5;
@@ -66889,7 +67046,7 @@ function skipped(code, summary3) {
66889
67046
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
66890
67047
  }
66891
67048
  function lastSyncAgeDays(ctx, kinds) {
66892
- 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();
67049
+ 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();
66893
67050
  if (!row?.createdAt) return null;
66894
67051
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS4);
66895
67052
  return Number.isFinite(age) ? age : null;
@@ -67384,7 +67541,7 @@ async function probeApiHost(host, options = {}) {
67384
67541
  }
67385
67542
 
67386
67543
  // ../api-routes/src/doctor/checks/integration-sync.ts
67387
- 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";
67544
+ 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";
67388
67545
  var SYNC_FAILURE_WINDOW = 3;
67389
67546
  var SUMMARY_ERROR_LIMIT = 300;
67390
67547
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -67477,8 +67634,8 @@ function readSyncRunState(db, projectId, kind, options = {}) {
67477
67634
  ...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
67478
67635
  );
67479
67636
  const graded = options.since ? and75(scope, gte22(runs.createdAt, options.since)) : scope;
67480
- 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();
67481
- 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();
67637
+ 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();
67638
+ 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();
67482
67639
  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();
67483
67640
  const failuresSinceSuccess = Number(failureCount?.count ?? 0);
67484
67641
  const latest = recent[0];
@@ -68039,7 +68196,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
68039
68196
  );
68040
68197
 
68041
68198
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
68042
- import { and as and77, desc as desc43, eq as eq93, gte as gte23, inArray as inArray39, sql as sql31 } from "drizzle-orm";
68199
+ import { and as and77, desc as desc43, eq as eq93, gte as gte23, inArray as inArray40, sql as sql31 } from "drizzle-orm";
68043
68200
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
68044
68201
  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`.";
68045
68202
  var reviewsAccessCheck = {
@@ -68162,7 +68319,7 @@ var reviewDeliveryCheck = {
68162
68319
  payload: reviewWebhookDeliveries.payload
68163
68320
  }).from(reviewWebhookDeliveries).where(and77(
68164
68321
  eq93(reviewWebhookDeliveries.projectId, ctx.project.id),
68165
- inArray39(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68322
+ inArray40(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
68166
68323
  gte23(lastTouched, since)
68167
68324
  )).orderBy(desc43(lastTouched)).all();
68168
68325
  if (rows.length === 0) {
@@ -68747,7 +68904,7 @@ var RUNTIME_STATE_CHECKS = [
68747
68904
  ];
68748
68905
 
68749
68906
  // ../api-routes/src/doctor/checks/traffic-source.ts
68750
- 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";
68907
+ 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";
68751
68908
  var RECENT_DATA_WARN_DAYS = 7;
68752
68909
  var RECENT_DATA_FAIL_DAYS = 30;
68753
68910
  function isCloudflareDirectPush(source) {
@@ -68927,7 +69084,7 @@ var recentDataCheck = {
68927
69084
  ctx.db.select({ total: sql32`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
68928
69085
  and78(
68929
69086
  eq94(crawlerEventsHourly.projectId, ctx.project.id),
68930
- inArray40(crawlerEventsHourly.sourceId, activeSourceIds),
69087
+ inArray41(crawlerEventsHourly.sourceId, activeSourceIds),
68931
69088
  gte24(crawlerEventsHourly.tsHour, warnCutoff)
68932
69089
  )
68933
69090
  ).get()?.total ?? 0
@@ -68936,7 +69093,7 @@ var recentDataCheck = {
68936
69093
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
68937
69094
  and78(
68938
69095
  eq94(aiReferralEventsHourly.projectId, ctx.project.id),
68939
- inArray40(aiReferralEventsHourly.sourceId, activeSourceIds),
69096
+ inArray41(aiReferralEventsHourly.sourceId, activeSourceIds),
68940
69097
  gte24(aiReferralEventsHourly.tsHour, warnCutoff)
68941
69098
  )
68942
69099
  ).get()?.total ?? 0
@@ -68945,7 +69102,7 @@ var recentDataCheck = {
68945
69102
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
68946
69103
  and78(
68947
69104
  eq94(aiUserFetchEventsHourly.projectId, ctx.project.id),
68948
- inArray40(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69105
+ inArray41(aiUserFetchEventsHourly.sourceId, activeSourceIds),
68949
69106
  gte24(aiUserFetchEventsHourly.tsHour, warnCutoff)
68950
69107
  )
68951
69108
  ).get()?.total ?? 0
@@ -68971,7 +69128,7 @@ var recentDataCheck = {
68971
69128
  ctx.db.select({ total: sql32`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
68972
69129
  and78(
68973
69130
  eq94(crawlerEventsHourly.projectId, ctx.project.id),
68974
- inArray40(crawlerEventsHourly.sourceId, activeSourceIds),
69131
+ inArray41(crawlerEventsHourly.sourceId, activeSourceIds),
68975
69132
  gte24(crawlerEventsHourly.tsHour, failCutoff)
68976
69133
  )
68977
69134
  ).get()?.total ?? 0
@@ -68980,7 +69137,7 @@ var recentDataCheck = {
68980
69137
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
68981
69138
  and78(
68982
69139
  eq94(aiReferralEventsHourly.projectId, ctx.project.id),
68983
- inArray40(aiReferralEventsHourly.sourceId, activeSourceIds),
69140
+ inArray41(aiReferralEventsHourly.sourceId, activeSourceIds),
68984
69141
  gte24(aiReferralEventsHourly.tsHour, failCutoff)
68985
69142
  )
68986
69143
  ).get()?.total ?? 0
@@ -68989,7 +69146,7 @@ var recentDataCheck = {
68989
69146
  ctx.db.select({ total: sql32`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
68990
69147
  and78(
68991
69148
  eq94(aiUserFetchEventsHourly.projectId, ctx.project.id),
68992
- inArray40(aiUserFetchEventsHourly.sourceId, activeSourceIds),
69149
+ inArray41(aiUserFetchEventsHourly.sourceId, activeSourceIds),
68993
69150
  gte24(aiUserFetchEventsHourly.tsHour, failCutoff)
68994
69151
  )
68995
69152
  ).get()?.total ?? 0
@@ -70245,7 +70402,7 @@ async function statusRoutes(app, opts) {
70245
70402
 
70246
70403
  // ../api-routes/src/discovery/routes.ts
70247
70404
  import crypto54 from "crypto";
70248
- 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";
70405
+ 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";
70249
70406
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
70250
70407
  async function discoveryRoutes(app, opts) {
70251
70408
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -70300,7 +70457,7 @@ async function discoveryRoutes(app, opts) {
70300
70457
  // must never reuse another set's session. Null (= Gemini-only
70301
70458
  // default, including explicit ['gemini']) matches legacy rows.
70302
70459
  seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq96(discoverySessions.seedProviders, seedProviders),
70303
- inArray41(discoverySessions.status, [
70460
+ inArray42(discoverySessions.status, [
70304
70461
  DiscoverySessionStatuses.queued,
70305
70462
  DiscoverySessionStatuses.seeding,
70306
70463
  DiscoverySessionStatuses.probing
@@ -71008,7 +71165,7 @@ function dedupeStrings(input) {
71008
71165
 
71009
71166
  // ../api-routes/src/technical-aeo.ts
71010
71167
  import crypto56 from "crypto";
71011
- 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";
71168
+ 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";
71012
71169
  import { alias } from "drizzle-orm/sqlite-core";
71013
71170
 
71014
71171
  // ../api-routes/src/technical-aeo-issues.ts
@@ -71644,7 +71801,7 @@ async function technicalAeoRoutes(app, opts) {
71644
71801
  eq99(siteCrawlSnapshots.projectId, projectId),
71645
71802
  eq99(runs.projectId, projectId),
71646
71803
  eq99(runs.kind, RunKinds["site-audit"]),
71647
- inArray42(runs.status, SURFACEABLE_STATUSES),
71804
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71648
71805
  notProbeRun()
71649
71806
  ];
71650
71807
  const availableScanDatesFor = (projectId, requestedDate) => {
@@ -71714,7 +71871,7 @@ async function technicalAeoRoutes(app, opts) {
71714
71871
  eq99(siteAuditSnapshots.projectId, projectId),
71715
71872
  eq99(runs.projectId, projectId),
71716
71873
  eq99(runs.kind, RunKinds["site-audit"]),
71717
- inArray42(runs.status, SURFACEABLE_STATUSES),
71874
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71718
71875
  notProbeRun(),
71719
71876
  ...recordedBefore ? [lt14(siteAuditSnapshots.createdAt, recordedBefore)] : []
71720
71877
  ];
@@ -71857,7 +72014,7 @@ async function technicalAeoRoutes(app, opts) {
71857
72014
  eq99(runs.id, runId),
71858
72015
  eq99(runs.projectId, projectId),
71859
72016
  eq99(runs.kind, RunKinds["site-audit"]),
71860
- inArray42(runs.status, SURFACEABLE_STATUSES),
72017
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71861
72018
  notProbeRun()
71862
72019
  )).limit(1).get());
71863
72020
  const assertKnownAuditRun = (projectId, runId) => {
@@ -71867,7 +72024,7 @@ async function technicalAeoRoutes(app, opts) {
71867
72024
  eq99(siteAuditSnapshots.projectId, projectId),
71868
72025
  eq99(runs.projectId, projectId),
71869
72026
  eq99(runs.kind, RunKinds["site-audit"]),
71870
- inArray42(runs.status, SURFACEABLE_STATUSES),
72027
+ inArray43(runs.status, SURFACEABLE_STATUSES),
71871
72028
  notProbeRun()
71872
72029
  )).limit(1).get());
71873
72030
  const emptyCrawlSummary = (projectName, legacyAuditAvailable) => ({
@@ -71998,7 +72155,7 @@ async function technicalAeoRoutes(app, opts) {
71998
72155
  eq99(siteAuditSnapshots.projectId, project.id),
71999
72156
  eq99(runs.projectId, project.id),
72000
72157
  eq99(runs.kind, RunKinds["site-audit"]),
72001
- inArray42(runs.status, SURFACEABLE_STATUSES),
72158
+ inArray43(runs.status, SURFACEABLE_STATUSES),
72002
72159
  notProbeRun()
72003
72160
  )).orderBy(desc47(siteAuditSnapshots.createdAt)).limit(limit).all();
72004
72161
  return { project: project.name, points: rows.reverse() };
@@ -72313,8 +72470,8 @@ async function technicalAeoRoutes(app, opts) {
72313
72470
  eq99(siteCrawlEdges.internal, true),
72314
72471
  isNotNull10(siteCrawlEdges.targetNodeKey),
72315
72472
  or21(
72316
- inArray42(siteCrawlEdges.sourceNodeKey, frontier),
72317
- inArray42(siteCrawlEdges.targetNodeKey, frontier)
72473
+ inArray43(siteCrawlEdges.sourceNodeKey, frontier),
72474
+ inArray43(siteCrawlEdges.targetNodeKey, frontier)
72318
72475
  )
72319
72476
  )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(maxEdges + maxNodes + 1).all();
72320
72477
  if (candidates.length > maxEdges + maxNodes) queryTruncated = true;
@@ -72351,7 +72508,7 @@ async function technicalAeoRoutes(app, opts) {
72351
72508
  eq99(siteCrawlPages.projectId, scope.projectId),
72352
72509
  eq99(siteCrawlPages.runId, scope.runId),
72353
72510
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72354
- inArray42(siteCrawlPages.nodeKey, nodeKeys)
72511
+ inArray43(siteCrawlPages.nodeKey, nodeKeys)
72355
72512
  )).all();
72356
72513
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
72357
72514
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
@@ -72361,8 +72518,8 @@ async function technicalAeoRoutes(app, opts) {
72361
72518
  eq99(siteCrawlEdges.attemptId, scope.attemptId),
72362
72519
  eq99(siteCrawlEdges.internal, true),
72363
72520
  isNotNull10(siteCrawlEdges.targetNodeKey),
72364
- inArray42(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
72365
- inArray42(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
72521
+ inArray43(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
72522
+ inArray43(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
72366
72523
  )).limit(1).get();
72367
72524
  if (omittedOutermostEdge && !seenEdgeKeys.has(omittedOutermostEdge.edgeKey)) {
72368
72525
  omittedEdgeKeys.add(omittedOutermostEdge.edgeKey);
@@ -72463,7 +72620,7 @@ async function technicalAeoRoutes(app, opts) {
72463
72620
  eq99(siteCrawlEdges.followable, true),
72464
72621
  eq99(siteCrawlEdges.relation, "anchor"),
72465
72622
  isNotNull10(siteCrawlEdges.targetNodeKey),
72466
- inArray42(siteCrawlEdges.sourceNodeKey, frontier)
72623
+ inArray43(siteCrawlEdges.sourceNodeKey, frontier)
72467
72624
  )).orderBy(asc21(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
72468
72625
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
72469
72626
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
@@ -72471,7 +72628,7 @@ async function technicalAeoRoutes(app, opts) {
72471
72628
  eq99(siteCrawlPages.projectId, scope.projectId),
72472
72629
  eq99(siteCrawlPages.runId, scope.runId),
72473
72630
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72474
- inArray42(siteCrawlPages.nodeKey, candidateTargetKeys)
72631
+ inArray43(siteCrawlPages.nodeKey, candidateTargetKeys)
72475
72632
  )).all().map((row) => row.nodeKey));
72476
72633
  const next = /* @__PURE__ */ new Set();
72477
72634
  for (const edge of candidates.slice(0, SITE_HEALTH_PATH_MAX_VISITED_NODES)) {
@@ -72516,7 +72673,7 @@ async function technicalAeoRoutes(app, opts) {
72516
72673
  eq99(siteCrawlPages.projectId, scope.projectId),
72517
72674
  eq99(siteCrawlPages.runId, scope.runId),
72518
72675
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72519
- inArray42(siteCrawlPages.nodeKey, pathKeys)
72676
+ inArray43(siteCrawlPages.nodeKey, pathKeys)
72520
72677
  )).all();
72521
72678
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
72522
72679
  const pathDetection = templateDetectionOf(snapshot.templateDetection);
@@ -72807,14 +72964,14 @@ async function technicalAeoRoutes(app, opts) {
72807
72964
  eq99(siteCrawlPages.projectId, scope.projectId),
72808
72965
  eq99(siteCrawlPages.runId, scope.runId),
72809
72966
  eq99(siteCrawlPages.attemptId, scope.attemptId),
72810
- inArray42(siteCrawlPages.nodeKey, pageKeys)
72967
+ inArray43(siteCrawlPages.nodeKey, pageKeys)
72811
72968
  )).all();
72812
72969
  const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and82(
72813
72970
  eq99(siteCrawlEdges.projectId, scope.projectId),
72814
72971
  eq99(siteCrawlEdges.runId, scope.runId),
72815
72972
  eq99(siteCrawlEdges.attemptId, scope.attemptId),
72816
72973
  eq99(siteCrawlEdges.internal, true),
72817
- inArray42(siteCrawlEdges.edgeKey, linkKeys)
72974
+ inArray43(siteCrawlEdges.edgeKey, linkKeys)
72818
72975
  )).all();
72819
72976
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
72820
72977
  const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -73340,9 +73497,9 @@ async function technicalAeoRoutes(app, opts) {
73340
73497
  eq99(siteCrawlSnapshots.projectId, project.id),
73341
73498
  eq99(runs.projectId, project.id),
73342
73499
  eq99(runs.kind, RunKinds["site-audit"]),
73343
- inArray42(runs.status, SURFACEABLE_STATUSES),
73500
+ inArray43(runs.status, SURFACEABLE_STATUSES),
73344
73501
  notProbeRun(),
73345
- inArray42(siteCrawlSnapshots.runId, runIds)
73502
+ inArray43(siteCrawlSnapshots.runId, runIds)
73346
73503
  )).all().map((row) => row.runId));
73347
73504
  return {
73348
73505
  project: project.name,
@@ -73483,7 +73640,7 @@ async function technicalAeoRoutes(app, opts) {
73483
73640
  )).where(and82(
73484
73641
  eq99(runs.projectId, project.id),
73485
73642
  eq99(runs.kind, RunKinds["site-audit"]),
73486
- inArray42(runs.status, [RunStatuses.queued, RunStatuses.running])
73643
+ inArray43(runs.status, [RunStatuses.queued, RunStatuses.running])
73487
73644
  )).get();
73488
73645
  if (existing) {
73489
73646
  if (existing.identityKey === identityKey) {
@@ -74342,6 +74499,7 @@ async function apiRoutes(app, opts) {
74342
74499
  vercelTrafficCredentialStore: opts.vercelTrafficCredentialStore,
74343
74500
  pullVercelTrafficEvents: opts.pullVercelTrafficEvents,
74344
74501
  vercelSyncDeadlineMs: opts.vercelSyncDeadlineMs,
74502
+ trafficBackfillLeaseRenewMs: opts.trafficBackfillLeaseRenewMs,
74345
74503
  cloudflareTrafficCredentialStore: opts.cloudflareTrafficCredentialStore,
74346
74504
  pullCloudflareQueueMessages: opts.pullCloudflareQueueMessages,
74347
74505
  ackCloudflareQueueMessages: opts.ackCloudflareQueueMessages,