@canonry/canonry 7.97.8 → 7.98.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 (135) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -0
  2. package/assets/assets/{AgentSetupPage-DiX4GptZ.js → AgentSetupPage-h1kJhy9q.js} +1 -1
  3. package/assets/assets/{AuditHistoryPanel-de0zJkNC.js → AuditHistoryPanel-CNUApNiJ.js} +1 -1
  4. package/assets/assets/{BacklinksPage-BFfksR1M.js → BacklinksPage-CpKmlAek.js} +1 -1
  5. package/assets/assets/{HistoryPage-DQnGncRE.js → HistoryPage-DeN79d3c.js} +1 -1
  6. package/assets/assets/{MeasurementPropertyPage-DYCBQSnh.js → MeasurementPropertyPage-CcHnYn81.js} +1 -1
  7. package/assets/assets/{ProjectPage-BwwkYgfJ.js → ProjectPage-Tmuqmust.js} +1 -1
  8. package/assets/assets/{RunsPage-VeWCiQeM.js → RunsPage-BD4k3MjX.js} +1 -1
  9. package/assets/assets/{SettingsPage-BXPhnRMo.js → SettingsPage-BHy30JP5.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-Ch9tNd8P.js → SiteHealthSection-SYdtuGjq.js} +2 -2
  11. package/assets/assets/ToneBadge-Cii5KNtg.js +60 -0
  12. package/assets/assets/{TrafficPage-CIzz7gmK.js → TrafficPage-DuxtRqnc.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-6IbzMhJ6.js → TrafficSourceDetailPage-DiX1bB02.js} +1 -1
  14. package/assets/assets/{account-context-DIWdmpj6.js → account-context-CdEHcI5S.js} +1 -1
  15. package/assets/assets/{agent-trust-copy-2Os0XoaQ.js → agent-trust-copy-N6WacCSg.js} +1 -1
  16. package/assets/assets/{card-CoYH9h6o.js → card-B1O0SunG.js} +1 -1
  17. package/assets/assets/{format-helpers-cRaGEIxc.js → format-helpers-V8Pyc7cR.js} +1 -1
  18. package/assets/assets/{index-vfNb458q.js → index-BTz-B9hV.js} +4 -4
  19. package/assets/assets/{query-client-Bf1oywTZ.js → query-client-C1ycmW1c.js} +1 -1
  20. package/assets/assets/{react-query-DYI08wkz.js → react-query-DI7VfOND.js} +1 -1
  21. package/assets/assets/{react-sigma_core.esm.min-BEhi6Q9o.js → react-sigma_core.esm.min-f4uuOgrw.js} +1 -1
  22. package/assets/assets/{server-traffic-tgMckYtk.js → server-traffic-CkSCFMnL.js} +1 -1
  23. package/assets/assets/{service-CN0y5nRe.js → service-DSgXWhNq.js} +1 -1
  24. package/assets/index.html +7 -7
  25. package/dist/{ads-F7ACQA6Z.js → ads-PQ4OP6OH.js} +3 -3
  26. package/dist/{agent-FBSRQSUX.js → agent-B6ZPL2HH.js} +7 -7
  27. package/dist/{agent-ask-VYSXDUCL.js → agent-ask-ZUKNXRAX.js} +3 -3
  28. package/dist/{agent-conversations-IKAS3RE6.js → agent-conversations-DJ7WU7PL.js} +3 -3
  29. package/dist/{agent-journeys-L3ABBCNC.js → agent-journeys-LGUI3SQG.js} +3 -3
  30. package/dist/{agent-memory-SKWZDBMQ.js → agent-memory-PWZJ7G5Q.js} +3 -3
  31. package/dist/{agent-providers-JBTFE5TJ.js → agent-providers-TEERDNJT.js} +3 -3
  32. package/dist/{agent-transcript-YGLXXFF7.js → agent-transcript-KDPOPK5Z.js} +3 -3
  33. package/dist/{analytics-ULDRQRRL.js → analytics-NFW5RQRT.js} +3 -3
  34. package/dist/{apply-KC4CCHY5.js → apply-EP4V4UF5.js} +3 -3
  35. package/dist/{backfill-5Y2GMEAH.js → backfill-6UTNNVFO.js} +10 -10
  36. package/dist/{backlinks-NXWU4BPY.js → backlinks-SG5GV5X5.js} +3 -3
  37. package/dist/{bing-E2KM2CW6.js → bing-Q4W24JNC.js} +3 -3
  38. package/dist/{bootstrap-QYWTDUIZ.js → bootstrap-WEMERVNZ.js} +10 -10
  39. package/dist/{cdp-E7WXCN5U.js → cdp-3XKBJGHO.js} +7 -7
  40. package/dist/{chunk-UBF2S3DF.js → chunk-2Q7JEC6N.js} +2 -2
  41. package/dist/{chunk-TUQYYF4G.js → chunk-2XCYEBQV.js} +2 -2
  42. package/dist/{chunk-TUBFKC3O.js → chunk-3BA75QYU.js} +1 -1
  43. package/dist/{chunk-LPSKHUCV.js → chunk-3GMBHGQ5.js} +3 -3
  44. package/dist/{chunk-4VSL2CMF.js → chunk-3JWY65XO.js} +7 -7
  45. package/dist/{chunk-RJNAUQSV.js → chunk-52M2JWCB.js} +1 -1
  46. package/dist/{chunk-7TIX5NHC.js → chunk-64KB6YTG.js} +3 -3
  47. package/dist/{chunk-YO2OW67H.js → chunk-64R66MWZ.js} +3 -3
  48. package/dist/{chunk-RSJBMQMN.js → chunk-BLW327AU.js} +1 -1
  49. package/dist/{chunk-WWGOMPYP.js → chunk-CN6MVUY3.js} +1 -1
  50. package/dist/{chunk-GMCVZPKO.js → chunk-EJZ444VV.js} +1 -1
  51. package/dist/{chunk-KSB3UZMW.js → chunk-ER5ZHAB2.js} +2 -2
  52. package/dist/{chunk-C3LS3ZKI.js → chunk-FQ3L2TIX.js} +1 -1
  53. package/dist/{chunk-JYG3LUXU.js → chunk-IAGC2MGP.js} +357 -357
  54. package/dist/{chunk-5455IRP3.js → chunk-ISIJ2NS4.js} +1 -1
  55. package/dist/{chunk-22LX5CWD.js → chunk-IX5AC3QB.js} +370 -218
  56. package/dist/{chunk-FDVCTZXD.js → chunk-J6MYB3F4.js} +1 -1
  57. package/dist/{chunk-66DTIEWN.js → chunk-KK3Z34AN.js} +28 -1
  58. package/dist/{chunk-7CAOGKRV.js → chunk-MSI4BGFN.js} +3 -3
  59. package/dist/{chunk-FZIYHP67.js → chunk-O22WSFWL.js} +27 -4
  60. package/dist/{chunk-7APKPDOV.js → chunk-OL2L5ANJ.js} +2 -2
  61. package/dist/{chunk-AI2G7QMW.js → chunk-Q5SNOMRB.js} +4 -4
  62. package/dist/{chunk-X4DR2LK7.js → chunk-QSL3FXE4.js} +4 -4
  63. package/dist/{chunk-MA3ZD7TF.js → chunk-RGFIRJ7F.js} +2 -2
  64. package/dist/{chunk-I6G5K5PM.js → chunk-RLKI2QF7.js} +6 -6
  65. package/dist/{chunk-QM4PMS52.js → chunk-RTH5JVC3.js} +1 -1
  66. package/dist/{chunk-NLN7EGU3.js → chunk-SAKH2RRF.js} +5 -5
  67. package/dist/{chunk-Q4KVK426.js → chunk-TNOYUE2L.js} +5 -5
  68. package/dist/{chunk-4OGRTTZX.js → chunk-UXXZXXNG.js} +2 -2
  69. package/dist/{chunk-NPCML7HY.js → chunk-VMGZBH5L.js} +3 -3
  70. package/dist/{chunk-5X2N5OOF.js → chunk-WZSDGC45.js} +1 -1
  71. package/dist/{chunk-7737JZP6.js → chunk-X3UXMHWC.js} +1 -1
  72. package/dist/{chunk-RABCSQBW.js → chunk-YJITDEZR.js} +4 -4
  73. package/dist/{chunk-MAWNTOQE.js → chunk-ZADRESLB.js} +2 -2
  74. package/dist/{chunk-ZN3KIIMP.js → chunk-ZBTUISPG.js} +11 -1
  75. package/dist/{citations-CJ5Y3ZET.js → citations-5OSQ3ZYU.js} +3 -3
  76. package/dist/{cli-catalog-Z4XQIDYU.js → cli-catalog-T5OAVKGS.js} +3 -3
  77. package/dist/{cli-commands-3HZFXKVU.js → cli-commands-VATCVJKW.js} +7 -7
  78. package/dist/cli.js +13 -13
  79. package/dist/{competitor-FC43JBCH.js → competitor-YIKTMTHY.js} +3 -3
  80. package/dist/{content-LS4EAVOS.js → content-DS2LKR7J.js} +3 -3
  81. package/dist/{dashboard-AZUFESTB.js → dashboard-HLVRRRI4.js} +6 -6
  82. package/dist/{demo-LTVTVYGC.js → demo-A5DTVJGK.js} +1 -1
  83. package/dist/{demo-server-WHDXTUWQ.js → demo-server-P2JGAI6C.js} +12 -12
  84. package/dist/{discover-T5D7T5EZ.js → discover-VNZDO2MU.js} +3 -3
  85. package/dist/{discover-eval-FQWKYZD7.js → discover-eval-ON77LUEN.js} +3 -3
  86. package/dist/{doctor-UEFC5WP7.js → doctor-7TNNTI7H.js} +3 -3
  87. package/dist/{evidence-FXGHON3E.js → evidence-E54CNGXS.js} +3 -3
  88. package/dist/{export-cmd-AF6KF2GP.js → export-cmd-XXZ6KLQQ.js} +3 -3
  89. package/dist/{ga-RMYK4AQC.js → ga-ZQB4EJI3.js} +3 -3
  90. package/dist/{gbp-5PAKGLBI.js → gbp-ZQL6NQEK.js} +3 -3
  91. package/dist/{google-6NYRRHIS.js → google-7EIHQGSV.js} +4 -4
  92. package/dist/{google-marketing-6EVLEHKD.js → google-marketing-RSAO4PG7.js} +1 -1
  93. package/dist/{health-cmd-WXDFHL7O.js → health-cmd-R7WPKGWX.js} +3 -3
  94. package/dist/{history-MZWTC4OE.js → history-2CSWSVCY.js} +3 -3
  95. package/dist/index.d.ts +115 -5
  96. package/dist/index.js +25 -25
  97. package/dist/{init-OM64S7AT.js → init-FXZRMXZ3.js} +12 -12
  98. package/dist/{insights-CYE3NXGD.js → insights-EJUL3W6D.js} +3 -3
  99. package/dist/intelligence-service-TKK7O3Z5.js +16 -0
  100. package/dist/{journeys-YISJ3GH4.js → journeys-YUMBANFH.js} +3 -3
  101. package/dist/{keys-L76I5EZI.js → keys-KLOTGT4H.js} +3 -3
  102. package/dist/{keyword-54UML7XE.js → keyword-5NSJFEOI.js} +3 -3
  103. package/dist/{logs-ZG44USRX.js → logs-WW3RLCRG.js} +3 -3
  104. package/dist/{mcp-T7ZNR55S.js → mcp-NTBEIEVX.js} +4 -4
  105. package/dist/mcp.js +9 -9
  106. package/dist/{measurement-plan-E7FAS5V3.js → measurement-plan-BLPAZEXS.js} +5 -5
  107. package/dist/{notify-ZB4V7B4L.js → notify-KAGE6UEN.js} +5 -4
  108. package/dist/{organic-evidence-W5BJ37VR.js → organic-evidence-HTYRXRML.js} +3 -3
  109. package/dist/{overview-MULDZS6B.js → overview-GSJ4GP2T.js} +4 -4
  110. package/dist/{project-LFDGHF3H.js → project-NYUWICVC.js} +3 -3
  111. package/dist/{query-ZV3XX5YW.js → query-3CRRDOHD.js} +3 -3
  112. package/dist/{research-HGAKJGVQ.js → research-ZULVD3TW.js} +3 -3
  113. package/dist/{results-export-5HCHJGBM.js → results-export-STM7SQ4J.js} +3 -3
  114. package/dist/{run-5VYP7S3V.js → run-S4MFU4MK.js} +4 -4
  115. package/dist/{schedule-FD2VIHX7.js → schedule-6W3Q2FUL.js} +3 -3
  116. package/dist/{search-6TJJWFJB.js → search-EKXXN7C4.js} +3 -3
  117. package/dist/{serve-4QZEQUER.js → serve-TFBFPFAQ.js} +26 -26
  118. package/dist/{session-PA4SLGYI.js → session-4B5V3AH6.js} +13 -13
  119. package/dist/{settings-NOXPXCAY.js → settings-VRTDPFR3.js} +3 -3
  120. package/dist/{sitemaps-P52HCBCM.js → sitemaps-R7XFCH6E.js} +3 -3
  121. package/dist/{skills-EBWXL2WN.js → skills-44XYCTVD.js} +2 -2
  122. package/dist/{snapshot-IST2UAPI.js → snapshot-ROAOJI4W.js} +3 -3
  123. package/dist/{src-53VQLBQC.js → src-ZJRIKEDB.js} +4 -2
  124. package/dist/{status-X2RYXIU5.js → status-EN3A2TX5.js} +4 -4
  125. package/dist/{technical-aeo-HRQRU2L3.js → technical-aeo-ZV466F65.js} +3 -3
  126. package/dist/{telemetry-KQATCLNR.js → telemetry-EZHPNZ2A.js} +4 -4
  127. package/dist/{tools-VLQ3GZUE.js → tools-IH4M7AAG.js} +11 -11
  128. package/dist/{traffic-WK6HEQWG.js → traffic-VFLXWB3Z.js} +5 -5
  129. package/dist/{trends-353NF4OU.js → trends-ZXJIX3Z3.js} +3 -3
  130. package/dist/{users-YXBFZHVO.js → users-J2BYEWOE.js} +3 -3
  131. package/dist/{visibility-stats-N4KZ3N4P.js → visibility-stats-AJ7NY2BP.js} +3 -3
  132. package/dist/{wordpress-3IPY5TA3.js → wordpress-U6MS35VE.js} +3 -3
  133. package/package.json +1 -1
  134. package/assets/assets/ToneBadge-JbnFjl09.js +0 -60
  135. 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-EJZ444VV.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-RLKI2QF7.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-Q5SNOMRB.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-J6MYB3F4.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-TNOYUE2L.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-YJITDEZR.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-FQ3L2TIX.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-2XCYEBQV.js";
