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