@canonry/canonry 7.97.8 → 7.99.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 (136) hide show
  1. package/assets/agent-workspace/skills/aero/references/site-health.md +11 -1
  2. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -0
  3. package/assets/assets/{AgentSetupPage-DiX4GptZ.js → AgentSetupPage-yOqSVJ-e.js} +1 -1
  4. package/assets/assets/{AuditHistoryPanel-de0zJkNC.js → AuditHistoryPanel-CiTzFgbw.js} +1 -1
  5. package/assets/assets/{BacklinksPage-BFfksR1M.js → BacklinksPage-DmRdQsFQ.js} +1 -1
  6. package/assets/assets/{HistoryPage-DQnGncRE.js → HistoryPage-DNE76LNh.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-DYCBQSnh.js → MeasurementPropertyPage-Cx64Fnmz.js} +1 -1
  8. package/assets/assets/{ProjectPage-BwwkYgfJ.js → ProjectPage-DNfgC82V.js} +1 -1
  9. package/assets/assets/{RunsPage-VeWCiQeM.js → RunsPage-BmPKBKKr.js} +1 -1
  10. package/assets/assets/{SettingsPage-BXPhnRMo.js → SettingsPage-Dl0TNeVK.js} +1 -1
  11. package/assets/assets/{SiteHealthSection-Ch9tNd8P.js → SiteHealthSection-DxQPaR--.js} +2 -2
  12. package/assets/assets/ToneBadge-BhjTBHi-.js +60 -0
  13. package/assets/assets/{TrafficPage-CIzz7gmK.js → TrafficPage-CJp0pS01.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-6IbzMhJ6.js → TrafficSourceDetailPage-DKE8tSw5.js} +1 -1
  15. package/assets/assets/{account-context-DIWdmpj6.js → account-context-j3asaevn.js} +1 -1
  16. package/assets/assets/{agent-trust-copy-2Os0XoaQ.js → agent-trust-copy--U_DFYQE.js} +1 -1
  17. package/assets/assets/{card-CoYH9h6o.js → card-CQAQ-c3g.js} +1 -1
  18. package/assets/assets/{format-helpers-cRaGEIxc.js → format-helpers-BPugHeIX.js} +1 -1
  19. package/assets/assets/{index-vfNb458q.js → index-xlDLvgRC.js} +4 -4
  20. package/assets/assets/{query-client-Bf1oywTZ.js → query-client-CwZpfiiD.js} +1 -1
  21. package/assets/assets/{react-query-DYI08wkz.js → react-query-CBpNFR4t.js} +1 -1
  22. package/assets/assets/{react-sigma_core.esm.min-BEhi6Q9o.js → react-sigma_core.esm.min-BAq_-iP1.js} +1 -1
  23. package/assets/assets/{server-traffic-tgMckYtk.js → server-traffic-jKXLZyeC.js} +1 -1
  24. package/assets/assets/{service-CN0y5nRe.js → service-DFumlmXt.js} +1 -1
  25. package/assets/index.html +7 -7
  26. package/dist/{ads-F7ACQA6Z.js → ads-EYDUTG4U.js} +3 -3
  27. package/dist/{agent-FBSRQSUX.js → agent-KTK2IDB3.js} +7 -7
  28. package/dist/{agent-ask-VYSXDUCL.js → agent-ask-KPZOH6RY.js} +3 -3
  29. package/dist/{agent-conversations-IKAS3RE6.js → agent-conversations-BCWA4JJL.js} +3 -3
  30. package/dist/{agent-journeys-L3ABBCNC.js → agent-journeys-UBVLQ7JV.js} +3 -3
  31. package/dist/{agent-memory-SKWZDBMQ.js → agent-memory-24SYEGEX.js} +3 -3
  32. package/dist/{agent-providers-JBTFE5TJ.js → agent-providers-E7EWFE5P.js} +3 -3
  33. package/dist/{agent-transcript-YGLXXFF7.js → agent-transcript-QGJ45NW5.js} +3 -3
  34. package/dist/{analytics-ULDRQRRL.js → analytics-GHVG5CCT.js} +3 -3
  35. package/dist/{apply-KC4CCHY5.js → apply-7YLB2DPH.js} +3 -3
  36. package/dist/{backfill-5Y2GMEAH.js → backfill-WNQ6O5WP.js} +10 -10
  37. package/dist/{backlinks-NXWU4BPY.js → backlinks-ZBZR4D27.js} +3 -3
  38. package/dist/{bing-E2KM2CW6.js → bing-4VBSKPSX.js} +3 -3
  39. package/dist/{bootstrap-QYWTDUIZ.js → bootstrap-7ESCLP2G.js} +10 -10
  40. package/dist/{cdp-E7WXCN5U.js → cdp-WBL6FZTL.js} +7 -7
  41. package/dist/{chunk-KSB3UZMW.js → chunk-2RW5D55K.js} +2 -2
  42. package/dist/{chunk-RSJBMQMN.js → chunk-3MFYM2IS.js} +1 -1
  43. package/dist/{chunk-I6G5K5PM.js → chunk-3MKJ4LP6.js} +6 -6
  44. package/dist/{chunk-TUBFKC3O.js → chunk-3VPBRGPN.js} +1 -1
  45. package/dist/{chunk-7737JZP6.js → chunk-3WDQ5GPO.js} +1 -1
  46. package/dist/{chunk-ZN3KIIMP.js → chunk-47X24IJG.js} +24 -2
  47. package/dist/{chunk-GMCVZPKO.js → chunk-5ZOM5BJS.js} +1 -1
  48. package/dist/{chunk-4VSL2CMF.js → chunk-74V5EPZH.js} +7 -7
  49. package/dist/{chunk-7APKPDOV.js → chunk-AJD252SX.js} +2 -2
  50. package/dist/{chunk-NPCML7HY.js → chunk-AY7SDX6G.js} +3 -3
  51. package/dist/{chunk-TUQYYF4G.js → chunk-C55P75G4.js} +2 -2
  52. package/dist/{chunk-LPSKHUCV.js → chunk-DLOWRO5R.js} +3 -3
  53. package/dist/{chunk-AI2G7QMW.js → chunk-DMPSA7ZS.js} +4 -4
  54. package/dist/{chunk-66DTIEWN.js → chunk-L53ZSQN5.js} +28 -1
  55. package/dist/{chunk-NLN7EGU3.js → chunk-LM7HTNUO.js} +5 -5
  56. package/dist/{chunk-C3LS3ZKI.js → chunk-O6RXNSNW.js} +1 -1
  57. package/dist/{chunk-QM4PMS52.js → chunk-P764TJKQ.js} +1 -1
  58. package/dist/{chunk-FDVCTZXD.js → chunk-PT5HNVCM.js} +1 -1
  59. package/dist/{chunk-Q4KVK426.js → chunk-RRXLJPBN.js} +5 -5
  60. package/dist/{chunk-UBF2S3DF.js → chunk-SY3CWJR4.js} +2 -2
  61. package/dist/{chunk-X4DR2LK7.js → chunk-TVBOK4P2.js} +4 -4
  62. package/dist/{chunk-JYG3LUXU.js → chunk-UPHHKO3L.js} +357 -357
  63. package/dist/{chunk-4OGRTTZX.js → chunk-UY6SKAKW.js} +2 -2
  64. package/dist/{chunk-RABCSQBW.js → chunk-VADVPARO.js} +4 -4
  65. package/dist/{chunk-7TIX5NHC.js → chunk-VOVT2H25.js} +3 -3
  66. package/dist/{chunk-YO2OW67H.js → chunk-WA6APXI6.js} +3 -3
  67. package/dist/{chunk-MAWNTOQE.js → chunk-WXAF7AMC.js} +2 -2
  68. package/dist/{chunk-22LX5CWD.js → chunk-XPIMVXAI.js} +561 -359
  69. package/dist/{chunk-MA3ZD7TF.js → chunk-XQP3XHLJ.js} +2 -2
  70. package/dist/{chunk-RJNAUQSV.js → chunk-YACKJGY4.js} +1 -1
  71. package/dist/{chunk-5455IRP3.js → chunk-YBWATBX4.js} +1 -1
  72. package/dist/{chunk-WWGOMPYP.js → chunk-YEUULP44.js} +1 -1
  73. package/dist/{chunk-5X2N5OOF.js → chunk-YMHN4UIT.js} +1 -1
  74. package/dist/{chunk-FZIYHP67.js → chunk-ZGBLNKFN.js} +31 -5
  75. package/dist/{chunk-7CAOGKRV.js → chunk-ZN2TEBBX.js} +3 -3
  76. package/dist/{citations-CJ5Y3ZET.js → citations-N5MGNKJN.js} +3 -3
  77. package/dist/{cli-catalog-Z4XQIDYU.js → cli-catalog-FFU7PHW7.js} +3 -3
  78. package/dist/{cli-commands-3HZFXKVU.js → cli-commands-AMIDEUO2.js} +7 -7
  79. package/dist/cli.js +13 -13
  80. package/dist/{competitor-FC43JBCH.js → competitor-GRA2GMZR.js} +3 -3
  81. package/dist/{content-LS4EAVOS.js → content-YVAPTIV4.js} +3 -3
  82. package/dist/{dashboard-AZUFESTB.js → dashboard-33ACBR5A.js} +6 -6
  83. package/dist/{demo-LTVTVYGC.js → demo-AGXWBMR6.js} +1 -1
  84. package/dist/{demo-server-WHDXTUWQ.js → demo-server-W4YAC7GT.js} +12 -12
  85. package/dist/{discover-T5D7T5EZ.js → discover-BH33TO4Z.js} +3 -3
  86. package/dist/{discover-eval-FQWKYZD7.js → discover-eval-3CEA6DEE.js} +3 -3
  87. package/dist/{doctor-UEFC5WP7.js → doctor-ZAYTD5PC.js} +3 -3
  88. package/dist/{evidence-FXGHON3E.js → evidence-247GL7UK.js} +3 -3
  89. package/dist/{export-cmd-AF6KF2GP.js → export-cmd-HLKWFI5Q.js} +3 -3
  90. package/dist/{ga-RMYK4AQC.js → ga-BMING57Z.js} +3 -3
  91. package/dist/{gbp-5PAKGLBI.js → gbp-MIB5CXSD.js} +3 -3
  92. package/dist/{google-6NYRRHIS.js → google-5JP6RYAW.js} +4 -4
  93. package/dist/{google-marketing-6EVLEHKD.js → google-marketing-25OBMPS6.js} +1 -1
  94. package/dist/{health-cmd-WXDFHL7O.js → health-cmd-D5XSEL7H.js} +3 -3
  95. package/dist/{history-MZWTC4OE.js → history-6MDRFPQ3.js} +3 -3
  96. package/dist/index.d.ts +122 -12
  97. package/dist/index.js +25 -25
  98. package/dist/{init-OM64S7AT.js → init-RDR75N2Y.js} +12 -12
  99. package/dist/{insights-CYE3NXGD.js → insights-QZVMBGVM.js} +3 -3
  100. package/dist/intelligence-service-KQE7RO5I.js +16 -0
  101. package/dist/{journeys-YISJ3GH4.js → journeys-TABTZDFZ.js} +3 -3
  102. package/dist/{keys-L76I5EZI.js → keys-SL5YF7NQ.js} +3 -3
  103. package/dist/{keyword-54UML7XE.js → keyword-OCDEXRSW.js} +3 -3
  104. package/dist/{logs-ZG44USRX.js → logs-CSC7O364.js} +3 -3
  105. package/dist/{mcp-T7ZNR55S.js → mcp-P725OHAJ.js} +4 -4
  106. package/dist/mcp.js +9 -9
  107. package/dist/{measurement-plan-E7FAS5V3.js → measurement-plan-Z42GBJBO.js} +5 -5
  108. package/dist/{notify-ZB4V7B4L.js → notify-E5JBES4I.js} +5 -4
  109. package/dist/{organic-evidence-W5BJ37VR.js → organic-evidence-CUA3T64O.js} +3 -3
  110. package/dist/{overview-MULDZS6B.js → overview-UKQ52I72.js} +4 -4
  111. package/dist/{project-LFDGHF3H.js → project-2ULM64TV.js} +3 -3
  112. package/dist/{query-ZV3XX5YW.js → query-Y47IEFKJ.js} +3 -3
  113. package/dist/{research-HGAKJGVQ.js → research-DPFQZ3TE.js} +3 -3
  114. package/dist/{results-export-5HCHJGBM.js → results-export-KBHOBQAH.js} +3 -3
  115. package/dist/{run-5VYP7S3V.js → run-4CYHVIIU.js} +4 -4
  116. package/dist/{schedule-FD2VIHX7.js → schedule-GY4P3RON.js} +3 -3
  117. package/dist/{search-6TJJWFJB.js → search-AGVNZMJQ.js} +3 -3
  118. package/dist/{serve-4QZEQUER.js → serve-5LFKIXNL.js} +26 -26
  119. package/dist/{session-PA4SLGYI.js → session-YAYEDM3P.js} +13 -13
  120. package/dist/{settings-NOXPXCAY.js → settings-HUKMWRHY.js} +3 -3
  121. package/dist/{sitemaps-P52HCBCM.js → sitemaps-APTY7U6H.js} +3 -3
  122. package/dist/{skills-EBWXL2WN.js → skills-4BUONEIZ.js} +2 -2
  123. package/dist/{snapshot-IST2UAPI.js → snapshot-BXCWSF5P.js} +3 -3
  124. package/dist/{src-53VQLBQC.js → src-R7XZUX3D.js} +4 -2
  125. package/dist/{status-X2RYXIU5.js → status-SB6HPBYJ.js} +4 -4
  126. package/dist/{technical-aeo-HRQRU2L3.js → technical-aeo-G7UEMWZD.js} +3 -3
  127. package/dist/{telemetry-KQATCLNR.js → telemetry-H4S4Y7YV.js} +4 -4
  128. package/dist/{tools-VLQ3GZUE.js → tools-46XX6I4R.js} +11 -11
  129. package/dist/{traffic-WK6HEQWG.js → traffic-I6WRJN3W.js} +5 -5
  130. package/dist/{trends-353NF4OU.js → trends-AQK3GLWF.js} +3 -3
  131. package/dist/{users-YXBFZHVO.js → users-SNTVY6OB.js} +3 -3
  132. package/dist/{visibility-stats-N4KZ3N4P.js → visibility-stats-44QAD2XS.js} +3 -3
  133. package/dist/{wordpress-3IPY5TA3.js → wordpress-N7LTOW46.js} +3 -3
  134. package/package.json +2 -2
  135. package/assets/assets/ToneBadge-JbnFjl09.js +0 -60
  136. package/dist/intelligence-service-ILIYUOKY.js +0 -16
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  answerServiceAdapters,
3
3
  answerServiceDescriptors
4
- } from "./chunk-GMCVZPKO.js";
4
+ } from "./chunk-5ZOM5BJS.js";
5
5
  import {
6
6
  AeroLlmUsageFeatures,
7
7
  aeroTurnStatus,
@@ -27,14 +27,14 @@ import {
27
27
  resolveConfiguredAgentProvider,
28
28
  resolveSessionProviderAndModel,
29
29
  setAeroSystemPrompt
30
- } from "./chunk-I6G5K5PM.js";
30
+ } from "./chunk-3MKJ4LP6.js";
31
31
  import {
32
32
  AeroToolProfiles,
33
33
  AeroToolScopes,
34
34
  buildAeroStateTools,
35
35
  isAeroToolProfile,
36
36
  truncateToolResult
37
- } from "./chunk-AI2G7QMW.js";
37
+ } from "./chunk-DMPSA7ZS.js";
38
38
  import {
39
39
  getGoogleAuthConfig,
40
40
  getGoogleConnection,
@@ -53,7 +53,7 @@ import {
53
53
  removeCloudflareTrafficConnection,
54
54
  removeCloudflareTrafficConnectionBySourceId,
55
55
  upsertCloudflareTrafficConnection
56
- } from "./chunk-FDVCTZXD.js";
56
+ } from "./chunk-PT5HNVCM.js";
57
57
  import {
58
58
  claudeAdapter,
59
59
  embedQueries,
@@ -65,7 +65,7 @@ import {
65
65
  openrouterAdapter,
66
66
  perplexityAdapter,
67
67
  startTrafficReclassification
68
- } from "./chunk-Q4KVK426.js";
68
+ } from "./chunk-RRXLJPBN.js";
69
69
  import {
70
70
  classifyUsageSurface,
71
71
  createApiUsageTelemetry,
@@ -77,7 +77,7 @@ import {
77
77
  startOutcomeTimer,
78
78
  trackFeatureCompleted,
79
79
  trackInstallState
80
- } from "./chunk-RABCSQBW.js";
80
+ } from "./chunk-VADVPARO.js";
81
81
  import {
82
82
  isLoopbackBindHost
83
83
  } from "./chunk-TSENU4GG.js";
@@ -85,7 +85,7 @@ import {
85
85
  isApiProviderRegistrable,
86
86
  isCdpProviderRegistrable,
87
87
  registeredProviderNames
88
- } from "./chunk-C3LS3ZKI.js";
88
+ } from "./chunk-O6RXNSNW.js";
89
89
  import {
90
90
  checkLatestVersionForServer,
91
91
  getOrCreateAnonymousId,
@@ -97,17 +97,17 @@ import {
97
97
  recordDashboardEvent,
98
98
  setTelemetryPreference,
99
99
  trackEvent
100
- } from "./chunk-TUQYYF4G.js";
100
+ } from "./chunk-C55P75G4.js";
101
101
  import {
102
102
  createCanonryMcpServer
103
- } from "./chunk-YO2OW67H.js";
103
+ } from "./chunk-WA6APXI6.js";
104
104
  import {
105
105
  CANONRY_MCP_TIERS,
106
106
  CANONRY_MCP_TOOLKIT_NAMES
107
107
  } from "./chunk-MTW37GXF.js";
108
108
  import {
109
109
  ApiClient
110
- } from "./chunk-7APKPDOV.js";
110
+ } from "./chunk-AJD252SX.js";
111
111
  import {
112
112
  AGENT_PROVIDERS,
113
113
  adapterSupportsBatch,
@@ -139,21 +139,21 @@ import {
139
139
  saveConfigPatch,
140
140
  sentimentInstallReadiness,
141
141
  updateSharedProviderExecutionGate
142
- } from "./chunk-RSJBMQMN.js";
142
+ } from "./chunk-3MFYM2IS.js";
143
143
  import {
144
144
  getBundledSkillSnapshots
145
- } from "./chunk-QM4PMS52.js";
145
+ } from "./chunk-P764TJKQ.js";
146
146
  import {
147
147
  computeFactorAverages,
148
148
  deleteSiteCrawlGraphLayout,
149
149
  persistSiteCrawlGraphLayout,
150
150
  prepareSiteCrawlGraphLayout,
151
151
  recordedSharePct
152
- } from "./chunk-UBF2S3DF.js";
152
+ } from "./chunk-SY3CWJR4.js";
153
153
  import {
154
154
  CanonryMcpToolNames,
155
155
  canonryMcpTools
156
- } from "./chunk-LPSKHUCV.js";
156
+ } from "./chunk-DLOWRO5R.js";
157
157
  import {
158
158
  PACKAGE_VERSION
159
159
  } from "./chunk-AOJU5LKX.js";
@@ -162,16 +162,16 @@ import {
162
162
  } from "./chunk-HGMYEDAQ.js";