101
101
  import {
102
102
  createCanonryMcpServer
103
- } from "./chunk-YO2OW67H.js";
103
+ } from "./chunk-64R66MWZ.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-OL2L5ANJ.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-BLW327AU.js";
143
143
  import {
144
144
  getBundledSkillSnapshots
145
- } from "./chunk-QM4PMS52.js";
145
+ } from "./chunk-RTH5JVC3.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-2Q7JEC6N.js";
153
153
  import {
154
154
  CanonryMcpToolNames,
155
155
  canonryMcpTools
156
- } from "./chunk-LPSKHUCV.js";
156
+ } from "./chunk-3GMBHGQ5.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-SAKH2RRF.js";
166
166
  import {
167
167
  computeCitedCompetitorDomains,
168
168
  determineCitationState
169
- } from "./chunk-5455IRP3.js";
169
+ } from "./chunk-ISIJ2NS4.js";
170
170
  import {
171
171
  addLogListener,
172
172
  createFastifyLogger,
173
173
  createLogger
174
- } from "./chunk-4OGRTTZX.js";
174
+ } from "./chunk-UXXZXXNG.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-O22WSFWL.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-KK3Z34AN.js";
417
418
  import {
418
419
  AGENT_MEMORY_VALUE_MAX_BYTES,
419
420
  AI_ENGINE_DOMAINS,
@@ -673,7 +674,7 @@ import {
673
674
  winnabilityClassLabel,
674
675
  withRetry,
675
676
  withSkippedProviders
676
- } from "./chunk-ZN3KIIMP.js";
677
+ } from "./chunk-ZBTUISPG.js";
677
678
 
678
679
  // src/google-marketing-runtime.ts
679
680
  import crypto2 from "crypto";
