@oneuptime/common 11.5.11 → 11.5.13

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 (142) hide show
  1. package/Models/AnalyticsModels/NetworkFlow.ts +12 -1
  2. package/Models/DatabaseModels/AIInsight.ts +30 -0
  3. package/Models/DatabaseModels/Project.ts +30 -0
  4. package/Models/DatabaseModels/TelemetryException.ts +128 -0
  5. package/Server/API/AIAgentDataAPI.ts +129 -5
  6. package/Server/API/AIInvestigationAPI.ts +87 -0
  7. package/Server/API/StatusPageAPI.ts +14 -1
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.ts +56 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -2
  10. package/Server/Services/AIRunService.ts +8 -0
  11. package/Server/Services/IncidentService.ts +62 -67
  12. package/Server/Services/MonitorGroupService.ts +15 -2
  13. package/Server/Services/MonitorService.ts +91 -3
  14. package/Server/Services/MonitorStatusTimelineService.ts +285 -100
  15. package/Server/Services/StatusPageService.ts +59 -34
  16. package/Server/Services/TelemetryExceptionService.ts +320 -24
  17. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +15 -4
  18. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +15 -4
  19. package/Server/Utils/AI/SRE/Insights/InsightScanner.ts +222 -24
  20. package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +225 -9
  21. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +39 -0
  22. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +114 -0
  23. package/Server/Utils/Monitor/MonitorResource.ts +43 -14
  24. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +32 -7
  25. package/Server/Utils/Monitor/MonitorStatusTimelineReconciler.ts +362 -0
  26. package/Server/Utils/Telemetry/ExceptionSanitizer.ts +222 -0
  27. package/Tests/Server/Services/MonitorStatusTimelineService.test.ts +235 -0
  28. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +205 -1
  29. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +150 -141
  30. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +277 -1
  31. package/Tests/Server/Utils/ExceptionSanitizer.test.ts +70 -0
  32. package/Tests/Server/Utils/InsightTriageClassification.test.ts +57 -0
  33. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineReconciler.test.ts +555 -0
  34. package/Tests/Server/Utils/TelemetryImprovementTaskTrigger.test.ts +141 -0
  35. package/Tests/Types/Database/CompareOperatorWireSerialization.test.ts +216 -0
  36. package/Tests/Types/Database/GreaterThan.test.ts +5 -1
  37. package/Tests/Types/Database/GreaterThanOrEqual.test.ts +5 -1
  38. package/Tests/Types/Database/GreaterThanOrNull.test.ts +24 -1
  39. package/Tests/Types/Database/LessThan.test.ts +5 -1
  40. package/Tests/Types/Database/LessThanOrEqual.test.ts +24 -1
  41. package/Tests/Types/Database/LessThanOrNull.test.ts +5 -1
  42. package/Tests/UI/Components/ModelTableSelectFromColumns.test.ts +259 -0
  43. package/Tests/Utils/Uptime/UptimeUtil.test.ts +726 -0
  44. package/Types/AI/CodeFixTaskContext.ts +6 -0
  45. package/Types/AI/CodeFixTaskType.ts +38 -1
  46. package/Types/AI/ExceptionAIClassification.ts +27 -0
  47. package/Types/BaseDatabase/GreaterThan.ts +8 -1
  48. package/Types/BaseDatabase/GreaterThanOrEqual.ts +8 -1
  49. package/Types/BaseDatabase/GreaterThanOrNull.ts +9 -1
  50. package/Types/BaseDatabase/LessThan.ts +8 -1
  51. package/Types/BaseDatabase/LessThanOrEqual.ts +9 -1
  52. package/Types/BaseDatabase/LessThanOrNull.ts +8 -1
  53. package/Types/Log/LogScrubPatternType.ts +7 -0
  54. package/Types/Trace/TraceScrubPatternType.ts +7 -0
  55. package/UI/Components/ModelTable/BaseModelTable.tsx +15 -46
  56. package/UI/Components/ModelTable/SelectFromColumns.ts +129 -0
  57. package/Utils/StatusPage/ResourceUptime.ts +11 -1
  58. package/Utils/Uptime/UptimeUtil.ts +142 -36
  59. package/build/dist/Models/AnalyticsModels/NetworkFlow.js +12 -1
  60. package/build/dist/Models/AnalyticsModels/NetworkFlow.js.map +1 -1
  61. package/build/dist/Models/DatabaseModels/AIInsight.js +31 -0
  62. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  63. package/build/dist/Models/DatabaseModels/Project.js +31 -0
  64. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  65. package/build/dist/Models/DatabaseModels/TelemetryException.js +134 -0
  66. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  67. package/build/dist/Server/API/AIAgentDataAPI.js +97 -8
  68. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  69. package/build/dist/Server/API/AIInvestigationAPI.js +55 -0
  70. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  71. package/build/dist/Server/API/StatusPageAPI.js +12 -2
  72. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  73. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js +33 -0
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784640000000-AddExceptionTriageAndPrivacyColumns.js.map +1 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -2
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  77. package/build/dist/Server/Services/AIRunService.js +9 -2
  78. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  79. package/build/dist/Server/Services/IncidentService.js +47 -56
  80. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  81. package/build/dist/Server/Services/MonitorGroupService.js +15 -2
  82. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  83. package/build/dist/Server/Services/MonitorService.js +66 -3
  84. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  85. package/build/dist/Server/Services/MonitorStatusTimelineService.js +244 -102
  86. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  87. package/build/dist/Server/Services/StatusPageService.js +57 -33
  88. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  89. package/build/dist/Server/Services/TelemetryExceptionService.js +263 -22
  90. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  91. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +12 -4
  92. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  93. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +12 -4
  94. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  95. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js +193 -22
  96. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js.map +1 -1
  97. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +178 -9
  98. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -1
  99. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +37 -0
  100. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  101. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +98 -0
  102. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js.map +1 -0
  103. package/build/dist/Server/Utils/Monitor/MonitorResource.js +34 -12
  104. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  105. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +25 -8
  106. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  107. package/build/dist/Server/Utils/Monitor/MonitorStatusTimelineReconciler.js +268 -0
  108. package/build/dist/Server/Utils/Monitor/MonitorStatusTimelineReconciler.js.map +1 -0
  109. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js +149 -0
  110. package/build/dist/Server/Utils/Telemetry/ExceptionSanitizer.js.map +1 -0
  111. package/build/dist/Types/AI/CodeFixTaskType.js +38 -1
  112. package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -1
  113. package/build/dist/Types/AI/ExceptionAIClassification.js +28 -0
  114. package/build/dist/Types/AI/ExceptionAIClassification.js.map +1 -0
  115. package/build/dist/Types/BaseDatabase/GreaterThan.js +8 -1
  116. package/build/dist/Types/BaseDatabase/GreaterThan.js.map +1 -1
  117. package/build/dist/Types/BaseDatabase/GreaterThanOrEqual.js +8 -1
  118. package/build/dist/Types/BaseDatabase/GreaterThanOrEqual.js.map +1 -1
  119. package/build/dist/Types/BaseDatabase/GreaterThanOrNull.js +9 -1
  120. package/build/dist/Types/BaseDatabase/GreaterThanOrNull.js.map +1 -1
  121. package/build/dist/Types/BaseDatabase/LessThan.js +8 -1
  122. package/build/dist/Types/BaseDatabase/LessThan.js.map +1 -1
  123. package/build/dist/Types/BaseDatabase/LessThanOrEqual.js +9 -1
  124. package/build/dist/Types/BaseDatabase/LessThanOrEqual.js.map +1 -1
  125. package/build/dist/Types/BaseDatabase/LessThanOrNull.js +8 -1
  126. package/build/dist/Types/BaseDatabase/LessThanOrNull.js.map +1 -1
  127. package/build/dist/Types/Log/LogScrubPatternType.js +7 -0
  128. package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -1
  129. package/build/dist/Types/Trace/TraceScrubPatternType.js +7 -0
  130. package/build/dist/Types/Trace/TraceScrubPatternType.js.map +1 -1
  131. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -37
  132. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  133. package/build/dist/UI/Components/ModelTable/SelectFromColumns.js +82 -0
  134. package/build/dist/UI/Components/ModelTable/SelectFromColumns.js.map +1 -0
  135. package/build/dist/Utils/StatusPage/ResourceUptime.js +4 -1
  136. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  137. package/build/dist/Utils/Uptime/UptimeUtil.js +86 -33
  138. package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
  139. package/package.json +1 -1
  140. package/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.ts +0 -101
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js +0 -40
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784401962564-MigrationName.js.map +0 -1
@@ -285,8 +285,19 @@ export default class NetworkFlow extends AnalyticsBaseModel {
285
285
  * (a retentionDate column computed at ingest, like Log/Metric) is
286
286
  * the phase-2 follow-up. Keyed on server-assigned ingestedAt so a
287
287
  * device with a wrong clock cannot make rows expire early.
288
+ *
289
+ * toDateTime() is NOT redundant: ingestedAt is DateTime64(9), so
290
+ * `ingestedAt + INTERVAL 30 DAY` stays DateTime64(9), and a TTL
291
+ * expression must evaluate to Date or DateTime. ClickHouse only began
292
+ * accepting DateTime64 there in 25.x; on 24.x it fails the CREATE with
293
+ * BAD_TTL_EXPRESSION, which aborts the whole boot schema-sync (it has
294
+ * no per-table error handling) and takes every table registered after
295
+ * this one down with it. The Helm chart ships `tag: latest` but tells
296
+ * operators to pin for production, so older servers are supported and
297
+ * this must stay version-portable. Narrowing to second precision is
298
+ * lossless for a 30-day retention window.
288
299
  */
289
- ttlExpression: "ingestedAt + INTERVAL 30 DAY DELETE",
300
+ ttlExpression: "toDateTime(ingestedAt) + INTERVAL 30 DAY DELETE",
290
301
  defaultSortColumn: "flowStartAt",
291
302
  });
