@canonry/canonry 7.87.2 → 7.88.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 (134) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +31 -5
  2. package/assets/assets/{AgentSetupPage-_ZP3F_zA.js → AgentSetupPage-DV06oPTd.js} +1 -1
  3. package/assets/assets/{AuditHistoryPanel-9B0XpYsN.js → AuditHistoryPanel-C1JnxBFK.js} +1 -1
  4. package/assets/assets/{BacklinksPage-CsvVgXTZ.js → BacklinksPage-Dw0wgYlg.js} +1 -1
  5. package/assets/assets/{HistoryPage-BCxpCul0.js → HistoryPage-DsDx15am.js} +1 -1
  6. package/assets/assets/{MeasurementPropertyPage-BObvWjVx.js → MeasurementPropertyPage-Bkxt3QcY.js} +1 -1
  7. package/assets/assets/{ProjectPage-y378tOaT.js → ProjectPage-CzrD1lpf.js} +1 -1
  8. package/assets/assets/{RunsPage-CfQ_kqlu.js → RunsPage-BggqKbOG.js} +1 -1
  9. package/assets/assets/{SettingsPage-PIgRvqJc.js → SettingsPage-B7ns_ECx.js} +1 -1
  10. package/assets/assets/{SiteHealthSection-DuUE1cB0.js → SiteHealthSection-B2YibqCs.js} +2 -2
  11. package/assets/assets/{ToneBadge-BgTLSTKp.js → ToneBadge-B8hKcr8p.js} +1 -1
  12. package/assets/assets/{TrafficPage-B_YunwjH.js → TrafficPage-DRTbgzKI.js} +1 -1
  13. package/assets/assets/{TrafficSourceDetailPage-C51W87fU.js → TrafficSourceDetailPage-sgLE2gdJ.js} +1 -1
  14. package/assets/assets/{account-context-CVjb3ZSn.js → account-context-pzV-boTi.js} +1 -1
  15. package/assets/assets/{agent-trust-copy-BlpC8jo5.js → agent-trust-copy-DBtiOpFu.js} +1 -1
  16. package/assets/assets/{card-BnVez9nG.js → card-Mu_5i5fM.js} +1 -1
  17. package/assets/assets/{extract-error-message-Dqirbrl0.js → extract-error-message-D_ptq0tW.js} +1 -1
  18. package/assets/assets/{format-helpers-DVXZD864.js → format-helpers-C8QYorwm.js} +1 -1
  19. package/assets/assets/{index-CsvEqhK4.js → index-fN5qDR8H.js} +6 -6
  20. package/assets/assets/{query-client-CVc7wMKJ.js → query-client-CdG4DC3B.js} +1 -1
  21. package/assets/assets/{react-query-CSlTSOjz.js → react-query-CxeygwoL.js} +1 -1
  22. package/assets/assets/{react-sigma_core.esm.min-CVfGIEIE.js → react-sigma_core.esm.min-BfnfPCis.js} +1 -1
  23. package/assets/assets/{service-BGqeyqhZ.js → service-B85wjZQP.js} +1 -1
  24. package/assets/index.html +7 -7
  25. package/dist/{ads-ZUZWSIBV.js → ads-4TAIMWZC.js} +3 -3
  26. package/dist/{agent-YDS3WY7B.js → agent-C7NKP2TO.js} +7 -7
  27. package/dist/{agent-ask-7VUPXTXL.js → agent-ask-EPH5FDM6.js} +3 -3
  28. package/dist/{agent-conversations-WPEBK3BS.js → agent-conversations-LPBBVRKK.js} +3 -3
  29. package/dist/{agent-journeys-H5CCYLNB.js → agent-journeys-MBORINJA.js} +3 -3
  30. package/dist/{agent-memory-CNPKPIWD.js → agent-memory-NJIZO75R.js} +3 -3
  31. package/dist/{agent-providers-U7FLJO5I.js → agent-providers-ZQVUOCX6.js} +3 -3
  32. package/dist/{agent-transcript-VY3BQNPU.js → agent-transcript-67HFY5GT.js} +3 -3
  33. package/dist/{analytics-PYUNO7C2.js → analytics-TUO6BVB6.js} +3 -3
  34. package/dist/{apply-YNBEYC2G.js → apply-V7A5O7YS.js} +3 -3
  35. package/dist/{backfill-6AYHWEQX.js → backfill-PGKYR6NA.js} +10 -10
  36. package/dist/{backlinks-LVHZ2MQ7.js → backlinks-5GGPQNR4.js} +3 -3
  37. package/dist/bing-PQOLIW2X.js +759 -0
  38. package/dist/{bootstrap-27NOBWTP.js → bootstrap-IH3XX6PI.js} +10 -10
  39. package/dist/{cdp-HCYY4SXR.js → cdp-B3PFQ5RB.js} +7 -7
  40. package/dist/{chunk-TFB2S6TQ.js → chunk-2KL2XHS6.js} +4 -4
  41. package/dist/{chunk-DOPUEXMC.js → chunk-4A4X7VXE.js} +2 -2
  42. package/dist/{chunk-2AXFKYJU.js → chunk-4T2VZPDG.js} +2 -2
  43. package/dist/{chunk-AI75KS7G.js → chunk-4VMHN5LL.js} +2497 -1577
  44. package/dist/{chunk-2FJB3CSS.js → chunk-5KDJJO2N.js} +1 -1
  45. package/dist/{chunk-BJHHD7VE.js → chunk-5WWEZ26Y.js} +2 -2
  46. package/dist/{chunk-ZCNKGBIV.js → chunk-6DIDDYG6.js} +5 -5
  47. package/dist/{chunk-IBGZ6D6R.js → chunk-BDLC3DFM.js} +2 -2
  48. package/dist/{chunk-IK7QEKKD.js → chunk-CYQCO5NY.js} +1 -1
  49. package/dist/{chunk-FAMCDCVK.js → chunk-D354CQ6T.js} +1 -1
  50. package/dist/{chunk-3KK6FWDF.js → chunk-DILCH7RL.js} +1 -1
  51. package/dist/{chunk-IVGVPPQT.js → chunk-DVWAA3ES.js} +1088 -646
  52. package/dist/{chunk-T2MGGW6Y.js → chunk-E4DUE2PW.js} +6 -6
  53. package/dist/{chunk-TEQF4ZJA.js → chunk-EGUJNHAZ.js} +162 -2
  54. package/dist/{chunk-JDTKG7KJ.js → chunk-EL4HRIID.js} +3 -3
  55. package/dist/{chunk-LYTBHEID.js → chunk-EYSQMZ6L.js} +530 -342
  56. package/dist/{chunk-MNLAIB67.js → chunk-FL76YWYN.js} +1 -1
  57. package/dist/{chunk-INH773FB.js → chunk-FLJVGHTQ.js} +3 -3
  58. package/dist/{chunk-4LNZTL7M.js → chunk-FNQFM3TK.js} +3 -3
  59. package/dist/{chunk-GBWCEOZR.js → chunk-IPOYH4WE.js} +1 -1
  60. package/dist/{chunk-POMLL2OZ.js → chunk-IR3MYKIA.js} +1 -1
  61. package/dist/{chunk-IE4D47JE.js → chunk-LBZYPZAA.js} +7 -7
  62. package/dist/{chunk-4FWDITQG.js → chunk-MCQK432C.js} +1 -1
  63. package/dist/{chunk-CBWGUGME.js → chunk-MEN27RAJ.js} +18 -2
  64. package/dist/{chunk-BVJA3TV2.js → chunk-N6AWKBLY.js} +5 -5
  65. package/dist/{chunk-TN53B5AB.js → chunk-O7OZVZWR.js} +210 -3
  66. package/dist/{chunk-6F5ULQDY.js → chunk-OKLDBQYW.js} +4 -4
  67. package/dist/{chunk-ZJVQ72VC.js → chunk-PBOIN52J.js} +2 -2
  68. package/dist/{chunk-GXTCDNX7.js → chunk-PZLEKWYA.js} +1 -1
  69. package/dist/{chunk-F643WELJ.js → chunk-Q2SJRZA3.js} +297 -5
  70. package/dist/{chunk-ICS2WJ7H.js → chunk-R7K24KY7.js} +3 -3
  71. package/dist/{chunk-3FHBPJ5R.js → chunk-TAQHHXBC.js} +4 -4
  72. package/dist/{chunk-JAYUWQAV.js → chunk-TL52ELES.js} +1 -1
  73. package/dist/{chunk-GDU6IMGB.js → chunk-UVXQQZB5.js} +2 -2
  74. package/dist/{chunk-AYYZ4HKJ.js → chunk-ZQGWB7VJ.js} +230 -1
  75. package/dist/{citations-3EZTHA65.js → citations-J3IGPX4L.js} +3 -3
  76. package/dist/{cli-catalog-CBTM3JKP.js → cli-catalog-2PCRGKPH.js} +3 -3
  77. package/dist/{cli-commands-H7BFVUGC.js → cli-commands-4MUJZCZO.js} +7 -7
  78. package/dist/cli.js +13 -13
  79. package/dist/{competitor-VH4TXJLT.js → competitor-X7Q2AZGL.js} +3 -3
  80. package/dist/{content-TSMNHXJZ.js → content-HTR3PJPX.js} +3 -3
  81. package/dist/{dashboard-KT2ACPI5.js → dashboard-LSO57ZEE.js} +6 -6
  82. package/dist/{demo-LWSLULEZ.js → demo-P25N4SKK.js} +1 -1
  83. package/dist/{demo-server-UENMDVMG.js → demo-server-6Z5XSHFS.js} +12 -12
  84. package/dist/{discover-XTJXTKCI.js → discover-KE6TQPOC.js} +3 -3
  85. package/dist/{discover-eval-IVQI5YEW.js → discover-eval-XKOYGCRW.js} +3 -3
  86. package/dist/{doctor-SJ4SHSQO.js → doctor-RL2J54I3.js} +3 -3
  87. package/dist/{evidence-C6T556YH.js → evidence-CYJ4AKUZ.js} +3 -3
  88. package/dist/{export-cmd-JQZ5ZRSI.js → export-cmd-2YM3PXCW.js} +3 -3
  89. package/dist/{ga-W2WHVRHN.js → ga-XAB66MJO.js} +3 -3
  90. package/dist/{gbp-3AZIV5WF.js → gbp-CDGBLRVV.js} +3 -3
  91. package/dist/{google-3CKWPZT7.js → google-XHCUKQ3J.js} +4 -4
  92. package/dist/{google-marketing-PJBGQYYW.js → google-marketing-ZR6NTWEY.js} +1 -1
  93. package/dist/{health-cmd-4JTQMCXM.js → health-cmd-VS3EIFJD.js} +3 -3
  94. package/dist/{history-TXUJFEO2.js → history-YIGYYNZJ.js} +3 -3
  95. package/dist/index.d.ts +1893 -401
  96. package/dist/index.js +25 -25
  97. package/dist/{init-TF3OPCEC.js → init-ST3BXHQF.js} +12 -12
  98. package/dist/{insights-XZQO4B45.js → insights-KZS6TYYL.js} +3 -3
  99. package/dist/intelligence-service-LCTP5BC7.js +16 -0
  100. package/dist/{journeys-VFIV5JYV.js → journeys-USOPWGW5.js} +3 -3
  101. package/dist/{keys-PV23G4LW.js → keys-XY7LSM3S.js} +3 -3
  102. package/dist/{keyword-D76KBIV5.js → keyword-PKBFHHF5.js} +3 -3
  103. package/dist/{logs-DYDZ6IEJ.js → logs-PROW5TQJ.js} +3 -3
  104. package/dist/{mcp-GAR5LDQD.js → mcp-OQGC62QU.js} +4 -4
  105. package/dist/mcp.js +9 -9
  106. package/dist/{measurement-plan-K24EOGMI.js → measurement-plan-AI4NNBTO.js} +5 -5
  107. package/dist/{notify-XQHMPDQQ.js → notify-6HRPNJZ5.js} +3 -3
  108. package/dist/{organic-evidence-UHHE7AO4.js → organic-evidence-IXCSUGGY.js} +3 -3
  109. package/dist/{overview-AUZ3XRS5.js → overview-2WS3CFHK.js} +4 -4
  110. package/dist/{project-FUCPU2BD.js → project-ENUZG62V.js} +3 -3
  111. package/dist/{query-HRMSXDKL.js → query-WE3ZWTIB.js} +3 -3
  112. package/dist/{research-R23XLZJ3.js → research-DZSD5UZ2.js} +3 -3
  113. package/dist/{results-export-N6IV3KG4.js → results-export-JMUMYNUE.js} +3 -3
  114. package/dist/{run-CJLG5EXH.js → run-QXOO7CRK.js} +4 -4
  115. package/dist/{schedule-IPDCQ2I5.js → schedule-3RJKGUDN.js} +3 -3
  116. package/dist/{search-ZU4AKJ76.js → search-SGS4H7I4.js} +3 -3
  117. package/dist/{serve-LLIUILD2.js → serve-T24QKEUY.js} +26 -26
  118. package/dist/{session-2NQKSF2N.js → session-HPH2A4AZ.js} +13 -13
  119. package/dist/{settings-4FS5NRKP.js → settings-K44RK2TW.js} +3 -3
  120. package/dist/{skills-GSFHACGN.js → skills-HGPPGBXG.js} +2 -2
  121. package/dist/{snapshot-4NUVPDK3.js → snapshot-FEHHQRY3.js} +3 -3
  122. package/dist/{src-JQYZ2TP6.js → src-4PAYI7GE.js} +16 -2
  123. package/dist/{status-ASBLMFP2.js → status-L5HVFWTS.js} +4 -4
  124. package/dist/{technical-aeo-QXHRBGYY.js → technical-aeo-Z43UCW6E.js} +3 -3
  125. package/dist/{telemetry-J2I577I3.js → telemetry-D47SSSG7.js} +4 -4
  126. package/dist/{tools-UPJRW4DN.js → tools-WCWUY2ZJ.js} +11 -11
  127. package/dist/{traffic-GWBZKYR5.js → traffic-5KH62P43.js} +5 -5
  128. package/dist/{trends-YOLCJBNM.js → trends-RMAXZKRO.js} +3 -3
  129. package/dist/{users-LPSCZV3M.js → users-WT54XNEI.js} +3 -3
  130. package/dist/{visibility-stats-JJQTZSSK.js → visibility-stats-HU62U5MA.js} +3 -3
  131. package/dist/{wordpress-4IKDT5KT.js → wordpress-BDAKQ4MU.js} +3 -3
  132. package/package.json +1 -1
  133. package/dist/bing-A2MUG6AV.js +0 -418
  134. package/dist/intelligence-service-LEH5UVIC.js +0 -16
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  answerServiceAdapters,
3
3
  answerServiceDescriptors