@@ -3357,13 +3358,13 @@ function createGoogleMarketingRuntime(options) {
3357
3358
 
3358
3359
  // src/server.ts
3359
3360
  import { createRequire } from "module";
3360
- import crypto27 from "crypto";
3361
+ import crypto28 from "crypto";
3361
3362
  import fs5 from "fs";
3362
3363
  import path5 from "path";
3363
3364
  import { isIP as isIP2 } from "net";
3364
3365
  import { fileURLToPath } from "url";
3365
3366
  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";
3367
+ import { and as and30, eq as eq36, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql15 } from "drizzle-orm";
3367
3368
 
3368
3369
  // src/seo-runtime.ts
3369
3370
  import { eq, inArray } from "drizzle-orm";
@@ -6001,7 +6002,7 @@ function nonBlank(value) {
6001
6002
  return trimmed ? trimmed : void 0;
6002
6003
  }
6003
6004
  function resolveBuildCommit(env = process.env) {
6004
- const embedded = true ? "58098d90ba7afb8fa38629099f82a91d52ed434c" : void 0;
6005
+ const embedded = true ? "161453e7b8d6e3b33614392ce97134fc484106ec" : void 0;
6005
6006
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6006
6007
  }
6007
6008
  function resolveInstanceIdentity(env = process.env) {
@@ -15139,8 +15140,85 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
15139
15140
  }
15140
15141
 
15141
15142
  // 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";
15143
+ import { eq as eq26, desc as desc10, and as and23, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
15144
+ import crypto23 from "crypto";
15145
+
15146
+ // src/site-health-alerts.ts
15143
15147
  import crypto22 from "crypto";
15148
+ import { and as and22, asc as asc5, desc as desc9, eq as eq25, gt as gt3 } from "drizzle-orm";
15149
+ var ISSUE_TITLES = {
15150
+ "missing-h1": "Missing H1",
15151
+ "multiple-h1": "Multiple H1 tags",
15152
+ "missing-title": "Missing title",
15153
+ "names-another-entity": "Names another property"
15154
+ };
15155
+ var PAGE_BATCH = 500;
15156
+ var MAX_EXAMPLES = 5;
15157
+ function siteHealthProblemKey(code, url) {
15158
+ return `${code}|${crypto22.createHash("sha256").update(url).digest("hex").slice(0, 16)}`;
15159
+ }
15160
+ function readSiteHealthScan(db, projectId, runId) {
15161
+ const snapshot = db.select().from(siteCrawlSnapshots).where(and22(eq25(siteCrawlSnapshots.projectId, projectId), eq25(siteCrawlSnapshots.runId, runId))).orderBy(desc9(siteCrawlSnapshots.updatedAt)).limit(1).get();
15162
+ if (!snapshot?.detailsAvailable || !snapshot.attemptId) return null;
15163
+ const scope = and22(
15164
+ eq25(siteCrawlPages.projectId, projectId),
15165
+ eq25(siteCrawlPages.runId, runId),
15166
+ eq25(siteCrawlPages.attemptId, snapshot.attemptId)
15167
+ );
15168
+ const pagesByCode = /* @__PURE__ */ new Map();
15169
+ const keys = /* @__PURE__ */ new Set();
15170
+ let after;
15171
+ for (; ; ) {
15172
+ const pages = db.select({ nodeKey: siteCrawlPages.nodeKey, url: siteCrawlPages.url, auditFields: siteCrawlPages.auditFields }).from(siteCrawlPages).where(after === void 0 ? scope : and22(scope, gt3(siteCrawlPages.nodeKey, after))).orderBy(asc5(siteCrawlPages.nodeKey)).limit(PAGE_BATCH).all();
15173
+ for (const page of pages) {
15174
+ const defects = page.auditFields.criticalDefects;
15175
+ for (const defect of Array.isArray(defects) ? defects : []) {
15176
+ if (!defect || typeof defect !== "object") continue;
15177
+ const { id, severity } = defect;
15178
+ if (typeof id !== "string" || id === "" || severity !== "critical") continue;
15179
+ const key = siteHealthProblemKey(id, page.url);
15180
+ keys.add(key);
15181
+ let pagesForCode = pagesByCode.get(id);
15182
+ if (!pagesForCode) {
15183
+ pagesForCode = /* @__PURE__ */ new Map();
15184
+ pagesByCode.set(id, pagesForCode);
15185
+ }
15186
+ pagesForCode.set(key, page.url);
15187
+ }
15188
+ }
15189
+ if (pages.length < PAGE_BATCH) break;
15190
+ after = pages.at(-1).nodeKey;
15191
+ }
15192
+ return { complete: snapshot.complete, pagesByCode, keys };
15193
+ }
15194
+ function newSiteHealthIssues(scan, reported) {
15195
+ const issues = [];
15196
+ for (const [code, pages] of scan.pagesByCode) {
15197
+ const fresh = [...pages].filter(([key]) => !reported.has(key)).map(([, url]) => url).sort(compareText);
15198
+ if (fresh.length === 0) continue;
15199
+ issues.push({
15200
+ code,
15201
+ title: ISSUE_TITLES[code] ?? code,
15202
+ severity: "critical",
15203
+ affectedPages: pages.size,
15204
+ newPages: fresh.length,
15205
+ examples: fresh.slice(0, MAX_EXAMPLES)
15206
+ });
15207
+ }
15208
+ return issues.sort((a, b) => b.newPages - a.newPages || compareText(a.code, b.code));
15209
+ }
15210
+ function settledSiteHealthKeys(scan, reported) {
15211
+ return [...scan.complete ? scan.keys : /* @__PURE__ */ new Set([...reported, ...scan.keys])].sort(compareText);
15212
+ }
15213
+ function readSiteHealthNotifyState(db, projectId) {
15214
+ return db.select().from(siteHealthNotifyState).where(eq25(siteHealthNotifyState.projectId, projectId)).get() ?? null;
15215
+ }
15216
+ function writeSiteHealthNotifyState(db, input) {
15217
+ const values = { reportedKeys: input.reportedKeys, runId: input.runId, updatedAt: input.updatedAt, notifiedAt: input.notifiedAt };
15218
+ db.insert(siteHealthNotifyState).values({ projectId: input.projectId, ...values }).onConflictDoUpdate({ target: siteHealthNotifyState.projectId, set: values }).run();
15219
+ }
15220
+
15221
+ // src/notifier.ts
15144
15222
  var log24 = createLogger("Notifier");
15145
15223
  var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
15146
15224
  function trackWebhookDelivery(input) {
@@ -15262,18 +15340,18 @@ var Notifier = class {
15262
15340
  /** Called after a run completes (success, partial, or failed). */
15263
15341
  async onRunCompleted(runId, projectId, opts) {
15264
15342
  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);
15343
+ const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15266
15344
  if (notifs.length === 0) {
15267
15345
  log24.info("notifications.none-enabled", { projectId });
15268
15346
  return;
15269
15347
  }
15270
15348
  log24.info("notifications.found", { projectId, count: notifs.length });
15271
- const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15349
+ const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15272
15350
  if (!run) {
15273
15351
  log24.error("run.not-found", { runId, msg: "skipping notification dispatch" });
15274
15352
  return;
15275
15353
  }
15276
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15354
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15277
15355
  if (!project) {
15278
15356
  log24.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
15279
15357
  return;
@@ -15352,7 +15430,7 @@ var Notifier = class {
15352
15430
  * than inferred from the absence of a webhook.
15353
15431
  */
15354
15432
  async onHealthChecked(projectId, report) {
15355
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15433
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15356
15434
  if (!project) {
15357
15435
  log24.error("project.not-found", { projectId, msg: "skipping health notification" });
15358
15436
  return null;
@@ -15385,7 +15463,7 @@ var Notifier = class {
15385
15463
  const status = noSignal ? "warn" : worst ? worst.status : "ok";
15386
15464
  const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
15387
15465
  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();
15466
+ const previous = this.db.select().from(doctorHealthState).where(eq26(doctorHealthState.projectId, projectId)).get();
15389
15467
  const previousStatus = previous?.status ?? null;
15390
15468
  const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
15391
15469
  const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
@@ -15455,7 +15533,7 @@ var Notifier = class {
15455
15533
  const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
15456
15534
  const generalEvent = event;
15457
15535
  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) : [];
15536
+ const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15459
15537
  const listedEvents = (notif) => notif.config.events;
15460
15538
  const hasUrl = (notif) => Boolean(notif.config.url);
15461
15539
  const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15503,7 +15581,7 @@ var Notifier = class {
15503
15581
  if (previous === void 0) {
15504
15582
  this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
15505
15583
  } else {
15506
- this.db.update(doctorHealthState).set(observation).where(eq25(doctorHealthState.projectId, projectId)).run();
15584
+ this.db.update(doctorHealthState).set(observation).where(eq26(doctorHealthState.projectId, projectId)).run();
15507
15585
  }
15508
15586
  for (const group of urgentGroups) {
15509
15587
  const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
@@ -15547,7 +15625,7 @@ var Notifier = class {
15547
15625
  }
15548
15626
  if (accepted > 0) {
15549
15627
  recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
15550
- this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq25(doctorHealthState.projectId, projectId)).run();
15628
+ this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq26(doctorHealthState.projectId, projectId)).run();
15551
15629
  }
15552
15630
  if (urgentNotifs.length > 0) {
15553
15631
  log24.info("health.urgent-notified", {
@@ -15591,7 +15669,7 @@ var Notifier = class {
15591
15669
  if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
15592
15670
  }
15593
15671
  if (delivered > 0) {
15594
- this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq25(doctorHealthState.projectId, projectId)).run();
15672
+ this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq26(doctorHealthState.projectId, projectId)).run();
15595
15673
  }
15596
15674
  log24.info("health.notified", {
15597
15675
  projectId,
@@ -15621,12 +15699,12 @@ var Notifier = class {
15621
15699
  async onSiteLivenessChecked(projectId, result) {
15622
15700
  const { check, checkedAt } = result;
15623
15701
  if (check.status !== "ok" && check.status !== "fail") return null;
15624
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15702
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15625
15703
  if (!project) {
15626
15704
  log24.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15627
15705
  return null;
15628
15706
  }
15629
- const previous = this.db.select().from(siteLivenessState).where(eq25(siteLivenessState.projectId, projectId)).get();
15707
+ const previous = this.db.select().from(siteLivenessState).where(eq26(siteLivenessState.projectId, projectId)).get();
15630
15708
  const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
15631
15709
  const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
15632
15710
  const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
@@ -15636,7 +15714,7 @@ var Notifier = class {
15636
15714
  if (check.status === "ok" && outagePaged) event = "health.recovered";
15637
15715
  const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
15638
15716
  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) : [];
15717
+ const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15640
15718
  const listedEvents = (notif) => notif.config.events;
15641
15719
  const hasUrl = (notif) => Boolean(notif.config.url);
15642
15720
  const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15669,7 +15747,7 @@ var Notifier = class {
15669
15747
  if (previous === void 0) {
15670
15748
  this.db.insert(siteLivenessState).values(observation).run();
15671
15749
  } else {
15672
- this.db.update(siteLivenessState).set(observation).where(eq25(siteLivenessState.projectId, projectId)).run();
15750
+ this.db.update(siteLivenessState).set(observation).where(eq26(siteLivenessState.projectId, projectId)).run();
15673
15751
  }
15674
15752
  if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
15675
15753
  const payloadFor = (name) => {
@@ -15705,7 +15783,7 @@ var Notifier = class {
15705
15783
  }
15706
15784
  }
15707
15785
  if (event === "health.degraded" && delivered > 0) {
15708
- this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq25(siteLivenessState.projectId, projectId)).run();
15786
+ this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq26(siteLivenessState.projectId, projectId)).run();
15709
15787
  }
15710
15788
  log24.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15711
15789
  }
@@ -15719,7 +15797,7 @@ var Notifier = class {
15719
15797
  }
15720
15798
  if (accepted > 0) {
15721
15799
  recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
15722
- this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq25(siteLivenessState.projectId, projectId)).run();
15800
+ this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq26(siteLivenessState.projectId, projectId)).run();
15723
15801
  }
15724
15802
  if (urgentNotifs.length > 0 || generalIds.size > 0) {
15725
15803
  log24.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
@@ -15745,7 +15823,7 @@ var Notifier = class {
15745
15823
  */
15746
15824
  isNewInsightNews(projectId, insight) {
15747
15825
  const key = insightNotifyKey(projectId, insight);
15748
- const previous = this.db.select().from(insightNotifyState).where(eq25(insightNotifyState.key, key)).get();
15826
+ const previous = this.db.select().from(insightNotifyState).where(eq26(insightNotifyState.key, key)).get();
15749
15827
  if (!previous) return true;
15750
15828
  if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
15751
15829
  const magnitude = insightMagnitude(insight);
@@ -15767,9 +15845,9 @@ var Notifier = class {
15767
15845
  magnitude: insightMagnitude(insight),
15768
15846
  notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
15769
15847
  };
15770
- const existing = this.db.select().from(insightNotifyState).where(eq25(insightNotifyState.key, key)).get();
15848
+ const existing = this.db.select().from(insightNotifyState).where(eq26(insightNotifyState.key, key)).get();
15771
15849
  if (existing) {
15772
- this.db.update(insightNotifyState).set(row).where(eq25(insightNotifyState.key, key)).run();
15850
+ this.db.update(insightNotifyState).set(row).where(eq26(insightNotifyState.key, key)).run();
15773
15851
  } else {
15774
15852
  this.db.insert(insightNotifyState).values(row).run();
15775
15853
  }
@@ -15783,11 +15861,11 @@ var Notifier = class {
15783
15861
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
15784
15862
  if (highInsights.length > 0) insightEvents.push("insight.high");
15785
15863
  if (insightEvents.length === 0) return;
15786
- const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15864
+ const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15787
15865
  if (notifs.length === 0) return;
15788
- const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15866
+ const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15789
15867
  if (!run) return;
15790
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15868
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15791
15869
  if (!project) return;
15792
15870
  for (const notif of notifs) {
15793
15871
  const config = notif.config;
@@ -15868,9 +15946,9 @@ var Notifier = class {
15868
15946
  return delivered;
15869
15947
  }
15870
15948
  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();
15949
+ const pendingReviews = this.db.select().from(gbpReviews).where(and23(eq26(gbpReviews.projectId, projectId), eq26(gbpReviews.alertState, "pending"))).all();
15950
+ const pendingRatings = this.db.select().from(gbpReviewRatings).where(and23(eq26(gbpReviewRatings.projectId, projectId), eq26(gbpReviewRatings.alertState, "pending"))).all();
15951
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15874
15952
  if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
15875
15953
  const chatSendsLeft = /* @__PURE__ */ new Map();
15876
15954
  const now = /* @__PURE__ */ new Date();
@@ -15882,10 +15960,10 @@ var Notifier = class {
15882
15960
  displayName: gbpLocations.displayName,
15883
15961
  mapsUri: gbpLocations.mapsUri,
15884
15962
  reviewsAccess: gbpLocations.reviewsAccess
15885
- }).from(gbpLocations).where(eq25(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15963
+ }).from(gbpLocations).where(eq26(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15886
15964
  );
15887
15965
  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)
15966
+ this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and23(eq26(gbpReviewRatings.projectId, projectId), eq26(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
15889
15967
  );
15890
15968
  const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
15891
15969
  const locationRef = (locationName) => {
@@ -15914,7 +15992,7 @@ var Notifier = class {
15914
15992
  else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
15915
15993
  else ratingsToSend.push(row);
15916
15994
  }
15917
- const enabled = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15995
+ const enabled = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15918
15996
  const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
15919
15997
  const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
15920
15998
  const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
@@ -15968,13 +16046,13 @@ var Notifier = class {
15968
16046
  if (!sent) all = false;
15969
16047
  }
15970
16048
  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)
16049
+ this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and23(
16050
+ eq26(gbpReviews.id, review.id),
16051
+ eq26(gbpReviews.alertState, "pending"),
16052
+ eq26(gbpReviews.updateTime, review.updateTime),
16053
+ review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq26(gbpReviews.alertStateAt, review.alertStateAt),
16054
+ review.comment === null ? isNull5(gbpReviews.comment) : eq26(gbpReviews.comment, review.comment),
16055
+ review.starRating === null ? isNull5(gbpReviews.starRating) : eq26(gbpReviews.starRating, review.starRating)
15978
16056
  )).run();
15979
16057
  }
15980
16058
  }
@@ -16015,7 +16093,7 @@ var Notifier = class {
16015
16093
  return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
16016
16094
  }
16017
16095
  computeTransitions(runId, projectId) {
16018
- const thisRun = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
16096
+ const thisRun = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
16019
16097
  if (!thisRun) return [];
16020
16098
  const completeness = measurementRunCompleteness(this.db, runId);
16021
16099
  if (completeness.planned && !completeness.complete) {
@@ -16026,10 +16104,10 @@ var Notifier = class {
16026
16104
  });
16027
16105
  return [];
16028
16106
  }
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),
16107
+ const groupSiblings = this.db.select().from(runs).where(and23(
16108
+ eq26(runs.projectId, projectId),
16109
+ eq26(runs.kind, thisRun.kind),
16110
+ eq26(runs.createdAt, thisRun.createdAt),
16033
16111
  notProbeRun()
16034
16112
  )).all();
16035
16113
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
@@ -16046,20 +16124,20 @@ var Notifier = class {
16046
16124
  return candidate.id > best.id ? candidate : best;
16047
16125
  });
16048
16126
  if (winner.id !== runId) return [];
16049
- const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq25(projects.id, projectId)).get();
16127
+ const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq26(projects.id, projectId)).get();
16050
16128
  const locationCount = Math.max(
16051
16129
  1,
16052
16130
  (projectLocations?.locations ?? []).length
16053
16131
  );
16054
16132
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
16055
16133
  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")),
16134
+ and23(
16135
+ eq26(runs.projectId, projectId),
16136
+ eq26(runs.kind, thisRun.kind),
16137
+ or2(eq26(runs.status, "completed"), eq26(runs.status, "partial")),
16060
16138
  notProbeRun()
16061
16139
  )
16062
- ).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16140
+ ).orderBy(desc10(runs.createdAt), desc10(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16063
16141
  const groups = groupRunsByCreatedAt(recentRuns);
16064
16142
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
16065
16143
  if (currentGroupIdx < 0) return [];
@@ -16074,7 +16152,7 @@ var Notifier = class {
16074
16152
  provider: querySnapshots.provider,
16075
16153
  location: querySnapshots.location,
16076
16154
  citationState: querySnapshots.citationState
16077
- }).from(querySnapshots).leftJoin(queries, eq25(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
16155
+ }).from(querySnapshots).leftJoin(queries, eq26(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
16078
16156
  const previousSnapshots = this.db.select({
16079
16157
  queryId: querySnapshots.queryId,
16080
16158
  provider: querySnapshots.provider,
@@ -16125,12 +16203,12 @@ var Notifier = class {
16125
16203
  recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
16126
16204
  if (due.length === 0) continue;
16127
16205
  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"));
16206
+ const subscribed = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("traffic.agent.discovered"));
16129
16207
  if (subscribed.length === 0) {
16130
16208
  recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
16131
16209
  continue;
16132
16210
  }
16133
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
16211
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
16134
16212
  if (!project) continue;
16135
16213
  const payload = {
16136
16214
  source: "canonry",
@@ -16153,6 +16231,73 @@ var Notifier = class {
16153
16231
  }
16154
16232
  return sentNames;
16155
16233
  }
16234
+ /**
16235
+ * After a site audit, send `site-health.issue.new` to the webhooks that list
16236
+ * it when the scan found a critical page problem on a page where it was not
16237
+ * reported before.
16238
+ *
16239
+ * What was reported lives in `site_health_notify_state`. It changes when a
16240
+ * subscriber accepts an alert, so a refused alert is owed again after the
16241
+ * next audit, and when a complete scan no longer finds a problem, so the
16242
+ * problem is reported again if it comes back. A partial scan may have missed
16243
+ * pages, so it never clears anything. Nothing is recorded while no webhook
16244
+ * subscribes: a new subscriber hears about every current problem once.
16245
+ *
16246
+ * Returns the event sent, or null when nothing was sent, so "stayed quiet" is
16247
+ * observable in tests.
16248
+ */
16249
+ async onSiteAuditCompleted(runId, projectId) {
16250
+ const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
16251
+ if (!run || run.kind !== RunKinds["site-audit"]) return null;
16252
+ if (run.status !== RunStatuses.completed && run.status !== RunStatuses.partial) return null;
16253
+ const subscribed = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("site-health.issue.new"));
16254
+ if (subscribed.length === 0) return null;
16255
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
16256
+ if (!project) return null;
16257
+ const scan = readSiteHealthScan(this.db, projectId, runId);
16258
+ if (!scan) return null;
16259
+ const state = readSiteHealthNotifyState(this.db, projectId);
16260
+ const reported = new Set(state?.reportedKeys ?? []);
16261
+ const now = (/* @__PURE__ */ new Date()).toISOString();
16262
+ const issues = newSiteHealthIssues(scan, reported);
16263
+ if (issues.length === 0) {
16264
+ if (state && scan.complete) {
16265
+ writeSiteHealthNotifyState(this.db, {
16266
+ projectId,
16267
+ runId,
16268
+ reportedKeys: settledSiteHealthKeys(scan, reported),
16269
+ updatedAt: now,
16270
+ notifiedAt: state.notifiedAt
16271
+ });
16272
+ }
16273
+ return null;
16274
+ }
16275
+ const payload = {
16276
+ source: "canonry",
16277
+ event: "site-health.issue.new",
16278
+ project: { name: project.name, canonicalDomain: project.canonicalDomain },
16279
+ run: { id: run.id, status: run.status, finishedAt: run.finishedAt },
16280
+ scanComplete: scan.complete,
16281
+ issues,
16282
+ dashboardUrl: `${this.serverUrl}/projects/${project.name}/technical-aeo`
16283
+ };
16284
+ let accepted = false;
16285
+ for (const notif of subscribed) {
16286
+ if (await this.sendWebhook(notif.config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) accepted = true;
16287
+ }
16288
+ if (!accepted) {
16289
+ log24.info("site-health.alert-owed", { runId, projectId, issues: issues.length });
16290
+ return null;
16291
+ }
16292
+ writeSiteHealthNotifyState(this.db, {
16293
+ projectId,
16294
+ runId,
16295
+ reportedKeys: settledSiteHealthKeys(scan, reported),
16296
+ updatedAt: now,
16297
+ notifiedAt: now
16298
+ });
16299
+ return "site-health.issue.new";
16300
+ }
16156
16301
  async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
16157
16302
  const elapsed = startOutcomeTimer();
16158
16303
  const destination = resolveDestination(url);
@@ -16220,7 +16365,7 @@ var Notifier = class {
16220
16365
  }
16221
16366
  logDelivery(projectId, notificationId, event, status, error) {
16222
16367
  this.db.insert(auditLog).values({
16223
- id: crypto22.randomUUID(),
16368
+ id: crypto23.randomUUID(),
16224
16369
  projectId,
16225
16370
  actor: "scheduler",
16226
16371
  action: `notification.${status}`,
@@ -16233,7 +16378,7 @@ var Notifier = class {
16233
16378
  };
16234
16379
 
16235
16380
  // src/run-coordinator.ts
16236
- import { eq as eq26 } from "drizzle-orm";
16381
+ import { eq as eq27 } from "drizzle-orm";
16237
16382
  var log25 = createLogger("RunCoordinator");
16238
16383
  function reportInsightsGenerated(runTrigger, outcome) {
16239
16384
  trackFeatureCompleted({
@@ -16267,7 +16412,7 @@ var RunCoordinator = class {
16267
16412
  */
16268
16413
  onAnswersRecorded;
16269
16414
  async onRunCompleted(runId, projectId, opts) {
16270
- const runRow = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
16415
+ const runRow = this.db.select().from(runs).where(eq27(runs.id, runId)).get();
16271
16416
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
16272
16417
  if (runRow?.trigger === RunTriggers.probe) {
16273
16418
  log25.info("probe.skip-side-effects", { runId, projectId, kind });
@@ -16361,6 +16506,13 @@ var RunCoordinator = class {
16361
16506
  } catch (err) {
16362
16507
  log25.error("notifier.failed", { runId, error: describeError(err) });
16363
16508
  }
16509
+ if (kind === RunKinds["site-audit"]) {
16510
+ try {
16511
+ await this.notifier.onSiteAuditCompleted(runId, projectId);
16512
+ } catch (err) {
16513
+ log25.error("site-health-webhook.failed", { runId, error: describeError(err) });
16514
+ }
16515
+ }
16364
16516
  if (this.onAeroEvent) {
16365
16517
  try {
16366
16518
  const ctx = kind === RunKinds["aeo-discover-probe"] ? this.buildDiscoveryAeroContext(runId, projectId, runRow?.status === "failed" ? "failed" : "completed", runRow?.error ?? null) : {
@@ -16387,7 +16539,7 @@ var RunCoordinator = class {
16387
16539
  * so the Aero queue is never starved of a follow-up.
16388
16540
  */
16389
16541
  buildDiscoveryAeroContext(runId, projectId, status, error) {
16390
- const session = this.db.select().from(discoverySessions).where(eq26(discoverySessions.runId, runId)).get();
16542
+ const session = this.db.select().from(discoverySessions).where(eq27(discoverySessions.runId, runId)).get();
16391
16543
  const competitorMap = session ? session.competitorMap : [];
16392
16544
  return {
16393
16545
  kind: RunKinds["aeo-discover-probe"],
@@ -16429,8 +16581,8 @@ function analysisResultFromInsights(insights2) {
16429
16581
  }
16430
16582
 
16431
16583
  // src/agent/session-registry.ts
16432
- import crypto24 from "crypto";
16433
- import { eq as eq29 } from "drizzle-orm";
16584
+ import crypto25 from "crypto";
16585
+ import { eq as eq30 } from "drizzle-orm";
16434
16586
 
16435
16587
  // src/agent/remote-mcp.ts
16436
16588
  import { Type } from "@earendil-works/pi-ai";
@@ -16534,8 +16686,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
16534
16686
  }
16535
16687
 
16536
16688
  // 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";
16689
+ import crypto24 from "crypto";
16690
+ import { and as and24, desc as desc11, eq as eq28, like, sql as sql12, or as or3 } from "drizzle-orm";
16539
16691
  var COMPACTION_KEY_PREFIX = "compaction:";
16540
16692
  var COMPACTION_NOTES_PER_SESSION = 3;
16541
16693
  function rowToDto(row) {
@@ -16549,7 +16701,7 @@ function rowToDto(row) {
16549
16701
  };
16550
16702
  }
16551
16703
  function listMemoryEntries(db, projectId, opts = {}) {
16552
- const query = db.select().from(agentMemory).where(eq27(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
16704
+ const query = db.select().from(agentMemory).where(eq28(agentMemory.projectId, projectId)).orderBy(desc11(agentMemory.updatedAt));
16553
16705
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
16554
16706
  return rows.map(rowToDto);
16555
16707
  }
@@ -16563,7 +16715,7 @@ function upsertMemoryEntry(db, args) {
16563
16715
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
16564
16716
  }
16565
16717
  const now = (/* @__PURE__ */ new Date()).toISOString();
16566
- const id = crypto23.randomUUID();
16718
+ const id = crypto24.randomUUID();
16567
16719
  db.insert(agentMemory).values({
16568
16720
  id,
16569
16721
  projectId: args.projectId,
@@ -16580,22 +16732,22 @@ function upsertMemoryEntry(db, args) {
16580
16732
  updatedAt: now
16581
16733
  }
16582
16734
  }).run();
16583
- const row = db.select().from(agentMemory).where(and23(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, args.key))).get();
16735
+ const row = db.select().from(agentMemory).where(and24(eq28(agentMemory.projectId, args.projectId), eq28(agentMemory.key, args.key))).get();
16584
16736
  if (!row) throw new Error("memory upsert produced no row");
16585
16737
  return rowToDto(row);
16586
16738
  }
16587
16739
  function deleteMemoryEntry(db, projectId, key) {
16588
- const result = db.delete(agentMemory).where(and23(eq27(agentMemory.projectId, projectId), eq27(agentMemory.key, key))).run();
16740
+ const result = db.delete(agentMemory).where(and24(eq28(agentMemory.projectId, projectId), eq28(agentMemory.key, key))).run();
16589
16741
  const changes = result.changes ?? 0;
16590
16742
  return changes > 0;
16591
16743
  }
16592
16744
  function loadRecentForHydrate(db, projectId, limit, sessionId) {
16593
16745
  if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
16594
16746
  const sessionPrefix = `compaction:${sessionId ?? ""}:`;
16595
- return db.select().from(agentMemory).where(and23(
16596
- eq27(agentMemory.projectId, projectId),
16747
+ return db.select().from(agentMemory).where(and24(
16748
+ eq28(agentMemory.projectId, projectId),
16597
16749
  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);
16750
+ )).orderBy(desc11(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16599
16751
  }
16600
16752
  function writeCompactionNote(db, args) {
16601
16753
  if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
@@ -16605,7 +16757,7 @@ function writeCompactionNote(db, args) {
16605
16757
  }
16606
16758
  const now = (/* @__PURE__ */ new Date()).toISOString();
16607
16759
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
16608
- const id = crypto23.randomUUID();
16760
+ const id = crypto24.randomUUID();
16609
16761
  let inserted;
16610
16762
  db.transaction((tx) => {
16611
16763
  tx.insert(agentMemory).values({
@@ -16619,16 +16771,16 @@ function writeCompactionNote(db, args) {
16619
16771
  }).run();
16620
16772
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
16621
16773
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
16622
- and23(
16623
- eq27(agentMemory.projectId, args.projectId),
16774
+ and24(
16775
+ eq28(agentMemory.projectId, args.projectId),
16624
16776
  like(agentMemory.key, `${sessionPrefix}%`)
16625
16777
  )
16626
- ).orderBy(desc10(agentMemory.updatedAt)).all();
16778
+ ).orderBy(desc11(agentMemory.updatedAt)).all();
16627
16779
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
16628
16780
  if (stale.length > 0) {
16629
16781
  tx.delete(agentMemory).where(sql12`${agentMemory.id} IN (${sql12.join(stale.map((s) => sql12`${s}`), sql12`, `)})`).run();
16630
16782
  }
16631
- const row = tx.select().from(agentMemory).where(and23(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, key))).get();
16783
+ const row = tx.select().from(agentMemory).where(and24(eq28(agentMemory.projectId, args.projectId), eq28(agentMemory.key, key))).get();
16632
16784
  if (row) inserted = rowToDto(row);
16633
16785
  });
16634
16786
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -16702,7 +16854,7 @@ function trackAeroTurn(agent, turn) {
16702
16854
  }
16703
16855
 
16704
16856
  // src/agent/project-shape.ts
16705
- import { and as and24, count as count5, eq as eq28 } from "drizzle-orm";
16857
+ import { and as and25, count as count5, eq as eq29 } from "drizzle-orm";
16706
16858
  var ADVANCED_TOOLS = [
16707
16859
  "canonry_measurement_portfolio_summary",
16708
16860
  "canonry_measurement_overview",
@@ -16729,10 +16881,10 @@ var ADVANCED_ROUTES = [
16729
16881
  ];
16730
16882
  function aeroProjectShape(db, projectId) {
16731
16883
  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)
16884
+ const pointer = db.select().from(measurementPlans).where(eq29(measurementPlans.projectId, projectId)).get();
16885
+ const version = pointer ? db.select().from(measurementPlanVersions).where(and25(
16886
+ eq29(measurementPlanVersions.projectId, projectId),
16887
+ eq29(measurementPlanVersions.id, pointer.activeVersionId)
16736
16888
  )).get() : void 0;
16737
16889
  if (version) {
16738
16890
  const plan = JSON.parse(version.canonicalJson);
@@ -16759,7 +16911,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
16759
16911
  pinned: ADVANCED_TOOLS
16760
16912
  };
16761
16913
  }
16762
- const tracked = db.select({ total: count5() }).from(queries).where(eq28(queries.projectId, projectId)).get()?.total ?? 0;
16914
+ const tracked = db.select({ total: count5() }).from(queries).where(eq29(queries.projectId, projectId)).get()?.total ?? 0;
16763
16915
  return {
16764
16916
  prompt: `
16765
16917
 
@@ -17018,7 +17170,7 @@ var SessionRegistry = class {
17018
17170
  modelProvider: effectiveProvider,
17019
17171
  modelId: effectiveModelId,
17020
17172
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17021
- }).where(eq29(agentSessions.projectId, projectId)).run();
17173
+ }).where(eq30(agentSessions.projectId, projectId)).run();
17022
17174
  }
17023
17175
  const agent2 = createAeroSession({
17024
17176
  projectName,
@@ -17047,7 +17199,7 @@ var SessionRegistry = class {
17047
17199
  return agent2;
17048
17200
  }
17049
17201
  const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
17050
- const sessionId = crypto24.randomUUID();
17202
+ const sessionId = crypto25.randomUUID();
17051
17203
  const agent = createAeroSession({
17052
17204
  projectName,
17053
17205
  client: this.opts.client,
@@ -17337,7 +17489,7 @@ ${lines.join("\n")}
17337
17489
  modelProvider: nextProvider,
17338
17490
  modelId: nextModelId,
17339
17491
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17340
- }).where(eq29(agentSessions.projectId, projectId)).run();
17492
+ }).where(eq30(agentSessions.projectId, projectId)).run();
17341
17493
  }
17342
17494
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
17343
17495
  prepareConversationChange(projectName) {
@@ -17545,7 +17697,7 @@ ${lines.join("\n")}
17545
17697
  providerAccountKey(provider, apiKey) {
17546
17698
  const canonical = coerceAgentProvider(provider);
17547
17699
  const vendor = canonical ? getAgentProvider(canonical).piAiProvider : provider;
17548
- return crypto24.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
17700
+ return crypto25.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
17549
17701
  }
17550
17702
  providerCoolingDown(projectName) {
17551
17703
  for (const [key, cooldown] of this.providerCooldowns) {
@@ -17562,18 +17714,18 @@ ${lines.join("\n")}
17562
17714
  return id;
17563
17715
  }
17564
17716
  tryResolveProjectId(projectName) {
17565
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq29(projects.name, projectName)).get();
17717
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq30(projects.name, projectName)).get();
17566
17718
  return row?.id;
17567
17719
  }
17568
17720
  loadRow(projectId) {
17569
- const row = this.opts.db.select().from(agentSessions).where(eq29(agentSessions.projectId, projectId)).get();
17721
+ const row = this.opts.db.select().from(agentSessions).where(eq30(agentSessions.projectId, projectId)).get();
17570
17722
  return row ?? null;
17571
17723
  }
17572
17724
  insertRow(params) {
17573
17725
  const now = (/* @__PURE__ */ new Date()).toISOString();
17574
17726
  const modelProvider = params.provider ?? params.modelProvider ?? AgentProviderIds.claude;
17575
17727
  this.opts.db.insert(agentSessions).values({
17576
- id: params.id ?? crypto24.randomUUID(),
17728
+ id: params.id ?? crypto25.randomUUID(),
17577
17729
  projectId: params.projectId,
17578
17730
  systemPrompt: params.systemPrompt,
17579
17731
  modelProvider,
@@ -17586,7 +17738,7 @@ ${lines.join("\n")}
17586
17738
  }
17587
17739
  updateRow(projectId, patch) {
17588
17740
  const now = (/* @__PURE__ */ new Date()).toISOString();
17589
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq29(agentSessions.projectId, projectId)).run();
17741
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq30(agentSessions.projectId, projectId)).run();
17590
17742
  }
17591
17743
  /**
17592
17744
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -17595,13 +17747,13 @@ ${lines.join("\n")}
17595
17747
  * recently used right after an upgrade.
17596
17748
  */
17597
17749
  persistPromptSnapshot(projectId, systemPrompt) {
17598
- this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq29(agentSessions.projectId, projectId)).run();
17750
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq30(agentSessions.projectId, projectId)).run();
17599
17751
  }
17600
17752
  };
17601
17753
 
17602
17754
  // 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";
17755
+ import crypto26 from "crypto";
17756
+ import { and as and26, eq as eq31, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
17605
17757
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
17606
17758
  CanonryMcpToolNames.canonry_ads_account,
17607
17759
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -17654,8 +17806,8 @@ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
17654
17806
  }
17655
17807
  var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
17656
17808
  function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
17657
- const raw = `cnry_${crypto25.randomBytes(24).toString("hex")}`;
17658
- const id = crypto25.randomUUID();
17809
+ const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17810
+ const id = crypto26.randomUUID();
17659
17811
  db.insert(apiKeys).values({
17660
17812
  id,
17661
17813
  name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
@@ -17778,12 +17930,12 @@ var ViewerAeroSessions = class {
17778
17930
  */
17779
17931
  removeKey(id) {
17780
17932
  try {
17781
- this.opts.db.delete(apiKeys).where(eq30(apiKeys.id, id)).run();
17933
+ this.opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
17782
17934
  } catch {
17783
17935
  try {
17784
17936
  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();
17937
+ tx.delete(dashboardSessions).where(eq31(dashboardSessions.apiKeyId, id)).run();
17938
+ tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq31(apiKeys.id, id)).run();
17787
17939
  });
17788
17940
  } catch {
17789
17941
  }
@@ -17796,7 +17948,7 @@ var ViewerAeroSessions = class {
17796
17948
  */
17797
17949
  revokeOrphanedKeys() {
17798
17950
  try {
17799
- this.opts.db.delete(apiKeys).where(and25(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17951
+ this.opts.db.delete(apiKeys).where(and26(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17800
17952
  } catch {
17801
17953
  }
17802
17954
  }
@@ -17811,9 +17963,9 @@ var ViewerAeroSessions = class {
17811
17963
  };
17812
17964
 
17813
17965
  // src/mcp-http.ts
17814
- import crypto26 from "crypto";
17966
+ import crypto27 from "crypto";
17815
17967
  import { isIP } from "net";
17816
- import { and as and26, eq as eq31, isNull as isNull6 } from "drizzle-orm";
17968
+ import { and as and27, eq as eq32, isNull as isNull6 } from "drizzle-orm";
17817
17969
  import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
17818
17970
  import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
17819
17971
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -17822,8 +17974,8 @@ var MAX_CLEANUP_RETRIES_PER_SWEEP = 8;
17822
17974
  var MAX_PENDING_KEY_RETIREMENTS = 256;
17823
17975
  var MAX_CLEANUP_BACKOFF_MS = 10 * 60 * 1e3;
17824
17976
  function mintSessionKey(db, scopes, userId, oauthGrantId, createdAt) {
17825
- const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17826
- const id = crypto26.randomUUID();
17977
+ const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
17978
+ const id = crypto27.randomUUID();
17827
17979
  db.insert(apiKeys).values({
17828
17980
  id,
17829
17981
  name: `mcp-session:${userId}`,
@@ -17919,12 +18071,12 @@ function registerMcpHttpRoutes(scope, opts) {
17919
18071
  try {
17920
18072
  withoutLockWait(() => {
17921
18073
  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();
18074
+ tx.delete(dashboardSessions).where(eq32(dashboardSessions.apiKeyId, id)).run();
18075
+ tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and27(eq32(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
17924
18076
  });
17925
18077
  if (remove) {
17926
18078
  try {
17927
- opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
18079
+ opts.db.delete(apiKeys).where(eq32(apiKeys.id, id)).run();
17928
18080
  } catch (error) {
17929
18081
  scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
17930
18082
  }
@@ -17975,14 +18127,14 @@ function registerMcpHttpRoutes(scope, opts) {
17975
18127
  withoutLockWait(() => {
17976
18128
  const cutoff = now() - SESSION_IDLE_MS;
17977
18129
  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;
18130
+ const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq32(apiKeys.id, session.sessionKeyId)).get() : void 0;
17979
18131
  if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
17980
18132
  void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
17981
18133
  }
17982
18134
  }
17983
18135
  for (const exchange of modernExchanges) {
17984
18136
  if (!exchange.sessionKey) continue;
17985
- const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq31(apiKeys.id, exchange.sessionKey.id)).get();
18137
+ const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq32(apiKeys.id, exchange.sessionKey.id)).get();
17986
18138
  if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
17987
18139
  void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
17988
18140
  }
@@ -18037,7 +18189,7 @@ function registerMcpHttpRoutes(scope, opts) {
18037
18189
  clientName: `canonry-mcp/${PACKAGE_VERSION}`,
18038
18190
  surface: "mcp-http",
18039
18191
  // Correlation only: this is never accepted as caller identity or authority.
18040
- ...modern ? {} : { actorSession: crypto26.randomUUID() }
18192
+ ...modern ? {} : { actorSession: crypto27.randomUUID() }
18041
18193
  });
18042
18194
  let server;
18043
18195
  try {
@@ -18062,7 +18214,7 @@ function registerMcpHttpRoutes(scope, opts) {
18062
18214
  if (!bearer) return null;
18063
18215
  const { server, sessionKey } = createAuthorizedServer(request, segment, bearer);
18064
18216
  const transport = new NodeStreamableHTTPServerTransport({
18065
- sessionIdGenerator: () => crypto26.randomUUID(),
18217
+ sessionIdGenerator: () => crypto27.randomUUID(),
18066
18218
  onsessioninitialized: (id) => {
18067
18219
  if (!shuttingDown && !closing) sessions.set(id, session);
18068
18220
  }
@@ -18232,7 +18384,7 @@ function registerMcpHttpRoutes(scope, opts) {
18232
18384
  }
18233
18385
 
18234
18386
  // src/agent/agent-routes.ts
18235
- import { eq as eq32 } from "drizzle-orm";
18387
+ import { eq as eq33 } from "drizzle-orm";
18236
18388
  function viewerAeroCaller(request, opts) {
18237
18389
  if (!opts.viewerSessions) return null;
18238
18390
  const principal = request.principal;
@@ -18272,7 +18424,7 @@ function turnAttribution(request) {
18272
18424
  };
18273
18425
  }
18274
18426
  function resolveProject(db, name) {
18275
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq32(projects.name, name)).get();
18427
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq33(projects.name, name)).get();
18276
18428
  if (!row) throw notFound("project", name);
18277
18429
  return row;
18278
18430
  }
@@ -18391,7 +18543,7 @@ function registerAgentRoutes(app, opts) {
18391
18543
  }
18392
18544
  requireInstanceAdministrator(request);
18393
18545
  const project = resolveProject(opts.db, request.params.name);
18394
- const row = opts.db.select().from(agentSessions).where(eq32(agentSessions.projectId, project.id)).get();
18546
+ const row = opts.db.select().from(agentSessions).where(eq33(agentSessions.projectId, project.id)).get();
18395
18547
  const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
18396
18548
  if (!row) {
18397
18549
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
@@ -18433,7 +18585,7 @@ function registerAgentRoutes(app, opts) {
18433
18585
  const project = resolveProject(opts.db, request.params.name);
18434
18586
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
18435
18587
  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();
18588
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq33(agentSessions.projectId, project.id)).run();
18437
18589
  return { status: "reset" };
18438
18590
  }
18439
18591
  );
@@ -18457,7 +18609,7 @@ function registerAgentRoutes(app, opts) {
18457
18609
  return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
18458
18610
  }
18459
18611
  if (body.conversationId !== void 0) {
18460
- const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq32(agentSessions.projectId, project.id)).get();
18612
+ const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq33(agentSessions.projectId, project.id)).get();
18461
18613
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
18462
18614
  }
18463
18615
  const promptText = body.prompt;
@@ -19417,7 +19569,7 @@ function clipText(value, length) {
19417
19569
  }
19418
19570
 
19419
19571
  // src/research-runner.ts
19420
- import { and as and27, eq as eq33, inArray as inArray13, sql as sql13 } from "drizzle-orm";
19572
+ import { and as and28, eq as eq34, inArray as inArray13, sql as sql13 } from "drizzle-orm";
19421
19573
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
19422
19574
  function finalResearchRunStatus(completed, failed, waiting) {
19423
19575
  if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
@@ -19425,13 +19577,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
19425
19577
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
19426
19578
  }
19427
19579
  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();
19580
+ const run = db.select().from(researchRuns).where(and28(eq34(researchRuns.id, runId), eq34(researchRuns.projectId, projectId))).get();
19429
19581
  if (!run || run.status !== ResearchRunStatuses.queued) return;
19430
- const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
19582
+ const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
19431
19583
  if (!project) return;
19432
19584
  const provider = registry.get(run.provider);
19433
19585
  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();
19586
+ const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and28(eq34(researchRuns.id, runId), eq34(researchRuns.status, ResearchRunStatuses.queued))).run();
19435
19587
  if (claim.changes !== 1) return;
19436
19588
  const elapsed = startOutcomeTimer();
19437
19589
  const requested = currentOutcomeOrigin();
@@ -19465,8 +19617,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19465
19617
  }
19466
19618
  reserved = run.totalQueries;
19467
19619
  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();
19620
+ const rows = db.select().from(researchRunQueries).where(eq34(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
19621
+ const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq34(competitors.projectId, projectId)).orderBy(competitors.domain).all();
19470
19622
  const competitorDomains = competitorRows.map((row) => row.domain);
19471
19623
  const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
19472
19624
  const domains = effectiveDomains(project);
@@ -19476,7 +19628,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19476
19628
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
19477
19629
  try {
19478
19630
  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();
19631
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and28(eq34(researchRunQueries.id, row.id), eq34(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
19480
19632
  const raw = await gate.run(async () => {
19481
19633
  dispatched++;
19482
19634
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -19507,7 +19659,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19507
19659
  rawResponse: raw.rawResponse,
19508
19660
  answerEvidence: raw.evidence ?? null,
19509
19661
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19510
- }).where(and27(eq33(researchRunQueries.id, row.id), eq33(researchRunQueries.status, ResearchQueryStatuses.running))).run();
19662
+ }).where(and28(eq34(researchRunQueries.id, row.id), eq34(researchRunQueries.status, ResearchQueryStatuses.running))).run();
19511
19663
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
19512
19664
  } catch (error) {
19513
19665
  if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
@@ -19558,11 +19710,11 @@ function researchOutcomeStatus(status) {
19558
19710
  }
19559
19711
  }
19560
19712
  function incrementResearchProgress(db, runId, column) {
19561
- db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(eq33(researchRuns.id, runId)).run();
19713
+ db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(eq34(researchRuns.id, runId)).run();
19562
19714
  }
19563
19715
  function markResearchQueryFailed(db, runId, queryId, error) {
19564
19716
  const message = describeError(error);
19565
- const unfinished = and27(eq33(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19717
+ const unfinished = and28(eq34(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19566
19718
  if (error instanceof IncompleteAnswerError && error.resumable) {
19567
19719
  db.update(researchRunQueries).set({
19568
19720
  status: ResearchQueryStatuses["waiting-upstream"],
@@ -19578,10 +19730,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
19578
19730
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
19579
19731
  }
19580
19732
  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();
19733
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and28(eq34(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
19582
19734
  }
19583
19735
  function finalizeResearchRun(db, runId, fatalError) {
19584
- const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq33(researchRunQueries.researchRunId, runId)).all();
19736
+ const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq34(researchRunQueries.researchRunId, runId)).all();
19585
19737
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
19586
19738
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
19587
19739
  const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
@@ -19593,12 +19745,12 @@ function finalizeResearchRun(db, runId, fatalError) {
19593
19745
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
19594
19746
  // A run waiting on upstream jobs has not finished.
19595
19747
  finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
19596
- }).where(eq33(researchRuns.id, runId)).run();
19748
+ }).where(eq34(researchRuns.id, runId)).run();
19597
19749
  return { status, total: rows.length, completed, failed };
19598
19750
  }
19599
19751
 
19600
19752
  // src/google-marketing-sync.ts
19601
- import { and as and28, eq as eq34, isNull as isNull7, sql as sql14 } from "drizzle-orm";
19753
+ import { and as and29, eq as eq35, isNull as isNull7, sql as sql14 } from "drizzle-orm";
19602
19754
  var RUNTIME_FAILURE_REASONS = {
19603
19755
  INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
19604
19756
  NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
@@ -19635,10 +19787,10 @@ function assertSnapshotContext(metadata, expected) {
19635
19787
  }
19636
19788
  }
19637
19789
  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)
19790
+ const run = db.select().from(runs).where(and29(
19791
+ eq35(runs.id, runId),
19792
+ eq35(runs.projectId, projectId),
19793
+ eq35(runs.kind, kind)
19642
19794
  )).get();
19643
19795
  if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
19644
19796
  if (run.status !== RunStatuses.queued) {
@@ -19654,19 +19806,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
19654
19806
  status: RunStatuses.failed,
19655
19807
  error: serializeRunError({ message: safeErrorMessage(error) }),
19656
19808
  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)
19809
+ }).where(and29(
19810
+ eq35(runs.id, runId),
19811
+ eq35(runs.projectId, projectId),
19812
+ eq35(runs.kind, kind),
19813
+ eq35(runs.status, expectedStatus)
19662
19814
  )).run();
19663
19815
  }
19664
19816
  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)
19817
+ const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and29(
19818
+ eq35(runs.id, runId),
19819
+ eq35(runs.projectId, projectId),
19820
+ eq35(runs.kind, kind),
19821
+ eq35(runs.status, RunStatuses.queued)
19670
19822
  )).run();
19671
19823
  return claim.changes === 1;
19672
19824
  }
@@ -19676,9 +19828,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19676
19828
  let evidenceCollected = false;
19677
19829
  try {
19678
19830
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
19679
- const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
19831
+ const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
19680
19832
  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();
19833
+ const connection = db.select().from(googleAdsConnections).where(eq35(googleAdsConnections.projectId, projectId)).get();
19682
19834
  if (!connection?.selectedCustomerId) {
19683
19835
  throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19684
19836
  }
@@ -19730,11 +19882,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19730
19882
  status: RunStatuses.completed,
19731
19883
  finishedAt,
19732
19884
  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)
19885
+ }).where(and29(
19886
+ eq35(runs.id, runId),
19887
+ eq35(runs.projectId, projectId),
19888
+ eq35(runs.kind, RunKinds["google-ads-sync"]),
19889
+ eq35(runs.status, RunStatuses.running)
19738
19890
  )).run();
19739
19891
  if (terminalClaim.changes !== 1) {
19740
19892
  throw withOutcomeReason(
@@ -19754,12 +19906,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19754
19906
  lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
19755
19907
  lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
19756
19908
  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)
19909
+ }).where(and29(
19910
+ eq35(googleAdsConnections.id, connection.id),
19911
+ eq35(googleAdsConnections.projectId, projectId),
19912
+ eq35(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19913
+ connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq35(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19914
+ eq35(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19763
19915
  )).run();
19764
19916
  if (connectionClaim.changes !== 1) {
19765
19917
  throw withOutcomeReason(
@@ -19799,9 +19951,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19799
19951
  let evidenceCollected = false;
19800
19952
  try {
19801
19953
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
19802
- const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
19954
+ const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
19803
19955
  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();
19956
+ const connection = db.select().from(gtmConnections).where(eq35(gtmConnections.projectId, projectId)).get();
19805
19957
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
19806
19958
  throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19807
19959
  }
@@ -19839,11 +19991,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19839
19991
  status: RunStatuses.completed,
19840
19992
  finishedAt,
19841
19993
  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)
19994
+ }).where(and29(
19995
+ eq35(runs.id, runId),
19996
+ eq35(runs.projectId, projectId),
19997
+ eq35(runs.kind, RunKinds["gtm-sync"]),
19998
+ eq35(runs.status, RunStatuses.running)
19847
19999
  )).run();
19848
20000
  if (terminalClaim.changes !== 1) {
19849
20001
  throw withOutcomeReason(
@@ -19864,13 +20016,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19864
20016
  lastSnapshotAt: snapshot.metadata.capturedAt,
19865
20017
  lastSnapshotId: snapshot.metadata.id,
19866
20018
  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)
20019
+ }).where(and29(
20020
+ eq35(gtmConnections.id, connection.id),
20021
+ eq35(gtmConnections.projectId, projectId),
20022
+ eq35(gtmConnections.selectedAccountId, connection.selectedAccountId),
20023
+ eq35(gtmConnections.selectedContainerId, connection.selectedContainerId),
20024
+ connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq35(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
20025
+ eq35(gtmConnections.selectionGeneration, connection.selectionGeneration)
19874
20026
  )).run();
19875
20027
  if (connectionClaim.changes !== 1) {
19876
20028
  throw withOutcomeReason(
@@ -20073,8 +20225,8 @@ var DASHBOARD_SCRYPT_KEYLEN = 64;
20073
20225
  var DASHBOARD_SCRYPT_COST = 1 << 15;
20074
20226
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
20075
20227
  function hashDashboardPassword(password) {
20076
- const salt = crypto27.randomBytes(16);
20077
- const derived = crypto27.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
20228
+ const salt = crypto28.randomBytes(16);
20229
+ const derived = crypto28.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
20078
20230
  N: DASHBOARD_SCRYPT_COST,
20079
20231
  maxmem: DASHBOARD_SCRYPT_MAXMEM
20080
20232
  });
@@ -20095,14 +20247,14 @@ function verifyDashboardPassword(password, storedHash) {
20095
20247
  } catch {
20096
20248
  return { ok: false, needsRehash: false };
20097
20249
  }
20098
- const derived = crypto27.scryptSync(password, salt, expected.length, {
20250
+ const derived = crypto28.scryptSync(password, salt, expected.length, {
20099
20251
  N: DASHBOARD_SCRYPT_COST,
20100
20252
  maxmem: DASHBOARD_SCRYPT_MAXMEM
20101
20253
  });
20102
20254
  if (derived.length !== expected.length)
20103
20255
  return { ok: false, needsRehash: false };
20104
20256
  return {
20105
- ok: crypto27.timingSafeEqual(derived, expected),
20257
+ ok: crypto28.timingSafeEqual(derived, expected),
20106
20258
  needsRehash: false
20107
20259
  };
20108
20260
  }
@@ -20111,7 +20263,7 @@ function verifyDashboardPassword(password, storedHash) {
20111
20263
  const expected = Buffer.from(storedHash, "hex");
20112
20264
  if (candidate.length !== expected.length)
20113
20265
  return { ok: false, needsRehash: false };
20114
- const ok = crypto27.timingSafeEqual(candidate, expected);
20266
+ const ok = crypto28.timingSafeEqual(candidate, expected);
20115
20267
  return { ok, needsRehash: ok };
20116
20268
  }
20117
20269
  return { ok: false, needsRehash: false };
@@ -20278,7 +20430,7 @@ function cloneGtmConfig(config) {
20278
20430
  function createGoogleMarketingConfigCredentialStore(input) {
20279
20431
  const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
20280
20432
  const env = input.env ?? process.env;
20281
- const randomUUID2 = input.randomUUID ?? crypto27.randomUUID;
20433
+ const randomUUID2 = input.randomUUID ?? crypto28.randomUUID;
20282
20434
  const snapshot = () => ({
20283
20435
  googleAds: cloneGoogleAdsConfig(input.config.googleAds),
20284
20436
  gtm: cloneGtmConfig(input.config.gtm)
@@ -20395,7 +20547,7 @@ async function createServer(opts) {
20395
20547
  const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
20396
20548
  const app = Fastify({
20397
20549
  loggerInstance: logger,
20398
- genReqId: () => crypto27.randomUUID(),
20550
+ genReqId: () => crypto28.randomUUID(),
20399
20551
  trustProxy
20400
20552
  });
20401
20553
  if (!ownsDefaultConfig) {
@@ -20526,7 +20678,7 @@ async function createServer(opts) {
20526
20678
  async (ctx) => {
20527
20679
  if (!sessionRegistry) return;
20528
20680
  if (!agentProactive) return;
20529
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq35(projects.id, ctx.projectId)).get();
20681
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq36(projects.id, ctx.projectId)).get();
20530
20682
  if (!project) return;
20531
20683
  let content;
20532
20684
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -21000,7 +21152,7 @@ async function createServer(opts) {
21000
21152
  void (async () => {
21001
21153
  try {
21002
21154
  const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
21003
- const project = opts.db.select().from(projects).where(eq35(projects.name, projectName)).get();
21155
+ const project = opts.db.select().from(projects).where(eq36(projects.name, projectName)).get();
21004
21156
  if (!project) {
21005
21157
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
21006
21158
  return;
@@ -21194,7 +21346,7 @@ async function createServer(opts) {
21194
21346
  return removed;
21195
21347
  }
21196
21348
  };
21197
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto27.randomBytes(32).toString("hex");
21349
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto28.randomBytes(32).toString("hex");
21198
21350
  const googleConnectionStore = {
21199
21351
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
21200
21352
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -21292,11 +21444,11 @@ async function createServer(opts) {
21292
21444
  })();
21293
21445
  if (opts.config.apiKey) {
21294
21446
  const keyHash = hashApiKey(opts.config.apiKey);
21295
- const existing = opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, keyHash)).get();
21447
+ const existing = opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, keyHash)).get();
21296
21448
  if (!existing) {
21297
21449
  const prefix = opts.config.apiKey.slice(0, 12);
21298
21450
  opts.db.insert(apiKeys).values({
21299
- id: `key_${crypto27.randomBytes(8).toString("hex")}`,
21451
+ id: `key_${crypto28.randomBytes(8).toString("hex")}`,
21300
21452
  name: "default",
21301
21453
  keyHash,
21302
21454
  keyPrefix: prefix,
@@ -21311,14 +21463,14 @@ async function createServer(opts) {
21311
21463
  );
21312
21464
  const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
21313
21465
  opts.db.delete(dashboardSessions).where(or4(
21314
- eq35(dashboardSessions.apiKeyHash, ""),
21315
- and29(
21466
+ eq36(dashboardSessions.apiKeyHash, ""),
21467
+ and30(
21316
21468
  isNotNull5(dashboardSessions.passwordFingerprint),
21317
21469
  ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
21318
21470
  )
21319
21471
  )).run();
21320
21472
  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;
21473
+ const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and30(eq36(auditLog.entityType, "dashboard-password"), eq36(auditLog.action, action))).get()?.at ?? null;
21322
21474
  const createdAt = lastPasswordEventAt("dashboard-password.created");
21323
21475
  const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
21324
21476
  if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
@@ -21333,7 +21485,7 @@ async function createServer(opts) {
21333
21485
  const createSession = (key, passwordBased = false, db = opts.db) => {
21334
21486
  const now = /* @__PURE__ */ new Date();
21335
21487
  db.delete(dashboardSessions).where(lte3(dashboardSessions.expiresAt, now.toISOString())).run();
21336
- const sessionId = crypto27.randomBytes(32).toString("hex");
21488
+ const sessionId = crypto28.randomBytes(32).toString("hex");
21337
21489
  db.insert(dashboardSessions).values({
21338
21490
  tokenHash: hashSessionToken(sessionId),
21339
21491
  apiKeyId: key.id,
@@ -21352,23 +21504,23 @@ async function createServer(opts) {
21352
21504
  currentApiKeyHash: apiKeys.keyHash,
21353
21505
  passwordFingerprint: dashboardSessions.passwordFingerprint,
21354
21506
  expiresAt: dashboardSessions.expiresAt
21355
- }).from(dashboardSessions).innerJoin(apiKeys, eq35(apiKeys.id, dashboardSessions.apiKeyId)).where(and29(eq35(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
21507
+ }).from(dashboardSessions).innerJoin(apiKeys, eq36(apiKeys.id, dashboardSessions.apiKeyId)).where(and30(eq36(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
21356
21508
  if (!session) return null;
21357
21509
  const expiresAt = Date.parse(session.expiresAt);
21358
21510
  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();
21511
+ opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, tokenHash)).run();
21360
21512
  return null;
21361
21513
  }
21362
21514
  return session.apiKeyId;
21363
21515
  };
21364
21516
  const clearSession = (sessionId) => {
21365
21517
  if (sessionId) {
21366
- opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21518
+ opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21367
21519
  }
21368
21520
  };
21369
21521
  const getDefaultApiKey = () => {
21370
21522
  if (!opts.config.apiKey) return void 0;
21371
- return opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
21523
+ return opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
21372
21524
  };
21373
21525
  const setSessionCookie = (reply, sessionId) => {
21374
21526
  reply.header(
@@ -21397,7 +21549,7 @@ async function createServer(opts) {
21397
21549
  if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
21398
21550
  const presented = Buffer.from(hashApiKey(parts[1]), "hex");
21399
21551
  const expected = Buffer.from(key.keyHash, "hex");
21400
- return presented.length === expected.length && crypto27.timingSafeEqual(presented, expected);
21552
+ return presented.length === expected.length && crypto28.timingSafeEqual(presented, expected);
21401
21553
  };
21402
21554
  const namedAccountsInUse = () => anyUsersExist(opts.db);
21403
21555
  const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
@@ -21529,12 +21681,12 @@ async function createServer(opts) {
21529
21681
  return reply.send({ authenticated: true });
21530
21682
  }
21531
21683
  if (apiKey) {
21532
- const key = opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, hashApiKey(apiKey))).get();
21684
+ const key = opts.db.select().from(apiKeys).where(eq36(apiKeys.keyHash, hashApiKey(apiKey))).get();
21533
21685
  if (!key || key.revokedAt) {
21534
21686
  const err2 = authInvalid();
21535
21687
  return reply.status(err2.statusCode).send(err2.toJSON());
21536
21688
  }
21537
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq35(apiKeys.id, key.id)).run();
21689
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq36(apiKeys.id, key.id)).run();
21538
21690
  setSessionCookie(reply, createSession(key));
21539
21691
  return reply.send({ authenticated: true });
21540
21692
  }
@@ -21612,7 +21764,7 @@ async function createServer(opts) {
21612
21764
  const recomputeDebts = /* @__PURE__ */ new Map();
21613
21765
  let recomputeSequence = 0;
21614
21766
  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();
21767
+ opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql15`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq36(projects.id, projectId)).run();
21616
21768
  };
21617
21769
  const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
21618
21770
  const sequence = ++recomputeSequence;
@@ -21630,10 +21782,10 @@ async function createServer(opts) {
21630
21782
  if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
21631
21783
  try {
21632
21784
  if (debt.latest === sequence && debt.latestFull === 0) {
21633
- opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq35(projects.id, projectId)).run();
21785
+ opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq36(projects.id, projectId)).run();
21634
21786
  if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
21635
21787
  } else if (debt.latestFull === 0) {
21636
- opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and29(eq35(projects.id, projectId), eq35(projects.answerFieldsRecompute, "full"))).run();
21788
+ opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and30(eq36(projects.id, projectId), eq36(projects.answerFieldsRecompute, "full"))).run();
21637
21789
  }
21638
21790
  } catch (err) {
21639
21791
  app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
@@ -21670,7 +21822,7 @@ async function createServer(opts) {
21670
21822
  await yieldToEventLoop4();
21671
21823
  const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
21672
21824
  competitorAutoAliasReasons.delete(projectId);
21673
- const project = opts.db.select().from(projects).where(eq35(projects.id, projectId)).get();
21825
+ const project = opts.db.select().from(projects).where(eq36(projects.id, projectId)).get();
21674
21826
  if (!project || project.competitorAutoAliases === "off") return;
21675
21827
  if (project.competitorAutoAliases === "apply") {
21676
21828
  await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
@@ -21870,8 +22022,8 @@ async function createServer(opts) {
21870
22022
  getAgentPin: () => describeAgentPin(opts.config),
21871
22023
  getGoogleMarketingDoctorInput: (ctx) => {
21872
22024
  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();
22025
+ const googleAds = opts.db.select().from(googleAdsConnections).where(eq36(googleAdsConnections.projectId, ctx.project.id)).get();
22026
+ const gtm = opts.db.select().from(gtmConnections).where(eq36(gtmConnections.projectId, ctx.project.id)).get();
21875
22027
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
21876
22028
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
21877
22029
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -22000,7 +22152,7 @@ async function createServer(opts) {
22000
22152
  deps: {
22001
22153
  enqueueAutoExtract: ({ projectId, release: r }) => {
22002
22154
  const now = (/* @__PURE__ */ new Date()).toISOString();
22003
- const runId = crypto27.randomUUID();
22155
+ const runId = crypto28.randomUUID();
22004
22156
  opts.db.insert(runs).values({
22005
22157
  id: runId,
22006
22158
  projectId,
@@ -22121,7 +22273,7 @@ async function createServer(opts) {
22121
22273
  ...inspectOpts,
22122
22274
  config: opts.config
22123
22275
  }).then(() => {
22124
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq35(runs.id, runId)).get();
22276
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq36(runs.id, runId)).get();
22125
22277
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
22126
22278
  return maybeRefreshGscCoverage(
22127
22279
  opts.db,
@@ -22633,7 +22785,7 @@ async function createServer(opts) {
22633
22785
  isBacklinksInstalled: () => isDuckdbInstalled()
22634
22786
  });
22635
22787
  }
22636
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq35(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22788
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq36(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22637
22789
  dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
22638
22790
  }
22639
22791
  runtimeStartupSettled = true;