@oneuptime/common 11.5.12 → 11.6.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/Models/DatabaseModels/AIInsight.ts +30 -0
- package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
- package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
- package/Models/DatabaseModels/GlobalConfig.ts +76 -0
- package/Models/DatabaseModels/Project.ts +30 -0
- package/Models/DatabaseModels/TelemetryException.ts +128 -0
- package/Server/API/AIAgentDataAPI.ts +129 -5
- package/Server/API/AIInvestigationAPI.ts +87 -0
- package/Server/API/EnterpriseLicenseAPI.ts +57 -0
- package/Server/API/GlobalConfigAPI.ts +62 -1
- package/Server/EnvironmentConfig.ts +22 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.ts +56 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AIRunService.ts +8 -0
- package/Server/Services/AnalyticsDatabaseService.ts +48 -45
- package/Server/Services/DatabaseService.ts +66 -1
- package/Server/Services/TelemetryExceptionService.ts +320 -24
- package/Server/Services/WorkflowService.ts +100 -39
- package/Server/Types/Workflow/Components/Schedule.ts +57 -32
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +15 -4
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +15 -4
- package/Server/Utils/AI/SRE/Insights/InsightScanner.ts +222 -24
- package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +225 -9
- package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +39 -0
- package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +114 -0
- package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
- package/Server/Utils/Express.ts +13 -0
- package/Server/Utils/Telemetry/ExceptionSanitizer.ts +222 -0
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
- package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
- package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
- package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
- package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +205 -1
- package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
- package/Tests/Server/Services/WorkflowService.test.ts +308 -0
- package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +150 -141
- package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +277 -1
- package/Tests/Server/Utils/Attribution.test.ts +203 -0
- package/Tests/Server/Utils/ExceptionSanitizer.test.ts +70 -0
- package/Tests/Server/Utils/InsightTriageClassification.test.ts +57 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
- package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
- package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
- package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
- package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
- package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
- package/Tests/Server/Utils/TelemetryImprovementTaskTrigger.test.ts +141 -0
- package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
- package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
- package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
- package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
- package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
- package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
- package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
- package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
- package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
- package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
- package/Tests/UI/Components/ModelTableSelectFromColumns.test.ts +259 -0
- package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
- package/Tests/Utils/Array.test.ts +156 -0
- package/Tests/Utils/CronTab.test.ts +199 -0
- package/Tests/Utils/ModelImportExport.test.ts +101 -0
- package/Tests/Utils/Number.test.ts +179 -0
- package/Tests/Utils/VersionUtil.test.ts +348 -0
- package/Tests/jest.setup.ts +1 -0
- package/Types/AI/CodeFixTaskContext.ts +6 -0
- package/Types/AI/CodeFixTaskType.ts +38 -1
- package/Types/AI/ExceptionAIClassification.ts +27 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
- package/Types/Log/LogScrubPatternType.ts +7 -0
- package/Types/Monitor/MonitorStep.ts +24 -4
- package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
- package/Types/Trace/TraceScrubPatternType.ts +7 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
- package/UI/Components/ModelTable/BaseModelTable.tsx +15 -46
- package/UI/Components/ModelTable/SelectFromColumns.ts +129 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
- package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
- package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
- package/UI/Utils/NotificationMethodUtil.ts +310 -0
- package/Utils/CronTab.ts +823 -0
- package/Utils/ModelImportExport.ts +21 -6
- package/Utils/VersionUtil.ts +260 -0
- package/build/dist/Models/DatabaseModels/AIInsight.js +31 -0
- package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +31 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +134 -0
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +97 -8
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +55 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +17 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +9 -2
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +44 -16
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +53 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +263 -22
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Services/WorkflowService.js +80 -30
- package/build/dist/Server/Services/WorkflowService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
- package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +12 -4
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +12 -4
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js +193 -22
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +178 -9
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +37 -0
- package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +98 -0
- package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
- package/build/dist/Server/Utils/Express.js +12 -0
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js +149 -0
- package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
- package/build/dist/Types/AI/CodeFixTaskType.js +38 -1
- package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -1
- package/build/dist/Types/AI/ExceptionAIClassification.js +28 -0
- package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -0
- package/build/dist/Types/Log/LogScrubPatternType.js +7 -0
- package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +20 -4
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
- package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
- package/build/dist/Types/Trace/TraceScrubPatternType.js +7 -0
- package/build/dist/Types/Trace/TraceScrubPatternType.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -37
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/SelectFromColumns.js +82 -0
- package/build/dist/UI/Components/ModelTable/SelectFromColumns.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
- package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
- package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
- package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
- package/build/dist/Utils/CronTab.js +609 -0
- package/build/dist/Utils/CronTab.js.map +1 -0
- package/build/dist/Utils/ModelImportExport.js +20 -6
- package/build/dist/Utils/ModelImportExport.js.map +1 -1
- package/build/dist/Utils/VersionUtil.js +193 -0
- package/build/dist/Utils/VersionUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import ObjectID from "../../../../../Types/ObjectID";
|
|
2
2
|
import OneUptimeDate from "../../../../../Types/Date";
|
|
3
3
|
import AIInsight from "../../../../../Models/DatabaseModels/AIInsight";
|
|
4
|
+
import Project from "../../../../../Models/DatabaseModels/Project";
|
|
4
5
|
import AIInsightEvidence from "../../../../../Types/AI/AIInsightEvidence";
|
|
6
|
+
import AIInsightStatus, {
|
|
7
|
+
AIInsightStatusHelper,
|
|
8
|
+
} from "../../../../../Types/AI/AIInsightStatus";
|
|
9
|
+
import AIInsightType from "../../../../../Types/AI/AIInsightType";
|
|
10
|
+
import ExceptionAIClassification from "../../../../../Types/AI/ExceptionAIClassification";
|
|
5
11
|
import AIInsightService from "../../../../Services/AIInsightService";
|
|
12
|
+
import ProjectService from "../../../../Services/ProjectService";
|
|
13
|
+
import TelemetryExceptionService from "../../../../Services/TelemetryExceptionService";
|
|
6
14
|
import { AI_INSIGHT_TRIAGE_FEATURE } from "../../../../Services/AIService";
|
|
7
15
|
import AIInvestigationEngine from "../AIInvestigationEngine";
|
|
8
16
|
import AIInvestigationQueue from "../InvestigationQueue";
|
|
17
|
+
import InsightFixRouting, { InsightFixRoutingResult } from "./FixRouting";
|
|
9
18
|
import { ConfidenceSignal } from "../ConfidenceSignal";
|
|
10
19
|
import { ObservabilityAssistantResult } from "../../Chat/ObservabilityAssistant";
|
|
20
|
+
import ToolResultSerializer from "../../Toolbox/Serializer";
|
|
11
21
|
import logger from "../../../Logger";
|
|
12
22
|
import CaptureSpan from "../../../Telemetry/CaptureSpan";
|
|
13
23
|
|
|
@@ -44,7 +54,17 @@ const PREVENTIVE_TRIAGE_FRAMING: string = `IMPORTANT — this is PREVENTIVE TRIA
|
|
|
44
54
|
- Establish the most likely root cause of the finding.
|
|
45
55
|
- Estimate the blast radius: which services/users/operations are affected, and how badly.
|
|
46
56
|
- Recommend exactly ONE next action (the single most useful thing an engineer should do first).
|
|
47
|
-
Cite the evidence for every factual claim. If the telemetry cannot support a conclusion, say so plainly. Do not refer to "the incident" — there is none
|
|
57
|
+
Cite the evidence for every factual claim. If the telemetry cannot support a conclusion, say so plainly. Do not refer to "the incident" — there is none.
|
|
58
|
+
|
|
59
|
+
CLASSIFICATION — you MUST end your analysis with one line in EXACTLY this format, on its own line, with nothing after it:
|
|
60
|
+
Classification: <verdict>
|
|
61
|
+
where <verdict> is exactly one of: code-fault, user-error, expected-denial, infrastructure, unknown.
|
|
62
|
+
- code-fault: a defect in the monitored code that a code change should fix.
|
|
63
|
+
- user-error: an expected consequence of invalid end-user input (bad parameters, malformed values, garbage in a URL). The code rejected it correctly; the input was wrong.
|
|
64
|
+
- expected-denial: an intentional check doing its job — authentication/authorization failures, plan or paywall rejections, rate limits, or security scanners/fuzzers tripping validation on purpose-built probes.
|
|
65
|
+
- infrastructure: an environmental condition (network timeout, connection reset, resource exhaustion, dependency outage) rather than a logic defect.
|
|
66
|
+
- unknown: the evidence does not support a confident verdict.
|
|
67
|
+
This verdict gates automation: ONLY code-fault findings may get an automatic fix pull request, so classify conservatively — when torn between code-fault and anything else, pick the other one.`;
|
|
48
68
|
|
|
49
69
|
export default class InsightTriageRunner {
|
|
50
70
|
/*
|
|
@@ -63,11 +83,13 @@ export default class InsightTriageRunner {
|
|
|
63
83
|
const { aiRunId, projectId, sentinelInsightId, attemptCount } = data;
|
|
64
84
|
|
|
65
85
|
let contextSummary: string;
|
|
86
|
+
let insight: AIInsight | null = null;
|
|
66
87
|
try {
|
|
67
|
-
|
|
88
|
+
insight = await AIInsightService.findOneById({
|
|
68
89
|
id: sentinelInsightId,
|
|
69
90
|
select: {
|
|
70
91
|
_id: true,
|
|
92
|
+
projectId: true,
|
|
71
93
|
insightType: true,
|
|
72
94
|
severity: true,
|
|
73
95
|
title: true,
|
|
@@ -75,6 +97,7 @@ export default class InsightTriageRunner {
|
|
|
75
97
|
evidence: true,
|
|
76
98
|
serviceName: true,
|
|
77
99
|
traceId: true,
|
|
100
|
+
telemetryExceptionId: true,
|
|
78
101
|
metricName: true,
|
|
79
102
|
firstSeenAt: true,
|
|
80
103
|
lastSeenAt: true,
|
|
@@ -118,30 +141,217 @@ export default class InsightTriageRunner {
|
|
|
118
141
|
confidence: ConfidenceSignal;
|
|
119
142
|
result: ObservabilityAssistantResult;
|
|
120
143
|
}): Promise<void> => {
|
|
144
|
+
const classification: ExceptionAIClassification =
|
|
145
|
+
this.parseClassification(postData.analysisMarkdown);
|
|
146
|
+
|
|
121
147
|
/*
|
|
122
|
-
* The quiet inbox —
|
|
123
|
-
* no workspace notification, no owner/on-call
|
|
124
|
-
* and no instrumentation-task trigger. Insights
|
|
125
|
-
* never enter the notification pipeline
|
|
126
|
-
* the summary waits on the Insights page
|
|
148
|
+
* The quiet inbox — the summary and verdict land on the insight
|
|
149
|
+
* row: no feed items, no workspace notification, no owner/on-call
|
|
150
|
+
* ping, no metrics, and no instrumentation-task trigger. Insights
|
|
151
|
+
* never page and never enter the notification pipeline
|
|
152
|
+
* (Preventive-lane rule); the summary waits on the Insights page
|
|
153
|
+
* until a human looks.
|
|
127
154
|
*/
|
|
128
155
|
await AIInsightService.updateOneById({
|
|
129
156
|
id: sentinelInsightId,
|
|
130
157
|
data: {
|
|
131
158
|
triageSummaryMarkdown: postData.analysisMarkdown,
|
|
132
159
|
triageCompletedAt: OneUptimeDate.getCurrentDate(),
|
|
160
|
+
classification: classification,
|
|
133
161
|
},
|
|
134
162
|
props: { isRoot: true },
|
|
135
163
|
});
|
|
136
164
|
|
|
137
165
|
logger.debug(
|
|
138
|
-
`AI insights: triage summary posted for insight ${sentinelInsightId.toString()} (run ${aiRunId.toString()}, confident=${postData.confidence.confident} via ${postData.confidence.source}).`,
|
|
166
|
+
`AI insights: triage summary posted for insight ${sentinelInsightId.toString()} (run ${aiRunId.toString()}, classification=${classification}, confident=${postData.confidence.confident} via ${postData.confidence.source}).`,
|
|
139
167
|
);
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
* Verdict-driven follow-through. Best-effort by design: the
|
|
171
|
+
* triage summary is already posted, and a failure here must not
|
|
172
|
+
* fail the triage run itself.
|
|
173
|
+
*/
|
|
174
|
+
try {
|
|
175
|
+
await this.actOnClassification({
|
|
176
|
+
insight: insight!,
|
|
177
|
+
classification: classification,
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.error(
|
|
181
|
+
`AI insights: post-triage action failed for insight ${sentinelInsightId.toString()} (verdict ${classification}): ${error}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
140
184
|
},
|
|
141
185
|
},
|
|
142
186
|
});
|
|
143
187
|
}
|
|
144
188
|
|
|
189
|
+
/*
|
|
190
|
+
* Parse the mandatory trailing "Classification: <verdict>" line out of
|
|
191
|
+
* the triage analysis. The LAST match wins (the model may discuss the
|
|
192
|
+
* taxonomy earlier in its reasoning). Anything unparseable is Unknown —
|
|
193
|
+
* and Unknown never routes a fix, so a malformed answer fails closed.
|
|
194
|
+
*/
|
|
195
|
+
public static parseClassification(
|
|
196
|
+
analysisMarkdown: string,
|
|
197
|
+
): ExceptionAIClassification {
|
|
198
|
+
const matches: Array<RegExpMatchArray> = Array.from(
|
|
199
|
+
(analysisMarkdown || "").matchAll(
|
|
200
|
+
/^\s*\**\s*classification\s*\**\s*[:=]\s*\**\s*(code-fault|user-error|expected-denial|infrastructure|unknown)\b/gim,
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const last: RegExpMatchArray | undefined = matches[matches.length - 1];
|
|
205
|
+
|
|
206
|
+
if (!last || !last[1]) {
|
|
207
|
+
return ExceptionAIClassification.Unknown;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return last[1].toLowerCase() as ExceptionAIClassification;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
* Act on the triage verdict:
|
|
215
|
+
*
|
|
216
|
+
* 1. Stamp the verdict onto the exception group row (aiClassification)
|
|
217
|
+
* so the exceptions list can filter on it and the fix lane can skip
|
|
218
|
+
* known-non-defects without re-triaging.
|
|
219
|
+
* 2. code-fault → route the automatic fix (InsightFixRouting owns every
|
|
220
|
+
* gate: enableAi, enableInsightFixTasks, budget, readiness, dedupe)
|
|
221
|
+
* and flip the insight to FixOpened when a run was queued.
|
|
222
|
+
* 3. expected-denial → optionally auto-archive the exception group when
|
|
223
|
+
* the project opted in (autoArchiveNonActionableExceptions). Only
|
|
224
|
+
* expected denials are archived — user errors and infrastructure
|
|
225
|
+
* conditions stay visible for a human to judge.
|
|
226
|
+
*/
|
|
227
|
+
private static async actOnClassification(data: {
|
|
228
|
+
insight: AIInsight;
|
|
229
|
+
classification: ExceptionAIClassification;
|
|
230
|
+
}): Promise<void> {
|
|
231
|
+
const { insight, classification } = data;
|
|
232
|
+
|
|
233
|
+
if (!insight.id || !insight.projectId) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* The verdict stamp on the exception group is metadata and always
|
|
239
|
+
* lands; the AUTOMATION below (fix PR, auto-archive) additionally
|
|
240
|
+
* requires that no human closed the insight while the triage run sat
|
|
241
|
+
* in the queue or executed. The `insight` object is a snapshot from
|
|
242
|
+
* claim time — minutes to half an hour old — so the status MUST be
|
|
243
|
+
* re-read here: an insight a human Dismissed or Resolved in the
|
|
244
|
+
* meantime gets no PR and keeps its terminal status.
|
|
245
|
+
*/
|
|
246
|
+
if (insight.telemetryExceptionId) {
|
|
247
|
+
await TelemetryExceptionService.updateOneById({
|
|
248
|
+
id: insight.telemetryExceptionId,
|
|
249
|
+
data: {
|
|
250
|
+
aiClassification: classification,
|
|
251
|
+
},
|
|
252
|
+
props: { isRoot: true },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const currentInsight: AIInsight | null = await AIInsightService.findOneById(
|
|
257
|
+
{
|
|
258
|
+
id: insight.id,
|
|
259
|
+
select: {
|
|
260
|
+
_id: true,
|
|
261
|
+
status: true,
|
|
262
|
+
},
|
|
263
|
+
props: { isRoot: true },
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
!currentInsight ||
|
|
269
|
+
(currentInsight.status &&
|
|
270
|
+
AIInsightStatusHelper.isTerminalStatus(currentInsight.status))
|
|
271
|
+
) {
|
|
272
|
+
logger.debug(
|
|
273
|
+
`AI insights: skipping post-triage automation for insight ${insight.id.toString()} — insight is ${currentInsight?.status || "deleted"}.`,
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const project: Project | null = await ProjectService.findOneById({
|
|
279
|
+
id: insight.projectId,
|
|
280
|
+
select: {
|
|
281
|
+
_id: true,
|
|
282
|
+
enableAi: true,
|
|
283
|
+
enableInsightFixTasks: true,
|
|
284
|
+
autoArchiveNonActionableExceptions: true,
|
|
285
|
+
},
|
|
286
|
+
props: { isRoot: true },
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (!project) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/*
|
|
294
|
+
* TraceLatencyRegression insights are fix-routed DETERMINISTICALLY at
|
|
295
|
+
* scan time (their span-tree evidence needs no LLM verdict — see
|
|
296
|
+
* InsightScanner.routeNewInsight); routing them again here would
|
|
297
|
+
* duplicate the run/PR. The verdict gate applies to exception
|
|
298
|
+
* insights only.
|
|
299
|
+
*/
|
|
300
|
+
const isVerdictGatedInsight: boolean =
|
|
301
|
+
insight.insightType !== AIInsightType.TraceLatencyRegression;
|
|
302
|
+
|
|
303
|
+
if (
|
|
304
|
+
classification === ExceptionAIClassification.CodeFault &&
|
|
305
|
+
isVerdictGatedInsight
|
|
306
|
+
) {
|
|
307
|
+
const fixResult: InsightFixRoutingResult =
|
|
308
|
+
await InsightFixRouting.routeInsightFix({
|
|
309
|
+
insight: insight,
|
|
310
|
+
project: project,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (fixResult.fixAiRunId) {
|
|
314
|
+
/*
|
|
315
|
+
* Conditional flip: only a still-ActionRequired insight moves to
|
|
316
|
+
* FixOpened, so a dismissal landing between the status re-read
|
|
317
|
+
* above and this write can never be clobbered.
|
|
318
|
+
*/
|
|
319
|
+
await AIInsightService.updateOneBy({
|
|
320
|
+
query: {
|
|
321
|
+
_id: insight.id.toString(),
|
|
322
|
+
status: AIInsightStatus.ActionRequired,
|
|
323
|
+
},
|
|
324
|
+
data: {
|
|
325
|
+
status: AIInsightStatus.FixOpened,
|
|
326
|
+
fixAiRunId: fixResult.fixAiRunId,
|
|
327
|
+
},
|
|
328
|
+
props: { isRoot: true },
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (
|
|
336
|
+
classification === ExceptionAIClassification.ExpectedDenial &&
|
|
337
|
+
project.autoArchiveNonActionableExceptions === true &&
|
|
338
|
+
insight.telemetryExceptionId
|
|
339
|
+
) {
|
|
340
|
+
await TelemetryExceptionService.updateOneById({
|
|
341
|
+
id: insight.telemetryExceptionId,
|
|
342
|
+
data: {
|
|
343
|
+
isArchived: true,
|
|
344
|
+
markedAsArchivedAt: OneUptimeDate.getCurrentDate(),
|
|
345
|
+
},
|
|
346
|
+
props: { isRoot: true },
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
logger.debug(
|
|
350
|
+
`AI insights: auto-archived exception ${insight.telemetryExceptionId.toString()} (expected-denial verdict, project opted in).`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
145
355
|
// Build the compact insight record that seeds the triage.
|
|
146
356
|
private static buildInsightSummary(insight: AIInsight): string {
|
|
147
357
|
const lines: Array<string> = [];
|
|
@@ -191,7 +401,13 @@ export default class InsightTriageRunner {
|
|
|
191
401
|
lines.push(renderedEvidence);
|
|
192
402
|
}
|
|
193
403
|
|
|
194
|
-
|
|
404
|
+
/*
|
|
405
|
+
* Detector detailMarkdown and evidence quote raw exception messages —
|
|
406
|
+
* sweep secrets/PII before the text reaches the LLM provider. (The
|
|
407
|
+
* chat/investigation toolbox path already redacts its tool results;
|
|
408
|
+
* this closes the same gap for the detector-supplied context.)
|
|
409
|
+
*/
|
|
410
|
+
return ToolResultSerializer.redact(lines.join("\n")).text;
|
|
195
411
|
}
|
|
196
412
|
|
|
197
413
|
/*
|
|
@@ -116,6 +116,45 @@ export default class SubjectCodeFixRun {
|
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/*
|
|
120
|
+
* Per-(service, recipe) dedupe for the service-scoped instrumentation
|
|
121
|
+
* recipes (ImproveLogging / ImproveTracing): at most one non-terminal
|
|
122
|
+
* run of a recipe per (project, telemetryServiceId). Same in-memory
|
|
123
|
+
* taskContext matching as the per-trace guard above — the id lives in
|
|
124
|
+
* JSON the Query layer cannot filter on, and the candidate set is
|
|
125
|
+
* bounded and rare.
|
|
126
|
+
*/
|
|
127
|
+
@CaptureSpan()
|
|
128
|
+
public static async findNonTerminalRunForTelemetryService(data: {
|
|
129
|
+
projectId: ObjectID;
|
|
130
|
+
taskType: CodeFixTaskType;
|
|
131
|
+
telemetryServiceId: string;
|
|
132
|
+
}): Promise<AIRun | null> {
|
|
133
|
+
const activeRuns: Array<AIRun> = await AIRunService.findBy({
|
|
134
|
+
query: {
|
|
135
|
+
projectId: data.projectId,
|
|
136
|
+
runType: AIRunType.CodeFix,
|
|
137
|
+
codeFixTaskType: data.taskType,
|
|
138
|
+
status: QueryHelper.notIn([
|
|
139
|
+
AIRunStatus.Completed,
|
|
140
|
+
AIRunStatus.Error,
|
|
141
|
+
AIRunStatus.Cancelled,
|
|
142
|
+
AIRunStatus.Stale,
|
|
143
|
+
]),
|
|
144
|
+
},
|
|
145
|
+
select: { _id: true, taskContext: true },
|
|
146
|
+
limit: LIMIT_PER_PROJECT,
|
|
147
|
+
skip: 0,
|
|
148
|
+
props: { isRoot: true },
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
activeRuns.find((run: AIRun): boolean => {
|
|
153
|
+
return run.taskContext?.telemetryServiceId === data.telemetryServiceId;
|
|
154
|
+
}) || null
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
119
158
|
/*
|
|
120
159
|
* Record the durable intent as a Queued CodeFix AIRun that the external
|
|
121
160
|
* agent worker claims via /ai-agent-task/get-pending-task. `userId` is
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
2
|
+
import CodeFixTaskType from "../../../../Types/AI/CodeFixTaskType";
|
|
3
|
+
import CodeFixTaskContext from "../../../../Types/AI/CodeFixTaskContext";
|
|
4
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
5
|
+
import AIRun from "../../../../Models/DatabaseModels/AIRun";
|
|
6
|
+
import Service from "../../../../Models/DatabaseModels/Service";
|
|
7
|
+
import ServiceService from "../../../Services/ServiceService";
|
|
8
|
+
import SubjectCodeFixRun from "./SubjectCodeFixRun";
|
|
9
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* AI SRE — the service-scoped instrumentation-improvement triggers:
|
|
13
|
+
* ImproveLogging and ImproveTracing. One click on a service's Logs or
|
|
14
|
+
* Traces page opens a draft PR that improves that service's logging or
|
|
15
|
+
* tracing hygiene — parameterized log messages, correct severities, spans
|
|
16
|
+
* on uninstrumented paths, proper exception recording — WITHOUT changing
|
|
17
|
+
* behavior.
|
|
18
|
+
*
|
|
19
|
+
* Like FixFromIncident and FixPerformance these are human-triggered from a
|
|
20
|
+
* user-facing endpoint (POST /ai-investigation/
|
|
21
|
+
* create-telemetry-improvement-task), so there is no project opt-in flag —
|
|
22
|
+
* the human in the loop IS the gate — and every unmet gate FAILS EARLY
|
|
23
|
+
* with a clear message. The recipe's grounding is the SERVICE: repository
|
|
24
|
+
* resolution at task-details time uses the service name (name-match /
|
|
25
|
+
* only-repository), the same path the incident-subject recipes use.
|
|
26
|
+
*/
|
|
27
|
+
export default class TelemetryImprovementTaskTrigger {
|
|
28
|
+
/*
|
|
29
|
+
* Gate and enqueue an ImproveLogging / ImproveTracing CodeFix run for a
|
|
30
|
+
* telemetry service. The caller must already have loaded the service
|
|
31
|
+
* under the USER's permissions (the access check) — this method reads
|
|
32
|
+
* and writes as root.
|
|
33
|
+
*
|
|
34
|
+
* Throws BadDataException naming the failed gate: unsupported recipe, no
|
|
35
|
+
* GitHub-App repository, a duplicate active run for the same service, or
|
|
36
|
+
* (via enqueueSubjectCodeFixRun) the daily fix-run budget.
|
|
37
|
+
*/
|
|
38
|
+
@CaptureSpan()
|
|
39
|
+
public static async createTelemetryImprovementTask(data: {
|
|
40
|
+
projectId: ObjectID;
|
|
41
|
+
telemetryServiceId: ObjectID;
|
|
42
|
+
taskType: CodeFixTaskType;
|
|
43
|
+
userId: ObjectID;
|
|
44
|
+
}): Promise<AIRun> {
|
|
45
|
+
if (
|
|
46
|
+
data.taskType !== CodeFixTaskType.ImproveLogging &&
|
|
47
|
+
data.taskType !== CodeFixTaskType.ImproveTracing
|
|
48
|
+
) {
|
|
49
|
+
throw new BadDataException(
|
|
50
|
+
`Task type "${data.taskType}" is not a telemetry-improvement recipe. Supported: ${CodeFixTaskType.ImproveLogging}, ${CodeFixTaskType.ImproveTracing}.`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const service: Service | null = await ServiceService.findOneById({
|
|
55
|
+
id: data.telemetryServiceId,
|
|
56
|
+
select: {
|
|
57
|
+
_id: true,
|
|
58
|
+
name: true,
|
|
59
|
+
projectId: true,
|
|
60
|
+
},
|
|
61
|
+
props: { isRoot: true },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
!service ||
|
|
66
|
+
service.projectId?.toString() !== data.projectId.toString()
|
|
67
|
+
) {
|
|
68
|
+
throw new BadDataException("Telemetry service not found.");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Gate — a repository the agent can actually open a PR against.
|
|
72
|
+
const hasConnectedRepository: boolean =
|
|
73
|
+
await SubjectCodeFixRun.hasGitHubAppConnectedRepository(data.projectId);
|
|
74
|
+
|
|
75
|
+
if (!hasConnectedRepository) {
|
|
76
|
+
throw new BadDataException(
|
|
77
|
+
"No GitHub-App-connected repository exists for this project, so the agent has nowhere to open the pull request. Connect one under AI > Code Repositories.",
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Gate — per-(service, recipe) dedupe: repeated clicks must not fan
|
|
83
|
+
* out into duplicate PRs.
|
|
84
|
+
*/
|
|
85
|
+
const existingRun: AIRun | null =
|
|
86
|
+
await SubjectCodeFixRun.findNonTerminalRunForTelemetryService({
|
|
87
|
+
projectId: data.projectId,
|
|
88
|
+
taskType: data.taskType,
|
|
89
|
+
telemetryServiceId: data.telemetryServiceId.toString(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (existingRun) {
|
|
93
|
+
throw new BadDataException(
|
|
94
|
+
`An ${
|
|
95
|
+
data.taskType === CodeFixTaskType.ImproveLogging
|
|
96
|
+
? "improve-logging"
|
|
97
|
+
: "improve-tracing"
|
|
98
|
+
} task is already queued or running for this service. Track its progress on the AI > Tasks page.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const taskContext: CodeFixTaskContext = {
|
|
103
|
+
telemetryServiceId: data.telemetryServiceId.toString(),
|
|
104
|
+
serviceName: service.name || undefined,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return SubjectCodeFixRun.enqueueSubjectCodeFixRun({
|
|
108
|
+
projectId: data.projectId,
|
|
109
|
+
taskType: data.taskType,
|
|
110
|
+
userId: data.userId,
|
|
111
|
+
taskContext,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ambient context that makes ClickHouse inserts idempotent across queue
|
|
5
|
+
* retries. The telemetry queue worker wraps each job in
|
|
6
|
+
* `runWithInsertDedup(jobId, ...)`; token consumers then derive
|
|
7
|
+
* `insert_deduplication_token = "<tokenBase>:<table>:<chunkIndex>"`, so a
|
|
8
|
+
* stalled-job recovery or attempts-based retry that re-processes the same
|
|
9
|
+
* payload re-issues byte-identical tokens and ClickHouse drops the duplicate
|
|
10
|
+
* blocks (on replicated tables; on plain MergeTree the token is ignored
|
|
11
|
+
* unless non_replicated_deduplication_window is set — no harm either way).
|
|
12
|
+
* The chunk counter is per table because one job inserts into several tables
|
|
13
|
+
* (e.g. Span + ExceptionInstance) in a deterministic order.
|
|
14
|
+
*
|
|
15
|
+
* Two consumers exist:
|
|
16
|
+
* - AnalyticsDatabaseService.insertJsonRows stamps direct inserts that run
|
|
17
|
+
* inside the context without an explicit token.
|
|
18
|
+
* - TelemetryFanInWriter captures a token per submission AT SUBMIT TIME
|
|
19
|
+
* (still inside the job's context) so cross-job batching downstream can
|
|
20
|
+
* preserve per-job retry idempotence.
|
|
21
|
+
*/
|
|
22
|
+
export interface InsertDedupContextStore {
|
|
23
|
+
tokenBase: string;
|
|
24
|
+
chunkIndexByTable: Map<string, number>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const insertDedupContext: AsyncLocalStorage<InsertDedupContextStore> =
|
|
28
|
+
new AsyncLocalStorage<InsertDedupContextStore>();
|
|
29
|
+
|
|
30
|
+
export function runWithInsertDedup<T>(
|
|
31
|
+
tokenBase: string,
|
|
32
|
+
fn: () => Promise<T>,
|
|
33
|
+
): Promise<T> {
|
|
34
|
+
return insertDedupContext.run(
|
|
35
|
+
{ tokenBase, chunkIndexByTable: new Map<string, number>() },
|
|
36
|
+
fn,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Consume the next deterministic dedup token for `tableName` from the
|
|
42
|
+
* ambient context, or undefined when called outside a runWithInsertDedup
|
|
43
|
+
* scope. Consuming advances the per-table chunk counter, so calls must
|
|
44
|
+
* happen in a deterministic order relative to the payload — true for the
|
|
45
|
+
* ingest services, whose chunking is a pure function of the (byte-identical
|
|
46
|
+
* across retries) job body.
|
|
47
|
+
*/
|
|
48
|
+
export function nextInsertDedupToken(tableName: string): string | undefined {
|
|
49
|
+
const store: InsertDedupContextStore | undefined =
|
|
50
|
+
insertDedupContext.getStore();
|
|
51
|
+
|
|
52
|
+
if (!store) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const chunkIndex: number = store.chunkIndexByTable.get(tableName) ?? 0;
|
|
57
|
+
store.chunkIndexByTable.set(tableName, chunkIndex + 1);
|
|
58
|
+
return `${store.tokenBase}:${tableName}:${chunkIndex}`;
|
|
59
|
+
}
|
package/Server/Utils/Express.ts
CHANGED
|
@@ -103,6 +103,19 @@ class Express {
|
|
|
103
103
|
|
|
104
104
|
if (!this.httpServer) {
|
|
105
105
|
this.httpServer = createServer(this.app);
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
* Keep upstream (nginx) connection reuse safe. When the ingress pools
|
|
109
|
+
* idle upstream connections (proxy keepalive), the backend must NOT be
|
|
110
|
+
* the side that closes an idle socket first — otherwise nginx can pick a
|
|
111
|
+
* socket the backend already closed and 502 a request it cannot safely
|
|
112
|
+
* retry (e.g. a POST on the telemetry ingest path). Node's default
|
|
113
|
+
* keepAliveTimeout (~5s) is well below nginx's upstream keepalive_timeout
|
|
114
|
+
* (60s), so raise it above that window. headersTimeout must stay greater
|
|
115
|
+
* than keepAliveTimeout or Node can cut an idle socket mid-request.
|
|
116
|
+
*/
|
|
117
|
+
this.httpServer.keepAliveTimeout = 75 * 1000;
|
|
118
|
+
this.httpServer.headersTimeout = 76 * 1000;
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
/*
|