@oneuptime/common 11.5.3 → 11.5.5

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 (513) hide show
  1. package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +56 -45
  2. package/Models/DatabaseModels/AIInsight.ts +748 -0
  3. package/Models/DatabaseModels/AIRun.ts +262 -0
  4. package/Models/DatabaseModels/CodeRepository.ts +42 -0
  5. package/Models/DatabaseModels/Dashboard.ts +3 -3
  6. package/Models/DatabaseModels/GlobalConfig.ts +105 -0
  7. package/Models/DatabaseModels/Index.ts +4 -8
  8. package/Models/DatabaseModels/InstanceHealthLog.ts +277 -0
  9. package/Models/DatabaseModels/LlmLog.ts +33 -0
  10. package/Models/DatabaseModels/Project.ts +123 -6
  11. package/Models/DatabaseModels/Runbook.ts +1 -1
  12. package/Server/API/AIAgentDataAPI.ts +802 -140
  13. package/Server/API/AIAgentTaskAPI.ts +132 -69
  14. package/Server/API/AIAgentTaskLogAPI.ts +65 -41
  15. package/Server/API/AIAgentTaskPullRequestAPI.ts +41 -0
  16. package/Server/API/AIInsightAPI.ts +220 -0
  17. package/Server/API/AIInvestigationAPI.ts +363 -1
  18. package/Server/API/CodeFixRunAPI.ts +251 -0
  19. package/Server/API/GitHubAPI.ts +246 -3
  20. package/Server/API/TelemetryExceptionAPI.ts +193 -48
  21. package/Server/EnvironmentConfig.ts +1 -1
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1783943300000-DropServiceCodeRepository.ts +26 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1783947444597-CodeFixRunsOnAIRun.ts +73 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1783950962813-AddCodeFixTaskType.ts +29 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1783954040984-AddInstrumentationFixTasksFlag.ts +31 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783957160597-MigrationName.ts +51 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1783958542237-AddTaskContextToAIRun.ts +25 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1783965945957-AddInvestigationVerdictAndGrade.ts +47 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1783970619301-AddFixGuardrailColumns.ts +53 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1783970619302-DropLegacyAIAgentTaskTables.ts +31 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1783977781677-AddCiStatusToFixPullRequests.ts +37 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1783990000000-AddCompletionTokensToLlmLog.ts +25 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1784010274993-AddSentinelInsight.ts +75 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1784010274994-AddSentinelInsightFlags.ts +37 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1784030612266-RenameSentinelToAI.ts +203 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1784033837629-MigrationName.ts +163 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +30 -0
  38. package/Server/Infrastructure/Queue.ts +82 -4
  39. package/Server/Infrastructure/Semaphore.ts +62 -1
  40. package/Server/Services/AIAgentService.ts +32 -0
  41. package/Server/Services/AIAgentTaskPullRequestService.ts +117 -0
  42. package/Server/Services/AIInsightService.ts +174 -0
  43. package/Server/Services/AIRunEventService.ts +63 -0
  44. package/Server/Services/AIRunService.ts +259 -1
  45. package/Server/Services/AIService.ts +249 -7
  46. package/Server/Services/AlertService.ts +4 -4
  47. package/Server/Services/CodeRepositoryService.ts +209 -0
  48. package/Server/Services/DashboardService.ts +11 -2
  49. package/Server/Services/GlobalConfigService.ts +148 -0
  50. package/Server/Services/IncidentService.ts +183 -38
  51. package/Server/Services/IncidentStateTimelineService.ts +24 -4
  52. package/Server/Services/Index.ts +4 -4
  53. package/Server/Services/{AIAgentTaskLogService.ts → InstanceHealthLogService.ts} +1 -1
  54. package/Server/Services/LlmLogService.ts +27 -0
  55. package/Server/Services/LlmProviderService.ts +108 -0
  56. package/Server/Services/MetricBaselineService.ts +123 -0
  57. package/Server/Services/MonitorService.ts +15 -0
  58. package/Server/Services/TelemetryExceptionService.ts +323 -116
  59. package/Server/Services/TraceAggregationService.ts +176 -0
  60. package/Server/Types/Workflow/ComponentCode.ts +6 -0
  61. package/Server/Types/Workflow/Components/AI/GenerateText.ts +326 -0
  62. package/Server/Types/Workflow/Components/Index.ts +2 -0
  63. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +1 -1
  64. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +71 -0
  65. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +243 -0
  66. package/Server/Utils/AI/CodeFix/CodeFixRunQueue.ts +131 -0
  67. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +148 -0
  68. package/Server/Utils/AI/CodeFix/OpenPullRequestCap.ts +117 -0
  69. package/Server/Utils/AI/Eval/EvalCorpus.ts +369 -0
  70. package/Server/Utils/AI/Eval/EvalScores.ts +172 -0
  71. package/Server/Utils/AI/Eval/ReplayInvestigation.ts +87 -0
  72. package/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.ts +676 -0
  73. package/Server/Utils/AI/{Sentinel/SentinelInvestigationEngine.ts → SRE/AIInvestigationEngine.ts} +58 -37
  74. package/Server/Utils/AI/{Sentinel/SentinelMemory.ts → SRE/AIMemory.ts} +3 -3
  75. package/Server/Utils/AI/{Sentinel → SRE}/AlertInvestigationRunner.ts +50 -20
  76. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +293 -0
  77. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +115 -0
  78. package/Server/Utils/AI/SRE/FixPerformanceTaskTrigger.ts +223 -0
  79. package/Server/Utils/AI/{Sentinel → SRE}/IncidentInvestigationRunner.ts +71 -21
  80. package/Server/Utils/AI/{Sentinel → SRE}/IncidentPostmortemRunner.ts +8 -8
  81. package/Server/Utils/AI/SRE/Insights/Detectors/ErrorLogSpikeDetector.ts +364 -0
  82. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +317 -0
  83. package/Server/Utils/AI/SRE/Insights/Detectors/Index.ts +27 -0
  84. package/Server/Utils/AI/SRE/Insights/Detectors/MetricDriftDetector.ts +221 -0
  85. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +238 -0
  86. package/Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector.ts +521 -0
  87. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +242 -0
  88. package/Server/Utils/AI/SRE/Insights/InsightScanner.ts +290 -0
  89. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +283 -0
  90. package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +242 -0
  91. package/Server/Utils/AI/SRE/Insights/Triage.ts +162 -0
  92. package/Server/Utils/AI/SRE/Insights/Types.ts +49 -0
  93. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +212 -0
  94. package/Server/Utils/AI/SRE/InvestigationGrader.ts +282 -0
  95. package/Server/Utils/AI/{Sentinel → SRE}/InvestigationQueue.ts +114 -19
  96. package/Server/Utils/AI/{Sentinel → SRE}/README.md +4 -4
  97. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +172 -0
  98. package/Server/Utils/AI/Toolbox/{SentinelActionTools.ts → AIActionTools.ts} +22 -1
  99. package/Server/Utils/AI/Toolbox/Index.ts +2 -2
  100. package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +526 -0
  101. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +319 -1
  102. package/Server/Utils/CodeRepository/ServiceRepoLinkSuggester.ts +221 -0
  103. package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +571 -0
  104. package/Server/Utils/Execute.ts +34 -13
  105. package/Server/Utils/LLM/LLMService.ts +160 -30
  106. package/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.ts +152 -0
  107. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +29 -0
  108. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +225 -0
  109. package/Tests/Server/Infrastructure/Queue.test.ts +227 -0
  110. package/Tests/Server/Infrastructure/SemaphorePermit.test.ts +159 -0
  111. package/Tests/Server/Services/AIAgentTaskPullRequestOutcomeStats.test.ts +154 -0
  112. package/Tests/Server/Services/AIInsightService.test.ts +324 -0
  113. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +401 -0
  114. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +161 -0
  115. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +95 -5
  116. package/Tests/Server/Services/AIServiceProjectAccess.test.ts +259 -0
  117. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +249 -0
  118. package/Tests/Server/Services/FixPerformanceTaskTrigger.test.ts +274 -0
  119. package/Tests/Server/Services/GlobalConfigService.test.ts +80 -0
  120. package/Tests/Server/Services/IncidentInternalNoteAnnouncement.test.ts +1 -1
  121. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +313 -0
  122. package/Tests/Server/Services/LlmProviderProjectOwned.test.ts +132 -0
  123. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +405 -0
  124. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +322 -0
  125. package/Tests/Server/Services/TraceServiceLatencyProfile.test.ts +278 -0
  126. package/Tests/Server/Types/Workflow/Components/AIGenerateText.test.ts +722 -0
  127. package/Tests/Server/Utils/AI/{SentinelAlertGating.test.ts → AIAlertGating.test.ts} +19 -19
  128. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +362 -0
  129. package/Tests/Server/Utils/AI/{SentinelInvestigationQueue.test.ts → AIInvestigationQueue.test.ts} +19 -19
  130. package/Tests/Server/Utils/AI/CodeAgentWorkspaceGuard.test.ts +138 -0
  131. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +335 -0
  132. package/Tests/Server/Utils/AI/CodeFixRunQueue.test.ts +134 -0
  133. package/Tests/Server/Utils/AI/EvalCorpus.test.ts +209 -0
  134. package/Tests/Server/Utils/AI/EvalScores.test.ts +343 -0
  135. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +218 -0
  136. package/Tests/Server/Utils/AI/Insights/ErrorLogSpikeDetector.test.ts +437 -0
  137. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +287 -0
  138. package/Tests/Server/Utils/AI/Insights/InsightDetectorsIndex.test.ts +34 -0
  139. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +572 -0
  140. package/Tests/Server/Utils/AI/Insights/InsightNeverThrowsHardening.test.ts +344 -0
  141. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +720 -0
  142. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +467 -0
  143. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +370 -0
  144. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +210 -0
  145. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +221 -0
  146. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +328 -0
  147. package/Tests/Server/Utils/AI/Insights/MetricBaselineDrift.test.ts +182 -0
  148. package/Tests/Server/Utils/AI/Insights/MetricDriftDetector.test.ts +280 -0
  149. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +259 -0
  150. package/Tests/Server/Utils/AI/Insights/TraceLatencyRegressionDetector.test.ts +770 -0
  151. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +343 -0
  152. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +307 -0
  153. package/Tests/Server/Utils/AI/OpenPullRequestCap.test.ts +170 -0
  154. package/Tests/Server/Utils/AI/SpanTreeAnalyzer.test.ts +559 -0
  155. package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +158 -0
  156. package/Tests/Server/Utils/GitHubCheckRunsConclusion.test.ts +100 -0
  157. package/Tests/Server/Utils/GitHubPullRequestStateMapping.test.ts +50 -0
  158. package/Tests/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.test.ts +266 -0
  159. package/Tests/Server/Utils/ServiceRepoLinkSuggester.test.ts +167 -0
  160. package/Tests/Server/Utils/StackTraceRepoResolver.test.ts +527 -0
  161. package/Tests/Types/AI/AIChatPermissionMode.test.ts +112 -0
  162. package/Tests/Types/AI/CodeFixTaskType.test.ts +152 -0
  163. package/Tests/Types/AI/FixPullRequestCiStatus.test.ts +181 -0
  164. package/Tests/Types/Monitor/MonitorStepSqlMonitor.test.ts +110 -0
  165. package/Tests/Types/Monitor/SqlDatabaseType.test.ts +55 -0
  166. package/Tests/Types/Workflow/AIGenerateTextMetadata.test.ts +180 -0
  167. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +168 -0
  168. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +523 -0
  169. package/Types/AI/AIChatTypes.ts +8 -0
  170. package/Types/AI/AIInsightEvidence.ts +77 -0
  171. package/Types/AI/AIInsightHumanVerdict.ts +15 -0
  172. package/Types/AI/AIInsightSeverity.ts +13 -0
  173. package/Types/AI/AIInsightStatus.ts +38 -0
  174. package/Types/AI/AIInsightType.ts +41 -0
  175. package/Types/AI/AIRunAutoGrade.ts +18 -0
  176. package/Types/AI/AIRunEventType.ts +5 -0
  177. package/Types/AI/AIRunHumanVerdict.ts +17 -0
  178. package/Types/AI/AIRunType.ts +6 -0
  179. package/Types/AI/CodeFixTaskContext.ts +88 -0
  180. package/Types/AI/CodeFixTaskType.ts +132 -0
  181. package/Types/AI/FixPullRequestCiStatus.ts +132 -0
  182. package/Types/BaseDatabase/AggregationIntervalUtil.ts +67 -0
  183. package/Types/Incident/IncidentMetricType.ts +7 -0
  184. package/Types/Monitor/CriteriaFilter.ts +8 -0
  185. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  186. package/Types/Monitor/MonitorStep.ts +38 -0
  187. package/Types/Monitor/MonitorStepSqlMonitor.ts +141 -0
  188. package/Types/Monitor/MonitorType.ts +17 -0
  189. package/Types/Monitor/SqlDatabaseType.ts +49 -0
  190. package/Types/Monitor/SqlMonitor/SqlMonitorResponse.ts +28 -0
  191. package/Types/Permission.ts +11 -95
  192. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  193. package/Types/Runbook/RunbookStep.ts +26 -2
  194. package/Types/Runbook/RunbookStepType.ts +1 -0
  195. package/Types/Workflow/Component.ts +13 -0
  196. package/Types/Workflow/ComponentID.ts +1 -0
  197. package/Types/Workflow/Components/AI.ts +148 -0
  198. package/Types/Workflow/Components.ts +8 -0
  199. package/UI/Components/AI/GenerateFromAIModal.tsx +1 -1
  200. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +11 -0
  201. package/UI/Components/MoreMenu/MoreMenu.tsx +1 -1
  202. package/UI/Components/Table/Table.tsx +38 -2
  203. package/UI/Utils/TableColumnsToCsv.ts +385 -0
  204. package/Utils/Monitor/MonitorMetricType.ts +1 -0
  205. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +59 -45
  206. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/AIInsight.js +795 -0
  208. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -0
  209. package/build/dist/Models/DatabaseModels/AIRun.js +274 -0
  210. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  211. package/build/dist/Models/DatabaseModels/CodeRepository.js +43 -0
  212. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/Dashboard.js +3 -3
  214. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/GlobalConfig.js +110 -0
  216. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/Index.js +4 -8
  218. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  219. package/build/dist/Models/DatabaseModels/InstanceHealthLog.js +304 -0
  220. package/build/dist/Models/DatabaseModels/InstanceHealthLog.js.map +1 -0
  221. package/build/dist/Models/DatabaseModels/LlmLog.js +35 -0
  222. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
  223. package/build/dist/Models/DatabaseModels/Project.js +127 -6
  224. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  225. package/build/dist/Models/DatabaseModels/Runbook.js +1 -1
  226. package/build/dist/Models/DatabaseModels/Runbook.js.map +1 -1
  227. package/build/dist/Server/API/AIAgentDataAPI.js +568 -85
  228. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  229. package/build/dist/Server/API/AIAgentTaskAPI.js +100 -44
  230. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  231. package/build/dist/Server/API/AIAgentTaskLogAPI.js +49 -25
  232. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  233. package/build/dist/Server/API/AIAgentTaskPullRequestAPI.js +27 -0
  234. package/build/dist/Server/API/AIAgentTaskPullRequestAPI.js.map +1 -1
  235. package/build/dist/Server/API/AIInsightAPI.js +140 -0
  236. package/build/dist/Server/API/AIInsightAPI.js.map +1 -0
  237. package/build/dist/Server/API/AIInvestigationAPI.js +257 -1
  238. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  239. package/build/dist/Server/API/CodeFixRunAPI.js +181 -0
  240. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -0
  241. package/build/dist/Server/API/GitHubAPI.js +164 -2
  242. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  243. package/build/dist/Server/API/TelemetryExceptionAPI.js +148 -43
  244. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  245. package/build/dist/Server/EnvironmentConfig.js +1 -1
  246. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783943300000-DropServiceCodeRepository.js +23 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783943300000-DropServiceCodeRepository.js.map +1 -0
  249. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783947444597-CodeFixRunsOnAIRun.js +32 -0
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783947444597-CodeFixRunsOnAIRun.js.map +1 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783950962813-AddCodeFixTaskType.js +16 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783950962813-AddCodeFixTaskType.js.map +1 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783954040984-AddInstrumentationFixTasksFlag.js +16 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783954040984-AddInstrumentationFixTasksFlag.js.map +1 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783957160597-MigrationName.js +24 -0
  256. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783957160597-MigrationName.js.map +1 -0
  257. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783958542237-AddTaskContextToAIRun.js +16 -0
  258. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783958542237-AddTaskContextToAIRun.js.map +1 -0
  259. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783965945957-AddInvestigationVerdictAndGrade.js +24 -0
  260. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783965945957-AddInvestigationVerdictAndGrade.js.map +1 -0
  261. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619301-AddFixGuardrailColumns.js +24 -0
  262. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619301-AddFixGuardrailColumns.js.map +1 -0
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619302-DropLegacyAIAgentTaskTables.js +24 -0
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619302-DropLegacyAIAgentTaskTables.js.map +1 -0
  265. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783977781677-AddCiStatusToFixPullRequests.js +18 -0
  266. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783977781677-AddCiStatusToFixPullRequests.js.map +1 -0
  267. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783990000000-AddCompletionTokensToLlmLog.js +18 -0
  268. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783990000000-AddCompletionTokensToLlmLog.js.map +1 -0
  269. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274993-AddSentinelInsight.js +32 -0
  270. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274993-AddSentinelInsight.js.map +1 -0
  271. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274994-AddSentinelInsightFlags.js +18 -0
  272. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274994-AddSentinelInsightFlags.js.map +1 -0
  273. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784030612266-RenameSentinelToAI.js +125 -0
  274. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784030612266-RenameSentinelToAI.js.map +1 -0
  275. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784033837629-MigrationName.js +62 -0
  276. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784033837629-MigrationName.js.map +1 -0
  277. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +30 -0
  278. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  279. package/build/dist/Server/Infrastructure/Queue.js +73 -3
  280. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  281. package/build/dist/Server/Infrastructure/Semaphore.js +44 -1
  282. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  283. package/build/dist/Server/Services/AIAgentService.js +29 -0
  284. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  285. package/build/dist/Server/Services/AIAgentTaskPullRequestService.js +78 -0
  286. package/build/dist/Server/Services/AIAgentTaskPullRequestService.js.map +1 -1
  287. package/build/dist/Server/Services/AIInsightService.js +164 -0
  288. package/build/dist/Server/Services/AIInsightService.js.map +1 -0
  289. package/build/dist/Server/Services/AIRunEventService.js +49 -0
  290. package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
  291. package/build/dist/Server/Services/AIRunService.js +221 -0
  292. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  293. package/build/dist/Server/Services/AIService.js +198 -11
  294. package/build/dist/Server/Services/AIService.js.map +1 -1
  295. package/build/dist/Server/Services/AlertService.js +4 -4
  296. package/build/dist/Server/Services/AlertService.js.map +1 -1
  297. package/build/dist/Server/Services/CodeRepositoryService.js +180 -0
  298. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  299. package/build/dist/Server/Services/DashboardService.js +8 -2
  300. package/build/dist/Server/Services/DashboardService.js.map +1 -1
  301. package/build/dist/Server/Services/GlobalConfigService.js +84 -0
  302. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  303. package/build/dist/Server/Services/IncidentService.js +149 -37
  304. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  305. package/build/dist/Server/Services/IncidentStateTimelineService.js +24 -4
  306. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  307. package/build/dist/Server/Services/Index.js +4 -4
  308. package/build/dist/Server/Services/Index.js.map +1 -1
  309. package/build/dist/Server/Services/{AIAgentTaskLogService.js → InstanceHealthLogService.js} +2 -2
  310. package/build/dist/Server/Services/InstanceHealthLogService.js.map +1 -0
  311. package/build/dist/Server/Services/LlmLogService.js +23 -0
  312. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  313. package/build/dist/Server/Services/LlmProviderService.js +96 -0
  314. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  315. package/build/dist/Server/Services/MetricBaselineService.js +65 -0
  316. package/build/dist/Server/Services/MetricBaselineService.js.map +1 -1
  317. package/build/dist/Server/Services/MonitorService.js +9 -1
  318. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  319. package/build/dist/Server/Services/TelemetryExceptionService.js +243 -82
  320. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  321. package/build/dist/Server/Services/TraceAggregationService.js +124 -0
  322. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  323. package/build/dist/Server/Types/Workflow/ComponentCode.js.map +1 -1
  324. package/build/dist/Server/Types/Workflow/Components/AI/GenerateText.js +240 -0
  325. package/build/dist/Server/Types/Workflow/Components/AI/GenerateText.js.map +1 -0
  326. package/build/dist/Server/Types/Workflow/Components/Index.js +2 -0
  327. package/build/dist/Server/Types/Workflow/Components/Index.js.map +1 -1
  328. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +48 -0
  329. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -0
  330. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +174 -0
  331. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -0
  332. package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js +126 -0
  333. package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js.map +1 -0
  334. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +131 -0
  335. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -0
  336. package/build/dist/Server/Utils/AI/CodeFix/OpenPullRequestCap.js +99 -0
  337. package/build/dist/Server/Utils/AI/CodeFix/OpenPullRequestCap.js.map +1 -0
  338. package/build/dist/Server/Utils/AI/Eval/EvalCorpus.js +245 -0
  339. package/build/dist/Server/Utils/AI/Eval/EvalCorpus.js.map +1 -0
  340. package/build/dist/Server/Utils/AI/Eval/EvalScores.js +83 -0
  341. package/build/dist/Server/Utils/AI/Eval/EvalScores.js.map +1 -0
  342. package/build/dist/Server/Utils/AI/Eval/ReplayInvestigation.js +12 -0
  343. package/build/dist/Server/Utils/AI/Eval/ReplayInvestigation.js.map +1 -0
  344. package/build/dist/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.js +439 -0
  345. package/build/dist/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.js.map +1 -0
  346. package/build/dist/Server/Utils/AI/{Sentinel/SentinelInvestigationEngine.js → SRE/AIInvestigationEngine.js} +47 -31
  347. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -0
  348. package/build/dist/Server/Utils/AI/{Sentinel/SentinelMemory.js → SRE/AIMemory.js} +5 -5
  349. package/build/dist/Server/Utils/AI/SRE/AIMemory.js.map +1 -0
  350. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/AlertInvestigationRunner.js +42 -19
  351. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -0
  352. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +226 -0
  353. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -0
  354. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +100 -0
  355. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -0
  356. package/build/dist/Server/Utils/AI/SRE/FixPerformanceTaskTrigger.js +174 -0
  357. package/build/dist/Server/Utils/AI/SRE/FixPerformanceTaskTrigger.js.map +1 -0
  358. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/IncidentInvestigationRunner.js +60 -22
  359. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -0
  360. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/IncidentPostmortemRunner.js +9 -9
  361. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -0
  362. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ErrorLogSpikeDetector.js +268 -0
  363. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ErrorLogSpikeDetector.js.map +1 -0
  364. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +226 -0
  365. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -0
  366. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/Index.js +26 -0
  367. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/Index.js.map +1 -0
  368. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/MetricDriftDetector.js +159 -0
  369. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/MetricDriftDetector.js.map +1 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +177 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -0
  372. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector.js +384 -0
  373. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector.js.map +1 -0
  374. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js +180 -0
  375. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -0
  376. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js +269 -0
  377. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js.map +1 -0
  378. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +231 -0
  379. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -0
  380. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +206 -0
  381. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -0
  382. package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js +121 -0
  383. package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js.map +1 -0
  384. package/build/dist/Server/Utils/AI/SRE/Insights/Types.js +2 -0
  385. package/build/dist/Server/Utils/AI/SRE/Insights/Types.js.map +1 -0
  386. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +148 -0
  387. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -0
  388. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +231 -0
  389. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -0
  390. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/InvestigationQueue.js +102 -23
  391. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -0
  392. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +167 -0
  393. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -0
  394. package/build/dist/Server/Utils/AI/Toolbox/{SentinelActionTools.js → AIActionTools.js} +18 -2
  395. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -0
  396. package/build/dist/Server/Utils/AI/Toolbox/Index.js +2 -2
  397. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  398. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +301 -0
  399. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -0
  400. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +240 -2
  401. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  402. package/build/dist/Server/Utils/CodeRepository/ServiceRepoLinkSuggester.js +127 -0
  403. package/build/dist/Server/Utils/CodeRepository/ServiceRepoLinkSuggester.js.map +1 -0
  404. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +336 -0
  405. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -0
  406. package/build/dist/Server/Utils/Execute.js +21 -11
  407. package/build/dist/Server/Utils/Execute.js.map +1 -1
  408. package/build/dist/Server/Utils/LLM/LLMService.js +125 -25
  409. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  410. package/build/dist/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.js +120 -0
  411. package/build/dist/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.js.map +1 -0
  412. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +21 -0
  413. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  414. package/build/dist/Types/AI/AIInsightEvidence.js +2 -0
  415. package/build/dist/Types/AI/AIInsightEvidence.js.map +1 -0
  416. package/build/dist/Types/AI/AIInsightHumanVerdict.js +16 -0
  417. package/build/dist/Types/AI/AIInsightHumanVerdict.js.map +1 -0
  418. package/build/dist/Types/AI/AIInsightSeverity.js +14 -0
  419. package/build/dist/Types/AI/AIInsightSeverity.js.map +1 -0
  420. package/build/dist/Types/AI/AIInsightStatus.js +36 -0
  421. package/build/dist/Types/AI/AIInsightStatus.js.map +1 -0
  422. package/build/dist/Types/AI/AIInsightType.js +42 -0
  423. package/build/dist/Types/AI/AIInsightType.js.map +1 -0
  424. package/build/dist/Types/AI/AIRunAutoGrade.js +19 -0
  425. package/build/dist/Types/AI/AIRunAutoGrade.js.map +1 -0
  426. package/build/dist/Types/AI/AIRunEventType.js +5 -0
  427. package/build/dist/Types/AI/AIRunEventType.js.map +1 -1
  428. package/build/dist/Types/AI/AIRunHumanVerdict.js +18 -0
  429. package/build/dist/Types/AI/AIRunHumanVerdict.js.map +1 -0
  430. package/build/dist/Types/AI/AIRunType.js +6 -0
  431. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  432. package/build/dist/Types/AI/CodeFixTaskContext.js +29 -0
  433. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -0
  434. package/build/dist/Types/AI/CodeFixTaskType.js +122 -0
  435. package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -0
  436. package/build/dist/Types/AI/FixPullRequestCiStatus.js +102 -0
  437. package/build/dist/Types/AI/FixPullRequestCiStatus.js.map +1 -0
  438. package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js +55 -0
  439. package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js.map +1 -1
  440. package/build/dist/Types/Incident/IncidentMetricType.js +7 -0
  441. package/build/dist/Types/Incident/IncidentMetricType.js.map +1 -1
  442. package/build/dist/Types/Monitor/CriteriaFilter.js +7 -0
  443. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  444. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  445. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  446. package/build/dist/Types/Monitor/MonitorStep.js +28 -0
  447. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  448. package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js +93 -0
  449. package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js.map +1 -0
  450. package/build/dist/Types/Monitor/MonitorType.js +15 -0
  451. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  452. package/build/dist/Types/Monitor/SqlDatabaseType.js +47 -0
  453. package/build/dist/Types/Monitor/SqlDatabaseType.js.map +1 -0
  454. package/build/dist/Types/Monitor/SqlMonitor/SqlMonitorResponse.js +2 -0
  455. package/build/dist/Types/Monitor/SqlMonitor/SqlMonitorResponse.js.map +1 -0
  456. package/build/dist/Types/Permission.js +10 -84
  457. package/build/dist/Types/Permission.js.map +1 -1
  458. package/build/dist/Types/Runbook/RunbookStepType.js +1 -0
  459. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  460. package/build/dist/Types/Workflow/ComponentID.js +1 -0
  461. package/build/dist/Types/Workflow/ComponentID.js.map +1 -1
  462. package/build/dist/Types/Workflow/Components/AI.js +135 -0
  463. package/build/dist/Types/Workflow/Components/AI.js.map +1 -0
  464. package/build/dist/Types/Workflow/Components.js +7 -0
  465. package/build/dist/Types/Workflow/Components.js.map +1 -1
  466. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -1
  467. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  468. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +9 -0
  469. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  470. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +1 -1
  471. package/build/dist/UI/Components/Table/Table.js +28 -2
  472. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  473. package/build/dist/UI/Utils/TableColumnsToCsv.js +291 -0
  474. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -0
  475. package/build/dist/Utils/Monitor/MonitorMetricType.js +1 -0
  476. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  477. package/package.json +1 -1
  478. package/Models/DatabaseModels/AIAgentTask.ts +0 -660
  479. package/Models/DatabaseModels/AIAgentTaskLog.ts +0 -426
  480. package/Models/DatabaseModels/AIAgentTaskTelemetryException.ts +0 -399
  481. package/Models/DatabaseModels/ServiceCodeRepository.ts +0 -647
  482. package/Server/Services/AIAgentTaskService.ts +0 -240
  483. package/Server/Services/AIAgentTaskTelemetryExceptionService.ts +0 -39
  484. package/Server/Services/ServiceCodeRepositoryService.ts +0 -55
  485. package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +0 -146
  486. package/Types/AI/AIAgentTaskMetadata.ts +0 -25
  487. package/Types/AI/AIAgentTaskType.ts +0 -40
  488. package/build/dist/Models/DatabaseModels/AIAgentTask.js +0 -691
  489. package/build/dist/Models/DatabaseModels/AIAgentTask.js.map +0 -1
  490. package/build/dist/Models/DatabaseModels/AIAgentTaskLog.js +0 -447
  491. package/build/dist/Models/DatabaseModels/AIAgentTaskLog.js.map +0 -1
  492. package/build/dist/Models/DatabaseModels/AIAgentTaskTelemetryException.js +0 -415
  493. package/build/dist/Models/DatabaseModels/AIAgentTaskTelemetryException.js.map +0 -1
  494. package/build/dist/Models/DatabaseModels/ServiceCodeRepository.js +0 -665
  495. package/build/dist/Models/DatabaseModels/ServiceCodeRepository.js.map +0 -1
  496. package/build/dist/Server/Services/AIAgentTaskLogService.js.map +0 -1
  497. package/build/dist/Server/Services/AIAgentTaskService.js +0 -217
  498. package/build/dist/Server/Services/AIAgentTaskService.js.map +0 -1
  499. package/build/dist/Server/Services/AIAgentTaskTelemetryExceptionService.js +0 -36
  500. package/build/dist/Server/Services/AIAgentTaskTelemetryExceptionService.js.map +0 -1
  501. package/build/dist/Server/Services/ServiceCodeRepositoryService.js +0 -54
  502. package/build/dist/Server/Services/ServiceCodeRepositoryService.js.map +0 -1
  503. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +0 -1
  504. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +0 -1
  505. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +0 -1
  506. package/build/dist/Server/Utils/AI/Sentinel/InvestigationQueue.js.map +0 -1
  507. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +0 -1
  508. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +0 -1
  509. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +0 -1
  510. package/build/dist/Types/AI/AIAgentTaskMetadata.js +0 -6
  511. package/build/dist/Types/AI/AIAgentTaskMetadata.js.map +0 -1
  512. package/build/dist/Types/AI/AIAgentTaskType.js +0 -29
  513. package/build/dist/Types/AI/AIAgentTaskType.js.map +0 -1