163
163
  import {
164
164
  IntelligenceService
165
- } from "./chunk-NLN7EGU3.js";
165
+ } from "./chunk-LM7HTNUO.js";
166
166
  import {
167
167
  computeCitedCompetitorDomains,
168
168
  determineCitationState
169
- } from "./chunk-5455IRP3.js";
169
+ } from "./chunk-YBWATBX4.js";
170
170
  import {
171
171
  addLogListener,
172
172
  createFastifyLogger,
173
173
  createLogger
174
- } from "./chunk-4OGRTTZX.js";
174
+ } from "./chunk-UY6SKAKW.js";
175
175
  import {
176
176
  BingApiError,
177
177
  CC_CACHE_DIR,
@@ -316,7 +316,7 @@ import {
316
316
  webhookResponseReason,
317
317
  webhookTargetRefusalReason,
318
318
  writeAuditLog
319
- } from "./chunk-FZIYHP67.js";
319
+ } from "./chunk-ZGBLNKFN.js";
320
320
  import {
321
321
  OperationalLogStore,
322
322
  SentimentIdempotencyConflict,
@@ -407,13 +407,14 @@ import {
407
407
  siteCrawlPages,
408
408
  siteCrawlRunRequests,
409
409
  siteCrawlSnapshots,
410
+ siteHealthNotifyState,
410
411
  siteLivenessState,
411
412
  sitemapListSyncs,
412
413
  trafficSources,
413
414
  unrecognizedAgentEventsHourly,
414
415
  unrecognizedAgentNotifyState,
415
416
  usageCounters
416
- } from "./chunk-66DTIEWN.js";
417
+ } from "./chunk-L53ZSQN5.js";
417
418
  import {
418
419
  AGENT_MEMORY_VALUE_MAX_BYTES,
419
420
  AI_ENGINE_DOMAINS,
@@ -628,6 +629,7 @@ import {
628
629
  parseMeasurementRunManifestV1,
629
630
  parseOriginList,
630
631
  parseRunError,
632
+ parseStoredMeasurementPlanAnyVersion,
631
633
  percentOf,
632
634
  providerConnectionName,
633
635
  providerError,
@@ -673,7 +675,7 @@ import {
673
675
  winnabilityClassLabel,
674
676
  withRetry,
675
677
  withSkippedProviders
676
- } from "./chunk-ZN3KIIMP.js";
678
+ } from "./chunk-47X24IJG.js";
677
679
 
678
680
  // src/google-marketing-runtime.ts
679
681
  import crypto2 from "crypto";
@@ -3357,13 +3359,13 @@ function createGoogleMarketingRuntime(options) {
3357
3359
 
3358
3360
  // src/server.ts
3359
3361
  import { createRequire } from "module";
3360
- import crypto27 from "crypto";
3362
+ import crypto28 from "crypto";
3361
3363
  import fs5 from "fs";
3362
3364
  import path5 from "path";
3363
3365
  import { isIP as isIP2 } from "net";
3364
3366
  import { fileURLToPath } from "url";
3365
3367
  import { setImmediate as yieldToEventLoop4 } from "timers/promises";
3366
- import { and as and29, eq as eq35, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql15 } from "drizzle-orm";
3368
+ import { and as and31, eq as eq37, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql15 } from "drizzle-orm";
3367
3369
 
3368
3370
  // src/seo-runtime.ts
3369
3371
  import { eq, inArray } from "drizzle-orm";
@@ -6001,7 +6003,7 @@ function nonBlank(value) {
6001
6003
  return trimmed ? trimmed : void 0;
6002
6004
  }
6003
6005
  function resolveBuildCommit(env = process.env) {
6004
- const embedded = true ? "58098d90ba7afb8fa38629099f82a91d52ed434c" : void 0;
6006
+ const embedded = true ? "74e5d2930f44211335b0da42dd2200f8ac58ae90" : void 0;
6005
6007
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6006
6008
  }
6007
6009
  function resolveInstanceIdentity(env = process.env) {
@@ -12233,7 +12235,7 @@ function buildDiscoveryInsightTitle(input) {
12233
12235
 
12234
12236
  // src/execute-site-audit.ts
12235
12237
  import crypto20 from "crypto";
12236
- import { and as and15, eq as eq19, ne as ne2, sql as sql7 } from "drizzle-orm";
12238
+ import { and as and16, eq as eq20, ne as ne2, sql as sql7 } from "drizzle-orm";
12237
12239
  import { runSiteCrawl } from "@canonry/agent-readiness";
12238
12240
 
12239
12241
  // src/site-audit-root.ts
@@ -12544,6 +12546,45 @@ function buildSiteCrawlIssueEvidence(siteIssues, pages) {
12544
12546
  return { schemaVersion: "1.0", groupIdentityVersion: GROUP_IDENTITY_VERSION, siteIssues: groups };
12545
12547
  }
12546
12548
 
12549
+ // src/site-audit-entity-registry.ts
12550
+ import { and as and15, eq as eq19 } from "drizzle-orm";
12551
+ function entityMatcher(matcher) {
12552
+ switch (matcher.kind) {
12553
+ case "prefix":
12554
+ return { host: matcher.host, pathPrefix: matcher.pathPrefix };
12555
+ case "host":
12556
+ return { host: matcher.host, pathPrefix: "/" };
12557
+ case "exact": {
12558
+ try {
12559
+ const url = new URL(matcher.url);
12560
+ return { host: url.hostname, pathPrefix: url.pathname };
12561
+ } catch {
12562
+ return null;
12563
+ }
12564
+ }
12565
+ }
12566
+ }
12567
+ function planEntityRegistry(plan) {
12568
+ if (plan.targets.length === 0) return null;
12569
+ return {
12570
+ entities: plan.targets.map((target) => ({
12571
+ key: target.stableKey,
12572
+ names: [.../* @__PURE__ */ new Set([target.label, ...target.aliases, ...target.identityAliases ?? []])],
12573
+ urlMatchers: target.urlMatchers.map(entityMatcher).filter((matcher) => matcher !== null)
12574
+ }))
12575
+ };
12576
+ }
12577
+ function activePlanEntityRegistry(db, projectId) {
12578
+ const pointer = db.select({ versionId: measurementPlans.activeVersionId }).from(measurementPlans).where(eq19(measurementPlans.projectId, projectId)).get();
12579
+ if (!pointer) return null;
12580
+ const version = db.select({ revision: measurementPlanVersions.revision, canonicalJson: measurementPlanVersions.canonicalJson }).from(measurementPlanVersions).where(and15(eq19(measurementPlanVersions.projectId, projectId), eq19(measurementPlanVersions.id, pointer.versionId))).get();
12581
+ if (!version) return null;
12582
+ const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
12583
+ if (plan.schemaVersion !== MEASUREMENT_PLAN_V2_SCHEMA_VERSION) return null;
12584
+ const registry = planEntityRegistry(plan);
12585
+ return registry ? { planRevision: version.revision, registry } : null;
12586
+ }
12587
+
12547
12588
  // src/execute-site-audit.ts
12548
12589
  var log15 = createLogger("SiteAudit");
12549
12590
  var SiteAuditCancelledError = class extends Error {
@@ -12697,7 +12738,7 @@ function trackSiteAuditOutcome(db, input) {
12697
12738
  );
12698
12739
  const pagesAudited = input.crawl?.pagesAudited ?? 0;
12699
12740
  if (pagesAudited === 0 || input.trigger === RunTriggers.probe) return;
12700
- const prior = db.select({ id: siteAuditSnapshots.id }).from(siteAuditSnapshots).where(and15(eq19(siteAuditSnapshots.projectId, input.projectId), ne2(siteAuditSnapshots.runId, input.runId))).limit(1).get();
12741
+ const prior = db.select({ id: siteAuditSnapshots.id }).from(siteAuditSnapshots).where(and16(eq20(siteAuditSnapshots.projectId, input.projectId), ne2(siteAuditSnapshots.runId, input.runId))).limit(1).get();
12701
12742
  if (prior) return;
12702
12743
  trackEvent("activation.completed", {
12703
12744
  flowVersion: ONBOARDING_FLOW_VERSION,
@@ -12711,12 +12752,12 @@ function trackSiteAuditOutcome(db, input) {
12711
12752
  }
12712
12753
  async function executeSiteAudit(db, runId, projectId, opts = {}) {
12713
12754
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
12714
- const claim = db.update(runs).set({ status: "running", startedAt }).where(and15(eq19(runs.id, runId), eq19(runs.projectId, projectId), eq19(runs.status, "queued"))).run();
12755
+ const claim = db.update(runs).set({ status: "running", startedAt }).where(and16(eq20(runs.id, runId), eq20(runs.projectId, projectId), eq20(runs.status, "queued"))).run();
12715
12756
  if (claim.changes === 0) return;
12716
12757
  const startTime = Date.now();
12717
- const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq19(runs.id, runId)).get()?.trigger ?? null;
12758
+ const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq20(runs.id, runId)).get()?.trigger ?? null;
12718
12759
  let canonicalDomain = null;
12719
- const attemptNumber = (db.select({ value: sql7`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq19(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
12760
+ const attemptNumber = (db.select({ value: sql7`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq20(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
12720
12761
  const attemptId = crypto20.randomUUID();
12721
12762
  db.insert(siteCrawlAttempts).values({
12722
12763
  id: attemptId,
@@ -12751,22 +12792,22 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12751
12792
  const nodeKey = nodeKeyByUrl.get(url);
12752
12793
  if (!nodeKey) continue;
12753
12794
  const scope = [
12754
- eq19(siteCrawlEdges.projectId, projectId),
12755
- eq19(siteCrawlEdges.runId, runId),
12756
- eq19(siteCrawlEdges.attemptId, attemptId)
12795
+ eq20(siteCrawlEdges.projectId, projectId),
12796
+ eq20(siteCrawlEdges.runId, runId),
12797
+ eq20(siteCrawlEdges.attemptId, attemptId)
12757
12798
  ];
12758
- tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(and15(
12799
+ tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(and16(
12759
12800
  ...scope,
12760
- eq19(siteCrawlEdges.sourceUrl, url)
12801
+ eq20(siteCrawlEdges.sourceUrl, url)
12761
12802
  )).run();
12762
- tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(and15(
12803
+ tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(and16(
12763
12804
  ...scope,
12764
- eq19(siteCrawlEdges.targetUrl, url)
12805
+ eq20(siteCrawlEdges.targetUrl, url)
12765
12806
  )).run();
12766
12807
  const pageScope = [
12767
- eq19(siteCrawlPages.projectId, projectId),
12768
- eq19(siteCrawlPages.runId, runId),
12769
- eq19(siteCrawlPages.attemptId, attemptId)
12808
+ eq20(siteCrawlPages.projectId, projectId),
12809
+ eq20(siteCrawlPages.runId, runId),
12810
+ eq20(siteCrawlPages.attemptId, attemptId)
12770
12811
  ];
12771
12812
  const canonicalTargets = tx.select({
12772
12813
  id: siteCrawlPages.id,
@@ -12774,13 +12815,13 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12774
12815
  fetchState: siteCrawlPages.fetchState,
12775
12816
  indexabilityState: siteCrawlPages.indexabilityState,
12776
12817
  indexabilityReasons: siteCrawlPages.indexabilityReasons
12777
- }).from(siteCrawlPages).where(and15(...pageScope, eq19(siteCrawlPages.canonicalUrl, url))).all();
12818
+ }).from(siteCrawlPages).where(and16(...pageScope, eq20(siteCrawlPages.canonicalUrl, url))).all();
12778
12819
  for (const target of canonicalTargets) {
12779
12820
  tx.update(siteCrawlPages).set({
12780
12821
  canonicalNodeKey: nodeKey,
12781
12822
  healthState: deriveSiteHealthState({ ...target, canonicalNodeKey: nodeKey }),
12782
12823
  updatedAt: now
12783
- }).where(eq19(siteCrawlPages.id, target.id)).run();
12824
+ }).where(eq20(siteCrawlPages.id, target.id)).run();
12784
12825
  }
12785
12826
  }
12786
12827
  };
@@ -12939,20 +12980,20 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12939
12980
  linkScoreRaw: metrics.linkScoreRaw,
12940
12981
  linkScoreNormalized: metrics.linkScore,
12941
12982
  updatedAt: now
12942
- }).where(and15(
12943
- eq19(siteCrawlPages.projectId, projectId),
12944
- eq19(siteCrawlPages.runId, runId),
12945
- eq19(siteCrawlPages.attemptId, attemptId),
12946
- eq19(siteCrawlPages.nodeKey, key)
12983
+ }).where(and16(
12984
+ eq20(siteCrawlPages.projectId, projectId),
12985
+ eq20(siteCrawlPages.runId, runId),
12986
+ eq20(siteCrawlPages.attemptId, attemptId),
12987
+ eq20(siteCrawlPages.nodeKey, key)
12947
12988
  )).run();
12948
12989
  };
12949
12990
  const persistEvent = async (event) => {
12950
12991
  const now = (/* @__PURE__ */ new Date()).toISOString();
12951
12992
  db.transaction((tx) => {
12952
- const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(and15(
12953
- eq19(siteCrawlEventReceipts.attemptId, attemptId),
12954
- eq19(siteCrawlEventReceipts.sequence, event.sequence),
12955
- eq19(siteCrawlEventReceipts.batchId, event.batchId)
12993
+ const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(and16(
12994
+ eq20(siteCrawlEventReceipts.attemptId, attemptId),
12995
+ eq20(siteCrawlEventReceipts.sequence, event.sequence),
12996
+ eq20(siteCrawlEventReceipts.batchId, event.batchId)
12956
12997
  )).get();
12957
12998
  if (receipt) {
12958
12999
  if (receipt.checksum !== event.checksum) {
@@ -12987,18 +13028,18 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12987
13028
  urlVersionsSkipped: event.type === "summary" ? event.summary.urlVersions?.skipped ?? 0 : sql7`${siteCrawlAttempts.urlVersionsSkipped}`,
12988
13029
  edgesDiscovered: progress.edgesObserved,
12989
13030
  updatedAt: now
12990
- }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
13031
+ }).where(eq20(siteCrawlAttempts.id, attemptId)).run();
12991
13032
  } else {
12992
13033
  tx.update(siteCrawlAttempts).set({
12993
13034
  lastEventSequence: event.sequence,
12994
13035
  lastEventChecksum: event.checksum,
12995
13036
  updatedAt: now
12996
- }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
13037
+ }).where(eq20(siteCrawlAttempts.id, attemptId)).run();
12997
13038
  }
12998
13039
  });
12999
13040
  };
13000
13041
  try {
13001
- const project = db.select().from(projects).where(eq19(projects.id, projectId)).get();
13042
+ const project = db.select().from(projects).where(eq20(projects.id, projectId)).get();
13002
13043
  if (!project) throw new Error(`Project not found: ${projectId}`);
13003
13044
  canonicalDomain = project.canonicalDomain;
13004
13045
  const homepageUrl = toHomepageUrl(project.canonicalDomain);
@@ -13010,6 +13051,12 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13010
13051
  });
13011
13052
  const crawlRootUrl = resolvedRoot.effectiveUrl;
13012
13053
  if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
13054
+ let entities = null;
13055
+ try {
13056
+ entities = activePlanEntityRegistry(db, projectId);
13057
+ } catch (error) {
13058
+ log15.warn("entity-registry.unavailable", { runId, projectId, error: describeError(error) });
13059
+ }
13013
13060
  log15.info("start", {
13014
13061
  runId,
13015
13062
  projectId,
@@ -13018,7 +13065,8 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13018
13065
  redirects: resolvedRoot.redirects.length,
13019
13066
  maxPages,
13020
13067
  maxEdges,
13021
- maxDepth: opts.maxDepth ?? null
13068
+ maxDepth: opts.maxDepth ?? null,
13069
+ entityPlanRevision: entities?.planRevision ?? null
13022
13070
  });
13023
13071
  const report = await runSiteCrawl(crawlRootUrl, {
13024
13072
  mode: "summary",
@@ -13030,6 +13078,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13030
13078
  includeGeo: opts.includeGeo ?? false,
13031
13079
  includeAgentSkills: opts.includeAgentSkills ?? true,
13032
13080
  skipUrlVersionsAfter: SITE_AUDIT_SKIP_URL_VERSIONS_AFTER,
13081
+ ...entities ? { entityRegistry: entities.registry } : {},
13033
13082
  signal: opts.signal,
13034
13083
  onEvent: persistEvent
13035
13084
  });
@@ -13102,7 +13151,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13102
13151
  }
13103
13152
  }
13104
13153
  const published = db.transaction((tx) => {
13105
- const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(and15(eq19(runs.id, runId), eq19(runs.status, "running"))).run();
13154
+ const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(and16(eq20(runs.id, runId), eq20(runs.status, "running"))).run();
13106
13155
  if (claim2.changes === 0) return false;
13107
13156
  if (aggregateScore !== null) {
13108
13157
  tx.insert(siteAuditSnapshots).values({
@@ -13158,7 +13207,9 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13158
13207
  maxDepth: opts.maxDepth ?? null,
13159
13208
  checkDeadLinks: opts.checkDeadLinks ?? false,
13160
13209
  includeGeo: opts.includeGeo ?? false,
13161
- includeAgentSkills: opts.includeAgentSkills ?? true
13210
+ includeAgentSkills: opts.includeAgentSkills ?? true,
13211
+ // The plan revision whose Properties pages were checked against; null when none were.
13212
+ entityPlanRevision: entities?.planRevision ?? null
13162
13213
  },
13163
13214
  pageBudget: maxPages,
13164
13215
  edgeBudget: maxEdges ?? null,
@@ -13212,7 +13263,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13212
13263
  edgesDiscovered: crawlSummary.edgesObserved,
13213
13264
  finishedAt,
13214
13265
  updatedAt: finishedAt
13215
- }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
13266
+ }).where(eq20(siteCrawlAttempts.id, attemptId)).run();
13216
13267
  return true;
13217
13268
  });
13218
13269
  if (!published) {
@@ -13225,7 +13276,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13225
13276
  error: describeError(cleanupError)
13226
13277
  });
13227
13278
  }
13228
- const current = db.select({ status: runs.status }).from(runs).where(eq19(runs.id, runId)).get();
13279
+ const current = db.select({ status: runs.status }).from(runs).where(eq20(runs.id, runId)).get();
13229
13280
  if (current?.status === "cancelled") throw new SiteAuditCancelledError(`Site audit cancelled before publication: ${runId}`);
13230
13281
  throw new Error(`Site audit run was no longer running before publication: ${runId}`);
13231
13282
  }
@@ -13273,12 +13324,12 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
13273
13324
  error: message,
13274
13325
  finishedAt,
13275
13326
  updatedAt: finishedAt
13276
- }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
13327
+ }).where(eq20(siteCrawlAttempts.id, attemptId)).run();
13277
13328
  tx.update(runs).set({
13278
13329
  status: cancelled ? "cancelled" : "failed",
13279
13330
  error: message,
13280
13331
  finishedAt
13281
- }).where(and15(eq19(runs.id, runId), eq19(runs.status, "running"))).run();
13332
+ }).where(and16(eq20(runs.id, runId), eq20(runs.status, "running"))).run();
13282
13333
  });
13283
13334
  log15.error("failed", { runId, projectId, cancelled, error: message });
