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