@@ -0,0 +1,770 @@
1
+ import { afterEach, describe, expect, test } from "@jest/globals";
2
+ import TraceLatencyRegressionDetector, {
3
+ LATENCY_HIGH_SEVERITY_MULTIPLIER,
4
+ LATENCY_MAX_TRACE_SPANS,
5
+ LATENCY_MIN_OPERATION_SAMPLE_COUNT,
6
+ LATENCY_MIN_PRIOR_SAMPLE_COUNT,
7
+ LATENCY_MIN_RECENT_P99_MS,
8
+ LATENCY_SERVICE_LIMIT,
9
+ LATENCY_TOP_OPERATION_CANDIDATES,
10
+ LatencyRegressionDecision,
11
+ ServiceLatencyWindows,
12
+ } from "../../../../../Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector";
13
+ import { InsightCandidate } from "../../../../../Server/Utils/AI/SRE/Insights/Types";
14
+ import TraceAggregationService, {
15
+ ServiceLatencyProfile,
16
+ ServiceLatencyProfileRow,
17
+ TraceAnalyticsTopItem,
18
+ } from "../../../../../Server/Services/TraceAggregationService";
19
+ import SpanService from "../../../../../Server/Services/SpanService";
20
+ import ServiceService from "../../../../../Server/Services/ServiceService";
21
+ import Span from "../../../../../Models/AnalyticsModels/Span";
22
+ import Service from "../../../../../Models/DatabaseModels/Service";
23
+ import { AnalyzableSpan } from "../../../../../Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer";
24
+ import AIInsightSeverity from "../../../../../Types/AI/AIInsightSeverity";
25
+ import AIInsightType from "../../../../../Types/AI/AIInsightType";
26
+ import InBetween from "../../../../../Types/BaseDatabase/InBetween";
27
+ import SortOrder from "../../../../../Types/BaseDatabase/SortOrder";
28
+ import ObjectID from "../../../../../Types/ObjectID";
29
+
30
+ /*
31
+ * Invariant under test: the TraceLatencyRegression detector fires exactly
32
+ * when a service's recent-hour p99 is >= 1000ms AND >= 2x its own prior-24h
33
+ * p99 AND the prior window has >= 100 spans (High at 4x) — and ONLY then
34
+ * drills into a representative trace (slowest CREDIBLE operation → slowest
35
+ * span → whole trace → deterministic span-tree findings + code locations,
36
+ * with correct ns→ms conversion), storing the drilled evidence on the
37
+ * candidate because span retention is short. No LLM is ever involved.
38
+ *
39
+ * Two properties the whole lane's noise budget rests on, pinned here:
40
+ * 1. Stage 1 reads the PROJECTION-served profile query, never the
41
+ * full-scan analytics table — and a possibly-partial profile (the
42
+ * execution cap firing under timeout_overflow_mode='break') files
43
+ * NOTHING rather than comparing a truncated window to a whole one.
44
+ * 2. The drilled operation — which is part of the dedupe fingerprint —
45
+ * is chosen deterministically and only from operations with real
46
+ * traffic behind them, so one ongoing regression stays ONE insight.
47
+ */
48
+
49
+ const projectId: ObjectID = ObjectID.generate();
50
+ const entityId: string = ObjectID.generate().toString();
51
+ const now: Date = new Date("2026-07-14T12:00:00.000Z");
52
+ const oneHourAgo: Date = new Date("2026-07-14T11:00:00.000Z");
53
+ const twentyFiveHoursAgo: Date = new Date("2026-07-13T11:00:00.000Z");
54
+
55
+ function profileRow(input: {
56
+ entityId: string;
57
+ count: number;
58
+ p99Ms: number;
59
+ }): ServiceLatencyProfileRow {
60
+ return {
61
+ primaryEntityId: input.entityId,
62
+ count: input.count,
63
+ p99DurationMs: input.p99Ms,
64
+ };
65
+ }
66
+
67
+ function profile(rows: Array<ServiceLatencyProfileRow>): ServiceLatencyProfile {
68
+ return { rows, isPartial: false };
69
+ }
70
+
71
+ function topItem(input: {
72
+ value: string;
73
+ p99Ms: number;
74
+ count: number;
75
+ }): TraceAnalyticsTopItem {
76
+ return {
77
+ value: input.value,
78
+ metricValue: input.p99Ms,
79
+ count: input.count,
80
+ };
81
+ }
82
+
83
+ /*
84
+ * A two-span trace engineered to trip the analyzer's dominant-span rule:
85
+ * the child covers 95% of the 1000ms trace with no children of its own, so
86
+ * its SELF time dominates. Times are NANOSECONDS, as stored on Span rows.
87
+ */
88
+ function fakeTraceSpans(): Array<Span> {
89
+ return [
90
+ {
91
+ spanId: "root",
92
+ parentSpanId: "",
93
+ name: "GET /users/42",
94
+ startTimeUnixNano: 0,
95
+ endTimeUnixNano: 1_000_000_000,
96
+ durationUnixNano: 1_000_000_000,
97
+ attributes: {},
98
+ },
99
+ {
100
+ spanId: "slow-child",
101
+ parentSpanId: "root",
102
+ name: "SELECT users",
103
+ startTimeUnixNano: 0,
104
+ endTimeUnixNano: 950_000_000,
105
+ durationUnixNano: 950_000_000,
106
+ attributes: {
107
+ "code.filepath": "src/db.ts",
108
+ "code.function": "loadUsers",
109
+ "code.lineno": "42",
110
+ },
111
+ },
112
+ ] as unknown as Array<Span>;
113
+ }
114
+
115
+ describe("TraceLatencyRegressionDetector.evaluateRegression (pure decision matrix)", () => {
116
+ test("all thresholds exactly at their boundaries → regression at Medium", () => {
117
+ const decision: LatencyRegressionDecision =
118
+ TraceLatencyRegressionDetector.evaluateRegression({
119
+ recentP99Ms: LATENCY_MIN_RECENT_P99_MS,
120
+ priorP99Ms: LATENCY_MIN_RECENT_P99_MS / 2,
121
+ priorSampleCount: LATENCY_MIN_PRIOR_SAMPLE_COUNT,
122
+ });
123
+ expect(decision.isRegression).toBe(true);
124
+ expect(decision.multiplier).toBe(2);
125
+ expect(decision.severity).toBe(AIInsightSeverity.Medium);
126
+ });
127
+
128
+ test("recent p99 just below the absolute floor never regresses", () => {
129
+ const decision: LatencyRegressionDecision =
130
+ TraceLatencyRegressionDetector.evaluateRegression({
131
+ recentP99Ms: LATENCY_MIN_RECENT_P99_MS - 1,
132
+ priorP99Ms: 100,
133
+ priorSampleCount: 10_000,
134
+ });
135
+ expect(decision.isRegression).toBe(false);
136
+ });
137
+
138
+ test("multiplier just below 2x does not regress", () => {
139
+ const decision: LatencyRegressionDecision =
140
+ TraceLatencyRegressionDetector.evaluateRegression({
141
+ recentP99Ms: 1000,
142
+ priorP99Ms: 501,
143
+ priorSampleCount: 1000,
144
+ });
145
+ expect(decision.isRegression).toBe(false);
146
+ });
147
+
148
+ test("thin prior sample count (one below the gate) does not regress", () => {
149
+ const decision: LatencyRegressionDecision =
150
+ TraceLatencyRegressionDetector.evaluateRegression({
151
+ recentP99Ms: 5000,
152
+ priorP99Ms: 500,
153
+ priorSampleCount: LATENCY_MIN_PRIOR_SAMPLE_COUNT - 1,
154
+ });
155
+ expect(decision.isRegression).toBe(false);
156
+ });
157
+
158
+ test("zero prior p99 (no baseline) never regresses and yields multiplier 0", () => {
159
+ const decision: LatencyRegressionDecision =
160
+ TraceLatencyRegressionDetector.evaluateRegression({
161
+ recentP99Ms: 5000,
162
+ priorP99Ms: 0,
163
+ priorSampleCount: 1000,
164
+ });
165
+ expect(decision.isRegression).toBe(false);
166
+ expect(decision.multiplier).toBe(0);
167
+ });
168
+
169
+ test("severity boundary: exactly 4x is High, just under stays Medium", () => {
170
+ const high: LatencyRegressionDecision =
171
+ TraceLatencyRegressionDetector.evaluateRegression({
172
+ recentP99Ms: 4000,
173
+ priorP99Ms: 1000,
174
+ priorSampleCount: 1000,
175
+ });
176
+ expect(high.multiplier).toBe(LATENCY_HIGH_SEVERITY_MULTIPLIER);
177
+ expect(high.severity).toBe(AIInsightSeverity.High);
178
+
179
+ const medium: LatencyRegressionDecision =
180
+ TraceLatencyRegressionDetector.evaluateRegression({
181
+ recentP99Ms: 3999,
182
+ priorP99Ms: 1000,
183
+ priorSampleCount: 1000,
184
+ });
185
+ expect(medium.isRegression).toBe(true);
186
+ expect(medium.severity).toBe(AIInsightSeverity.Medium);
187
+ });
188
+ });
189
+
190
+ describe("TraceLatencyRegressionDetector.joinServiceWindows (pure)", () => {
191
+ test("joins recent and prior rows on primaryEntityId", () => {
192
+ const joined: Array<ServiceLatencyWindows> =
193
+ TraceLatencyRegressionDetector.joinServiceWindows(
194
+ [profileRow({ entityId: "e1", count: 500, p99Ms: 2300 })],
195
+ [profileRow({ entityId: "e1", count: 400, p99Ms: 1000 })],
196
+ );
197
+ expect(joined).toEqual([
198
+ {
199
+ primaryEntityId: "e1",
200
+ recentP99Ms: 2300,
201
+ recentSampleCount: 500,
202
+ priorP99Ms: 1000,
203
+ priorSampleCount: 400,
204
+ },
205
+ ]);
206
+ });
207
+
208
+ test("a service missing from the prior window joins with a zero baseline", () => {
209
+ const joined: Array<ServiceLatencyWindows> =
210
+ TraceLatencyRegressionDetector.joinServiceWindows(
211
+ [profileRow({ entityId: "e1", count: 500, p99Ms: 2300 })],
212
+ [],
213
+ );
214
+ expect(joined[0]!.priorP99Ms).toBe(0);
215
+ expect(joined[0]!.priorSampleCount).toBe(0);
216
+ });
217
+
218
+ test("rows without an entity id are dropped from both sides", () => {
219
+ const joined: Array<ServiceLatencyWindows> =
220
+ TraceLatencyRegressionDetector.joinServiceWindows(
221
+ [profileRow({ entityId: "", count: 500, p99Ms: 2300 })],
222
+ [profileRow({ entityId: "", count: 400, p99Ms: 1000 })],
223
+ );
224
+ expect(joined).toEqual([]);
225
+ });
226
+ });
227
+
228
+ /*
229
+ * The operation name goes into the fingerprint, so this picker IS the
230
+ * dedupe key. A rule that lets a one-off slow span win produces a new
231
+ * fingerprint — and therefore a new insight row and a new triage LLM run —
232
+ * every 15 minutes for a single ongoing regression.
233
+ */
234
+ describe("TraceLatencyRegressionDetector.pickSlowestOperation (pure decision matrix)", () => {
235
+ test("a freak-slow op with no traffic loses to the slowest op that has traffic", () => {
236
+ expect(
237
+ TraceLatencyRegressionDetector.pickSlowestOperation([
238
+ topItem({ value: "POST /admin/export", p99Ms: 8000, count: 1 }),
239
+ topItem({ value: "GET /orders", p99Ms: 1200, count: 10_000 }),
240
+ ]),
241
+ ).toBe("GET /orders");
242
+ });
243
+
244
+ test("floor boundary: exactly at the constant qualifies, one below does not", () => {
245
+ expect(
246
+ TraceLatencyRegressionDetector.pickSlowestOperation([
247
+ topItem({
248
+ value: "at-floor",
249
+ p99Ms: 5000,
250
+ count: LATENCY_MIN_OPERATION_SAMPLE_COUNT,
251
+ }),
252
+ topItem({ value: "busy", p99Ms: 1200, count: 10_000 }),
253
+ ]),
254
+ ).toBe("at-floor");
255
+
256
+ expect(
257
+ TraceLatencyRegressionDetector.pickSlowestOperation([
258
+ topItem({
259
+ value: "below-floor",
260
+ p99Ms: 5000,
261
+ count: LATENCY_MIN_OPERATION_SAMPLE_COUNT - 1,
262
+ }),
263
+ topItem({ value: "busy", p99Ms: 1200, count: 10_000 }),
264
+ ]),
265
+ ).toBe("busy");
266
+ });
267
+
268
+ test("every candidate below the floor → undefined (service-level fallback)", () => {
269
+ expect(
270
+ TraceLatencyRegressionDetector.pickSlowestOperation([
271
+ topItem({ value: "rare-a", p99Ms: 9000, count: 1 }),
272
+ topItem({ value: "rare-b", p99Ms: 8000, count: 3 }),
273
+ ]),
274
+ ).toBeUndefined();
275
+ expect(
276
+ TraceLatencyRegressionDetector.pickSlowestOperation([]),
277
+ ).toBeUndefined();
278
+ });
279
+
280
+ test("empty dimension values never win, whatever their p99", () => {
281
+ expect(
282
+ TraceLatencyRegressionDetector.pickSlowestOperation([
283
+ topItem({ value: "", p99Ms: 9000, count: 10_000 }),
284
+ topItem({ value: "GET /orders", p99Ms: 1200, count: 10_000 }),
285
+ ]),
286
+ ).toBe("GET /orders");
287
+ });
288
+
289
+ test("tie-break is deterministic and independent of the row order the query returned", () => {
290
+ const items: Array<TraceAnalyticsTopItem> = [
291
+ topItem({ value: "b-op", p99Ms: 3000, count: 50 }),
292
+ topItem({ value: "a-op", p99Ms: 3000, count: 50 }),
293
+ // Same p99, more traffic — volume breaks the tie before the name does.
294
+ topItem({ value: "z-op", p99Ms: 3000, count: 900 }),
295
+ ];
296
+
297
+ expect(TraceLatencyRegressionDetector.pickSlowestOperation(items)).toBe(
298
+ "z-op",
299
+ );
300
+ expect(
301
+ TraceLatencyRegressionDetector.pickSlowestOperation([...items].reverse()),
302
+ ).toBe("z-op");
303
+
304
+ // p99 AND volume tied: the name is the last, total tie-break.
305
+ const namesOnly: Array<TraceAnalyticsTopItem> = [
306
+ topItem({ value: "b-op", p99Ms: 3000, count: 50 }),
307
+ topItem({ value: "a-op", p99Ms: 3000, count: 50 }),
308
+ ];
309
+ expect(TraceLatencyRegressionDetector.pickSlowestOperation(namesOnly)).toBe(
310
+ "a-op",
311
+ );
312
+ expect(
313
+ TraceLatencyRegressionDetector.pickSlowestOperation(
314
+ [...namesOnly].reverse(),
315
+ ),
316
+ ).toBe("a-op");
317
+ });
318
+
319
+ test("the picker does not mutate the caller's array", () => {
320
+ const items: Array<TraceAnalyticsTopItem> = [
321
+ topItem({ value: "slow", p99Ms: 9000, count: 1 }),
322
+ topItem({ value: "busy", p99Ms: 1200, count: 10_000 }),
323
+ ];
324
+ TraceLatencyRegressionDetector.pickSlowestOperation(items);
325
+ expect(items[0]!.value).toBe("slow");
326
+ });
327
+
328
+ /*
329
+ * Fingerprint stability across ticks: the rare ops churn from scan to
330
+ * scan while the regressing endpoint stays put. The fingerprint must not
331
+ * move with them, or the operator's dismissal (keyed on fingerprint)
332
+ * never suppresses anything.
333
+ */
334
+ test("fingerprint is stable across ticks as the rare-op set churns", () => {
335
+ const tickOne: Array<TraceAnalyticsTopItem> = [
336
+ topItem({ value: "POST /admin/export", p99Ms: 8000, count: 1 }),
337
+ topItem({ value: "GET /orders/42", p99Ms: 1200, count: 10_000 }),
338
+ ];
339
+ const tickTwo: Array<TraceAnalyticsTopItem> = [
340
+ topItem({ value: "GET /debug/dump", p99Ms: 7000, count: 2 }),
341
+ topItem({ value: "GET /orders/97", p99Ms: 1300, count: 9_000 }),
342
+ ];
343
+
344
+ const fingerprintOne: string =
345
+ TraceLatencyRegressionDetector.buildFingerprint(
346
+ "svc",
347
+ TraceLatencyRegressionDetector.pickSlowestOperation(tickOne),
348
+ );
349
+ const fingerprintTwo: string =
350
+ TraceLatencyRegressionDetector.buildFingerprint(
351
+ "svc",
352
+ TraceLatencyRegressionDetector.pickSlowestOperation(tickTwo),
353
+ );
354
+
355
+ expect(fingerprintOne).toBe("latency:svc:GET /orders/{n}");
356
+ expect(fingerprintTwo).toBe(fingerprintOne);
357
+ });
358
+ });
359
+
360
+ describe("TraceLatencyRegressionDetector.toAnalyzableSpans (pure ns→ms)", () => {
361
+ test("converts nanosecond timestamps and durations to milliseconds", () => {
362
+ const converted: Array<AnalyzableSpan> =
363
+ TraceLatencyRegressionDetector.toAnalyzableSpans([
364
+ {
365
+ spanId: "s1",
366
+ parentSpanId: "p1",
367
+ name: "op",
368
+ startTimeUnixNano: 1_500_000,
369
+ endTimeUnixNano: 4_500_000,
370
+ durationUnixNano: 3_000_000,
371
+ attributes: { "db.system": "postgres", numeric: 7 },
372
+ },
373
+ ] as unknown as Array<Span>);
374
+
375
+ expect(converted).toEqual([
376
+ {
377
+ spanId: "s1",
378
+ parentSpanId: "p1",
379
+ name: "op",
380
+ startMs: 1.5,
381
+ endMs: 4.5,
382
+ durationMs: 3,
383
+ attributes: { "db.system": "postgres", numeric: "7" },
384
+ },
385
+ ]);
386
+ });
387
+
388
+ test("missing parent and empty attributes stay well-formed", () => {
389
+ const converted: Array<AnalyzableSpan> =
390
+ TraceLatencyRegressionDetector.toAnalyzableSpans([
391
+ {
392
+ spanId: "s1",
393
+ name: "op",
394
+ startTimeUnixNano: 0,
395
+ endTimeUnixNano: 1_000_000,
396
+ durationUnixNano: 1_000_000,
397
+ },
398
+ ] as unknown as Array<Span>);
399
+ expect(converted[0]!.parentSpanId).toBeUndefined();
400
+ expect(converted[0]!.attributes).toEqual({});
401
+ });
402
+ });
403
+
404
+ describe("TraceLatencyRegressionDetector.buildFingerprint (pure)", () => {
405
+ test("normalizes ids/digits in the operation name for a stable key", () => {
406
+ expect(
407
+ TraceLatencyRegressionDetector.buildFingerprint("svc", "GET /users/42"),
408
+ ).toBe("latency:svc:GET /users/{n}");
409
+ expect(
410
+ TraceLatencyRegressionDetector.buildFingerprint("svc", "GET /users/97"),
411
+ ).toBe("latency:svc:GET /users/{n}");
412
+ });
413
+
414
+ test("missing operation yields a stable service-scoped key", () => {
415
+ expect(
416
+ TraceLatencyRegressionDetector.buildFingerprint("svc", undefined),
417
+ ).toBe("latency:svc:");
418
+ });
419
+ });
420
+
421
+ describe("TraceLatencyRegressionDetector.detect (IO wiring)", () => {
422
+ afterEach(() => {
423
+ jest.restoreAllMocks();
424
+ });
425
+
426
+ test("regression → drill to slowest op, representative trace, findings and code locations", async () => {
427
+ const profileSpy: jest.SpyInstance = jest
428
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
429
+ .mockResolvedValueOnce(
430
+ profile([profileRow({ entityId, count: 500, p99Ms: 2300 })]),
431
+ )
432
+ .mockResolvedValueOnce(
433
+ profile([profileRow({ entityId, count: 400, p99Ms: 1000 })]),
434
+ );
435
+ const topListSpy: jest.SpyInstance = jest
436
+ .spyOn(TraceAggregationService, "getAnalyticsTopList")
437
+ .mockResolvedValue([
438
+ { value: "GET /users/42", metricValue: 2300, count: 40 },
439
+ ]);
440
+ const findBySpy: jest.SpyInstance = jest
441
+ .spyOn(SpanService, "findBy")
442
+ .mockResolvedValueOnce([{ traceId: "trace-1" } as unknown as Span])
443
+ .mockResolvedValueOnce(fakeTraceSpans());
444
+ jest
445
+ .spyOn(ServiceService, "findOneById")
446
+ .mockResolvedValue({ name: "checkout" } as unknown as Service);
447
+
448
+ const detector: TraceLatencyRegressionDetector =
449
+ new TraceLatencyRegressionDetector();
450
+ const candidates: Array<InsightCandidate> = await detector.detect({
451
+ projectId,
452
+ now,
453
+ });
454
+
455
+ /*
456
+ * Stage 1: recent-hour and prior-24h per-service latency profiles, via
457
+ * the projection-served method — NOT getAnalyticsTable, whose stat set
458
+ * and raw-startTime predicate force a 25h full span scan per tick.
459
+ */
460
+ expect(profileSpy).toHaveBeenCalledTimes(2);
461
+ expect(profileSpy).toHaveBeenNthCalledWith(1, {
462
+ projectId: projectId,
463
+ startTime: oneHourAgo,
464
+ endTime: now,
465
+ limit: LATENCY_SERVICE_LIMIT,
466
+ });
467
+ expect(profileSpy).toHaveBeenNthCalledWith(2, {
468
+ projectId: projectId,
469
+ startTime: twentyFiveHoursAgo,
470
+ endTime: oneHourAgo,
471
+ limit: LATENCY_SERVICE_LIMIT,
472
+ });
473
+
474
+ // Drill 1: candidate operations ranked by p99, pinned to the service.
475
+ expect(topListSpy).toHaveBeenCalledTimes(1);
476
+ const topListRequest: Record<string, unknown> = topListSpy.mock
477
+ .calls[0]![0] as unknown as Record<string, unknown>;
478
+ expect(topListRequest["groupBy"]).toEqual(["name"]);
479
+ expect(topListRequest["metric"]).toBe("p99Duration");
480
+ expect(topListRequest["limit"]).toBe(LATENCY_TOP_OPERATION_CANDIDATES);
481
+ expect(
482
+ (topListRequest["serviceIds"] as Array<ObjectID>)[0]!.toString(),
483
+ ).toBe(entityId);
484
+
485
+ // Drill 2: slowest span of that op in the spike window, root query.
486
+ expect(findBySpy).toHaveBeenCalledTimes(2);
487
+ const slowestSpanFind: Record<string, unknown> = findBySpy.mock
488
+ .calls[0]![0] as unknown as Record<string, unknown>;
489
+ const slowestSpanQuery: Record<string, unknown> = slowestSpanFind[
490
+ "query"
491
+ ] as Record<string, unknown>;
492
+ expect(slowestSpanQuery["projectId"]).toBe(projectId);
493
+ expect(slowestSpanQuery["name"]).toBe("GET /users/42");
494
+ expect((slowestSpanQuery["primaryEntityId"] as ObjectID).toString()).toBe(
495
+ entityId,
496
+ );
497
+ const spanWindow: InBetween<Date> = slowestSpanQuery[
498
+ "startTime"
499
+ ] as InBetween<Date>;
500
+ expect(spanWindow).toBeInstanceOf(InBetween);
501
+ expect(spanWindow.startValue).toEqual(oneHourAgo);
502
+ expect(spanWindow.endValue).toEqual(now);
503
+ expect(slowestSpanFind["limit"]).toBe(1);
504
+ expect(slowestSpanFind["sort"]).toEqual({
505
+ durationUnixNano: SortOrder.Descending,
506
+ });
507
+ expect(slowestSpanFind["props"]).toEqual(
508
+ expect.objectContaining({ isRoot: true }),
509
+ );
510
+
511
+ // Drill 3: the whole trace, explicitly project-scoped, capped spans.
512
+ const traceFind: Record<string, unknown> = findBySpy.mock
513
+ .calls[1]![0] as unknown as Record<string, unknown>;
514
+ expect(traceFind["query"]).toEqual(
515
+ expect.objectContaining({ projectId: projectId, traceId: "trace-1" }),
516
+ );
517
+ expect(traceFind["limit"]).toBe(LATENCY_MAX_TRACE_SPANS);
518
+ expect(traceFind["props"]).toEqual(
519
+ expect.objectContaining({ isRoot: true }),
520
+ );
521
+
522
+ // The candidate carries the full drilled evidence.
523
+ expect(candidates).toHaveLength(1);
524
+ const candidate: InsightCandidate = candidates[0]!;
525
+ expect(candidate.insightType).toBe(AIInsightType.TraceLatencyRegression);
526
+ expect(candidate.fingerprint).toBe(`latency:${entityId}:GET /users/{n}`);
527
+ expect(candidate.title).toBe(
528
+ "Latency regression: p99 2.3x on GET /users/42 in checkout",
529
+ );
530
+ expect(candidate.severity).toBe(AIInsightSeverity.Medium);
531
+ expect(candidate.serviceName).toBe("checkout");
532
+ expect(candidate.telemetryServiceId?.toString()).toBe(entityId);
533
+ expect(candidate.traceId).toBe("trace-1");
534
+ expect(candidate.evidence.latency).toEqual(
535
+ expect.objectContaining({
536
+ recentP99Ms: 2300,
537
+ baselineP99Ms: 1000,
538
+ regressionMultiplier: 2.3,
539
+ operationName: "GET /users/42",
540
+ sampleTraceId: "trace-1",
541
+ }),
542
+ );
543
+ expect(
544
+ candidate.evidence.latency!.performanceFindings!.length,
545
+ ).toBeGreaterThan(0);
546
+ expect(candidate.evidence.latency!.codeLocations).toEqual([
547
+ { filePath: "src/db.ts", functionName: "loadUsers", lineNumber: 42 },
548
+ ]);
549
+ expect(candidate.detailMarkdown).toContain("Span-tree findings");
550
+ expect(candidate.detailMarkdown).toContain("Regression multiplier: 2.3x");
551
+ });
552
+
553
+ test("no regression → no drill queries at all", async () => {
554
+ jest
555
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
556
+ .mockResolvedValueOnce(
557
+ profile([profileRow({ entityId, count: 500, p99Ms: 1200 })]),
558
+ )
559
+ .mockResolvedValueOnce(
560
+ profile([profileRow({ entityId, count: 400, p99Ms: 900 })]),
561
+ );
562
+ const topListSpy: jest.SpyInstance = jest.spyOn(
563
+ TraceAggregationService,
564
+ "getAnalyticsTopList",
565
+ );
566
+ const findBySpy: jest.SpyInstance = jest.spyOn(SpanService, "findBy");
567
+
568
+ const detector: TraceLatencyRegressionDetector =
569
+ new TraceLatencyRegressionDetector();
570
+ const candidates: Array<InsightCandidate> = await detector.detect({
571
+ projectId,
572
+ now,
573
+ });
574
+
575
+ expect(candidates).toEqual([]);
576
+ expect(topListSpy).not.toHaveBeenCalled();
577
+ expect(findBySpy).not.toHaveBeenCalled();
578
+ });
579
+
580
+ test("regression with no drillable operation still produces evidence-bearing insight", async () => {
581
+ jest
582
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
583
+ .mockResolvedValueOnce(
584
+ profile([profileRow({ entityId, count: 500, p99Ms: 5000 })]),
585
+ )
586
+ .mockResolvedValueOnce(
587
+ profile([profileRow({ entityId, count: 400, p99Ms: 1000 })]),
588
+ );
589
+ jest
590
+ .spyOn(TraceAggregationService, "getAnalyticsTopList")
591
+ .mockResolvedValue([]);
592
+ const findBySpy: jest.SpyInstance = jest.spyOn(SpanService, "findBy");
593
+ jest.spyOn(ServiceService, "findOneById").mockResolvedValue(null);
594
+
595
+ const detector: TraceLatencyRegressionDetector =
596
+ new TraceLatencyRegressionDetector();
597
+ const candidates: Array<InsightCandidate> = await detector.detect({
598
+ projectId,
599
+ now,
600
+ });
601
+
602
+ expect(findBySpy).not.toHaveBeenCalled();
603
+ expect(candidates).toHaveLength(1);
604
+ expect(candidates[0]!.fingerprint).toBe(`latency:${entityId}:`);
605
+ expect(candidates[0]!.severity).toBe(AIInsightSeverity.High);
606
+ expect(candidates[0]!.traceId).toBeUndefined();
607
+ expect(
608
+ candidates[0]!.evidence.latency!.performanceFindings,
609
+ ).toBeUndefined();
610
+ });
611
+
612
+ /*
613
+ * The regression is real, but every operation in the spike hour is a
614
+ * one-off. Naming one of them would be a guess AND would move the dedupe
615
+ * key next tick — so the insight stays at service level: no drill
616
+ * queries, no operation in the title, stable service-scoped fingerprint.
617
+ */
618
+ test("no operation clears the sample floor → service-level insight, no drill, stable fingerprint", async () => {
619
+ jest
620
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
621
+ .mockResolvedValueOnce(
622
+ profile([profileRow({ entityId, count: 500, p99Ms: 5000 })]),
623
+ )
624
+ .mockResolvedValueOnce(
625
+ profile([profileRow({ entityId, count: 400, p99Ms: 1000 })]),
626
+ );
627
+ jest
628
+ .spyOn(TraceAggregationService, "getAnalyticsTopList")
629
+ .mockResolvedValue([
630
+ topItem({ value: "POST /admin/export", p99Ms: 9000, count: 1 }),
631
+ topItem({
632
+ value: "GET /rare",
633
+ p99Ms: 8000,
634
+ count: LATENCY_MIN_OPERATION_SAMPLE_COUNT - 1,
635
+ }),
636
+ ]);
637
+ const findBySpy: jest.SpyInstance = jest.spyOn(SpanService, "findBy");
638
+ jest
639
+ .spyOn(ServiceService, "findOneById")
640
+ .mockResolvedValue({ name: "checkout" } as unknown as Service);
641
+
642
+ const detector: TraceLatencyRegressionDetector =
643
+ new TraceLatencyRegressionDetector();
644
+ const candidates: Array<InsightCandidate> = await detector.detect({
645
+ projectId,
646
+ now,
647
+ });
648
+
649
+ expect(findBySpy).not.toHaveBeenCalled();
650
+ expect(candidates).toHaveLength(1);
651
+ expect(candidates[0]!.fingerprint).toBe(`latency:${entityId}:`);
652
+ expect(candidates[0]!.title).toBe(
653
+ "Latency regression: p99 5.0x in checkout",
654
+ );
655
+ expect(candidates[0]!.evidence.latency!.operationName).toBeUndefined();
656
+ expect(candidates[0]!.detailMarkdown).not.toContain("Slowest operation");
657
+ });
658
+
659
+ /*
660
+ * The drill must follow the CREDIBLE operation, not the freak one — the
661
+ * representative trace, the code locations and the fingerprint all hang
662
+ * off this choice.
663
+ */
664
+ test("a one-off slow op does not win the drill over the operation with real traffic", async () => {
665
+ jest
666
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
667
+ .mockResolvedValueOnce(
668
+ profile([profileRow({ entityId, count: 500, p99Ms: 2300 })]),
669
+ )
670
+ .mockResolvedValueOnce(
671
+ profile([profileRow({ entityId, count: 400, p99Ms: 1000 })]),
672
+ );
673
+ jest
674
+ .spyOn(TraceAggregationService, "getAnalyticsTopList")
675
+ .mockResolvedValue([
676
+ topItem({ value: "POST /admin/export", p99Ms: 9000, count: 1 }),
677
+ topItem({ value: "GET /users/42", p99Ms: 2300, count: 10_000 }),
678
+ ]);
679
+ const findBySpy: jest.SpyInstance = jest
680
+ .spyOn(SpanService, "findBy")
681
+ .mockResolvedValueOnce([{ traceId: "trace-1" } as unknown as Span])
682
+ .mockResolvedValueOnce(fakeTraceSpans());
683
+ jest
684
+ .spyOn(ServiceService, "findOneById")
685
+ .mockResolvedValue({ name: "checkout" } as unknown as Service);
686
+
687
+ const detector: TraceLatencyRegressionDetector =
688
+ new TraceLatencyRegressionDetector();
689
+ const candidates: Array<InsightCandidate> = await detector.detect({
690
+ projectId,
691
+ now,
692
+ });
693
+
694
+ const slowestSpanQuery: Record<string, unknown> = (
695
+ findBySpy.mock.calls[0]![0] as unknown as Record<string, unknown>
696
+ )["query"] as Record<string, unknown>;
697
+ expect(slowestSpanQuery["name"]).toBe("GET /users/42");
698
+ expect(candidates[0]!.fingerprint).toBe(
699
+ `latency:${entityId}:GET /users/{n}`,
700
+ );
701
+ expect(candidates[0]!.evidence.latency!.operationName).toBe(
702
+ "GET /users/42",
703
+ );
704
+ });
705
+
706
+ /*
707
+ * timeout_overflow_mode='break' returns partial aggregates with no
708
+ * error. A p99 over a fraction of the window compared against a whole
709
+ * one fabricates regressions — so a possibly-partial profile files
710
+ * NOTHING and waits for the next tick. Asserted on BOTH windows: the
711
+ * prior one is where a truncated scan does its worst damage.
712
+ */
713
+ test("a possibly-partial recent profile files nothing and never drills", async () => {
714
+ jest
715
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
716
+ .mockResolvedValueOnce({
717
+ rows: [profileRow({ entityId, count: 500, p99Ms: 5000 })],
718
+ isPartial: true,
719
+ })
720
+ .mockResolvedValueOnce(
721
+ profile([profileRow({ entityId, count: 400, p99Ms: 1000 })]),
722
+ );
723
+ const topListSpy: jest.SpyInstance = jest.spyOn(
724
+ TraceAggregationService,
725
+ "getAnalyticsTopList",
726
+ );
727
+ const findBySpy: jest.SpyInstance = jest.spyOn(SpanService, "findBy");
728
+
729
+ const detector: TraceLatencyRegressionDetector =
730
+ new TraceLatencyRegressionDetector();
731
+
732
+ await expect(detector.detect({ projectId, now })).resolves.toEqual([]);
733
+ expect(topListSpy).not.toHaveBeenCalled();
734
+ expect(findBySpy).not.toHaveBeenCalled();
735
+ });
736
+
737
+ test("a possibly-partial prior (baseline) profile files nothing", async () => {
738
+ jest
739
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
740
+ .mockResolvedValueOnce(
741
+ profile([profileRow({ entityId, count: 500, p99Ms: 5000 })]),
742
+ )
743
+ .mockResolvedValueOnce({
744
+ rows: [profileRow({ entityId, count: 400, p99Ms: 1000 })],
745
+ isPartial: true,
746
+ });
747
+ const topListSpy: jest.SpyInstance = jest.spyOn(
748
+ TraceAggregationService,
749
+ "getAnalyticsTopList",
750
+ );
751
+
752
+ const detector: TraceLatencyRegressionDetector =
753
+ new TraceLatencyRegressionDetector();
754
+
755
+ await expect(detector.detect({ projectId, now })).resolves.toEqual([]);
756
+ expect(topListSpy).not.toHaveBeenCalled();
757
+ });
758
+
759
+ test("detect propagates aggregation errors — the scanner isolates detectors", async () => {
760
+ jest
761
+ .spyOn(TraceAggregationService, "getServiceLatencyProfile")
762
+ .mockRejectedValue(new Error("clickhouse down"));
763
+
764
+ const detector: TraceLatencyRegressionDetector =
765
+ new TraceLatencyRegressionDetector();
766
+ await expect(detector.detect({ projectId, now })).rejects.toThrow(
767
+ "clickhouse down",
768
+ );
769
+ });
770
+ });