@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.
Files changed (193) hide show
  1. package/Models/DatabaseModels/AIInsight.ts +30 -0
  2. package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
  3. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
  4. package/Models/DatabaseModels/GlobalConfig.ts +76 -0
  5. package/Models/DatabaseModels/Project.ts +30 -0
  6. package/Models/DatabaseModels/TelemetryException.ts +128 -0
  7. package/Server/API/AIAgentDataAPI.ts +129 -5
  8. package/Server/API/AIInvestigationAPI.ts +87 -0
  9. package/Server/API/EnterpriseLicenseAPI.ts +57 -0
  10. package/Server/API/GlobalConfigAPI.ts +62 -1
  11. package/Server/EnvironmentConfig.ts +22 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.ts +56 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Services/AIRunService.ts +8 -0
  17. package/Server/Services/AnalyticsDatabaseService.ts +48 -45
  18. package/Server/Services/DatabaseService.ts +66 -1
  19. package/Server/Services/TelemetryExceptionService.ts +320 -24
  20. package/Server/Services/WorkflowService.ts +100 -39
  21. package/Server/Types/Workflow/Components/Schedule.ts +57 -32
  22. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +15 -4
  23. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +15 -4
  24. package/Server/Utils/AI/SRE/Insights/InsightScanner.ts +222 -24
  25. package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +225 -9
  26. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +39 -0
  27. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +114 -0
  28. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
  29. package/Server/Utils/Express.ts +13 -0
  30. package/Server/Utils/Telemetry/ExceptionSanitizer.ts +222 -0
  31. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
  32. package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
  33. package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
  34. package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
  35. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
  36. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +205 -1
  37. package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
  38. package/Tests/Server/Services/WorkflowService.test.ts +308 -0
  39. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +150 -141
  40. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +277 -1
  41. package/Tests/Server/Utils/Attribution.test.ts +203 -0
  42. package/Tests/Server/Utils/ExceptionSanitizer.test.ts +70 -0
  43. package/Tests/Server/Utils/InsightTriageClassification.test.ts +57 -0
  44. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
  45. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
  46. package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
  47. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
  48. package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
  49. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
  50. package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
  51. package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
  52. package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
  53. package/Tests/Server/Utils/TelemetryImprovementTaskTrigger.test.ts +141 -0
  54. package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
  55. package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
  56. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
  57. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
  58. package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
  59. package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
  60. package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
  61. package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
  62. package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
  63. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
  64. package/Tests/UI/Components/ModelTableSelectFromColumns.test.ts +259 -0
  65. package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
  66. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
  67. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
  68. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
  69. package/Tests/Utils/Array.test.ts +156 -0
  70. package/Tests/Utils/CronTab.test.ts +199 -0
  71. package/Tests/Utils/ModelImportExport.test.ts +101 -0
  72. package/Tests/Utils/Number.test.ts +179 -0
  73. package/Tests/Utils/VersionUtil.test.ts +348 -0
  74. package/Tests/jest.setup.ts +1 -0
  75. package/Types/AI/CodeFixTaskContext.ts +6 -0
  76. package/Types/AI/CodeFixTaskType.ts +38 -1
  77. package/Types/AI/ExceptionAIClassification.ts +27 -0
  78. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
  79. package/Types/Log/LogScrubPatternType.ts +7 -0
  80. package/Types/Monitor/MonitorStep.ts +24 -4
  81. package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
  82. package/Types/Trace/TraceScrubPatternType.ts +7 -0
  83. package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
  84. package/UI/Components/ModelTable/BaseModelTable.tsx +15 -46
  85. package/UI/Components/ModelTable/SelectFromColumns.ts +129 -0
  86. package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
  87. package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
  88. package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
  89. package/UI/Utils/NotificationMethodUtil.ts +310 -0
  90. package/Utils/CronTab.ts +823 -0
  91. package/Utils/ModelImportExport.ts +21 -6
  92. package/Utils/VersionUtil.ts +260 -0
  93. package/build/dist/Models/DatabaseModels/AIInsight.js +31 -0
  94. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  95. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
  96. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  97. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
  100. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  101. package/build/dist/Models/DatabaseModels/Project.js +31 -0
  102. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  103. package/build/dist/Models/DatabaseModels/TelemetryException.js +134 -0
  104. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  105. package/build/dist/Server/API/AIAgentDataAPI.js +97 -8
  106. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  107. package/build/dist/Server/API/AIInvestigationAPI.js +55 -0
  108. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  109. package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
  110. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  111. package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
  112. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  113. package/build/dist/Server/EnvironmentConfig.js +17 -0
  114. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js +33 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  123. package/build/dist/Server/Services/AIRunService.js +9 -2
  124. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  125. package/build/dist/Server/Services/AnalyticsDatabaseService.js +44 -16
  126. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  127. package/build/dist/Server/Services/DatabaseService.js +53 -1
  128. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  129. package/build/dist/Server/Services/TelemetryExceptionService.js +263 -22
  130. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  131. package/build/dist/Server/Services/WorkflowService.js +80 -30
  132. package/build/dist/Server/Services/WorkflowService.js.map +1 -1
  133. package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
  134. package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
  135. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +12 -4
  136. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  137. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +12 -4
  138. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  139. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js +193 -22
  140. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js.map +1 -1
  141. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +178 -9
  142. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
  143. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +37 -0
  144. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  145. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +98 -0
  146. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js.map +1 -0
  147. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
  148. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
  149. package/build/dist/Server/Utils/Express.js +12 -0
  150. package/build/dist/Server/Utils/Express.js.map +1 -1
  151. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js +149 -0
  152. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js.map +1 -0
  153. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
  154. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
  155. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
  156. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
  157. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
  158. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
  159. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
  160. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
  161. package/build/dist/Types/AI/CodeFixTaskType.js +38 -1
  162. package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -1
  163. package/build/dist/Types/AI/ExceptionAIClassification.js +28 -0
  164. package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -0
  165. package/build/dist/Types/Log/LogScrubPatternType.js +7 -0
  166. package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -1
  167. package/build/dist/Types/Monitor/MonitorStep.js +20 -4
  168. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  169. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
  170. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
  171. package/build/dist/Types/Trace/TraceScrubPatternType.js +7 -0
  172. package/build/dist/Types/Trace/TraceScrubPatternType.js.map +1 -1
  173. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
  174. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  175. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -37
  176. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  177. package/build/dist/UI/Components/ModelTable/SelectFromColumns.js +82 -0
  178. package/build/dist/UI/Components/ModelTable/SelectFromColumns.js.map +1 -0
  179. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
  180. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  181. package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
  182. package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
  183. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
  184. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
  185. package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
  186. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
  187. package/build/dist/Utils/CronTab.js +609 -0
  188. package/build/dist/Utils/CronTab.js.map +1 -0
  189. package/build/dist/Utils/ModelImportExport.js +20 -6
  190. package/build/dist/Utils/ModelImportExport.js.map +1 -1
  191. package/build/dist/Utils/VersionUtil.js +193 -0
  192. package/build/dist/Utils/VersionUtil.js.map +1 -0
  193. 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
- const insight: AIInsight | null = await AIInsightService.findOneById({
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 — deliberately the ONLY write: no feed items,
123
- * no workspace notification, no owner/on-call ping, no metrics,
124
- * and no instrumentation-task trigger. Insights never page and
125
- * never enter the notification pipeline (Preventive-lane rule);
126
- * the summary waits on the Insights page until a human looks.
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
- return lines.join("\n");
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
+ }
@@ -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
  /*