13284
13335
  trackSiteAuditOutcome(db, {
@@ -13476,7 +13527,7 @@ function derivedRouteAdapter(adapter, route) {
13476
13527
  }
13477
13528
 
13478
13529
  // src/install-state-telemetry.ts
13479
- import { and as and16, count as count4, eq as eq20, gte as gte2, inArray as inArray8, isNotNull as isNotNull2, isNull as isNull2, ne as ne3, sql as sql8 } from "drizzle-orm";
13530
+ import { and as and17, count as count4, eq as eq21, gte as gte2, inArray as inArray8, isNotNull as isNotNull2, isNull as isNull2, ne as ne3, sql as sql8 } from "drizzle-orm";
13480
13531
  var log17 = createLogger("InstallStateTelemetry");
13481
13532
  var INSTALL_STATE_FIRST_DELAY_MS = 5 * 6e4;
13482
13533
  var INSTALL_STATE_INTERVAL_MS = 24 * 60 * 6e4;
@@ -13501,8 +13552,8 @@ function buildInstallState(sources, now, startedAt) {
13501
13552
  const total = (query) => whole(query.get()?.value);
13502
13553
  const googleByType = new Map(db.select({ type: googleConnections.connectionType, value: count4() }).from(googleConnections).groupBy(googleConnections.connectionType).all().map((row) => [row.type, whole(row.value)]));
13503
13554
  const gaServiceAccounts = total(db.select({ value: count4() }).from(gaConnections));
13504
- const trafficByType = db.select({ type: trafficSources.sourceType, value: count4() }).from(trafficSources).where(and16(isNull2(trafficSources.archivedAt), ne3(trafficSources.status, TrafficSourceStatuses.archived))).groupBy(trafficSources.sourceType).all();
13505
- const webhookSources = db.select({ config: notifications.config }).from(notifications).where(eq20(notifications.enabled, true)).all().map((row) => row.config.source);
13555
+ const trafficByType = db.select({ type: trafficSources.sourceType, value: count4() }).from(trafficSources).where(and17(isNull2(trafficSources.archivedAt), ne3(trafficSources.status, TrafficSourceStatuses.archived))).groupBy(trafficSources.sourceType).all();
13556
+ const webhookSources = db.select({ config: notifications.config }).from(notifications).where(eq21(notifications.enabled, true)).all().map((row) => row.config.source);
13506
13557
  const agentWebhooks = webhookSources.filter((source) => source === AGENT_WEBHOOK_SOURCE).length;
13507
13558
  const webhooks = webhookSources.length - agentWebhooks;
13508
13559
  const bing = total(db.select({ value: count4() }).from(bingConnections));
@@ -13516,7 +13567,7 @@ function buildInstallState(sources, now, startedAt) {
13516
13567
  queries: total(db.select({ value: count4() }).from(queries)),
13517
13568
  competitors: total(db.select({ value: count4() }).from(competitors)),
13518
13569
  locations: total(db.select({ value: sql8`coalesce(sum(json_array_length(${projects.locations})), 0)` }).from(projects)),
13519
- schedules: total(db.select({ value: count4() }).from(schedules).where(eq20(schedules.enabled, true))),
13570
+ schedules: total(db.select({ value: count4() }).from(schedules).where(eq21(schedules.enabled, true))),
13520
13571
  webhooks,
13521
13572
  agentWebhooks,
13522
13573
  trafficSources: trafficByType.reduce((sum, row) => sum + whole(row.value), 0),
@@ -13560,14 +13611,14 @@ function buildInstallState(sources, now, startedAt) {
13560
13611
  costMicros: sql8`coalesce(sum(${usagePath("estimatedCostMicros")}), 0)`
13561
13612
  }).from(querySnapshots).where(gte2(querySnapshots.createdAt, since)).groupBy(sql8`+${querySnapshots.provider}`).all();
13562
13613
  for (const row of answers) addCalls(row.provider, whole(row.answers));
13563
- for (const row of db.select({ provider: researchRuns.provider, answered: sql8`coalesce(sum(${researchRuns.completedQueries}), 0)` }).from(researchRuns).where(and16(inArray8(researchRuns.projectId, projectIds), gte2(researchRuns.createdAt, since))).groupBy(researchRuns.provider).all()) {
13614
+ for (const row of db.select({ provider: researchRuns.provider, answered: sql8`coalesce(sum(${researchRuns.completedQueries}), 0)` }).from(researchRuns).where(and17(inArray8(researchRuns.projectId, projectIds), gte2(researchRuns.createdAt, since))).groupBy(researchRuns.provider).all()) {
13564
13615
  addCalls(row.provider, whole(row.answered));
13565
13616
  }
13566
13617
  for (const session of db.select({
13567
13618
  seedProviders: discoverySessions.seedProviders,
13568
13619
  seedCountRaw: discoverySessions.seedCountRaw,
13569
13620
  probeCount: discoverySessions.probeCount
13570
- }).from(discoverySessions).where(and16(inArray8(discoverySessions.projectId, projectIds), gte2(discoverySessions.createdAt, since))).all()) {
13621
+ }).from(discoverySessions).where(and17(inArray8(discoverySessions.projectId, projectIds), gte2(discoverySessions.createdAt, since))).all()) {
13571
13622
  if (session.seedCountRaw !== null) {
13572
13623
  for (const provider of session.seedProviders?.length ? session.seedProviders : [ProviderNames.gemini]) addCalls(provider, 1);
13573
13624
  }
@@ -13576,15 +13627,15 @@ function buildInstallState(sources, now, startedAt) {
13576
13627
  const answered = answers.reduce((sum, row) => sum + whole(row.answers), 0);
13577
13628
  const measured = answers.reduce((sum, row) => sum + whole(row.measured), 0);
13578
13629
  const usage24h = {
13579
- sweeps: total(db.select({ value: count4() }).from(runs).where(and16(
13630
+ sweeps: total(db.select({ value: count4() }).from(runs).where(and17(
13580
13631
  inArray8(runs.projectId, projectIds),
13581
- eq20(runs.kind, RunKinds["answer-visibility"]),
13632
+ eq21(runs.kind, RunKinds["answer-visibility"]),
13582
13633
  gte2(runs.createdAt, since),
13583
13634
  ne3(runs.trigger, RunTriggers.probe)
13584
13635
  ))),
13585
- audits: total(db.select({ value: count4() }).from(runs).where(and16(
13636
+ audits: total(db.select({ value: count4() }).from(runs).where(and17(
13586
13637
  inArray8(runs.projectId, projectIds),
13587
- eq20(runs.kind, RunKinds["site-audit"]),
13638
+ eq21(runs.kind, RunKinds["site-audit"]),
13588
13639
  gte2(runs.createdAt, since)
13589
13640
  ))),
13590
13641
  providerCalls: [...providerCalls.values()].reduce((sum, calls) => sum + calls, 0)
@@ -13601,14 +13652,14 @@ function buildInstallState(sources, now, startedAt) {
13601
13652
  inputTokens: sql8`coalesce(sum(${llmUsageEvents.inputTokens}), 0)`,
13602
13653
  outputTokens: sql8`coalesce(sum(${llmUsageEvents.outputTokens}), 0)`,
13603
13654
  costMillicents: sql8`coalesce(sum(${llmUsageEvents.costMillicents}), 0)`
13604
- }).from(llmUsageEvents).where(and16(eq20(llmUsageEvents.feature, AeroLlmUsageFeatures.turn), gte2(llmUsageEvents.createdAt, since))).get();
13655
+ }).from(llmUsageEvents).where(and17(eq21(llmUsageEvents.feature, AeroLlmUsageFeatures.turn), gte2(llmUsageEvents.createdAt, since))).get();
13605
13656
  const aeroCalls = whole(aero?.calls);
13606
13657
  usage24h.aeroModelCalls = aeroCalls;
13607
13658
  usage24h.aeroInputTokens = whole(aero?.inputTokens);
13608
13659
  usage24h.aeroOutputTokens = whole(aero?.outputTokens);
13609
13660
  const aeroMillicents = whole(aero?.costMillicents);
13610
13661
  if (aeroCalls === 0 || aeroMillicents > 0) usage24h.aeroCostMicros = aeroMillicents * 10;
13611
- usage24h.webhookDeliveries = total(db.select({ value: count4() }).from(auditLog).where(and16(gte2(auditLog.createdAt, since), eq20(auditLog.action, WEBHOOK_DELIVERED_ACTION))));
13662
+ usage24h.webhookDeliveries = total(db.select({ value: count4() }).from(auditLog).where(and17(gte2(auditLog.createdAt, since), eq21(auditLog.action, WEBHOOK_DELIVERED_ACTION))));
13612
13663
  const providerCalls24h = Object.fromEntries(PROVIDER_NAMES.flatMap((name) => providerCalls.has(name) ? [[name, providerCalls.get(name)]] : []));
13613
13664
  return {
13614
13665
  providers: registeredProviderNames(config).filter((name) => KNOWN_PROVIDERS2.has(name)),
@@ -13738,7 +13789,7 @@ function configuredProviderEntries(config, apiAdapters, cdpAdapter) {
13738
13789
 
13739
13790
  // src/provider-reload-batch-guard.ts
13740
13791
  import { isDeepStrictEqual } from "util";
13741
- import { and as and17, inArray as inArray9 } from "drizzle-orm";
13792
+ import { and as and18, inArray as inArray9 } from "drizzle-orm";
13742
13793
  function pendingBatchIdentity(provider) {
13743
13794
  const { quotaPolicy: _quotaPolicy, ...identity } = resolveRegistration(provider.adapter, provider.config).config;
13744
13795
  return identity;
@@ -13752,7 +13803,7 @@ function assertProviderReloadKeepsPendingBatches(db, previous, next, executing =
13752
13803
  const changed = previous.filter(changedRegistration).map((provider) => provider.adapter.name);
13753
13804
  const active = executing.filter((snapshot) => changedRegistration(snapshot.registration));
13754
13805
  if (changed.length === 0 && active.length === 0) return;
13755
- const pending = changed.length === 0 ? [] : db.select({ id: providerBatches.id, provider: providerBatches.provider, runId: providerBatches.runId }).from(providerBatches).where(and17(
13806
+ const pending = changed.length === 0 ? [] : db.select({ id: providerBatches.id, provider: providerBatches.provider, runId: providerBatches.runId }).from(providerBatches).where(and18(
13756
13807
  inArray9(providerBatches.provider, changed),
13757
13808
  inArray9(providerBatches.status, [ProviderBatchStatuses.submitting, ProviderBatchStatuses.submitted, ProviderBatchStatuses.ended])
13758
13809
  )).all();
@@ -13834,7 +13885,7 @@ function createProviderModelCatalog(registry) {
13834
13885
  // src/scheduler.ts
13835
13886
  import crypto21 from "crypto";
13836
13887
  import cron from "node-cron";
13837
- import { and as and18, eq as eq21, inArray as inArray10, notExists, sql as sql9 } from "drizzle-orm";
13888
+ import { and as and19, eq as eq22, inArray as inArray10, notExists, sql as sql9 } from "drizzle-orm";
13838
13889
  var log18 = createLogger("Scheduler");
13839
13890
  var SLOT_FIRED = { status: OutcomeStatuses.succeeded };
13840
13891
  var slotSkipped = (reasonCode) => ({ status: OutcomeStatuses.skipped, reasonCode });
@@ -13913,9 +13964,9 @@ var Scheduler = class {
13913
13964
  */
13914
13965
  ensureHealthSchedules() {
13915
13966
  const projectsWithoutHealth = this.db.select({ id: projects.id }).from(projects).where(notExists(
13916
- this.db.select({ one: sql9`1` }).from(schedules).where(and18(
13917
- eq21(schedules.projectId, projects.id),
13918
- eq21(schedules.kind, SchedulableRunKinds.doctor)
13967
+ this.db.select({ one: sql9`1` }).from(schedules).where(and19(
13968
+ eq22(schedules.projectId, projects.id),
13969
+ eq22(schedules.kind, SchedulableRunKinds.doctor)
13919
13970
  ))
13920
13971
  )).all();
13921
13972
  if (projectsWithoutHealth.length === 0) return;
@@ -13958,7 +14009,7 @@ var Scheduler = class {
13958
14009
  start() {
13959
14010
  this.ensureHealthSchedules();
13960
14011
  this.ensureQueryBaskets();
13961
- const allSchedules = this.db.select().from(schedules).where(eq21(schedules.enabled, true)).all();
14012
+ const allSchedules = this.db.select().from(schedules).where(eq22(schedules.enabled, true)).all();
13962
14013
  for (const schedule of allSchedules) {
13963
14014
  const missedRunAt = schedule.nextRunAt;
13964
14015
  const recurrence = scheduleRecurrence(schedule);
@@ -13992,7 +14043,7 @@ var Scheduler = class {
13992
14043
  this.stopTask(key, existing, "Stopped");
13993
14044
  this.tasks.delete(key);
13994
14045
  }
13995
- const schedule = this.db.select().from(schedules).where(and18(eq21(schedules.projectId, projectId), eq21(schedules.kind, kind))).get();
14046
+ const schedule = this.db.select().from(schedules).where(and19(eq22(schedules.projectId, projectId), eq22(schedules.kind, kind))).get();
13996
14047
  if (schedule && schedule.enabled) {
13997
14048
  this.registerCronTask(schedule);
13998
14049
  }
@@ -14025,12 +14076,12 @@ var Scheduler = class {
14025
14076
  /** Runtime timing writes must not restore a version already issued to a settings caller. */
14026
14077
  updateScheduleTiming(scheduleId, timing) {
14027
14078
  this.db.transaction((tx) => {
14028
- const current = tx.select({ updatedAt: schedules.updatedAt }).from(schedules).where(eq21(schedules.id, scheduleId)).get();
14079
+ const current = tx.select({ updatedAt: schedules.updatedAt }).from(schedules).where(eq22(schedules.id, scheduleId)).get();
14029
14080
  if (!current) return;
14030
14081
  tx.update(schedules).set({
14031
14082
  ...timing,
14032
14083
  updatedAt: nextScheduleUpdatedAt(current.updatedAt)
14033
- }).where(eq21(schedules.id, scheduleId)).run();
14084
+ }).where(eq22(schedules.id, scheduleId)).run();
14034
14085
  });
14035
14086
  }
14036
14087
  registerCronTask(schedule, options = {}) {
@@ -14106,7 +14157,7 @@ var Scheduler = class {
14106
14157
  };
14107
14158
  const tick = () => {
14108
14159
  if (stopped) return;
14109
- const current = this.db.select().from(schedules).where(eq21(schedules.id, scheduleId)).get();
14160
+ const current = this.db.select().from(schedules).where(eq22(schedules.id, scheduleId)).get();
14110
14161
  if (!current || !current.enabled || !scheduleRecurrence(current)) {
14111
14162
  this.remove(projectId, kind);
14112
14163
  return;
@@ -14124,7 +14175,7 @@ var Scheduler = class {
14124
14175
  this.triggerRun(scheduleId, projectId, kind);
14125
14176
  }
14126
14177
  }
14127
- arm(this.db.select({ nextRunAt: schedules.nextRunAt }).from(schedules).where(eq21(schedules.id, scheduleId)).get()?.nextRunAt);
14178
+ arm(this.db.select({ nextRunAt: schedules.nextRunAt }).from(schedules).where(eq22(schedules.id, scheduleId)).get()?.nextRunAt);
14128
14179
  };
14129
14180
  this.tasks.set(key, task);
14130
14181
  let registeredNextRunAt = schedule.nextRunAt;
@@ -14139,7 +14190,7 @@ var Scheduler = class {
14139
14190
  /** Atomically advance a due callback-only calendar occurrence before dispatch. */
14140
14191
  claimCalendarOccurrence(schedule, dueAt, now) {
14141
14192
  return this.db.transaction((tx) => {
14142
- const current = tx.select().from(schedules).where(eq21(schedules.id, schedule.id)).get();
14193
+ const current = tx.select().from(schedules).where(eq22(schedules.id, schedule.id)).get();
14143
14194
  if (!current || !current.enabled || current.updatedAt !== schedule.updatedAt || current.nextRunAt !== dueAt) return false;
14144
14195
  const recurrence = scheduleRecurrence(current);
14145
14196
  if (!recurrence) return false;
@@ -14148,11 +14199,11 @@ var Scheduler = class {
14148
14199
  const result = tx.update(schedules).set({
14149
14200
  nextRunAt,
14150
14201
  updatedAt: nextScheduleUpdatedAt(current.updatedAt)
14151
- }).where(and18(
14152
- eq21(schedules.id, current.id),
14153
- eq21(schedules.enabled, true),
14154
- eq21(schedules.nextRunAt, dueAt),
14155
- eq21(schedules.updatedAt, schedule.updatedAt)
14202
+ }).where(and19(
14203
+ eq22(schedules.id, current.id),
14204
+ eq22(schedules.enabled, true),
14205
+ eq22(schedules.nextRunAt, dueAt),
14206
+ eq22(schedules.updatedAt, schedule.updatedAt)
14156
14207
  )).run();
14157
14208
  return result.changes === 1;
14158
14209
  });
@@ -14174,7 +14225,7 @@ var Scheduler = class {
14174
14225
  fireSlot(scheduleId, projectId, kind, claimedOccurrence) {
14175
14226
  try {
14176
14227
  const now = (/* @__PURE__ */ new Date()).toISOString();
14177
- const currentSchedule = this.db.select().from(schedules).where(eq21(schedules.id, scheduleId)).get();
14228
+ const currentSchedule = this.db.select().from(schedules).where(eq22(schedules.id, scheduleId)).get();
14178
14229
  if (!currentSchedule || !currentSchedule.enabled) {
14179
14230
  log18.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
14180
14231
  this.remove(projectId, kind);
@@ -14190,7 +14241,7 @@ var Scheduler = class {
14190
14241
  timezone: currentSchedule.timezone,
14191
14242
  recurrence
14192
14243
  });
14193
- const project = this.db.select().from(projects).where(eq21(projects.id, projectId)).get();
14244
+ const project = this.db.select().from(projects).where(eq22(projects.id, projectId)).get();
14194
14245
  if (!project) {
14195
14246
  log18.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
14196
14247
  this.remove(projectId, kind);
@@ -14241,9 +14292,9 @@ var Scheduler = class {
14241
14292
  log18.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
14242
14293
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
14243
14294
  }
14244
- const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and18(
14245
- eq21(runs.projectId, projectId),
14246
- eq21(runs.kind, RunKinds["ads-sync"]),
14295
+ const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and19(
14296
+ eq22(runs.projectId, projectId),
14297
+ eq22(runs.kind, RunKinds["ads-sync"]),
14247
14298
  inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
14248
14299
  )).get();
14249
14300
  if (activeAdsRun) {
@@ -14273,9 +14324,9 @@ var Scheduler = class {
14273
14324
  log18.warn("seo-sync.no-callback", { scheduleId, projectId, msg: "host did not register onSeoSyncRequested" });
14274
14325
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
14275
14326
  }
14276
- const activeSeoRun = this.db.select({ id: runs.id }).from(runs).where(and18(
14277
- eq21(runs.projectId, projectId),
14278
- eq21(runs.kind, RunKinds["seo-sync"]),
14327
+ const activeSeoRun = this.db.select({ id: runs.id }).from(runs).where(and19(
14328
+ eq22(runs.projectId, projectId),
14329
+ eq22(runs.kind, RunKinds["seo-sync"]),
14279
14330
  inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
14280
14331
  )).get();
14281
14332
  if (activeSeoRun) {
@@ -14335,9 +14386,9 @@ var Scheduler = class {
14335
14386
  log18.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
14336
14387
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
14337
14388
  }
14338
- const active = this.db.select({ id: runs.id }).from(runs).where(and18(
14339
- eq21(runs.projectId, projectId),
14340
- eq21(runs.kind, RunKinds["site-audit"]),
14389
+ const active = this.db.select({ id: runs.id }).from(runs).where(and19(
14390
+ eq22(runs.projectId, projectId),
14391
+ eq22(runs.kind, RunKinds["site-audit"]),
14341
14392
  inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
14342
14393
  )).get();
14343
14394
  if (active) {
@@ -14553,7 +14604,7 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
14553
14604
 
14554
14605
  // src/review-webhook-delivery.ts
14555
14606
  import { createHash as createHash7 } from "crypto";
14556
- import { and as and19, asc as asc4, eq as eq22, isNull as isNull3, lte as lte2, or } from "drizzle-orm";
14607
+ import { and as and20, asc as asc4, eq as eq23, isNull as isNull3, lte as lte2, or } from "drizzle-orm";
14557
14608
  var log20 = createLogger("ReviewDelivery");
14558
14609
  var hash = (value) => createHash7("sha256").update(JSON.stringify(value)).digest("hex");
14559
14610
  var REVIEW_DELIVERY_MAX_ATTEMPTS = 10;
@@ -14581,20 +14632,20 @@ function reviewEventId(projectId, review) {
14581
14632
  function reviewEventRefusal(db, projectId, payload, notification, targetDigest, now) {
14582
14633
  if (!notification?.enabled || !notification.config.events.includes("review.negative")) return "destination_disabled";
14583
14634
  if (targetDigest !== void 0 && hash(notification.config.url) !== targetDigest) return "destination_changed";
14584
- const project = db.select().from(projects).where(eq22(projects.id, projectId)).get();
14635
+ const project = db.select().from(projects).where(eq23(projects.id, projectId)).get();
14585
14636
  if (!project || project.name !== payload.project.name || project.canonicalDomain !== payload.project.canonicalDomain) return "project_changed";
14586
14637
  const review = payload.reviews[0];
14587
- const location = db.select().from(gbpLocations).where(and19(
14588
- eq22(gbpLocations.projectId, projectId),
14589
- eq22(gbpLocations.locationName, review.location.name),
14590
- eq22(gbpLocations.selected, true)
14638
+ const location = db.select().from(gbpLocations).where(and20(
14639
+ eq23(gbpLocations.projectId, projectId),
14640
+ eq23(gbpLocations.locationName, review.location.name),
14641
+ eq23(gbpLocations.selected, true)
14591
14642
  )).get();
14592
14643
  if (!location || review.origin === "gbp" && !review.reviewName.startsWith(`${location.accountName}/${location.locationName}/reviews/`)) return "location_removed_or_deselected";
14593
- const current = db.select().from(gbpReviews).where(and19(
14594
- eq22(gbpReviews.projectId, projectId),
14595
- eq22(gbpReviews.origin, review.origin),
14596
- eq22(gbpReviews.reviewName, review.reviewName),
14597
- eq22(gbpReviews.locationName, review.location.name)
14644
+ const current = db.select().from(gbpReviews).where(and20(
14645
+ eq23(gbpReviews.projectId, projectId),
14646
+ eq23(gbpReviews.origin, review.origin),
14647
+ eq23(gbpReviews.reviewName, review.reviewName),
14648
+ eq23(gbpReviews.locationName, review.location.name)
14598
14649
  )).get();
14599
14650
  if (!current) return "review_removed";
14600
14651
  if (!isWithinAlertWindow(review.updateTime, now) || !isWithinAlertWindow(current.updateTime, now)) return "review_expired";
@@ -14602,10 +14653,10 @@ function reviewEventRefusal(db, projectId, payload, notification, targetDigest,
14602
14653
  if (!isNegativeReviewRating(current.starRating, threshold) || !isNegativeReviewRating(review.starRating, threshold)) return "review_no_longer_negative";
14603
14654
  if (["baseline", "skipped", "suppressed", "stale", "none"].includes(current.alertState)) return "review_suppressed";
14604
14655
  if (review.origin === "places") {
14605
- const v4HasWorked = db.select({ id: gbpReviewRatings.id }).from(gbpReviewRatings).where(and19(
14606
- eq22(gbpReviewRatings.projectId, projectId),
14607
- eq22(gbpReviewRatings.locationName, review.location.name),
14608
- eq22(gbpReviewRatings.origin, "gbp")
14656
+ const v4HasWorked = db.select({ id: gbpReviewRatings.id }).from(gbpReviewRatings).where(and20(
14657
+ eq23(gbpReviewRatings.projectId, projectId),
14658
+ eq23(gbpReviewRatings.locationName, review.location.name),
14659
+ eq23(gbpReviewRatings.origin, "gbp")
14609
14660
  )).get() !== void 0;
14610
14661
  if (businessProfileReviewsCover(location.reviewsAccess, v4HasWorked)) return "review_origin_superseded";
14611
14662
  }
@@ -14613,9 +14664,9 @@ function reviewEventRefusal(db, projectId, payload, notification, targetDigest,
14613
14664
  }
14614
14665
  function cancelReviewDelivery(db, id, reason) {
14615
14666
  db.transaction((tx) => {
14616
- const canceled = tx.update(reviewWebhookDeliveries).set({ state: ReviewDeliveryStates.Canceled, retryAt: null }).where(and19(
14617
- eq22(reviewWebhookDeliveries.id, id),
14618
- eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending)
14667
+ const canceled = tx.update(reviewWebhookDeliveries).set({ state: ReviewDeliveryStates.Canceled, retryAt: null }).where(and20(
14668
+ eq23(reviewWebhookDeliveries.id, id),
14669
+ eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending)
14619
14670
  )).returning({ projectId: reviewWebhookDeliveries.projectId, eventId: reviewWebhookDeliveries.eventId, notificationId: reviewWebhookDeliveries.notificationId, targetDigest: reviewWebhookDeliveries.targetDigest }).get();
14620
14671
  if (canceled) writeAuditLog(tx, {
14621
14672
  projectId: canceled.projectId,
@@ -14631,10 +14682,10 @@ function deferReviewDelivery(db, saved, now) {
14631
14682
  if (saved.state !== ReviewDeliveryStates.Pending && saved.state !== ReviewDeliveryStates.Attempting) return;
14632
14683
  const waited = now - Date.parse(saved.createdAt);
14633
14684
  const delay = Math.min(Math.max(RETRY_BASE_MS, Number.isFinite(waited) ? waited / 4 : RETRY_BASE_MS), RETRY_MAX_MS);
14634
- db.update(reviewWebhookDeliveries).set({ retryAt: new Date(now + delay).toISOString() }).where(and19(
14635
- eq22(reviewWebhookDeliveries.id, saved.id),
14636
- eq22(reviewWebhookDeliveries.state, saved.state),
14637
- eq22(reviewWebhookDeliveries.attempts, saved.attempts)
14685
+ db.update(reviewWebhookDeliveries).set({ retryAt: new Date(now + delay).toISOString() }).where(and20(
14686
+ eq23(reviewWebhookDeliveries.id, saved.id),
14687
+ eq23(reviewWebhookDeliveries.state, saved.state),
14688
+ eq23(reviewWebhookDeliveries.attempts, saved.attempts)
14638
14689
  )).run();
14639
14690
  }
14640
14691
  function auditUndelivered(db, saved, outcome) {
@@ -14651,8 +14702,8 @@ function dueReviewDeliveryProjects(db, now) {
14651
14702
  const at = new Date(now).toISOString();
14652
14703
  const staleAttempt = new Date(now - 6e4).toISOString();
14653
14704
  return db.selectDistinct({ projectId: reviewWebhookDeliveries.projectId }).from(reviewWebhookDeliveries).where(or(
14654
- and19(eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending), or(isNull3(reviewWebhookDeliveries.retryAt), lte2(reviewWebhookDeliveries.retryAt, at))),
14655
- and19(eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14705
+ and20(eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending), or(isNull3(reviewWebhookDeliveries.retryAt), lte2(reviewWebhookDeliveries.retryAt, at))),
14706
+ and20(eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14656
14707
  )).all().map((row) => row.projectId);
14657
14708
  }
14658
14709
  function startReviewDeliveryLoop(drain, intervalMs = REVIEW_DELIVERY_INTERVAL_MS) {
@@ -14673,12 +14724,12 @@ async function drainReviewDeliveries(options) {
14673
14724
  const now = (options.now ?? Date.now)();
14674
14725
  const at = new Date(now).toISOString();
14675
14726
  const staleAttempt = new Date(now - 6e4).toISOString();
14676
- const ready = options.db.select().from(reviewWebhookDeliveries).where(and19(
14677
- eq22(reviewWebhookDeliveries.projectId, options.projectId),
14727
+ const ready = options.db.select().from(reviewWebhookDeliveries).where(and20(
14728
+ eq23(reviewWebhookDeliveries.projectId, options.projectId),
14678
14729
  or(isNull3(reviewWebhookDeliveries.retryAt), lte2(reviewWebhookDeliveries.retryAt, at)),
14679
14730
  or(
14680
- eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14681
- and19(eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14731
+ eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14732
+ and20(eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14682
14733
  )
14683
14734
  )).orderBy(asc4(reviewWebhookDeliveries.retryAt), asc4(reviewWebhookDeliveries.attemptedAt), asc4(reviewWebhookDeliveries.createdAt), asc4(reviewWebhookDeliveries.id)).limit(100).all();
14684
14735
  let delivered = 0;
@@ -14702,15 +14753,15 @@ async function deliverReviewEvent(options) {
14702
14753
  if (!payload.eventId || !/^evt_[a-f0-9]{64}$/u.test(payload.eventId) || payload.reviews.length !== 1)
14703
14754
  return false;
14704
14755
  const notification = db.select().from(notifications).where(
14705
- and19(
14706
- eq22(notifications.id, notificationId),
14707
- eq22(notifications.projectId, projectId)
14756
+ and20(
14757
+ eq23(notifications.id, notificationId),
14758
+ eq23(notifications.projectId, projectId)
14708
14759
  )
14709
14760
  ).get();
14710
14761
  const id = hash([projectId, notificationId, payload.eventId]);
14711
14762
  const now = options.now ?? Date.now;
14712
14763
  const at = new Date(now()).toISOString();
14713
- let saved = db.select().from(reviewWebhookDeliveries).where(eq22(reviewWebhookDeliveries.id, id)).get();
14764
+ let saved = db.select().from(reviewWebhookDeliveries).where(eq23(reviewWebhookDeliveries.id, id)).get();
14714
14765
  const event = saved?.payload ?? payload;
14715
14766
  const refusal = reviewEventRefusal(db, projectId, event, notification, saved?.targetDigest, new Date(now()));
14716
14767
  if (refusal) {
@@ -14729,7 +14780,7 @@ async function deliverReviewEvent(options) {
14729
14780
  payload,
14730
14781
  createdAt: at
14731
14782
  }).onConflictDoNothing().run();
14732
- saved = db.select().from(reviewWebhookDeliveries).where(eq22(reviewWebhookDeliveries.id, id)).get();
14783
+ saved = db.select().from(reviewWebhookDeliveries).where(eq23(reviewWebhookDeliveries.id, id)).get();
14733
14784
  if (!saved || saved.targetDigest !== targetDigest) return false;
14734
14785
  if (saved.state === ReviewDeliveryStates.Sent) return true;
14735
14786
  if (saved.state === ReviewDeliveryStates.Uncertain || saved.state === ReviewDeliveryStates.Canceled) return false;
@@ -14744,17 +14795,17 @@ async function deliverReviewEvent(options) {
14744
14795
  return false;
14745
14796
  const state2 = destination.signed ? ReviewDeliveryStates.Pending : ReviewDeliveryStates.Uncertain;
14746
14797
  const moved = db.update(reviewWebhookDeliveries).set({ state: state2 }).where(
14747
- and19(
14748
- eq22(reviewWebhookDeliveries.id, id),
14749
- eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14750
- eq22(reviewWebhookDeliveries.attempts, saved.attempts),
14751
- eq22(reviewWebhookDeliveries.attemptedAt, saved.attemptedAt)
14798
+ and20(
14799
+ eq23(reviewWebhookDeliveries.id, id),
14800
+ eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14801
+ eq23(reviewWebhookDeliveries.attempts, saved.attempts),
14802
+ eq23(reviewWebhookDeliveries.attemptedAt, saved.attemptedAt)
14752
14803
  )
14753
14804
  ).returning({ id: reviewWebhookDeliveries.id }).get();
14754
14805
  if (moved && state2 === ReviewDeliveryStates.Uncertain) {
14755
14806
  auditUndelivered(db, saved, { state: state2, reason: "interrupted_attempt", attempts: saved.attempts });
14756
14807
  }
14757
- saved = db.select().from(reviewWebhookDeliveries).where(eq22(reviewWebhookDeliveries.id, id)).get();
14808
+ saved = db.select().from(reviewWebhookDeliveries).where(eq23(reviewWebhookDeliveries.id, id)).get();
14758
14809
  if (!saved || saved.state !== ReviewDeliveryStates.Pending) return false;
14759
14810
  }
14760
14811
  if (saved.retryAt && Date.parse(saved.retryAt) > now()) return false;
@@ -14776,9 +14827,9 @@ async function deliverReviewEvent(options) {
14776
14827
  return false;
14777
14828
  }
14778
14829
  const latest = db.select().from(notifications).where(
14779
- and19(
14780
- eq22(notifications.id, notificationId),
14781
- eq22(notifications.projectId, projectId)
14830
+ and20(
14831
+ eq23(notifications.id, notificationId),
14832
+ eq23(notifications.projectId, projectId)
14782
14833
  )
14783
14834
  ).get();
14784
14835
  const latestRefusal = reviewEventRefusal(db, projectId, saved.payload, latest, targetDigest, new Date(now()));
@@ -14791,10 +14842,10 @@ async function deliverReviewEvent(options) {
14791
14842
  attempts: saved.attempts + 1,
14792
14843
  attemptedAt: new Date(now()).toISOString()
14793
14844
  }).where(
14794
- and19(
14795
- eq22(reviewWebhookDeliveries.id, id),
14796
- eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14797
- eq22(reviewWebhookDeliveries.attempts, saved.attempts)
14845
+ and20(
14846
+ eq23(reviewWebhookDeliveries.id, id),
14847
+ eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14848
+ eq23(reviewWebhookDeliveries.attempts, saved.attempts)
14798
14849
  )
14799
14850
  ).returning({ id: reviewWebhookDeliveries.id }).get();
14800
14851
  if (!claimed) return false;
@@ -14821,10 +14872,10 @@ async function deliverReviewEvent(options) {
14821
14872
  deliveredAt: sent ? new Date(now()).toISOString() : null,
14822
14873
  retryAt: state === ReviewDeliveryStates.Pending ? new Date(now() + retryDelayMs(attempts, result.retryAfterSeconds)).toISOString() : null
14823
14874
  }).where(
14824
- and19(
14825
- eq22(reviewWebhookDeliveries.id, id),
14826
- eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14827
- eq22(reviewWebhookDeliveries.attempts, attempts)
14875
+ and20(
14876
+ eq23(reviewWebhookDeliveries.id, id),
14877
+ eq23(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14878
+ eq23(reviewWebhookDeliveries.attempts, attempts)
14828
14879
  )
14829
14880
  ).run();
14830
14881
  if (state === ReviewDeliveryStates.Uncertain || state === ReviewDeliveryStates.Failed) {
@@ -14836,7 +14887,7 @@ async function deliverReviewEvent(options) {
14836
14887
  }
14837
14888
 
14838
14889
  // src/unrecognized-agent-alerts.ts
14839
- import { and as and20, desc as desc8, eq as eq23, gte as gte3, sql as sql10 } from "drizzle-orm";
14890
+ import { and as and21, desc as desc8, eq as eq24, gte as gte3, sql as sql10 } from "drizzle-orm";
14840
14891
  var log21 = createLogger("UnrecognizedAgentAlerts");
14841
14892
  var UNRECOGNIZED_AGENT_ALERT_WINDOW_MS = 7 * 864e5;
14842
14893
  var UNRECOGNIZED_AGENT_ALERT_MIN_HITS = 3;
@@ -14852,13 +14903,13 @@ function projectsWithRecentUnrecognizedAgents(db, now) {
14852
14903
  return db.selectDistinct({ projectId: unrecognizedAgentEventsHourly.projectId }).from(unrecognizedAgentEventsHourly).where(gte3(unrecognizedAgentEventsHourly.tsHour, since)).all().map((row) => row.projectId);
14853
14904
  }
14854
14905
  function undecidedUnrecognizedAgents(db, projectId, now) {
14855
- const decided = new Set(db.select({ agentName: unrecognizedAgentNotifyState.agentName }).from(unrecognizedAgentNotifyState).where(eq23(unrecognizedAgentNotifyState.projectId, projectId)).all().map((row) => row.agentName));
14906
+ const decided = new Set(db.select({ agentName: unrecognizedAgentNotifyState.agentName }).from(unrecognizedAgentNotifyState).where(eq24(unrecognizedAgentNotifyState.projectId, projectId)).all().map((row) => row.agentName));
14856
14907
  const totals = db.select({
14857
14908
  name: unrecognizedAgentEventsHourly.agentName,
14858
14909
  hits: sql10`SUM(${unrecognizedAgentEventsHourly.hits})`,
14859
14910
  firstSeenAt: sql10`MIN(${unrecognizedAgentEventsHourly.tsHour})`,
14860
14911
  lastSeenAt: sql10`MAX(${unrecognizedAgentEventsHourly.tsHour})`
14861
- }).from(unrecognizedAgentEventsHourly).where(eq23(unrecognizedAgentEventsHourly.projectId, projectId)).groupBy(unrecognizedAgentEventsHourly.agentName).all();
14912
+ }).from(unrecognizedAgentEventsHourly).where(eq24(unrecognizedAgentEventsHourly.projectId, projectId)).groupBy(unrecognizedAgentEventsHourly.agentName).all();
14862
14913
  const windowStart = new Date(now - UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
14863
14914
  const recordingStart = totals.reduce((earliest, total) => earliest === null || total.firstSeenAt < earliest ? total.firstSeenAt : earliest, null);
14864
14915
  const baselineEnd = recordingStart === null ? null : new Date(Date.parse(recordingStart) + UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
@@ -14876,7 +14927,7 @@ function undecidedUnrecognizedAgents(db, projectId, now) {
14876
14927
  continue;
14877
14928
  }
14878
14929
  if (Number(total.hits) < UNRECOGNIZED_AGENT_ALERT_MIN_HITS) continue;
14879
- const latest = db.select({ sampledUserAgent: unrecognizedAgentEventsHourly.sampledUserAgent, sampledPath: unrecognizedAgentEventsHourly.sampledPath }).from(unrecognizedAgentEventsHourly).where(and20(eq23(unrecognizedAgentEventsHourly.projectId, projectId), eq23(unrecognizedAgentEventsHourly.agentName, total.name))).orderBy(desc8(unrecognizedAgentEventsHourly.tsHour)).limit(1).get();
14930
+ const latest = db.select({ sampledUserAgent: unrecognizedAgentEventsHourly.sampledUserAgent, sampledPath: unrecognizedAgentEventsHourly.sampledPath }).from(unrecognizedAgentEventsHourly).where(and21(eq24(unrecognizedAgentEventsHourly.projectId, projectId), eq24(unrecognizedAgentEventsHourly.agentName, total.name))).orderBy(desc8(unrecognizedAgentEventsHourly.tsHour)).limit(1).get();
14880
14931
  due.push({
14881
14932
  name: total.name,
14882
14933
  hits: Number(total.hits),
@@ -14911,7 +14962,7 @@ function startUnrecognizedAgentAlertLoop(dispatch, intervalMs = UNRECOGNIZED_AGE
14911
14962
  }
14912
14963
 
14913
14964
  // src/provider-batch-poller.ts
14914
- import { and as and21, eq as eq24, inArray as inArray11, isNotNull as isNotNull3, isNull as isNull4, notExists as notExists2, sql as sql11 } from "drizzle-orm";
14965
+ import { and as and22, eq as eq25, inArray as inArray11, isNotNull as isNotNull3, isNull as isNull4, notExists as notExists2, sql as sql11 } from "drizzle-orm";
14915
14966
  var log22 = createLogger("ProviderBatchPoller");
14916
14967
  var PROVIDER_BATCH_POLL_INITIAL_MS = 3e4;
14917
14968
  var PROVIDER_BATCH_POLL_MAX_MS = 10 * 6e4;
@@ -14954,7 +15005,7 @@ var ProviderBatchPoller = class {
14954
15005
  * outstanding (ingested or cancelled), so its run may now finalize.
14955
15006
  */
14956
15007
  async advance(row) {
14957
- const runStatus = this.deps.db.select({ status: runs.status }).from(runs).where(eq24(runs.id, row.runId)).get()?.status;
15008
+ const runStatus = this.deps.db.select({ status: runs.status }).from(runs).where(eq25(runs.id, row.runId)).get()?.status;
14958
15009
  if (runStatus !== RunStatuses.running) {
14959
15010
  await this.deps.runner.abandonProviderBatches([row.id], "Cancelled because its run is no longer running.");
14960
15011
  this.schedule.delete(row.id);
@@ -14971,7 +15022,7 @@ var ProviderBatchPoller = class {
14971
15022
  status = poll.status;
14972
15023
  if (poll.status === "ended") {
14973
15024
  const endedAt = poll.endedAt ?? new Date(this.now()).toISOString();
14974
- const ended = this.deps.db.update(providerBatches).set({ status: ProviderBatchStatuses.ended, endedAt, resultsExpireAt: poll.resultsExpireAt ?? null, updatedAt: endedAt }).where(and21(eq24(providerBatches.id, row.id), eq24(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
15025
+ const ended = this.deps.db.update(providerBatches).set({ status: ProviderBatchStatuses.ended, endedAt, resultsExpireAt: poll.resultsExpireAt ?? null, updatedAt: endedAt }).where(and22(eq25(providerBatches.id, row.id), eq25(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14975
15026
  this.schedule.delete(row.id);
14976
15027
  return ended ? this.ingest({ ...row, status: ProviderBatchStatuses.ended, resultsExpireAt: poll.resultsExpireAt ?? null }) : false;
14977
15028
  }
@@ -14995,9 +15046,9 @@ var ProviderBatchPoller = class {
14995
15046
  const at = new Date(now).toISOString();
14996
15047
  if (row.cancelRequestedAt === null) {
14997
15048
  if (now < Date.parse(row.deadlineAt)) return false;
14998
- const claimed = this.deps.db.update(providerBatches).set({ cancelRequestedAt: at, updatedAt: at }).where(and21(
14999
- eq24(providerBatches.id, row.id),
15000
- eq24(providerBatches.status, ProviderBatchStatuses.submitted),
15049
+ const claimed = this.deps.db.update(providerBatches).set({ cancelRequestedAt: at, updatedAt: at }).where(and22(
15050
+ eq25(providerBatches.id, row.id),
15051
+ eq25(providerBatches.status, ProviderBatchStatuses.submitted),
15001
15052
  isNull4(providerBatches.cancelRequestedAt)
15002
15053
  )).run().changes === 1;
15003
15054
  if (!claimed) return false;
@@ -15016,7 +15067,7 @@ var ProviderBatchPoller = class {
15016
15067
  status: ProviderBatchStatuses.cancelled,
15017
15068
  error: `The provider batch did not end within an hour of being cancelled at its deadline; its ${row.requestCount} answer(s) were not recorded.`,
15018
15069
  updatedAt: at
15019
- }).where(and21(eq24(providerBatches.id, row.id), eq24(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
15070
+ }).where(and22(eq25(providerBatches.id, row.id), eq25(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
15020
15071
  this.schedule.delete(row.id);
15021
15072
  if (gaveUp) log22.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
15022
15073
  return gaveUp;
@@ -15056,10 +15107,10 @@ var ProviderBatchPoller = class {
15056
15107
  /** Runs the sweep handed off whose batches have all settled. */
15057
15108
  finalizableRuns() {
15058
15109
  const holding = [ProviderBatchStatuses.submitting, ...OUTSTANDING_PROVIDER_BATCH_STATUSES];
15059
- return this.deps.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and21(
15060
- eq24(runs.status, RunStatuses.running),
15110
+ return this.deps.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and22(
15111
+ eq25(runs.status, RunStatuses.running),
15061
15112
  isNotNull3(runs.pendingProviderErrors),
15062
- notExists2(this.deps.db.select({ one: sql11`1` }).from(providerBatches).where(and21(eq24(providerBatches.runId, runs.id), inArray11(providerBatches.status, holding))))
15113
+ notExists2(this.deps.db.select({ one: sql11`1` }).from(providerBatches).where(and22(eq25(providerBatches.runId, runs.id), inArray11(providerBatches.status, holding))))
15063
15114
  )).all();
15064
15115
  }
15065
15116
  };
@@ -15139,8 +15190,85 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
15139
15190
  }
15140
15191
 
15141
15192
  // src/notifier.ts
15142
- import { eq as eq25, desc as desc9, and as and22, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
15193
+ import { eq as eq27, desc as desc10, and as and24, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
15194
+ import crypto23 from "crypto";
15195
+
15196
+ // src/site-health-alerts.ts
15143
15197
  import crypto22 from "crypto";
15198
+ import { and as and23, asc as asc5, desc as desc9, eq as eq26, gt as gt3 } from "drizzle-orm";
15199
+ var ISSUE_TITLES = {
15200
+ "missing-h1": "Missing H1",
15201
+ "multiple-h1": "Multiple H1 tags",
15202
+ "missing-title": "Missing title",
15203
+ "names-another-entity": "Names another property"
15204
+ };
15205
+ var PAGE_BATCH = 500;
15206
+ var MAX_EXAMPLES = 5;
15207
+ function siteHealthProblemKey(code, url) {
15208
+ return `${code}|${crypto22.createHash("sha256").update(url).digest("hex").slice(0, 16)}`;
15209
+ }
15210
+ function readSiteHealthScan(db, projectId, runId) {
15211
+ const snapshot = db.select().from(siteCrawlSnapshots).where(and23(eq26(siteCrawlSnapshots.projectId, projectId), eq26(siteCrawlSnapshots.runId, runId))).orderBy(desc9(siteCrawlSnapshots.updatedAt)).limit(1).get();
15212
+ if (!snapshot?.detailsAvailable || !snapshot.attemptId) return null;
15213
+ const scope = and23(
15214
+ eq26(siteCrawlPages.projectId, projectId),
15215
+ eq26(siteCrawlPages.runId, runId),
15216
+ eq26(siteCrawlPages.attemptId, snapshot.attemptId)
15217
+ );
15218
+ const pagesByCode = /* @__PURE__ */ new Map();
15219
+ const keys = /* @__PURE__ */ new Set();
15220
+ let after;
15221
+ for (; ; ) {
15222
+ const pages = db.select({ nodeKey: siteCrawlPages.nodeKey, url: siteCrawlPages.url, auditFields: siteCrawlPages.auditFields }).from(siteCrawlPages).where(after === void 0 ? scope : and23(scope, gt3(siteCrawlPages.nodeKey, after))).orderBy(asc5(siteCrawlPages.nodeKey)).limit(PAGE_BATCH).all();
15223
+ for (const page of pages) {
15224
+ const defects = page.auditFields.criticalDefects;
15225
+ for (const defect of Array.isArray(defects) ? defects : []) {
15226
+ if (!defect || typeof defect !== "object") continue;
15227
+ const { id, severity } = defect;
15228
+ if (typeof id !== "string" || id === "" || severity !== "critical") continue;
15229
+ const key = siteHealthProblemKey(id, page.url);
15230
+ keys.add(key);
15231
+ let pagesForCode = pagesByCode.get(id);
15232
+ if (!pagesForCode) {
15233
+ pagesForCode = /* @__PURE__ */ new Map();
15234
+ pagesByCode.set(id, pagesForCode);
15235
+ }
15236
+ pagesForCode.set(key, page.url);
15237
+ }
15238
+ }
15239
+ if (pages.length < PAGE_BATCH) break;
15240
+ after = pages.at(-1).nodeKey;
15241
+ }
15242
+ return { complete: snapshot.complete, pagesByCode, keys };
15243
+ }
15244
+ function newSiteHealthIssues(scan, reported) {
15245
+ const issues = [];
15246
+ for (const [code, pages] of scan.pagesByCode) {
15247
+ const fresh = [...pages].filter(([key]) => !reported.has(key)).map(([, url]) => url).sort(compareText);
15248
+ if (fresh.length === 0) continue;
15249
+ issues.push({
15250
+ code,
15251
+ title: ISSUE_TITLES[code] ?? code,
15252
+ severity: "critical",
15253
+ affectedPages: pages.size,
15254
+ newPages: fresh.length,
15255
+ examples: fresh.slice(0, MAX_EXAMPLES)
15256
+ });
15257
+ }
15258
+ return issues.sort((a, b) => b.newPages - a.newPages || compareText(a.code, b.code));
15259
+ }
15260
+ function settledSiteHealthKeys(scan, reported) {
15261
+ return [...scan.complete ? scan.keys : /* @__PURE__ */ new Set([...reported, ...scan.keys])].sort(compareText);
15262
+ }
15263
+ function readSiteHealthNotifyState(db, projectId) {
15264
+ return db.select().from(siteHealthNotifyState).where(eq26(siteHealthNotifyState.projectId, projectId)).get() ?? null;
15265
+ }
15266
+ function writeSiteHealthNotifyState(db, input) {
15267
+ const values = { reportedKeys: input.reportedKeys, runId: input.runId, updatedAt: input.updatedAt, notifiedAt: input.notifiedAt };
15268
+ db.insert(siteHealthNotifyState).values({ projectId: input.projectId, ...values }).onConflictDoUpdate({ target: siteHealthNotifyState.projectId, set: values }).run();
15269
+ }
15270
+
15271
+ // src/notifier.ts
15144
15272
  var log24 = createLogger("Notifier");
15145
15273
  var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
15146
15274
  function trackWebhookDelivery(input) {
@@ -15262,18 +15390,18 @@ var Notifier = class {
15262
15390
  /** Called after a run completes (success, partial, or failed). */
15263
15391
  async onRunCompleted(runId, projectId, opts) {
15264
15392
  log24.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
15265
- const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15393
+ const notifs = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15266
15394
  if (notifs.length === 0) {
15267
15395
  log24.info("notifications.none-enabled", { projectId });
15268
15396
  return;
15269
15397
  }
15270
15398
  log24.info("notifications.found", { projectId, count: notifs.length });
15271
- const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15399
+ const run = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
15272
15400
  if (!run) {
15273
15401
  log24.error("run.not-found", { runId, msg: "skipping notification dispatch" });
15274
15402
  return;
15275
15403
  }
15276
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15404
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
15277
15405
  if (!project) {
15278
15406
  log24.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
15279
15407
  return;
@@ -15352,7 +15480,7 @@ var Notifier = class {
15352
15480
  * than inferred from the absence of a webhook.
15353
15481
  */
15354
15482
  async onHealthChecked(projectId, report) {
15355
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15483
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
15356
15484
  if (!project) {
15357
15485
  log24.error("project.not-found", { projectId, msg: "skipping health notification" });
15358
15486
  return null;
@@ -15385,7 +15513,7 @@ var Notifier = class {
15385
15513
  const status = noSignal ? "warn" : worst ? worst.status : "ok";
15386
15514
  const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
15387
15515
  const summary = noSignal ? `No health check produced a result (${healthChecks.length} skipped) \u2014 health is unknown, not confirmed.` : worst?.summary ?? `All ${graded.length} health check(s) passing.`;
15388
- const previous = this.db.select().from(doctorHealthState).where(eq25(doctorHealthState.projectId, projectId)).get();
15516
+ const previous = this.db.select().from(doctorHealthState).where(eq27(doctorHealthState.projectId, projectId)).get();
15389
15517
  const previousStatus = previous?.status ?? null;
15390
15518
  const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
15391
15519
  const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
@@ -15455,7 +15583,7 @@ var Notifier = class {
15455
15583
  const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
15456
15584
  const generalEvent = event;
15457
15585
  const urgentRecord = previous?.urgentNotified ?? null;
15458
- const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15586
+ const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15459
15587
  const listedEvents = (notif) => notif.config.events;
15460
15588
  const hasUrl = (notif) => Boolean(notif.config.url);
15461
15589
  const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15503,7 +15631,7 @@ var Notifier = class {
15503
15631
  if (previous === void 0) {
15504
15632
  this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
15505
15633
  } else {
15506
- this.db.update(doctorHealthState).set(observation).where(eq25(doctorHealthState.projectId, projectId)).run();
15634
+ this.db.update(doctorHealthState).set(observation).where(eq27(doctorHealthState.projectId, projectId)).run();
15507
15635
  }
15508
15636
  for (const group of urgentGroups) {
15509
15637
  const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
@@ -15547,7 +15675,7 @@ var Notifier = class {
15547
15675
  }
15548
15676
  if (accepted > 0) {
15549
15677
  recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
15550
- this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq25(doctorHealthState.projectId, projectId)).run();
15678
+ this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq27(doctorHealthState.projectId, projectId)).run();
15551
15679
  }
15552
15680
  if (urgentNotifs.length > 0) {
15553
15681
  log24.info("health.urgent-notified", {
@@ -15591,7 +15719,7 @@ var Notifier = class {
15591
15719
  if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
15592
15720
  }
15593
15721
  if (delivered > 0) {
15594
- this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq25(doctorHealthState.projectId, projectId)).run();
15722
+ this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq27(doctorHealthState.projectId, projectId)).run();
15595
15723
  }
15596
15724
  log24.info("health.notified", {
15597
15725
  projectId,
@@ -15621,12 +15749,12 @@ var Notifier = class {
15621
15749
  async onSiteLivenessChecked(projectId, result) {
15622
15750
  const { check, checkedAt } = result;
15623
15751
  if (check.status !== "ok" && check.status !== "fail") return null;
15624
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15752
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
15625
15753
  if (!project) {
15626
15754
  log24.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15627
15755
  return null;
15628
15756
  }
15629
- const previous = this.db.select().from(siteLivenessState).where(eq25(siteLivenessState.projectId, projectId)).get();
15757
+ const previous = this.db.select().from(siteLivenessState).where(eq27(siteLivenessState.projectId, projectId)).get();
15630
15758
  const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
15631
15759
  const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
15632
15760
  const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
@@ -15636,7 +15764,7 @@ var Notifier = class {
15636
15764
  if (check.status === "ok" && outagePaged) event = "health.recovered";
15637
15765
  const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
15638
15766
  const urgentRecord = previous?.urgentNotifiedAt ?? null;
15639
- const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15767
+ const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15640
15768
  const listedEvents = (notif) => notif.config.events;
15641
15769
  const hasUrl = (notif) => Boolean(notif.config.url);
15642
15770
  const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15669,7 +15797,7 @@ var Notifier = class {
15669
15797
  if (previous === void 0) {
15670
15798
  this.db.insert(siteLivenessState).values(observation).run();
15671
15799
  } else {
15672
- this.db.update(siteLivenessState).set(observation).where(eq25(siteLivenessState.projectId, projectId)).run();
15800
+ this.db.update(siteLivenessState).set(observation).where(eq27(siteLivenessState.projectId, projectId)).run();
15673
15801
  }
15674
15802
  if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
15675
15803
  const payloadFor = (name) => {
@@ -15705,7 +15833,7 @@ var Notifier = class {
15705
15833
  }
15706
15834
  }
15707
15835
  if (event === "health.degraded" && delivered > 0) {
15708
- this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq25(siteLivenessState.projectId, projectId)).run();
15836
+ this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq27(siteLivenessState.projectId, projectId)).run();
15709
15837
  }
15710
15838
  log24.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15711
15839
  }
@@ -15719,7 +15847,7 @@ var Notifier = class {
15719
15847
  }
15720
15848
  if (accepted > 0) {
15721
15849
  recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
15722
- this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq25(siteLivenessState.projectId, projectId)).run();
15850
+ this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq27(siteLivenessState.projectId, projectId)).run();
15723
15851
  }
15724
15852
  if (urgentNotifs.length > 0 || generalIds.size > 0) {
15725
15853
  log24.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
@@ -15745,7 +15873,7 @@ var Notifier = class {
15745
15873
  */
15746
15874
  isNewInsightNews(projectId, insight) {
15747
15875
  const key = insightNotifyKey(projectId, insight);
15748
- const previous = this.db.select().from(insightNotifyState).where(eq25(insightNotifyState.key, key)).get();
15876
+ const previous = this.db.select().from(insightNotifyState).where(eq27(insightNotifyState.key, key)).get();
15749
15877
  if (!previous) return true;
15750
15878
  if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
15751
15879
  const magnitude = insightMagnitude(insight);
@@ -15767,9 +15895,9 @@ var Notifier = class {
15767
15895
  magnitude: insightMagnitude(insight),
15768
15896
  notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
15769
15897
  };
15770
- const existing = this.db.select().from(insightNotifyState).where(eq25(insightNotifyState.key, key)).get();
15898
+ const existing = this.db.select().from(insightNotifyState).where(eq27(insightNotifyState.key, key)).get();
15771
15899
  if (existing) {
15772
- this.db.update(insightNotifyState).set(row).where(eq25(insightNotifyState.key, key)).run();
15900
+ this.db.update(insightNotifyState).set(row).where(eq27(insightNotifyState.key, key)).run();
15773
15901
  } else {
15774
15902
  this.db.insert(insightNotifyState).values(row).run();
15775
15903
  }
@@ -15783,11 +15911,11 @@ var Notifier = class {
15783
15911
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
15784
15912
  if (highInsights.length > 0) insightEvents.push("insight.high");
15785
15913
  if (insightEvents.length === 0) return;
15786
- const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15914
+ const notifs = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15787
15915
  if (notifs.length === 0) return;
15788
- const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15916
+ const run = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
15789
15917
  if (!run) return;
15790
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15918
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
15791
15919
  if (!project) return;
15792
15920
  for (const notif of notifs) {
15793
15921
  const config = notif.config;
@@ -15868,9 +15996,9 @@ var Notifier = class {
15868
15996
  return delivered;
15869
15997
  }
15870
15998
  async dispatchQueuedReviewAlerts(projectId) {
15871
- const pendingReviews = this.db.select().from(gbpReviews).where(and22(eq25(gbpReviews.projectId, projectId), eq25(gbpReviews.alertState, "pending"))).all();
15872
- const pendingRatings = this.db.select().from(gbpReviewRatings).where(and22(eq25(gbpReviewRatings.projectId, projectId), eq25(gbpReviewRatings.alertState, "pending"))).all();
15873
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15999
+ const pendingReviews = this.db.select().from(gbpReviews).where(and24(eq27(gbpReviews.projectId, projectId), eq27(gbpReviews.alertState, "pending"))).all();
16000
+ const pendingRatings = this.db.select().from(gbpReviewRatings).where(and24(eq27(gbpReviewRatings.projectId, projectId), eq27(gbpReviewRatings.alertState, "pending"))).all();
16001
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
15874
16002
  if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
15875
16003
  const chatSendsLeft = /* @__PURE__ */ new Map();
15876
16004
  const now = /* @__PURE__ */ new Date();
@@ -15882,10 +16010,10 @@ var Notifier = class {
15882
16010
  displayName: gbpLocations.displayName,
15883
16011
  mapsUri: gbpLocations.mapsUri,
15884
16012
  reviewsAccess: gbpLocations.reviewsAccess
15885
- }).from(gbpLocations).where(eq25(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
16013
+ }).from(gbpLocations).where(eq27(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15886
16014
  );
15887
16015
  const v4HasWorked = new Set(
15888
- this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and22(eq25(gbpReviewRatings.projectId, projectId), eq25(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
16016
+ this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and24(eq27(gbpReviewRatings.projectId, projectId), eq27(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
15889
16017
  );
15890
16018
  const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
15891
16019
  const locationRef = (locationName) => {
@@ -15914,7 +16042,7 @@ var Notifier = class {
15914
16042
  else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
15915
16043
  else ratingsToSend.push(row);
15916
16044
  }
15917
- const enabled = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
16045
+ const enabled = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15918
16046
  const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
15919
16047
  const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
15920
16048
  const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
@@ -15968,13 +16096,13 @@ var Notifier = class {
15968
16096
  if (!sent) all = false;
15969
16097
  }
15970
16098
  if (all) {
15971
- this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and22(
15972
- eq25(gbpReviews.id, review.id),
15973
- eq25(gbpReviews.alertState, "pending"),
15974
- eq25(gbpReviews.updateTime, review.updateTime),
15975
- review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq25(gbpReviews.alertStateAt, review.alertStateAt),
15976
- review.comment === null ? isNull5(gbpReviews.comment) : eq25(gbpReviews.comment, review.comment),
15977
- review.starRating === null ? isNull5(gbpReviews.starRating) : eq25(gbpReviews.starRating, review.starRating)
16099
+ this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and24(
16100
+ eq27(gbpReviews.id, review.id),
16101
+ eq27(gbpReviews.alertState, "pending"),
16102
+ eq27(gbpReviews.updateTime, review.updateTime),
16103
+ review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq27(gbpReviews.alertStateAt, review.alertStateAt),
16104
+ review.comment === null ? isNull5(gbpReviews.comment) : eq27(gbpReviews.comment, review.comment),
16105
+ review.starRating === null ? isNull5(gbpReviews.starRating) : eq27(gbpReviews.starRating, review.starRating)
15978
16106
  )).run();
15979
16107
  }
15980
16108
  }
@@ -16015,7 +16143,7 @@ var Notifier = class {
16015
16143
  return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
16016
16144
  }
16017
16145
  computeTransitions(runId, projectId) {
16018
- const thisRun = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
16146
+ const thisRun = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
16019
16147
  if (!thisRun) return [];
16020
16148
  const completeness = measurementRunCompleteness(this.db, runId);
16021
16149
  if (completeness.planned && !completeness.complete) {
@@ -16026,10 +16154,10 @@ var Notifier = class {
16026
16154
  });
16027
16155
  return [];
16028
16156
  }
16029
- const groupSiblings = this.db.select().from(runs).where(and22(
16030
- eq25(runs.projectId, projectId),
16031
- eq25(runs.kind, thisRun.kind),
16032
- eq25(runs.createdAt, thisRun.createdAt),
16157
+ const groupSiblings = this.db.select().from(runs).where(and24(
16158
+ eq27(runs.projectId, projectId),
16159
+ eq27(runs.kind, thisRun.kind),
16160
+ eq27(runs.createdAt, thisRun.createdAt),
16033
16161
  notProbeRun()
16034
16162
  )).all();
16035
16163
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
@@ -16046,20 +16174,20 @@ var Notifier = class {
16046
16174
  return candidate.id > best.id ? candidate : best;
16047
16175
  });
16048
16176
  if (winner.id !== runId) return [];
16049
- const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq25(projects.id, projectId)).get();
16177
+ const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq27(projects.id, projectId)).get();
16050
16178
  const locationCount = Math.max(
16051
16179
  1,
16052
16180
  (projectLocations?.locations ?? []).length
16053
16181
  );
16054
16182
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
16055
16183
  const recentRuns = this.db.select().from(runs).where(
16056
- and22(
16057
- eq25(runs.projectId, projectId),
16058
- eq25(runs.kind, thisRun.kind),
16059
- or2(eq25(runs.status, "completed"), eq25(runs.status, "partial")),
16184
+ and24(
16185
+ eq27(runs.projectId, projectId),
16186
+ eq27(runs.kind, thisRun.kind),
16187
+ or2(eq27(runs.status, "completed"), eq27(runs.status, "partial")),
16060
16188
  notProbeRun()
16061
16189
  )
16062
- ).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16190
+ ).orderBy(desc10(runs.createdAt), desc10(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16063
16191
  const groups = groupRunsByCreatedAt(recentRuns);
16064
16192
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
16065
16193
  if (currentGroupIdx < 0) return [];
@@ -16074,7 +16202,7 @@ var Notifier = class {
16074
16202
  provider: querySnapshots.provider,
16075
16203
  location: querySnapshots.location,
16076
16204
  citationState: querySnapshots.citationState
16077
- }).from(querySnapshots).leftJoin(queries, eq25(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
16205
+ }).from(querySnapshots).leftJoin(queries, eq27(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
16078
16206
  const previousSnapshots = this.db.select({
16079
16207
  queryId: querySnapshots.queryId,
16080
16208
  provider: querySnapshots.provider,
@@ -16125,12 +16253,12 @@ var Notifier = class {
16125
16253
  recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
16126
16254
  if (due.length === 0) continue;
16127
16255
  const names = due.map((agent) => agent.name);
16128
- const subscribed = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("traffic.agent.discovered"));
16256
+ const subscribed = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("traffic.agent.discovered"));
16129
16257
  if (subscribed.length === 0) {
16130
16258
  recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
16131
16259
  continue;
16132
16260
  }
16133
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
16261
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
16134
16262
  if (!project) continue;
16135
16263
  const payload = {
16136
16264
  source: "canonry",
@@ -16153,6 +16281,73 @@ var Notifier = class {
16153
16281
  }
16154
16282
  return sentNames;
16155
16283
  }
16284
+ /**
16285
+ * After a site audit, send `site-health.issue.new` to the webhooks that list
16286
+ * it when the scan found a critical page problem on a page where it was not
16287
+ * reported before.
16288
+ *
16289
+ * What was reported lives in `site_health_notify_state`. It changes when a
16290
+ * subscriber accepts an alert, so a refused alert is owed again after the
16291
+ * next audit, and when a complete scan no longer finds a problem, so the
16292
+ * problem is reported again if it comes back. A partial scan may have missed
16293
+ * pages, so it never clears anything. Nothing is recorded while no webhook
16294
+ * subscribes: a new subscriber hears about every current problem once.
16295
+ *
16296
+ * Returns the event sent, or null when nothing was sent, so "stayed quiet" is
16297
+ * observable in tests.
16298
+ */
16299
+ async onSiteAuditCompleted(runId, projectId) {
16300
+ const run = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
16301
+ if (!run || run.kind !== RunKinds["site-audit"]) return null;
16302
+ if (run.status !== RunStatuses.completed && run.status !== RunStatuses.partial) return null;
16303
+ const subscribed = this.db.select().from(notifications).where(eq27(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("site-health.issue.new"));
16304
+ if (subscribed.length === 0) return null;
16305
+ const project = this.db.select().from(projects).where(eq27(projects.id, projectId)).get();
16306
+ if (!project) return null;
16307
+ const scan = readSiteHealthScan(this.db, projectId, runId);
16308
+ if (!scan) return null;
16309
+ const state = readSiteHealthNotifyState(this.db, projectId);
16310
+ const reported = new Set(state?.reportedKeys ?? []);
16311
+ const now = (/* @__PURE__ */ new Date()).toISOString();
16312
+ const issues = newSiteHealthIssues(scan, reported);
16313
+ if (issues.length === 0) {
16314
+ if (state && scan.complete) {
16315
+ writeSiteHealthNotifyState(this.db, {
16316
+ projectId,
16317
+ runId,
16318
+ reportedKeys: settledSiteHealthKeys(scan, reported),
16319
+ updatedAt: now,
16320
+ notifiedAt: state.notifiedAt
16321
+ });
16322
+ }
16323
+ return null;
16324
+ }
16325
+ const payload = {
16326
+ source: "canonry",
16327
+ event: "site-health.issue.new",
16328
+ project: { name: project.name, canonicalDomain: project.canonicalDomain },
16329
+ run: { id: run.id, status: run.status, finishedAt: run.finishedAt },
16330
+ scanComplete: scan.complete,
16331
+ issues,
16332
+ dashboardUrl: `${this.serverUrl}/projects/${project.name}/technical-aeo`
16333
+ };
16334
+ let accepted = false;
16335
+ for (const notif of subscribed) {
16336
+ if (await this.sendWebhook(notif.config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) accepted = true;
16337
+ }
16338
+ if (!accepted) {
16339
+ log24.info("site-health.alert-owed", { runId, projectId, issues: issues.length });
16340
+ return null;
16341
+ }
16342
+ writeSiteHealthNotifyState(this.db, {
16343
+ projectId,
16344
+ runId,
16345
+ reportedKeys: settledSiteHealthKeys(scan, reported),
16346
+ updatedAt: now,
16347
+ notifiedAt: now
16348
+ });
16349
+ return "site-health.issue.new";
16350
+ }
16156
16351
  async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
16157
16352
  const elapsed = startOutcomeTimer();
16158
16353
  const destination = resolveDestination(url);
@@ -16220,7 +16415,7 @@ var Notifier = class {
16220
16415
  }
16221
16416
  logDelivery(projectId, notificationId, event, status, error) {
16222
16417
  this.db.insert(auditLog).values({
16223
- id: crypto22.randomUUID(),
16418
+ id: crypto23.randomUUID(),
16224
16419
  projectId,
16225
16420
  actor: "scheduler",
16226
16421
  action: `notification.${status}`,
@@ -16233,7 +16428,7 @@ var Notifier = class {
16233
16428
  };
16234
16429
 
16235
16430
  // src/run-coordinator.ts
16236
- import { eq as eq26 } from "drizzle-orm";
16431
+ import { eq as eq28 } from "drizzle-orm";
16237
16432
  var log25 = createLogger("RunCoordinator");
16238
16433
  function reportInsightsGenerated(runTrigger, outcome) {
16239
16434
  trackFeatureCompleted({
@@ -16267,7 +16462,7 @@ var RunCoordinator = class {
16267
16462
  */
16268
16463
  onAnswersRecorded;
16269
16464
  async onRunCompleted(runId, projectId, opts) {
16270
- const runRow = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
16465
+ const runRow = this.db.select().from(runs).where(eq28(runs.id, runId)).get();
16271
16466
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
16272
16467
  if (runRow?.trigger === RunTriggers.probe) {
16273
16468
  log25.info("probe.skip-side-effects", { runId, projectId, kind });
@@ -16361,6 +16556,13 @@ var RunCoordinator = class {
16361
16556
  } catch (err) {
16362
16557
  log25.error("notifier.failed", { runId, error: describeError(err) });
16363
16558
  }
16559
+ if (kind === RunKinds["site-audit"]) {
16560
+ try {
16561
+ await this.notifier.onSiteAuditCompleted(runId, projectId);
16562
+ } catch (err) {
16563
+ log25.error("site-health-webhook.failed", { runId, error: describeError(err) });
16564
+ }
16565
+ }
16364
16566
  if (this.onAeroEvent) {
16365
16567
  try {
16366
16568
  const ctx = kind === RunKinds["aeo-discover-probe"] ? this.buildDiscoveryAeroContext(runId, projectId, runRow?.status === "failed" ? "failed" : "completed", runRow?.error ?? null) : {
@@ -16387,7 +16589,7 @@ var RunCoordinator = class {
16387
16589
  * so the Aero queue is never starved of a follow-up.
16388
16590
  */
16389
16591
  buildDiscoveryAeroContext(runId, projectId, status, error) {
16390
- const session = this.db.select().from(discoverySessions).where(eq26(discoverySessions.runId, runId)).get();
16592
+ const session = this.db.select().from(discoverySessions).where(eq28(discoverySessions.runId, runId)).get();
16391
16593
  const competitorMap = session ? session.competitorMap : [];
16392
16594
  return {
16393
16595
  kind: RunKinds["aeo-discover-probe"],
@@ -16429,8 +16631,8 @@ function analysisResultFromInsights(insights2) {
16429
16631
  }
16430
16632
 
16431
16633
  // src/agent/session-registry.ts
16432
- import crypto24 from "crypto";
16433
- import { eq as eq29 } from "drizzle-orm";
16634
+ import crypto25 from "crypto";
16635
+ import { eq as eq31 } from "drizzle-orm";
16434
16636
 
16435
16637
  // src/agent/remote-mcp.ts
16436
16638
  import { Type } from "@earendil-works/pi-ai";
@@ -16534,8 +16736,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
16534
16736
  }
16535
16737
 
16536
16738
  // src/agent/memory-store.ts
16537
- import crypto23 from "crypto";
16538
- import { and as and23, desc as desc10, eq as eq27, like, sql as sql12, or as or3 } from "drizzle-orm";
16739
+ import crypto24 from "crypto";
16740
+ import { and as and25, desc as desc11, eq as eq29, like, sql as sql12, or as or3 } from "drizzle-orm";
16539
16741
  var COMPACTION_KEY_PREFIX = "compaction:";
16540
16742
  var COMPACTION_NOTES_PER_SESSION = 3;
16541
16743
  function rowToDto(row) {
@@ -16549,7 +16751,7 @@ function rowToDto(row) {
16549
16751
  };
16550
16752
  }
16551
16753
  function listMemoryEntries(db, projectId, opts = {}) {
16552
- const query = db.select().from(agentMemory).where(eq27(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
16754
+ const query = db.select().from(agentMemory).where(eq29(agentMemory.projectId, projectId)).orderBy(desc11(agentMemory.updatedAt));
16553
16755
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
16554
16756
  return rows.map(rowToDto);
16555
16757
  }
@@ -16563,7 +16765,7 @@ function upsertMemoryEntry(db, args) {
16563
16765
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
16564
16766
  }
16565
16767
  const now = (/* @__PURE__ */ new Date()).toISOString();
16566
- const id = crypto23.randomUUID();
16768
+ const id = crypto24.randomUUID();
16567
16769
  db.insert(agentMemory).values({
16568
16770
  id,
16569
16771
  projectId: args.projectId,
@@ -16580,22 +16782,22 @@ function upsertMemoryEntry(db, args) {
16580
16782
  updatedAt: now
16581
16783
  }
16582
16784
  }).run();
16583
- const row = db.select().from(agentMemory).where(and23(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, args.key))).get();
16785
+ const row = db.select().from(agentMemory).where(and25(eq29(agentMemory.projectId, args.projectId), eq29(agentMemory.key, args.key))).get();
16584
16786
  if (!row) throw new Error("memory upsert produced no row");
16585
16787
  return rowToDto(row);
16586
16788
  }
16587
16789
  function deleteMemoryEntry(db, projectId, key) {
16588
- const result = db.delete(agentMemory).where(and23(eq27(agentMemory.projectId, projectId), eq27(agentMemory.key, key))).run();
16790
+ const result = db.delete(agentMemory).where(and25(eq29(agentMemory.projectId, projectId), eq29(agentMemory.key, key))).run();
16589
16791
  const changes = result.changes ?? 0;
16590
16792
  return changes > 0;
16591
16793
  }
16592
16794
  function loadRecentForHydrate(db, projectId, limit, sessionId) {
16593
16795
  if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
16594
16796
  const sessionPrefix = `compaction:${sessionId ?? ""}:`;
16595
- return db.select().from(agentMemory).where(and23(
16596
- eq27(agentMemory.projectId, projectId),
16797
+ return db.select().from(agentMemory).where(and25(
16798
+ eq29(agentMemory.projectId, projectId),
16597
16799
  or3(sql12`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql12`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
16598
- )).orderBy(desc10(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16800
+ )).orderBy(desc11(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16599
16801
  }
16600
16802
  function writeCompactionNote(db, args) {
16601
16803
  if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
@@ -16605,7 +16807,7 @@ function writeCompactionNote(db, args) {
16605
16807
  }
16606
16808
  const now = (/* @__PURE__ */ new Date()).toISOString();
16607
16809
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
16608
- const id = crypto23.randomUUID();
16810
+ const id = crypto24.randomUUID();
16609
16811
  let inserted;
16610
16812
  db.transaction((tx) => {
16611
16813
  tx.insert(agentMemory).values({
@@ -16619,16 +16821,16 @@ function writeCompactionNote(db, args) {
16619
16821
  }).run();
16620
16822
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
16621
16823
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
16622
- and23(
16623
- eq27(agentMemory.projectId, args.projectId),
16824
+ and25(
16825
+ eq29(agentMemory.projectId, args.projectId),
16624
16826
  like(agentMemory.key, `${sessionPrefix}%`)
16625
16827
  )
16626
- ).orderBy(desc10(agentMemory.updatedAt)).all();
16828
+ ).orderBy(desc11(agentMemory.updatedAt)).all();
16627
16829
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
16628
16830
  if (stale.length > 0) {
16629
16831
  tx.delete(agentMemory).where(sql12`${agentMemory.id} IN (${sql12.join(stale.map((s) => sql12`${s}`), sql12`, `)})`).run();
16630
16832
  }
16631
- const row = tx.select().from(agentMemory).where(and23(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, key))).get();
16833
+ const row = tx.select().from(agentMemory).where(and25(eq29(agentMemory.projectId, args.projectId), eq29(agentMemory.key, key))).get();
16632
16834
  if (row) inserted = rowToDto(row);
16633
16835
  });
16634
16836
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -16702,7 +16904,7 @@ function trackAeroTurn(agent, turn) {
16702
16904
  }
16703
16905
 
16704
16906
  // src/agent/project-shape.ts
16705
- import { and as and24, count as count5, eq as eq28 } from "drizzle-orm";
16907
+ import { and as and26, count as count5, eq as eq30 } from "drizzle-orm";
16706
16908
  var ADVANCED_TOOLS = [
16707
16909
  "canonry_measurement_portfolio_summary",
16708
16910
  "canonry_measurement_overview",
@@ -16729,10 +16931,10 @@ var ADVANCED_ROUTES = [
16729
16931
  ];
16730
16932
  function aeroProjectShape(db, projectId) {
16731
16933
  try {
16732
- const pointer = db.select().from(measurementPlans).where(eq28(measurementPlans.projectId, projectId)).get();
16733
- const version = pointer ? db.select().from(measurementPlanVersions).where(and24(
16734
- eq28(measurementPlanVersions.projectId, projectId),
16735
- eq28(measurementPlanVersions.id, pointer.activeVersionId)
16934
+ const pointer = db.select().from(measurementPlans).where(eq30(measurementPlans.projectId, projectId)).get();
16935
+ const version = pointer ? db.select().from(measurementPlanVersions).where(and26(
16936
+ eq30(measurementPlanVersions.projectId, projectId),
16937
+ eq30(measurementPlanVersions.id, pointer.activeVersionId)
16736
16938
  )).get() : void 0;
16737
16939
  if (version) {
16738
16940
  const plan = JSON.parse(version.canonicalJson);
@@ -16759,7 +16961,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
16759
16961
  pinned: ADVANCED_TOOLS
16760
16962
  };
16761
16963
  }
16762
- const tracked = db.select({ total: count5() }).from(queries).where(eq28(queries.projectId, projectId)).get()?.total ?? 0;
16964
+ const tracked = db.select({ total: count5() }).from(queries).where(eq30(queries.projectId, projectId)).get()?.total ?? 0;
16763
16965
  return {
16764
16966
  prompt: `
16765
16967
 
@@ -17018,7 +17220,7 @@ var SessionRegistry = class {
17018
17220
  modelProvider: effectiveProvider,
17019
17221
  modelId: effectiveModelId,
17020
17222
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17021
- }).where(eq29(agentSessions.projectId, projectId)).run();
17223
+ }).where(eq31(agentSessions.projectId, projectId)).run();
17022
17224
  }
17023
17225
  const agent2 = createAeroSession({
17024
17226
  projectName,
@@ -17047,7 +17249,7 @@ var SessionRegistry = class {
17047
17249
  return agent2;
17048
17250
  }
17049
17251
  const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
17050
- const sessionId = crypto24.randomUUID();
17252
+ const sessionId = crypto25.randomUUID();
17051
17253
  const agent = createAeroSession({
17052
17254
  projectName,
17053
17255
  client: this.opts.client,
@@ -17337,7 +17539,7 @@ ${lines.join("\n")}
17337
17539
  modelProvider: nextProvider,
17338
17540
  modelId: nextModelId,
17339
17541
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17340
- }).where(eq29(agentSessions.projectId, projectId)).run();
17542
+ }).where(eq31(agentSessions.projectId, projectId)).run();
17341
17543
  }
17342
17544
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
17343
17545
  prepareConversationChange(projectName) {
@@ -17545,7 +17747,7 @@ ${lines.join("\n")}
17545
17747
  providerAccountKey(provider, apiKey) {
17546
17748
  const canonical = coerceAgentProvider(provider);
17547
17749
  const vendor = canonical ? getAgentProvider(canonical).piAiProvider : provider;
17548
- return crypto24.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
17750
+ return crypto25.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
17549
17751
  }
17550
17752
  providerCoolingDown(projectName) {
17551
17753
  for (const [key, cooldown] of this.providerCooldowns) {
@@ -17562,18 +17764,18 @@ ${lines.join("\n")}
17562
17764
  return id;
17563
17765
  }
17564
17766
  tryResolveProjectId(projectName) {
17565
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq29(projects.name, projectName)).get();
17767
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq31(projects.name, projectName)).get();
17566
17768
  return row?.id;
17567
17769
  }
17568
17770
  loadRow(projectId) {
17569
- const row = this.opts.db.select().from(agentSessions).where(eq29(agentSessions.projectId, projectId)).get();
17771
+ const row = this.opts.db.select().from(agentSessions).where(eq31(agentSessions.projectId, projectId)).get();
17570
17772
  return row ?? null;
17571
17773
  }
17572
17774
  insertRow(params) {
17573
17775
  const now = (/* @__PURE__ */ new Date()).toISOString();
17574
17776
  const modelProvider = params.provider ?? params.modelProvider ?? AgentProviderIds.claude;
17575
17777
  this.opts.db.insert(agentSessions).values({
17576
- id: params.id ?? crypto24.randomUUID(),
17778
+ id: params.id ?? crypto25.randomUUID(),
17577
17779
  projectId: params.projectId,
17578
17780
  systemPrompt: params.systemPrompt,
17579
17781
  modelProvider,
@@ -17586,7 +17788,7 @@ ${lines.join("\n")}
17586
17788
  }
17587
17789
  updateRow(projectId, patch) {
17588
17790
  const now = (/* @__PURE__ */ new Date()).toISOString();
17589
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq29(agentSessions.projectId, projectId)).run();
17791
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq31(agentSessions.projectId, projectId)).run();
17590
17792
  }
17591
17793
  /**
17592
17794
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -17595,13 +17797,13 @@ ${lines.join("\n")}
17595
17797
  * recently used right after an upgrade.
17596
17798
  */
17597
17799
  persistPromptSnapshot(projectId, systemPrompt) {
17598
- this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq29(agentSessions.projectId, projectId)).run();
17800
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq31(agentSessions.projectId, projectId)).run();
17599
17801
  }
17600
17802
  };
17601
17803
 
17602
17804
  // src/agent/viewer-sessions.ts
17603
- import crypto25 from "crypto";
17604
- import { and as and25, eq as eq30, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
17805
+ import crypto26 from "crypto";
17806
+ import { and as and27, eq as eq32, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
17605
17807
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
17606
17808
  CanonryMcpToolNames.canonry_ads_account,
17607
17809
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -17654,8 +17856,8 @@ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
17654
17856
  }
17655
17857
  var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
17656
17858
  function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
17657
- const raw = `cnry_${crypto25.randomBytes(24).toString("hex")}`;
17658
- const id = crypto25.randomUUID();
17859
+ const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17860
+ const id = crypto26.randomUUID();
17659
17861
  db.insert(apiKeys).values({
17660
17862
  id,
17661
17863
  name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
@@ -17778,12 +17980,12 @@ var ViewerAeroSessions = class {
17778
17980
  */
17779
17981
  removeKey(id) {
17780
17982
  try {
17781
- this.opts.db.delete(apiKeys).where(eq30(apiKeys.id, id)).run();
17983
+ this.opts.db.delete(apiKeys).where(eq32(apiKeys.id, id)).run();
17782
17984
  } catch {
17783
17985
  try {
17784
17986
  this.opts.db.transaction((tx) => {
17785
- tx.delete(dashboardSessions).where(eq30(dashboardSessions.apiKeyId, id)).run();
17786
- tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq30(apiKeys.id, id)).run();
17987
+ tx.delete(dashboardSessions).where(eq32(dashboardSessions.apiKeyId, id)).run();
17988
+ tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq32(apiKeys.id, id)).run();
17787
17989
  });
17788
17990
  } catch {
17789
17991
  }
@@ -17796,7 +17998,7 @@ var ViewerAeroSessions = class {
17796
17998
  */
17797
17999
  revokeOrphanedKeys() {
17798
18000
  try {
17799
- this.opts.db.delete(apiKeys).where(and25(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
18001
+ this.opts.db.delete(apiKeys).where(and27(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17800
18002
  } catch {
17801
18003
  }
17802
18004
  }
@@ -17811,9 +18013,9 @@ var ViewerAeroSessions = class {
17811
18013
  };
17812
18014
 
17813
18015
  // src/mcp-http.ts
17814
- import crypto26 from "crypto";
18016
+ import crypto27 from "crypto";
17815
18017
  import { isIP } from "net";
17816
- import { and as and26, eq as eq31, isNull as isNull6 } from "drizzle-orm";
18018
+ import { and as and28, eq as eq33, isNull as isNull6 } from "drizzle-orm";
17817
18019
  import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
17818
18020
  import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
17819
18021
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -17822,8 +18024,8 @@ var MAX_CLEANUP_RETRIES_PER_SWEEP = 8;
17822
18024
  var MAX_PENDING_KEY_RETIREMENTS = 256;
17823
18025
  var MAX_CLEANUP_BACKOFF_MS = 10 * 60 * 1e3;
17824
18026
  function mintSessionKey(db, scopes, userId, oauthGrantId, createdAt) {
17825
- const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17826
- const id = crypto26.randomUUID();
18027
+ const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
18028
+ const id = crypto27.randomUUID();
17827
18029
  db.insert(apiKeys).values({
17828
18030
  id,
17829
18031
  name: `mcp-session:${userId}`,
@@ -17919,12 +18121,12 @@ function registerMcpHttpRoutes(scope, opts) {
17919
18121
  try {
17920
18122
  withoutLockWait(() => {
17921
18123
  opts.db.transaction((tx) => {
17922
- tx.delete(dashboardSessions).where(eq31(dashboardSessions.apiKeyId, id)).run();
17923
- tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and26(eq31(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
18124
+ tx.delete(dashboardSessions).where(eq33(dashboardSessions.apiKeyId, id)).run();
18125
+ tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and28(eq33(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
17924
18126
  });
17925
18127
  if (remove) {
17926
18128
  try {
17927
- opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
18129
+ opts.db.delete(apiKeys).where(eq33(apiKeys.id, id)).run();
17928
18130
  } catch (error) {
17929
18131
  scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
17930
18132
  }
@@ -17975,14 +18177,14 @@ function registerMcpHttpRoutes(scope, opts) {
17975
18177
  withoutLockWait(() => {
17976
18178
  const cutoff = now() - SESSION_IDLE_MS;
17977
18179
  for (const session of legacyServers) {
17978
- const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq31(apiKeys.id, session.sessionKeyId)).get() : void 0;
18180
+ const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq33(apiKeys.id, session.sessionKeyId)).get() : void 0;
17979
18181
  if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
17980
18182
  void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
17981
18183
  }
17982
18184
  }
17983
18185
  for (const exchange of modernExchanges) {
17984
18186
  if (!exchange.sessionKey) continue;
17985
- const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq31(apiKeys.id, exchange.sessionKey.id)).get();
18187
+ const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq33(apiKeys.id, exchange.sessionKey.id)).get();
17986
18188
  if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
17987
18189
  void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
17988
18190
  }
@@ -18037,7 +18239,7 @@ function registerMcpHttpRoutes(scope, opts) {
18037
18239
  clientName: `canonry-mcp/${PACKAGE_VERSION}`,
18038
18240
  surface: "mcp-http",
18039
18241
  // Correlation only: this is never accepted as caller identity or authority.
18040
- ...modern ? {} : { actorSession: crypto26.randomUUID() }
18242
+ ...modern ? {} : { actorSession: crypto27.randomUUID() }
18041
18243
  });
18042
18244
  let server;
18043
18245
  try {
@@ -18062,7 +18264,7 @@ function registerMcpHttpRoutes(scope, opts) {
18062
18264
  if (!bearer) return null;
18063
18265
  const { server, sessionKey } = createAuthorizedServer(request, segment, bearer);
18064
18266
  const transport = new NodeStreamableHTTPServerTransport({
18065
- sessionIdGenerator: () => crypto26.randomUUID(),
18267
+ sessionIdGenerator: () => crypto27.randomUUID(),
18066
18268
  onsessioninitialized: (id) => {
18067
18269
  if (!shuttingDown && !closing) sessions.set(id, session);
18068
18270
  }
@@ -18232,7 +18434,7 @@ function registerMcpHttpRoutes(scope, opts) {
18232
18434
  }
18233
18435
 
18234
18436
  // src/agent/agent-routes.ts
18235
- import { eq as eq32 } from "drizzle-orm";
18437
+ import { eq as eq34 } from "drizzle-orm";
18236
18438
  function viewerAeroCaller(request, opts) {
18237
18439
  if (!opts.viewerSessions) return null;
18238
18440
  const principal = request.principal;
@@ -18272,7 +18474,7 @@ function turnAttribution(request) {
18272
18474
  };
18273
18475
  }
18274
18476
  function resolveProject(db, name) {
18275
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq32(projects.name, name)).get();
18477
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq34(projects.name, name)).get();
18276
18478
  if (!row) throw notFound("project", name);
18277
18479
  return row;
18278
18480
  }
@@ -18391,7 +18593,7 @@ function registerAgentRoutes(app, opts) {
18391
18593
  }
18392
18594
  requireInstanceAdministrator(request);
18393
18595
  const project = resolveProject(opts.db, request.params.name);
18394
- const row = opts.db.select().from(agentSessions).where(eq32(agentSessions.projectId, project.id)).get();
18596
+ const row = opts.db.select().from(agentSessions).where(eq34(agentSessions.projectId, project.id)).get();
18395
18597
  const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
18396
18598
  if (!row) {
18397
18599
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
@@ -18433,7 +18635,7 @@ function registerAgentRoutes(app, opts) {
18433
18635
  const project = resolveProject(opts.db, request.params.name);
18434
18636
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
18435
18637
  opts.sessionRegistry.reset(project.name);
18436
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq32(agentSessions.projectId, project.id)).run();
18638
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq34(agentSessions.projectId, project.id)).run();
18437
18639
  return { status: "reset" };
18438
18640
  }
18439
18641
  );
@@ -18457,7 +18659,7 @@ function registerAgentRoutes(app, opts) {
18457
18659
  return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
18458
18660
  }
18459
18661
  if (body.conversationId !== void 0) {
18460
- const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq32(agentSessions.projectId, project.id)).get();
18662
+ const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq34(agentSessions.projectId, project.id)).get();
18461
18663
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
18462
18664
  }
18463
18665
  const promptText = body.prompt;
@@ -19417,7 +19619,7 @@ function clipText(value, length) {
19417
19619
  }
19418
19620
 
19419
19621
  // src/research-runner.ts
19420
- import { and as and27, eq as eq33, inArray as inArray13, sql as sql13 } from "drizzle-orm";
19622
+ import { and as and29, eq as eq35, inArray as inArray13, sql as sql13 } from "drizzle-orm";
19421
19623
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
19422
19624
  function finalResearchRunStatus(completed, failed, waiting) {
19423
19625
  if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
@@ -19425,13 +19627,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
19425
19627
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
19426
19628
  }
19427
19629
  async function executeResearchRun(db, registry, runId, projectId, options = {}) {
19428
- const run = db.select().from(researchRuns).where(and27(eq33(researchRuns.id, runId), eq33(researchRuns.projectId, projectId))).get();
19630
+ const run = db.select().from(researchRuns).where(and29(eq35(researchRuns.id, runId), eq35(researchRuns.projectId, projectId))).get();
19429
19631
  if (!run || run.status !== ResearchRunStatuses.queued) return;
19430
- const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
19632
+ const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
19431
19633
  if (!project) return;
19432
19634
  const provider = registry.get(run.provider);
19433
19635
  const now = (/* @__PURE__ */ new Date()).toISOString();
19434
- const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and27(eq33(researchRuns.id, runId), eq33(researchRuns.status, ResearchRunStatuses.queued))).run();
19636
+ const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and29(eq35(researchRuns.id, runId), eq35(researchRuns.status, ResearchRunStatuses.queued))).run();
19435
19637
  if (claim.changes !== 1) return;
19436
19638
  const elapsed = startOutcomeTimer();
19437
19639
  const requested = currentOutcomeOrigin();
@@ -19465,8 +19667,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19465
19667
  }
19466
19668
  reserved = run.totalQueries;
19467
19669
  reservation = { scope, period };
19468
- const rows = db.select().from(researchRunQueries).where(eq33(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
19469
- const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq33(competitors.projectId, projectId)).orderBy(competitors.domain).all();
19670
+ const rows = db.select().from(researchRunQueries).where(eq35(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
19671
+ const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq35(competitors.projectId, projectId)).orderBy(competitors.domain).all();
19470
19672
  const competitorDomains = competitorRows.map((row) => row.domain);
19471
19673
  const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
19472
19674
  const domains = effectiveDomains(project);
@@ -19476,7 +19678,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19476
19678
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
19477
19679
  try {
19478
19680
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
19479
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and27(eq33(researchRunQueries.id, row.id), eq33(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
19681
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and29(eq35(researchRunQueries.id, row.id), eq35(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
19480
19682
  const raw = await gate.run(async () => {
19481
19683
  dispatched++;
19482
19684
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -19507,7 +19709,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19507
19709
  rawResponse: raw.rawResponse,
19508
19710
  answerEvidence: raw.evidence ?? null,
19509
19711
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19510
- }).where(and27(eq33(researchRunQueries.id, row.id), eq33(researchRunQueries.status, ResearchQueryStatuses.running))).run();
19712
+ }).where(and29(eq35(researchRunQueries.id, row.id), eq35(researchRunQueries.status, ResearchQueryStatuses.running))).run();
19511
19713
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
19512
19714
  } catch (error) {
19513
19715
  if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
@@ -19558,11 +19760,11 @@ function researchOutcomeStatus(status) {
19558
19760
  }
19559
19761
  }
19560
19762
  function incrementResearchProgress(db, runId, column) {
19561
- db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(eq33(researchRuns.id, runId)).run();
19763
+ db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(eq35(researchRuns.id, runId)).run();
19562
19764
  }
19563
19765
  function markResearchQueryFailed(db, runId, queryId, error) {
19564
19766
  const message = describeError(error);
19565
- const unfinished = and27(eq33(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19767
+ const unfinished = and29(eq35(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19566
19768
  if (error instanceof IncompleteAnswerError && error.resumable) {
19567
19769
  db.update(researchRunQueries).set({
19568
19770
  status: ResearchQueryStatuses["waiting-upstream"],
@@ -19578,10 +19780,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
19578
19780
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
19579
19781
  }
19580
19782
  function markUnfinishedResearchQueriesFailed(db, runId, error) {
19581
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and27(eq33(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
19783
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and29(eq35(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
19582
19784
  }
19583
19785
  function finalizeResearchRun(db, runId, fatalError) {
19584
- const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq33(researchRunQueries.researchRunId, runId)).all();
19786
+ const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq35(researchRunQueries.researchRunId, runId)).all();
19585
19787
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
19586
19788
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
19587
19789
  const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
@@ -19593,12 +19795,12 @@ function finalizeResearchRun(db, runId, fatalError) {
19593
19795
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
19594
19796
  // A run waiting on upstream jobs has not finished.
19595
19797
  finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
19596
- }).where(eq33(researchRuns.id, runId)).run();
19798
+ }).where(eq35(researchRuns.id, runId)).run();
19597
19799
  return { status, total: rows.length, completed, failed };
19598
19800
  }
19599
19801
 
19600
19802
  // src/google-marketing-sync.ts
19601
- import { and as and28, eq as eq34, isNull as isNull7, sql as sql14 } from "drizzle-orm";
19803
+ import { and as and30, eq as eq36, isNull as isNull7, sql as sql14 } from "drizzle-orm";
19602
19804
  var RUNTIME_FAILURE_REASONS = {
19603
19805
  INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
19604
19806
  NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
@@ -19635,10 +19837,10 @@ function assertSnapshotContext(metadata, expected) {
19635
19837
  }
19636
19838
  }
19637
19839
  function requireSyncRun(db, runId, projectId, kind) {
19638
- const run = db.select().from(runs).where(and28(
19639
- eq34(runs.id, runId),
19640
- eq34(runs.projectId, projectId),
19641
- eq34(runs.kind, kind)
19840
+ const run = db.select().from(runs).where(and30(
19841
+ eq36(runs.id, runId),
19842
+ eq36(runs.projectId, projectId),
19843
+ eq36(runs.kind, kind)
19642
19844
  )).get();
19643
19845
  if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
19644
19846
  if (run.status !== RunStatuses.queued) {
@@ -19654,19 +19856,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
19654
19856
  status: RunStatuses.failed,
19655
19857
  error: serializeRunError({ message: safeErrorMessage(error) }),
19656
19858
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19657
- }).where(and28(
19658
- eq34(runs.id, runId),
19659
- eq34(runs.projectId, projectId),
19660
- eq34(runs.kind, kind),
19661
- eq34(runs.status, expectedStatus)
19859
+ }).where(and30(
19860
+ eq36(runs.id, runId),
19861
+ eq36(runs.projectId, projectId),
19862
+ eq36(runs.kind, kind),
19863
+ eq36(runs.status, expectedStatus)
19662
19864
  )).run();
19663
19865
  }
19664
19866
  function claimSyncRun(db, runId, projectId, kind, startedAt) {
19665
- const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and28(
19666
- eq34(runs.id, runId),
19667
- eq34(runs.projectId, projectId),
19668
- eq34(runs.kind, kind),
19669
- eq34(runs.status, RunStatuses.queued)
19867
+ const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and30(
19868
+ eq36(runs.id, runId),
19869
+ eq36(runs.projectId, projectId),
19870
+ eq36(runs.kind, kind),
19871
+ eq36(runs.status, RunStatuses.queued)
19670
19872
  )).run();
19671
19873
  return claim.changes === 1;
19672
19874
  }
@@ -19676,9 +19878,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19676
19878
  let evidenceCollected = false;
19677
19879
  try {
19678
19880
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
19679
- const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
19881
+ const project = db.select().from(projects).where(eq36(projects.id, projectId)).get();
19680
19882
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19681
- const connection = db.select().from(googleAdsConnections).where(eq34(googleAdsConnections.projectId, projectId)).get();
19883
+ const connection = db.select().from(googleAdsConnections).where(eq36(googleAdsConnections.projectId, projectId)).get();
19682
19884
  if (!connection?.selectedCustomerId) {
19683
19885
  throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19684
19886
  }
@@ -19730,11 +19932,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19730
19932
  status: RunStatuses.completed,
19731
19933
  finishedAt,
19732
19934
  error: null
19733
- }).where(and28(
19734
- eq34(runs.id, runId),
19735
- eq34(runs.projectId, projectId),
19736
- eq34(runs.kind, RunKinds["google-ads-sync"]),
19737
- eq34(runs.status, RunStatuses.running)
19935
+ }).where(and30(
19936
+ eq36(runs.id, runId),
19937
+ eq36(runs.projectId, projectId),
19938
+ eq36(runs.kind, RunKinds["google-ads-sync"]),
19939
+ eq36(runs.status, RunStatuses.running)
19738
19940
  )).run();
19739
19941
  if (terminalClaim.changes !== 1) {
19740
19942
  throw withOutcomeReason(
@@ -19754,12 +19956,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19754
19956
  lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
19755
19957
  lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
19756
19958
  updatedAt: finishedAt
19757
- }).where(and28(
19758
- eq34(googleAdsConnections.id, connection.id),
19759
- eq34(googleAdsConnections.projectId, projectId),
19760
- eq34(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19761
- connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq34(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19762
- eq34(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19959
+ }).where(and30(
19960
+ eq36(googleAdsConnections.id, connection.id),
19961
+ eq36(googleAdsConnections.projectId, projectId),
19962
+ eq36(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19963
+ connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq36(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19964
+ eq36(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19763
19965
  )).run();
19764
19966
  if (connectionClaim.changes !== 1) {
19765
19967
  throw withOutcomeReason(
@@ -19799,9 +20001,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19799
20001
  let evidenceCollected = false;
19800
20002
  try {
19801
20003
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
19802
- const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
20004
+ const project = db.select().from(projects).where(eq36(projects.id, projectId)).get();
19803
20005
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19804
- const connection = db.select().from(gtmConnections).where(eq34(gtmConnections.projectId, projectId)).get();
20006
+ const connection = db.select().from(gtmConnections).where(eq36(gtmConnections.projectId, projectId)).get();
19805
20007
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
19806
20008
  throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19807
20009
  }
@@ -19839,11 +20041,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19839
20041
  status: RunStatuses.completed,
19840
20042
  finishedAt,
19841
20043
  error: null
19842
- }).where(and28(
19843
- eq34(runs.id, runId),
19844
- eq34(runs.projectId, projectId),
19845
- eq34(runs.kind, RunKinds["gtm-sync"]),
19846
- eq34(runs.status, RunStatuses.running)
20044
+ }).where(and30(
20045
+ eq36(runs.id, runId),
20046
+ eq36(runs.projectId, projectId),
20047
+ eq36(runs.kind, RunKinds["gtm-sync"]),
20048
+ eq36(runs.status, RunStatuses.running)
19847
20049
  )).run();
19848
20050
  if (terminalClaim.changes !== 1) {
19849
20051
  throw withOutcomeReason(
@@ -19864,13 +20066,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19864
20066
  lastSnapshotAt: snapshot.metadata.capturedAt,
19865
20067
  lastSnapshotId: snapshot.metadata.id,
19866
20068
  updatedAt: finishedAt
19867
- }).where(and28(
19868
- eq34(gtmConnections.id, connection.id),
19869
- eq34(gtmConnections.projectId, projectId),
19870
- eq34(gtmConnections.selectedAccountId, connection.selectedAccountId),
19871
- eq34(gtmConnections.selectedContainerId, connection.selectedContainerId),
19872
- connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq34(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
19873
- eq34(gtmConnections.selectionGeneration, connection.selectionGeneration)
20069
+ }).where(and30(
20070
+ eq36(gtmConnections.id, connection.id),
20071
+ eq36(gtmConnections.projectId, projectId),
20072
+ eq36(gtmConnections.selectedAccountId, connection.selectedAccountId),
20073
+ eq36(gtmConnections.selectedContainerId, connection.selectedContainerId),
20074
+ connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq36(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
20075
+ eq36(gtmConnections.selectionGeneration, connection.selectionGeneration)
19874
20076
  )).run();
19875
20077
  if (connectionClaim.changes !== 1) {
19876
20078
  throw withOutcomeReason(
@@ -20073,8 +20275,8 @@ var DASHBOARD_SCRYPT_KEYLEN = 64;
20073
20275
  var DASHBOARD_SCRYPT_COST = 1 << 15;
20074
20276
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
20075
20277
  function hashDashboardPassword(password) {
20076
- const salt = crypto27.randomBytes(16);
20077
- const derived = crypto27.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
20278
+ const salt = crypto28.randomBytes(16);
20279
+ const derived = crypto28.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
20078
20280
  N: DASHBOARD_SCRYPT_COST,
20079
20281
  maxmem: DASHBOARD_SCRYPT_MAXMEM
20080
20282
  });
@@ -20095,14 +20297,14 @@ function verifyDashboardPassword(password, storedHash) {
20095
20297
  } catch {
20096
20298
  return { ok: false, needsRehash: false };
20097
20299
  }
20098
- const derived = crypto27.scryptSync(password, salt, expected.length, {
20300
+ const derived = crypto28.scryptSync(password, salt, expected.length, {
20099
20301
  N: DASHBOARD_SCRYPT_COST,
20100
20302
  maxmem: DASHBOARD_SCRYPT_MAXMEM
20101
20303
  });
20102
20304
  if (derived.length !== expected.length)
20103
20305
  return { ok: false, needsRehash: false };
20104
20306
  return {
20105
- ok: crypto27.timingSafeEqual(derived, expected),
20307
+ ok: crypto28.timingSafeEqual(derived, expected),
20106
20308
  needsRehash: false
20107
20309
  };
20108
20310
  }
@@ -20111,7 +20313,7 @@ function verifyDashboardPassword(password, storedHash) {
20111
20313
  const expected = Buffer.from(storedHash, "hex");
20112
20314
  if (candidate.length !== expected.length)
20113
20315
  return { ok: false, needsRehash: false };
20114
- const ok = crypto27.timingSafeEqual(candidate, expected);
20316
+ const ok = crypto28.timingSafeEqual(candidate, expected);
20115
20317
  return { ok, needsRehash: ok };
20116
20318
  }
20117
20319
  return { ok: false, needsRehash: false };
@@ -20278,7 +20480,7 @@ function cloneGtmConfig(config) {
20278
20480
  function createGoogleMarketingConfigCredentialStore(input) {
20279
20481
  const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
20280
20482
  const env = input.env ?? process.env;
20281
- const randomUUID2 = input.randomUUID ?? crypto27.randomUUID;
20483
+ const randomUUID2 = input.randomUUID ?? crypto28.randomUUID;
20282
20484
  const snapshot = () => ({
20283
20485
  googleAds: cloneGoogleAdsConfig(input.config.googleAds),
20284
20486
  gtm: cloneGtmConfig(input.config.gtm)
@@ -20395,7 +20597,7 @@ async function createServer(opts) {
20395
20597
  const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
20396
20598
  const app = Fastify({
20397
20599
  loggerInstance: logger,
20398
- genReqId: () => crypto27.randomUUID(),
20600
+ genReqId: () => crypto28.randomUUID(),
20399
20601
  trustProxy
20400
20602
  });
20401
20603
  if (!ownsDefaultConfig) {
@@ -20526,7 +20728,7 @@ async function createServer(opts) {
20526
20728
  async (ctx) => {
20527
20729
  if (!sessionRegistry) return;
20528
20730
  if (!agentProactive) return;
20529
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq35(projects.id, ctx.projectId)).get();
20731
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq37(projects.id, ctx.projectId)).get();
20530
20732
  if (!project) return;
20531
20733
  let content;
20532
20734
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -21000,7 +21202,7 @@ async function createServer(opts) {
21000
21202
  void (async () => {
21001
21203
  try {
21002
21204
  const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
21003
- const project = opts.db.select().from(projects).where(eq35(projects.name, projectName)).get();
21205
+ const project = opts.db.select().from(projects).where(eq37(projects.name, projectName)).get();
21004
21206
  if (!project) {
21005
21207
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
21006
21208
  return;
@@ -21194,7 +21396,7 @@ async function createServer(opts) {
21194
21396
  return removed;
21195
21397
  }
21196
21398
  };
21197
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto27.randomBytes(32).toString("hex");
21399
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto28.randomBytes(32).toString("hex");
21198
21400
  const googleConnectionStore = {
21199
21401
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
21200
21402
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -21292,11 +21494,11 @@ async function createServer(opts) {
21292
21494
  })();
21293
21495
  if (opts.config.apiKey) {
21294
21496
  const keyHash = hashApiKey(opts.config.apiKey);
21295
- const existing = opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, keyHash)).get();
21497
+ const existing = opts.db.select().from(apiKeys).where(eq37(apiKeys.keyHash, keyHash)).get();
21296
21498
  if (!existing) {
21297
21499
  const prefix = opts.config.apiKey.slice(0, 12);
21298
21500
  opts.db.insert(apiKeys).values({
21299
- id: `key_${crypto27.randomBytes(8).toString("hex")}`,
21501
+ id: `key_${crypto28.randomBytes(8).toString("hex")}`,
21300
21502
  name: "default",
21301
21503
  keyHash,
21302
21504
  keyPrefix: prefix,
@@ -21311,14 +21513,14 @@ async function createServer(opts) {
21311
21513
  );
21312
21514
  const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
21313
21515
  opts.db.delete(dashboardSessions).where(or4(
21314
- eq35(dashboardSessions.apiKeyHash, ""),
21315
- and29(
21516
+ eq37(dashboardSessions.apiKeyHash, ""),
21517
+ and31(
21316
21518
  isNotNull5(dashboardSessions.passwordFingerprint),
21317
21519
  ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
21318
21520
  )
21319
21521
  )).run();
21320
21522
  if (!opts.config.dashboardPasswordHash) {
21321
- const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and29(eq35(auditLog.entityType, "dashboard-password"), eq35(auditLog.action, action))).get()?.at ?? null;
21523
+ const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and31(eq37(auditLog.entityType, "dashboard-password"), eq37(auditLog.action, action))).get()?.at ?? null;
21322
21524
  const createdAt = lastPasswordEventAt("dashboard-password.created");
21323
21525
  const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
21324
21526
  if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
@@ -21333,7 +21535,7 @@ async function createServer(opts) {
21333
21535
  const createSession = (key, passwordBased = false, db = opts.db) => {
21334
21536
  const now = /* @__PURE__ */ new Date();
21335
21537
  db.delete(dashboardSessions).where(lte3(dashboardSessions.expiresAt, now.toISOString())).run();
21336
- const sessionId = crypto27.randomBytes(32).toString("hex");
21538
+ const sessionId = crypto28.randomBytes(32).toString("hex");
21337
21539
  db.insert(dashboardSessions).values({
21338
21540
  tokenHash: hashSessionToken(sessionId),
21339
21541
  apiKeyId: key.id,
@@ -21352,23 +21554,23 @@ async function createServer(opts) {
21352
21554
  currentApiKeyHash: apiKeys.keyHash,
21353
21555
  passwordFingerprint: dashboardSessions.passwordFingerprint,
21354
21556
  expiresAt: dashboardSessions.expiresAt
21355
- }).from(dashboardSessions).innerJoin(apiKeys, eq35(apiKeys.id, dashboardSessions.apiKeyId)).where(and29(eq35(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
21557
+ }).from(dashboardSessions).innerJoin(apiKeys, eq37(apiKeys.id, dashboardSessions.apiKeyId)).where(and31(eq37(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
21356
21558
  if (!session) return null;
21357
21559
  const expiresAt = Date.parse(session.expiresAt);
21358
21560
  if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
21359
- opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, tokenHash)).run();
21561
+ opts.db.delete(dashboardSessions).where(eq37(dashboardSessions.tokenHash, tokenHash)).run();
21360
21562
  return null;
21361
21563
  }
21362
21564
  return session.apiKeyId;
21363
21565
  };
21364
21566
  const clearSession = (sessionId) => {
21365
21567
  if (sessionId) {
21366
- opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21568
+ opts.db.delete(dashboardSessions).where(eq37(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21367
21569
  }
21368
21570
  };
21369
21571
  const getDefaultApiKey = () => {
21370
21572
  if (!opts.config.apiKey) return void 0;
21371
- return opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
21573
+ return opts.db.select().from(apiKeys).where(eq37(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
21372
21574
  };
21373
21575
  const setSessionCookie = (reply, sessionId) => {
21374
21576
  reply.header(
@@ -21397,7 +21599,7 @@ async function createServer(opts) {
21397
21599
  if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
21398
21600
  const presented = Buffer.from(hashApiKey(parts[1]), "hex");
21399
21601
  const expected = Buffer.from(key.keyHash, "hex");
21400
- return presented.length === expected.length && crypto27.timingSafeEqual(presented, expected);
21602
+ return presented.length === expected.length && crypto28.timingSafeEqual(presented, expected);
21401
21603
  };
21402
21604
  const namedAccountsInUse = () => anyUsersExist(opts.db);
21403
21605
  const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
@@ -21529,12 +21731,12 @@ async function createServer(opts) {
21529
21731
  return reply.send({ authenticated: true });
21530
21732
  }
21531
21733
  if (apiKey) {
21532
- const key = opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, hashApiKey(apiKey))).get();
21734
+ const key = opts.db.select().from(apiKeys).where(eq37(apiKeys.keyHash, hashApiKey(apiKey))).get();
21533
21735
  if (!key || key.revokedAt) {
21534
21736
  const err2 = authInvalid();
21535
21737
  return reply.status(err2.statusCode).send(err2.toJSON());
21536
21738
  }
21537
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq35(apiKeys.id, key.id)).run();
21739
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq37(apiKeys.id, key.id)).run();
21538
21740
  setSessionCookie(reply, createSession(key));
21539
21741
  return reply.send({ authenticated: true });
21540
21742
  }
@@ -21612,7 +21814,7 @@ async function createServer(opts) {
21612
21814
  const recomputeDebts = /* @__PURE__ */ new Map();
21613
21815
  let recomputeSequence = 0;
21614
21816
  const markAnswerFieldRecompute = (projectId, full) => {
21615
- opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql15`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq35(projects.id, projectId)).run();
21817
+ opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql15`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq37(projects.id, projectId)).run();
21616
21818
  };
21617
21819
  const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
21618
21820
  const sequence = ++recomputeSequence;
@@ -21630,10 +21832,10 @@ async function createServer(opts) {
21630
21832
  if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
21631
21833
  try {
21632
21834
  if (debt.latest === sequence && debt.latestFull === 0) {
21633
- opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq35(projects.id, projectId)).run();
21835
+ opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq37(projects.id, projectId)).run();
21634
21836
  if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
21635
21837
  } else if (debt.latestFull === 0) {
21636
- opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and29(eq35(projects.id, projectId), eq35(projects.answerFieldsRecompute, "full"))).run();
21838
+ opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and31(eq37(projects.id, projectId), eq37(projects.answerFieldsRecompute, "full"))).run();
21637
21839
  }
21638
21840
  } catch (err) {
21639
21841
  app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
@@ -21670,7 +21872,7 @@ async function createServer(opts) {
21670
21872
  await yieldToEventLoop4();
21671
21873
  const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
21672
21874
  competitorAutoAliasReasons.delete(projectId);
21673
- const project = opts.db.select().from(projects).where(eq35(projects.id, projectId)).get();
21875
+ const project = opts.db.select().from(projects).where(eq37(projects.id, projectId)).get();
21674
21876
  if (!project || project.competitorAutoAliases === "off") return;
21675
21877
  if (project.competitorAutoAliases === "apply") {
21676
21878
  await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
@@ -21870,8 +22072,8 @@ async function createServer(opts) {
21870
22072
  getAgentPin: () => describeAgentPin(opts.config),
21871
22073
  getGoogleMarketingDoctorInput: (ctx) => {
21872
22074
  if (!ctx.project) return null;
21873
- const googleAds = opts.db.select().from(googleAdsConnections).where(eq35(googleAdsConnections.projectId, ctx.project.id)).get();
21874
- const gtm = opts.db.select().from(gtmConnections).where(eq35(gtmConnections.projectId, ctx.project.id)).get();
22075
+ const googleAds = opts.db.select().from(googleAdsConnections).where(eq37(googleAdsConnections.projectId, ctx.project.id)).get();
22076
+ const gtm = opts.db.select().from(gtmConnections).where(eq37(gtmConnections.projectId, ctx.project.id)).get();
21875
22077
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
21876
22078
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
21877
22079
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -22000,7 +22202,7 @@ async function createServer(opts) {
22000
22202
  deps: {
22001
22203
  enqueueAutoExtract: ({ projectId, release: r }) => {
22002
22204
  const now = (/* @__PURE__ */ new Date()).toISOString();
22003
- const runId = crypto27.randomUUID();
22205
+ const runId = crypto28.randomUUID();
22004
22206
  opts.db.insert(runs).values({
22005
22207
  id: runId,
22006
22208
  projectId,
@@ -22121,7 +22323,7 @@ async function createServer(opts) {
22121
22323
  ...inspectOpts,
22122
22324
  config: opts.config
22123
22325
  }).then(() => {
22124
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq35(runs.id, runId)).get();
22326
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq37(runs.id, runId)).get();
22125
22327
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
22126
22328
  return maybeRefreshGscCoverage(
22127
22329
  opts.db,
@@ -22633,7 +22835,7 @@ async function createServer(opts) {
22633
22835
  isBacklinksInstalled: () => isDuckdbInstalled()
22634
22836
  });
22635
22837
  }
22636
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq35(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22838
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq37(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22637
22839
  dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
22638
22840
  }
22639
22841
  runtimeStartupSettled = true;