292
303
  }
@@ -205,6 +205,36 @@ export default class AIInsight extends BaseModel {
205
205
  })
206
206
  public severity?: AIInsightSeverity = undefined;
207
207
 
208
+ /*
209
+ * Triage verdict (ExceptionAIClassification values) written by the
210
+ * insight triage runner. Null until triage completes. The automatic
211
+ * fix lane only opens pull requests for insights triaged as
212
+ * code-fault — see InsightScanner / InsightFixRouting.
213
+ */
214
+ @ColumnAccessControl({
215
+ create: [],
216
+ read: [
217
+ Permission.ProjectOwner,
218
+ Permission.ProjectAdmin,
219
+ Permission.ProjectMember,
220
+ ],
221
+ update: [],
222
+ })
223
+ @TableColumn({
224
+ required: false,
225
+ type: TableColumnType.ShortText,
226
+ title: "Classification",
227
+ description:
228
+ "AI triage verdict: code-fault, user-error, expected-denial, infrastructure or unknown. Automatic fix pull requests are only opened for code-fault.",
229
+ canReadOnRelationQuery: true,
230
+ })
231
+ @Column({
232
+ nullable: true,
233
+ type: ColumnType.ShortText,
234
+ length: ColumnLength.ShortText,
235
+ })
236
+ public classification?: string = undefined;
237
+
208
238
  /*
209
239
  * The detector's stable dedupe key for this finding (e.g.
210
240
  * "new-exception:<telemetryExceptionId>"). The scanner refreshes the
@@ -1599,6 +1599,36 @@ export default class Project extends TenantModel {
1599
1599
  })
1600
1600
  public enableInsightFixTasks?: boolean = undefined;
1601
1601
 
1602
+ @ColumnAccessControl({
1603
+ create: [],
1604
+ read: [
1605
+ Permission.ProjectOwner,
1606
+ Permission.ProjectAdmin,
1607
+ Permission.ProjectMember,
1608
+ Permission.Viewer,
1609
+ Permission.ReadProject,
1610
+ Permission.UnAuthorizedSsoUser,
1611
+ Permission.ProjectUser,
1612
+ ],
1613
+ update: [Permission.ProjectOwner, Permission.ProjectAdmin],
1614
+ })
1615
+ @TableColumn({
1616
+ required: true,
1617
+ isDefaultValueColumn: true,
1618
+ type: TableColumnType.Boolean,
1619
+ title: "Auto Archive Non-Actionable Exceptions",
1620
+ description:
1621
+ "When enabled, exception groups the AI triage classifies as expected denials (auth failures, plan/paywall rejections, scanner probes tripping intentional validation) are automatically archived so they stop surfacing in the unresolved list and never queue AI fix tasks. Groups classified as user errors or infrastructure conditions are NOT auto-archived — only clear expected denials are. Archiving is reversible from the Archived tab.",
1622
+ defaultValue: false,
1623
+ example: true,
1624
+ })
1625
+ @Column({
1626
+ nullable: false,
1627
+ default: false,
1628
+ type: ColumnType.Boolean,
1629
+ })
1630
+ public autoArchiveNonActionableExceptions?: boolean = undefined;
1631
+
1602
1632
  @ColumnAccessControl({
1603
1633
  create: [],
1604
1634
  read: [
@@ -1281,4 +1281,132 @@ export default class TelemetryException extends DatabaseBaseModel {
1281
1281
  length: ColumnLength.LongText,
1282
1282
  })
1283
1283
  public environment?: string = undefined;
1284
+
1285
+ @ColumnAccessControl({
1286
+ /*
1287
+ * Server-written only (isRoot bypasses column ACL): this is derived
1288
+ * state maintained by ingest / triage / the PR-state sync — a client
1289
+ * PATCH must not be able to set it (the value flows into LLM prompts
1290
+ * and public PR text, and it gates the automatic fix lane).
1291
+ */
1292
+ create: [],
1293
+ read: [
1294
+ Permission.ProjectOwner,
1295
+ Permission.ProjectAdmin,
1296
+ Permission.ProjectMember,
1297
+ Permission.Viewer,
1298
+ Permission.TelemetryAdmin,
1299
+ Permission.TelemetryMember,
1300
+ Permission.TelemetryViewer,
1301
+ Permission.ReadTelemetryException,
1302
+ ],
1303
+ update: [],
1304
+ })
1305
+ /*
1306
+ * Sticky rollup of the OTel exception.escaped flag: true once ANY
1307
+ * occurrence of this group escaped its span scope (was unhandled).
1308
+ * Maintained by the ingest upsert with `unhandled OR EXCLUDED.unhandled`
1309
+ * — a group never flips back to handled. Handled exceptions are usually
1310
+ * expected/operational errors, which is a key signal for deciding
1311
+ * whether an AI fix PR is warranted.
1312
+ */
1313
+ @TableColumn({
1314
+ title: "Unhandled",
1315
+ description:
1316
+ "True when at least one occurrence of this exception escaped its span scope (was unhandled, per OTel exception.escaped)",
1317
+ isDefaultValueColumn: true,
1318
+ required: true,
1319
+ type: TableColumnType.Boolean,
1320
+ defaultValue: false,
1321
+ example: true,
1322
+ })
1323
+ @Column({
1324
+ type: ColumnType.Boolean,
1325
+ nullable: false,
1326
+ unique: false,
1327
+ default: false,
1328
+ })
1329
+ public unhandled?: boolean = undefined;
1330
+
1331
+ @ColumnAccessControl({
1332
+ /*
1333
+ * Server-written only (isRoot bypasses column ACL): this is derived
1334
+ * state maintained by ingest / triage / the PR-state sync — a client
1335
+ * PATCH must not be able to set it (the value flows into LLM prompts
1336
+ * and public PR text, and it gates the automatic fix lane).
1337
+ */
1338
+ create: [],
1339
+ read: [
1340
+ Permission.ProjectOwner,
1341
+ Permission.ProjectAdmin,
1342
+ Permission.ProjectMember,
1343
+ Permission.Viewer,
1344
+ Permission.TelemetryAdmin,
1345
+ Permission.TelemetryMember,
1346
+ Permission.TelemetryViewer,
1347
+ Permission.ReadTelemetryException,
1348
+ ],
1349
+ update: [],
1350
+ })
1351
+ /*
1352
+ * Verdict of the AI insight triage for this exception group: is this a
1353
+ * code defect the AI should try to fix, an expected end-user error, an
1354
+ * expected denial (auth/paywall/probe), or an infrastructure condition?
1355
+ * Values come from ExceptionAIClassification. Written by the triage
1356
+ * runner; the automatic fix lane only opens PRs for CodeFault groups.
1357
+ */
1358
+ @TableColumn({
1359
+ title: "AI Classification",
1360
+ description:
1361
+ "AI triage verdict for this exception group (code-fault, user-error, expected-denial, infrastructure)",
1362
+ required: false,
1363
+ type: TableColumnType.ShortText,
1364
+ example: "code-fault",
1365
+ })
1366
+ @Column({
1367
+ nullable: true,
1368
+ type: ColumnType.ShortText,
1369
+ length: ColumnLength.ShortText,
1370
+ })
1371
+ public aiClassification?: string = undefined;
1372
+
1373
+ @ColumnAccessControl({
1374
+ /*
1375
+ * Server-written only (isRoot bypasses column ACL): this is derived
1376
+ * state maintained by ingest / triage / the PR-state sync — a client
1377
+ * PATCH must not be able to set it (the value flows into LLM prompts
1378
+ * and public PR text, and it gates the automatic fix lane).
1379
+ */
1380
+ create: [],
1381
+ read: [
1382
+ Permission.ProjectOwner,
1383
+ Permission.ProjectAdmin,
1384
+ Permission.ProjectMember,
1385
+ Permission.Viewer,
1386
+ Permission.TelemetryAdmin,
1387
+ Permission.TelemetryMember,
1388
+ Permission.TelemetryViewer,
1389
+ Permission.ReadTelemetryException,
1390
+ ],
1391
+ update: [],
1392
+ })
1393
+ /*
1394
+ * Stamped when a human closes an AI-authored fix PR for this exception
1395
+ * without merging it — the strongest "the AI fix was not wanted" signal.
1396
+ * The automatic fix lane skips groups carrying this stamp; a human
1397
+ * clicking "Fix with AI" on the exception page clears it and retries.
1398
+ */
1399
+ @TableColumn({
1400
+ title: "AI Fix Declined At",
1401
+ description:
1402
+ "Set when an AI-authored fix pull request for this exception was closed without merging; suppresses further automatic fix attempts",
1403
+ required: false,
1404
+ type: TableColumnType.Date,
1405
+ example: "2026-01-01T00:00:00.000Z",
1406
+ })
1407
+ @Column({
1408
+ nullable: true,
1409
+ type: ColumnType.Date,
1410
+ })
1411
+ public aiFixDeclinedAt?: Date = undefined;
1284
1412
  }