4
- } from "./chunk-IK7QEKKD.js";
4
+ } from "./chunk-CYQCO5NY.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-T2MGGW6Y.js";
30
+ } from "./chunk-E4DUE2PW.js";
31
31
  import {
32
32
  AeroToolProfiles,
33
33
  AeroToolScopes,
34
34
  buildAeroStateTools,
35
35
  isAeroToolProfile,
36
36
  truncateToolResult
37
- } from "./chunk-TFB2S6TQ.js";
37
+ } from "./chunk-2KL2XHS6.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-4FWDITQG.js";
56
+ } from "./chunk-MCQK432C.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-BVJA3TV2.js";
68
+ } from "./chunk-N6AWKBLY.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-6F5ULQDY.js";
80
+ } from "./chunk-OKLDBQYW.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-3KK6FWDF.js";
88
+ } from "./chunk-DILCH7RL.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-DOPUEXMC.js";
100
+ } from "./chunk-4A4X7VXE.js";
101
101
  import {
102
102
  createCanonryMcpServer
103
- } from "./chunk-ICS2WJ7H.js";
103
+ } from "./chunk-R7K24KY7.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-TEQF4ZJA.js";
110
+ } from "./chunk-EGUJNHAZ.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-2FJB3CSS.js";
142
+ } from "./chunk-5KDJJO2N.js";
143
143
  import {
144
144
  getBundledSkillSnapshots
145
- } from "./chunk-FAMCDCVK.js";
145
+ } from "./chunk-D354CQ6T.js";
146
146
  import {
147
147
  computeFactorAverages,
148
148
  deleteSiteCrawlGraphLayout,
149
149
  persistSiteCrawlGraphLayout,
150
150
  prepareSiteCrawlGraphLayout,
151
151
  recordedSharePct
152
- } from "./chunk-ZJVQ72VC.js";
152
+ } from "./chunk-PBOIN52J.js";
153
153
  import {
154
154
  CanonryMcpToolNames,
155
155
  canonryMcpTools
156
- } from "./chunk-TN53B5AB.js";
156
+ } from "./chunk-O7OZVZWR.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-ZCNKGBIV.js";
165
+ } from "./chunk-6DIDDYG6.js";
166
166
  import {
167
167
  computeCitedCompetitorDomains,
168
168
  determineCitationState
169
- } from "./chunk-JAYUWQAV.js";
169
+ } from "./chunk-TL52ELES.js";
170
170
  import {
171
171
  addLogListener,
172
172
  createFastifyLogger,
173
173
  createLogger
174
- } from "./chunk-IBGZ6D6R.js";
174
+ } from "./chunk-BDLC3DFM.js";
175
175
  import {
176
176
  BingApiError,
177
177
  CC_CACHE_DIR,
@@ -199,6 +199,9 @@ import {
199
199
  auditFromRequest,
200
200
  backfillProjectAnswerMentions,
201
201
  backfillProjectAnswerMentionsInChunks,
202
+ bingDateToDay,
203
+ bingKeywordMarket,
204
+ bingPositionOrNull,
202
205
  blockedAddressReason,
203
206
  buildLocationProfileFields,
204
207
  captureSimpleMeasurementDefinition,
@@ -222,13 +225,31 @@ import {
222
225
  fetchDailyMetrics,
223
226
  fetchSearchAnalytics,
224
227
  formatStorefrontAddress,
228
+ getActivePagePreviewBlocks,
225
229
  getAttributes,
226
230
  getAuthUrl,
231
+ getBlockedUrls,
232
+ getConnectedPages,
233
+ getContentSubmissionQuota,
234
+ getCountryRegionSettings,
227
235
  getCrawlIssues,
236
+ getCrawlSettings,
237
+ getCrawlStats,
238
+ getDeepLinkBlocks,
239
+ getFeedDetails,
240
+ getFeeds,
241
+ getFetchedUrls,
242
+ getKeywordHistory,
228
243
  getLodging,
244
+ getPageStats,
229
245
  getPlaceDetails,
230
246
  getPlaceReviewSignals,
247
+ getQueryParameters,
248
+ getQueryStats,
249
+ getRankAndTrafficStats,
250
+ getSiteRoles,
231
251
  getUrlInfo,
252
+ getUrlSubmissionQuota,
232
253
  hasOutstandingProviderBatch,
233
254
  hasProxyHeaders,
234
255
  hashApiKey,
@@ -238,6 +259,7 @@ import {
238
259
  hashSessionToken,
239
260
  inspectUrl,
240
261
  installDuckdb,
262
+ isBlockingCrawlIssue,
241
263
  isDuckdbInstalled,
242
264
  isValidReleaseId,
243
265
  latestQueryBasketRevision,
@@ -255,6 +277,7 @@ import {
255
277
  nextRunFromCron,
256
278
  nextRunFromSchedule,
257
279
  notProbeRun,
280
+ parseBingDate,
258
281
  parseCookieHeader,
259
282
  previewCompetitorAutoAliases,
260
283
  probeLatestRelease,
@@ -292,7 +315,7 @@ import {
292
315
  webhookResponseReason,
293
316
  webhookTargetRefusalReason,
294
317
  writeAuditLog
295
- } from "./chunk-AI75KS7G.js";
318
+ } from "./chunk-4VMHN5LL.js";
296
319
  import {
297
320
  OperationalLogStore,
298
321
  SentimentIdempotencyConflict,
@@ -311,6 +334,13 @@ import {
311
334
  backlinkSummaries,
312
335
  bingConnections,
313
336
  bingCoverageSnapshots,
337
+ bingCrawlStats,
338
+ bingKeywordDemand,
339
+ bingPageStats,
340
+ bingQueryStats,
341
+ bingSiteSnapshots,
342
+ bingSitemaps,
343
+ bingTrafficDaily,
314
344
  bingUrlInspections,
315
345
  ccReleaseSyncs,
316
346
  competitors,
@@ -380,7 +410,7 @@ import {
380
410
  unrecognizedAgentEventsHourly,
381
411
  unrecognizedAgentNotifyState,
382
412
  usageCounters
383
- } from "./chunk-AYYZ4HKJ.js";
413
+ } from "./chunk-ZQGWB7VJ.js";
384
414
  import {
385
415
  AGENT_MEMORY_VALUE_MAX_BYTES,
386
416
  AI_ENGINE_DOMAINS,
@@ -390,6 +420,7 @@ import {
390
420
  AgentProviderIds,
391
421
  AppError,
392
422
  BacklinkSources,
423
+ BingSiteSnapshotKinds,
393
424
  CHECK_RUNTIME_ERROR_SUFFIX,
394
425
  CITED_URL_CAPTURE_VERSION,
395
426
  CcReleaseSyncStatuses,
@@ -636,7 +667,7 @@ import {
636
667
  winnabilityClassLabel,
637
668
  withRetry,
638
669
  withSkippedProviders
639
- } from "./chunk-F643WELJ.js";
670
+ } from "./chunk-Q2SJRZA3.js";
640
671
 
641
672
  // src/google-marketing-runtime.ts
642
673
  import crypto2 from "crypto";
@@ -3320,13 +3351,13 @@ function createGoogleMarketingRuntime(options) {
3320
3351
 
3321
3352
  // src/server.ts
3322
3353
  import { createRequire } from "module";
3323
- import crypto26 from "crypto";
3354
+ import crypto27 from "crypto";
3324
3355
  import fs5 from "fs";
3325
3356
  import path5 from "path";
3326
3357
  import { isIP as isIP2 } from "net";
3327
3358
  import { fileURLToPath } from "url";
3328
3359
  import { setImmediate as yieldToEventLoop4 } from "timers/promises";
3329
- import { and as and28, eq as eq34, isNotNull as isNotNull5, isNull as isNull8, lte as lte3, max, ne as ne4, or as or4, sql as sql14 } from "drizzle-orm";
3360
+ 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";
3330
3361
 
3331
3362
  // src/seo-runtime.ts
3332
3363
  import { eq, inArray } from "drizzle-orm";
@@ -5940,7 +5971,7 @@ function nonBlank(value) {
5940
5971
  return trimmed ? trimmed : void 0;
5941
5972
  }
5942
5973
  function resolveBuildCommit(env = process.env) {
5943
- const embedded = true ? "3e6b78ab9a0ad676e2df968c5b36d5746d9639cf" : void 0;
5974
+ const embedded = true ? "ecd1f66b433f5d60221d22ded51250c0565edf7b" : void 0;
5944
5975
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
5945
5976
  }
5946
5977
  function resolveInstanceIdentity(env = process.env) {
@@ -7916,15 +7947,15 @@ var JobRunner = class {
7916
7947
  const dispatchable = [];
7917
7948
  for (const registered of activeProviders) {
7918
7949
  const name = registered.adapter.name;
7919
- const count5 = unitsByProvider.get(name)?.length ?? 0;
7950
+ const count6 = unitsByProvider.get(name)?.length ?? 0;
7920
7951
  const scope = `${projectId}:${adapterConnectionName(registered.adapter)}`;
7921
7952
  const limit = registered.config.quotaPolicy.maxRequestsPerDay;
7922
- const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count: count5, limit });
7953
+ const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count: count6, limit });
7923
7954
  if (!quota.reserved) {
7924
- providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${count5} more.`);
7955
+ providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${count6} more.`);
7925
7956
  continue;
7926
7957
  }
7927
- providerReservations.set(name, { scope, period: todayPeriod, reserved: count5 });
7958
+ providerReservations.set(name, { scope, period: todayPeriod, reserved: count6 });
7928
7959
  dispatchable.push(registered);
7929
7960
  }
7930
7961
  const executionGates = /* @__PURE__ */ new Map();
@@ -8036,8 +8067,8 @@ var JobRunner = class {
8036
8067
  const remaining = /* @__PURE__ */ new Map();
8037
8068
  for (const slot of state.missing) remaining.set(slot.provider, (remaining.get(slot.provider) ?? 0) + 1);
8038
8069
  const providers = {};
8039
- for (const [provider, count5] of remaining) {
8040
- providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `Expected measurements not run yet: ${count5}.` };
8070
+ for (const [provider, count6] of remaining) {
8071
+ providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `Expected measurements not run yet: ${count6}.` };
8041
8072
  }
8042
8073
  txDb.update(runs).set({ error: serializeRunError({ providers }) }).where(and3(eq4(runs.id, input.runId), eq4(runs.status, "partial"))).run();
8043
8074
  }
@@ -8294,7 +8325,7 @@ var JobRunner = class {
8294
8325
  log2.error("run.answer-fields-rescore-failed", { runId: recording.runId, projectId, error: describeError(err) });
8295
8326
  }
8296
8327
  }
8297
- incrementUsage(scope, metric, count5) {
8328
+ incrementUsage(scope, metric, count6) {
8298
8329
  const now = (/* @__PURE__ */ new Date()).toISOString();
8299
8330
  const period = now.slice(0, 10);
8300
8331
  this.db.insert(usageCounters).values({
@@ -8302,11 +8333,11 @@ var JobRunner = class {
8302
8333
  scope,
8303
8334
  period,
8304
8335
  metric,
8305
- count: count5,
8336
+ count: count6,
8306
8337
  updatedAt: now
8307
8338
  }).onConflictDoUpdate({
8308
8339
  target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
8309
- set: { count: sql2`${usageCounters.count} + ${count5}`, updatedAt: now }
8340
+ set: { count: sql2`${usageCounters.count} + ${count6}`, updatedAt: now }
8310
8341
  }).run();
8311
8342
  }
8312
8343
  flushProviderUsage(providerDispatchCounts, providerReservations) {
@@ -10595,20 +10626,6 @@ function bingRunFailure(err) {
10595
10626
  return runFailure(err, err instanceof BingApiError && err.isThrottle ? OutcomeReasonCodes.RATE_LIMITED : void 0);
10596
10627
  }
10597
10628
  var BING_INSPECT_SPACING_MS = 2e3;
10598
- function parseBingDate(value) {
10599
- if (!value) return null;
10600
- const match = /\/Date\((-?\d+)(?:[-+]\d+)?\)\//.exec(value);
10601
- if (!match) return null;
10602
- const ms = parseInt(match[1], 10);
10603
- if (ms <= 0) return null;
10604
- return new Date(ms).toISOString();
10605
- }
10606
- function isBlockingIssueType(issueType) {
10607
- if (!issueType) return true;
10608
- const trimmed = issueType.trim();
10609
- if (!trimmed) return true;
10610
- return trimmed.split(/\s+/).some((flag) => !/^(?:None|Seo(?:Issues|Concerns))$/i.test(flag));
10611
- }
10612
10629
  async function executeBingInspectSitemap(db, runId, projectId, opts) {
10613
10630
  const reportOutcome = startRunOutcome(db, runId, FeatureNames.bing, "sync");
10614
10631
  let abortCause;
@@ -10653,7 +10670,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
10653
10670
  try {
10654
10671
  const issues = await getCrawlIssues(conn.apiKey, conn.siteUrl);
10655
10672
  for (const issue of issues) {
10656
- if (issue.Url && isBlockingIssueType(issue.IssueType ?? null)) {
10673
+ if (issue.Url && isBlockingCrawlIssue(issue.IssueType ?? null)) {
10657
10674
  blockedUrls.add(issue.Url);
10658
10675
  }
10659
10676
  }
@@ -10846,10 +10863,427 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
10846
10863
  }
10847
10864
  }
10848
10865
 
10849
- // src/coverage-refresh.ts
10866
+ // src/bing-sync.ts
10850
10867
  import crypto15 from "crypto";
10851
- import { and as and9, desc as desc6, eq as eq13, inArray as inArray6 } from "drizzle-orm";
10852
- var log10 = createLogger("CoverageRefresh");
10868
+ import { and as and9, eq as eq13, notInArray as notInArray2, sql as sql5 } from "drizzle-orm";
10869
+ var log10 = createLogger("BingSync");
10870
+ var BING_SYNC_SPACING_MS = 2e3;
10871
+ var BING_KEYWORD_DEMAND_REFRESH_DAYS = 7;
10872
+ var BING_KEYWORD_DEMAND_MAX_PER_SYNC = 30;
10873
+ var BING_SITEMAP_INDEX_MAX_PER_SYNC = 20;
10874
+ var BING_NOT_AUTHORIZED = 14;
10875
+ var BingSyncThrottled = class extends Error {
10876
+ constructor(cause) {
10877
+ super(describeError(cause), { cause });
10878
+ this.name = "BingSyncThrottled";
10879
+ }
10880
+ };
10881
+ function isThrottle(err) {
10882
+ return err instanceof BingApiError && (err.isThrottle || err.status === 429);
10883
+ }
10884
+ function count3(value) {
10885
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.round(value) : 0;
10886
+ }
10887
+ var BLOCKED_ENTITY_TYPES = ["page", "directory"];
10888
+ var BLOCKED_REQUEST_TYPES = ["cache-only", "full-removal"];
10889
+ var COUNTRY_REGION_TYPES = ["page", "directory", "domain", "subdomain"];
10890
+ var SITE_ROLES = ["administrator", "read-only", "read-write"];
10891
+ function enumWord(words, value) {
10892
+ return typeof value === "number" && Number.isInteger(value) ? words[value] ?? null : null;
10893
+ }
10894
+ function convertDates(row) {
10895
+ const out = {};
10896
+ for (const [key, value] of Object.entries(row)) {
10897
+ if (key === "__type") continue;
10898
+ out[key] = typeof value === "string" && value.startsWith("/Date(") ? parseBingDate(value) : value;
10899
+ }
10900
+ return out;
10901
+ }
10902
+ function mergeWeekly(rows) {
10903
+ const merged = /* @__PURE__ */ new Map();
10904
+ for (const row of rows) {
10905
+ const date = bingDateToDay(row.Date);
10906
+ const key = typeof row.Query === "string" ? row.Query.trim() : "";
10907
+ if (!date || !key) continue;
10908
+ const id = `${date}\0${key}`;
10909
+ const entry = merged.get(id) ?? { date, key, clicks: 0, impressions: 0, clickPosWeight: 0, clickPosSum: 0, imprPosWeight: 0, imprPosSum: 0 };
10910
+ const clicks = count3(row.Clicks);
10911
+ const impressions = count3(row.Impressions);
10912
+ entry.clicks += clicks;
10913
+ entry.impressions += impressions;
10914
+ const clickPos = bingPositionOrNull(row.AvgClickPosition);
10915
+ if (clickPos !== null) {
10916
+ const weight = Math.max(clicks, 1);
10917
+ entry.clickPosSum += clickPos * weight;
10918
+ entry.clickPosWeight += weight;
10919
+ }
10920
+ const imprPos = bingPositionOrNull(row.AvgImpressionPosition);
10921
+ if (imprPos !== null) {
10922
+ const weight = Math.max(impressions, 1);
10923
+ entry.imprPosSum += imprPos * weight;
10924
+ entry.imprPosWeight += weight;
10925
+ }
10926
+ merged.set(id, entry);
10927
+ }
10928
+ return [...merged.values()].map((entry) => ({
10929
+ date: entry.date,
10930
+ key: entry.key,
10931
+ clicks: entry.clicks,
10932
+ impressions: entry.impressions,
10933
+ avgClickPosition: entry.clickPosWeight > 0 ? entry.clickPosSum / entry.clickPosWeight : null,
10934
+ avgImpressionPosition: entry.imprPosWeight > 0 ? entry.imprPosSum / entry.imprPosWeight : null
10935
+ }));
10936
+ }
10937
+ async function executeBingSync(db, runId, projectId, opts) {
10938
+ const reportOutcome = startRunOutcome(db, runId, FeatureNames.bing, "sync");
10939
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
10940
+ const sleep3 = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
10941
+ db.update(runs).set({ status: RunStatuses.running, startedAt: now().toISOString() }).where(eq13(runs.id, runId)).run();
10942
+ try {
10943
+ const project = db.select().from(projects).where(eq13(projects.id, projectId)).get();
10944
+ if (!project) {
10945
+ throw withOutcomeReason(new Error(`Project not found: ${projectId}`), OutcomeReasonCodes.NOT_FOUND);
10946
+ }
10947
+ const conn = opts.config.bing?.connections?.find((c) => c.domain === project.canonicalDomain);
10948
+ if (!conn) {
10949
+ throw withOutcomeReason(
10950
+ new Error('No Bing connection found for this project. Run "canonry bing connect <project>" first.'),
10951
+ OutcomeReasonCodes.NOT_CONNECTED
10952
+ );
10953
+ }
10954
+ if (!conn.siteUrl) {
10955
+ throw withOutcomeReason(
10956
+ new Error('No Bing site configured. Run "canonry bing set-site <project> <url>" first.'),
10957
+ OutcomeReasonCodes.PROPERTY_NOT_FOUND
10958
+ );
10959
+ }
10960
+ const apiKey = conn.apiKey;
10961
+ const siteUrl = conn.siteUrl;
10962
+ const gate = sharedRateGate(credentialGateKey("bing", apiKey));
10963
+ let throttled = false;
10964
+ async function call(read) {
10965
+ if (throttled) throw new BingSyncThrottled(new Error("Bing throttled this account earlier in the run"));
10966
+ await gate.take(BING_SYNC_SPACING_MS, sleep3);
10967
+ try {
10968
+ return await read();
10969
+ } catch (err) {
10970
+ if (isThrottle(err)) {
10971
+ throttled = true;
10972
+ throw new BingSyncThrottled(err);
10973
+ }
10974
+ throw err;
10975
+ }
10976
+ }
10977
+ const results = [];
10978
+ async function section(name, work) {
10979
+ try {
10980
+ const rows2 = await work();
10981
+ results.push({ section: name, ok: true, rows: rows2 });
10982
+ log10.info("section.completed", { runId, projectId, section: name, rows: rows2 });
10983
+ } catch (err) {
10984
+ const error2 = describeError(err);
10985
+ results.push({ section: name, ok: false, rows: 0, error: error2 });
10986
+ log10.warn("section.failed", { runId, projectId, section: name, error: error2, throttled: err instanceof BingSyncThrottled });
10987
+ }
10988
+ }
10989
+ await section("traffic", async () => {
10990
+ const rows2 = await call(() => getRankAndTrafficStats(apiKey, siteUrl));
10991
+ const syncedAt = now().toISOString();
10992
+ let written = 0;
10993
+ db.transaction((tx) => {
10994
+ for (const row of rows2) {
10995
+ const date = bingDateToDay(row.Date);
10996
+ if (!date) continue;
10997
+ const values = { clicks: count3(row.Clicks), impressions: count3(row.Impressions), syncRunId: runId, syncedAt };
10998
+ tx.insert(bingTrafficDaily).values({ id: crypto15.randomUUID(), projectId, date, ...values }).onConflictDoUpdate({ target: [bingTrafficDaily.projectId, bingTrafficDaily.date], set: values }).run();
10999
+ written += 1;
11000
+ }
11001
+ });
11002
+ return written;
11003
+ });
11004
+ await section("queries", async () => {
11005
+ const merged = mergeWeekly(await call(() => getQueryStats(apiKey, siteUrl)));
11006
+ const syncedAt = now().toISOString();
11007
+ db.transaction((tx) => {
11008
+ for (const row of merged) {
11009
+ const values = { clicks: row.clicks, impressions: row.impressions, avgClickPosition: row.avgClickPosition, avgImpressionPosition: row.avgImpressionPosition, syncRunId: runId, syncedAt };
11010
+ tx.insert(bingQueryStats).values({ id: crypto15.randomUUID(), projectId, date: row.date, query: row.key, ...values }).onConflictDoUpdate({ target: [bingQueryStats.projectId, bingQueryStats.date, bingQueryStats.query], set: values }).run();
11011
+ }
11012
+ });
11013
+ return merged.length;
11014
+ });
11015
+ await section("pages", async () => {
11016
+ const merged = mergeWeekly(await call(() => getPageStats(apiKey, siteUrl)));
11017
+ const syncedAt = now().toISOString();
11018
+ db.transaction((tx) => {
11019
+ for (const row of merged) {
11020
+ const values = { clicks: row.clicks, impressions: row.impressions, avgClickPosition: row.avgClickPosition, avgImpressionPosition: row.avgImpressionPosition, syncRunId: runId, syncedAt };
11021
+ tx.insert(bingPageStats).values({ id: crypto15.randomUUID(), projectId, date: row.date, page: row.key, ...values }).onConflictDoUpdate({ target: [bingPageStats.projectId, bingPageStats.date, bingPageStats.page], set: values }).run();
11022
+ }
11023
+ });
11024
+ return merged.length;
11025
+ });
11026
+ await section("crawl-stats", async () => {
11027
+ const rows2 = await call(() => getCrawlStats(apiKey, siteUrl));
11028
+ const syncedAt = now().toISOString();
11029
+ let written = 0;
11030
+ db.transaction((tx) => {
11031
+ for (const row of rows2) {
11032
+ const date = bingDateToDay(row.Date);
11033
+ if (!date) continue;
11034
+ const values = {
11035
+ crawledPages: count3(row.CrawledPages),
11036
+ inIndex: count3(row.InIndex),
11037
+ crawlErrors: count3(row.CrawlErrors),
11038
+ inLinks: count3(row.InLinks),
11039
+ code2xx: count3(row.Code2xx),
11040
+ code301: count3(row.Code301),
11041
+ code302: count3(row.Code302),
11042
+ code4xx: count3(row.Code4xx),
11043
+ code5xx: count3(row.Code5xx),
11044
+ allOtherCodes: count3(row.AllOtherCodes),
11045
+ blockedByRobotsTxt: count3(row.BlockedByRobotsTxt),
11046
+ connectionTimeout: count3(row.ConnectionTimeout),
11047
+ containsMalware: count3(row.ContainsMalware),
11048
+ dnsFailures: count3(row.DnsFailures),
11049
+ syncRunId: runId,
11050
+ syncedAt
11051
+ };
11052
+ tx.insert(bingCrawlStats).values({ id: crypto15.randomUUID(), projectId, date, ...values }).onConflictDoUpdate({ target: [bingCrawlStats.projectId, bingCrawlStats.date], set: values }).run();
11053
+ written += 1;
11054
+ }
11055
+ });
11056
+ return written;
11057
+ });
11058
+ await section("sitemaps", async () => {
11059
+ const top = await call(() => getFeeds(apiKey, siteUrl));
11060
+ const collected = /* @__PURE__ */ new Map();
11061
+ for (const feed of top) {
11062
+ if (feed.Url) collected.set(feed.Url, { feed, parentUrl: null });
11063
+ }
11064
+ const indexes = top.filter((feed) => /index/i.test(feed.Type ?? "")).slice(0, BING_SITEMAP_INDEX_MAX_PER_SYNC);
11065
+ for (const index of indexes) {
11066
+ const details = await call(() => getFeedDetails(apiKey, siteUrl, index.Url));
11067
+ for (const child of details) {
11068
+ if (!child.Url || child.Url === index.Url || collected.has(child.Url)) continue;
11069
+ collected.set(child.Url, { feed: child, parentUrl: index.Url });
11070
+ }
11071
+ }
11072
+ const syncedAt = now().toISOString();
11073
+ const urls = [...collected.keys()];
11074
+ db.transaction((tx) => {
11075
+ for (const { feed, parentUrl } of collected.values()) {
11076
+ const values = {
11077
+ parentUrl,
11078
+ type: feed.Type ?? "Unknown",
11079
+ status: feed.Status ?? "Unknown",
11080
+ urlCount: count3(feed.UrlCount),
11081
+ fileSize: count3(feed.FileSize),
11082
+ compressed: feed.Compressed === true,
11083
+ submittedAt: parseBingDate(feed.Submitted ?? null),
11084
+ lastCrawledAt: parseBingDate(feed.LastCrawled ?? null),
11085
+ syncRunId: runId,
11086
+ syncedAt
11087
+ };
11088
+ tx.insert(bingSitemaps).values({ id: crypto15.randomUUID(), projectId, url: feed.Url, ...values }).onConflictDoUpdate({ target: [bingSitemaps.projectId, bingSitemaps.url], set: values }).run();
11089
+ }
11090
+ tx.delete(bingSitemaps).where(urls.length > 0 ? and9(eq13(bingSitemaps.projectId, projectId), notInArray2(bingSitemaps.url, urls)) : eq13(bingSitemaps.projectId, projectId)).run();
11091
+ });
11092
+ return collected.size;
11093
+ });
11094
+ await section("site", async () => {
11095
+ const reads = [
11096
+ {
11097
+ kind: BingSiteSnapshotKinds["url-submission-quota"],
11098
+ read: async () => {
11099
+ const quota = await getUrlSubmissionQuota(apiKey, siteUrl);
11100
+ return { dailyQuota: count3(quota?.DailyQuota), monthlyQuota: count3(quota?.MonthlyQuota) };
11101
+ }
11102
+ },
11103
+ {
11104
+ kind: BingSiteSnapshotKinds["content-submission-quota"],
11105
+ read: async () => {
11106
+ const quota = await getContentSubmissionQuota(apiKey, siteUrl);
11107
+ return { dailyQuota: count3(quota?.DailyQuota), monthlyQuota: count3(quota?.MonthlyQuota) };
11108
+ }
11109
+ },
11110
+ {
11111
+ kind: BingSiteSnapshotKinds["crawl-settings"],
11112
+ read: async () => {
11113
+ const settings = await getCrawlSettings(apiKey, siteUrl);
11114
+ return {
11115
+ crawlBoostAvailable: settings?.CrawlBoostAvailable === true,
11116
+ crawlBoostEnabled: settings?.CrawlBoostEnabled === true,
11117
+ crawlRate: Array.isArray(settings?.CrawlRate) ? settings.CrawlRate.filter((v) => typeof v === "number") : []
11118
+ };
11119
+ }
11120
+ },
11121
+ {
11122
+ kind: BingSiteSnapshotKinds["crawl-issues"],
11123
+ read: async () => (await getCrawlIssues(apiKey, siteUrl)).map((issue) => ({
11124
+ url: issue.Url,
11125
+ httpCode: typeof issue.HttpCode === "number" ? issue.HttpCode : null,
11126
+ inLinks: typeof issue.InLinks === "number" ? issue.InLinks : null,
11127
+ issues: issue.Issues ?? issue.IssueType ?? null
11128
+ }))
11129
+ },
11130
+ {
11131
+ kind: BingSiteSnapshotKinds["blocked-urls"],
11132
+ read: async () => (await getBlockedUrls(apiKey, siteUrl)).map((row) => ({
11133
+ url: row.Url,
11134
+ date: parseBingDate(row.Date ?? null),
11135
+ daysToExpire: typeof row.DaysToExpire === "number" ? row.DaysToExpire : null,
11136
+ entityType: enumWord(BLOCKED_ENTITY_TYPES, row.EntityType),
11137
+ requestType: enumWord(BLOCKED_REQUEST_TYPES, row.RequestType)
11138
+ }))
11139
+ },
11140
+ {
11141
+ kind: BingSiteSnapshotKinds["page-preview-blocks"],
11142
+ read: async () => (await getActivePagePreviewBlocks(apiKey, siteUrl)).map(convertDates)
11143
+ },
11144
+ {
11145
+ kind: BingSiteSnapshotKinds["deep-link-blocks"],
11146
+ read: async () => (await getDeepLinkBlocks(apiKey, siteUrl)).map(convertDates)
11147
+ },
11148
+ {
11149
+ kind: BingSiteSnapshotKinds["query-parameters"],
11150
+ read: async () => (await getQueryParameters(apiKey, siteUrl)).map((row) => ({
11151
+ parameter: row.Parameter,
11152
+ isEnabled: typeof row.IsEnabled === "boolean" ? row.IsEnabled : null,
11153
+ date: parseBingDate(row.Date ?? null),
11154
+ source: row.Source ?? null
11155
+ }))
11156
+ },
11157
+ {
11158
+ kind: BingSiteSnapshotKinds["country-region-settings"],
11159
+ read: async () => (await getCountryRegionSettings(apiKey, siteUrl)).map((row) => ({
11160
+ url: row.Url,
11161
+ countryCode: row.TwoLetterIsoCountryCode,
11162
+ type: enumWord(COUNTRY_REGION_TYPES, row.Type),
11163
+ date: parseBingDate(row.Date ?? null)
11164
+ }))
11165
+ },
11166
+ {
11167
+ kind: BingSiteSnapshotKinds["fetched-urls"],
11168
+ read: async () => (await getFetchedUrls(apiKey, siteUrl)).map((row) => ({
11169
+ url: row.Url,
11170
+ date: parseBingDate(row.Date ?? null),
11171
+ expired: typeof row.Expired === "boolean" ? row.Expired : null,
11172
+ fetched: typeof row.Fetched === "boolean" ? row.Fetched : null
11173
+ }))
11174
+ },
11175
+ {
11176
+ kind: BingSiteSnapshotKinds["connected-pages"],
11177
+ read: async () => (await getConnectedPages(apiKey, siteUrl)).map(convertDates)
11178
+ },
11179
+ {
11180
+ kind: BingSiteSnapshotKinds["site-roles"],
11181
+ read: async () => (await getSiteRoles(apiKey, siteUrl)).map((row) => ({
11182
+ site: row.Site ?? null,
11183
+ email: row.Email ?? null,
11184
+ role: enumWord(SITE_ROLES, row.Role),
11185
+ date: parseBingDate(row.Date ?? null),
11186
+ expired: typeof row.Expired === "boolean" ? row.Expired : null
11187
+ }))
11188
+ }
11189
+ ];
11190
+ let failed2 = 0;
11191
+ let stored = 0;
11192
+ for (const { kind, read } of reads) {
11193
+ let status2 = "ok";
11194
+ let payload = null;
11195
+ let error2 = null;
11196
+ try {
11197
+ payload = await call(read);
11198
+ } catch (err) {
11199
+ if (err instanceof BingSyncThrottled) throw err;
11200
+ const unauthorized = err instanceof BingApiError && err.bingErrorCode === BING_NOT_AUTHORIZED;
11201
+ status2 = unauthorized ? "unauthorized" : "failed";
11202
+ error2 = unauthorized ? "This Bing key is not allowed to read this setting (owner-only)." : describeError(err);
11203
+ if (!unauthorized) failed2 += 1;
11204
+ }
11205
+ const syncedAt = now().toISOString();
11206
+ const previous = status2 === "failed" ? db.select({ payload: bingSiteSnapshots.payload }).from(bingSiteSnapshots).where(and9(eq13(bingSiteSnapshots.projectId, projectId), eq13(bingSiteSnapshots.kind, kind))).get() : void 0;
11207
+ const values = { status: status2, payload: status2 === "failed" ? previous?.payload ?? null : payload, error: error2, syncRunId: runId, syncedAt };
11208
+ db.insert(bingSiteSnapshots).values({ id: crypto15.randomUUID(), projectId, kind, ...values }).onConflictDoUpdate({ target: [bingSiteSnapshots.projectId, bingSiteSnapshots.kind], set: values }).run();
11209
+ stored += 1;
11210
+ }
11211
+ if (failed2 > 0) throw new Error(`${failed2} of ${reads.length} Bing site settings could not be read`);
11212
+ return stored;
11213
+ });
11214
+ await section("keyword-demand", async () => {
11215
+ const market = bingKeywordMarket(project.country, project.language);
11216
+ const tracked = db.select({ query: queries.query }).from(queries).where(eq13(queries.projectId, projectId)).all();
11217
+ if (tracked.length === 0) return 0;
11218
+ const cutoff = new Date(now().getTime() - BING_KEYWORD_DEMAND_REFRESH_DAYS * 864e5).toISOString();
11219
+ const lastSynced = new Map(db.select({ query: bingKeywordDemand.query, syncedAt: sql5`MAX(${bingKeywordDemand.syncedAt})` }).from(bingKeywordDemand).where(and9(
11220
+ eq13(bingKeywordDemand.projectId, projectId),
11221
+ eq13(bingKeywordDemand.country, market.country),
11222
+ eq13(bingKeywordDemand.language, market.language)
11223
+ )).groupBy(bingKeywordDemand.query).all().map((row) => [row.query, row.syncedAt]));
11224
+ const due = tracked.map((row) => ({ query: row.query, syncedAt: lastSynced.get(row.query) ?? null })).filter((row) => row.syncedAt === null || row.syncedAt < cutoff).sort((a, b) => (a.syncedAt ?? "").localeCompare(b.syncedAt ?? "")).slice(0, BING_KEYWORD_DEMAND_MAX_PER_SYNC);
11225
+ let written = 0;
11226
+ let failures = 0;
11227
+ let lastError;
11228
+ for (const { query } of due) {
11229
+ let history;
11230
+ try {
11231
+ history = await call(() => getKeywordHistory(apiKey, query, market));
11232
+ } catch (err) {
11233
+ if (err instanceof BingSyncThrottled) throw err;
11234
+ failures += 1;
11235
+ lastError = err;
11236
+ continue;
11237
+ }
11238
+ const syncedAt = now().toISOString();
11239
+ db.transaction((tx) => {
11240
+ for (const week of history) {
11241
+ const date = bingDateToDay(week.Date);
11242
+ if (!date) continue;
11243
+ const values = { impressions: count3(week.Impressions), broadImpressions: count3(week.BroadImpressions), syncRunId: runId, syncedAt };
11244
+ tx.insert(bingKeywordDemand).values({ id: crypto15.randomUUID(), projectId, query, country: market.country, language: market.language, date, ...values }).onConflictDoUpdate({
11245
+ target: [bingKeywordDemand.projectId, bingKeywordDemand.query, bingKeywordDemand.country, bingKeywordDemand.language, bingKeywordDemand.date],
11246
+ set: values
11247
+ }).run();
11248
+ written += 1;
11249
+ }
11250
+ if (history.length === 0) {
11251
+ const date = now().toISOString().slice(0, 10);
11252
+ tx.insert(bingKeywordDemand).values({ id: crypto15.randomUUID(), projectId, query, country: market.country, language: market.language, date, impressions: 0, broadImpressions: 0, syncRunId: runId, syncedAt }).onConflictDoNothing().run();
11253
+ }
11254
+ });
11255
+ }
11256
+ if (failures > 0) throw new Error(`${failures} of ${due.length} keyword demand reads failed; last: ${describeError(lastError)}`);
11257
+ return written;
11258
+ });
11259
+ const failed = results.filter((r) => !r.ok);
11260
+ const status = failed.length === 0 ? RunStatuses.completed : failed.length === results.length ? RunStatuses.failed : RunStatuses.partial;
11261
+ const error = failed.length === 0 ? null : `${throttled ? "Bing throttled this account; stopped early. " : ""}${failed.map((r) => `${r.section}: ${r.error}`).join("; ")}`.slice(0, 2e3);
11262
+ db.update(runs).set({ status, error, finishedAt: now().toISOString() }).where(eq13(runs.id, runId)).run();
11263
+ const rows = results.reduce((sum, r) => sum + r.rows, 0);
11264
+ log10.info("sync.completed", { runId, projectId, status, rows, failedSections: failed.map((r) => r.section) });
11265
+ if (status === RunStatuses.completed) {
11266
+ reportOutcome({ status: OutcomeStatuses.succeeded, counts: { rows, items: results.length } });
11267
+ } else {
11268
+ reportOutcome({
11269
+ status: status === RunStatuses.partial ? OutcomeStatuses.partial : OutcomeStatuses.failed,
11270
+ ...runFailure(new Error(error ?? "Bing sync failed"), throttled ? OutcomeReasonCodes.RATE_LIMITED : void 0),
11271
+ counts: { rows, items: results.length, failures: failed.length }
11272
+ });
11273
+ }
11274
+ } catch (err) {
11275
+ const message = describeError(err);
11276
+ db.update(runs).set({ status: RunStatuses.failed, error: message, finishedAt: now().toISOString() }).where(eq13(runs.id, runId)).run();
11277
+ log10.error("sync.failed", { runId, projectId, error: message });
11278
+ reportOutcome({ status: OutcomeStatuses.failed, ...runFailure(err) });
11279
+ throw err;
11280
+ }
11281
+ }
11282
+
11283
+ // src/coverage-refresh.ts
11284
+ import crypto16 from "crypto";
11285
+ import { and as and10, desc as desc6, eq as eq14, inArray as inArray6 } from "drizzle-orm";
11286
+ var log11 = createLogger("CoverageRefresh");
10853
11287
  var COVERAGE_REFRESH_MIN_INTERVAL_MS = 60 * 60 * 1e3;
10854
11288
  var COVERAGE_REFRESH_MANUAL_MIN_INTERVAL_MS = 2 * 60 * 1e3;
10855
11289
  var ACTIVE_OR_DONE_STATUSES = [
@@ -10860,44 +11294,44 @@ var ACTIVE_OR_DONE_STATUSES = [
10860
11294
  ];
10861
11295
  var IN_FLIGHT_STATUSES = [RunStatuses.queued, RunStatuses.running];
10862
11296
  function runWasUserInitiated(db, runId) {
10863
- const row = db.select({ trigger: runs.trigger }).from(runs).where(eq13(runs.id, runId)).get();
11297
+ const row = db.select({ trigger: runs.trigger }).from(runs).where(eq14(runs.id, runId)).get();
10864
11298
  return row?.trigger === RunTriggers.manual;
10865
11299
  }
10866
11300
  var defaultDeps = { executeInspectSitemap };
10867
11301
  async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps, nowMs = Date.now(), opts = {}) {
10868
- const project = db.select({ canonicalDomain: projects.canonicalDomain }).from(projects).where(eq13(projects.id, projectId)).get();
11302
+ const project = db.select({ canonicalDomain: projects.canonicalDomain }).from(projects).where(eq14(projects.id, projectId)).get();
10869
11303
  if (!project) return null;
10870
11304
  const { clientId, clientSecret } = getGoogleAuthConfig(config);
10871
11305
  if (!clientId || !clientSecret) return null;
10872
11306
  const conn = getGoogleConnection(config, project.canonicalDomain, "gsc");
10873
11307
  if (!conn?.refreshToken || !conn.propertyId) return null;
10874
11308
  const inFlight = db.select({ createdAt: runs.createdAt }).from(runs).where(
10875
- and9(
10876
- eq13(runs.projectId, projectId),
10877
- eq13(runs.kind, RunKinds["inspect-sitemap"]),
11309
+ and10(
11310
+ eq14(runs.projectId, projectId),
11311
+ eq14(runs.kind, RunKinds["inspect-sitemap"]),
10878
11312
  inArray6(runs.status, IN_FLIGHT_STATUSES)
10879
11313
  )
10880
11314
  ).orderBy(desc6(runs.createdAt)).limit(1).get();
10881
11315
  if (inFlight) {
10882
- log10.info("skip.in-flight", { projectId });
11316
+ log11.info("skip.in-flight", { projectId });
10883
11317
  return null;
10884
11318
  }
10885
11319
  const windowMs = opts.userInitiated ? COVERAGE_REFRESH_MANUAL_MIN_INTERVAL_MS : COVERAGE_REFRESH_MIN_INTERVAL_MS;
10886
11320
  const recent = db.select({ createdAt: runs.createdAt }).from(runs).where(
10887
- and9(
10888
- eq13(runs.projectId, projectId),
10889
- eq13(runs.kind, RunKinds["inspect-sitemap"]),
11321
+ and10(
11322
+ eq14(runs.projectId, projectId),
11323
+ eq14(runs.kind, RunKinds["inspect-sitemap"]),
10890
11324
  inArray6(runs.status, ACTIVE_OR_DONE_STATUSES)
10891
11325
  )
10892
11326
  ).orderBy(desc6(runs.createdAt)).limit(1).get();
10893
11327
  if (recent) {
10894
11328
  const ageMs = nowMs - Date.parse(recent.createdAt);
10895
11329
  if (Number.isFinite(ageMs) && ageMs < windowMs) {
10896
- log10.info("skip.recent", { projectId, ageMs, windowMs, userInitiated: opts.userInitiated === true });
11330
+ log11.info("skip.recent", { projectId, ageMs, windowMs, userInitiated: opts.userInitiated === true });
10897
11331
  return null;
10898
11332
  }
10899
11333
  }
10900
- const runId = crypto15.randomUUID();
11334
+ const runId = crypto16.randomUUID();
10901
11335
  db.insert(runs).values({
10902
11336
  id: runId,
10903
11337
  projectId,
@@ -10906,11 +11340,11 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
10906
11340
  trigger: RunTriggers.scheduled,
10907
11341
  createdAt: new Date(nowMs).toISOString()
10908
11342
  }).run();
10909
- log10.info("refresh.start", { projectId, runId });
11343
+ log11.info("refresh.start", { projectId, runId });
10910
11344
  try {
10911
11345
  await deps.executeInspectSitemap(db, runId, projectId, { config });
10912
11346
  } catch (err) {
10913
- log10.error("refresh.failed", {
11347
+ log11.error("refresh.failed", {
10914
11348
  projectId,
10915
11349
  runId,
10916
11350
  error: describeError(err)
@@ -10920,10 +11354,10 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
10920
11354
  }
10921
11355
 
10922
11356
  // src/commoncrawl-sync.ts
10923
- import crypto16 from "crypto";
11357
+ import crypto17 from "crypto";
10924
11358
  import path4 from "path";
10925
- import { and as and10, eq as eq14, sql as sql5 } from "drizzle-orm";
10926
- var log11 = createLogger("CommonCrawlSync");
11359
+ import { and as and11, eq as eq15, sql as sql6 } from "drizzle-orm";
11360
+ var log12 = createLogger("CommonCrawlSync");
10927
11361
  var INSERT_CHUNK_SIZE = 1e4;
10928
11362
  function defaultDeps2() {
10929
11363
  return {
@@ -10957,7 +11391,7 @@ async function executeReleaseSync(db, syncId, opts) {
10957
11391
  phaseDetail: "downloading vertices + edges",
10958
11392
  updatedAt: downloadStartedAt,
10959
11393
  error: null
10960
- }).where(eq14(ccReleaseSyncs.id, syncId)).run();
11394
+ }).where(eq15(ccReleaseSyncs.id, syncId)).run();
10961
11395
  const paths = ccReleasePaths(release);
10962
11396
  const releaseCacheDir = path4.join(deps.cacheDir, release);
10963
11397
  const vertexPath = path4.join(releaseCacheDir, paths.vertexFilename);
@@ -10980,7 +11414,7 @@ async function executeReleaseSync(db, syncId, opts) {
10980
11414
  vertexSha256: vertex.sha256,
10981
11415
  edgesSha256: edges.sha256,
10982
11416
  updatedAt: downloadFinishedAt
10983
- }).where(eq14(ccReleaseSyncs.id, syncId)).run();
11417
+ }).where(eq15(ccReleaseSyncs.id, syncId)).run();
10984
11418
  const allProjects = db.select().from(projects).all();
10985
11419
  const targets = Array.from(new Set(allProjects.map((p) => p.canonicalDomain)));
10986
11420
  let rows = [];
@@ -10996,15 +11430,15 @@ async function executeReleaseSync(db, syncId, opts) {
10996
11430
  }
10997
11431
  const queriedAt = deps.now().toISOString();
10998
11432
  db.transaction((tx) => {
10999
- tx.delete(backlinkDomains).where(eq14(backlinkDomains.releaseSyncId, syncId)).run();
11000
- tx.delete(backlinkSummaries).where(eq14(backlinkSummaries.releaseSyncId, syncId)).run();
11433
+ tx.delete(backlinkDomains).where(eq15(backlinkDomains.releaseSyncId, syncId)).run();
11434
+ tx.delete(backlinkSummaries).where(eq15(backlinkSummaries.releaseSyncId, syncId)).run();
11001
11435
  const expanded = [];
11002
11436
  for (const r of rows) {
11003
11437
  const projectIds = projectsByDomain.get(r.targetDomain);
11004
11438
  if (!projectIds) continue;
11005
11439
  for (const projectId of projectIds) {
11006
11440
  expanded.push({
11007
- id: crypto16.randomUUID(),
11441
+ id: crypto17.randomUUID(),
11008
11442
  projectId,
11009
11443
  releaseSyncId: syncId,
11010
11444
  release,
@@ -11024,7 +11458,7 @@ async function executeReleaseSync(db, syncId, opts) {
11024
11458
  const projectRows = rowsByProject.get(p.id) ?? [];
11025
11459
  const summary = computeBacklinkSummaryMetrics(projectRows);
11026
11460
  tx.insert(backlinkSummaries).values({
11027
- id: crypto16.randomUUID(),
11461
+ id: crypto17.randomUUID(),
11028
11462
  projectId: p.id,
11029
11463
  releaseSyncId: syncId,
11030
11464
  source: BacklinkSources.commoncrawl,
@@ -11057,8 +11491,8 @@ async function executeReleaseSync(db, syncId, opts) {
11057
11491
  domainsDiscovered: rows.length,
11058
11492
  updatedAt: finishedAt,
11059
11493
  error: null
11060
- }).where(eq14(ccReleaseSyncs.id, syncId)).run();
11061
- log11.info("sync.completed", {
11494
+ }).where(eq15(ccReleaseSyncs.id, syncId)).run();
11495
+ log12.info("sync.completed", {
11062
11496
  syncId,
11063
11497
  release,
11064
11498
  projectsProcessed: allProjects.length,
@@ -11076,7 +11510,7 @@ async function executeReleaseSync(db, syncId, opts) {
11076
11510
  try {
11077
11511
  deps.enqueueAutoExtract({ projectId: p.id, release });
11078
11512
  } catch (err) {
11079
- log11.error("auto-extract.enqueue-failed", {
11513
+ log12.error("auto-extract.enqueue-failed", {
11080
11514
  syncId,
11081
11515
  release,
11082
11516
  projectId: p.id,
@@ -11093,8 +11527,8 @@ async function executeReleaseSync(db, syncId, opts) {
11093
11527
  error: errorMsg,
11094
11528
  phaseDetail: null,
11095
11529
  updatedAt: finishedAt
11096
- }).where(eq14(ccReleaseSyncs.id, syncId)).run();
11097
- log11.error("sync.failed", { syncId, release, error: errorMsg });
11530
+ }).where(eq15(ccReleaseSyncs.id, syncId)).run();
11531
+ log12.error("sync.failed", { syncId, release, error: errorMsg });
11098
11532
  trackFeatureCompleted({
11099
11533
  ...outcome,
11100
11534
  status: OutcomeStatuses.failed,
@@ -11126,7 +11560,7 @@ async function syncLatestReleaseOnSchedule(projectName, deps) {
11126
11560
  if (probed === null) report({ status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_FOUND });
11127
11561
  return;
11128
11562
  }
11129
- const alreadySynced = deps.db.select().from(ccReleaseSyncs).where(and10(eq14(ccReleaseSyncs.release, probed.release), eq14(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready))).limit(1).get();
11563
+ const alreadySynced = deps.db.select().from(ccReleaseSyncs).where(and11(eq15(ccReleaseSyncs.release, probed.release), eq15(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready))).limit(1).get();
11130
11564
  if (alreadySynced) {
11131
11565
  deps.log.info({ projectName, release: probed.release }, "Scheduled backlinks sync: already up to date, skipping");
11132
11566
  report({ status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NOT_DUE });
@@ -11157,10 +11591,10 @@ function groupByProject(rows, projectsByDomain) {
11157
11591
  }
11158
11592
 
11159
11593
  // src/backlink-extract.ts
11160
- import crypto17 from "crypto";
11594
+ import crypto18 from "crypto";
11161
11595
  import fs4 from "fs";
11162
- import { and as and11, desc as desc7, eq as eq15 } from "drizzle-orm";
11163
- var log12 = createLogger("BacklinkExtract");
11596
+ import { and as and12, desc as desc7, eq as eq16 } from "drizzle-orm";
11597
+ var log13 = createLogger("BacklinkExtract");
11164
11598
  function defaultDeps3() {
11165
11599
  return {
11166
11600
  queryBacklinks,
@@ -11173,14 +11607,14 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
11173
11607
  const startedAt = deps.now().toISOString();
11174
11608
  const reportOutcome = startRunOutcome(db, runId, "backlinks", "extract");
11175
11609
  let refusal;
11176
- db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq15(runs.id, runId)).run();
11610
+ db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq16(runs.id, runId)).run();
11177
11611
  try {
11178
- const project = db.select().from(projects).where(eq15(projects.id, projectId)).get();
11612
+ const project = db.select().from(projects).where(eq16(projects.id, projectId)).get();
11179
11613
  if (!project) {
11180
11614
  refusal = OutcomeReasonCodes.NOT_FOUND;
11181
11615
  throw new Error(`Project not found: ${projectId}`);
11182
11616
  }
11183
- const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq15(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq15(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc7(ccReleaseSyncs.createdAt)).limit(1).get();
11617
+ const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq16(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq16(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc7(ccReleaseSyncs.createdAt)).limit(1).get();
11184
11618
  refusal = OutcomeReasonCodes.NO_DATA;
11185
11619
  if (!sync) {
11186
11620
  throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
@@ -11210,15 +11644,15 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
11210
11644
  const targetDomain = project.canonicalDomain;
11211
11645
  db.transaction((tx) => {
11212
11646
  tx.delete(backlinkDomains).where(
11213
- and11(
11214
- eq15(backlinkDomains.projectId, projectId),
11215
- eq15(backlinkDomains.source, BacklinkSources.commoncrawl),
11216
- eq15(backlinkDomains.release, release)
11647
+ and12(
11648
+ eq16(backlinkDomains.projectId, projectId),
11649
+ eq16(backlinkDomains.source, BacklinkSources.commoncrawl),
11650
+ eq16(backlinkDomains.release, release)
11217
11651
  )
11218
11652
  ).run();
11219
11653
  if (rows.length > 0) {
11220
11654
  const values = rows.map((r) => ({
11221
- id: crypto17.randomUUID(),
11655
+ id: crypto18.randomUUID(),
11222
11656
  projectId,
11223
11657
  releaseSyncId: syncId,
11224
11658
  source: BacklinkSources.commoncrawl,
@@ -11232,7 +11666,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
11232
11666
  }
11233
11667
  const summary2 = computeSummary(rows);
11234
11668
  tx.insert(backlinkSummaries).values({
11235
- id: crypto17.randomUUID(),
11669
+ id: crypto18.randomUUID(),
11236
11670
  projectId,
11237
11671
  releaseSyncId: syncId,
11238
11672
  source: BacklinkSources.commoncrawl,
@@ -11256,8 +11690,8 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
11256
11690
  }).run();
11257
11691
  });
11258
11692
  const finishedAt = deps.now().toISOString();
11259
- db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq15(runs.id, runId)).run();
11260
- log12.info("extract.completed", { runId, projectId, release, rows: rows.length });
11693
+ db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq16(runs.id, runId)).run();
11694
+ log13.info("extract.completed", { runId, projectId, release, rows: rows.length });
11261
11695
  const summary = computeSummary(rows);
11262
11696
  reportOutcome({ status: OutcomeStatuses.succeeded, counts: { domains: summary.totalLinkingDomains, links: summary.totalHosts } });
11263
11697
  } catch (err) {
@@ -11267,8 +11701,8 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
11267
11701
  status: RunStatuses.failed,
11268
11702
  error: errorMsg,
11269
11703
  finishedAt
11270
- }).where(eq15(runs.id, runId)).run();
11271
- log12.error("extract.failed", { runId, projectId, error: errorMsg });
11704
+ }).where(eq16(runs.id, runId)).run();
11705
+ log13.error("extract.failed", { runId, projectId, error: errorMsg });
11272
11706
  reportOutcome({ status: OutcomeStatuses.failed, ...outcomeFailure(err, refusal) });
11273
11707
  throw err;
11274
11708
  }
@@ -11278,9 +11712,9 @@ function computeSummary(rows) {
11278
11712
  }
11279
11713
 
11280
11714
  // src/discovery-run.ts
11281
- import crypto18 from "crypto";
11282
- import { and as and12, eq as eq16 } from "drizzle-orm";
11283
- var log13 = createLogger("DiscoveryRun");
11715
+ import crypto19 from "crypto";
11716
+ import { and as and13, eq as eq17 } from "drizzle-orm";
11717
+ var log14 = createLogger("DiscoveryRun");
11284
11718
  var DEFAULT_SEED_COUNT = 30;
11285
11719
  var EMBED_RETRY_MAX_RETRIES = 3;
11286
11720
  var EMBED_RETRY_MAX_DELAY_MS = 1e4;
@@ -11312,7 +11746,7 @@ async function embedWithRetry(fn) {
11312
11746
  isRetryable: isRetryableHttpError,
11313
11747
  maxDelayMs: EMBED_RETRY_MAX_DELAY_MS,
11314
11748
  onRetry: ({ attempt, delayMs }) => {
11315
- log13.warn("discovery.embed.retry", { attempt, delayMs });
11749
+ log14.warn("discovery.embed.retry", { attempt, delayMs });
11316
11750
  }
11317
11751
  }
11318
11752
  );
@@ -11322,11 +11756,11 @@ async function executeDiscoveryRun(opts) {
11322
11756
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
11323
11757
  const reportOutcome = startRunOutcome(opts.db, opts.runId, "discovery", "run");
11324
11758
  let notConfigured = false;
11325
- opts.db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq16(runs.id, opts.runId)).run();
11759
+ opts.db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq17(runs.id, opts.runId)).run();
11326
11760
  try {
11327
- const projectRow = opts.db.select().from(projects).where(eq16(projects.id, opts.projectId)).get();
11761
+ const projectRow = opts.db.select().from(projects).where(eq17(projects.id, opts.projectId)).get();
11328
11762
  if (!projectRow) throw new Error(`Project ${opts.projectId} not found`);
11329
- const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq16(competitors.projectId, opts.projectId)).orderBy(competitors.domain).all().map((r) => r.domain.toLowerCase());
11763
+ const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq17(competitors.projectId, opts.projectId)).orderBy(competitors.domain).all().map((r) => r.domain.toLowerCase());
11330
11764
  const canonicalDomains = effectiveDomains({
11331
11765
  canonicalDomain: projectRow.canonicalDomain,
11332
11766
  ownedDomains: projectRow.ownedDomains
@@ -11368,8 +11802,8 @@ async function executeDiscoveryRun(opts) {
11368
11802
  seedProvider: result.seedProvider,
11369
11803
  result
11370
11804
  });
11371
- opts.db.update(runs).set({ status: RunStatuses.completed, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq16(runs.id, opts.runId)).run();
11372
- log13.info("discovery.completed", {
11805
+ opts.db.update(runs).set({ status: RunStatuses.completed, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(runs.id, opts.runId)).run();
11806
+ log14.info("discovery.completed", {
11373
11807
  runId: opts.runId,
11374
11808
  sessionId: opts.sessionId,
11375
11809
  buckets: result.buckets,
@@ -11386,13 +11820,13 @@ async function executeDiscoveryRun(opts) {
11386
11820
  });
11387
11821
  } catch (err) {
11388
11822
  const errorMsg = describeError(err);
11389
- log13.error("discovery.failed", { runId: opts.runId, sessionId: opts.sessionId, error: errorMsg });
11823
+ log14.error("discovery.failed", { runId: opts.runId, sessionId: opts.sessionId, error: errorMsg });
11390
11824
  markSessionFailed(opts.db, opts.sessionId, errorMsg);
11391
11825
  opts.db.update(runs).set({
11392
11826
  status: RunStatuses.failed,
11393
11827
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
11394
11828
  error: errorMsg
11395
- }).where(eq16(runs.id, opts.runId)).run();
11829
+ }).where(eq17(runs.id, opts.runId)).run();
11396
11830
  const failure2 = classifyProviderOutcomeError(err);
11397
11831
  reportOutcome({
11398
11832
  status: OutcomeStatuses.failed,
@@ -11661,13 +12095,13 @@ function writeDiscoveryInsight(db, input) {
11661
12095
  totalProbes
11662
12096
  });
11663
12097
  db.transaction((tx) => {
11664
- tx.update(insights).set({ dismissed: true }).where(and12(
11665
- eq16(insights.projectId, input.projectId),
11666
- eq16(insights.type, "discovery.basket-divergence"),
11667
- eq16(insights.dismissed, false)
12098
+ tx.update(insights).set({ dismissed: true }).where(and13(
12099
+ eq17(insights.projectId, input.projectId),
12100
+ eq17(insights.type, "discovery.basket-divergence"),
12101
+ eq17(insights.dismissed, false)
11668
12102
  )).run();
11669
12103
  tx.insert(insights).values({
11670
- id: crypto18.randomUUID(),
12104
+ id: crypto19.randomUUID(),
11671
12105
  projectId: input.projectId,
11672
12106
  runId: input.runId,
11673
12107
  type: "discovery.basket-divergence",
@@ -11703,8 +12137,8 @@ function buildDiscoveryInsightTitle(input) {
11703
12137
  }
11704
12138
 
11705
12139
  // src/execute-site-audit.ts
11706
- import crypto19 from "crypto";
11707
- import { and as and14, eq as eq18, ne as ne2, sql as sql6 } from "drizzle-orm";
12140
+ import crypto20 from "crypto";
12141
+ import { and as and15, eq as eq19, ne as ne2, sql as sql7 } from "drizzle-orm";
11708
12142
  import { runSiteCrawl } from "@canonry/aeo-audit";
11709
12143
 
11710
12144
  // src/site-audit-root.ts
@@ -11892,7 +12326,7 @@ async function requestPinnedSiteAuditRoot(target, options) {
11892
12326
  }
11893
12327
 
11894
12328
  // src/site-crawl-template-links.ts
11895
- import { and as and13, asc as asc3, eq as eq17, gt as gt2, inArray as inArray7 } from "drizzle-orm";
12329
+ import { and as and14, asc as asc3, eq as eq18, gt as gt2, inArray as inArray7 } from "drizzle-orm";
11896
12330
  var TEMPLATE_LINK_READ_BATCH = 2e3;
11897
12331
  var TEMPLATE_LINK_WRITE_BATCH = 500;
11898
12332
  function placementOccurrencesOf(row) {
@@ -11914,10 +12348,10 @@ function* streamAttemptEdges(db, scope) {
11914
12348
  placementNavigationOccurrences: siteCrawlEdges.placementNavigationOccurrences,
11915
12349
  placementContentOccurrences: siteCrawlEdges.placementContentOccurrences,
11916
12350
  placementUnknownOccurrences: siteCrawlEdges.placementUnknownOccurrences
11917
- }).from(siteCrawlEdges).where(and13(
11918
- eq17(siteCrawlEdges.projectId, scope.projectId),
11919
- eq17(siteCrawlEdges.runId, scope.runId),
11920
- eq17(siteCrawlEdges.attemptId, scope.attemptId),
12351
+ }).from(siteCrawlEdges).where(and14(
12352
+ eq18(siteCrawlEdges.projectId, scope.projectId),
12353
+ eq18(siteCrawlEdges.runId, scope.runId),
12354
+ eq18(siteCrawlEdges.attemptId, scope.attemptId),
11921
12355
  gt2(siteCrawlEdges.edgeKey, after)
11922
12356
  )).orderBy(asc3(siteCrawlEdges.edgeKey)).limit(TEMPLATE_LINK_READ_BATCH).all();
11923
12357
  if (rows.length === 0) return;
@@ -11942,10 +12376,10 @@ function writeGroupKey(isTemplate, templateRatio) {
11942
12376
  return `${isTemplate ? "1" : "0"}${templateRatio ?? ""}`;
11943
12377
  }
11944
12378
  function classifySiteCrawlTemplateLinks(db, scope, pagesFetched, placementRulesetVersion) {
11945
- const scopeFilter = and13(
11946
- eq17(siteCrawlEdges.projectId, scope.projectId),
11947
- eq17(siteCrawlEdges.runId, scope.runId),
11948
- eq17(siteCrawlEdges.attemptId, scope.attemptId)
12379
+ const scopeFilter = and14(
12380
+ eq18(siteCrawlEdges.projectId, scope.projectId),
12381
+ eq18(siteCrawlEdges.runId, scope.runId),
12382
+ eq18(siteCrawlEdges.attemptId, scope.attemptId)
11949
12383
  );
11950
12384
  const placementAvailable = templateLinkPlacementAvailable(placementRulesetVersion);
11951
12385
  const ubiquityAvailable = templateLinkUbiquityAvailable(pagesFetched);
@@ -11980,7 +12414,7 @@ function classifySiteCrawlTemplateLinks(db, scope, pagesFetched, placementRulese
11980
12414
  const now = (/* @__PURE__ */ new Date()).toISOString();
11981
12415
  for (const group of edgeKeysByValue.values()) {
11982
12416
  for (const chunk of chunked(group.edgeKeys)) {
11983
- db.update(siteCrawlEdges).set({ isTemplate: group.isTemplate, templateRatio: group.templateRatio, updatedAt: now }).where(and13(scopeFilter, inArray7(siteCrawlEdges.edgeKey, chunk))).run();
12417
+ db.update(siteCrawlEdges).set({ isTemplate: group.isTemplate, templateRatio: group.templateRatio, updatedAt: now }).where(and14(scopeFilter, inArray7(siteCrawlEdges.edgeKey, chunk))).run();
11984
12418
  }
11985
12419
  }
11986
12420
  return {
@@ -12016,7 +12450,7 @@ function buildSiteCrawlIssueEvidence(siteIssues, pages) {
12016
12450
  }
12017
12451
 
12018
12452
  // src/execute-site-audit.ts
12019
- var log14 = createLogger("SiteAudit");
12453
+ var log15 = createLogger("SiteAudit");
12020
12454
  var SiteAuditCancelledError = class extends Error {
12021
12455
  name = "SiteAuditCancelledError";
12022
12456
  };
@@ -12168,7 +12602,7 @@ function trackSiteAuditOutcome(db, input) {
12168
12602
  );
12169
12603
  const pagesAudited = input.crawl?.pagesAudited ?? 0;
12170
12604
  if (pagesAudited === 0 || input.trigger === RunTriggers.probe) return;
12171
- const prior = db.select({ id: siteAuditSnapshots.id }).from(siteAuditSnapshots).where(and14(eq18(siteAuditSnapshots.projectId, input.projectId), ne2(siteAuditSnapshots.runId, input.runId))).limit(1).get();
12605
+ const prior = db.select({ id: siteAuditSnapshots.id }).from(siteAuditSnapshots).where(and15(eq19(siteAuditSnapshots.projectId, input.projectId), ne2(siteAuditSnapshots.runId, input.runId))).limit(1).get();
12172
12606
  if (prior) return;
12173
12607
  trackEvent("activation.completed", {
12174
12608
  flowVersion: ONBOARDING_FLOW_VERSION,
@@ -12177,18 +12611,18 @@ function trackSiteAuditOutcome(db, input) {
12177
12611
  pagesAuditedBucket: bucketOnboardingCount(pagesAudited)
12178
12612
  });
12179
12613
  } catch (error) {
12180
- log14.warn("telemetry.failed", { runId: input.runId, projectId: input.projectId, error: describeError(error) });
12614
+ log15.warn("telemetry.failed", { runId: input.runId, projectId: input.projectId, error: describeError(error) });
12181
12615
  }
12182
12616
  }
12183
12617
  async function executeSiteAudit(db, runId, projectId, opts = {}) {
12184
12618
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
12185
- const claim = db.update(runs).set({ status: "running", startedAt }).where(and14(eq18(runs.id, runId), eq18(runs.projectId, projectId), eq18(runs.status, "queued"))).run();
12619
+ const claim = db.update(runs).set({ status: "running", startedAt }).where(and15(eq19(runs.id, runId), eq19(runs.projectId, projectId), eq19(runs.status, "queued"))).run();
12186
12620
  if (claim.changes === 0) return;
12187
12621
  const startTime = Date.now();
12188
- const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq18(runs.id, runId)).get()?.trigger ?? null;
12622
+ const trigger = db.select({ trigger: runs.trigger }).from(runs).where(eq19(runs.id, runId)).get()?.trigger ?? null;
12189
12623
  let canonicalDomain = null;
12190
- const attemptNumber = (db.select({ value: sql6`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq18(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
12191
- const attemptId = crypto19.randomUUID();
12624
+ const attemptNumber = (db.select({ value: sql7`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq19(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
12625
+ const attemptId = crypto20.randomUUID();
12192
12626
  db.insert(siteCrawlAttempts).values({
12193
12627
  id: attemptId,
12194
12628
  projectId,
@@ -12222,22 +12656,22 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12222
12656
  const nodeKey = nodeKeyByUrl.get(url);
12223
12657
  if (!nodeKey) continue;
12224
12658
  const scope = [
12225
- eq18(siteCrawlEdges.projectId, projectId),
12226
- eq18(siteCrawlEdges.runId, runId),
12227
- eq18(siteCrawlEdges.attemptId, attemptId)
12659
+ eq19(siteCrawlEdges.projectId, projectId),
12660
+ eq19(siteCrawlEdges.runId, runId),
12661
+ eq19(siteCrawlEdges.attemptId, attemptId)
12228
12662
  ];
12229
- tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(and14(
12663
+ tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(and15(
12230
12664
  ...scope,
12231
- eq18(siteCrawlEdges.sourceUrl, url)
12665
+ eq19(siteCrawlEdges.sourceUrl, url)
12232
12666
  )).run();
12233
- tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(and14(
12667
+ tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(and15(
12234
12668
  ...scope,
12235
- eq18(siteCrawlEdges.targetUrl, url)
12669
+ eq19(siteCrawlEdges.targetUrl, url)
12236
12670
  )).run();
12237
12671
  const pageScope = [
12238
- eq18(siteCrawlPages.projectId, projectId),
12239
- eq18(siteCrawlPages.runId, runId),
12240
- eq18(siteCrawlPages.attemptId, attemptId)
12672
+ eq19(siteCrawlPages.projectId, projectId),
12673
+ eq19(siteCrawlPages.runId, runId),
12674
+ eq19(siteCrawlPages.attemptId, attemptId)
12241
12675
  ];
12242
12676
  const canonicalTargets = tx.select({
12243
12677
  id: siteCrawlPages.id,
@@ -12245,13 +12679,13 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12245
12679
  fetchState: siteCrawlPages.fetchState,
12246
12680
  indexabilityState: siteCrawlPages.indexabilityState,
12247
12681
  indexabilityReasons: siteCrawlPages.indexabilityReasons
12248
- }).from(siteCrawlPages).where(and14(...pageScope, eq18(siteCrawlPages.canonicalUrl, url))).all();
12682
+ }).from(siteCrawlPages).where(and15(...pageScope, eq19(siteCrawlPages.canonicalUrl, url))).all();
12249
12683
  for (const target of canonicalTargets) {
12250
12684
  tx.update(siteCrawlPages).set({
12251
12685
  canonicalNodeKey: nodeKey,
12252
12686
  healthState: deriveSiteHealthState({ ...target, canonicalNodeKey: nodeKey }),
12253
12687
  updatedAt: now
12254
- }).where(eq18(siteCrawlPages.id, target.id)).run();
12688
+ }).where(eq19(siteCrawlPages.id, target.id)).run();
12255
12689
  }
12256
12690
  }
12257
12691
  };
@@ -12267,7 +12701,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12267
12701
  nodeKey: page.key
12268
12702
  });
12269
12703
  tx.insert(siteCrawlPages).values({
12270
- id: crypto19.randomUUID(),
12704
+ id: crypto20.randomUUID(),
12271
12705
  projectId,
12272
12706
  runId,
12273
12707
  attemptId,
@@ -12318,7 +12752,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12318
12752
  discoveryProvenance: [{ discoveredFrom: page.provenance.discoveredFrom, sitemapSources: page.provenance.sitemapSources, root: page.provenance.root }],
12319
12753
  sitemapMetadata: { sources: page.provenance.sitemapSources },
12320
12754
  fetchState: page.state,
12321
- fetchedAt: page.state === "discovered" || page.state === "robots-blocked" ? null : sql6`coalesce(${siteCrawlPages.fetchedAt}, ${now})`,
12755
+ fetchedAt: page.state === "discovered" || page.state === "robots-blocked" ? null : sql7`coalesce(${siteCrawlPages.fetchedAt}, ${now})`,
12322
12756
  httpStatus: page.statusCode,
12323
12757
  contentType: page.contentType,
12324
12758
  finalUrl: page.finalUrl,
@@ -12362,7 +12796,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12362
12796
  if (isSelfLink(edge.from, edge.to)) return;
12363
12797
  observedEdges.set(edge.key, edge);
12364
12798
  tx.insert(siteCrawlEdges).values({
12365
- id: crypto19.randomUUID(),
12799
+ id: crypto20.randomUUID(),
12366
12800
  projectId,
12367
12801
  runId,
12368
12802
  attemptId,
@@ -12410,20 +12844,20 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12410
12844
  linkScoreRaw: metrics.linkScoreRaw,
12411
12845
  linkScoreNormalized: metrics.linkScore,
12412
12846
  updatedAt: now
12413
- }).where(and14(
12414
- eq18(siteCrawlPages.projectId, projectId),
12415
- eq18(siteCrawlPages.runId, runId),
12416
- eq18(siteCrawlPages.attemptId, attemptId),
12417
- eq18(siteCrawlPages.nodeKey, key)
12847
+ }).where(and15(
12848
+ eq19(siteCrawlPages.projectId, projectId),
12849
+ eq19(siteCrawlPages.runId, runId),
12850
+ eq19(siteCrawlPages.attemptId, attemptId),
12851
+ eq19(siteCrawlPages.nodeKey, key)
12418
12852
  )).run();
12419
12853
  };
12420
12854
  const persistEvent = async (event) => {
12421
12855
  const now = (/* @__PURE__ */ new Date()).toISOString();
12422
12856
  db.transaction((tx) => {
12423
- const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(and14(
12424
- eq18(siteCrawlEventReceipts.attemptId, attemptId),
12425
- eq18(siteCrawlEventReceipts.sequence, event.sequence),
12426
- eq18(siteCrawlEventReceipts.batchId, event.batchId)
12857
+ const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(and15(
12858
+ eq19(siteCrawlEventReceipts.attemptId, attemptId),
12859
+ eq19(siteCrawlEventReceipts.sequence, event.sequence),
12860
+ eq19(siteCrawlEventReceipts.batchId, event.batchId)
12427
12861
  )).get();
12428
12862
  if (receipt) {
12429
12863
  if (receipt.checksum !== event.checksum) {
@@ -12432,7 +12866,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12432
12866
  return;
12433
12867
  }
12434
12868
  tx.insert(siteCrawlEventReceipts).values({
12435
- id: crypto19.randomUUID(),
12869
+ id: crypto20.randomUUID(),
12436
12870
  projectId,
12437
12871
  runId,
12438
12872
  attemptId,
@@ -12452,24 +12886,24 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12452
12886
  lastEventChecksum: event.checksum,
12453
12887
  pagesDiscovered: progress.pagesDiscovered,
12454
12888
  pagesFetched: progress.pagesFetched,
12455
- pagesEligible: event.type === "summary" ? observedPages.size === 0 ? 0 : [...observedPages.values()].filter(isInventoryEligible).length : sql6`${siteCrawlAttempts.pagesEligible}`,
12456
- pagesErrored: event.type === "summary" ? observedErrorCount(observedPages.values()) : sql6`${siteCrawlAttempts.pagesErrored}`,
12889
+ pagesEligible: event.type === "summary" ? observedPages.size === 0 ? 0 : [...observedPages.values()].filter(isInventoryEligible).length : sql7`${siteCrawlAttempts.pagesEligible}`,
12890
+ pagesErrored: event.type === "summary" ? observedErrorCount(observedPages.values()) : sql7`${siteCrawlAttempts.pagesErrored}`,
12457
12891
  // Only the summary knows how many URL versions the crawl left unfetched.
12458
- urlVersionsSkipped: event.type === "summary" ? event.summary.urlVersions?.skipped ?? 0 : sql6`${siteCrawlAttempts.urlVersionsSkipped}`,
12892
+ urlVersionsSkipped: event.type === "summary" ? event.summary.urlVersions?.skipped ?? 0 : sql7`${siteCrawlAttempts.urlVersionsSkipped}`,
12459
12893
  edgesDiscovered: progress.edgesObserved,
12460
12894
  updatedAt: now
12461
- }).where(eq18(siteCrawlAttempts.id, attemptId)).run();
12895
+ }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
12462
12896
  } else {
12463
12897
  tx.update(siteCrawlAttempts).set({
12464
12898
  lastEventSequence: event.sequence,
12465
12899
  lastEventChecksum: event.checksum,
12466
12900
  updatedAt: now
12467
- }).where(eq18(siteCrawlAttempts.id, attemptId)).run();
12901
+ }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
12468
12902
  }
12469
12903
  });
12470
12904
  };
12471
12905
  try {
12472
- const project = db.select().from(projects).where(eq18(projects.id, projectId)).get();
12906
+ const project = db.select().from(projects).where(eq19(projects.id, projectId)).get();
12473
12907
  if (!project) throw new Error(`Project not found: ${projectId}`);
12474
12908
  canonicalDomain = project.canonicalDomain;
12475
12909
  const homepageUrl = toHomepageUrl(project.canonicalDomain);
@@ -12481,7 +12915,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12481
12915
  });
12482
12916
  const crawlRootUrl = resolvedRoot.effectiveUrl;
12483
12917
  if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
12484
- log14.info("start", {
12918
+ log15.info("start", {
12485
12919
  runId,
12486
12920
  projectId,
12487
12921
  homepageUrl,
@@ -12515,7 +12949,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12515
12949
  crawlSummary.pagesFetched,
12516
12950
  placementRulesetVersion
12517
12951
  );
12518
- log14.info("template-links", {
12952
+ log15.info("template-links", {
12519
12953
  runId,
12520
12954
  projectId,
12521
12955
  detection: templateLinks.detection,
@@ -12547,7 +12981,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12547
12981
  try {
12548
12982
  persistSiteCrawlGraphLayout(db, { projectId, runId, attemptId }, graphLayout, finishedAt);
12549
12983
  } catch (error) {
12550
- log14.warn("graph-layout.persist-failed", {
12984
+ log15.warn("graph-layout.persist-failed", {
12551
12985
  runId,
12552
12986
  projectId,
12553
12987
  error: describeError(error)
@@ -12565,7 +12999,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12565
12999
  edges: []
12566
13000
  }, finishedAt);
12567
13001
  } catch (markerError) {
12568
- log14.warn("graph-layout.marker-persist-failed", {
13002
+ log15.warn("graph-layout.marker-persist-failed", {
12569
13003
  runId,
12570
13004
  projectId,
12571
13005
  error: describeError(markerError)
@@ -12573,11 +13007,11 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12573
13007
  }
12574
13008
  }
12575
13009
  const published = db.transaction((tx) => {
12576
- const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(and14(eq18(runs.id, runId), eq18(runs.status, "running"))).run();
13010
+ const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(and15(eq19(runs.id, runId), eq19(runs.status, "running"))).run();
12577
13011
  if (claim2.changes === 0) return false;
12578
13012
  if (aggregateScore !== null) {
12579
13013
  tx.insert(siteAuditSnapshots).values({
12580
- id: crypto19.randomUUID(),
13014
+ id: crypto20.randomUUID(),
12581
13015
  projectId,
12582
13016
  runId,
12583
13017
  sitemapUrl: opts.sitemapUrl ?? new URL("/sitemap.xml", crawlRootUrl).href,
@@ -12595,7 +13029,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12595
13029
  for (const page of observedPages.values()) {
12596
13030
  if (!isLegacyScorecardPage(page)) continue;
12597
13031
  tx.insert(siteAuditPages).values({
12598
- id: crypto19.randomUUID(),
13032
+ id: crypto20.randomUUID(),
12599
13033
  projectId,
12600
13034
  runId,
12601
13035
  url: page.finalUrl ?? page.requestedUrl,
@@ -12608,7 +13042,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12608
13042
  }
12609
13043
  }
12610
13044
  tx.insert(siteCrawlSnapshots).values({
12611
- id: crypto19.randomUUID(),
13045
+ id: crypto20.randomUUID(),
12612
13046
  projectId,
12613
13047
  runId,
12614
13048
  attemptId,
@@ -12657,7 +13091,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12657
13091
  if (opts.checkDeadLinks) {
12658
13092
  for (const finding of deadLinkFindings) {
12659
13093
  tx.insert(siteCrawlFindings).values({
12660
- id: crypto19.randomUUID(),
13094
+ id: crypto20.randomUUID(),
12661
13095
  projectId,
12662
13096
  runId,
12663
13097
  attemptId,
@@ -12683,24 +13117,24 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12683
13117
  edgesDiscovered: crawlSummary.edgesObserved,
12684
13118
  finishedAt,
12685
13119
  updatedAt: finishedAt
12686
- }).where(eq18(siteCrawlAttempts.id, attemptId)).run();
13120
+ }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
12687
13121
  return true;
12688
13122
  });
12689
13123
  if (!published) {
12690
13124
  try {
12691
13125
  deleteSiteCrawlGraphLayout(db, { projectId, runId, attemptId });
12692
13126
  } catch (cleanupError) {
12693
- log14.warn("graph-layout.cleanup-failed", {
13127
+ log15.warn("graph-layout.cleanup-failed", {
12694
13128
  runId,
12695
13129
  projectId,
12696
13130
  error: describeError(cleanupError)
12697
13131
  });
12698
13132
  }
12699
- const current = db.select({ status: runs.status }).from(runs).where(eq18(runs.id, runId)).get();
13133
+ const current = db.select({ status: runs.status }).from(runs).where(eq19(runs.id, runId)).get();
12700
13134
  if (current?.status === "cancelled") throw new SiteAuditCancelledError(`Site audit cancelled before publication: ${runId}`);
12701
13135
  throw new Error(`Site audit run was no longer running before publication: ${runId}`);
12702
13136
  }
12703
- log14.info("completed", {
13137
+ log15.info("completed", {
12704
13138
  runId,
12705
13139
  projectId,
12706
13140
  status: terminalStatus,
@@ -12744,14 +13178,14 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
12744
13178
  error: message,
12745
13179
  finishedAt,
12746
13180
  updatedAt: finishedAt
12747
- }).where(eq18(siteCrawlAttempts.id, attemptId)).run();
13181
+ }).where(eq19(siteCrawlAttempts.id, attemptId)).run();
12748
13182
  tx.update(runs).set({
12749
13183
  status: cancelled ? "cancelled" : "failed",
12750
13184
  error: message,
12751
13185
  finishedAt
12752
- }).where(and14(eq18(runs.id, runId), eq18(runs.status, "running"))).run();
13186
+ }).where(and15(eq19(runs.id, runId), eq19(runs.status, "running"))).run();
12753
13187
  });
12754
- log14.error("failed", { runId, projectId, cancelled, error: message });
13188
+ log15.error("failed", { runId, projectId, cancelled, error: message });
12755
13189
  trackSiteAuditOutcome(db, {
12756
13190
  runId,
12757
13191
  projectId,
@@ -12787,7 +13221,7 @@ function createAnswerFieldRecomputeQueue(opts) {
12787
13221
 
12788
13222
  // src/competitor-auto-alias-runner.ts
12789
13223
  import { setImmediate as yieldToEventLoop3 } from "timers/promises";
12790
- var log15 = createLogger("CompetitorAutoAliases");
13224
+ var log16 = createLogger("CompetitorAutoAliases");
12791
13225
  function createCompetitorAutoAliasRunner(opts) {
12792
13226
  const reasons = /* @__PURE__ */ new Map();
12793
13227
  const queue = createProjectPassQueue(async (projectId) => {
@@ -12798,7 +13232,7 @@ function createCompetitorAutoAliasRunner(opts) {
12798
13232
  if (!result) return null;
12799
13233
  const added = result.detection.competitors.reduce((sum, competitor) => sum + competitor.added.length, 0);
12800
13234
  const removed = result.detection.competitors.reduce((sum, competitor) => sum + competitor.removed.length, 0);
12801
- log15.info("detection.completed", {
13235
+ log16.info("detection.completed", {
12802
13236
  projectId,
12803
13237
  reason,
12804
13238
  runs: result.detection.scan.runs,
@@ -12811,7 +13245,7 @@ function createCompetitorAutoAliasRunner(opts) {
12811
13245
  try {
12812
13246
  opts.onNamesChanged(projectId, result.detection.project);
12813
13247
  } catch (err) {
12814
- log15.error("names-changed.failed", { projectId, error: describeError(err) });
13248
+ log16.error("names-changed.failed", { projectId, error: describeError(err) });
12815
13249
  }
12816
13250
  }
12817
13251
  return result;
@@ -12823,14 +13257,14 @@ function createCompetitorAutoAliasRunner(opts) {
12823
13257
  return {
12824
13258
  schedule(projectId, reason) {
12825
13259
  enqueue(projectId, reason).catch((err) => {
12826
- log15.error("detection.failed", { projectId, reason, error: describeError(err) });
13260
+ log16.error("detection.failed", { projectId, reason, error: describeError(err) });
12827
13261
  });
12828
13262
  },
12829
13263
  async request(projectId, reason) {
12830
13264
  try {
12831
13265
  return await enqueue(projectId, reason);
12832
13266
  } catch (err) {
12833
- log15.error("detection.failed", { projectId, reason, error: describeError(err) });
13267
+ log16.error("detection.failed", { projectId, reason, error: describeError(err) });
12834
13268
  throw err;
12835
13269
  }
12836
13270
  },
@@ -12947,8 +13381,8 @@ function derivedRouteAdapter(adapter, route) {
12947
13381
  }
12948
13382
 
12949
13383
  // src/install-state-telemetry.ts
12950
- import { and as and15, count as count3, eq as eq19, gte as gte2, inArray as inArray8, isNotNull as isNotNull2, isNull as isNull2, ne as ne3, sql as sql7 } from "drizzle-orm";
12951
- var log16 = createLogger("InstallStateTelemetry");
13384
+ import { and as and16, count as count4, eq as eq20, gte as gte2, inArray as inArray8, isNotNull as isNotNull2, isNull as isNull2, ne as ne3, sql as sql8 } from "drizzle-orm";
13385
+ var log17 = createLogger("InstallStateTelemetry");
12952
13386
  var INSTALL_STATE_FIRST_DELAY_MS = 5 * 6e4;
12953
13387
  var INSTALL_STATE_INTERVAL_MS = 24 * 60 * 6e4;
12954
13388
  var KNOWN_PROVIDERS2 = new Set(PROVIDER_NAMES);
@@ -12970,31 +13404,31 @@ function buildInstallState(sources, now, startedAt) {
12970
13404
  const since = new Date(now - INSTALL_STATE_INTERVAL_MS).toISOString();
12971
13405
  const projectIds = db.select({ id: projects.id }).from(projects);
12972
13406
  const total = (query) => whole(query.get()?.value);
12973
- const googleByType = new Map(db.select({ type: googleConnections.connectionType, value: count3() }).from(googleConnections).groupBy(googleConnections.connectionType).all().map((row) => [row.type, whole(row.value)]));
12974
- const gaServiceAccounts = total(db.select({ value: count3() }).from(gaConnections));
12975
- const trafficByType = db.select({ type: trafficSources.sourceType, value: count3() }).from(trafficSources).where(and15(isNull2(trafficSources.archivedAt), ne3(trafficSources.status, TrafficSourceStatuses.archived))).groupBy(trafficSources.sourceType).all();
12976
- const webhookSources = db.select({ config: notifications.config }).from(notifications).where(eq19(notifications.enabled, true)).all().map((row) => row.config.source);
13407
+ const googleByType = new Map(db.select({ type: googleConnections.connectionType, value: count4() }).from(googleConnections).groupBy(googleConnections.connectionType).all().map((row) => [row.type, whole(row.value)]));
13408
+ const gaServiceAccounts = total(db.select({ value: count4() }).from(gaConnections));
13409
+ const trafficByType = db.select({ type: trafficSources.sourceType, value: count4() }).from(trafficSources).where(and16(isNull2(trafficSources.archivedAt), ne3(trafficSources.status, TrafficSourceStatuses.archived))).groupBy(trafficSources.sourceType).all();
13410
+ const webhookSources = db.select({ config: notifications.config }).from(notifications).where(eq20(notifications.enabled, true)).all().map((row) => row.config.source);
12977
13411
  const agentWebhooks = webhookSources.filter((source) => source === AGENT_WEBHOOK_SOURCE).length;
12978
13412
  const webhooks = webhookSources.length - agentWebhooks;
12979
- const bing = total(db.select({ value: count3() }).from(bingConnections));
12980
- const openAiAds = total(db.select({ value: count3() }).from(adsConnections));
12981
- const googleAdsConnected = total(db.select({ value: count3() }).from(googleAdsConnections));
12982
- const backlinkSources = total(db.select({ value: count3() }).from(
13413
+ const bing = total(db.select({ value: count4() }).from(bingConnections));
13414
+ const openAiAds = total(db.select({ value: count4() }).from(adsConnections));
13415
+ const googleAdsConnected = total(db.select({ value: count4() }).from(googleAdsConnections));
13416
+ const backlinkSources = total(db.select({ value: count4() }).from(
12983
13417
  db.selectDistinct({ projectId: backlinkSummaries.projectId, source: backlinkSummaries.source }).from(backlinkSummaries).as("backlink_sources")
12984
13418
  ));
12985
13419
  const counts = {
12986
- projects: total(db.select({ value: count3() }).from(projects)),
12987
- queries: total(db.select({ value: count3() }).from(queries)),
12988
- competitors: total(db.select({ value: count3() }).from(competitors)),
12989
- locations: total(db.select({ value: sql7`coalesce(sum(json_array_length(${projects.locations})), 0)` }).from(projects)),
12990
- schedules: total(db.select({ value: count3() }).from(schedules).where(eq19(schedules.enabled, true))),
13420
+ projects: total(db.select({ value: count4() }).from(projects)),
13421
+ queries: total(db.select({ value: count4() }).from(queries)),
13422
+ competitors: total(db.select({ value: count4() }).from(competitors)),
13423
+ locations: total(db.select({ value: sql8`coalesce(sum(json_array_length(${projects.locations})), 0)` }).from(projects)),
13424
+ schedules: total(db.select({ value: count4() }).from(schedules).where(eq20(schedules.enabled, true))),
12991
13425
  webhooks,
12992
13426
  agentWebhooks,
12993
13427
  trafficSources: trafficByType.reduce((sum, row) => sum + whole(row.value), 0),
12994
13428
  googleConnections: [...googleByType.values()].reduce((sum, value) => sum + value, 0) + gaServiceAccounts,
12995
13429
  bingConnections: bing,
12996
13430
  backlinkSources,
12997
- adsAccounts: openAiAds + total(db.select({ value: count3() }).from(googleAdsConnections).where(isNotNull2(googleAdsConnections.selectedCustomerId)))
13431
+ adsAccounts: openAiAds + total(db.select({ value: count4() }).from(googleAdsConnections).where(isNotNull2(googleAdsConnections.selectedCustomerId)))
12998
13432
  };
12999
13433
  const connected = /* @__PURE__ */ new Set();
13000
13434
  if ((googleByType.get("gsc") ?? 0) > 0) connected.add(IntegrationNames.gsc);
@@ -13002,7 +13436,7 @@ function buildInstallState(sources, now, startedAt) {
13002
13436
  if ((googleByType.get("gbp") ?? 0) > 0) connected.add(IntegrationNames.gbp);
13003
13437
  if (bing > 0) connected.add(IntegrationNames.bing);
13004
13438
  if (googleAdsConnected > 0) connected.add(IntegrationNames.google_ads);
13005
- if (total(db.select({ value: count3() }).from(gtmConnections)) > 0) connected.add(IntegrationNames.gtm);
13439
+ if (total(db.select({ value: count4() }).from(gtmConnections)) > 0) connected.add(IntegrationNames.gtm);
13006
13440
  if (openAiAds > 0) connected.add(IntegrationNames.openai_ads);
13007
13441
  for (const row of trafficByType) {
13008
13442
  const integration = TRAFFIC_INTEGRATIONS[row.type];
@@ -13020,25 +13454,25 @@ function buildInstallState(sources, now, startedAt) {
13020
13454
  if (!KNOWN_PROVIDERS2.has(connection)) return;
13021
13455
  providerCalls.set(connection, (providerCalls.get(connection) ?? 0) + calls);
13022
13456
  };
13023
- const usagePath = (field) => sql7`json_extract(${querySnapshots.usage}, ${`$.${field}`})`;
13457
+ const usagePath = (field) => sql8`json_extract(${querySnapshots.usage}, ${`$.${field}`})`;
13024
13458
  const answers = db.select({
13025
13459
  provider: querySnapshots.provider,
13026
- answers: count3(),
13027
- measured: sql7`sum(case when ${usagePath("inputTokens")} is not null then 1 else 0 end)`,
13028
- unpriced: sql7`sum(case when ${usagePath("inputTokens")} is not null and ${usagePath("estimatedCostMicros")} is null then 1 else 0 end)`,
13029
- inputTokens: sql7`coalesce(sum(${usagePath("inputTokens")}), 0)`,
13030
- outputTokens: sql7`coalesce(sum(${usagePath("outputTokens")}), 0)`,
13031
- costMicros: sql7`coalesce(sum(${usagePath("estimatedCostMicros")}), 0)`
13032
- }).from(querySnapshots).where(gte2(querySnapshots.createdAt, since)).groupBy(sql7`+${querySnapshots.provider}`).all();
13460
+ answers: count4(),
13461
+ measured: sql8`sum(case when ${usagePath("inputTokens")} is not null then 1 else 0 end)`,
13462
+ unpriced: sql8`sum(case when ${usagePath("inputTokens")} is not null and ${usagePath("estimatedCostMicros")} is null then 1 else 0 end)`,
13463
+ inputTokens: sql8`coalesce(sum(${usagePath("inputTokens")}), 0)`,
13464
+ outputTokens: sql8`coalesce(sum(${usagePath("outputTokens")}), 0)`,
13465
+ costMicros: sql8`coalesce(sum(${usagePath("estimatedCostMicros")}), 0)`
13466
+ }).from(querySnapshots).where(gte2(querySnapshots.createdAt, since)).groupBy(sql8`+${querySnapshots.provider}`).all();
13033
13467
  for (const row of answers) addCalls(row.provider, whole(row.answers));
13034
- for (const row of db.select({ provider: researchRuns.provider, answered: sql7`coalesce(sum(${researchRuns.completedQueries}), 0)` }).from(researchRuns).where(and15(inArray8(researchRuns.projectId, projectIds), gte2(researchRuns.createdAt, since))).groupBy(researchRuns.provider).all()) {
13468
+ for (const row of db.select({ provider: researchRuns.provider, answered: sql8`coalesce(sum(${researchRuns.completedQueries}), 0)` }).from(researchRuns).where(and16(inArray8(researchRuns.projectId, projectIds), gte2(researchRuns.createdAt, since))).groupBy(researchRuns.provider).all()) {
13035
13469
  addCalls(row.provider, whole(row.answered));
13036
13470
  }
13037
13471
  for (const session of db.select({
13038
13472
  seedProviders: discoverySessions.seedProviders,
13039
13473
  seedCountRaw: discoverySessions.seedCountRaw,
13040
13474
  probeCount: discoverySessions.probeCount
13041
- }).from(discoverySessions).where(and15(inArray8(discoverySessions.projectId, projectIds), gte2(discoverySessions.createdAt, since))).all()) {
13475
+ }).from(discoverySessions).where(and16(inArray8(discoverySessions.projectId, projectIds), gte2(discoverySessions.createdAt, since))).all()) {
13042
13476
  if (session.seedCountRaw !== null) {
13043
13477
  for (const provider of session.seedProviders?.length ? session.seedProviders : [ProviderNames.gemini]) addCalls(provider, 1);
13044
13478
  }
@@ -13047,15 +13481,15 @@ function buildInstallState(sources, now, startedAt) {
13047
13481
  const answered = answers.reduce((sum, row) => sum + whole(row.answers), 0);
13048
13482
  const measured = answers.reduce((sum, row) => sum + whole(row.measured), 0);
13049
13483
  const usage24h = {
13050
- sweeps: total(db.select({ value: count3() }).from(runs).where(and15(
13484
+ sweeps: total(db.select({ value: count4() }).from(runs).where(and16(
13051
13485
  inArray8(runs.projectId, projectIds),
13052
- eq19(runs.kind, RunKinds["answer-visibility"]),
13486
+ eq20(runs.kind, RunKinds["answer-visibility"]),
13053
13487
  gte2(runs.createdAt, since),
13054
13488
  ne3(runs.trigger, RunTriggers.probe)
13055
13489
  ))),
13056
- audits: total(db.select({ value: count3() }).from(runs).where(and15(
13490
+ audits: total(db.select({ value: count4() }).from(runs).where(and16(
13057
13491
  inArray8(runs.projectId, projectIds),
13058
- eq19(runs.kind, RunKinds["site-audit"]),
13492
+ eq20(runs.kind, RunKinds["site-audit"]),
13059
13493
  gte2(runs.createdAt, since)
13060
13494
  ))),
13061
13495
  providerCalls: [...providerCalls.values()].reduce((sum, calls) => sum + calls, 0)
@@ -13068,18 +13502,18 @@ function buildInstallState(sources, now, startedAt) {
13068
13502
  }
13069
13503
  }
13070
13504
  const aero = db.select({
13071
- calls: count3(),
13072
- inputTokens: sql7`coalesce(sum(${llmUsageEvents.inputTokens}), 0)`,
13073
- outputTokens: sql7`coalesce(sum(${llmUsageEvents.outputTokens}), 0)`,
13074
- costMillicents: sql7`coalesce(sum(${llmUsageEvents.costMillicents}), 0)`
13075
- }).from(llmUsageEvents).where(and15(eq19(llmUsageEvents.feature, AeroLlmUsageFeatures.turn), gte2(llmUsageEvents.createdAt, since))).get();
13505
+ calls: count4(),
13506
+ inputTokens: sql8`coalesce(sum(${llmUsageEvents.inputTokens}), 0)`,
13507
+ outputTokens: sql8`coalesce(sum(${llmUsageEvents.outputTokens}), 0)`,
13508
+ costMillicents: sql8`coalesce(sum(${llmUsageEvents.costMillicents}), 0)`
13509
+ }).from(llmUsageEvents).where(and16(eq20(llmUsageEvents.feature, AeroLlmUsageFeatures.turn), gte2(llmUsageEvents.createdAt, since))).get();
13076
13510
  const aeroCalls = whole(aero?.calls);
13077
13511
  usage24h.aeroModelCalls = aeroCalls;
13078
13512
  usage24h.aeroInputTokens = whole(aero?.inputTokens);
13079
13513
  usage24h.aeroOutputTokens = whole(aero?.outputTokens);
13080
13514
  const aeroMillicents = whole(aero?.costMillicents);
13081
13515
  if (aeroCalls === 0 || aeroMillicents > 0) usage24h.aeroCostMicros = aeroMillicents * 10;
13082
- usage24h.webhookDeliveries = total(db.select({ value: count3() }).from(auditLog).where(and15(gte2(auditLog.createdAt, since), eq19(auditLog.action, WEBHOOK_DELIVERED_ACTION))));
13516
+ usage24h.webhookDeliveries = total(db.select({ value: count4() }).from(auditLog).where(and16(gte2(auditLog.createdAt, since), eq20(auditLog.action, WEBHOOK_DELIVERED_ACTION))));
13083
13517
  const providerCalls24h = Object.fromEntries(PROVIDER_NAMES.flatMap((name) => providerCalls.has(name) ? [[name, providerCalls.get(name)]] : []));
13084
13518
  return {
13085
13519
  providers: registeredProviderNames(config).filter((name) => KNOWN_PROVIDERS2.has(name)),
@@ -13113,7 +13547,7 @@ function startInstallStateTelemetry(sources) {
13113
13547
  try {
13114
13548
  if (isTelemetryEnabled()) trackInstallState(buildInstallState(sources, Date.now(), startedAt));
13115
13549
  } catch (err) {
13116
- log16.warn("telemetry.install-state-failed", { error: describeError(err) });
13550
+ log17.warn("telemetry.install-state-failed", { error: describeError(err) });
13117
13551
  }
13118
13552
  schedule(INSTALL_STATE_INTERVAL_MS);
13119
13553
  };
@@ -13209,7 +13643,7 @@ function configuredProviderEntries(config, apiAdapters, cdpAdapter) {
13209
13643
 
13210
13644
  // src/provider-reload-batch-guard.ts
13211
13645
  import { isDeepStrictEqual } from "util";
13212
- import { and as and16, inArray as inArray9 } from "drizzle-orm";
13646
+ import { and as and17, inArray as inArray9 } from "drizzle-orm";
13213
13647
  function pendingBatchIdentity(provider) {
13214
13648
  const { quotaPolicy: _quotaPolicy, ...identity } = resolveRegistration(provider.adapter, provider.config).config;
13215
13649
  return identity;
@@ -13223,7 +13657,7 @@ function assertProviderReloadKeepsPendingBatches(db, previous, next, executing =
13223
13657
  const changed = previous.filter(changedRegistration).map((provider) => provider.adapter.name);
13224
13658
  const active = executing.filter((snapshot) => changedRegistration(snapshot.registration));
13225
13659
  if (changed.length === 0 && active.length === 0) return;
13226
- const pending = changed.length === 0 ? [] : db.select({ id: providerBatches.id, provider: providerBatches.provider, runId: providerBatches.runId }).from(providerBatches).where(and16(
13660
+ const pending = changed.length === 0 ? [] : db.select({ id: providerBatches.id, provider: providerBatches.provider, runId: providerBatches.runId }).from(providerBatches).where(and17(
13227
13661
  inArray9(providerBatches.provider, changed),
13228
13662
  inArray9(providerBatches.status, [ProviderBatchStatuses.submitting, ProviderBatchStatuses.submitted, ProviderBatchStatuses.ended])
13229
13663
  )).all();
@@ -13303,10 +13737,10 @@ function createProviderModelCatalog(registry) {
13303
13737
  }
13304
13738
 
13305
13739
  // src/scheduler.ts
13306
- import crypto20 from "crypto";
13740
+ import crypto21 from "crypto";
13307
13741
  import cron from "node-cron";
13308
- import { and as and17, eq as eq20, inArray as inArray10, notExists, sql as sql8 } from "drizzle-orm";
13309
- var log17 = createLogger("Scheduler");
13742
+ import { and as and18, eq as eq21, inArray as inArray10, notExists, sql as sql9 } from "drizzle-orm";
13743
+ var log18 = createLogger("Scheduler");
13310
13744
  var SLOT_FIRED = { status: OutcomeStatuses.succeeded };
13311
13745
  var slotSkipped = (reasonCode) => ({ status: OutcomeStatuses.skipped, reasonCode });
13312
13746
  var slotFailed = (reasonCode) => ({ status: OutcomeStatuses.failed, reasonCode });
@@ -13315,7 +13749,7 @@ var SCHEDULE_SPREAD_MS = 12e4;
13315
13749
  var UNSPREAD_KINDS = /* @__PURE__ */ new Set([SchedulableRunKinds["answer-visibility"]]);
13316
13750
  function scheduleSpreadMs(projectId, kind, spreadMs = SCHEDULE_SPREAD_MS) {
13317
13751
  if (spreadMs <= 0) return 0;
13318
- return crypto20.createHash("sha256").update(`${projectId}:${kind}`).digest().readUInt32BE(0) % spreadMs;
13752
+ return crypto21.createHash("sha256").update(`${projectId}:${kind}`).digest().readUInt32BE(0) % spreadMs;
13319
13753
  }
13320
13754
  function shortestCronGapMs(cronExpr, timezone, from = /* @__PURE__ */ new Date()) {
13321
13755
  let previous = nextRunFromCron(cronExpr, timezone, from);
@@ -13330,7 +13764,7 @@ function shortestCronGapMs(cronExpr, timezone, from = /* @__PURE__ */ new Date()
13330
13764
  }
13331
13765
  function ensureDefaultHealthSchedule(db, projectId, now = (/* @__PURE__ */ new Date()).toISOString()) {
13332
13766
  const result = db.insert(schedules).values({
13333
- id: crypto20.randomUUID(),
13767
+ id: crypto21.randomUUID(),
13334
13768
  projectId,
13335
13769
  kind: SchedulableRunKinds.doctor,
13336
13770
  cronExpr: DEFAULT_HEALTH_CRON,
@@ -13384,9 +13818,9 @@ var Scheduler = class {
13384
13818
  */
13385
13819
  ensureHealthSchedules() {
13386
13820
  const projectsWithoutHealth = this.db.select({ id: projects.id }).from(projects).where(notExists(
13387
- this.db.select({ one: sql8`1` }).from(schedules).where(and17(
13388
- eq20(schedules.projectId, projects.id),
13389
- eq20(schedules.kind, SchedulableRunKinds.doctor)
13821
+ this.db.select({ one: sql9`1` }).from(schedules).where(and18(
13822
+ eq21(schedules.projectId, projects.id),
13823
+ eq21(schedules.kind, SchedulableRunKinds.doctor)
13390
13824
  ))
13391
13825
  )).all();
13392
13826
  if (projectsWithoutHealth.length === 0) return;
@@ -13396,7 +13830,7 @@ var Scheduler = class {
13396
13830
  if (ensureDefaultHealthSchedule(this.db, project.id, now)) seeded += 1;
13397
13831
  }
13398
13832
  if (seeded > 0) {
13399
- log17.info("health-schedule.seeded", { projectCount: seeded, cron: DEFAULT_HEALTH_CRON });
13833
+ log18.info("health-schedule.seeded", { projectCount: seeded, cron: DEFAULT_HEALTH_CRON });
13400
13834
  }
13401
13835
  }
13402
13836
  /**
@@ -13420,16 +13854,16 @@ var Scheduler = class {
13420
13854
  const current = ensureCurrentQueryBasketRevision(this.db, project.id);
13421
13855
  if (current !== null && current.revision !== previous) minted += 1;
13422
13856
  } catch (err) {
13423
- log17.warn("query-basket.mint-failed", { projectId: project.id, err: String(err) });
13857
+ log18.warn("query-basket.mint-failed", { projectId: project.id, err: String(err) });
13424
13858
  }
13425
13859
  }
13426
- if (minted > 0) log17.info("query-basket.ensured", { projectCount: minted });
13860
+ if (minted > 0) log18.info("query-basket.ensured", { projectCount: minted });
13427
13861
  }
13428
13862
  /** Load all enabled schedules from DB and register cron jobs. */
13429
13863
  start() {
13430
13864
  this.ensureHealthSchedules();
13431
13865
  this.ensureQueryBaskets();
13432
- const allSchedules = this.db.select().from(schedules).where(eq20(schedules.enabled, true)).all();
13866
+ const allSchedules = this.db.select().from(schedules).where(eq21(schedules.enabled, true)).all();
13433
13867
  for (const schedule of allSchedules) {
13434
13868
  const missedRunAt = schedule.nextRunAt;
13435
13869
  const recurrence = scheduleRecurrence(schedule);
@@ -13437,12 +13871,12 @@ var Scheduler = class {
13437
13871
  if (registered && missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
13438
13872
  const answerRecurrence = recurrence && schedule.kind === SchedulableRunKinds["answer-visibility"];
13439
13873
  if (!recurrence || answerRecurrence || this.claimCalendarOccurrence(schedule, missedRunAt, /* @__PURE__ */ new Date())) {
13440
- log17.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
13874
+ log18.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
13441
13875
  this.triggerRun(schedule.id, schedule.projectId, schedule.kind, answerRecurrence ? missedRunAt : void 0, OutcomeTriggers.startup);
13442
13876
  }
13443
13877
  }
13444
13878
  }
13445
- log17.info("started", { scheduleCount: allSchedules.length });
13879
+ log18.info("started", { scheduleCount: allSchedules.length });
13446
13880
  }
13447
13881
  /** Stop all cron tasks for graceful shutdown. */
13448
13882
  stop() {
@@ -13463,7 +13897,7 @@ var Scheduler = class {
13463
13897
  this.stopTask(key, existing, "Stopped");
13464
13898
  this.tasks.delete(key);
13465
13899
  }
13466
- const schedule = this.db.select().from(schedules).where(and17(eq20(schedules.projectId, projectId), eq20(schedules.kind, kind))).get();
13900
+ const schedule = this.db.select().from(schedules).where(and18(eq21(schedules.projectId, projectId), eq21(schedules.kind, kind))).get();
13467
13901
  if (schedule && schedule.enabled) {
13468
13902
  this.registerCronTask(schedule);
13469
13903
  }
@@ -13491,17 +13925,17 @@ var Scheduler = class {
13491
13925
  clearTimeout(pending);
13492
13926
  this.spreadTimers.delete(key);
13493
13927
  }
13494
- log17.info(`task.${verb.toLowerCase()}`, { key });
13928
+ log18.info(`task.${verb.toLowerCase()}`, { key });
13495
13929
  }
13496
13930
  /** Runtime timing writes must not restore a version already issued to a settings caller. */
13497
13931
  updateScheduleTiming(scheduleId, timing) {
13498
13932
  this.db.transaction((tx) => {
13499
- const current = tx.select({ updatedAt: schedules.updatedAt }).from(schedules).where(eq20(schedules.id, scheduleId)).get();
13933
+ const current = tx.select({ updatedAt: schedules.updatedAt }).from(schedules).where(eq21(schedules.id, scheduleId)).get();
13500
13934
  if (!current) return;
13501
13935
  tx.update(schedules).set({
13502
13936
  ...timing,
13503
13937
  updatedAt: nextScheduleUpdatedAt(current.updatedAt)
13504
- }).where(eq20(schedules.id, scheduleId)).run();
13938
+ }).where(eq21(schedules.id, scheduleId)).run();
13505
13939
  });
13506
13940
  }
13507
13941
  registerCronTask(schedule, options = {}) {
@@ -13511,13 +13945,13 @@ var Scheduler = class {
13511
13945
  return true;
13512
13946
  }
13513
13947
  if (schedule.recurrence) {
13514
- log17.error("calendar.invalid", { projectId: schedule.projectId, kind: schedule.kind });
13948
+ log18.error("calendar.invalid", { projectId: schedule.projectId, kind: schedule.kind });
13515
13949
  return false;
13516
13950
  }
13517
13951
  const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
13518
13952
  const kind = schedule.kind;
13519
13953
  if (!cron.validate(cronExpr)) {
13520
- log17.error("cron.invalid", { projectId, kind, cronExpr });
13954
+ log18.error("cron.invalid", { projectId, kind, cronExpr });
13521
13955
  return false;
13522
13956
  }
13523
13957
  const key = taskKey(projectId, kind);
@@ -13547,7 +13981,7 @@ var Scheduler = class {
13547
13981
  });
13548
13982
  }
13549
13983
  const label = schedule.preset ?? cronExpr;
13550
- log17.info("cron.registered", { projectId, kind, schedule: label, timezone });
13984
+ log18.info("cron.registered", { projectId, kind, schedule: label, timezone });
13551
13985
  return true;
13552
13986
  }
13553
13987
  /** Calendar schedules are checked at most once a minute so long intervals
@@ -13577,7 +14011,7 @@ var Scheduler = class {
13577
14011
  };
13578
14012
  const tick = () => {
13579
14013
  if (stopped) return;
13580
- const current = this.db.select().from(schedules).where(eq20(schedules.id, scheduleId)).get();
14014
+ const current = this.db.select().from(schedules).where(eq21(schedules.id, scheduleId)).get();
13581
14015
  if (!current || !current.enabled || !scheduleRecurrence(current)) {
13582
14016
  this.remove(projectId, kind);
13583
14017
  return;
@@ -13595,7 +14029,7 @@ var Scheduler = class {
13595
14029
  this.triggerRun(scheduleId, projectId, kind);
13596
14030
  }
13597
14031
  }
13598
- arm(this.db.select({ nextRunAt: schedules.nextRunAt }).from(schedules).where(eq20(schedules.id, scheduleId)).get()?.nextRunAt);
14032
+ arm(this.db.select({ nextRunAt: schedules.nextRunAt }).from(schedules).where(eq21(schedules.id, scheduleId)).get()?.nextRunAt);
13599
14033
  };
13600
14034
  this.tasks.set(key, task);
13601
14035
  let registeredNextRunAt = schedule.nextRunAt;
@@ -13605,12 +14039,12 @@ var Scheduler = class {
13605
14039
  }
13606
14040
  if (options.preserveNextRunAt) arm(registeredNextRunAt);
13607
14041
  else timer = setTimeout(tick, 0);
13608
- log17.info("calendar.registered", { projectId, kind, recurrence, timezone });
14042
+ log18.info("calendar.registered", { projectId, kind, recurrence, timezone });
13609
14043
  }
13610
14044
  /** Atomically advance a due callback-only calendar occurrence before dispatch. */
13611
14045
  claimCalendarOccurrence(schedule, dueAt, now) {
13612
14046
  return this.db.transaction((tx) => {
13613
- const current = tx.select().from(schedules).where(eq20(schedules.id, schedule.id)).get();
14047
+ const current = tx.select().from(schedules).where(eq21(schedules.id, schedule.id)).get();
13614
14048
  if (!current || !current.enabled || current.updatedAt !== schedule.updatedAt || current.nextRunAt !== dueAt) return false;
13615
14049
  const recurrence = scheduleRecurrence(current);
13616
14050
  if (!recurrence) return false;
@@ -13619,11 +14053,11 @@ var Scheduler = class {
13619
14053
  const result = tx.update(schedules).set({
13620
14054
  nextRunAt,
13621
14055
  updatedAt: nextScheduleUpdatedAt(current.updatedAt)
13622
- }).where(and17(
13623
- eq20(schedules.id, current.id),
13624
- eq20(schedules.enabled, true),
13625
- eq20(schedules.nextRunAt, dueAt),
13626
- eq20(schedules.updatedAt, schedule.updatedAt)
14056
+ }).where(and18(
14057
+ eq21(schedules.id, current.id),
14058
+ eq21(schedules.enabled, true),
14059
+ eq21(schedules.nextRunAt, dueAt),
14060
+ eq21(schedules.updatedAt, schedule.updatedAt)
13627
14061
  )).run();
13628
14062
  return result.changes === 1;
13629
14063
  });
@@ -13645,15 +14079,15 @@ var Scheduler = class {
13645
14079
  fireSlot(scheduleId, projectId, kind, claimedOccurrence) {
13646
14080
  try {
13647
14081
  const now = (/* @__PURE__ */ new Date()).toISOString();
13648
- const currentSchedule = this.db.select().from(schedules).where(eq20(schedules.id, scheduleId)).get();
14082
+ const currentSchedule = this.db.select().from(schedules).where(eq21(schedules.id, scheduleId)).get();
13649
14083
  if (!currentSchedule || !currentSchedule.enabled) {
13650
- log17.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
14084
+ log18.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
13651
14085
  this.remove(projectId, kind);
13652
14086
  return slotSkipped(currentSchedule ? OutcomeReasonCodes.NOT_DUE : OutcomeReasonCodes.NOT_FOUND);
13653
14087
  }
13654
14088
  const recurrence = scheduleRecurrence(currentSchedule);
13655
14089
  if (claimedOccurrence && (!recurrence || currentSchedule.nextRunAt !== claimedOccurrence)) {
13656
- log17.info("calendar.stale-claim", { projectId, scheduleId, kind });
14090
+ log18.info("calendar.stale-claim", { projectId, scheduleId, kind });
13657
14091
  return slotSkipped(OutcomeReasonCodes.NOT_DUE);
13658
14092
  }
13659
14093
  const nextRunAt = nextRunFromSchedule({
@@ -13661,36 +14095,36 @@ var Scheduler = class {
13661
14095
  timezone: currentSchedule.timezone,
13662
14096
  recurrence
13663
14097
  });
13664
- const project = this.db.select().from(projects).where(eq20(projects.id, projectId)).get();
14098
+ const project = this.db.select().from(projects).where(eq21(projects.id, projectId)).get();
13665
14099
  if (!project) {
13666
- log17.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
14100
+ log18.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
13667
14101
  this.remove(projectId, kind);
13668
14102
  return slotSkipped(OutcomeReasonCodes.NOT_FOUND);
13669
14103
  }
13670
14104
  if (kind === SchedulableRunKinds["traffic-sync"]) {
13671
14105
  const sourceId = currentSchedule.sourceId;
13672
14106
  if (!sourceId) {
13673
- log17.warn("traffic-sync.missing-source", { scheduleId, projectId });
14107
+ log18.warn("traffic-sync.missing-source", { scheduleId, projectId });
13674
14108
  return slotFailed(OutcomeReasonCodes.VALIDATION);
13675
14109
  }
13676
14110
  if (!this.callbacks.onTrafficSyncRequested) {
13677
- log17.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
14111
+ log18.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
13678
14112
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13679
14113
  }
13680
14114
  this.updateScheduleTiming(currentSchedule.id, {
13681
14115
  lastRunAt: now,
13682
14116
  nextRunAt
13683
14117
  });
13684
- log17.info("traffic-sync.triggered", { projectName: project.name, sourceId });
14118
+ log18.info("traffic-sync.triggered", { projectName: project.name, sourceId });
13685
14119
  this.callbacks.onTrafficSyncRequested(project.name, sourceId);
13686
14120
  return SLOT_FIRED;
13687
14121
  }
13688
14122
  if (kind === SchedulableRunKinds["gbp-sync"]) {
13689
14123
  if (!this.callbacks.onGbpSyncRequested) {
13690
- log17.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
14124
+ log18.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
13691
14125
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13692
14126
  }
13693
- const runId2 = crypto20.randomUUID();
14127
+ const runId2 = crypto21.randomUUID();
13694
14128
  this.db.insert(runs).values({
13695
14129
  id: runId2,
13696
14130
  projectId,
@@ -13703,26 +14137,26 @@ var Scheduler = class {
13703
14137
  lastRunAt: now,
13704
14138
  nextRunAt
13705
14139
  });
13706
- log17.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
14140
+ log18.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
13707
14141
  this.callbacks.onGbpSyncRequested(runId2, projectId);
13708
14142
  return SLOT_FIRED;
13709
14143
  }
13710
14144
  if (kind === SchedulableRunKinds["ads-sync"]) {
13711
14145
  if (!this.callbacks.onAdsSyncRequested) {
13712
- log17.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
14146
+ log18.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
13713
14147
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13714
14148
  }
13715
- const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and17(
13716
- eq20(runs.projectId, projectId),
13717
- eq20(runs.kind, RunKinds["ads-sync"]),
14149
+ const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and18(
14150
+ eq21(runs.projectId, projectId),
14151
+ eq21(runs.kind, RunKinds["ads-sync"]),
13718
14152
  inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
13719
14153
  )).get();
13720
14154
  if (activeAdsRun) {
13721
- log17.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
14155
+ log18.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
13722
14156
  this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
13723
14157
  return slotSkipped(OutcomeReasonCodes.OPERATION_IN_PROGRESS);
13724
14158
  }
13725
- const runId2 = crypto20.randomUUID();
14159
+ const runId2 = crypto21.randomUUID();
13726
14160
  this.db.insert(runs).values({
13727
14161
  id: runId2,
13728
14162
  projectId,
@@ -13735,22 +14169,22 @@ var Scheduler = class {
13735
14169
  lastRunAt: now,
13736
14170
  nextRunAt
13737
14171
  });
13738
- log17.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
14172
+ log18.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
13739
14173
  this.callbacks.onAdsSyncRequested(runId2, projectId);
13740
14174
  return SLOT_FIRED;
13741
14175
  }
13742
14176
  if (kind === SchedulableRunKinds["seo-sync"]) {
13743
14177
  if (!this.callbacks.onSeoSyncRequested) {
13744
- log17.warn("seo-sync.no-callback", { scheduleId, projectId, msg: "host did not register onSeoSyncRequested" });
14178
+ log18.warn("seo-sync.no-callback", { scheduleId, projectId, msg: "host did not register onSeoSyncRequested" });
13745
14179
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13746
14180
  }
13747
- const activeSeoRun = this.db.select({ id: runs.id }).from(runs).where(and17(
13748
- eq20(runs.projectId, projectId),
13749
- eq20(runs.kind, RunKinds["seo-sync"]),
14181
+ const activeSeoRun = this.db.select({ id: runs.id }).from(runs).where(and18(
14182
+ eq21(runs.projectId, projectId),
14183
+ eq21(runs.kind, RunKinds["seo-sync"]),
13750
14184
  inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
13751
14185
  )).get();
13752
14186
  if (activeSeoRun) {
13753
- log17.info("seo-sync.skipped-active", { projectName: project.name, activeRunId: activeSeoRun.id });
14187
+ log18.info("seo-sync.skipped-active", { projectName: project.name, activeRunId: activeSeoRun.id });
13754
14188
  this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
13755
14189
  return slotSkipped(OutcomeReasonCodes.OPERATION_IN_PROGRESS);
13756
14190
  }
@@ -13758,65 +14192,65 @@ var Scheduler = class {
13758
14192
  lastRunAt: now,
13759
14193
  nextRunAt
13760
14194
  });
13761
- log17.info("seo-sync.triggered", { projectName: project.name });
14195
+ log18.info("seo-sync.triggered", { projectName: project.name });
13762
14196
  this.callbacks.onSeoSyncRequested({ scheduleId, projectId, occurrence: now.slice(0, 16) });
13763
14197
  return SLOT_FIRED;
13764
14198
  }
13765
14199
  if (kind === SchedulableRunKinds["data-refresh"]) {
13766
14200
  if (!this.callbacks.onDataRefreshRequested) {
13767
- log17.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
14201
+ log18.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
13768
14202
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13769
14203
  }
13770
14204
  this.updateScheduleTiming(currentSchedule.id, {
13771
14205
  lastRunAt: now,
13772
14206
  nextRunAt
13773
14207
  });
13774
- log17.info("data-refresh.triggered", { projectName: project.name });
14208
+ log18.info("data-refresh.triggered", { projectName: project.name });
13775
14209
  this.callbacks.onDataRefreshRequested(project.name);
13776
14210
  return SLOT_FIRED;
13777
14211
  }
13778
14212
  if (kind === SchedulableRunKinds.doctor) {
13779
14213
  if (!this.callbacks.onDoctorRequested) {
13780
- log17.warn("doctor.no-callback", { scheduleId, projectId, msg: "host did not register onDoctorRequested" });
14214
+ log18.warn("doctor.no-callback", { scheduleId, projectId, msg: "host did not register onDoctorRequested" });
13781
14215
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13782
14216
  }
13783
14217
  this.updateScheduleTiming(currentSchedule.id, {
13784
14218
  lastRunAt: now,
13785
14219
  nextRunAt
13786
14220
  });
13787
- log17.info("doctor.triggered", { projectName: project.name });
14221
+ log18.info("doctor.triggered", { projectName: project.name });
13788
14222
  this.callbacks.onDoctorRequested(project.name);
13789
14223
  return SLOT_FIRED;
13790
14224
  }
13791
14225
  if (kind === SchedulableRunKinds["backlinks-sync"]) {
13792
14226
  if (!this.callbacks.onBacklinksSyncRequested) {
13793
- log17.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
14227
+ log18.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
13794
14228
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13795
14229
  }
13796
14230
  this.updateScheduleTiming(currentSchedule.id, {
13797
14231
  lastRunAt: now,
13798
14232
  nextRunAt
13799
14233
  });
13800
- log17.info("backlinks-sync.triggered", { projectName: project.name });
14234
+ log18.info("backlinks-sync.triggered", { projectName: project.name });
13801
14235
  this.callbacks.onBacklinksSyncRequested(project.name);
13802
14236
  return SLOT_FIRED;
13803
14237
  }
13804
14238
  if (kind === SchedulableRunKinds["site-audit"]) {
13805
14239
  if (!this.callbacks.onSiteAuditRequested) {
13806
- log17.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
14240
+ log18.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
13807
14241
  return slotFailed(OutcomeReasonCodes.UNSUPPORTED);
13808
14242
  }
13809
- const active = this.db.select({ id: runs.id }).from(runs).where(and17(
13810
- eq20(runs.projectId, projectId),
13811
- eq20(runs.kind, RunKinds["site-audit"]),
14243
+ const active = this.db.select({ id: runs.id }).from(runs).where(and18(
14244
+ eq21(runs.projectId, projectId),
14245
+ eq21(runs.kind, RunKinds["site-audit"]),
13812
14246
  inArray10(runs.status, [RunStatuses.queued, RunStatuses.running])
13813
14247
  )).get();
13814
14248
  if (active) {
13815
- log17.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
14249
+ log18.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
13816
14250
  this.updateScheduleTiming(currentSchedule.id, { nextRunAt });
13817
14251
  return slotSkipped(OutcomeReasonCodes.OPERATION_IN_PROGRESS);
13818
14252
  }
13819
- const runId2 = crypto20.randomUUID();
14253
+ const runId2 = crypto21.randomUUID();
13820
14254
  const effectiveRequest = normalizeSiteAuditRunRequest({}, project.siteAuditMaxPages);
13821
14255
  this.db.transaction((tx) => {
13822
14256
  tx.insert(runs).values({
@@ -13839,7 +14273,7 @@ var Scheduler = class {
13839
14273
  lastRunAt: now,
13840
14274
  nextRunAt
13841
14275
  });
13842
- log17.info("site-audit.triggered", { runId: runId2, projectName: project.name });
14276
+ log18.info("site-audit.triggered", { runId: runId2, projectName: project.name });
13843
14277
  this.callbacks.onSiteAuditRequested(runId2, projectId, { maxPages: effectiveRequest.maxPages });
13844
14278
  return SLOT_FIRED;
13845
14279
  }
@@ -13848,7 +14282,7 @@ var Scheduler = class {
13848
14282
  if (project.defaultLocation) {
13849
14283
  const loc = projectLocations.find((l) => l.label === project.defaultLocation);
13850
14284
  if (!loc) {
13851
- log17.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
14285
+ log18.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
13852
14286
  return slotFailed(OutcomeReasonCodes.VALIDATION);
13853
14287
  }
13854
14288
  resolvedLocation = loc;
@@ -13857,7 +14291,7 @@ var Scheduler = class {
13857
14291
  const scheduleProviders = currentSchedule.providers;
13858
14292
  const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
13859
14293
  if (claimedOccurrence && recurrence && !nextRunAt) {
13860
- log17.error("calendar.invalid", { projectId, kind, scheduleId });
14294
+ log18.error("calendar.invalid", { projectId, kind, scheduleId });
13861
14295
  return slotFailed(OutcomeReasonCodes.VALIDATION);
13862
14296
  }
13863
14297
  const queueResult = queueRunIfProjectIdle(this.db, {
@@ -13885,11 +14319,11 @@ var Scheduler = class {
13885
14319
  });
13886
14320
  if (queueResult.conflict) {
13887
14321
  if (queueResult.scheduleClaimed === false) {
13888
- log17.info("calendar.skipped-claimed", { projectName: project.name, scheduleId: currentSchedule.id });
14322
+ log18.info("calendar.skipped-claimed", { projectName: project.name, scheduleId: currentSchedule.id });
13889
14323
  return slotSkipped(OutcomeReasonCodes.NOT_DUE);
13890
14324
  }
13891
14325
  const batchPending = hasOutstandingProviderBatch(this.db, queueResult.activeRunId);
13892
- log17.info("run.skipped-active", {
14326
+ log18.info("run.skipped-active", {
13893
14327
  projectName: project.name,
13894
14328
  activeRunId: queueResult.activeRunId,
13895
14329
  ...batchPending ? { reason: "batch-pending" } : {}
@@ -13898,7 +14332,7 @@ var Scheduler = class {
13898
14332
  return slotSkipped(OutcomeReasonCodes.OPERATION_IN_PROGRESS);
13899
14333
  }
13900
14334
  if (queueResult.refused) {
13901
- log17.warn("run.skipped-providers-failing", {
14335
+ log18.warn("run.skipped-providers-failing", {
13902
14336
  projectName: project.name,
13903
14337
  scheduleId: currentSchedule.id,
13904
14338
  ...queueResult.refused
@@ -13917,7 +14351,7 @@ var Scheduler = class {
13917
14351
  const runId = queueResult.runId;
13918
14352
  for (const [provider, reason] of Object.entries(queueResult.dispatch.ineligible)) {
13919
14353
  if (!reason) continue;
13920
- log17.warn("run.dispatch-sync-fallback", {
14354
+ log18.warn("run.dispatch-sync-fallback", {
13921
14355
  runId,
13922
14356
  projectName: project.name,
13923
14357
  provider,
@@ -13931,7 +14365,7 @@ var Scheduler = class {
13931
14365
  lastRunAt: now,
13932
14366
  nextRunAt
13933
14367
  });
13934
- log17.info("run.triggered", {
14368
+ log18.info("run.triggered", {
13935
14369
  runId,
13936
14370
  projectName: project.name,
13937
14371
  providers: providers ?? "all",
@@ -13940,14 +14374,14 @@ var Scheduler = class {
13940
14374
  this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
13941
14375
  return SLOT_FIRED;
13942
14376
  } catch (err) {
13943
- log17.error("trigger.error", { scheduleId, projectId, kind, error: describeError(err) });
14377
+ log18.error("trigger.error", { scheduleId, projectId, kind, error: describeError(err) });
13944
14378
  return { status: OutcomeStatuses.failed, ...outcomeFailure(err) };
13945
14379
  }
13946
14380
  }
13947
14381
  };
13948
14382
 
13949
14383
  // src/site-liveness-loop.ts
13950
- var log18 = createLogger("SiteLiveness");
14384
+ var log19 = createLogger("SiteLiveness");
13951
14385
  var SITE_LIVENESS_INTERVAL_MS = 10 * 6e4;
13952
14386
  async function runSiteLivenessPass(deps) {
13953
14387
  const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
@@ -13964,10 +14398,10 @@ async function runSiteLivenessPass(deps) {
13964
14398
  const event = await deps.notify(project.id, { check, checkedAt: now() });
13965
14399
  if (event) {
13966
14400
  events += 1;
13967
- log18.info("site-liveness.notified", { projectName: project.name, event, code: check.code });
14401
+ log19.info("site-liveness.notified", { projectName: project.name, event, code: check.code });
13968
14402
  }
13969
14403
  } catch (err) {
13970
- log18.warn("site-liveness.project-failed", { projectName: project.name, err: String(err) });
14404
+ log19.warn("site-liveness.project-failed", { projectName: project.name, err: String(err) });
13971
14405
  if (failed === 0) firstFailure = err;
13972
14406
  failed += 1;
13973
14407
  }
@@ -13999,14 +14433,14 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
13999
14433
  };
14000
14434
  const timer = setInterval(() => {
14001
14435
  if (running) {
14002
- log18.warn("site-liveness.pass-still-running", { intervalMs });
14436
+ log19.warn("site-liveness.pass-still-running", { intervalMs });
14003
14437
  report({ status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.OPERATION_IN_PROGRESS });
14004
14438
  return;
14005
14439
  }
14006
14440
  running = true;
14007
14441
  const elapsed = startOutcomeTimer();
14008
14442
  void runSiteLivenessPass(deps).then((result) => report({ ...livenessOutcome(result), durationBucket: elapsed() })).catch((err) => {
14009
- log18.warn("site-liveness.pass-failed", { err: String(err) });
14443
+ log19.warn("site-liveness.pass-failed", { err: String(err) });
14010
14444
  report({ status: OutcomeStatuses.failed, ...outcomeFailure(err), durationBucket: elapsed() });
14011
14445
  }).finally(() => {
14012
14446
  running = false;
@@ -14018,8 +14452,8 @@ function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
14018
14452
 
14019
14453
  // src/review-webhook-delivery.ts
14020
14454
  import { createHash as createHash7 } from "crypto";
14021
- import { and as and18, asc as asc4, eq as eq21, isNull as isNull3, lte as lte2, or } from "drizzle-orm";
14022
- var log19 = createLogger("ReviewDelivery");
14455
+ import { and as and19, asc as asc4, eq as eq22, isNull as isNull3, lte as lte2, or } from "drizzle-orm";
14456
+ var log20 = createLogger("ReviewDelivery");
14023
14457
  var hash = (value) => createHash7("sha256").update(JSON.stringify(value)).digest("hex");
14024
14458
  var REVIEW_DELIVERY_MAX_ATTEMPTS = 10;
14025
14459
  var REVIEW_CHAT_SENDS_PER_PASS = 3;
@@ -14046,20 +14480,20 @@ function reviewEventId(projectId, review) {
14046
14480
  function reviewEventRefusal(db, projectId, payload, notification, targetDigest, now) {
14047
14481
  if (!notification?.enabled || !notification.config.events.includes("review.negative")) return "destination_disabled";
14048
14482
  if (targetDigest !== void 0 && hash(notification.config.url) !== targetDigest) return "destination_changed";
14049
- const project = db.select().from(projects).where(eq21(projects.id, projectId)).get();
14483
+ const project = db.select().from(projects).where(eq22(projects.id, projectId)).get();
14050
14484
  if (!project || project.name !== payload.project.name || project.canonicalDomain !== payload.project.canonicalDomain) return "project_changed";
14051
14485
  const review = payload.reviews[0];
14052
- const location = db.select().from(gbpLocations).where(and18(
14053
- eq21(gbpLocations.projectId, projectId),
14054
- eq21(gbpLocations.locationName, review.location.name),
14055
- eq21(gbpLocations.selected, true)
14486
+ const location = db.select().from(gbpLocations).where(and19(
14487
+ eq22(gbpLocations.projectId, projectId),
14488
+ eq22(gbpLocations.locationName, review.location.name),
14489
+ eq22(gbpLocations.selected, true)
14056
14490
  )).get();
14057
14491
  if (!location || review.origin === "gbp" && !review.reviewName.startsWith(`${location.accountName}/${location.locationName}/reviews/`)) return "location_removed_or_deselected";
14058
- const current = db.select().from(gbpReviews).where(and18(
14059
- eq21(gbpReviews.projectId, projectId),
14060
- eq21(gbpReviews.origin, review.origin),
14061
- eq21(gbpReviews.reviewName, review.reviewName),
14062
- eq21(gbpReviews.locationName, review.location.name)
14492
+ const current = db.select().from(gbpReviews).where(and19(
14493
+ eq22(gbpReviews.projectId, projectId),
14494
+ eq22(gbpReviews.origin, review.origin),
14495
+ eq22(gbpReviews.reviewName, review.reviewName),
14496
+ eq22(gbpReviews.locationName, review.location.name)
14063
14497
  )).get();
14064
14498
  if (!current) return "review_removed";
14065
14499
  if (!isWithinAlertWindow(review.updateTime, now) || !isWithinAlertWindow(current.updateTime, now)) return "review_expired";
@@ -14067,10 +14501,10 @@ function reviewEventRefusal(db, projectId, payload, notification, targetDigest,
14067
14501
  if (!isNegativeReviewRating(current.starRating, threshold) || !isNegativeReviewRating(review.starRating, threshold)) return "review_no_longer_negative";
14068
14502
  if (["baseline", "skipped", "suppressed", "stale", "none"].includes(current.alertState)) return "review_suppressed";
14069
14503
  if (review.origin === "places") {
14070
- const v4HasWorked = db.select({ id: gbpReviewRatings.id }).from(gbpReviewRatings).where(and18(
14071
- eq21(gbpReviewRatings.projectId, projectId),
14072
- eq21(gbpReviewRatings.locationName, review.location.name),
14073
- eq21(gbpReviewRatings.origin, "gbp")
14504
+ const v4HasWorked = db.select({ id: gbpReviewRatings.id }).from(gbpReviewRatings).where(and19(
14505
+ eq22(gbpReviewRatings.projectId, projectId),
14506
+ eq22(gbpReviewRatings.locationName, review.location.name),
14507
+ eq22(gbpReviewRatings.origin, "gbp")
14074
14508
  )).get() !== void 0;
14075
14509
  if (businessProfileReviewsCover(location.reviewsAccess, v4HasWorked)) return "review_origin_superseded";
14076
14510
  }
@@ -14078,9 +14512,9 @@ function reviewEventRefusal(db, projectId, payload, notification, targetDigest,
14078
14512
  }
14079
14513
  function cancelReviewDelivery(db, id, reason) {
14080
14514
  db.transaction((tx) => {
14081
- const canceled = tx.update(reviewWebhookDeliveries).set({ state: ReviewDeliveryStates.Canceled, retryAt: null }).where(and18(
14082
- eq21(reviewWebhookDeliveries.id, id),
14083
- eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending)
14515
+ const canceled = tx.update(reviewWebhookDeliveries).set({ state: ReviewDeliveryStates.Canceled, retryAt: null }).where(and19(
14516
+ eq22(reviewWebhookDeliveries.id, id),
14517
+ eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending)
14084
14518
  )).returning({ projectId: reviewWebhookDeliveries.projectId, eventId: reviewWebhookDeliveries.eventId, notificationId: reviewWebhookDeliveries.notificationId, targetDigest: reviewWebhookDeliveries.targetDigest }).get();
14085
14519
  if (canceled) writeAuditLog(tx, {
14086
14520
  projectId: canceled.projectId,
@@ -14096,10 +14530,10 @@ function deferReviewDelivery(db, saved, now) {
14096
14530
  if (saved.state !== ReviewDeliveryStates.Pending && saved.state !== ReviewDeliveryStates.Attempting) return;
14097
14531
  const waited = now - Date.parse(saved.createdAt);
14098
14532
  const delay = Math.min(Math.max(RETRY_BASE_MS, Number.isFinite(waited) ? waited / 4 : RETRY_BASE_MS), RETRY_MAX_MS);
14099
- db.update(reviewWebhookDeliveries).set({ retryAt: new Date(now + delay).toISOString() }).where(and18(
14100
- eq21(reviewWebhookDeliveries.id, saved.id),
14101
- eq21(reviewWebhookDeliveries.state, saved.state),
14102
- eq21(reviewWebhookDeliveries.attempts, saved.attempts)
14533
+ db.update(reviewWebhookDeliveries).set({ retryAt: new Date(now + delay).toISOString() }).where(and19(
14534
+ eq22(reviewWebhookDeliveries.id, saved.id),
14535
+ eq22(reviewWebhookDeliveries.state, saved.state),
14536
+ eq22(reviewWebhookDeliveries.attempts, saved.attempts)
14103
14537
  )).run();
14104
14538
  }
14105
14539
  function auditUndelivered(db, saved, outcome) {
@@ -14116,8 +14550,8 @@ function dueReviewDeliveryProjects(db, now) {
14116
14550
  const at = new Date(now).toISOString();
14117
14551
  const staleAttempt = new Date(now - 6e4).toISOString();
14118
14552
  return db.selectDistinct({ projectId: reviewWebhookDeliveries.projectId }).from(reviewWebhookDeliveries).where(or(
14119
- and18(eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending), or(isNull3(reviewWebhookDeliveries.retryAt), lte2(reviewWebhookDeliveries.retryAt, at))),
14120
- and18(eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14553
+ and19(eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending), or(isNull3(reviewWebhookDeliveries.retryAt), lte2(reviewWebhookDeliveries.retryAt, at))),
14554
+ and19(eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14121
14555
  )).all().map((row) => row.projectId);
14122
14556
  }
14123
14557
  function startReviewDeliveryLoop(drain, intervalMs = REVIEW_DELIVERY_INTERVAL_MS) {
@@ -14126,7 +14560,7 @@ function startReviewDeliveryLoop(drain, intervalMs = REVIEW_DELIVERY_INTERVAL_MS
14126
14560
  if (running) return;
14127
14561
  running = true;
14128
14562
  void drain().catch((err) => {
14129
- log19.warn("review-delivery.drain-failed", { error: describeError(err) });
14563
+ log20.warn("review-delivery.drain-failed", { error: describeError(err) });
14130
14564
  }).finally(() => {
14131
14565
  running = false;
14132
14566
  });
@@ -14138,12 +14572,12 @@ async function drainReviewDeliveries(options) {
14138
14572
  const now = (options.now ?? Date.now)();
14139
14573
  const at = new Date(now).toISOString();
14140
14574
  const staleAttempt = new Date(now - 6e4).toISOString();
14141
- const ready = options.db.select().from(reviewWebhookDeliveries).where(and18(
14142
- eq21(reviewWebhookDeliveries.projectId, options.projectId),
14575
+ const ready = options.db.select().from(reviewWebhookDeliveries).where(and19(
14576
+ eq22(reviewWebhookDeliveries.projectId, options.projectId),
14143
14577
  or(isNull3(reviewWebhookDeliveries.retryAt), lte2(reviewWebhookDeliveries.retryAt, at)),
14144
14578
  or(
14145
- eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14146
- and18(eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14579
+ eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14580
+ and19(eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting), lte2(reviewWebhookDeliveries.attemptedAt, staleAttempt))
14147
14581
  )
14148
14582
  )).orderBy(asc4(reviewWebhookDeliveries.retryAt), asc4(reviewWebhookDeliveries.attemptedAt), asc4(reviewWebhookDeliveries.createdAt), asc4(reviewWebhookDeliveries.id)).limit(100).all();
14149
14583
  let delivered = 0;
@@ -14167,15 +14601,15 @@ async function deliverReviewEvent(options) {
14167
14601
  if (!payload.eventId || !/^evt_[a-f0-9]{64}$/u.test(payload.eventId) || payload.reviews.length !== 1)
14168
14602
  return false;
14169
14603
  const notification = db.select().from(notifications).where(
14170
- and18(
14171
- eq21(notifications.id, notificationId),
14172
- eq21(notifications.projectId, projectId)
14604
+ and19(
14605
+ eq22(notifications.id, notificationId),
14606
+ eq22(notifications.projectId, projectId)
14173
14607
  )
14174
14608
  ).get();
14175
14609
  const id = hash([projectId, notificationId, payload.eventId]);
14176
14610
  const now = options.now ?? Date.now;
14177
14611
  const at = new Date(now()).toISOString();
14178
- let saved = db.select().from(reviewWebhookDeliveries).where(eq21(reviewWebhookDeliveries.id, id)).get();
14612
+ let saved = db.select().from(reviewWebhookDeliveries).where(eq22(reviewWebhookDeliveries.id, id)).get();
14179
14613
  const event = saved?.payload ?? payload;
14180
14614
  const refusal = reviewEventRefusal(db, projectId, event, notification, saved?.targetDigest, new Date(now()));
14181
14615
  if (refusal) {
@@ -14194,7 +14628,7 @@ async function deliverReviewEvent(options) {
14194
14628
  payload,
14195
14629
  createdAt: at
14196
14630
  }).onConflictDoNothing().run();
14197
- saved = db.select().from(reviewWebhookDeliveries).where(eq21(reviewWebhookDeliveries.id, id)).get();
14631
+ saved = db.select().from(reviewWebhookDeliveries).where(eq22(reviewWebhookDeliveries.id, id)).get();
14198
14632
  if (!saved || saved.targetDigest !== targetDigest) return false;
14199
14633
  if (saved.state === ReviewDeliveryStates.Sent) return true;
14200
14634
  if (saved.state === ReviewDeliveryStates.Uncertain || saved.state === ReviewDeliveryStates.Canceled) return false;
@@ -14209,17 +14643,17 @@ async function deliverReviewEvent(options) {
14209
14643
  return false;
14210
14644
  const state2 = destination.signed ? ReviewDeliveryStates.Pending : ReviewDeliveryStates.Uncertain;
14211
14645
  const moved = db.update(reviewWebhookDeliveries).set({ state: state2 }).where(
14212
- and18(
14213
- eq21(reviewWebhookDeliveries.id, id),
14214
- eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14215
- eq21(reviewWebhookDeliveries.attempts, saved.attempts),
14216
- eq21(reviewWebhookDeliveries.attemptedAt, saved.attemptedAt)
14646
+ and19(
14647
+ eq22(reviewWebhookDeliveries.id, id),
14648
+ eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14649
+ eq22(reviewWebhookDeliveries.attempts, saved.attempts),
14650
+ eq22(reviewWebhookDeliveries.attemptedAt, saved.attemptedAt)
14217
14651
  )
14218
14652
  ).returning({ id: reviewWebhookDeliveries.id }).get();
14219
14653
  if (moved && state2 === ReviewDeliveryStates.Uncertain) {
14220
14654
  auditUndelivered(db, saved, { state: state2, reason: "interrupted_attempt", attempts: saved.attempts });
14221
14655
  }
14222
- saved = db.select().from(reviewWebhookDeliveries).where(eq21(reviewWebhookDeliveries.id, id)).get();
14656
+ saved = db.select().from(reviewWebhookDeliveries).where(eq22(reviewWebhookDeliveries.id, id)).get();
14223
14657
  if (!saved || saved.state !== ReviewDeliveryStates.Pending) return false;
14224
14658
  }
14225
14659
  if (saved.retryAt && Date.parse(saved.retryAt) > now()) return false;
@@ -14241,9 +14675,9 @@ async function deliverReviewEvent(options) {
14241
14675
  return false;
14242
14676
  }
14243
14677
  const latest = db.select().from(notifications).where(
14244
- and18(
14245
- eq21(notifications.id, notificationId),
14246
- eq21(notifications.projectId, projectId)
14678
+ and19(
14679
+ eq22(notifications.id, notificationId),
14680
+ eq22(notifications.projectId, projectId)
14247
14681
  )
14248
14682
  ).get();
14249
14683
  const latestRefusal = reviewEventRefusal(db, projectId, saved.payload, latest, targetDigest, new Date(now()));
@@ -14256,10 +14690,10 @@ async function deliverReviewEvent(options) {
14256
14690
  attempts: saved.attempts + 1,
14257
14691
  attemptedAt: new Date(now()).toISOString()
14258
14692
  }).where(
14259
- and18(
14260
- eq21(reviewWebhookDeliveries.id, id),
14261
- eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14262
- eq21(reviewWebhookDeliveries.attempts, saved.attempts)
14693
+ and19(
14694
+ eq22(reviewWebhookDeliveries.id, id),
14695
+ eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Pending),
14696
+ eq22(reviewWebhookDeliveries.attempts, saved.attempts)
14263
14697
  )
14264
14698
  ).returning({ id: reviewWebhookDeliveries.id }).get();
14265
14699
  if (!claimed) return false;
@@ -14286,10 +14720,10 @@ async function deliverReviewEvent(options) {
14286
14720
  deliveredAt: sent ? new Date(now()).toISOString() : null,
14287
14721
  retryAt: state === ReviewDeliveryStates.Pending ? new Date(now() + retryDelayMs(attempts, result.retryAfterSeconds)).toISOString() : null
14288
14722
  }).where(
14289
- and18(
14290
- eq21(reviewWebhookDeliveries.id, id),
14291
- eq21(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14292
- eq21(reviewWebhookDeliveries.attempts, attempts)
14723
+ and19(
14724
+ eq22(reviewWebhookDeliveries.id, id),
14725
+ eq22(reviewWebhookDeliveries.state, ReviewDeliveryStates.Attempting),
14726
+ eq22(reviewWebhookDeliveries.attempts, attempts)
14293
14727
  )
14294
14728
  ).run();
14295
14729
  if (state === ReviewDeliveryStates.Uncertain || state === ReviewDeliveryStates.Failed) {
@@ -14301,8 +14735,8 @@ async function deliverReviewEvent(options) {
14301
14735
  }
14302
14736
 
14303
14737
  // src/unrecognized-agent-alerts.ts
14304
- import { and as and19, desc as desc8, eq as eq22, gte as gte3, sql as sql9 } from "drizzle-orm";
14305
- var log20 = createLogger("UnrecognizedAgentAlerts");
14738
+ import { and as and20, desc as desc8, eq as eq23, gte as gte3, sql as sql10 } from "drizzle-orm";
14739
+ var log21 = createLogger("UnrecognizedAgentAlerts");
14306
14740
  var UNRECOGNIZED_AGENT_ALERT_WINDOW_MS = 7 * 864e5;
14307
14741
  var UNRECOGNIZED_AGENT_ALERT_MIN_HITS = 3;
14308
14742
  var UNRECOGNIZED_AGENT_ALERT_INTERVAL_MS = 15 * 6e4;
@@ -14317,13 +14751,13 @@ function projectsWithRecentUnrecognizedAgents(db, now) {
14317
14751
  return db.selectDistinct({ projectId: unrecognizedAgentEventsHourly.projectId }).from(unrecognizedAgentEventsHourly).where(gte3(unrecognizedAgentEventsHourly.tsHour, since)).all().map((row) => row.projectId);
14318
14752
  }
14319
14753
  function undecidedUnrecognizedAgents(db, projectId, now) {
14320
- const decided = new Set(db.select({ agentName: unrecognizedAgentNotifyState.agentName }).from(unrecognizedAgentNotifyState).where(eq22(unrecognizedAgentNotifyState.projectId, projectId)).all().map((row) => row.agentName));
14754
+ const decided = new Set(db.select({ agentName: unrecognizedAgentNotifyState.agentName }).from(unrecognizedAgentNotifyState).where(eq23(unrecognizedAgentNotifyState.projectId, projectId)).all().map((row) => row.agentName));
14321
14755
  const totals = db.select({
14322
14756
  name: unrecognizedAgentEventsHourly.agentName,
14323
- hits: sql9`SUM(${unrecognizedAgentEventsHourly.hits})`,
14324
- firstSeenAt: sql9`MIN(${unrecognizedAgentEventsHourly.tsHour})`,
14325
- lastSeenAt: sql9`MAX(${unrecognizedAgentEventsHourly.tsHour})`
14326
- }).from(unrecognizedAgentEventsHourly).where(eq22(unrecognizedAgentEventsHourly.projectId, projectId)).groupBy(unrecognizedAgentEventsHourly.agentName).all();
14757
+ hits: sql10`SUM(${unrecognizedAgentEventsHourly.hits})`,
14758
+ firstSeenAt: sql10`MIN(${unrecognizedAgentEventsHourly.tsHour})`,
14759
+ lastSeenAt: sql10`MAX(${unrecognizedAgentEventsHourly.tsHour})`
14760
+ }).from(unrecognizedAgentEventsHourly).where(eq23(unrecognizedAgentEventsHourly.projectId, projectId)).groupBy(unrecognizedAgentEventsHourly.agentName).all();
14327
14761
  const windowStart = new Date(now - UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
14328
14762
  const recordingStart = totals.reduce((earliest, total) => earliest === null || total.firstSeenAt < earliest ? total.firstSeenAt : earliest, null);
14329
14763
  const baselineEnd = recordingStart === null ? null : new Date(Date.parse(recordingStart) + UNRECOGNIZED_AGENT_ALERT_WINDOW_MS).toISOString();
@@ -14341,7 +14775,7 @@ function undecidedUnrecognizedAgents(db, projectId, now) {
14341
14775
  continue;
14342
14776
  }
14343
14777
  if (Number(total.hits) < UNRECOGNIZED_AGENT_ALERT_MIN_HITS) continue;
14344
- const latest = db.select({ sampledUserAgent: unrecognizedAgentEventsHourly.sampledUserAgent, sampledPath: unrecognizedAgentEventsHourly.sampledPath }).from(unrecognizedAgentEventsHourly).where(and19(eq22(unrecognizedAgentEventsHourly.projectId, projectId), eq22(unrecognizedAgentEventsHourly.agentName, total.name))).orderBy(desc8(unrecognizedAgentEventsHourly.tsHour)).limit(1).get();
14778
+ const latest = db.select({ sampledUserAgent: unrecognizedAgentEventsHourly.sampledUserAgent, sampledPath: unrecognizedAgentEventsHourly.sampledPath }).from(unrecognizedAgentEventsHourly).where(and20(eq23(unrecognizedAgentEventsHourly.projectId, projectId), eq23(unrecognizedAgentEventsHourly.agentName, total.name))).orderBy(desc8(unrecognizedAgentEventsHourly.tsHour)).limit(1).get();
14345
14779
  due.push({
14346
14780
  name: total.name,
14347
14781
  hits: Number(total.hits),
@@ -14366,7 +14800,7 @@ function startUnrecognizedAgentAlertLoop(dispatch, intervalMs = UNRECOGNIZED_AGE
14366
14800
  if (running) return;
14367
14801
  running = true;
14368
14802
  void dispatch().catch((err) => {
14369
- log20.warn("unrecognized-agent-alerts.dispatch-failed", { error: describeError(err) });
14803
+ log21.warn("unrecognized-agent-alerts.dispatch-failed", { error: describeError(err) });
14370
14804
  }).finally(() => {
14371
14805
  running = false;
14372
14806
  });
@@ -14376,8 +14810,8 @@ function startUnrecognizedAgentAlertLoop(dispatch, intervalMs = UNRECOGNIZED_AGE
14376
14810
  }
14377
14811
 
14378
14812
  // src/provider-batch-poller.ts
14379
- import { and as and20, eq as eq23, inArray as inArray11, isNotNull as isNotNull3, isNull as isNull4, notExists as notExists2, sql as sql10 } from "drizzle-orm";
14380
- var log21 = createLogger("ProviderBatchPoller");
14813
+ import { and as and21, eq as eq24, inArray as inArray11, isNotNull as isNotNull3, isNull as isNull4, notExists as notExists2, sql as sql11 } from "drizzle-orm";
14814
+ var log22 = createLogger("ProviderBatchPoller");
14381
14815
  var PROVIDER_BATCH_POLL_INITIAL_MS = 3e4;
14382
14816
  var PROVIDER_BATCH_POLL_MAX_MS = 10 * 6e4;
14383
14817
  var PROVIDER_BATCH_CANCEL_GRACE_MS = 60 * 6e4;
@@ -14401,7 +14835,7 @@ var ProviderBatchPoller = class {
14401
14835
  try {
14402
14836
  if (await this.advance(row)) settledRuns.set(row.runId, row.projectId);
14403
14837
  } catch (err) {
14404
- log21.warn("batch.advance-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14838
+ log22.warn("batch.advance-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14405
14839
  this.reschedule(row.id, null);
14406
14840
  }
14407
14841
  }
@@ -14410,7 +14844,7 @@ var ProviderBatchPoller = class {
14410
14844
  try {
14411
14845
  this.deps.runner.finalizeBatchRun(runId, projectId);
14412
14846
  } catch (err) {
14413
- log21.warn("batch.finalize-failed", { runId, error: describeError(err) });
14847
+ log22.warn("batch.finalize-failed", { runId, error: describeError(err) });
14414
14848
  }
14415
14849
  }
14416
14850
  }
@@ -14419,7 +14853,7 @@ var ProviderBatchPoller = class {
14419
14853
  * outstanding (ingested or cancelled), so its run may now finalize.
14420
14854
  */
14421
14855
  async advance(row) {
14422
- const runStatus = this.deps.db.select({ status: runs.status }).from(runs).where(eq23(runs.id, row.runId)).get()?.status;
14856
+ const runStatus = this.deps.db.select({ status: runs.status }).from(runs).where(eq24(runs.id, row.runId)).get()?.status;
14423
14857
  if (runStatus !== RunStatuses.running) {
14424
14858
  await this.deps.runner.abandonProviderBatches([row.id], "Cancelled because its run is no longer running.");
14425
14859
  this.schedule.delete(row.id);
@@ -14436,15 +14870,15 @@ var ProviderBatchPoller = class {
14436
14870
  status = poll.status;
14437
14871
  if (poll.status === "ended") {
14438
14872
  const endedAt = poll.endedAt ?? new Date(this.now()).toISOString();
14439
- const ended = this.deps.db.update(providerBatches).set({ status: ProviderBatchStatuses.ended, endedAt, resultsExpireAt: poll.resultsExpireAt ?? null, updatedAt: endedAt }).where(and20(eq23(providerBatches.id, row.id), eq23(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14873
+ const ended = this.deps.db.update(providerBatches).set({ status: ProviderBatchStatuses.ended, endedAt, resultsExpireAt: poll.resultsExpireAt ?? null, updatedAt: endedAt }).where(and21(eq24(providerBatches.id, row.id), eq24(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14440
14874
  this.schedule.delete(row.id);
14441
14875
  return ended ? this.ingest({ ...row, status: ProviderBatchStatuses.ended, resultsExpireAt: poll.resultsExpireAt ?? null }) : false;
14442
14876
  }
14443
14877
  } catch (err) {
14444
- log21.warn("batch.poll-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14878
+ log22.warn("batch.poll-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error: describeError(err) });
14445
14879
  }
14446
14880
  } else {
14447
- log21.warn("batch.poll-unavailable", { batchId: row.id, runId: row.runId, provider: row.provider });
14881
+ log22.warn("batch.poll-unavailable", { batchId: row.id, runId: row.runId, provider: row.provider });
14448
14882
  }
14449
14883
  this.reschedule(row.id, status);
14450
14884
  return this.enforceDeadline(row, registered);
@@ -14460,18 +14894,18 @@ var ProviderBatchPoller = class {
14460
14894
  const at = new Date(now).toISOString();
14461
14895
  if (row.cancelRequestedAt === null) {
14462
14896
  if (now < Date.parse(row.deadlineAt)) return false;
14463
- const claimed = this.deps.db.update(providerBatches).set({ cancelRequestedAt: at, updatedAt: at }).where(and20(
14464
- eq23(providerBatches.id, row.id),
14465
- eq23(providerBatches.status, ProviderBatchStatuses.submitted),
14897
+ const claimed = this.deps.db.update(providerBatches).set({ cancelRequestedAt: at, updatedAt: at }).where(and21(
14898
+ eq24(providerBatches.id, row.id),
14899
+ eq24(providerBatches.status, ProviderBatchStatuses.submitted),
14466
14900
  isNull4(providerBatches.cancelRequestedAt)
14467
14901
  )).run().changes === 1;
14468
14902
  if (!claimed) return false;
14469
- log21.warn("batch.deadline-passed", { batchId: row.id, runId: row.runId, provider: row.provider, deadlineAt: row.deadlineAt });
14903
+ log22.warn("batch.deadline-passed", { batchId: row.id, runId: row.runId, provider: row.provider, deadlineAt: row.deadlineAt });
14470
14904
  if (registered?.adapter.batch && row.providerBatchId) {
14471
14905
  try {
14472
14906
  await registered.adapter.batch.cancel(row.providerBatchId, registered.config);
14473
14907
  } catch (err) {
14474
- log21.warn("batch.cancel-failed", { batchId: row.id, provider: row.provider, error: describeError(err) });
14908
+ log22.warn("batch.cancel-failed", { batchId: row.id, provider: row.provider, error: describeError(err) });
14475
14909
  }
14476
14910
  }
14477
14911
  return false;
@@ -14481,9 +14915,9 @@ var ProviderBatchPoller = class {
14481
14915
  status: ProviderBatchStatuses.cancelled,
14482
14916
  error: `The provider batch did not end within an hour of being cancelled at its deadline; its ${row.requestCount} answer(s) were not recorded.`,
14483
14917
  updatedAt: at
14484
- }).where(and20(eq23(providerBatches.id, row.id), eq23(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14918
+ }).where(and21(eq24(providerBatches.id, row.id), eq24(providerBatches.status, ProviderBatchStatuses.submitted))).run().changes === 1;
14485
14919
  this.schedule.delete(row.id);
14486
- if (gaveUp) log21.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
14920
+ if (gaveUp) log22.warn("batch.abandoned-after-deadline", { batchId: row.id, runId: row.runId, provider: row.provider });
14487
14921
  return gaveUp;
14488
14922
  }
14489
14923
  async ingest(row) {
@@ -14492,7 +14926,7 @@ var ProviderBatchPoller = class {
14492
14926
  return result.kind !== "skipped";
14493
14927
  } catch (err) {
14494
14928
  const error = describeError(err);
14495
- log21.warn("batch.ingest-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error });
14929
+ log22.warn("batch.ingest-failed", { batchId: row.id, runId: row.runId, provider: row.provider, error });
14496
14930
  const now = this.now();
14497
14931
  const unreadable = row.resultsExpireAt !== null && now >= Date.parse(row.resultsExpireAt);
14498
14932
  if (unreadable || now >= Date.parse(row.deadlineAt) + PROVIDER_BATCH_CANCEL_GRACE_MS) {
@@ -14521,10 +14955,10 @@ var ProviderBatchPoller = class {
14521
14955
  /** Runs the sweep handed off whose batches have all settled. */
14522
14956
  finalizableRuns() {
14523
14957
  const holding = [ProviderBatchStatuses.submitting, ...OUTSTANDING_PROVIDER_BATCH_STATUSES];
14524
- return this.deps.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and20(
14525
- eq23(runs.status, RunStatuses.running),
14958
+ return this.deps.db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and21(
14959
+ eq24(runs.status, RunStatuses.running),
14526
14960
  isNotNull3(runs.pendingProviderErrors),
14527
- notExists2(this.deps.db.select({ one: sql10`1` }).from(providerBatches).where(and20(eq23(providerBatches.runId, runs.id), inArray11(providerBatches.status, holding))))
14961
+ notExists2(this.deps.db.select({ one: sql11`1` }).from(providerBatches).where(and21(eq24(providerBatches.runId, runs.id), inArray11(providerBatches.status, holding))))
14528
14962
  )).all();
14529
14963
  }
14530
14964
  };
@@ -14534,7 +14968,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
14534
14968
  const timer = setInterval(() => {
14535
14969
  if (running) return;
14536
14970
  running = true;
14537
- void poller.tick().catch((err) => log21.warn("batch.pass-failed", { error: describeError(err) })).finally(() => {
14971
+ void poller.tick().catch((err) => log22.warn("batch.pass-failed", { error: describeError(err) })).finally(() => {
14538
14972
  running = false;
14539
14973
  });
14540
14974
  }, intervalMs);
@@ -14543,7 +14977,7 @@ function startProviderBatchPoller(deps, intervalMs = PROVIDER_BATCH_TICK_MS) {
14543
14977
  }
14544
14978
 
14545
14979
  // src/data-refresh.ts
14546
- var log22 = createLogger("DataRefresh");
14980
+ var log23 = createLogger("DataRefresh");
14547
14981
  function gaRefreshDays(now = /* @__PURE__ */ new Date()) {
14548
14982
  const [reportMonth] = reportMonthsForDoctor(void 0, now);
14549
14983
  const reach = inclusiveDayCount(`${reportMonth}-01`, now.toISOString().slice(0, 10)) ?? 0;
@@ -14562,6 +14996,7 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
14562
14996
  const integrations = [
14563
14997
  { name: "gsc", kind: RunKinds["gsc-sync"], completion: "queued", run: () => client.gscSync(projectName, {}) },
14564
14998
  { name: "bing", kind: RunKinds["bing-inspect-sitemap"], completion: "queued", run: () => client.bingInspectSitemap(projectName, {}) },
14999
+ { name: "bing-sync", kind: RunKinds["bing-sync"], completion: "queued", run: () => client.bingSync(projectName) },
14565
15000
  { name: "ga", kind: RunKinds["ga-sync"], completion: "synchronous", run: () => client.gaSync(projectName, { days: gaRefreshDays(now) }) },
14566
15001
  { name: "gbp", kind: RunKinds["gbp-sync"], completion: "queued", run: () => client.triggerGbpSync(projectName, {}) },
14567
15002
  // `ads` remains the OpenAI / ChatGPT Ads integration. For scheduled
@@ -14584,15 +15019,15 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
14584
15019
  if (result.status === "rejected") {
14585
15020
  const reason = result.reason;
14586
15021
  const message = describeError(reason);
14587
- log22.warn("integration.refresh-failed", { projectName, integration, error: message });
15022
+ log23.warn("integration.refresh-failed", { projectName, integration, error: message });
14588
15023
  return;
14589
15024
  }
14590
15025
  switch (completion) {
14591
15026
  case "synchronous":
14592
- log22.info("integration.refreshed", { projectName, integration });
15027
+ log23.info("integration.refreshed", { projectName, integration });
14593
15028
  return;
14594
15029
  case "queued":
14595
- log22.info("integration.queued", { projectName, integration, ...queuedRun(result.value) });
15030
+ log23.info("integration.queued", { projectName, integration, ...queuedRun(result.value) });
14596
15031
  return;
14597
15032
  default: {
14598
15033
  const _exhaustive = completion;
@@ -14603,9 +15038,9 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
14603
15038
  }
14604
15039
 
14605
15040
  // src/notifier.ts
14606
- import { eq as eq24, desc as desc9, and as and21, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
14607
- import crypto21 from "crypto";
14608
- var log23 = createLogger("Notifier");
15041
+ import { eq as eq25, desc as desc9, and as and22, inArray as inArray12, or as or2, isNull as isNull5 } from "drizzle-orm";
15042
+ import crypto22 from "crypto";
15043
+ var log24 = createLogger("Notifier");
14609
15044
  var sampleDelivery = createOutcomeSampler({ burst: 20, refillMs: 6e4, now: () => Date.now() });
14610
15045
  function trackWebhookDelivery(input) {
14611
15046
  const sample = sampleDelivery(input.failure ? "failed" : "succeeded");
@@ -14725,26 +15160,26 @@ var Notifier = class {
14725
15160
  }
14726
15161
  /** Called after a run completes (success, partial, or failed). */
14727
15162
  async onRunCompleted(runId, projectId, opts) {
14728
- log23.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
14729
- const notifs = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15163
+ log24.info("run.completed", { runId, projectId, origin: opts?.origin ?? "sweep" });
15164
+ const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled);
14730
15165
  if (notifs.length === 0) {
14731
- log23.info("notifications.none-enabled", { projectId });
15166
+ log24.info("notifications.none-enabled", { projectId });
14732
15167
  return;
14733
15168
  }
14734
- log23.info("notifications.found", { projectId, count: notifs.length });
14735
- const run = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
15169
+ log24.info("notifications.found", { projectId, count: notifs.length });
15170
+ const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
14736
15171
  if (!run) {
14737
- log23.error("run.not-found", { runId, msg: "skipping notification dispatch" });
15172
+ log24.error("run.not-found", { runId, msg: "skipping notification dispatch" });
14738
15173
  return;
14739
15174
  }
14740
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15175
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
14741
15176
  if (!project) {
14742
- log23.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
15177
+ log24.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
14743
15178
  return;
14744
15179
  }
14745
15180
  const transitions = this.computeTransitions(runId, projectId);
14746
15181
  const events = [];
14747
- log23.info("run.status", { runId: run.id, status: run.status, projectId });
15182
+ log24.info("run.status", { runId: run.id, status: run.status, projectId });
14748
15183
  if ((run.status === "completed" || run.status === "partial") && opts?.origin !== "fill") {
14749
15184
  events.push("run.completed");
14750
15185
  }
@@ -14760,7 +15195,7 @@ var Notifier = class {
14760
15195
  if (!config.url) continue;
14761
15196
  const subscribedEvents = config.events;
14762
15197
  const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
14763
- log23.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
15198
+ log24.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
14764
15199
  if (matchingEvents.length === 0) continue;
14765
15200
  for (const event of matchingEvents) {
14766
15201
  const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
@@ -14816,9 +15251,9 @@ var Notifier = class {
14816
15251
  * than inferred from the absence of a webhook.
14817
15252
  */
14818
15253
  async onHealthChecked(projectId, report) {
14819
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15254
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
14820
15255
  if (!project) {
14821
- log23.error("project.not-found", { projectId, msg: "skipping health notification" });
15256
+ log24.error("project.not-found", { projectId, msg: "skipping health notification" });
14822
15257
  return null;
14823
15258
  }
14824
15259
  const rank = healthRank;
@@ -14849,7 +15284,7 @@ var Notifier = class {
14849
15284
  const status = noSignal ? "warn" : worst ? worst.status : "ok";
14850
15285
  const code = noSignal ? HEALTH_NO_SIGNAL_CODE : worst?.code ?? "health.ok";
14851
15286
  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.`;
14852
- const previous = this.db.select().from(doctorHealthState).where(eq24(doctorHealthState.projectId, projectId)).get();
15287
+ const previous = this.db.select().from(doctorHealthState).where(eq25(doctorHealthState.projectId, projectId)).get();
14853
15288
  const previousStatus = previous?.status ?? null;
14854
15289
  const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
14855
15290
  const failingNow = failing.map((c) => ({ id: c.id, code: c.code, rank: rank(c.status) }));
@@ -14919,7 +15354,7 @@ var Notifier = class {
14919
15354
  const urgentById = new Map(urgentItems.map((item) => [item.id, item]));
14920
15355
  const generalEvent = event;
14921
15356
  const urgentRecord = previous?.urgentNotified ?? null;
14922
- const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15357
+ const enabled = generalEvent || urgentItems.length > 0 || urgentRecord ? this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
14923
15358
  const listedEvents = (notif) => notif.config.events;
14924
15359
  const hasUrl = (notif) => Boolean(notif.config.url);
14925
15360
  const urgentSubscribers = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -14967,7 +15402,7 @@ var Notifier = class {
14967
15402
  if (previous === void 0) {
14968
15403
  this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
14969
15404
  } else {
14970
- this.db.update(doctorHealthState).set(observation).where(eq24(doctorHealthState.projectId, projectId)).run();
15405
+ this.db.update(doctorHealthState).set(observation).where(eq25(doctorHealthState.projectId, projectId)).run();
14971
15406
  }
14972
15407
  for (const group of urgentGroups) {
14973
15408
  const { urgentEvent, urgentTold, urgentNew, urgentCleared, urgentNotifs, urgentSettled } = group;
@@ -15011,10 +15446,10 @@ var Notifier = class {
15011
15446
  }
15012
15447
  if (accepted > 0) {
15013
15448
  recordUrgentReceipts(urgentReceipts, group.subscribers, formatUrgentBreaches(urgentSettled) || null);
15014
- this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq24(doctorHealthState.projectId, projectId)).run();
15449
+ this.db.update(doctorHealthState).set({ urgentNotified: doctorUrgentSummary(urgentReceipts), urgentReceipts }).where(eq25(doctorHealthState.projectId, projectId)).run();
15015
15450
  }
15016
15451
  if (urgentNotifs.length > 0) {
15017
- log23.info("health.urgent-notified", {
15452
+ log24.info("health.urgent-notified", {
15018
15453
  projectId,
15019
15454
  event: urgentEvent,
15020
15455
  codes: urgentEvent === "health.urgent" ? breachCodes(urgentNew) : breachCodes(urgentCleared),
@@ -15025,9 +15460,9 @@ var Notifier = class {
15025
15460
  }
15026
15461
  if (!event) {
15027
15462
  if (clearedThisPass.length > 0 || easedThisPass.length > 0) {
15028
- log23.info("health.improved", { projectId, status, code, cleared: clearedThisPass, eased: easedThisPass });
15463
+ log24.info("health.improved", { projectId, status, code, cleared: clearedThisPass, eased: easedThisPass });
15029
15464
  } else {
15030
- log23.info("health.unchanged", { projectId, status, code });
15465
+ log24.info("health.unchanged", { projectId, status, code });
15031
15466
  }
15032
15467
  return null;
15033
15468
  }
@@ -15055,9 +15490,9 @@ var Notifier = class {
15055
15490
  if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
15056
15491
  }
15057
15492
  if (delivered > 0) {
15058
- this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq24(doctorHealthState.projectId, projectId)).run();
15493
+ this.db.update(doctorHealthState).set({ notifiedAt: now, notifiedSignature: formatBreaches(failingNow), pendingSignature: null }).where(eq25(doctorHealthState.projectId, projectId)).run();
15059
15494
  }
15060
- log23.info("health.notified", {
15495
+ log24.info("health.notified", {
15061
15496
  projectId,
15062
15497
  event,
15063
15498
  status,
@@ -15085,12 +15520,12 @@ var Notifier = class {
15085
15520
  async onSiteLivenessChecked(projectId, result) {
15086
15521
  const { check, checkedAt } = result;
15087
15522
  if (check.status !== "ok" && check.status !== "fail") return null;
15088
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15523
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15089
15524
  if (!project) {
15090
- log23.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15525
+ log24.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
15091
15526
  return null;
15092
15527
  }
15093
- const previous = this.db.select().from(siteLivenessState).where(eq24(siteLivenessState.projectId, projectId)).get();
15528
+ const previous = this.db.select().from(siteLivenessState).where(eq25(siteLivenessState.projectId, projectId)).get();
15094
15529
  const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
15095
15530
  const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
15096
15531
  const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
@@ -15100,7 +15535,7 @@ var Notifier = class {
15100
15535
  if (check.status === "ok" && outagePaged) event = "health.recovered";
15101
15536
  const confirmed = check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE;
15102
15537
  const urgentRecord = previous?.urgentNotifiedAt ?? null;
15103
- const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15538
+ const enabled = event !== null || confirmed || urgentRecord !== null ? this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled) : [];
15104
15539
  const listedEvents = (notif) => notif.config.events;
15105
15540
  const hasUrl = (notif) => Boolean(notif.config.url);
15106
15541
  const scoped = enabled.filter((n) => hasUrl(n) && (listedEvents(n) ?? []).some((e) => URGENT_HEALTH_EVENTS.has(e)));
@@ -15133,7 +15568,7 @@ var Notifier = class {
15133
15568
  if (previous === void 0) {
15134
15569
  this.db.insert(siteLivenessState).values(observation).run();
15135
15570
  } else {
15136
- this.db.update(siteLivenessState).set(observation).where(eq24(siteLivenessState.projectId, projectId)).run();
15571
+ this.db.update(siteLivenessState).set(observation).where(eq25(siteLivenessState.projectId, projectId)).run();
15137
15572
  }
15138
15573
  if (!event && urgentGroups.every((group) => group.urgentEvent === null)) return null;
15139
15574
  const payloadFor = (name) => {
@@ -15169,9 +15604,9 @@ var Notifier = class {
15169
15604
  }
15170
15605
  }
15171
15606
  if (event === "health.degraded" && delivered > 0) {
15172
- this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq24(siteLivenessState.projectId, projectId)).run();
15607
+ this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq25(siteLivenessState.projectId, projectId)).run();
15173
15608
  }
15174
- log23.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15609
+ log24.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
15175
15610
  }
15176
15611
  for (const group of urgentGroups) {
15177
15612
  const { urgentEvent, urgentNotifs, generalIds } = group;
@@ -15183,10 +15618,10 @@ var Notifier = class {
15183
15618
  }
15184
15619
  if (accepted > 0) {
15185
15620
  recordUrgentReceipts(urgentReceipts, group.subscribers, group.settled);
15186
- this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq24(siteLivenessState.projectId, projectId)).run();
15621
+ this.db.update(siteLivenessState).set({ urgentNotifiedAt: siteUrgentSummary(urgentReceipts), urgentReceipts }).where(eq25(siteLivenessState.projectId, projectId)).run();
15187
15622
  }
15188
15623
  if (urgentNotifs.length > 0 || generalIds.size > 0) {
15189
- log23.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
15624
+ log24.info("site-liveness.urgent-notified", { projectId, event: urgentEvent, code: check.code, subscribers: urgentNotifs.length + generalIds.size, delivered: accepted });
15190
15625
  }
15191
15626
  }
15192
15627
  return event;
@@ -15209,7 +15644,7 @@ var Notifier = class {
15209
15644
  */
15210
15645
  isNewInsightNews(projectId, insight) {
15211
15646
  const key = insightNotifyKey(projectId, insight);
15212
- const previous = this.db.select().from(insightNotifyState).where(eq24(insightNotifyState.key, key)).get();
15647
+ const previous = this.db.select().from(insightNotifyState).where(eq25(insightNotifyState.key, key)).get();
15213
15648
  if (!previous) return true;
15214
15649
  if (severityRank(insight.severity) > severityRank(previous.severity)) return true;
15215
15650
  const magnitude = insightMagnitude(insight);
@@ -15231,9 +15666,9 @@ var Notifier = class {
15231
15666
  magnitude: insightMagnitude(insight),
15232
15667
  notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
15233
15668
  };
15234
- const existing = this.db.select().from(insightNotifyState).where(eq24(insightNotifyState.key, key)).get();
15669
+ const existing = this.db.select().from(insightNotifyState).where(eq25(insightNotifyState.key, key)).get();
15235
15670
  if (existing) {
15236
- this.db.update(insightNotifyState).set(row).where(eq24(insightNotifyState.key, key)).run();
15671
+ this.db.update(insightNotifyState).set(row).where(eq25(insightNotifyState.key, key)).run();
15237
15672
  } else {
15238
15673
  this.db.insert(insightNotifyState).values(row).run();
15239
15674
  }
@@ -15247,11 +15682,11 @@ var Notifier = class {
15247
15682
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
15248
15683
  if (highInsights.length > 0) insightEvents.push("insight.high");
15249
15684
  if (insightEvents.length === 0) return;
15250
- const notifs = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15685
+ const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled);
15251
15686
  if (notifs.length === 0) return;
15252
- const run = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
15687
+ const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15253
15688
  if (!run) return;
15254
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15689
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15255
15690
  if (!project) return;
15256
15691
  for (const notif of notifs) {
15257
15692
  const config = notif.config;
@@ -15332,9 +15767,9 @@ var Notifier = class {
15332
15767
  return delivered;
15333
15768
  }
15334
15769
  async dispatchQueuedReviewAlerts(projectId) {
15335
- const pendingReviews = this.db.select().from(gbpReviews).where(and21(eq24(gbpReviews.projectId, projectId), eq24(gbpReviews.alertState, "pending"))).all();
15336
- const pendingRatings = this.db.select().from(gbpReviewRatings).where(and21(eq24(gbpReviewRatings.projectId, projectId), eq24(gbpReviewRatings.alertState, "pending"))).all();
15337
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15770
+ const pendingReviews = this.db.select().from(gbpReviews).where(and22(eq25(gbpReviews.projectId, projectId), eq25(gbpReviews.alertState, "pending"))).all();
15771
+ const pendingRatings = this.db.select().from(gbpReviewRatings).where(and22(eq25(gbpReviewRatings.projectId, projectId), eq25(gbpReviewRatings.alertState, "pending"))).all();
15772
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15338
15773
  if (!project) return { reviews: 0, ratingDrops: 0, delivered: 0 };
15339
15774
  const chatSendsLeft = /* @__PURE__ */ new Map();
15340
15775
  const now = /* @__PURE__ */ new Date();
@@ -15346,10 +15781,10 @@ var Notifier = class {
15346
15781
  displayName: gbpLocations.displayName,
15347
15782
  mapsUri: gbpLocations.mapsUri,
15348
15783
  reviewsAccess: gbpLocations.reviewsAccess
15349
- }).from(gbpLocations).where(eq24(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15784
+ }).from(gbpLocations).where(eq25(gbpLocations.projectId, projectId)).all().map((loc) => [loc.locationName, loc])
15350
15785
  );
15351
15786
  const v4HasWorked = new Set(
15352
- this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and21(eq24(gbpReviewRatings.projectId, projectId), eq24(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
15787
+ this.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and22(eq25(gbpReviewRatings.projectId, projectId), eq25(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
15353
15788
  );
15354
15789
  const coveredByBusinessProfile = (locationName) => businessProfileReviewsCover(locations.get(locationName)?.reviewsAccess ?? null, v4HasWorked.has(locationName));
15355
15790
  const locationRef = (locationName) => {
@@ -15378,7 +15813,7 @@ var Notifier = class {
15378
15813
  else if (row.rating === null || row.previousRating === null) setRatingState([row.id], "none");
15379
15814
  else ratingsToSend.push(row);
15380
15815
  }
15381
- const enabled = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15816
+ const enabled = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url);
15382
15817
  const subscribedTo = (event) => enabled.filter((n) => (n.config.events ?? []).includes(event));
15383
15818
  const projectRef = { name: project.name, canonicalDomain: project.canonicalDomain };
15384
15819
  const dashboardUrl = `${this.serverUrl}/projects/${project.name}`;
@@ -15432,13 +15867,13 @@ var Notifier = class {
15432
15867
  if (!sent) all = false;
15433
15868
  }
15434
15869
  if (all) {
15435
- this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and21(
15436
- eq24(gbpReviews.id, review.id),
15437
- eq24(gbpReviews.alertState, "pending"),
15438
- eq24(gbpReviews.updateTime, review.updateTime),
15439
- review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq24(gbpReviews.alertStateAt, review.alertStateAt),
15440
- review.comment === null ? isNull5(gbpReviews.comment) : eq24(gbpReviews.comment, review.comment),
15441
- review.starRating === null ? isNull5(gbpReviews.starRating) : eq24(gbpReviews.starRating, review.starRating)
15870
+ this.db.update(gbpReviews).set({ alertState: "sent", alertStateAt: nowIso }).where(and22(
15871
+ eq25(gbpReviews.id, review.id),
15872
+ eq25(gbpReviews.alertState, "pending"),
15873
+ eq25(gbpReviews.updateTime, review.updateTime),
15874
+ review.alertStateAt === null ? isNull5(gbpReviews.alertStateAt) : eq25(gbpReviews.alertStateAt, review.alertStateAt),
15875
+ review.comment === null ? isNull5(gbpReviews.comment) : eq25(gbpReviews.comment, review.comment),
15876
+ review.starRating === null ? isNull5(gbpReviews.starRating) : eq25(gbpReviews.starRating, review.starRating)
15442
15877
  )).run();
15443
15878
  }
15444
15879
  }
@@ -15470,7 +15905,7 @@ var Notifier = class {
15470
15905
  delivered += accepted;
15471
15906
  }
15472
15907
  }
15473
- log23.info("reviews.dispatched", {
15908
+ log24.info("reviews.dispatched", {
15474
15909
  projectId,
15475
15910
  reviews: reviewsToSend.length,
15476
15911
  ratingDrops: ratingsToSend.length,
@@ -15479,21 +15914,21 @@ var Notifier = class {
15479
15914
  return { reviews: reviewsToSend.length, ratingDrops: ratingsToSend.length, delivered };
15480
15915
  }
15481
15916
  computeTransitions(runId, projectId) {
15482
- const thisRun = this.db.select().from(runs).where(eq24(runs.id, runId)).get();
15917
+ const thisRun = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15483
15918
  if (!thisRun) return [];
15484
15919
  const completeness = measurementRunCompleteness(this.db, runId);
15485
15920
  if (completeness.planned && !completeness.complete) {
15486
- log23.info("transitions.skipped-incomplete", {
15921
+ log24.info("transitions.skipped-incomplete", {
15487
15922
  runId,
15488
15923
  executed: completeness.executed,
15489
15924
  expected: completeness.expected
15490
15925
  });
15491
15926
  return [];
15492
15927
  }
15493
- const groupSiblings = this.db.select().from(runs).where(and21(
15494
- eq24(runs.projectId, projectId),
15495
- eq24(runs.kind, thisRun.kind),
15496
- eq24(runs.createdAt, thisRun.createdAt),
15928
+ const groupSiblings = this.db.select().from(runs).where(and22(
15929
+ eq25(runs.projectId, projectId),
15930
+ eq25(runs.kind, thisRun.kind),
15931
+ eq25(runs.createdAt, thisRun.createdAt),
15497
15932
  notProbeRun()
15498
15933
  )).all();
15499
15934
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
@@ -15510,17 +15945,17 @@ var Notifier = class {
15510
15945
  return candidate.id > best.id ? candidate : best;
15511
15946
  });
15512
15947
  if (winner.id !== runId) return [];
15513
- const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq24(projects.id, projectId)).get();
15948
+ const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq25(projects.id, projectId)).get();
15514
15949
  const locationCount = Math.max(
15515
15950
  1,
15516
15951
  (projectLocations?.locations ?? []).length
15517
15952
  );
15518
15953
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
15519
15954
  const recentRuns = this.db.select().from(runs).where(
15520
- and21(
15521
- eq24(runs.projectId, projectId),
15522
- eq24(runs.kind, thisRun.kind),
15523
- or2(eq24(runs.status, "completed"), eq24(runs.status, "partial")),
15955
+ and22(
15956
+ eq25(runs.projectId, projectId),
15957
+ eq25(runs.kind, thisRun.kind),
15958
+ or2(eq25(runs.status, "completed"), eq25(runs.status, "partial")),
15524
15959
  notProbeRun()
15525
15960
  )
15526
15961
  ).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(RECENT_FETCH_LIMIT).all();
@@ -15538,7 +15973,7 @@ var Notifier = class {
15538
15973
  provider: querySnapshots.provider,
15539
15974
  location: querySnapshots.location,
15540
15975
  citationState: querySnapshots.citationState
15541
- }).from(querySnapshots).leftJoin(queries, eq24(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
15976
+ }).from(querySnapshots).leftJoin(queries, eq25(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
15542
15977
  const previousSnapshots = this.db.select({
15543
15978
  queryId: querySnapshots.queryId,
15544
15979
  provider: querySnapshots.provider,
@@ -15589,12 +16024,12 @@ var Notifier = class {
15589
16024
  recordUnrecognizedAgentDecisions(this.db, projectId, baseline, UnrecognizedAgentNotifyStates.baseline, now);
15590
16025
  if (due.length === 0) continue;
15591
16026
  const names = due.map((agent) => agent.name);
15592
- const subscribed = this.db.select().from(notifications).where(eq24(notifications.projectId, projectId)).all().filter((n) => n.enabled && n.config.url && n.config.events.includes("traffic.agent.discovered"));
16027
+ 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"));
15593
16028
  if (subscribed.length === 0) {
15594
16029
  recordUnrecognizedAgentDecisions(this.db, projectId, names, UnrecognizedAgentNotifyStates.skipped, now);
15595
16030
  continue;
15596
16031
  }
15597
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
16032
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15598
16033
  if (!project) continue;
15599
16034
  const payload = {
15600
16035
  source: "canonry",
@@ -15625,7 +16060,7 @@ var Notifier = class {
15625
16060
  const targetLabel = redactNotificationUrl(url).urlDisplay;
15626
16061
  const targetCheck = await resolveWebhookTarget(url, { allowLoopback: this.allowLoopbackWebhooks });
15627
16062
  if (!targetCheck.ok) {
15628
- log23.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
16063
+ log24.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
15629
16064
  this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
15630
16065
  const reasonCode = webhookTargetRefusalReason(targetCheck);
15631
16066
  trackWebhookDelivery({
@@ -15638,7 +16073,7 @@ var Notifier = class {
15638
16073
  });
15639
16074
  return false;
15640
16075
  }
15641
- log23.info("webhook.send", { event: payload.event, url: targetLabel });
16076
+ log24.info("webhook.send", { event: payload.event, url: targetLabel });
15642
16077
  const maxRetries = 3;
15643
16078
  const delays = [1e3, 4e3, 16e3];
15644
16079
  let lastStatusClass = "network";
@@ -15647,13 +16082,13 @@ var Notifier = class {
15647
16082
  try {
15648
16083
  const response = await deliverWebhook(targetCheck.target, body, signingSecret);
15649
16084
  if (response.status >= 200 && response.status < 300) {
15650
- log23.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
16085
+ log24.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
15651
16086
  this.logDelivery(projectId, notificationId, payload.event, "sent", null);
15652
16087
  trackWebhookDelivery({ url, event: payload.event, attempts: attempt + 1, durationBucket: elapsed(), statusClass: "2xx" });
15653
16088
  return true;
15654
16089
  }
15655
16090
  const errorDetail = response.error ?? `HTTP ${response.status}`;
15656
- log23.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
16091
+ log24.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
15657
16092
  lastStatusClass = response.status === 0 && response.timedOut ? "timeout" : statusClassOf(response.status);
15658
16093
  lastFailure = { reasonCode: webhookResponseReason(response) ?? "UNKNOWN" };
15659
16094
  if (attempt === maxRetries - 1) {
@@ -15665,7 +16100,7 @@ var Notifier = class {
15665
16100
  lastFailure = outcomeFailure(err);
15666
16101
  if (attempt === maxRetries - 1) {
15667
16102
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
15668
- log23.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
16103
+ log24.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
15669
16104
  }
15670
16105
  }
15671
16106
  if (attempt < maxRetries - 1) {
@@ -15684,7 +16119,7 @@ var Notifier = class {
15684
16119
  }
15685
16120
  logDelivery(projectId, notificationId, event, status, error) {
15686
16121
  this.db.insert(auditLog).values({
15687
- id: crypto21.randomUUID(),
16122
+ id: crypto22.randomUUID(),
15688
16123
  projectId,
15689
16124
  actor: "scheduler",
15690
16125
  action: `notification.${status}`,
@@ -15697,8 +16132,8 @@ var Notifier = class {
15697
16132
  };
15698
16133
 
15699
16134
  // src/run-coordinator.ts
15700
- import { eq as eq25 } from "drizzle-orm";
15701
- var log24 = createLogger("RunCoordinator");
16135
+ import { eq as eq26 } from "drizzle-orm";
16136
+ var log25 = createLogger("RunCoordinator");
15702
16137
  function reportInsightsGenerated(runTrigger, outcome) {
15703
16138
  trackFeatureCompleted({
15704
16139
  feature: "insights",
@@ -15731,22 +16166,22 @@ var RunCoordinator = class {
15731
16166
  */
15732
16167
  onAnswersRecorded;
15733
16168
  async onRunCompleted(runId, projectId, opts) {
15734
- const runRow = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
16169
+ const runRow = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15735
16170
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
15736
16171
  if (runRow?.trigger === RunTriggers.probe) {
15737
- log24.info("probe.skip-side-effects", { runId, projectId, kind });
16172
+ log25.info("probe.skip-side-effects", { runId, projectId, kind });
15738
16173
  return;
15739
16174
  }
15740
16175
  if (kind === RunKinds["answer-visibility"] && (runRow?.status === RunStatuses.completed || runRow?.status === RunStatuses.partial) && this.onAnswersRecorded) {
15741
16176
  try {
15742
16177
  await this.onAnswersRecorded(runId, projectId);
15743
16178
  } catch (err) {
15744
- log24.error("answers-recorded.failed", { runId, projectId, error: describeError(err) });
16179
+ log25.error("answers-recorded.failed", { runId, projectId, error: describeError(err) });
15745
16180
  }
15746
16181
  }
15747
16182
  const completeness = measurementRunCompleteness(this.db, runId);
15748
16183
  if (completeness.planned && !completeness.complete) {
15749
- log24.info("measurement.incomplete-skip-derived", {
16184
+ log25.info("measurement.incomplete-skip-derived", {
15750
16185
  runId,
15751
16186
  projectId,
15752
16187
  executed: completeness.executed,
@@ -15755,7 +16190,7 @@ var RunCoordinator = class {
15755
16190
  try {
15756
16191
  await this.notifier.onRunCompleted(runId, projectId);
15757
16192
  } catch (err) {
15758
- log24.error("notifier.failed", { runId, error: describeError(err) });
16193
+ log25.error("notifier.failed", { runId, error: describeError(err) });
15759
16194
  }
15760
16195
  return;
15761
16196
  }
@@ -15763,7 +16198,7 @@ var RunCoordinator = class {
15763
16198
  try {
15764
16199
  await this.onSentimentCompleted(runId, projectId);
15765
16200
  } catch {
15766
- log24.error("sentiment.wakeup-failed", { runId, projectId });
16201
+ log25.error("sentiment.wakeup-failed", { runId, projectId });
15767
16202
  }
15768
16203
  }
15769
16204
  let insightCount = 0;
@@ -15783,12 +16218,12 @@ var RunCoordinator = class {
15783
16218
  try {
15784
16219
  await this.onInsightsGenerated(runId, projectId, result);
15785
16220
  } catch (err) {
15786
- log24.error("insight-webhook.failed", { runId, error: describeError(err) });
16221
+ log25.error("insight-webhook.failed", { runId, error: describeError(err) });
15787
16222
  }
15788
16223
  }
15789
16224
  }
15790
16225
  } catch (err) {
15791
- log24.error("intelligence.failed", { runId, error: describeError(err) });
16226
+ log25.error("intelligence.failed", { runId, error: describeError(err) });
15792
16227
  insightsOutcome = { status: OutcomeStatuses.failed, ...outcomeFailure(err), durationBucket: elapsed() };
15793
16228
  }
15794
16229
  reportInsightsGenerated(runRow?.trigger, insightsOutcome);
@@ -15806,24 +16241,24 @@ var RunCoordinator = class {
15806
16241
  try {
15807
16242
  await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
15808
16243
  } catch (err) {
15809
- log24.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
16244
+ log25.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
15810
16245
  }
15811
16246
  }
15812
16247
  } catch (err) {
15813
- log24.error("gbp-intelligence.failed", { runId, error: describeError(err) });
16248
+ log25.error("gbp-intelligence.failed", { runId, error: describeError(err) });
15814
16249
  insightsOutcome = { status: OutcomeStatuses.failed, ...outcomeFailure(err), durationBucket: elapsed() };
15815
16250
  }
15816
16251
  reportInsightsGenerated(runRow?.trigger, insightsOutcome);
15817
16252
  try {
15818
16253
  await this.notifier.dispatchReviewAlerts(projectId);
15819
16254
  } catch (err) {
15820
- log24.error("review-webhook.failed", { runId, error: describeError(err) });
16255
+ log25.error("review-webhook.failed", { runId, error: describeError(err) });
15821
16256
  }
15822
16257
  }
15823
16258
  try {
15824
16259
  await (opts ? this.notifier.onRunCompleted(runId, projectId, opts) : this.notifier.onRunCompleted(runId, projectId));
15825
16260
  } catch (err) {
15826
- log24.error("notifier.failed", { runId, error: describeError(err) });
16261
+ log25.error("notifier.failed", { runId, error: describeError(err) });
15827
16262
  }
15828
16263
  if (this.onAeroEvent) {
15829
16264
  try {
@@ -15836,7 +16271,7 @@ var RunCoordinator = class {
15836
16271
  };
15837
16272
  await this.onAeroEvent(ctx);
15838
16273
  } catch (err) {
15839
- log24.error("aero.failed", { runId, error: describeError(err) });
16274
+ log25.error("aero.failed", { runId, error: describeError(err) });
15840
16275
  }
15841
16276
  }
15842
16277
  }
@@ -15851,7 +16286,7 @@ var RunCoordinator = class {
15851
16286
  * so the Aero queue is never starved of a follow-up.
15852
16287
  */
15853
16288
  buildDiscoveryAeroContext(runId, projectId, status, error) {
15854
- const session = this.db.select().from(discoverySessions).where(eq25(discoverySessions.runId, runId)).get();
16289
+ const session = this.db.select().from(discoverySessions).where(eq26(discoverySessions.runId, runId)).get();
15855
16290
  const competitorMap = session ? session.competitorMap : [];
15856
16291
  return {
15857
16292
  kind: RunKinds["aeo-discover-probe"],
@@ -15893,13 +16328,13 @@ function analysisResultFromInsights(insights2) {
15893
16328
  }
15894
16329
 
15895
16330
  // src/agent/session-registry.ts
15896
- import crypto23 from "crypto";
15897
- import { eq as eq28 } from "drizzle-orm";
16331
+ import crypto24 from "crypto";
16332
+ import { eq as eq29 } from "drizzle-orm";
15898
16333
 
15899
16334
  // src/agent/remote-mcp.ts
15900
16335
  import { Type } from "@earendil-works/pi-ai";
15901
16336
  import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
15902
- var log25 = createLogger("RemoteMcp");
16337
+ var log26 = createLogger("RemoteMcp");
15903
16338
  var AERO_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([]);
15904
16339
  async function connectStreamableHttp(server) {
15905
16340
  const transport = new StreamableHTTPClientTransport(new URL(server.url), {
@@ -15967,7 +16402,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15967
16402
  try {
15968
16403
  client = await connect(server);
15969
16404
  } catch (err) {
15970
- log25.error("external-mcp.connect-failed", {
16405
+ log26.error("external-mcp.connect-failed", {
15971
16406
  label,
15972
16407
  error: describeError(err)
15973
16408
  });
@@ -15977,7 +16412,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15977
16412
  try {
15978
16413
  listed = await client.listTools();
15979
16414
  } catch (err) {
15980
- log25.error("external-mcp.list-failed", {
16415
+ log26.error("external-mcp.list-failed", {
15981
16416
  label,
15982
16417
  error: describeError(err)
15983
16418
  });
@@ -15988,7 +16423,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
15988
16423
  if (!isReadOnly(tool)) continue;
15989
16424
  tools.push(adaptRemoteTool(client, tool));
15990
16425
  }
15991
- log25.info("external-mcp.loaded", {
16426
+ log26.info("external-mcp.loaded", {
15992
16427
  label,
15993
16428
  discovered: listed.tools.length,
15994
16429
  adopted: tools.length
@@ -15998,8 +16433,8 @@ async function loadExternalMcpTools(servers, opts = {}) {
15998
16433
  }
15999
16434
 
16000
16435
  // src/agent/memory-store.ts
16001
- import crypto22 from "crypto";
16002
- import { and as and22, desc as desc10, eq as eq26, like, sql as sql11, or as or3 } from "drizzle-orm";
16436
+ import crypto23 from "crypto";
16437
+ import { and as and23, desc as desc10, eq as eq27, like, sql as sql12, or as or3 } from "drizzle-orm";
16003
16438
  var COMPACTION_KEY_PREFIX = "compaction:";
16004
16439
  var COMPACTION_NOTES_PER_SESSION = 3;
16005
16440
  function rowToDto(row) {
@@ -16013,7 +16448,7 @@ function rowToDto(row) {
16013
16448
  };
16014
16449
  }
16015
16450
  function listMemoryEntries(db, projectId, opts = {}) {
16016
- const query = db.select().from(agentMemory).where(eq26(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
16451
+ const query = db.select().from(agentMemory).where(eq27(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
16017
16452
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
16018
16453
  return rows.map(rowToDto);
16019
16454
  }
@@ -16027,7 +16462,7 @@ function upsertMemoryEntry(db, args) {
16027
16462
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
16028
16463
  }
16029
16464
  const now = (/* @__PURE__ */ new Date()).toISOString();
16030
- const id = crypto22.randomUUID();
16465
+ const id = crypto23.randomUUID();
16031
16466
  db.insert(agentMemory).values({
16032
16467
  id,
16033
16468
  projectId: args.projectId,
@@ -16044,21 +16479,21 @@ function upsertMemoryEntry(db, args) {
16044
16479
  updatedAt: now
16045
16480
  }
16046
16481
  }).run();
16047
- const row = db.select().from(agentMemory).where(and22(eq26(agentMemory.projectId, args.projectId), eq26(agentMemory.key, args.key))).get();
16482
+ const row = db.select().from(agentMemory).where(and23(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, args.key))).get();
16048
16483
  if (!row) throw new Error("memory upsert produced no row");
16049
16484
  return rowToDto(row);
16050
16485
  }
16051
16486
  function deleteMemoryEntry(db, projectId, key) {
16052
- const result = db.delete(agentMemory).where(and22(eq26(agentMemory.projectId, projectId), eq26(agentMemory.key, key))).run();
16487
+ const result = db.delete(agentMemory).where(and23(eq27(agentMemory.projectId, projectId), eq27(agentMemory.key, key))).run();
16053
16488
  const changes = result.changes ?? 0;
16054
16489
  return changes > 0;
16055
16490
  }
16056
16491
  function loadRecentForHydrate(db, projectId, limit, sessionId) {
16057
16492
  if (sessionId === void 0) return listMemoryEntries(db, projectId, { limit });
16058
16493
  const sessionPrefix = `compaction:${sessionId ?? ""}:`;
16059
- return db.select().from(agentMemory).where(and22(
16060
- eq26(agentMemory.projectId, projectId),
16061
- or3(sql11`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql11`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
16494
+ return db.select().from(agentMemory).where(and23(
16495
+ eq27(agentMemory.projectId, projectId),
16496
+ or3(sql12`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql12`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
16062
16497
  )).orderBy(desc10(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
16063
16498
  }
16064
16499
  function writeCompactionNote(db, args) {
@@ -16069,7 +16504,7 @@ function writeCompactionNote(db, args) {
16069
16504
  }
16070
16505
  const now = (/* @__PURE__ */ new Date()).toISOString();
16071
16506
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
16072
- const id = crypto22.randomUUID();
16507
+ const id = crypto23.randomUUID();
16073
16508
  let inserted;
16074
16509
  db.transaction((tx) => {
16075
16510
  tx.insert(agentMemory).values({
@@ -16083,16 +16518,16 @@ function writeCompactionNote(db, args) {
16083
16518
  }).run();
16084
16519
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
16085
16520
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
16086
- and22(
16087
- eq26(agentMemory.projectId, args.projectId),
16521
+ and23(
16522
+ eq27(agentMemory.projectId, args.projectId),
16088
16523
  like(agentMemory.key, `${sessionPrefix}%`)
16089
16524
  )
16090
16525
  ).orderBy(desc10(agentMemory.updatedAt)).all();
16091
16526
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
16092
16527
  if (stale.length > 0) {
16093
- tx.delete(agentMemory).where(sql11`${agentMemory.id} IN (${sql11.join(stale.map((s) => sql11`${s}`), sql11`, `)})`).run();
16528
+ tx.delete(agentMemory).where(sql12`${agentMemory.id} IN (${sql12.join(stale.map((s) => sql12`${s}`), sql12`, `)})`).run();
16094
16529
  }
16095
- const row = tx.select().from(agentMemory).where(and22(eq26(agentMemory.projectId, args.projectId), eq26(agentMemory.key, key))).get();
16530
+ const row = tx.select().from(agentMemory).where(and23(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, key))).get();
16096
16531
  if (row) inserted = rowToDto(row);
16097
16532
  });
16098
16533
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -16166,7 +16601,7 @@ function trackAeroTurn(agent, turn) {
16166
16601
  }
16167
16602
 
16168
16603
  // src/agent/project-shape.ts
16169
- import { and as and23, count as count4, eq as eq27 } from "drizzle-orm";
16604
+ import { and as and24, count as count5, eq as eq28 } from "drizzle-orm";
16170
16605
  var ADVANCED_TOOLS = [
16171
16606
  "canonry_measurement_portfolio_summary",
16172
16607
  "canonry_measurement_overview",
@@ -16193,10 +16628,10 @@ var ADVANCED_ROUTES = [
16193
16628
  ];
16194
16629
  function aeroProjectShape(db, projectId) {
16195
16630
  try {
16196
- const pointer = db.select().from(measurementPlans).where(eq27(measurementPlans.projectId, projectId)).get();
16197
- const version = pointer ? db.select().from(measurementPlanVersions).where(and23(
16198
- eq27(measurementPlanVersions.projectId, projectId),
16199
- eq27(measurementPlanVersions.id, pointer.activeVersionId)
16631
+ const pointer = db.select().from(measurementPlans).where(eq28(measurementPlans.projectId, projectId)).get();
16632
+ const version = pointer ? db.select().from(measurementPlanVersions).where(and24(
16633
+ eq28(measurementPlanVersions.projectId, projectId),
16634
+ eq28(measurementPlanVersions.id, pointer.activeVersionId)
16200
16635
  )).get() : void 0;
16201
16636
  if (version) {
16202
16637
  const plan = JSON.parse(version.canonicalJson);
@@ -16223,7 +16658,7 @@ canonry_visibility_report describes the whole project, not one Property. canonry
16223
16658
  pinned: ADVANCED_TOOLS
16224
16659
  };
16225
16660
  }
16226
- const tracked = db.select({ total: count4() }).from(queries).where(eq27(queries.projectId, projectId)).get()?.total ?? 0;
16661
+ const tracked = db.select({ total: count5() }).from(queries).where(eq28(queries.projectId, projectId)).get()?.total ?? 0;
16227
16662
  return {
16228
16663
  prompt: `
16229
16664
 
@@ -16363,7 +16798,7 @@ async function compactMessages(args) {
16363
16798
  }
16364
16799
 
16365
16800
  // src/agent/session-registry.ts
16366
- var log26 = createLogger("SessionRegistry");
16801
+ var log27 = createLogger("SessionRegistry");
16367
16802
  var MAX_HYDRATE_NOTES = 20;
16368
16803
  var MAX_HYDRATE_BYTES = 32 * 1024;
16369
16804
  function escapeMemoryFragment(value) {
@@ -16422,7 +16857,7 @@ var SessionRegistry = class {
16422
16857
  if (!this.externalToolsPromise) {
16423
16858
  this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
16424
16859
  (err) => {
16425
- log26.error("external-mcp.load-failed", {
16860
+ log27.error("external-mcp.load-failed", {
16426
16861
  error: describeError(err)
16427
16862
  });
16428
16863
  return [];
@@ -16479,7 +16914,7 @@ var SessionRegistry = class {
16479
16914
  modelProvider: effectiveProvider,
16480
16915
  modelId: effectiveModelId,
16481
16916
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16482
- }).where(eq28(agentSessions.projectId, projectId)).run();
16917
+ }).where(eq29(agentSessions.projectId, projectId)).run();
16483
16918
  }
16484
16919
  const agent2 = createAeroSession({
16485
16920
  projectName,
@@ -16508,7 +16943,7 @@ var SessionRegistry = class {
16508
16943
  return agent2;
16509
16944
  }
16510
16945
  const { provider, modelId } = resolveSessionProviderAndModel(this.opts.config, preferences);
16511
- const sessionId = crypto23.randomUUID();
16946
+ const sessionId = crypto24.randomUUID();
16512
16947
  const agent = createAeroSession({
16513
16948
  projectName,
16514
16949
  client: this.opts.client,
@@ -16698,13 +17133,13 @@ ${lines.join("\n")}
16698
17133
  agent.state.messages = result.messages;
16699
17134
  setAeroSystemPrompt(agent, this.buildHydratedSystemPrompt(projectId, row.systemPrompt));
16700
17135
  this.save(projectName);
16701
- log26.info("compaction.completed", {
17136
+ log27.info("compaction.completed", {
16702
17137
  projectName,
16703
17138
  removedCount: result.removedCount,
16704
17139
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
16705
17140
  });
16706
17141
  } catch (err) {
16707
- log26.error("compaction.failed", {
17142
+ log27.error("compaction.failed", {
16708
17143
  projectName,
16709
17144
  error: describeError(err)
16710
17145
  });
@@ -16798,7 +17233,7 @@ ${lines.join("\n")}
16798
17233
  modelProvider: nextProvider,
16799
17234
  modelId: nextModelId,
16800
17235
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16801
- }).where(eq28(agentSessions.projectId, projectId)).run();
17236
+ }).where(eq29(agentSessions.projectId, projectId)).run();
16802
17237
  }
16803
17238
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
16804
17239
  prepareConversationChange(projectName) {
@@ -16882,7 +17317,7 @@ ${lines.join("\n")}
16882
17317
  }
16883
17318
  trackAeroTurn(agent, { trigger: OutcomeTriggers.agent });
16884
17319
  } catch (err) {
16885
- log26.error("drain.failed", {
17320
+ log27.error("drain.failed", {
16886
17321
  projectName,
16887
17322
  error: describeError(err)
16888
17323
  });
@@ -16979,18 +17414,18 @@ ${lines.join("\n")}
16979
17414
  return id;
16980
17415
  }
16981
17416
  tryResolveProjectId(projectName) {
16982
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq28(projects.name, projectName)).get();
17417
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq29(projects.name, projectName)).get();
16983
17418
  return row?.id;
16984
17419
  }
16985
17420
  loadRow(projectId) {
16986
- const row = this.opts.db.select().from(agentSessions).where(eq28(agentSessions.projectId, projectId)).get();
17421
+ const row = this.opts.db.select().from(agentSessions).where(eq29(agentSessions.projectId, projectId)).get();
16987
17422
  return row ?? null;
16988
17423
  }
16989
17424
  insertRow(params) {
16990
17425
  const now = (/* @__PURE__ */ new Date()).toISOString();
16991
17426
  const modelProvider = params.provider ?? params.modelProvider ?? AgentProviderIds.claude;
16992
17427
  this.opts.db.insert(agentSessions).values({
16993
- id: params.id ?? crypto23.randomUUID(),
17428
+ id: params.id ?? crypto24.randomUUID(),
16994
17429
  projectId: params.projectId,
16995
17430
  systemPrompt: params.systemPrompt,
16996
17431
  modelProvider,
@@ -17003,7 +17438,7 @@ ${lines.join("\n")}
17003
17438
  }
17004
17439
  updateRow(projectId, patch) {
17005
17440
  const now = (/* @__PURE__ */ new Date()).toISOString();
17006
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq28(agentSessions.projectId, projectId)).run();
17441
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq29(agentSessions.projectId, projectId)).run();
17007
17442
  }
17008
17443
  /**
17009
17444
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -17012,13 +17447,13 @@ ${lines.join("\n")}
17012
17447
  * recently used right after an upgrade.
17013
17448
  */
17014
17449
  persistPromptSnapshot(projectId, systemPrompt) {
17015
- this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq28(agentSessions.projectId, projectId)).run();
17450
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq29(agentSessions.projectId, projectId)).run();
17016
17451
  }
17017
17452
  };
17018
17453
 
17019
17454
  // src/agent/viewer-sessions.ts
17020
- import crypto24 from "crypto";
17021
- import { and as and24, eq as eq29, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
17455
+ import crypto25 from "crypto";
17456
+ import { and as and25, eq as eq30, isNotNull as isNotNull4, like as like2 } from "drizzle-orm";
17022
17457
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
17023
17458
  CanonryMcpToolNames.canonry_ads_account,
17024
17459
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -17071,8 +17506,8 @@ function trimViewerTranscript(messages, max2 = MAX_VIEWER_MESSAGES) {
17071
17506
  }
17072
17507
  var VIEWER_IDLE_MS = 12 * 60 * 60 * 1e3;
17073
17508
  function mintViewerAeroKey(db, userId, now = /* @__PURE__ */ new Date()) {
17074
- const raw = `cnry_${crypto24.randomBytes(24).toString("hex")}`;
17075
- const id = crypto24.randomUUID();
17509
+ const raw = `cnry_${crypto25.randomBytes(24).toString("hex")}`;
17510
+ const id = crypto25.randomUUID();
17076
17511
  db.insert(apiKeys).values({
17077
17512
  id,
17078
17513
  name: `${VIEWER_AERO_KEY_PREFIX}${userId}`,
@@ -17131,8 +17566,8 @@ var ViewerAeroSessions = class {
17131
17566
  if (this.isBusy(project.name, userId)) throw agentBusy(project.name);
17132
17567
  const today = new Date(this.now()).toISOString().slice(0, 10);
17133
17568
  const used = this.turnsToday.get(key);
17134
- const count5 = used?.date === today ? used.count : 0;
17135
- if (count5 >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
17569
+ const count6 = used?.date === today ? used.count : 0;
17570
+ if (count6 >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
17136
17571
  const minted = mintViewerAeroKey(this.opts.db, userId, new Date(this.now()));
17137
17572
  this.acquisitions.add(key);
17138
17573
  let handedOff = false;
@@ -17147,7 +17582,7 @@ var ViewerAeroSessions = class {
17147
17582
  setAeroSystemPrompt(agent, loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT + shape.prompt + aeroViewPrompt(preferences.context));
17148
17583
  configureAeroRuntime(agent, [...buildViewerAeroTools(client, project.name, this.opts.managedSweeps), buildAeroViewTool(view, evidence)], void 0, true, shape.pinned);
17149
17584
  this.sessions.set(key, { agent, lastUsedAt: this.now(), updatedAt: this.sessions.get(key)?.updatedAt ?? null });
17150
- this.turnsToday.set(key, { date: today, count: count5 + 1 });
17585
+ this.turnsToday.set(key, { date: today, count: count6 + 1 });
17151
17586
  handedOff = true;
17152
17587
  let released = false;
17153
17588
  return {
@@ -17195,12 +17630,12 @@ var ViewerAeroSessions = class {
17195
17630
  */
17196
17631
  removeKey(id) {
17197
17632
  try {
17198
- this.opts.db.delete(apiKeys).where(eq29(apiKeys.id, id)).run();
17633
+ this.opts.db.delete(apiKeys).where(eq30(apiKeys.id, id)).run();
17199
17634
  } catch {
17200
17635
  try {
17201
17636
  this.opts.db.transaction((tx) => {
17202
- tx.delete(dashboardSessions).where(eq29(dashboardSessions.apiKeyId, id)).run();
17203
- tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq29(apiKeys.id, id)).run();
17637
+ tx.delete(dashboardSessions).where(eq30(dashboardSessions.apiKeyId, id)).run();
17638
+ tx.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq30(apiKeys.id, id)).run();
17204
17639
  });
17205
17640
  } catch {
17206
17641
  }
@@ -17213,7 +17648,7 @@ var ViewerAeroSessions = class {
17213
17648
  */
17214
17649
  revokeOrphanedKeys() {
17215
17650
  try {
17216
- this.opts.db.delete(apiKeys).where(and24(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17651
+ this.opts.db.delete(apiKeys).where(and25(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull4(apiKeys.delegatedUserId))).run();
17217
17652
  } catch {
17218
17653
  }
17219
17654
  }
@@ -17228,9 +17663,9 @@ var ViewerAeroSessions = class {
17228
17663
  };
17229
17664
 
17230
17665
  // src/mcp-http.ts
17231
- import crypto25 from "crypto";
17666
+ import crypto26 from "crypto";
17232
17667
  import { isIP } from "net";
17233
- import { and as and25, eq as eq30, isNull as isNull6 } from "drizzle-orm";
17668
+ import { and as and26, eq as eq31, isNull as isNull6 } from "drizzle-orm";
17234
17669
  import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from "@modelcontextprotocol/node";
17235
17670
  import { McpServer, createMcpHandler, isInitializeRequest, isLegacyRequest } from "@modelcontextprotocol/server";
17236
17671
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -17239,8 +17674,8 @@ var MAX_CLEANUP_RETRIES_PER_SWEEP = 8;
17239
17674
  var MAX_PENDING_KEY_RETIREMENTS = 256;
17240
17675
  var MAX_CLEANUP_BACKOFF_MS = 10 * 60 * 1e3;
17241
17676
  function mintSessionKey(db, scopes, userId, oauthGrantId, createdAt) {
17242
- const raw = `cnry_${crypto25.randomBytes(24).toString("hex")}`;
17243
- const id = crypto25.randomUUID();
17677
+ const raw = `cnry_${crypto26.randomBytes(24).toString("hex")}`;
17678
+ const id = crypto26.randomUUID();
17244
17679
  db.insert(apiKeys).values({
17245
17680
  id,
17246
17681
  name: `mcp-session:${userId}`,
@@ -17336,12 +17771,12 @@ function registerMcpHttpRoutes(scope, opts) {
17336
17771
  try {
17337
17772
  withoutLockWait(() => {
17338
17773
  opts.db.transaction((tx) => {
17339
- tx.delete(dashboardSessions).where(eq30(dashboardSessions.apiKeyId, id)).run();
17340
- tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and25(eq30(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
17774
+ tx.delete(dashboardSessions).where(eq31(dashboardSessions.apiKeyId, id)).run();
17775
+ tx.update(apiKeys).set({ revokedAt: new Date(now()).toISOString() }).where(and26(eq31(apiKeys.id, id), isNull6(apiKeys.revokedAt))).run();
17341
17776
  });
17342
17777
  if (remove) {
17343
17778
  try {
17344
- opts.db.delete(apiKeys).where(eq30(apiKeys.id, id)).run();
17779
+ opts.db.delete(apiKeys).where(eq31(apiKeys.id, id)).run();
17345
17780
  } catch (error) {
17346
17781
  scope.log.warn({ err: error, keyId: id }, "Retained revoked MCP request key after deletion failed");
17347
17782
  }
@@ -17392,14 +17827,14 @@ function registerMcpHttpRoutes(scope, opts) {
17392
17827
  withoutLockWait(() => {
17393
17828
  const cutoff = now() - SESSION_IDLE_MS;
17394
17829
  for (const session of legacyServers) {
17395
- const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq30(apiKeys.id, session.sessionKeyId)).get() : void 0;
17830
+ const key = session.sessionKeyId ? opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq31(apiKeys.id, session.sessionKeyId)).get() : void 0;
17396
17831
  if (session.lastSeenAt < cutoff || session.expiresAt !== void 0 && session.expiresAt <= now() || session.sessionKeyId && (!key || key.revokedAt)) {
17397
17832
  void session.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP session"));
17398
17833
  }
17399
17834
  }
17400
17835
  for (const exchange of modernExchanges) {
17401
17836
  if (!exchange.sessionKey) continue;
17402
- const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq30(apiKeys.id, exchange.sessionKey.id)).get();
17837
+ const key = opts.db.select({ revokedAt: apiKeys.revokedAt }).from(apiKeys).where(eq31(apiKeys.id, exchange.sessionKey.id)).get();
17403
17838
  if (!key || key.revokedAt || exchange.sessionKey.expiresAt <= now()) {
17404
17839
  void exchange.close().catch((error) => scope.log.error({ err: error }, "Failed to close expired MCP request"));
17405
17840
  }
@@ -17454,7 +17889,7 @@ function registerMcpHttpRoutes(scope, opts) {
17454
17889
  clientName: `canonry-mcp/${PACKAGE_VERSION}`,
17455
17890
  surface: "mcp-http",
17456
17891
  // Correlation only: this is never accepted as caller identity or authority.
17457
- ...modern ? {} : { actorSession: crypto25.randomUUID() }
17892
+ ...modern ? {} : { actorSession: crypto26.randomUUID() }
17458
17893
  });
17459
17894
  let server;
17460
17895
  try {
@@ -17479,7 +17914,7 @@ function registerMcpHttpRoutes(scope, opts) {
17479
17914
  if (!bearer) return null;
17480
17915
  const { server, sessionKey } = createAuthorizedServer(request, segment, bearer);
17481
17916
  const transport = new NodeStreamableHTTPServerTransport({
17482
- sessionIdGenerator: () => crypto25.randomUUID(),
17917
+ sessionIdGenerator: () => crypto26.randomUUID(),
17483
17918
  onsessioninitialized: (id) => {
17484
17919
  if (!shuttingDown && !closing) sessions.set(id, session);
17485
17920
  }
@@ -17649,7 +18084,7 @@ function registerMcpHttpRoutes(scope, opts) {
17649
18084
  }
17650
18085
 
17651
18086
  // src/agent/agent-routes.ts
17652
- import { eq as eq31 } from "drizzle-orm";
18087
+ import { eq as eq32 } from "drizzle-orm";
17653
18088
  function viewerAeroCaller(request, opts) {
17654
18089
  if (!opts.viewerSessions) return null;
17655
18090
  const principal = request.principal;
@@ -17689,7 +18124,7 @@ function turnAttribution(request) {
17689
18124
  };
17690
18125
  }
17691
18126
  function resolveProject(db, name) {
17692
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq31(projects.name, name)).get();
18127
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq32(projects.name, name)).get();
17693
18128
  if (!row) throw notFound("project", name);
17694
18129
  return row;
17695
18130
  }
@@ -17808,7 +18243,7 @@ function registerAgentRoutes(app, opts) {
17808
18243
  }
17809
18244
  requireInstanceAdministrator(request);
17810
18245
  const project = resolveProject(opts.db, request.params.name);
17811
- const row = opts.db.select().from(agentSessions).where(eq31(agentSessions.projectId, project.id)).get();
18246
+ const row = opts.db.select().from(agentSessions).where(eq32(agentSessions.projectId, project.id)).get();
17812
18247
  const nextTurn = opts.sessionRegistry.nextTurnModel(project.name);
17813
18248
  if (!row) {
17814
18249
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, nextTurn, updatedAt: null };
@@ -17850,7 +18285,7 @@ function registerAgentRoutes(app, opts) {
17850
18285
  const project = resolveProject(opts.db, request.params.name);
17851
18286
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
17852
18287
  opts.sessionRegistry.reset(project.name);
17853
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq31(agentSessions.projectId, project.id)).run();
18288
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq32(agentSessions.projectId, project.id)).run();
17854
18289
  return { status: "reset" };
17855
18290
  }
17856
18291
  );
@@ -17874,7 +18309,7 @@ function registerAgentRoutes(app, opts) {
17874
18309
  return streamViewerTurn(opts, project, viewerId, body, reply, turnAttribution(request));
17875
18310
  }
17876
18311
  if (body.conversationId !== void 0) {
17877
- const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq31(agentSessions.projectId, project.id)).get();
18312
+ const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq32(agentSessions.projectId, project.id)).get();
17878
18313
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
17879
18314
  }
17880
18315
  const promptText = body.prompt;
@@ -18285,7 +18720,7 @@ function stripHtml(html) {
18285
18720
  }
18286
18721
 
18287
18722
  // src/snapshot-service.ts
18288
- var log27 = createLogger("Snapshot");
18723
+ var log28 = createLogger("Snapshot");
18289
18724
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "muse", "local"];
18290
18725
  var SNAPSHOT_QUERY_COUNT = 6;
18291
18726
  var SnapshotService = class {
@@ -18387,7 +18822,7 @@ var SnapshotService = class {
18387
18822
  return mapAuditReport(report);
18388
18823
  } catch (err) {
18389
18824
  const message = describeError(err);
18390
- log27.warn("audit.failed", { homepageUrl, error: message });
18825
+ log28.warn("audit.failed", { homepageUrl, error: message });
18391
18826
  return {
18392
18827
  url: homepageUrl,
18393
18828
  finalUrl: homepageUrl,
@@ -18416,7 +18851,7 @@ var SnapshotService = class {
18416
18851
  queries: parsedQueries
18417
18852
  };
18418
18853
  } catch (err) {
18419
- log27.warn("profile.generation-failed", {
18854
+ log28.warn("profile.generation-failed", {
18420
18855
  domain: ctx.domain,
18421
18856
  provider: ctx.analysisProvider.adapter.name,
18422
18857
  error: describeError(err)
@@ -18564,7 +18999,7 @@ var SnapshotService = class {
18564
18999
  recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
18565
19000
  };
18566
19001
  } catch (err) {
18567
- log27.warn("response.analysis-failed", {
19002
+ log28.warn("response.analysis-failed", {
18568
19003
  provider: ctx.analysisProvider.adapter.name,
18569
19004
  error: describeError(err)
18570
19005
  });
@@ -18690,7 +19125,7 @@ function buildSnapshotSummary(companyName, queries2, providers, queryResults, au
18690
19125
  competitorCounts.set(competitor, (competitorCounts.get(competitor) ?? 0) + 1);
18691
19126
  }
18692
19127
  }
18693
- const topCompetitors = [...competitorCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 10).map(([name, count5]) => ({ name, count: count5 }));
19128
+ const topCompetitors = [...competitorCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 10).map(([name, count6]) => ({ name, count: count6 }));
18694
19129
  const defaultMeaning = totalComparisons > 0 ? `${companyName} was mentioned in ${mentionCount}/${totalComparisons} successful provider-query response${totalComparisons === 1 ? "" : "s"} across ${queries2.length} category queries.` : `No successful provider responses were returned across ${queries2.length} category queries.`;
18695
19130
  const failureNote = failedComparisons > 0 ? [
18696
19131
  `${failedComparisons} provider response${failedComparisons === 1 ? "" : "s"} failed and ${failedComparisons === 1 ? "was" : "were"} excluded from visibility totals.`
@@ -18834,7 +19269,7 @@ function clipText(value, length) {
18834
19269
  }
18835
19270
 
18836
19271
  // src/research-runner.ts
18837
- import { and as and26, eq as eq32, inArray as inArray13, sql as sql12 } from "drizzle-orm";
19272
+ import { and as and27, eq as eq33, inArray as inArray13, sql as sql13 } from "drizzle-orm";
18838
19273
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
18839
19274
  function finalResearchRunStatus(completed, failed, waiting) {
18840
19275
  if (waiting > 0) return ResearchRunStatuses["waiting-upstream"];
@@ -18842,13 +19277,13 @@ function finalResearchRunStatus(completed, failed, waiting) {
18842
19277
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
18843
19278
  }
18844
19279
  async function executeResearchRun(db, registry, runId, projectId, options = {}) {
18845
- const run = db.select().from(researchRuns).where(and26(eq32(researchRuns.id, runId), eq32(researchRuns.projectId, projectId))).get();
19280
+ const run = db.select().from(researchRuns).where(and27(eq33(researchRuns.id, runId), eq33(researchRuns.projectId, projectId))).get();
18846
19281
  if (!run || run.status !== ResearchRunStatuses.queued) return;
18847
- const project = db.select().from(projects).where(eq32(projects.id, projectId)).get();
19282
+ const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
18848
19283
  if (!project) return;
18849
19284
  const provider = registry.get(run.provider);
18850
19285
  const now = (/* @__PURE__ */ new Date()).toISOString();
18851
- const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and26(eq32(researchRuns.id, runId), eq32(researchRuns.status, ResearchRunStatuses.queued))).run();
19286
+ const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and27(eq33(researchRuns.id, runId), eq33(researchRuns.status, ResearchRunStatuses.queued))).run();
18852
19287
  if (claim.changes !== 1) return;
18853
19288
  const elapsed = startOutcomeTimer();
18854
19289
  const requested = currentOutcomeOrigin();
@@ -18882,8 +19317,8 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
18882
19317
  }
18883
19318
  reserved = run.totalQueries;
18884
19319
  reservation = { scope, period };
18885
- const rows = db.select().from(researchRunQueries).where(eq32(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
18886
- const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq32(competitors.projectId, projectId)).orderBy(competitors.domain).all();
19320
+ const rows = db.select().from(researchRunQueries).where(eq33(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
19321
+ const competitorRows = db.select(competitorIdentityColumns).from(competitors).where(eq33(competitors.projectId, projectId)).orderBy(competitors.domain).all();
18887
19322
  const competitorDomains = competitorRows.map((row) => row.domain);
18888
19323
  const competitorAliases = new Map(competitorRows.map((row) => [row.domain, competitorIdentityAliases(row)]).filter(([, aliases]) => aliases.length > 0));
18889
19324
  const domains = effectiveDomains(project);
@@ -18893,7 +19328,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
18893
19328
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
18894
19329
  try {
18895
19330
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
18896
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and26(eq32(researchRunQueries.id, row.id), eq32(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
19331
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and27(eq33(researchRunQueries.id, row.id), eq33(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
18897
19332
  const raw = await gate.run(async () => {
18898
19333
  dispatched++;
18899
19334
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -18924,7 +19359,7 @@ async function executeResearchRun(db, registry, runId, projectId, options = {})
18924
19359
  rawResponse: raw.rawResponse,
18925
19360
  answerEvidence: raw.evidence ?? null,
18926
19361
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
18927
- }).where(and26(eq32(researchRunQueries.id, row.id), eq32(researchRunQueries.status, ResearchQueryStatuses.running))).run();
19362
+ }).where(and27(eq33(researchRunQueries.id, row.id), eq33(researchRunQueries.status, ResearchQueryStatuses.running))).run();
18928
19363
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
18929
19364
  } catch (error) {
18930
19365
  if (!(error instanceof IncompleteAnswerError && error.resumable)) queryFailure ??= error;
@@ -18975,11 +19410,11 @@ function researchOutcomeStatus(status) {
18975
19410
  }
18976
19411
  }
18977
19412
  function incrementResearchProgress(db, runId, column) {
18978
- db.update(researchRuns).set({ [column]: sql12`${researchRuns[column]} + 1` }).where(eq32(researchRuns.id, runId)).run();
19413
+ db.update(researchRuns).set({ [column]: sql13`${researchRuns[column]} + 1` }).where(eq33(researchRuns.id, runId)).run();
18979
19414
  }
18980
19415
  function markResearchQueryFailed(db, runId, queryId, error) {
18981
19416
  const message = describeError(error);
18982
- const unfinished = and26(eq32(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
19417
+ const unfinished = and27(eq33(researchRunQueries.id, queryId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses));
18983
19418
  if (error instanceof IncompleteAnswerError && error.resumable) {
18984
19419
  db.update(researchRunQueries).set({
18985
19420
  status: ResearchQueryStatuses["waiting-upstream"],
@@ -18995,10 +19430,10 @@ function markResearchQueryFailed(db, runId, queryId, error) {
18995
19430
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
18996
19431
  }
18997
19432
  function markUnfinishedResearchQueriesFailed(db, runId, error) {
18998
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and26(eq32(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
19433
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and27(eq33(researchRunQueries.researchRunId, runId), inArray13(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
18999
19434
  }
19000
19435
  function finalizeResearchRun(db, runId, fatalError) {
19001
- const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq32(researchRunQueries.researchRunId, runId)).all();
19436
+ const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq33(researchRunQueries.researchRunId, runId)).all();
19002
19437
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
19003
19438
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
19004
19439
  const waiting = rows.filter((row) => row.status === ResearchQueryStatuses["waiting-upstream"]).length;
@@ -19010,12 +19445,12 @@ function finalizeResearchRun(db, runId, fatalError) {
19010
19445
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
19011
19446
  // A run waiting on upstream jobs has not finished.
19012
19447
  finishedAt: waiting > 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
19013
- }).where(eq32(researchRuns.id, runId)).run();
19448
+ }).where(eq33(researchRuns.id, runId)).run();
19014
19449
  return { status, total: rows.length, completed, failed };
19015
19450
  }
19016
19451
 
19017
19452
  // src/google-marketing-sync.ts
19018
- import { and as and27, eq as eq33, isNull as isNull7, sql as sql13 } from "drizzle-orm";
19453
+ import { and as and28, eq as eq34, isNull as isNull7, sql as sql14 } from "drizzle-orm";
19019
19454
  var RUNTIME_FAILURE_REASONS = {
19020
19455
  INVALID_INPUT: OutcomeReasonCodes.VALIDATION,
19021
19456
  NOT_CONFIGURED: OutcomeReasonCodes.NOT_CONNECTED,
@@ -19052,10 +19487,10 @@ function assertSnapshotContext(metadata, expected) {
19052
19487
  }
19053
19488
  }
19054
19489
  function requireSyncRun(db, runId, projectId, kind) {
19055
- const run = db.select().from(runs).where(and27(
19056
- eq33(runs.id, runId),
19057
- eq33(runs.projectId, projectId),
19058
- eq33(runs.kind, kind)
19490
+ const run = db.select().from(runs).where(and28(
19491
+ eq34(runs.id, runId),
19492
+ eq34(runs.projectId, projectId),
19493
+ eq34(runs.kind, kind)
19059
19494
  )).get();
19060
19495
  if (!run) throw withOutcomeReason(new Error(`Run ${runId} is not a ${kind} run for this project.`), OutcomeReasonCodes.NOT_FOUND);
19061
19496
  if (run.status !== RunStatuses.queued) {
@@ -19071,19 +19506,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
19071
19506
  status: RunStatuses.failed,
19072
19507
  error: serializeRunError({ message: safeErrorMessage(error) }),
19073
19508
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
19074
- }).where(and27(
19075
- eq33(runs.id, runId),
19076
- eq33(runs.projectId, projectId),
19077
- eq33(runs.kind, kind),
19078
- eq33(runs.status, expectedStatus)
19509
+ }).where(and28(
19510
+ eq34(runs.id, runId),
19511
+ eq34(runs.projectId, projectId),
19512
+ eq34(runs.kind, kind),
19513
+ eq34(runs.status, expectedStatus)
19079
19514
  )).run();
19080
19515
  }
19081
19516
  function claimSyncRun(db, runId, projectId, kind, startedAt) {
19082
- const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and27(
19083
- eq33(runs.id, runId),
19084
- eq33(runs.projectId, projectId),
19085
- eq33(runs.kind, kind),
19086
- eq33(runs.status, RunStatuses.queued)
19517
+ const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and28(
19518
+ eq34(runs.id, runId),
19519
+ eq34(runs.projectId, projectId),
19520
+ eq34(runs.kind, kind),
19521
+ eq34(runs.status, RunStatuses.queued)
19087
19522
  )).run();
19088
19523
  return claim.changes === 1;
19089
19524
  }
@@ -19093,9 +19528,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19093
19528
  let evidenceCollected = false;
19094
19529
  try {
19095
19530
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
19096
- const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
19531
+ const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
19097
19532
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19098
- const connection = db.select().from(googleAdsConnections).where(eq33(googleAdsConnections.projectId, projectId)).get();
19533
+ const connection = db.select().from(googleAdsConnections).where(eq34(googleAdsConnections.projectId, projectId)).get();
19099
19534
  if (!connection?.selectedCustomerId) {
19100
19535
  throw withOutcomeReason(new Error("Google Ads needs a selected customer before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19101
19536
  }
@@ -19147,11 +19582,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19147
19582
  status: RunStatuses.completed,
19148
19583
  finishedAt,
19149
19584
  error: null
19150
- }).where(and27(
19151
- eq33(runs.id, runId),
19152
- eq33(runs.projectId, projectId),
19153
- eq33(runs.kind, RunKinds["google-ads-sync"]),
19154
- eq33(runs.status, RunStatuses.running)
19585
+ }).where(and28(
19586
+ eq34(runs.id, runId),
19587
+ eq34(runs.projectId, projectId),
19588
+ eq34(runs.kind, RunKinds["google-ads-sync"]),
19589
+ eq34(runs.status, RunStatuses.running)
19155
19590
  )).run();
19156
19591
  if (terminalClaim.changes !== 1) {
19157
19592
  throw withOutcomeReason(
@@ -19171,12 +19606,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
19171
19606
  lastMetricsSnapshotAt: result.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
19172
19607
  lastMetricsSnapshotId: result.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
19173
19608
  updatedAt: finishedAt
19174
- }).where(and27(
19175
- eq33(googleAdsConnections.id, connection.id),
19176
- eq33(googleAdsConnections.projectId, projectId),
19177
- eq33(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19178
- connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq33(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19179
- eq33(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19609
+ }).where(and28(
19610
+ eq34(googleAdsConnections.id, connection.id),
19611
+ eq34(googleAdsConnections.projectId, projectId),
19612
+ eq34(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
19613
+ connection.selectedLoginCustomerId === null ? isNull7(googleAdsConnections.selectedLoginCustomerId) : eq34(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
19614
+ eq34(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
19180
19615
  )).run();
19181
19616
  if (connectionClaim.changes !== 1) {
19182
19617
  throw withOutcomeReason(
@@ -19216,9 +19651,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19216
19651
  let evidenceCollected = false;
19217
19652
  try {
19218
19653
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
19219
- const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
19654
+ const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
19220
19655
  if (!project) throw withOutcomeReason(new Error(`Project ${projectId} was not found.`), OutcomeReasonCodes.NOT_FOUND);
19221
- const connection = db.select().from(gtmConnections).where(eq33(gtmConnections.projectId, projectId)).get();
19656
+ const connection = db.select().from(gtmConnections).where(eq34(gtmConnections.projectId, projectId)).get();
19222
19657
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
19223
19658
  throw withOutcomeReason(new Error("GTM needs a selected account and container before it can sync."), OutcomeReasonCodes.ACCOUNT_NOT_FOUND);
19224
19659
  }
@@ -19256,11 +19691,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19256
19691
  status: RunStatuses.completed,
19257
19692
  finishedAt,
19258
19693
  error: null
19259
- }).where(and27(
19260
- eq33(runs.id, runId),
19261
- eq33(runs.projectId, projectId),
19262
- eq33(runs.kind, RunKinds["gtm-sync"]),
19263
- eq33(runs.status, RunStatuses.running)
19694
+ }).where(and28(
19695
+ eq34(runs.id, runId),
19696
+ eq34(runs.projectId, projectId),
19697
+ eq34(runs.kind, RunKinds["gtm-sync"]),
19698
+ eq34(runs.status, RunStatuses.running)
19264
19699
  )).run();
19265
19700
  if (terminalClaim.changes !== 1) {
19266
19701
  throw withOutcomeReason(
@@ -19275,19 +19710,19 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19275
19710
  selectedWorkspaceName: selectedWorkspace?.name ?? null,
19276
19711
  ...workspaceReplaced ? {
19277
19712
  selectedWorkspaceId: workspaceId,
19278
- selectionGeneration: sql13`${gtmConnections.selectionGeneration} + 1`
19713
+ selectionGeneration: sql14`${gtmConnections.selectionGeneration} + 1`
19279
19714
  } : {},
19280
19715
  lastValidatedAt: snapshot.metadata.capturedAt,
19281
19716
  lastSnapshotAt: snapshot.metadata.capturedAt,
19282
19717
  lastSnapshotId: snapshot.metadata.id,
19283
19718
  updatedAt: finishedAt
19284
- }).where(and27(
19285
- eq33(gtmConnections.id, connection.id),
19286
- eq33(gtmConnections.projectId, projectId),
19287
- eq33(gtmConnections.selectedAccountId, connection.selectedAccountId),
19288
- eq33(gtmConnections.selectedContainerId, connection.selectedContainerId),
19289
- connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq33(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
19290
- eq33(gtmConnections.selectionGeneration, connection.selectionGeneration)
19719
+ }).where(and28(
19720
+ eq34(gtmConnections.id, connection.id),
19721
+ eq34(gtmConnections.projectId, projectId),
19722
+ eq34(gtmConnections.selectedAccountId, connection.selectedAccountId),
19723
+ eq34(gtmConnections.selectedContainerId, connection.selectedContainerId),
19724
+ connection.selectedWorkspaceId === null ? isNull7(gtmConnections.selectedWorkspaceId) : eq34(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
19725
+ eq34(gtmConnections.selectionGeneration, connection.selectionGeneration)
19291
19726
  )).run();
19292
19727
  if (connectionClaim.changes !== 1) {
19293
19728
  throw withOutcomeReason(
@@ -19325,7 +19760,7 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
19325
19760
  // src/server.ts
19326
19761
  var _require = createRequire(import.meta.url);
19327
19762
  var { version: PKG_VERSION } = _require("../package.json");
19328
- var log28 = createLogger("Server");
19763
+ var log29 = createLogger("Server");
19329
19764
  var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
19330
19765
  function waitForServerRuntimeStartup(app) {
19331
19766
  const startup = runtimeStartupByServer.get(app);
@@ -19490,8 +19925,8 @@ var DASHBOARD_SCRYPT_KEYLEN = 64;
19490
19925
  var DASHBOARD_SCRYPT_COST = 1 << 15;
19491
19926
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
19492
19927
  function hashDashboardPassword(password) {
19493
- const salt = crypto26.randomBytes(16);
19494
- const derived = crypto26.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
19928
+ const salt = crypto27.randomBytes(16);
19929
+ const derived = crypto27.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
19495
19930
  N: DASHBOARD_SCRYPT_COST,
19496
19931
  maxmem: DASHBOARD_SCRYPT_MAXMEM
19497
19932
  });
@@ -19512,14 +19947,14 @@ function verifyDashboardPassword(password, storedHash) {
19512
19947
  } catch {
19513
19948
  return { ok: false, needsRehash: false };
19514
19949
  }
19515
- const derived = crypto26.scryptSync(password, salt, expected.length, {
19950
+ const derived = crypto27.scryptSync(password, salt, expected.length, {
19516
19951
  N: DASHBOARD_SCRYPT_COST,
19517
19952
  maxmem: DASHBOARD_SCRYPT_MAXMEM
19518
19953
  });
19519
19954
  if (derived.length !== expected.length)
19520
19955
  return { ok: false, needsRehash: false };
19521
19956
  return {
19522
- ok: crypto26.timingSafeEqual(derived, expected),
19957
+ ok: crypto27.timingSafeEqual(derived, expected),
19523
19958
  needsRehash: false
19524
19959
  };
19525
19960
  }
@@ -19528,7 +19963,7 @@ function verifyDashboardPassword(password, storedHash) {
19528
19963
  const expected = Buffer.from(storedHash, "hex");
19529
19964
  if (candidate.length !== expected.length)
19530
19965
  return { ok: false, needsRehash: false };
19531
- const ok = crypto26.timingSafeEqual(candidate, expected);
19966
+ const ok = crypto27.timingSafeEqual(candidate, expected);
19532
19967
  return { ok, needsRehash: ok };
19533
19968
  }
19534
19969
  return { ok: false, needsRehash: false };
@@ -19575,7 +20010,7 @@ function applyLegacyCredentials(rows, config, persistConfigPatch = saveConfigPat
19575
20010
  }
19576
20011
  if (migratedGoogle > 0) {
19577
20012
  persistConfigPatch({ google: config.google });
19578
- log28.info("credentials.migrated", { type: "google", count: migratedGoogle });
20013
+ log29.info("credentials.migrated", { type: "google", count: migratedGoogle });
19579
20014
  }
19580
20015
  let migratedGa4 = 0;
19581
20016
  for (const row of rows.ga4) {
@@ -19593,7 +20028,7 @@ function applyLegacyCredentials(rows, config, persistConfigPatch = saveConfigPat
19593
20028
  }
19594
20029
  if (migratedGa4 > 0) {
19595
20030
  persistConfigPatch({ ga4: config.ga4 });
19596
- log28.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
20031
+ log29.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
19597
20032
  }
19598
20033
  }
19599
20034
  function isLoopbackPeerAddress(address) {
@@ -19695,7 +20130,7 @@ function cloneGtmConfig(config) {
19695
20130
  function createGoogleMarketingConfigCredentialStore(input) {
19696
20131
  const persistConfigPatch = input.saveConfigPatch ?? saveConfigPatch;
19697
20132
  const env = input.env ?? process.env;
19698
- const randomUUID2 = input.randomUUID ?? crypto26.randomUUID;
20133
+ const randomUUID2 = input.randomUUID ?? crypto27.randomUUID;
19699
20134
  const snapshot = () => ({
19700
20135
  googleAds: cloneGoogleAdsConfig(input.config.googleAds),
19701
20136
  gtm: cloneGtmConfig(input.config.gtm)
@@ -19812,7 +20247,7 @@ async function createServer(opts) {
19812
20247
  const trustProxy = resolveTrustProxy(process.env.CANONRY_TRUST_PROXY);
19813
20248
  const app = Fastify({
19814
20249
  loggerInstance: logger,
19815
- genReqId: () => crypto26.randomUUID(),
20250
+ genReqId: () => crypto27.randomUUID(),
19816
20251
  trustProxy
19817
20252
  });
19818
20253
  if (!ownsDefaultConfig) {
@@ -19876,17 +20311,17 @@ async function createServer(opts) {
19876
20311
  applyLegacyCredentials(legacyRows, opts.config, persistConfigPatch);
19877
20312
  dropLegacyCredentialColumns(opts.db);
19878
20313
  } catch (err) {
19879
- log28.warn("credentials.migration.failed", {
20314
+ log29.warn("credentials.migration.failed", {
19880
20315
  error: describeError(err)
19881
20316
  });
19882
20317
  }
19883
20318
  }
19884
- log28.info("providers.configured", {
20319
+ log29.info("providers.configured", {
19885
20320
  providers: registeredProviderNames({ providers, cdp: opts.config.cdp })
19886
20321
  });
19887
20322
  registry.replace(configuredProviderEntries(opts.config, API_ADAPTERS, cdpChatgptAdapter));
19888
20323
  for (const name of providersWithUnsupportedBatch(providers, (provider) => adapterMap[provider])) {
19889
- log28.warn("provider.batch.unsupported", {
20324
+ log29.warn("provider.batch.unsupported", {
19890
20325
  provider: name,
19891
20326
  message: `providers.${name}.batch.enabled is true, but the ${name} adapter has no batch API; its sweeps run sync.`
19892
20327
  });
@@ -19943,7 +20378,7 @@ async function createServer(opts) {
19943
20378
  async (ctx) => {
19944
20379
  if (!sessionRegistry) return;
19945
20380
  if (!agentProactive) return;
19946
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq34(projects.id, ctx.projectId)).get();
20381
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq35(projects.id, ctx.projectId)).get();
19947
20382
  if (!project) return;
19948
20383
  let content;
19949
20384
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -20417,7 +20852,7 @@ async function createServer(opts) {
20417
20852
  void (async () => {
20418
20853
  try {
20419
20854
  const report = await schedulerClient.runDoctor({ project: projectName, checkIds: scheduledHealthCheckIds() });
20420
- const project = opts.db.select().from(projects).where(eq34(projects.name, projectName)).get();
20855
+ const project = opts.db.select().from(projects).where(eq35(projects.name, projectName)).get();
20421
20856
  if (!project) {
20422
20857
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
20423
20858
  return;
@@ -20611,7 +21046,7 @@ async function createServer(opts) {
20611
21046
  return removed;
20612
21047
  }
20613
21048
  };
20614
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto26.randomBytes(32).toString("hex");
21049
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto27.randomBytes(32).toString("hex");
20615
21050
  const googleConnectionStore = {
20616
21051
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
20617
21052
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -20709,11 +21144,11 @@ async function createServer(opts) {
20709
21144
  })();
20710
21145
  if (opts.config.apiKey) {
20711
21146
  const keyHash = hashApiKey(opts.config.apiKey);
20712
- const existing = opts.db.select().from(apiKeys).where(eq34(apiKeys.keyHash, keyHash)).get();
21147
+ const existing = opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, keyHash)).get();
20713
21148
  if (!existing) {
20714
21149
  const prefix = opts.config.apiKey.slice(0, 12);
20715
21150
  opts.db.insert(apiKeys).values({
20716
- id: `key_${crypto26.randomBytes(8).toString("hex")}`,
21151
+ id: `key_${crypto27.randomBytes(8).toString("hex")}`,
20717
21152
  name: "default",
20718
21153
  keyHash,
20719
21154
  keyPrefix: prefix,
@@ -20728,14 +21163,14 @@ async function createServer(opts) {
20728
21163
  );
20729
21164
  const currentPasswordFingerprint = () => hashSessionToken(opts.config.dashboardPasswordHash ?? "");
20730
21165
  opts.db.delete(dashboardSessions).where(or4(
20731
- eq34(dashboardSessions.apiKeyHash, ""),
20732
- and28(
21166
+ eq35(dashboardSessions.apiKeyHash, ""),
21167
+ and29(
20733
21168
  isNotNull5(dashboardSessions.passwordFingerprint),
20734
21169
  ne4(dashboardSessions.passwordFingerprint, currentPasswordFingerprint())
20735
21170
  )
20736
21171
  )).run();
20737
21172
  if (!opts.config.dashboardPasswordHash) {
20738
- const lastPasswordEventAt = (action) => opts.db.select({ at: max(auditLog.createdAt) }).from(auditLog).where(and28(eq34(auditLog.entityType, "dashboard-password"), eq34(auditLog.action, action))).get()?.at ?? null;
21173
+ 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;
20739
21174
  const createdAt = lastPasswordEventAt("dashboard-password.created");
20740
21175
  const clearedAt = lastPasswordEventAt("dashboard-password.cleared");
20741
21176
  if (createdAt !== null && (clearedAt === null || clearedAt < createdAt)) {
@@ -20750,7 +21185,7 @@ async function createServer(opts) {
20750
21185
  const createSession = (key, passwordBased = false, db = opts.db) => {
20751
21186
  const now = /* @__PURE__ */ new Date();
20752
21187
  db.delete(dashboardSessions).where(lte3(dashboardSessions.expiresAt, now.toISOString())).run();
20753
- const sessionId = crypto26.randomBytes(32).toString("hex");
21188
+ const sessionId = crypto27.randomBytes(32).toString("hex");
20754
21189
  db.insert(dashboardSessions).values({
20755
21190
  tokenHash: hashSessionToken(sessionId),
20756
21191
  apiKeyId: key.id,
@@ -20769,23 +21204,23 @@ async function createServer(opts) {
20769
21204
  currentApiKeyHash: apiKeys.keyHash,
20770
21205
  passwordFingerprint: dashboardSessions.passwordFingerprint,
20771
21206
  expiresAt: dashboardSessions.expiresAt
20772
- }).from(dashboardSessions).innerJoin(apiKeys, eq34(apiKeys.id, dashboardSessions.apiKeyId)).where(and28(eq34(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
21207
+ }).from(dashboardSessions).innerJoin(apiKeys, eq35(apiKeys.id, dashboardSessions.apiKeyId)).where(and29(eq35(dashboardSessions.tokenHash, tokenHash), isNull8(apiKeys.revokedAt))).get();
20773
21208
  if (!session) return null;
20774
21209
  const expiresAt = Date.parse(session.expiresAt);
20775
21210
  if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || session.apiKeyHash !== session.currentApiKeyHash || session.passwordFingerprint !== null && session.passwordFingerprint !== currentPasswordFingerprint()) {
20776
- opts.db.delete(dashboardSessions).where(eq34(dashboardSessions.tokenHash, tokenHash)).run();
21211
+ opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, tokenHash)).run();
20777
21212
  return null;
20778
21213
  }
20779
21214
  return session.apiKeyId;
20780
21215
  };
20781
21216
  const clearSession = (sessionId) => {
20782
21217
  if (sessionId) {
20783
- opts.db.delete(dashboardSessions).where(eq34(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
21218
+ opts.db.delete(dashboardSessions).where(eq35(dashboardSessions.tokenHash, hashSessionToken(sessionId))).run();
20784
21219
  }
20785
21220
  };
20786
21221
  const getDefaultApiKey = () => {
20787
21222
  if (!opts.config.apiKey) return void 0;
20788
- return opts.db.select().from(apiKeys).where(eq34(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
21223
+ return opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
20789
21224
  };
20790
21225
  const setSessionCookie = (reply, sessionId) => {
20791
21226
  reply.header(
@@ -20814,7 +21249,7 @@ async function createServer(opts) {
20814
21249
  if (parts.length !== 2 || parts[0] !== "Bearer" || !parts[1]) return false;
20815
21250
  const presented = Buffer.from(hashApiKey(parts[1]), "hex");
20816
21251
  const expected = Buffer.from(key.keyHash, "hex");
20817
- return presented.length === expected.length && crypto26.timingSafeEqual(presented, expected);
21252
+ return presented.length === expected.length && crypto27.timingSafeEqual(presented, expected);
20818
21253
  };
20819
21254
  const namedAccountsInUse = () => anyUsersExist(opts.db);
20820
21255
  const NAMED_ACCOUNTS_MESSAGE = "This install uses named accounts. Sign in with your name and password.";
@@ -20946,12 +21381,12 @@ async function createServer(opts) {
20946
21381
  return reply.send({ authenticated: true });
20947
21382
  }
20948
21383
  if (apiKey) {
20949
- const key = opts.db.select().from(apiKeys).where(eq34(apiKeys.keyHash, hashApiKey(apiKey))).get();
21384
+ const key = opts.db.select().from(apiKeys).where(eq35(apiKeys.keyHash, hashApiKey(apiKey))).get();
20950
21385
  if (!key || key.revokedAt) {
20951
21386
  const err2 = authInvalid();
20952
21387
  return reply.status(err2.statusCode).send(err2.toJSON());
20953
21388
  }
20954
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq34(apiKeys.id, key.id)).run();
21389
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq35(apiKeys.id, key.id)).run();
20955
21390
  setSessionCookie(reply, createSession(key));
20956
21391
  return reply.send({ authenticated: true });
20957
21392
  }
@@ -21017,7 +21452,7 @@ async function createServer(opts) {
21017
21452
  const sentimentPoller = createSentimentPoller(
21018
21453
  () => sentimentWorker.tick(),
21019
21454
  () => {
21020
- log28.error("sentiment.worker-failed", { reason: "Stored sentiment worker could not finish its tick." });
21455
+ log29.error("sentiment.worker-failed", { reason: "Stored sentiment worker could not finish its tick." });
21021
21456
  }
21022
21457
  );
21023
21458
  const pollSentiment = sentimentPoller.poll;
@@ -21029,7 +21464,7 @@ async function createServer(opts) {
21029
21464
  const recomputeDebts = /* @__PURE__ */ new Map();
21030
21465
  let recomputeSequence = 0;
21031
21466
  const markAnswerFieldRecompute = (projectId, full) => {
21032
- opts.db.update(projects).set({ answerFieldsRecompute: full ? "full" : sql14`CASE WHEN ${projects.answerFieldsRecompute} = 'full' THEN 'full' ELSE 'competitors' END` }).where(eq34(projects.id, projectId)).run();
21467
+ 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();
21033
21468
  };
21034
21469
  const requestAnswerFieldRecompute = (projectId, projectName, scope, reason) => {
21035
21470
  const sequence = ++recomputeSequence;
@@ -21047,10 +21482,10 @@ async function createServer(opts) {
21047
21482
  if (pass.full && debt.latestFull <= sequence) debt.latestFull = 0;
21048
21483
  try {
21049
21484
  if (debt.latest === sequence && debt.latestFull === 0) {
21050
- opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq34(projects.id, projectId)).run();
21485
+ opts.db.update(projects).set({ answerFieldsRecompute: null }).where(eq35(projects.id, projectId)).run();
21051
21486
  if (recomputeDebts.get(projectId) === debt) recomputeDebts.delete(projectId);
21052
21487
  } else if (debt.latestFull === 0) {
21053
- opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and28(eq34(projects.id, projectId), eq34(projects.answerFieldsRecompute, "full"))).run();
21488
+ opts.db.update(projects).set({ answerFieldsRecompute: "competitors" }).where(and29(eq35(projects.id, projectId), eq35(projects.answerFieldsRecompute, "full"))).run();
21054
21489
  }
21055
21490
  } catch (err) {
21056
21491
  app.log.error({ err, projectId }, "could not clear the owed answer field recompute");
@@ -21087,7 +21522,7 @@ async function createServer(opts) {
21087
21522
  await yieldToEventLoop4();
21088
21523
  const reason = (competitorAutoAliasReasons.get(projectId) ?? []).join(",");
21089
21524
  competitorAutoAliasReasons.delete(projectId);
21090
- const project = opts.db.select().from(projects).where(eq34(projects.id, projectId)).get();
21525
+ const project = opts.db.select().from(projects).where(eq35(projects.id, projectId)).get();
21091
21526
  if (!project || project.competitorAutoAliases === "off") return;
21092
21527
  if (project.competitorAutoAliases === "apply") {
21093
21528
  await competitorAutoAliases.request(projectId, reason).catch(() => void 0);
@@ -21287,8 +21722,8 @@ async function createServer(opts) {
21287
21722
  getAgentPin: () => describeAgentPin(opts.config),
21288
21723
  getGoogleMarketingDoctorInput: (ctx) => {
21289
21724
  if (!ctx.project) return null;
21290
- const googleAds = opts.db.select().from(googleAdsConnections).where(eq34(googleAdsConnections.projectId, ctx.project.id)).get();
21291
- const gtm = opts.db.select().from(gtmConnections).where(eq34(gtmConnections.projectId, ctx.project.id)).get();
21725
+ const googleAds = opts.db.select().from(googleAdsConnections).where(eq35(googleAdsConnections.projectId, ctx.project.id)).get();
21726
+ const gtm = opts.db.select().from(gtmConnections).where(eq35(gtmConnections.projectId, ctx.project.id)).get();
21292
21727
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
21293
21728
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
21294
21729
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -21417,7 +21852,7 @@ async function createServer(opts) {
21417
21852
  deps: {
21418
21853
  enqueueAutoExtract: ({ projectId, release: r }) => {
21419
21854
  const now = (/* @__PURE__ */ new Date()).toISOString();
21420
- const runId = crypto26.randomUUID();
21855
+ const runId = crypto27.randomUUID();
21421
21856
  opts.db.insert(runs).values({
21422
21857
  id: runId,
21423
21858
  projectId,
@@ -21538,7 +21973,7 @@ async function createServer(opts) {
21538
21973
  ...inspectOpts,
21539
21974
  config: opts.config
21540
21975
  }).then(() => {
21541
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq34(runs.id, runId)).get();
21976
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq35(runs.id, runId)).get();
21542
21977
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
21543
21978
  return maybeRefreshGscCoverage(
21544
21979
  opts.db,
@@ -21554,6 +21989,13 @@ async function createServer(opts) {
21554
21989
  app.log.error({ runId, err }, "Bing inspect sitemap failed");
21555
21990
  });
21556
21991
  },
21992
+ onBingSyncRequested: (runId, projectId) => {
21993
+ executeBingSync(opts.db, runId, projectId, { config: opts.config }).catch(
21994
+ (err) => {
21995
+ app.log.error({ runId, err }, "Bing sync failed");
21996
+ }
21997
+ );
21998
+ },
21557
21999
  wordpressConnectionStore,
21558
22000
  ga4CredentialStore,
21559
22001
  cloudRunCredentialStore,
@@ -21784,7 +22226,7 @@ async function createServer(opts) {
21784
22226
  }
21785
22227
  ];
21786
22228
  },
21787
- onGenerateQueries: async (providerName, count5, project) => {
22229
+ onGenerateQueries: async (providerName, count6, project) => {
21788
22230
  const provider = registry.get(providerName);
21789
22231
  if (!provider)
21790
22232
  throw new Error(`Provider "${providerName}" is not configured`);
@@ -21798,10 +22240,10 @@ async function createServer(opts) {
21798
22240
  language: project.language,
21799
22241
  existingQueries: project.existingQueries,
21800
22242
  siteText,
21801
- count: count5
22243
+ count: count6
21802
22244
  });
21803
22245
  const raw = await provider.adapter.generateText(prompt, provider.config);
21804
- return parseQueryResponse(raw, count5);
22246
+ return parseQueryResponse(raw, count6);
21805
22247
  },
21806
22248
  onSnapshotRequested: async (input) => {
21807
22249
  return snapshotService.createReport(input);
@@ -22039,7 +22481,7 @@ async function createServer(opts) {
22039
22481
  isBacklinksInstalled: () => isDuckdbInstalled()
22040
22482
  });
22041
22483
  }
22042
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq34(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22484
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq35(researchRuns.status, ResearchRunStatuses.queued)).all()) {
22043
22485
  dispatchResearchRun(run.id, run.projectId, OutcomeTriggers.startup);
22044
22486
  }
22045
22487
  runtimeStartupSettled = true;
@@ -22104,7 +22546,7 @@ function buildQueryGenerationPrompt(ctx) {
22104
22546
  );
22105
22547
  return lines.join("\n");
22106
22548
  }
22107
- function parseQueryResponse(raw, count5) {
22549
+ function parseQueryResponse(raw, count6) {
22108
22550
  const seen = /* @__PURE__ */ new Set();
22109
22551
  const results = [];
22110
22552
  for (const line of raw.split("\n")) {
@@ -22120,7 +22562,7 @@ function parseQueryResponse(raw, count5) {
22120
22562
  if (seen.has(key)) continue;
22121
22563
  seen.add(key);
22122
22564
  results.push(cleaned);
22123
- if (results.length >= count5) break;
22565
+ if (results.length >= count6) break;
22124
22566
  }
22125
22567
  return results;
22126
22568
  }