@canonry/canonry 7.97.7 → 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-JPVLEXY6.js → chunk-IAGC2MGP.js} +357 -357
  54. package/dist/{chunk-5455IRP3.js → chunk-ISIJ2NS4.js} +1 -1
  55. package/dist/{chunk-TVVQRHVR.js → chunk-IX5AC3QB.js} +397 -224
  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-O4X2V6EO.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-FC6ICDDA.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 +111 -1
  96. package/dist/index.js +25 -25
  97. package/dist/{init-4KLJC5N5.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-VTMTK2TK.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";
@@ -3537,8 +3538,28 @@ var serpItemSchema = z2.object({
3537
3538
  domain: z2.string().nullish(),
3538
3539
  url: z2.string().nullish(),
3539
3540
  rank_group: z2.number().int().positive().nullish(),
3540
- rank_absolute: z2.number().int().positive().nullish()
3541
+ rank_absolute: z2.number().int().positive().nullish(),
3542
+ /** The address Google shows above the title, such as `https://www.example.com › homes`. */
3543
+ breadcrumb: z2.string().nullish()
3541
3544
  });
3545
+ function isGoogleRedirect(url) {
3546
+ if (!url) return false;
3547
+ try {
3548
+ const parsed = new URL(url);
3549
+ const host = parsed.hostname.toLowerCase().replace(/^www\./, "");
3550
+ return /^google\.[a-z]{2,3}(?:\.[a-z]{2})?$/.test(host) && (parsed.pathname === "/goto" || parsed.pathname === "/url");
3551
+ } catch {
3552
+ return false;
3553
+ }
3554
+ }
3555
+ function resultSite(item) {
3556
+ if (!isGoogleRedirect(item.url)) return { host: item.domain ? normalizeHostname(item.domain) : null, url: item.url ?? null };
3557
+ try {
3558
+ return { host: normalizeHostname(new URL(item.breadcrumb?.trim().split(/[\s\u203a]/)[0] ?? "").hostname), url: null };
3559
+ } catch {
3560
+ return { host: null, url: null };
3561
+ }
3562
+ }
3542
3563
  function provenance(request, collectedAt, sourceUpdatedAt) {
3543
3564
  const serp = request.capability === SeoCapabilities["market-rankings"];
3544
3565
  return {
@@ -3569,15 +3590,16 @@ function marketRankings(rawResult, request, collectedAt, partial) {
3569
3590
  if (searchIdentity(result.keyword) !== searchIdentity(searchText)) throw new Error("SEO provider returned a different search.");
3570
3591
  const organic = (result.items ?? []).filter((item) => serpItemTypeSchema.safeParse(item).data?.type === "organic").map((item) => serpItemSchema.parse(item)).filter((item) => item.rank_group != null);
3571
3592
  if (organic.length === 0) throw new Error("SEO provider returned no organic results.");
3593
+ const sites = organic.map((item) => ({ item, ...resultSite(item) }));
3594
+ if (!sites.some((site) => site.host)) throw new Error("SEO provider returned no organic result with a known site.");
3572
3595
  const depth = partial ? Math.min(search.depth, organic.reduce((deepest, item) => Math.max(deepest, item.rank_group), 0)) : search.depth;
3573
3596
  let best = null;
3574
3597
  const competitorRanks = /* @__PURE__ */ new Map();
3575
3598
  const competitors2 = new Set(search.markets.flatMap((market) => market.competitors));
3576
- for (const item of organic) {
3577
- const host = item.domain ? normalizeHostname(item.domain) : null;
3599
+ for (const { item, host, url } of sites) {
3578
3600
  if (!host) continue;
3579
3601
  if (hostBelongsTo(host, target)) {
3580
- if (!best || item.rank_group < best.rank_group) best = item;
3602
+ if (!best || item.rank_group < best.rank) best = { rank: item.rank_group, rankAbsolute: item.rank_absolute ?? null, url };
3581
3603
  continue;
3582
3604
  }
3583
3605
  const domain = registrableDomain(host);
@@ -3594,8 +3616,8 @@ function marketRankings(rawResult, request, collectedAt, partial) {
3594
3616
  locationLabel: search.locationLabel,
3595
3617
  target,
3596
3618
  searchStatus: partial ? SeoMarketRankingSearchStatuses.partial : SeoMarketRankingSearchStatuses.completed,
3597
- rankOrganic: best?.rank_group ?? null,
3598
- rankAbsolute: best?.rank_absolute ?? null,
3619
+ rankOrganic: best?.rank ?? null,
3620
+ rankAbsolute: best?.rankAbsolute ?? null,
3599
3621
  url: best?.url ?? null,
3600
3622
  depth,
3601
3623
  competitors: ranked.filter((competitor) => tracked.has(competitor.domain)),
@@ -5980,7 +6002,7 @@ function nonBlank(value) {
5980
6002
  return trimmed ? trimmed : void 0;
5981
6003
  }
5982
6004
  function resolveBuildCommit(env = process.env) {
5983
- const embedded = true ? "930bad0500d64bef2401b0455bf57cf735f3cc7a" : void 0;
6005
+ const embedded = true ? "161453e7b8d6e3b33614392ce97134fc484106ec" : void 0;
5984
6006
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
5985
6007
  }
5986
6008
  function resolveInstanceIdentity(env = process.env) {
@@ -15118,8 +15140,85 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
15118
15140
  }
15119
15141
 
15120
15142
  // src/notifier.ts
15121
- 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
15122
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
15123
15222
  var log24 = createLogger("Notifier");
15124
15223
  var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
15125
15224
  function trackWebhookDelivery(input) {
@@ -15241,18 +15340,18 @@ var Notifier = class {
15241
15340
  /** Called after a run completes (success, partial, or failed). */
15242
15341
  async onRunCompleted(runId, projectId, opts) {
15243
15342
  log24.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
15244
- 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);
15245
15344
  if (notifs.length === 0) {
15246
15345
  log24.info("notifications.none-enabled", { projectId });
15247
15346
  return;
15248
15347
  }
15249
15348
  log24.info("notifications.found", { projectId, count: notifs.length });
15250
- 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();
15251
15350
  if (!run) {
15252
15351
  log24.error("run.not-found", { runId, msg: "skipping notification dispatch" });
15253
15352
  return;
15254
15353
  }
15255
- 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();
15256
15355
  if (!project) {
15257
15356
  log24.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
15258
15357
  return;
@@ -15331,7 +15430,7 @@ var Notifier = class {
15331
15430
  * than inferred from the absence of a webhook.
15332
15431
  */
15333
15432
  async onHealthChecked(projectId, report) {
15334
- 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();
15335
15434
  if (!project) {
15336
15435
  log24.error("project.not-found", { projectId, msg: "skipping health notification" });
15337
15436
  return null;
@@ -15364,7 +15463,7 @@ var Notifier = class {
15364
15463
  const status = noSignal ? "warn" : worst ? worst.status : "ok";
15365
15464
  const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
15366
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.`;
15367
- 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();
15368
15467
  const previousStatus = previous?.status ?? null;
15369
15468
  const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
15370
15469
  const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
@@ -15434,7 +15533,7 @@ var Notifier = class {
15434
15533
  const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
15435
15534
  const generalEvent = event;
15436
15535
  const urgentRecord = previous?.urgentNotified ?? null;
15437
- 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) : [];
15438
15537
  const listedEvents = (notif) => notif.config.events;
15439
15538
  const hasUrl = (notif) => Boolean(notif.config.url);
15440
15539
  const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15482,7 +15581,7 @@ var Notifier = class {
15482
15581
  if (previous === void 0) {
15483
15582
  this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
15484
15583
  } else {
15485
- 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();
15486
15585
  }
15487
15586
  for (const group of urgentGroups) {
15488
15587
  const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
@@ -15526,7 +15625,7 @@ var Notifier = class {
15526
15625
  }
15527
15626
  if (accepted > 0) {
15528
15627
  recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
15529
- 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();
15530
15629
  }
15531
15630
  if (urgentNotifs.length > 0) {
15532
15631
  log24.info("health.urgent-notified", {
@@ -15570,7 +15669,7 @@ var Notifier = class {
15570
15669
  if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
15571
15670
  }
15572
15671
  if (delivered > 0) {
15573
- 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();
15574
15673
  }
15575
15674
  log24.info("health.notified", {
15576
15675
  projectId,
@@ -15600,12 +15699,12 @@ var Notifier = class {
15600
15699
  async onSiteLivenessChecked(projectId, result) {
15601
15700
  const { check, checkedAt } = result;
15602
15701
  if (check.status !== "ok" && check.status !== "fail") return null;
15603
- 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();
15604
15703
  if (!project) {
15605
15704
  log24.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15606
15705
  return null;
15607
15706
  }
15608
- 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();
15609
15708
  const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
15610
15709
  const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
15611
15710
  const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
@@ -15615,7 +15714,7 @@ var Notifier = class {
15615
15714
  if (check.status === "ok" && outagePaged) event = "health.recovered";
15616
15715
  const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
15617
15716
  const urgentRecord = previous?.urgentNotifiedAt ?? null;
15618
- 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) : [];
15619
15718
  const listedEvents = (notif) => notif.config.events;
15620
15719
  const hasUrl = (notif) => Boolean(notif.config.url);
15621
15720
  const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15648,7 +15747,7 @@ var Notifier = class {
15648
15747
  if (previous === void 0) {
15649
15748
  this.db.insert(siteLivenessState).values(observation).run();
15650
15749
  } else {
15651
- 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();
15652
15751
  }
15653
15752
  if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
15654
15753
  const payloadFor = (name) => {
@@ -15684,7 +15783,7 @@ var Notifier = class {
15684
15783
  }
15685
15784
  }
15686
15785
  if (event === "health.degraded" && delivered > 0) {
15687
- 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();
15688
15787
  }
15689
15788
  log24.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15690
15789
  }
@@ -15698,7 +15797,7 @@ var Notifier = class {
15698
15797
  }
15699
15798
  if (accepted > 0) {
15700
15799
  recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
15701
- 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();
15702
15801
  }
15703
15802
  if (urgentNotifs.length > 0 || generalIds.size > 0) {
15704
15803
  log24.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
@@ -15724,7 +15823,7 @@ var Notifier = class {
15724
15823
  */
15725
15824
  isNewInsightNews(projectId, insight) {
15726
15825
  const key = insightNotifyKey(projectId, insight);
15727
- 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();
15728
15827
  if (!previous) return true;
15729
15828
  if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
15730
15829
  const magnitude = insightMagnitude(insight);
@@ -15746,9 +15845,9 @@ var Notifier = class {
15746
15845
  magnitude: insightMagnitude(insight),
15747
15846
  notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
15748
15847
  };
15749
- 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();
15750
15849
  if (existing) {
15751
- 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();
15752
15851
  } else {
15753
15852
  this.db.insert(insightNotifyState).values(row).run();
15754
15853
  }
@@ -15762,11 +15861,11 @@ var Notifier = class {
15762
15861
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
15763
15862
  if (highInsights.length > 0) insightEvents.push("insight.high");
15764
15863
  if (insightEvents.length === 0) return;
15765
- 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);
15766
15865
  if (notifs.length === 0) return;
15767
- 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();
15768
15867
  if (!run) return;
15769
- 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();
15770
15869
  if (!project) return;
15771
15870
  for (const notif of notifs) {
15772
15871
  const config = notif.config;
@@ -15847,9 +15946,9 @@ var Notifier = class {
15847
15946
  return delivered;
15848
15947
  }
15849
15948
  async dispatchQueuedReviewAlerts(projectId) {
15850
- const pendingReviews = this.db.select().from(gbpReviews).where(and22(eq25(gbpReviews.projectId, projectId), eq25(gbpReviews.alertState, "pending"))).all();
15851
- const pendingRatings = this.db.select().from(gbpReviewRatings).where(and22(eq25(gbpReviewRatings.projectId, projectId), eq25(gbpReviewRatings.alertState, "pending"))).all();
15852
- 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();
15853
15952
  if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
15854
15953
  const chatSendsLeft = /* @__PURE__ */ new Map();
15855
15954
  const now = /* @__PURE__ */ new Date();
@@ -15861,10 +15960,10 @@ var Notifier = class {
15861
15960
  displayName: gbpLocations.displayName,
15862
15961
  mapsUri: gbpLocations.mapsUri,
15863
15962
  reviewsAccess: gbpLocations.reviewsAccess
15864
- }).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])
15865
15964
  );
15866
15965
  const v4HasWorked = new Set(
15867
- 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)
15868
15967
  );
15869
15968
  const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
15870
15969
  const locationRef = (locationName) => {
@@ -15893,7 +15992,7 @@ var Notifier = class {
15893
15992
  else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
15894
15993
  else ratingsToSend.push(row);
15895
15994
  }
15896
- 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);
15897
15996
  const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
15898
15997
  const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
15899
15998
  const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
@@ -15947,13 +16046,13 @@ var Notifier = class {
15947
16046
  if (!sent) all = false;
15948
16047
  }
15949
16048
  if (all) {
15950
- this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and22(
15951
- eq25(gbpReviews.id, review.id),
15952
- eq25(gbpReviews.alertState, "pending"),
15953
- eq25(gbpReviews.updateTime, review.updateTime),
15954
- review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq25(gbpReviews.alertStateAt, review.alertStateAt),
15955
- review.comment === null ? isNull5(gbpReviews.comment) : eq25(gbpReviews.comment, review.comment),
15956
- 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)
15957
16056
  )).run();
15958
16057
  }
15959
16058
  }
@@ -15994,7 +16093,7 @@ var Notifier = class {
15994
16093
  return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
15995
16094
  }
15996
16095
  computeTransitions(runId, projectId) {
15997
- 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();
15998
16097
  if (!thisRun) return [];
15999
16098
  const completeness = measurementRunCompleteness(this.db, runId);
16000
16099
  if (completeness.planned && !completeness.complete) {
@@ -16005,10 +16104,10 @@ var Notifier = class {
16005
16104
  });
16006
16105
  return [];
16007
16106
  }
16008
- const groupSiblings = this.db.select().from(runs).where(and22(
16009
- eq25(runs.projectId, projectId),
16010
- eq25(runs.kind, thisRun.kind),
16011
- 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),
16012
16111
  notProbeRun()
16013
16112
  )).all();
16014
16113
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
@@ -16025,20 +16124,20 @@ var Notifier = class {
16025
16124
  return candidate.id > best.id ? candidate : best;
16026
16125
  });
16027
16126
  if (winner.id !== runId) return [];
16028
- 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();
16029
16128
  const locationCount = Math.max(
16030
16129
  1,
16031
16130
  (projectLocations?.locations ?? []).length
16032
16131
  );
16033
16132
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
16034
16133
  const recentRuns = this.db.select().from(runs).where(
16035
- and22(
16036
- eq25(runs.projectId, projectId),
16037
- eq25(runs.kind, thisRun.kind),
16038
- 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")),
16039
16138
  notProbeRun()
16040
16139
  )
16041
- ).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();
16042
16141
  const groups = groupRunsByCreatedAt(recentRuns);
16043
16142
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
16044
16143
  if (currentGroupIdx < 0) return [];
@@ -16053,7 +16152,7 @@ var Notifier = class {
16053
16152
  provider: querySnapshots.provider,
16054
16153
  location: querySnapshots.location,
16055
16154
  citationState: querySnapshots.citationState
16056
- }).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();
16057
16156
  const previousSnapshots = this.db.select({
16058
16157
  queryId: querySnapshots.queryId,
16059
16158
  provider: querySnapshots.provider,
@@ -16104,12 +16203,12 @@ var Notifier = class {
16104
16203
  recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
16105
16204
  if (due.length === 0) continue;
16106
16205
  const names = due.map((agent) => agent.name);
16107
- 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"));
16108
16207
  if (subscribed.length === 0) {
16109
16208
  recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
16110
16209
  continue;
16111
16210
  }
16112
- 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();
16113
16212
  if (!project) continue;
16114
16213
  const payload = {
16115
16214
  source: "canonry",
@@ -16132,6 +16231,73 @@ var Notifier = class {
16132
16231
  }
16133
16232
  return sentNames;
16134
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
+ }
16135
16301
  async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
16136
16302
  const elapsed = startOutcomeTimer();
16137
16303
  const destination = resolveDestination(url);
@@ -16199,7 +16365,7 @@ var Notifier = class {
16199
16365
  }
16200
16366
  logDelivery(projectId, notificationId, event, status, error) {
16201
16367
  this.db.insert(auditLog).values({
16202
- id: crypto22.randomUUID(),
16368
+ id: crypto23.randomUUID(),
16203
16369
  projectId,
16204
16370
  actor: "scheduler",
16205
16371
  action: `notification.${status}`,
@@ -16212,7 +16378,7 @@ var Notifier = class {
16212
16378
  };
16213
16379
 
16214
16380
  // src/run-coordinator.ts
16215
- import { eq as eq26 } from "drizzle-orm";
16381
+ import { eq as eq27 } from "drizzle-orm";
16216
16382
  var log25 = createLogger("RunCoordinator");
16217
16383
  function reportInsightsGenerated(runTrigger, outcome) {
16218
16384
  trackFeatureCompleted({
@@ -16246,7 +16412,7 @@ var RunCoordinator = class {
16246
16412
  */
16247
16413
  onAnswersRecorded;
16248
16414
  async onRunCompleted(runId, projectId, opts) {
16249
- 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();
16250
16416
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
16251
16417
  if (runRow?.trigger === RunTriggers.probe) {
16252
16418
  log25.info("probe.skip-side-effects", { runId, projectId, kind });
@@ -16340,6 +16506,13 @@ var RunCoordinator = class {
16340
16506
  } catch (err) {
16341
16507
  log25.error("notifier.failed", { runId, error: describeError(err) });
16342
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
+ }
16343
16516
  if (this.onAeroEvent) {
16344
16517
  try {
16345
16518
  const ctx = kind === RunKinds["aeo-discover-probe"] ? this.buildDiscoveryAeroContext(runId, projectId, runRow?.status === "failed" ? "failed" : "completed", runRow?.error ?? null) : {
@@ -16366,7 +16539,7 @@ var RunCoordinator = class {
16366
16539
  * so the Aero queue is never starved of a follow-up.
16367
16540
  */
16368
16541
  buildDiscoveryAeroContext(runId, projectId, status, error) {
16369
- 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();
16370
16543
  const competitorMap = session ? session.competitorMap : [];
16371
16544
  return {
16372
16545
  kind: RunKinds["aeo-discover-probe"],
@@ -16408,8 +16581,8 @@ function analysisResultFromInsights(insights2) {
16408
16581
  }
16409
16582
 
16410
16583
  // src/agent/session-registry.ts
16411
- import crypto24 from "crypto";
16412
- import { eq as eq29 } from "drizzle-orm";
16584
+ import crypto25 from "crypto";
16585
+ import { eq as eq30 } from "drizzle-orm";
16413
16586
 
16414
16587
  // src/agent/remote-mcp.ts
16415
16588
  import { Type } from "@earendil-works/pi-ai";
@@ -16513,8 +16686,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
16513
16686
  }
16514
16687
 
16515
16688
  // src/agent/memory-store.ts
16516
- import crypto23 from "crypto";
16517
- 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";
16518
16691
  var COMPACTION_KEY_PREFIX = "compaction:";
16519
16692
  var COMPACTION_NOTES_PER_SESSION = 3;
16520
16693
  function rowToDto(row) {
@@ -16528,7 +16701,7 @@ function rowToDto(row) {
16528
16701
  };
16529
16702
  }
16530
16703
  function listMemoryEntries(db, projectId, opts = {}) {
16531
- 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));
16532
16705
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
16533
16706
  return rows.map(rowToDto);
16534
16707
  }
@@ -16542,7 +16715,7 @@ function upsertMemoryEntry(db, args) {
16542
16715
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
16543
16716
  }
16544
16717
  const now = (/* @__PURE__ */ new Date()).toISOString();
16545
- const id = crypto23.randomUUID();
16718
+ const id = crypto24.randomUUID();
16546
16719
  db.insert(agentMemory).values({
16547
16720
  id,
16548
16721
  projectId: args.projectId,
@@ -16559,22 +16732,22 @@ function upsertMemoryEntry(db, args) {
16559
16732
  updatedAt: now
16560
16733
  }
16561
16734
  }).run();
16562
- 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();
16563
16736
  if (!row) throw new Error("memory upsert produced no row");
16564
16737
  return rowToDto(row);
16565
16738
  }
16566
16739
  function deleteMemoryEntry(db, projectId, key) {
16567
- 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();
16568
16741
  const changes = result.changes ?? 0;
16569
16742
  return changes > 0;
16570
16743
  }
16571
16744
  function loadRecentForHydrate(db, projectId, limit, sessionId) {
16572
16745
  if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
16573
16746
  const sessionPrefix = `compaction:${sessionId ?? ""}:`;
16574
- return db.select().from(agentMemory).where(and23(
16575
- eq27(agentMemory.projectId, projectId),
16747
+ return db.select().from(agentMemory).where(and24(
16748
+ eq28(agentMemory.projectId, projectId),
16576
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)
16577
- )).orderBy(desc10(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16750
+ )).orderBy(desc11(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16578
16751
  }
16579
16752
  function writeCompactionNote(db, args) {
16580
16753
  if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
@@ -16584,7 +16757,7 @@ function writeCompactionNote(db, args) {
16584
16757
  }
16585
16758
  const now = (/* @__PURE__ */ new Date()).toISOString();
16586
16759
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
16587
- const id = crypto23.randomUUID();
16760
+ const id = crypto24.randomUUID();
16588
16761
  let inserted;
16589
16762
  db.transaction((tx) => {
16590
16763
  tx.insert(agentMemory).values({
@@ -16598,16 +16771,16 @@ function writeCompactionNote(db, args) {
16598
16771
  }).run();
16599
16772
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
16600
16773
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
16601
- and23(
16602
- eq27(agentMemory.projectId, args.projectId),
16774
+ and24(
16775
+ eq28(agentMemory.projectId, args.projectId),
16603
16776
  like(agentMemory.key, `${sessionPrefix}%`)
16604
16777
  )
16605
- ).orderBy(desc10(agentMemory.updatedAt)).all();
16778
+ ).orderBy(desc11(agentMemory.updatedAt)).all();
16606
16779
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
16607
16780
  if (stale.length > 0) {
16608
16781
  tx.delete(agentMemory).where(sql12`${agentMemory.id} IN (${sql12.join(stale.map((s) => sql12`${s}`), sql12`, `)})`).run();
16609
16782
  }
16610
- 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();
16611
16784
  if (row) inserted = rowToDto(row);
16612
16785
  });
16613
16786
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -16681,7 +16854,7 @@ function trackAeroTurn(agent, turn) {
16681
16854
  }
16682
16855
 
16683
16856
  // src/agent/project-shape.ts
16684
- 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";
16685
16858
  var ADVANCED_TOOLS = [
16686
16859
  "canonry_measurement_portfolio_summary",
16687
16860
  "canonry_measurement_overview",
@@ -16708,10 +16881,10 @@ var ADVANCED_ROUTES = [
16708
16881
  ];
16709
16882
  function aeroProjectShape(db, projectId) {
16710
16883
  try {
16711
- const pointer = db.select().from(measurementPlans).where(eq28(measurementPlans.projectId, projectId)).get();
16712
- const version = pointer ? db.select().from(measurementPlanVersions).where(and24(
16713
- eq28(measurementPlanVersions.projectId, projectId),
16714
- 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)
16715
16888
  )).get() : void 0;
16716
16889
  if (version) {
16717
16890
  const plan = JSON.parse(version.canonicalJson);
@@ -16738,7 +16911,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
16738
16911
  pinned: ADVANCED_TOOLS
16739
16912
  };
16740
16913
  }
16741
- 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;
16742
16915
  return {
16743
16916
  prompt: `
16744
16917
 
@@ -16997,7 +17170,7 @@ var SessionRegistry = class {
16997
17170
  modelProvider: effectiveProvider,
16998
17171
  modelId: effectiveModelId,
16999
17172
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17000
- }).where(eq29(agentSessions.projectId, projectId)).run();
17173
+ }).where(eq30(agentSessions.projectId, projectId)).run();
17001
17174
  }
17002
17175
  const agent2 = createAeroSession({
17003
17176
  projectName,
@@ -17026,7 +17199,7 @@ var SessionRegistry = class {
17026
17199
  return agent2;
17027
17200
  }
17028
17201
  const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
17029
- const sessionId = crypto24.randomUUID();
17202
+ const sessionId = crypto25.randomUUID();
17030
17203
  const agent = createAeroSession({
17031
17204
  projectName,
17032
17205
  client: this.opts.client,
@@ -17316,7 +17489,7 @@ ${lines.join("\n")}
17316
17489
  modelProvider: nextProvider,
17317
17490
  modelId: nextModelId,
17318
17491
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17319
- }).where(eq29(agentSessions.projectId, projectId)).run();
17492
+ }).where(eq30(agentSessions.projectId, projectId)).run();
17320
17493
  }
17321
17494
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
17322
17495
  prepareConversationChange(projectName) {
@@ -17524,7 +17697,7 @@ ${lines.join("\n")}
17524
17697
  providerAccountKey(provider, apiKey) {
17525
17698
  const canonical = coerceAgentProvider(provider);
17526
17699
  const vendor = canonical ? getAgentProvider(canonical).piAiProvider : provider;
17527
- return crypto24.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
17700
+ return crypto25.createHash("sha256").update(JSON.stringify([vendor, apiKey ?? ""])).digest("hex");
17528
17701
  }
17529
17702
  providerCoolingDown(projectName) {
17530
17703
  for (const [key, cooldown] of this.providerCooldowns) {
@@ -17541,18 +17714,18 @@ ${lines.join("\n")}
17541
17714
  return id;
17542
17715
  }
17543
17716
  tryResolveProjectId(projectName) {
17544
- 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();
17545
17718
  return row?.id;
17546
17719
  }
17547
17720
  loadRow(projectId) {
17548
- 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();
17549
17722
  return row ?? null;
17550
17723
  }
17551
17724
  insertRow(params) {
17552
17725
  const now = (/* @__PURE__ */ new Date()).toISOString();
17553
17726
  const modelProvider = params.provider ?? params.modelProvider ?? AgentProviderIds.claude;
17554
17727
  this.opts.db.insert(agentSessions).values({
17555
- id: params.id ?? crypto24.randomUUID(),
17728
+ id: params.id ?? crypto25.randomUUID(),
17556
17729
  projectId: params.projectId,
17557
17730
  systemPrompt: params.systemPrompt,
17558
17731
  modelProvider,
@@ -17565,7 +17738,7 @@ ${lines.join("\n")}
17565
17738
  }
17566
17739
  updateRow(projectId, patch) {
17567
17740
  const now = (/* @__PURE__ */ new Date()).toISOString();
17568
- 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();
17569
17742
  }
17570
17743
  /**
17571
17744
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -17574,13 +17747,13 @@ ${lines.join("\n")}
17574
17747
  * recently used right after an upgrade.
17575
17748
  */
17576
17749
  persistPromptSnapshot(projectId, systemPrompt) {
17577
- 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();
17578
17751
  }
17579
17752
  };
17580
17753
 
17581
17754
  // src/agent/viewer-sessions.ts
17582
- import crypto25 from "crypto";
17583
- 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";
17584
17757
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
17585
17758
  CanonryMcpToolNames.canonry_ads_account,
17586
17759
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -17633,8 +17806,8 @@ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
17633
17806
  }
17634
17807
  var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
17635
17808
  function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
17636
- const raw = `cnry_${crypto25.randomBytes(24).toString("hex")}`;
17637
- const id = crypto25.randomUUID();
17809
+ const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17810
+ const id = crypto26.randomUUID();
17638
17811
  db.insert(apiKeys).values({
17639
17812
  id,
17640
17813
  name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
@@ -17757,12 +17930,12 @@ var ViewerAeroSessions = class {
17757
17930
  */
17758
17931
  removeKey(id) {
17759
17932
  try {
17760
- this.opts.db.delete(apiKeys).where(eq30(apiKeys.id, id)).run();
17933
+ this.opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
17761
17934
  } catch {
17762
17935
  try {
17763
17936
  this.opts.db.transaction((tx) => {
17764
- tx.delete(dashboardSessions).where(eq30(dashboardSessions.apiKeyId, id)).run();
17765
- 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();
17766
17939
  });
17767
17940
  } catch {
17768
17941
  }
@@ -17775,7 +17948,7 @@ var ViewerAeroSessions = class {
17775
17948
  */
17776
17949
  revokeOrphanedKeys() {
17777
17950
  try {
17778
- 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();
17779
17952
  } catch {
17780
17953
  }
17781
17954
  }
@@ -17790,9 +17963,9 @@ var ViewerAeroSessions = class {
17790
17963
  };
17791
17964
 
17792
17965
  // src/mcp-http.ts
17793
- import crypto26 from "crypto";
17966
+ import crypto27 from "crypto";
17794
17967
  import { isIP } from "net";
17795
- 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";
17796
17969
  import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
17797
17970
  import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
17798
17971
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -17801,8 +17974,8 @@ var MAX_CLEANUP_RETRIES_PER_SWEEP = 8;
17801
17974
  var MAX_PENDING_KEY_RETIREMENTS = 256;
17802
17975
  var MAX_CLEANUP_BACKOFF_MS = 10 * 60 * 1e3;
17803
17976
  function mintSessionKey(db, scopes, userId, oauthGrantId, createdAt) {
17804
- const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17805
- const id = crypto26.randomUUID();
17977
+ const raw = `cnry_${crypto27.randomBytes(24).toString("hex")}`;
17978
+ const id = crypto27.randomUUID();
17806
17979
  db.insert(apiKeys).values({
17807
17980
  id,
17808
17981
  name: `mcp-session:${userId}`,
@@ -17898,12 +18071,12 @@ function registerMcpHttpRoutes(scope, opts) {
17898
18071
  try {
17899
18072
  withoutLockWait(() => {
17900
18073
  opts.db.transaction((tx) => {
17901
- tx.delete(dashboardSessions).where(eq31(dashboardSessions.apiKeyId, id)).run();
17902
- 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();
17903
18076
  });
17904
18077
  if (remove) {
17905
18078
  try {
17906
- opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
18079
+ opts.db.delete(apiKeys).where(eq32(apiKeys.id, id)).run();
17907
18080
  } catch (error) {
17908
18081
  scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
17909
18082
  }
@@ -17954,14 +18127,14 @@ function registerMcpHttpRoutes(scope, opts) {
17954
18127
  withoutLockWait(() => {
17955
18128
  const cutoff = now() - SESSION_IDLE_MS;
17956
18129
  for (const session of legacyServers) {
17957
- 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;
17958
18131
  if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
17959
18132
  void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
17960
18133
  }
17961
18134
  }
17962
18135
  for (const exchange of modernExchanges) {
17963
18136
  if (!exchange.sessionKey) continue;
17964
- 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();
17965
18138
  if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
17966
18139
  void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
17967
18140
  }
@@ -18016,7 +18189,7 @@ function registerMcpHttpRoutes(scope, opts) {
18016
18189
  clientName: `canonry-mcp/${PACKAGE_VERSION}`,
18017
18190
  surface: "mcp-http",
18018
18191
  // Correlation only: this is never accepted as caller identity or authority.
18019
- ...modern ? {} : { actorSession: crypto26.randomUUID() }
18192
+ ...modern ? {} : { actorSession: crypto27.randomUUID() }
18020
18193
  });
18021
18194
  let server;
18022
18195
  try {
@@ -18041,7 +18214,7 @@ function registerMcpHttpRoutes(scope, opts) {
18041
18214
  if (!bearer) return null;
18042
18215
  const { server, sessionKey } = createAuthorizedServer(request, segment, bearer);
18043
18216
  const transport = new NodeStreamableHTTPServerTransport({
18044
- sessionIdGenerator: () => crypto26.randomUUID(),
18217
+ sessionIdGenerator: () => crypto27.randomUUID(),
18045
18218
  onsessioninitialized: (id) => {
18046
18219
  if (!shuttingDown && !closing) sessions.set(id, session);
18047
18220
  }
@@ -18211,7 +18384,7 @@ function registerMcpHttpRoutes(scope, opts) {
18211
18384
  }
18212
18385
 
18213
18386
  // src/agent/agent-routes.ts
18214
- import { eq as eq32 } from "drizzle-orm";
18387
+ import { eq as eq33 } from "drizzle-orm";
18215
18388
  function viewerAeroCaller(request, opts) {
18216
18389
  if (!opts.viewerSessions) return null;
18217
18390
  const principal = request.principal;
@@ -18251,7 +18424,7 @@ function turnAttribution(request) {
18251
18424
  };
18252
18425
  }
18253
18426
  function resolveProject(db, name) {
18254
- 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();
18255
18428
  if (!row) throw notFound("project", name);
18256
18429
  return row;
18257
18430
  }
@@ -18370,7 +18543,7 @@ function registerAgentRoutes(app, opts) {
18370
18543
  }
18371
18544
  requireInstanceAdministrator(request);
18372
18545
  const project = resolveProject(opts.db, request.params.name);
18373
- 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();
18374
18547
  const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
18375
18548
  if (!row) {
18376
18549
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
@@ -18412,7 +18585,7 @@ function registerAgentRoutes(app, opts) {
18412
18585
  const project = resolveProject(opts.db, request.params.name);
18413
18586
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
18414
18587
  opts.sessionRegistry.reset(project.name);
18415
- 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();
18416
18589
  return { status: "reset" };
18417
18590
  }
18418
18591
  );
@@ -18436,7 +18609,7 @@ function registerAgentRoutes(app, opts) {
18436
18609
  return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
18437
18610
  }
18438
18611
  if (body.conversationId !== void 0) {
18439
- 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();
18440
18613
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
18441
18614
  }
18442
18615
  const promptText = body.prompt;
@@ -19396,7 +19569,7 @@ function clipText(value, length) {
19396
19569
  }
19397
19570
 
19398
19571
  // src/research-runner.ts
19399
- 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";
19400
19573
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
19401
19574
  function finalResearchRunStatus(completed, failed, waiting) {
19402
19575
  if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
@@ -19404,13 +19577,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
19404
19577
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
19405
19578
  }
19406
19579
  async function executeResearchRun(db, registry, runId, projectId, options = {}) {
19407
- 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();
19408
19581
  if (!run || run.status !== ResearchRunStatuses.queued) return;
19409
- 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();
19410
19583
  if (!project) return;
19411
19584
  const provider = registry.get(run.provider);
19412
19585
  const now = (/* @__PURE__ */ new Date()).toISOString();
19413
- 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();
19414
19587
  if (claim.changes !== 1) return;
19415
19588
  const elapsed = startOutcomeTimer();
19416
19589
  const requested = currentOutcomeOrigin();
@@ -19444,8 +19617,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19444
19617
  }
19445
19618
  reserved = run.totalQueries;
19446
19619
  reservation = { scope, period };
19447
- const rows = db.select().from(researchRunQueries).where(eq33(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
19448
- 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();
19449
19622
  const competitorDomains = competitorRows.map((row) => row.domain);
19450
19623
  const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
19451
19624
  const domains = effectiveDomains(project);
@@ -19455,7 +19628,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19455
19628
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
19456
19629
  try {
19457
19630
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
19458
- 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();
19459
19632
  const raw = await gate.run(async () => {
19460
19633
  dispatched++;
19461
19634
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -19486,7 +19659,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
19486
19659
  rawResponse: raw.rawResponse,
19487
19660
  answerEvidence: raw.evidence ?? null,
19488
19661
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19489
- }).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();
19490
19663
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
19491
19664
  } catch (error) {
19492
19665
  if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
@@ -19537,11 +19710,11 @@ function researchOutcomeStatus(status) {
19537
19710
  }
19538
19711
  }
19539
19712
  function incrementResearchProgress(db, runId, column) {
19540
- 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();
19541
19714
  }
19542
19715
  function markResearchQueryFailed(db, runId, queryId, error) {
19543
19716
  const message = describeError(error);
19544
- const unfinished = and27(eq33(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19717
+ const unfinished = and28(eq34(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19545
19718
  if (error instanceof IncompleteAnswerError && error.resumable) {
19546
19719
  db.update(researchRunQueries).set({
19547
19720
  status: ResearchQueryStatuses["waiting-upstream"],
@@ -19557,10 +19730,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
19557
19730
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
19558
19731
  }
19559
19732
  function markUnfinishedResearchQueriesFailed(db, runId, error) {
19560
- 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();
19561
19734
  }
19562
19735
  function finalizeResearchRun(db, runId, fatalError) {
19563
- 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();
19564
19737
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
19565
19738
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
19566
19739
  const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
@@ -19572,12 +19745,12 @@ function finalizeResearchRun(db, runId, fatalError) {
19572
19745
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
19573
19746
  // A run waiting on upstream jobs has not finished.
19574
19747
  finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
19575
- }).where(eq33(researchRuns.id, runId)).run();
19748
+ }).where(eq34(researchRuns.id, runId)).run();
19576
19749
  return { status, total: rows.length, completed, failed };
19577
19750
  }
19578
19751
 
19579
19752
  // src/google-marketing-sync.ts
19580
- 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";
19581
19754
  var RUNTIME_FAILURE_REASONS = {
19582
19755
  INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
19583
19756
  NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
@@ -19614,10 +19787,10 @@ function assertSnapshotContext(metadata, expected) {
19614
19787
  }
19615
19788
  }
19616
19789
  function requireSyncRun(db, runId, projectId, kind) {
19617
- const run = db.select().from(runs).where(and28(
19618
- eq34(runs.id, runId),
19619
- eq34(runs.projectId, projectId),
19620
- 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)
19621
19794
  )).get();
19622
19795
  if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
19623
19796
  if (run.status !== RunStatuses.queued) {
@@ -19633,19 +19806,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
19633
19806
  status: RunStatuses.failed,
19634
19807
  error: serializeRunError({ message: safeErrorMessage(error) }),
19635
19808
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19636
- }).where(and28(
19637
- eq34(runs.id, runId),
19638
- eq34(runs.projectId, projectId),
19639
- eq34(runs.kind, kind),
19640
- 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)
19641
19814
  )).run();
19642
19815
  }
19643
19816
  function claimSyncRun(db, runId, projectId, kind, startedAt) {
19644
- const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and28(
19645
- eq34(runs.id, runId),
19646
- eq34(runs.projectId, projectId),
19647
- eq34(runs.kind, kind),
19648
- 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)
19649
19822
  )).run();
19650
19823
  return claim.changes === 1;
19651
19824
  }
@@ -19655,9 +19828,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19655
19828
  let evidenceCollected = false;
19656
19829
  try {
19657
19830
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
19658
- 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();
19659
19832
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19660
- 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();
19661
19834
  if (!connection?.selectedCustomerId) {
19662
19835
  throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19663
19836
  }
@@ -19709,11 +19882,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19709
19882
  status: RunStatuses.completed,
19710
19883
  finishedAt,
19711
19884
  error: null
19712
- }).where(and28(
19713
- eq34(runs.id, runId),
19714
- eq34(runs.projectId, projectId),
19715
- eq34(runs.kind, RunKinds["google-ads-sync"]),
19716
- 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)
19717
19890
  )).run();
19718
19891
  if (terminalClaim.changes !== 1) {
19719
19892
  throw withOutcomeReason(
@@ -19733,12 +19906,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19733
19906
  lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
19734
19907
  lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
19735
19908
  updatedAt: finishedAt
19736
- }).where(and28(
19737
- eq34(googleAdsConnections.id, connection.id),
19738
- eq34(googleAdsConnections.projectId, projectId),
19739
- eq34(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19740
- connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq34(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19741
- 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)
19742
19915
  )).run();
19743
19916
  if (connectionClaim.changes !== 1) {
19744
19917
  throw withOutcomeReason(
@@ -19778,9 +19951,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19778
19951
  let evidenceCollected = false;
19779
19952
  try {
19780
19953
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
19781
- 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();
19782
19955
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19783
- 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();
19784
19957
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
19785
19958
  throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19786
19959
  }
@@ -19818,11 +19991,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19818
19991
  status: RunStatuses.completed,
19819
19992
  finishedAt,
19820
19993
  error: null
19821
- }).where(and28(
19822
- eq34(runs.id, runId),
19823
- eq34(runs.projectId, projectId),
19824
- eq34(runs.kind, RunKinds["gtm-sync"]),
19825
- 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)
19826
19999
  )).run();
19827
20000
  if (terminalClaim.changes !== 1) {
19828
20001
  throw withOutcomeReason(
@@ -19843,13 +20016,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19843
20016
  lastSnapshotAt: snapshot.metadata.capturedAt,
19844
20017
  lastSnapshotId: snapshot.metadata.id,
19845
20018
  updatedAt: finishedAt
19846
- }).where(and28(
19847
- eq34(gtmConnections.id, connection.id),
19848
- eq34(gtmConnections.projectId, projectId),
19849
- eq34(gtmConnections.selectedAccountId, connection.selectedAccountId),
19850
- eq34(gtmConnections.selectedContainerId, connection.selectedContainerId),
19851
- connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq34(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
19852
- 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)
19853
20026
  )).run();
19854
20027
  if (connectionClaim.changes !== 1) {
19855
20028
  throw withOutcomeReason(
@@ -20052,8 +20225,8 @@ var DASHBOARD_SCRYPT_KEYLEN = 64;
20052
20225
  var DASHBOARD_SCRYPT_COST = 1 << 15;
20053
20226
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
20054
20227
  function hashDashboardPassword(password) {
20055
- const salt = crypto27.randomBytes(16);
20056
- 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, {
20057
20230
  N: DASHBOARD_SCRYPT_COST,
20058
20231
  maxmem: DASHBOARD_SCRYPT_MAXMEM
20059
20232
  });
@@ -20074,14 +20247,14 @@ function verifyDashboardPassword(password, storedHash) {
20074
20247
  } catch {
20075
20248
  return { ok: false, needsRehash: false };
20076
20249
  }
20077
- const derived = crypto27.scryptSync(password, salt, expected.length, {
20250
+ const derived = crypto28.scryptSync(password, salt, expected.length, {
20078
20251
  N: DASHBOARD_SCRYPT_COST,
20079
20252
  maxmem: DASHBOARD_SCRYPT_MAXMEM
20080
20253
  });
20081
20254
  if (derived.length !== expected.length)
20082
20255
  return { ok: false, needsRehash: false };
20083
20256
  return {
20084
- ok: crypto27.timingSafeEqual(derived, expected),
20257
+ ok: crypto28.timingSafeEqual(derived, expected),
20085
20258
  needsRehash: false
20086
20259
  };
20087
20260
  }
@@ -20090,7 +20263,7 @@ function verifyDashboardPassword(password, storedHash) {
20090
20263
  const expected = Buffer.from(storedHash, "hex");
20091
20264
  if (candidate.length !== expected.length)
20092
20265
  return { ok: false, needsRehash: false };
20093
- const ok = crypto27.timingSafeEqual(candidate, expected);
20266
+ const ok = crypto28.timingSafeEqual(candidate, expected);
20094
20267
  return { ok, needsRehash: ok };
20095
20268
  }
20096
20269
  return { ok: false, needsRehash: false };
@@ -20257,7 +20430,7 @@ function cloneGtmConfig(config) {
20257
20430
  function createGoogleMarketingConfigCredentialStore(input) {
20258
20431
  const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
20259
20432
  const env = input.env ?? process.env;
20260
- const randomUUID2 = input.randomUUID ?? crypto27.randomUUID;
20433
+ const randomUUID2 = input.randomUUID ?? crypto28.randomUUID;
20261
20434
  const snapshot = () => ({
20262
20435
  googleAds: cloneGoogleAdsConfig(input.config.googleAds),
20263
20436
  gtm: cloneGtmConfig(input.config.gtm)
@@ -20374,7 +20547,7 @@ async function createServer(opts) {
20374
20547
  const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
20375
20548
  const app = Fastify({
20376
20549
  loggerInstance: logger,
20377
- genReqId: () => crypto27.randomUUID(),
20550
+ genReqId: () => crypto28.randomUUID(),
20378
20551
  trustProxy
20379
20552
  });
20380
20553
  if (!ownsDefaultConfig) {
@@ -20505,7 +20678,7 @@ async function createServer(opts) {
20505
20678
  async (ctx) => {
20506
20679
  if (!sessionRegistry) return;
20507
20680
  if (!agentProactive) return;
20508
- 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();
20509
20682
  if (!project) return;
20510
20683
  let content;
20511
20684
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -20979,7 +21152,7 @@ async function createServer(opts) {
20979
21152
  void (async () => {
20980
21153
  try {
20981
21154
  const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
20982
- 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();
20983
21156
  if (!project) {
20984
21157
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
20985
21158
  return;
@@ -21173,7 +21346,7 @@ async function createServer(opts) {
21173
21346
  return removed;
21174
21347
  }
21175
21348
  };
21176
- 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");
21177
21350
  const googleConnectionStore = {
21178
21351
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
21179
21352
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -21271,11 +21444,11 @@ async function createServer(opts) {
21271
21444
  })();
21272
21445
  if (opts.config.apiKey) {
21273
21446
  const keyHash = hashApiKey(opts.config.apiKey);
21274
- 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();
21275
21448
  if (!existing) {
21276
21449
  const prefix = opts.config.apiKey.slice(0, 12);
21277
21450
  opts.db.insert(apiKeys).values({
21278
- id: `key_${crypto27.randomBytes(8).toString("hex")}`,
21451
+ id: `key_${crypto28.randomBytes(8).toString("hex")}`,
21279
21452
  name: "default",
21280
21453
  keyHash,
21281
21454
  keyPrefix: prefix,
@@ -21290,14 +21463,14 @@ async function createServer(opts) {
21290
21463
  );
21291
21464
  const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
21292
21465
  opts.db.delete(dashboardSessions).where(or4(
21293
- eq35(dashboardSessions.apiKeyHash, ""),
21294
- and29(
21466
+ eq36(dashboardSessions.apiKeyHash, ""),
21467
+ and30(
21295
21468
  isNotNull5(dashboardSessions.passwordFingerprint),
21296
21469
  ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
21297
21470
  )
21298
21471
  )).run();
21299
21472
  if (!opts.config.dashboardPasswordHash) {
21300
- 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;
21301
21474
  const createdAt = lastPasswordEventAt("dashboard-password.created");
21302
21475
  const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
21303
21476
  if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
@@ -21312,7 +21485,7 @@ async function createServer(opts) {
21312
21485
  const createSession = (key, passwordBased = false, db = opts.db) => {
21313
21486
  const now = /* @__PURE__ */ new Date();
21314
21487
  db.delete(dashboardSessions).where(lte3(dashboardSessions.expiresAt, now.toISOString())).run();
21315
- const sessionId = crypto27.randomBytes(32).toString("hex");
21488
+ const sessionId = crypto28.randomBytes(32).toString("hex");
21316
21489
  db.insert(dashboardSessions).values({
21317
21490
  tokenHash: hashSessionToken(sessionId),
21318
21491
  apiKeyId: key.id,
@@ -21331,23 +21504,23 @@ async function createServer(opts) {
21331
21504
  currentApiKeyHash: apiKeys.keyHash,
21332
21505
  passwordFingerprint: dashboardSessions.passwordFingerprint,
21333
21506
  expiresAt: dashboardSessions.expiresAt
21334
- }).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();
21335
21508
  if (!session) return null;
21336
21509
  const expiresAt = Date.parse(session.expiresAt);
21337
21510
  if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
21338
- opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, tokenHash)).run();
21511
+ opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, tokenHash)).run();
21339
21512
  return null;
21340
21513
  }
21341
21514
  return session.apiKeyId;
21342
21515
  };
21343
21516
  const clearSession = (sessionId) => {
21344
21517
  if (sessionId) {
21345
- opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21518
+ opts.db.delete(dashboardSessions).where(eq36(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21346
21519
  }
21347
21520
  };
21348
21521
  const getDefaultApiKey = () => {
21349
21522
  if (!opts.config.apiKey) return void 0;
21350
- 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();
21351
21524
  };
21352
21525
  const setSessionCookie = (reply, sessionId) => {
21353
21526
  reply.header(
@@ -21376,7 +21549,7 @@ async function createServer(opts) {
21376
21549
  if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
21377
21550
  const presented = Buffer.from(hashApiKey(parts[1]), "hex");
21378
21551
  const expected = Buffer.from(key.keyHash, "hex");
21379
- return presented.length === expected.length && crypto27.timingSafeEqual(presented, expected);
21552
+ return presented.length === expected.length && crypto28.timingSafeEqual(presented, expected);
21380
21553
  };
21381
21554
  const namedAccountsInUse = () => anyUsersExist(opts.db);
21382
21555
  const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
@@ -21508,12 +21681,12 @@ async function createServer(opts) {
21508
21681
  return reply.send({ authenticated: true });
21509
21682
  }
21510
21683
  if (apiKey) {
21511
- 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();
21512
21685
  if (!key || key.revokedAt) {
21513
21686
  const err2 = authInvalid();
21514
21687
  return reply.status(err2.statusCode).send(err2.toJSON());
21515
21688
  }
21516
- 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();
21517
21690
  setSessionCookie(reply, createSession(key));
21518
21691
  return reply.send({ authenticated: true });
21519
21692
  }
@@ -21591,7 +21764,7 @@ async function createServer(opts) {
21591
21764
  const recomputeDebts = /* @__PURE__ */ new Map();
21592
21765
  let recomputeSequence = 0;
21593
21766
  const markAnswerFieldRecompute = (projectId, full) => {
21594
- 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();
21595
21768
  };
21596
21769
  const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
21597
21770
  const sequence = ++recomputeSequence;
@@ -21609,10 +21782,10 @@ async function createServer(opts) {
21609
21782
  if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
21610
21783
  try {
21611
21784
  if (debt.latest === sequence && debt.latestFull === 0) {
21612
- 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();
21613
21786
  if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
21614
21787
  } else if (debt.latestFull === 0) {
21615
- 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();
21616
21789
  }
21617
21790
  } catch (err) {
21618
21791
  app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
@@ -21649,7 +21822,7 @@ async function createServer(opts) {
21649
21822
  await yieldToEventLoop4();
21650
21823
  const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
21651
21824
  competitorAutoAliasReasons.delete(projectId);
21652
- 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();
21653
21826
  if (!project || project.competitorAutoAliases === "off") return;
21654
21827
  if (project.competitorAutoAliases === "apply") {
21655
21828
  await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
@@ -21849,8 +22022,8 @@ async function createServer(opts) {
21849
22022
  getAgentPin: () => describeAgentPin(opts.config),
21850
22023
  getGoogleMarketingDoctorInput: (ctx) => {
21851
22024
  if (!ctx.project) return null;
21852
- const googleAds = opts.db.select().from(googleAdsConnections).where(eq35(googleAdsConnections.projectId, ctx.project.id)).get();
21853
- 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();
21854
22027
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
21855
22028
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
21856
22029
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -21979,7 +22152,7 @@ async function createServer(opts) {
21979
22152
  deps: {
21980
22153
  enqueueAutoExtract: ({ projectId, release: r }) => {
21981
22154
  const now = (/* @__PURE__ */ new Date()).toISOString();
21982
- const runId = crypto27.randomUUID();
22155
+ const runId = crypto28.randomUUID();
21983
22156
  opts.db.insert(runs).values({
21984
22157
  id: runId,
21985
22158
  projectId,
@@ -22100,7 +22273,7 @@ async function createServer(opts) {
22100
22273
  ...inspectOpts,
22101
22274
  config: opts.config
22102
22275
  }).then(() => {
22103
- 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();
22104
22277
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
22105
22278
  return maybeRefreshGscCoverage(
22106
22279
  opts.db,
@@ -22612,7 +22785,7 @@ async function createServer(opts) {
22612
22785
  isBacklinksInstalled: () => isDuckdbInstalled()
22613
22786
  });
22614
22787
  }
22615
- 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()) {
22616
22789
  dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
22617
22790
  }
22618
22791
  runtimeStartupSettled = true;