@@ -43,6 +43,11 @@ import CodeFixTaskContext, {
43
43
  PerformanceFinding,
44
44
  } from "../../Types/AI/CodeFixTaskContext";
45
45
  import SpanTreeAnalyzer from "../Utils/AI/PerfEvidence/SpanTreeAnalyzer";
46
+ import {
47
+ sanitizeExceptionMessage,
48
+ sanitizeStackTrace,
49
+ } from "../Utils/Telemetry/ExceptionSanitizer";
50
+ import ToolResultSerializer from "../Utils/AI/Toolbox/Serializer";
46
51
  import OpenPullRequestCap, {
47
52
  OpenPullRequestCapDecision,
48
53
  } from "../Utils/AI/CodeFix/OpenPullRequestCap";
@@ -204,6 +209,7 @@ export default class AIAgentDataAPI {
204
209
  stackTrace: true,
205
210
  exceptionType: true,
206
211
  fingerprint: true,
212
+ aiClassification: true,
207
213
  primaryEntityId: true,
208
214
  primaryEntityType: true,
209
215
  },
@@ -221,7 +227,7 @@ export default class AIAgentDataAPI {
221
227
  }
222
228
 
223
229
  logger.debug(
224
- `Exception details fetched: ${exception._id} - ${exception.message?.substring(0, 100)}`,
230
+ `Exception details fetched: ${exception._id}`,
225
231
  getLogAttributesFromRequest(req as any),
226
232
  );
227
233
 
@@ -245,13 +251,24 @@ export default class AIAgentDataAPI {
245
251
  })
246
252
  : null;
247
253
 
254
+ /*
255
+ * Sanitize at the choke point: everything the AI agent worker
256
+ * sees — and therefore everything that can end up in an LLM
257
+ * prompt, a public pull-request title/body, or a commit
258
+ * message — flows through this response. The message gets
259
+ * dynamic-token normalization + secret redaction; the stack
260
+ * trace gets redaction only, so file:line frames stay intact
261
+ * for the code agent.
262
+ */
248
263
  return Response.sendJsonObjectResponse(req, res, {
249
264
  exception: {
250
265
  id: exception._id?.toString(),
251
- message: exception.message,
252
- stackTrace: exception.stackTrace,
253
- exceptionType: exception.exceptionType,
266
+ message: sanitizeExceptionMessage(exception.message || ""),
267
+ stackTrace: sanitizeStackTrace(exception.stackTrace || ""),
268
+ // Legacy pre-batch-upsert rows can carry NULL here.
269
+ exceptionType: exception.exceptionType || "",
254
270
  fingerprint: exception.fingerprint,
271
+ aiClassification: exception.aiClassification || null,
255
272
  },
256
273
  service: exception.primaryEntityId
257
274
  ? {
@@ -518,6 +535,103 @@ export default class AIAgentDataAPI {
518
535
  );
519
536
  }
520
537
 
538
+ /*
539
+ * Service-scoped instrumentation recipes (ImproveLogging /
540
+ * ImproveTracing): the context is just the service — resolve the
541
+ * repository by service name (no stack trace) and hand the
542
+ * worker a short brief; the recipe's checklist lives in the
543
+ * worker's prompt.
544
+ */
545
+ if (
546
+ taskType === CodeFixTaskType.ImproveLogging ||
547
+ taskType === CodeFixTaskType.ImproveTracing
548
+ ) {
549
+ const taskContext: CodeFixTaskContext | undefined = run.taskContext;
550
+ const improvementServiceName: string =
551
+ taskContext?.serviceName || "";
552
+
553
+ if (!taskContext?.telemetryServiceId) {
554
+ return Response.sendErrorResponse(
555
+ req,
556
+ res,
557
+ new BadDataException(
558
+ "This telemetry-improvement task has no stored service context — the task has nothing to work from.",
559
+ ),
560
+ );
561
+ }
562
+
563
+ const pillar: string =
564
+ taskType === CodeFixTaskType.ImproveLogging
565
+ ? "logging"
566
+ : "tracing";
567
+ const subjectTitle: string = improvementServiceName
568
+ ? `Improve ${pillar} for ${improvementServiceName}`
569
+ : `Improve ${pillar}`;
570
+
571
+ const resolution: RepoResolution | null =
572
+ await CodeRepositoryService.resolveRepositoryForException({
573
+ projectId: run.projectId,
574
+ stackTrace: null,
575
+ serviceName: improvementServiceName || null,
576
+ });
577
+
578
+ const repository: CodeRepository | null = resolution
579
+ ? await CodeRepositoryService.findOneById({
580
+ id: new ObjectID(resolution.codeRepositoryId),
581
+ select: {
582
+ _id: true,
583
+ name: true,
584
+ repositoryHostedAt: true,
585
+ organizationName: true,
586
+ repositoryName: true,
587
+ mainBranchName: true,
588
+ gitHubAppInstallationId: true,
589
+ },
590
+ props: { isRoot: true },
591
+ })
592
+ : null;
593
+
594
+ const basePayload: JSONObject = {
595
+ subjectType: "service",
596
+ subjectTitle,
597
+ analysisMarkdown: `OneUptime observes the telemetry of the service ${
598
+ improvementServiceName
599
+ ? `"${improvementServiceName}"`
600
+ : "in this repository"
601
+ }. A user asked for its ${pillar} instrumentation to be improved — follow the task checklist in your instructions.`,
602
+ serviceName: improvementServiceName,
603
+ projectId: run.projectId.toString(),
604
+ };
605
+
606
+ if (!resolution || !repository) {
607
+ return Response.sendJsonObjectResponse(req, res, {
608
+ ...basePayload,
609
+ repositories: [],
610
+ resolutionError:
611
+ "Could not resolve a repository for this task: no connected repository name matches the service and the project has more than one repository. Connect the right repository via the GitHub App, or rename one to match the service.",
612
+ });
613
+ }
614
+
615
+ return Response.sendJsonObjectResponse(req, res, {
616
+ ...basePayload,
617
+ repositories: [
618
+ {
619
+ id: repository.id!.toString(),
620
+ name: repository.name || "",
621
+ repositoryHostedAt: repository.repositoryHostedAt || "",
622
+ organizationName: repository.organizationName || "",
623
+ repositoryName: repository.repositoryName || "",
624
+ mainBranchName: repository.mainBranchName || "main",
625
+ servicePathInRepository: resolution.servicePathInRepository,
626
+ gitHubAppInstallationId:
627
+ repository.gitHubAppInstallationId || null,
628
+ resolutionMethod: resolution.method,
629
+ resolutionEvidence: resolution.evidence,
630
+ },
631
+ ],
632
+ });
633
+ }
634
+
521
635
  /*
522
636
  * Trace-evidence recipes (FixPerformance): everything the worker
523
637
  * needs was captured into taskContext at trigger time — the spans
@@ -607,8 +721,10 @@ export default class AIAgentDataAPI {
607
721
  const basePayload: JSONObject = {
608
722
  subjectType: "trace",
609
723
  subjectTitle: findings[0]!.headline,
610
- analysisMarkdown:
724
+ // Redact before it can reach a prompt or PR body.
725
+ analysisMarkdown: ToolResultSerializer.redact(
611
726
  SpanTreeAnalyzer.renderFindingsMarkdown(findings),
727
+ ).text,
612
728
  serviceName,
613
729
  projectId: run.projectId.toString(),
614
730
  traceId: taskContext.traceId,
@@ -788,6 +904,14 @@ export default class AIAgentDataAPI {
788
904
  );
789
905
  }
790
906
 
907
+ /*
908
+ * The analysis was written by the investigation LLM over
909
+ * redacted tool results, but quoted exception text can still
910
+ * carry secrets — sweep it again before it reaches the worker's
911
+ * prompt and the pull-request body.
912
+ */
913
+ analysisMarkdown = ToolResultSerializer.redact(analysisMarkdown).text;
914
+
791
915
  /*
792
916
  * Resolve the repository WITHOUT a stack trace — these tasks have
793
917
  * no exception, so only the name-match (against the subject's
@@ -31,6 +31,8 @@ import ServiceService from "../Services/ServiceService";
31
31
  import SpanService from "../Services/SpanService";
32
32
  import FixFromIncidentTaskTrigger from "../Utils/AI/SRE/FixFromIncidentTaskTrigger";
33
33
  import FixPerformanceTaskTrigger from "../Utils/AI/SRE/FixPerformanceTaskTrigger";
34
+ import TelemetryImprovementTaskTrigger from "../Utils/AI/SRE/TelemetryImprovementTaskTrigger";
35
+ import CodeFixTaskType from "../../Types/AI/CodeFixTaskType";
34
36
  import { AnalyzableSpan } from "../Utils/AI/PerfEvidence/SpanTreeAnalyzer";
35
37
 
36
38
  const router: ExpressRouter = Express.getRouter();
@@ -575,4 +577,89 @@ router.post(
575
577
  },
576
578
  );
577
579
 
580
+ /*
581
+ * "Improve logging / tracing with AI" from a telemetry service's Logs or
582
+ * Traces page: gate and enqueue a service-scoped instrumentation-
583
+ * improvement CodeFix run (ImproveLogging / ImproveTracing). Human-
584
+ * triggered — the click is the gate; budget, repository and per-service
585
+ * dedupe are enforced in the trigger. Returns { aiRunId }.
586
+ */
587
+ router.post(
588
+ "/ai-investigation/create-telemetry-improvement-task",
589
+ UserMiddleware.getUserMiddleware,
590
+ async (
591
+ req: ExpressRequest,
592
+ res: ExpressResponse,
593
+ next: NextFunction,
594
+ ): Promise<void> => {
595
+ try {
596
+ const props: DatabaseCommonInteractionProps = await getLoggedInProps(req);
597
+
598
+ const projectId: ObjectID | undefined = props.tenantId;
599
+
600
+ if (!projectId) {
601
+ throw new BadDataException("A project scope is required.");
602
+ }
603
+
604
+ const telemetryServiceIdParam: string | undefined = req.body[
605
+ "telemetryServiceId"
606
+ ] as string | undefined;
607
+
608
+ if (!telemetryServiceIdParam) {
609
+ throw new BadDataException("telemetryServiceId is required.");
610
+ }
611
+
612
+ const rawTaskType: string | undefined = req.body["taskType"] as
613
+ | string
614
+ | undefined;
615
+
616
+ if (
617
+ rawTaskType !== CodeFixTaskType.ImproveLogging &&
618
+ rawTaskType !== CodeFixTaskType.ImproveTracing
619
+ ) {
620
+ throw new BadDataException(
621
+ `taskType must be ${CodeFixTaskType.ImproveLogging} or ${CodeFixTaskType.ImproveTracing}.`,
622
+ );
623
+ }
624
+
625
+ const telemetryServiceId: ObjectID = new ObjectID(
626
+ telemetryServiceIdParam,
627
+ );
628
+
629
+ /*
630
+ * Access check under the USER's permissions: the caller must be able
631
+ * to read the service they are asking the agent to instrument. The
632
+ * trigger re-reads as root afterwards.
633
+ */
634
+ const service: Service | null = await ServiceService.findOneById({
635
+ id: telemetryServiceId,
636
+ select: { _id: true },
637
+ props: props,
638
+ });
639
+
640
+ if (!service) {
641
+ throw new BadDataException(
642
+ "Telemetry service not found (or you do not have access to it).",
643
+ );
644
+ }
645
+
646
+ const run: AIRun =
647
+ await TelemetryImprovementTaskTrigger.createTelemetryImprovementTask({
648
+ projectId,
649
+ telemetryServiceId,
650
+ taskType: rawTaskType,
651
+ userId: props.userId!,
652
+ });
653
+
654
+ Response.sendJsonObjectResponse(req, res, {
655
+ aiRunId: run.id!.toString(),
656
+ });
657
+ return;
658
+ } catch (err) {
659
+ next(err);
660
+ return;
661
+ }
662
+ },
663
+ );
664
+
578
665
  export default router;
@@ -88,7 +88,7 @@ import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
88
88
  import StatusPageResourceUptimeUtil from "../../Utils/StatusPage/ResourceUptime";
89
89
  import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
90
90
  import { Green } from "../../Types/BrandColors";
91
- import UptimeUtil from "../../Utils/Uptime/UptimeUtil";
91
+ import UptimeUtil, { UptimeWindow } from "../../Utils/Uptime/UptimeUtil";
92
92
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
93
93
  import URL from "../../Types/API/URL";
94
94
  import SMS from "../../Types/SMS/SMS";
@@ -1265,6 +1265,17 @@ export default class StatusPageAPI extends BaseAPI<
1265
1265
  const downtimeMonitorStatuses: Array<MonitorStatus> =
1266
1266
  statusPage.downtimeMonitorStatuses || [];
1267
1267
 
1268
+ /*
1269
+ * this endpoint reports uptime over an explicit [startDate, endDate] range, so events
1270
+ * have to be clipped to it and the denominator has to be the range itself. Without
1271
+ * this an open (endsAt = null) row that started before the range contributes its
1272
+ * entire duration to the downtime total.
1273
+ */
1274
+ const uptimeWindow: UptimeWindow = {
1275
+ startDate: startDate,
1276
+ endDate: endDate,
1277
+ };
1278
+
1268
1279
  type ResourceUptime = {
1269
1280
  statusPageResourceId: ObjectID;
1270
1281
  uptimePercent: number | null;
@@ -1369,6 +1380,7 @@ export default class StatusPageAPI extends BaseAPI<
1369
1380
  resourceStatusTimelines,
1370
1381
  precision,
1371
1382
  downtimeMonitorStatuses,
1383
+ uptimeWindow,
1372
1384
  );
1373
1385
 
1374
1386
  resourceUptime.uptimePercent = uptimePercent;
@@ -1419,6 +1431,7 @@ export default class StatusPageAPI extends BaseAPI<
1419
1431
  resourceStatusTimelines,
1420
1432
  precision,
1421
1433
  downtimeMonitorStatuses,
1434
+ uptimeWindow,
1422
1435
  );
1423
1436
 
1424
1437
  resourceUptime.uptimePercent = uptimePercent;
@@ -0,0 +1,56 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * AI exception triage + privacy columns:
5
+ *
6
+ * - TelemetryException.unhandled: sticky rollup of OTel exception.escaped
7
+ * (maintained by the ingest upsert with OR semantics).
8
+ * - TelemetryException.aiClassification: triage verdict (code-fault,
9
+ * user-error, expected-denial, infrastructure, unknown).
10
+ * - TelemetryException.aiFixDeclinedAt: stamped when a human closes an
11
+ * AI fix PR without merging; suppresses further automatic fix attempts.
12
+ * - AIInsight.classification: the same triage verdict on the insight row.
13
+ * - Project.autoArchiveNonActionableExceptions: opt-in auto-archive of
14
+ * expected-denial exception groups.
15
+ */
16
+ export class AddExceptionTriageAndPrivacyColumns1784640000000
17
+ implements MigrationInterface
18
+ {
19
+ public name: string = "AddExceptionTriageAndPrivacyColumns1784640000000";
20
+
21
+ public async up(queryRunner: QueryRunner): Promise<void> {
22
+ await queryRunner.query(
23
+ `ALTER TABLE "TelemetryException" ADD "unhandled" boolean NOT NULL DEFAULT false`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "TelemetryException" ADD "aiClassification" character varying(100)`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "TelemetryException" ADD "aiFixDeclinedAt" TIMESTAMP WITH TIME ZONE`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "AIInsight" ADD "classification" character varying(100)`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "Project" ADD "autoArchiveNonActionableExceptions" boolean NOT NULL DEFAULT false`,
36
+ );
37
+ }
38
+
39
+ public async down(queryRunner: QueryRunner): Promise<void> {
40
+ await queryRunner.query(
41
+ `ALTER TABLE "Project" DROP COLUMN "autoArchiveNonActionableExceptions"`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "AIInsight" DROP COLUMN "classification"`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "TelemetryException" DROP COLUMN "aiFixDeclinedAt"`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "TelemetryException" DROP COLUMN "aiClassification"`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "TelemetryException" DROP COLUMN "unhandled"`,
54
+ );
55
+ }
56
+ }
@@ -461,7 +461,7 @@ import { AddNetworkDeviceInventoryAndDiscoverySchedule1784211212164 } from "./17
461
461
  import { AddEnterpriseLicenseNotificationColumns1784218257664 } from "./1784218257664-AddEnterpriseLicenseNotificationColumns";
462
462
  import { AddAttributionColumnsToUserAndProject1784293516000 } from "./1784293516000-AddAttributionColumnsToUserAndProject";
463
463
  import { AddMarketingConversionTable1784298000000 } from "./1784298000000-AddMarketingConversionTable";
464
- import { MigrationName1784401962564 } from "./1784401962564-MigrationName";
464
+ import { AddExceptionTriageAndPrivacyColumns1784640000000 } from "./1784640000000-AddExceptionTriageAndPrivacyColumns";
465
465
 
466
466
  export default [
467
467
  InitialMigration,
@@ -927,5 +927,5 @@ export default [
927
927
  AddEnterpriseLicenseNotificationColumns1784218257664,
928
928
  AddAttributionColumnsToUserAndProject1784293516000,
929
929
  AddMarketingConversionTable1784298000000,
930
- MigrationName1784401962564,
930
+ AddExceptionTriageAndPrivacyColumns1784640000000,
931
931
  ];
@@ -187,6 +187,14 @@ export class Service extends DatabaseService<Model> {
187
187
  run.triggeredByIncidentId || run.triggeredByAlertId
188
188
  ? null
189
189
  : "Queued code-fix run has no incident or alert subject.";
190
+ } else if (
191
+ run.codeFixTaskType === CodeFixTaskType.ImproveLogging ||
192
+ run.codeFixTaskType === CodeFixTaskType.ImproveTracing
193
+ ) {
194
+ // Service-scoped instrumentation recipes carry the service instead.
195
+ missingContextMessage = run.taskContext?.telemetryServiceId
196
+ ? null
197
+ : "Queued code-fix run has no telemetry service in its task context.";
190
198
  } else {
191
199
  missingContextMessage = run.taskContext?.traceId
192
200
  ? null