@oneuptime/common 11.5.4 → 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 (505) 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/GlobalConfig.ts +105 -0
  6. package/Models/DatabaseModels/Index.ts +4 -8
  7. package/Models/DatabaseModels/InstanceHealthLog.ts +277 -0
  8. package/Models/DatabaseModels/LlmLog.ts +33 -0
  9. package/Models/DatabaseModels/Project.ts +123 -6
  10. package/Models/DatabaseModels/Runbook.ts +1 -1
  11. package/Server/API/AIAgentDataAPI.ts +802 -140
  12. package/Server/API/AIAgentTaskAPI.ts +132 -69
  13. package/Server/API/AIAgentTaskLogAPI.ts +65 -41
  14. package/Server/API/AIAgentTaskPullRequestAPI.ts +41 -0
  15. package/Server/API/AIInsightAPI.ts +220 -0
  16. package/Server/API/AIInvestigationAPI.ts +363 -1
  17. package/Server/API/CodeFixRunAPI.ts +251 -0
  18. package/Server/API/GitHubAPI.ts +246 -3
  19. package/Server/API/TelemetryExceptionAPI.ts +193 -48
  20. package/Server/EnvironmentConfig.ts +1 -1
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1783943300000-DropServiceCodeRepository.ts +26 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1783947444597-CodeFixRunsOnAIRun.ts +73 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1783950962813-AddCodeFixTaskType.ts +29 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1783954040984-AddInstrumentationFixTasksFlag.ts +31 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1783957160597-MigrationName.ts +51 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783958542237-AddTaskContextToAIRun.ts +25 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1783965945957-AddInvestigationVerdictAndGrade.ts +47 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1783970619301-AddFixGuardrailColumns.ts +53 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1783970619302-DropLegacyAIAgentTaskTables.ts +31 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1783977781677-AddCiStatusToFixPullRequests.ts +37 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1783990000000-AddCompletionTokensToLlmLog.ts +25 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1784010274993-AddSentinelInsight.ts +75 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1784010274994-AddSentinelInsightFlags.ts +37 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1784030612266-RenameSentinelToAI.ts +203 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1784033837629-MigrationName.ts +163 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +30 -0
  37. package/Server/Infrastructure/Queue.ts +82 -4
  38. package/Server/Infrastructure/Semaphore.ts +62 -1
  39. package/Server/Services/AIAgentService.ts +32 -0
  40. package/Server/Services/AIAgentTaskPullRequestService.ts +117 -0
  41. package/Server/Services/AIInsightService.ts +174 -0
  42. package/Server/Services/AIRunEventService.ts +63 -0
  43. package/Server/Services/AIRunService.ts +259 -1
  44. package/Server/Services/AIService.ts +249 -7
  45. package/Server/Services/AlertService.ts +4 -4
  46. package/Server/Services/CodeRepositoryService.ts +209 -0
  47. package/Server/Services/GlobalConfigService.ts +148 -0
  48. package/Server/Services/IncidentService.ts +183 -38
  49. package/Server/Services/IncidentStateTimelineService.ts +24 -4
  50. package/Server/Services/Index.ts +4 -4
  51. package/Server/Services/{AIAgentTaskLogService.ts → InstanceHealthLogService.ts} +1 -1
  52. package/Server/Services/LlmLogService.ts +27 -0
  53. package/Server/Services/LlmProviderService.ts +108 -0
  54. package/Server/Services/MetricBaselineService.ts +123 -0
  55. package/Server/Services/MonitorService.ts +15 -0
  56. package/Server/Services/TelemetryExceptionService.ts +323 -116
  57. package/Server/Services/TraceAggregationService.ts +176 -0
  58. package/Server/Types/Workflow/ComponentCode.ts +6 -0
  59. package/Server/Types/Workflow/Components/AI/GenerateText.ts +326 -0
  60. package/Server/Types/Workflow/Components/Index.ts +2 -0
  61. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +1 -1
  62. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +71 -0
  63. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +243 -0
  64. package/Server/Utils/AI/CodeFix/CodeFixRunQueue.ts +131 -0
  65. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +148 -0
  66. package/Server/Utils/AI/CodeFix/OpenPullRequestCap.ts +117 -0
  67. package/Server/Utils/AI/Eval/EvalCorpus.ts +369 -0
  68. package/Server/Utils/AI/Eval/EvalScores.ts +172 -0
  69. package/Server/Utils/AI/Eval/ReplayInvestigation.ts +87 -0
  70. package/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.ts +676 -0
  71. package/Server/Utils/AI/{Sentinel/SentinelInvestigationEngine.ts → SRE/AIInvestigationEngine.ts} +58 -37
  72. package/Server/Utils/AI/{Sentinel/SentinelMemory.ts → SRE/AIMemory.ts} +3 -3
  73. package/Server/Utils/AI/{Sentinel → SRE}/AlertInvestigationRunner.ts +50 -20
  74. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +293 -0
  75. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +115 -0
  76. package/Server/Utils/AI/SRE/FixPerformanceTaskTrigger.ts +223 -0
  77. package/Server/Utils/AI/{Sentinel → SRE}/IncidentInvestigationRunner.ts +71 -21
  78. package/Server/Utils/AI/{Sentinel → SRE}/IncidentPostmortemRunner.ts +8 -8
  79. package/Server/Utils/AI/SRE/Insights/Detectors/ErrorLogSpikeDetector.ts +364 -0
  80. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +317 -0
  81. package/Server/Utils/AI/SRE/Insights/Detectors/Index.ts +27 -0
  82. package/Server/Utils/AI/SRE/Insights/Detectors/MetricDriftDetector.ts +221 -0
  83. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +238 -0
  84. package/Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector.ts +521 -0
  85. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +242 -0
  86. package/Server/Utils/AI/SRE/Insights/InsightScanner.ts +290 -0
  87. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +283 -0
  88. package/Server/Utils/AI/SRE/Insights/InsightTriageRunner.ts +242 -0
  89. package/Server/Utils/AI/SRE/Insights/Triage.ts +162 -0
  90. package/Server/Utils/AI/SRE/Insights/Types.ts +49 -0
  91. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +212 -0
  92. package/Server/Utils/AI/SRE/InvestigationGrader.ts +282 -0
  93. package/Server/Utils/AI/{Sentinel → SRE}/InvestigationQueue.ts +114 -19
  94. package/Server/Utils/AI/{Sentinel → SRE}/README.md +4 -4
  95. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +172 -0
  96. package/Server/Utils/AI/Toolbox/{SentinelActionTools.ts → AIActionTools.ts} +22 -1
  97. package/Server/Utils/AI/Toolbox/Index.ts +2 -2
  98. package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +526 -0
  99. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +319 -1
  100. package/Server/Utils/CodeRepository/ServiceRepoLinkSuggester.ts +221 -0
  101. package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +571 -0
  102. package/Server/Utils/Execute.ts +2 -1
  103. package/Server/Utils/LLM/LLMService.ts +160 -30
  104. package/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.ts +152 -0
  105. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +29 -0
  106. package/Tests/Server/Infrastructure/Queue.test.ts +227 -0
  107. package/Tests/Server/Infrastructure/SemaphorePermit.test.ts +159 -0
  108. package/Tests/Server/Services/AIAgentTaskPullRequestOutcomeStats.test.ts +154 -0
  109. package/Tests/Server/Services/AIInsightService.test.ts +324 -0
  110. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +401 -0
  111. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +161 -0
  112. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +95 -5
  113. package/Tests/Server/Services/AIServiceProjectAccess.test.ts +259 -0
  114. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +249 -0
  115. package/Tests/Server/Services/FixPerformanceTaskTrigger.test.ts +274 -0
  116. package/Tests/Server/Services/GlobalConfigService.test.ts +80 -0
  117. package/Tests/Server/Services/IncidentInternalNoteAnnouncement.test.ts +1 -1
  118. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +313 -0
  119. package/Tests/Server/Services/LlmProviderProjectOwned.test.ts +132 -0
  120. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +405 -0
  121. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +322 -0
  122. package/Tests/Server/Services/TraceServiceLatencyProfile.test.ts +278 -0
  123. package/Tests/Server/Types/Workflow/Components/AIGenerateText.test.ts +722 -0
  124. package/Tests/Server/Utils/AI/{SentinelAlertGating.test.ts → AIAlertGating.test.ts} +19 -19
  125. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +362 -0
  126. package/Tests/Server/Utils/AI/{SentinelInvestigationQueue.test.ts → AIInvestigationQueue.test.ts} +19 -19
  127. package/Tests/Server/Utils/AI/CodeAgentWorkspaceGuard.test.ts +138 -0
  128. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +335 -0
  129. package/Tests/Server/Utils/AI/CodeFixRunQueue.test.ts +134 -0
  130. package/Tests/Server/Utils/AI/EvalCorpus.test.ts +209 -0
  131. package/Tests/Server/Utils/AI/EvalScores.test.ts +343 -0
  132. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +218 -0
  133. package/Tests/Server/Utils/AI/Insights/ErrorLogSpikeDetector.test.ts +437 -0
  134. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +287 -0
  135. package/Tests/Server/Utils/AI/Insights/InsightDetectorsIndex.test.ts +34 -0
  136. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +572 -0
  137. package/Tests/Server/Utils/AI/Insights/InsightNeverThrowsHardening.test.ts +344 -0
  138. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +720 -0
  139. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +467 -0
  140. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +370 -0
  141. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +210 -0
  142. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +221 -0
  143. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +328 -0
  144. package/Tests/Server/Utils/AI/Insights/MetricBaselineDrift.test.ts +182 -0
  145. package/Tests/Server/Utils/AI/Insights/MetricDriftDetector.test.ts +280 -0
  146. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +259 -0
  147. package/Tests/Server/Utils/AI/Insights/TraceLatencyRegressionDetector.test.ts +770 -0
  148. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +343 -0
  149. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +307 -0
  150. package/Tests/Server/Utils/AI/OpenPullRequestCap.test.ts +170 -0
  151. package/Tests/Server/Utils/AI/SpanTreeAnalyzer.test.ts +559 -0
  152. package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +158 -0
  153. package/Tests/Server/Utils/GitHubCheckRunsConclusion.test.ts +100 -0
  154. package/Tests/Server/Utils/GitHubPullRequestStateMapping.test.ts +50 -0
  155. package/Tests/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.test.ts +266 -0
  156. package/Tests/Server/Utils/ServiceRepoLinkSuggester.test.ts +167 -0
  157. package/Tests/Server/Utils/StackTraceRepoResolver.test.ts +527 -0
  158. package/Tests/Types/AI/AIChatPermissionMode.test.ts +112 -0
  159. package/Tests/Types/AI/CodeFixTaskType.test.ts +152 -0
  160. package/Tests/Types/AI/FixPullRequestCiStatus.test.ts +181 -0
  161. package/Tests/Types/Monitor/MonitorStepSqlMonitor.test.ts +110 -0
  162. package/Tests/Types/Monitor/SqlDatabaseType.test.ts +55 -0
  163. package/Tests/Types/Workflow/AIGenerateTextMetadata.test.ts +180 -0
  164. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +168 -0
  165. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +523 -0
  166. package/Types/AI/AIChatTypes.ts +8 -0
  167. package/Types/AI/AIInsightEvidence.ts +77 -0
  168. package/Types/AI/AIInsightHumanVerdict.ts +15 -0
  169. package/Types/AI/AIInsightSeverity.ts +13 -0
  170. package/Types/AI/AIInsightStatus.ts +38 -0
  171. package/Types/AI/AIInsightType.ts +41 -0
  172. package/Types/AI/AIRunAutoGrade.ts +18 -0
  173. package/Types/AI/AIRunEventType.ts +5 -0
  174. package/Types/AI/AIRunHumanVerdict.ts +17 -0
  175. package/Types/AI/AIRunType.ts +6 -0
  176. package/Types/AI/CodeFixTaskContext.ts +88 -0
  177. package/Types/AI/CodeFixTaskType.ts +132 -0
  178. package/Types/AI/FixPullRequestCiStatus.ts +132 -0
  179. package/Types/BaseDatabase/AggregationIntervalUtil.ts +67 -0
  180. package/Types/Incident/IncidentMetricType.ts +7 -0
  181. package/Types/Monitor/CriteriaFilter.ts +8 -0
  182. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  183. package/Types/Monitor/MonitorStep.ts +38 -0
  184. package/Types/Monitor/MonitorStepSqlMonitor.ts +141 -0
  185. package/Types/Monitor/MonitorType.ts +17 -0
  186. package/Types/Monitor/SqlDatabaseType.ts +49 -0
  187. package/Types/Monitor/SqlMonitor/SqlMonitorResponse.ts +28 -0
  188. package/Types/Permission.ts +11 -95
  189. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  190. package/Types/Runbook/RunbookStep.ts +26 -2
  191. package/Types/Runbook/RunbookStepType.ts +1 -0
  192. package/Types/Workflow/Component.ts +13 -0
  193. package/Types/Workflow/ComponentID.ts +1 -0
  194. package/Types/Workflow/Components/AI.ts +148 -0
  195. package/Types/Workflow/Components.ts +8 -0
  196. package/UI/Components/AI/GenerateFromAIModal.tsx +1 -1
  197. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +11 -0
  198. package/UI/Components/MoreMenu/MoreMenu.tsx +1 -1
  199. package/UI/Components/Table/Table.tsx +38 -2
  200. package/UI/Utils/TableColumnsToCsv.ts +385 -0
  201. package/Utils/Monitor/MonitorMetricType.ts +1 -0
  202. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +59 -45
  203. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/AIInsight.js +795 -0
  205. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -0
  206. package/build/dist/Models/DatabaseModels/AIRun.js +274 -0
  207. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/CodeRepository.js +43 -0
  209. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/GlobalConfig.js +110 -0
  211. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/Index.js +4 -8
  213. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/InstanceHealthLog.js +304 -0
  215. package/build/dist/Models/DatabaseModels/InstanceHealthLog.js.map +1 -0
  216. package/build/dist/Models/DatabaseModels/LlmLog.js +35 -0
  217. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
  218. package/build/dist/Models/DatabaseModels/Project.js +127 -6
  219. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  220. package/build/dist/Models/DatabaseModels/Runbook.js +1 -1
  221. package/build/dist/Models/DatabaseModels/Runbook.js.map +1 -1
  222. package/build/dist/Server/API/AIAgentDataAPI.js +568 -85
  223. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  224. package/build/dist/Server/API/AIAgentTaskAPI.js +100 -44
  225. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  226. package/build/dist/Server/API/AIAgentTaskLogAPI.js +49 -25
  227. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  228. package/build/dist/Server/API/AIAgentTaskPullRequestAPI.js +27 -0
  229. package/build/dist/Server/API/AIAgentTaskPullRequestAPI.js.map +1 -1
  230. package/build/dist/Server/API/AIInsightAPI.js +140 -0
  231. package/build/dist/Server/API/AIInsightAPI.js.map +1 -0
  232. package/build/dist/Server/API/AIInvestigationAPI.js +257 -1
  233. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  234. package/build/dist/Server/API/CodeFixRunAPI.js +181 -0
  235. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -0
  236. package/build/dist/Server/API/GitHubAPI.js +164 -2
  237. package/build/dist/Server/API/GitHubAPI.js.map +1 -1
  238. package/build/dist/Server/API/TelemetryExceptionAPI.js +148 -43
  239. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  240. package/build/dist/Server/EnvironmentConfig.js +1 -1
  241. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783943300000-DropServiceCodeRepository.js +23 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783943300000-DropServiceCodeRepository.js.map +1 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783947444597-CodeFixRunsOnAIRun.js +32 -0
  245. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783947444597-CodeFixRunsOnAIRun.js.map +1 -0
  246. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783950962813-AddCodeFixTaskType.js +16 -0
  247. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783950962813-AddCodeFixTaskType.js.map +1 -0
  248. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783954040984-AddInstrumentationFixTasksFlag.js +16 -0
  249. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783954040984-AddInstrumentationFixTasksFlag.js.map +1 -0
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783957160597-MigrationName.js +24 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783957160597-MigrationName.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783958542237-AddTaskContextToAIRun.js +16 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783958542237-AddTaskContextToAIRun.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783965945957-AddInvestigationVerdictAndGrade.js +24 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783965945957-AddInvestigationVerdictAndGrade.js.map +1 -0
  256. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619301-AddFixGuardrailColumns.js +24 -0
  257. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619301-AddFixGuardrailColumns.js.map +1 -0
  258. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619302-DropLegacyAIAgentTaskTables.js +24 -0
  259. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783970619302-DropLegacyAIAgentTaskTables.js.map +1 -0
  260. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783977781677-AddCiStatusToFixPullRequests.js +18 -0
  261. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783977781677-AddCiStatusToFixPullRequests.js.map +1 -0
  262. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783990000000-AddCompletionTokensToLlmLog.js +18 -0
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783990000000-AddCompletionTokensToLlmLog.js.map +1 -0
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274993-AddSentinelInsight.js +32 -0
  265. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274993-AddSentinelInsight.js.map +1 -0
  266. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274994-AddSentinelInsightFlags.js +18 -0
  267. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784010274994-AddSentinelInsightFlags.js.map +1 -0
  268. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784030612266-RenameSentinelToAI.js +125 -0
  269. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784030612266-RenameSentinelToAI.js.map +1 -0
  270. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784033837629-MigrationName.js +62 -0
  271. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784033837629-MigrationName.js.map +1 -0
  272. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +30 -0
  273. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  274. package/build/dist/Server/Infrastructure/Queue.js +73 -3
  275. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  276. package/build/dist/Server/Infrastructure/Semaphore.js +44 -1
  277. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  278. package/build/dist/Server/Services/AIAgentService.js +29 -0
  279. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  280. package/build/dist/Server/Services/AIAgentTaskPullRequestService.js +78 -0
  281. package/build/dist/Server/Services/AIAgentTaskPullRequestService.js.map +1 -1
  282. package/build/dist/Server/Services/AIInsightService.js +164 -0
  283. package/build/dist/Server/Services/AIInsightService.js.map +1 -0
  284. package/build/dist/Server/Services/AIRunEventService.js +49 -0
  285. package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
  286. package/build/dist/Server/Services/AIRunService.js +221 -0
  287. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  288. package/build/dist/Server/Services/AIService.js +198 -11
  289. package/build/dist/Server/Services/AIService.js.map +1 -1
  290. package/build/dist/Server/Services/AlertService.js +4 -4
  291. package/build/dist/Server/Services/AlertService.js.map +1 -1
  292. package/build/dist/Server/Services/CodeRepositoryService.js +180 -0
  293. package/build/dist/Server/Services/CodeRepositoryService.js.map +1 -1
  294. package/build/dist/Server/Services/GlobalConfigService.js +84 -0
  295. package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
  296. package/build/dist/Server/Services/IncidentService.js +149 -37
  297. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  298. package/build/dist/Server/Services/IncidentStateTimelineService.js +24 -4
  299. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  300. package/build/dist/Server/Services/Index.js +4 -4
  301. package/build/dist/Server/Services/Index.js.map +1 -1
  302. package/build/dist/Server/Services/{AIAgentTaskLogService.js → InstanceHealthLogService.js} +2 -2
  303. package/build/dist/Server/Services/InstanceHealthLogService.js.map +1 -0
  304. package/build/dist/Server/Services/LlmLogService.js +23 -0
  305. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  306. package/build/dist/Server/Services/LlmProviderService.js +96 -0
  307. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  308. package/build/dist/Server/Services/MetricBaselineService.js +65 -0
  309. package/build/dist/Server/Services/MetricBaselineService.js.map +1 -1
  310. package/build/dist/Server/Services/MonitorService.js +9 -1
  311. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  312. package/build/dist/Server/Services/TelemetryExceptionService.js +243 -82
  313. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  314. package/build/dist/Server/Services/TraceAggregationService.js +124 -0
  315. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  316. package/build/dist/Server/Types/Workflow/ComponentCode.js.map +1 -1
  317. package/build/dist/Server/Types/Workflow/Components/AI/GenerateText.js +240 -0
  318. package/build/dist/Server/Types/Workflow/Components/AI/GenerateText.js.map +1 -0
  319. package/build/dist/Server/Types/Workflow/Components/Index.js +2 -0
  320. package/build/dist/Server/Types/Workflow/Components/Index.js.map +1 -1
  321. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +48 -0
  322. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -0
  323. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +174 -0
  324. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -0
  325. package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js +126 -0
  326. package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js.map +1 -0
  327. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +131 -0
  328. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -0
  329. package/build/dist/Server/Utils/AI/CodeFix/OpenPullRequestCap.js +99 -0
  330. package/build/dist/Server/Utils/AI/CodeFix/OpenPullRequestCap.js.map +1 -0
  331. package/build/dist/Server/Utils/AI/Eval/EvalCorpus.js +245 -0
  332. package/build/dist/Server/Utils/AI/Eval/EvalCorpus.js.map +1 -0
  333. package/build/dist/Server/Utils/AI/Eval/EvalScores.js +83 -0
  334. package/build/dist/Server/Utils/AI/Eval/EvalScores.js.map +1 -0
  335. package/build/dist/Server/Utils/AI/Eval/ReplayInvestigation.js +12 -0
  336. package/build/dist/Server/Utils/AI/Eval/ReplayInvestigation.js.map +1 -0
  337. package/build/dist/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.js +439 -0
  338. package/build/dist/Server/Utils/AI/PerfEvidence/SpanTreeAnalyzer.js.map +1 -0
  339. package/build/dist/Server/Utils/AI/{Sentinel/SentinelInvestigationEngine.js → SRE/AIInvestigationEngine.js} +47 -31
  340. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -0
  341. package/build/dist/Server/Utils/AI/{Sentinel/SentinelMemory.js → SRE/AIMemory.js} +5 -5
  342. package/build/dist/Server/Utils/AI/SRE/AIMemory.js.map +1 -0
  343. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/AlertInvestigationRunner.js +42 -19
  344. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -0
  345. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +226 -0
  346. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -0
  347. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +100 -0
  348. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -0
  349. package/build/dist/Server/Utils/AI/SRE/FixPerformanceTaskTrigger.js +174 -0
  350. package/build/dist/Server/Utils/AI/SRE/FixPerformanceTaskTrigger.js.map +1 -0
  351. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/IncidentInvestigationRunner.js +60 -22
  352. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -0
  353. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/IncidentPostmortemRunner.js +9 -9
  354. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -0
  355. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ErrorLogSpikeDetector.js +268 -0
  356. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ErrorLogSpikeDetector.js.map +1 -0
  357. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +226 -0
  358. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -0
  359. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/Index.js +26 -0
  360. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/Index.js.map +1 -0
  361. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/MetricDriftDetector.js +159 -0
  362. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/MetricDriftDetector.js.map +1 -0
  363. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +177 -0
  364. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -0
  365. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector.js +384 -0
  366. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/TraceLatencyRegressionDetector.js.map +1 -0
  367. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js +180 -0
  368. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -0
  369. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js +269 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/InsightScanner.js.map +1 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +231 -0
  372. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -0
  373. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js +206 -0
  374. package/build/dist/Server/Utils/AI/SRE/Insights/InsightTriageRunner.js.map +1 -0
  375. package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js +121 -0
  376. package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js.map +1 -0
  377. package/build/dist/Server/Utils/AI/SRE/Insights/Types.js +2 -0
  378. package/build/dist/Server/Utils/AI/SRE/Insights/Types.js.map +1 -0
  379. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +148 -0
  380. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -0
  381. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +231 -0
  382. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -0
  383. package/build/dist/Server/Utils/AI/{Sentinel → SRE}/InvestigationQueue.js +102 -23
  384. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -0
  385. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +167 -0
  386. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -0
  387. package/build/dist/Server/Utils/AI/Toolbox/{SentinelActionTools.js → AIActionTools.js} +18 -2
  388. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -0
  389. package/build/dist/Server/Utils/AI/Toolbox/Index.js +2 -2
  390. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  391. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +301 -0
  392. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -0
  393. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +240 -2
  394. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  395. package/build/dist/Server/Utils/CodeRepository/ServiceRepoLinkSuggester.js +127 -0
  396. package/build/dist/Server/Utils/CodeRepository/ServiceRepoLinkSuggester.js.map +1 -0
  397. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +336 -0
  398. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -0
  399. package/build/dist/Server/Utils/Execute.js.map +1 -1
  400. package/build/dist/Server/Utils/LLM/LLMService.js +125 -25
  401. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  402. package/build/dist/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.js +120 -0
  403. package/build/dist/Server/Utils/Monitor/Criteria/SqlMonitorCriteria.js.map +1 -0
  404. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +21 -0
  405. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  406. package/build/dist/Types/AI/AIInsightEvidence.js +2 -0
  407. package/build/dist/Types/AI/AIInsightEvidence.js.map +1 -0
  408. package/build/dist/Types/AI/AIInsightHumanVerdict.js +16 -0
  409. package/build/dist/Types/AI/AIInsightHumanVerdict.js.map +1 -0
  410. package/build/dist/Types/AI/AIInsightSeverity.js +14 -0
  411. package/build/dist/Types/AI/AIInsightSeverity.js.map +1 -0
  412. package/build/dist/Types/AI/AIInsightStatus.js +36 -0
  413. package/build/dist/Types/AI/AIInsightStatus.js.map +1 -0
  414. package/build/dist/Types/AI/AIInsightType.js +42 -0
  415. package/build/dist/Types/AI/AIInsightType.js.map +1 -0
  416. package/build/dist/Types/AI/AIRunAutoGrade.js +19 -0
  417. package/build/dist/Types/AI/AIRunAutoGrade.js.map +1 -0
  418. package/build/dist/Types/AI/AIRunEventType.js +5 -0
  419. package/build/dist/Types/AI/AIRunEventType.js.map +1 -1
  420. package/build/dist/Types/AI/AIRunHumanVerdict.js +18 -0
  421. package/build/dist/Types/AI/AIRunHumanVerdict.js.map +1 -0
  422. package/build/dist/Types/AI/AIRunType.js +6 -0
  423. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  424. package/build/dist/Types/AI/CodeFixTaskContext.js +29 -0
  425. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -0
  426. package/build/dist/Types/AI/CodeFixTaskType.js +122 -0
  427. package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -0
  428. package/build/dist/Types/AI/FixPullRequestCiStatus.js +102 -0
  429. package/build/dist/Types/AI/FixPullRequestCiStatus.js.map +1 -0
  430. package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js +55 -0
  431. package/build/dist/Types/BaseDatabase/AggregationIntervalUtil.js.map +1 -1
  432. package/build/dist/Types/Incident/IncidentMetricType.js +7 -0
  433. package/build/dist/Types/Incident/IncidentMetricType.js.map +1 -1
  434. package/build/dist/Types/Monitor/CriteriaFilter.js +7 -0
  435. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  436. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  437. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  438. package/build/dist/Types/Monitor/MonitorStep.js +28 -0
  439. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  440. package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js +93 -0
  441. package/build/dist/Types/Monitor/MonitorStepSqlMonitor.js.map +1 -0
  442. package/build/dist/Types/Monitor/MonitorType.js +15 -0
  443. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  444. package/build/dist/Types/Monitor/SqlDatabaseType.js +47 -0
  445. package/build/dist/Types/Monitor/SqlDatabaseType.js.map +1 -0
  446. package/build/dist/Types/Monitor/SqlMonitor/SqlMonitorResponse.js +2 -0
  447. package/build/dist/Types/Monitor/SqlMonitor/SqlMonitorResponse.js.map +1 -0
  448. package/build/dist/Types/Permission.js +10 -84
  449. package/build/dist/Types/Permission.js.map +1 -1
  450. package/build/dist/Types/Runbook/RunbookStepType.js +1 -0
  451. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  452. package/build/dist/Types/Workflow/ComponentID.js +1 -0
  453. package/build/dist/Types/Workflow/ComponentID.js.map +1 -1
  454. package/build/dist/Types/Workflow/Components/AI.js +135 -0
  455. package/build/dist/Types/Workflow/Components/AI.js.map +1 -0
  456. package/build/dist/Types/Workflow/Components.js +7 -0
  457. package/build/dist/Types/Workflow/Components.js.map +1 -1
  458. package/build/dist/UI/Components/AI/GenerateFromAIModal.js +1 -1
  459. package/build/dist/UI/Components/AI/GenerateFromAIModal.js.map +1 -1
  460. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +9 -0
  461. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  462. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +1 -1
  463. package/build/dist/UI/Components/Table/Table.js +28 -2
  464. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  465. package/build/dist/UI/Utils/TableColumnsToCsv.js +291 -0
  466. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -0
  467. package/build/dist/Utils/Monitor/MonitorMetricType.js +1 -0
  468. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  469. package/package.json +1 -1
  470. package/Models/DatabaseModels/AIAgentTask.ts +0 -660
  471. package/Models/DatabaseModels/AIAgentTaskLog.ts +0 -426
  472. package/Models/DatabaseModels/AIAgentTaskTelemetryException.ts +0 -399
  473. package/Models/DatabaseModels/ServiceCodeRepository.ts +0 -647
  474. package/Server/Services/AIAgentTaskService.ts +0 -240
  475. package/Server/Services/AIAgentTaskTelemetryExceptionService.ts +0 -39
  476. package/Server/Services/ServiceCodeRepositoryService.ts +0 -55
  477. package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +0 -146
  478. package/Types/AI/AIAgentTaskMetadata.ts +0 -25
  479. package/Types/AI/AIAgentTaskType.ts +0 -40
  480. package/build/dist/Models/DatabaseModels/AIAgentTask.js +0 -691
  481. package/build/dist/Models/DatabaseModels/AIAgentTask.js.map +0 -1
  482. package/build/dist/Models/DatabaseModels/AIAgentTaskLog.js +0 -447
  483. package/build/dist/Models/DatabaseModels/AIAgentTaskLog.js.map +0 -1
  484. package/build/dist/Models/DatabaseModels/AIAgentTaskTelemetryException.js +0 -415
  485. package/build/dist/Models/DatabaseModels/AIAgentTaskTelemetryException.js.map +0 -1
  486. package/build/dist/Models/DatabaseModels/ServiceCodeRepository.js +0 -665
  487. package/build/dist/Models/DatabaseModels/ServiceCodeRepository.js.map +0 -1
  488. package/build/dist/Server/Services/AIAgentTaskLogService.js.map +0 -1
  489. package/build/dist/Server/Services/AIAgentTaskService.js +0 -217
  490. package/build/dist/Server/Services/AIAgentTaskService.js.map +0 -1
  491. package/build/dist/Server/Services/AIAgentTaskTelemetryExceptionService.js +0 -36
  492. package/build/dist/Server/Services/AIAgentTaskTelemetryExceptionService.js.map +0 -1
  493. package/build/dist/Server/Services/ServiceCodeRepositoryService.js +0 -54
  494. package/build/dist/Server/Services/ServiceCodeRepositoryService.js.map +0 -1
  495. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +0 -1
  496. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +0 -1
  497. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +0 -1
  498. package/build/dist/Server/Utils/AI/Sentinel/InvestigationQueue.js.map +0 -1
  499. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +0 -1
  500. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +0 -1
  501. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +0 -1
  502. package/build/dist/Types/AI/AIAgentTaskMetadata.js +0 -6
  503. package/build/dist/Types/AI/AIAgentTaskMetadata.js.map +0 -1
  504. package/build/dist/Types/AI/AIAgentTaskType.js +0 -29
  505. package/build/dist/Types/AI/AIAgentTaskType.js.map +0 -1
@@ -0,0 +1,720 @@
1
+ import InsightScanner from "../../../../../Server/Utils/AI/SRE/Insights/InsightScanner";
2
+ import InsightStore, {
3
+ UpsertCandidatesResult,
4
+ } from "../../../../../Server/Utils/AI/SRE/Insights/InsightStore";
5
+ import InsightDetectors from "../../../../../Server/Utils/AI/SRE/Insights/Detectors/Index";
6
+ import InsightFixRouting from "../../../../../Server/Utils/AI/SRE/Insights/FixRouting";
7
+ import InsightTriage from "../../../../../Server/Utils/AI/SRE/Insights/Triage";
8
+ import {
9
+ InsightCandidate,
10
+ InsightDetector,
11
+ InsightScanContext,
12
+ } from "../../../../../Server/Utils/AI/SRE/Insights/Types";
13
+ import FixRunBudget, {
14
+ FixRunBudgetDecision,
15
+ } from "../../../../../Server/Utils/AI/CodeFix/FixRunBudget";
16
+ import ProjectService from "../../../../../Server/Services/ProjectService";
17
+ import AIInsightService from "../../../../../Server/Services/AIInsightService";
18
+ import TelemetryExceptionService from "../../../../../Server/Services/TelemetryExceptionService";
19
+ import AIRunService from "../../../../../Server/Services/AIRunService";
20
+ import Project from "../../../../../Models/DatabaseModels/Project";
21
+ import AIInsight from "../../../../../Models/DatabaseModels/AIInsight";
22
+ import AIInsightStatus from "../../../../../Types/AI/AIInsightStatus";
23
+ import AIInsightType from "../../../../../Types/AI/AIInsightType";
24
+ import AIInsightSeverity from "../../../../../Types/AI/AIInsightSeverity";
25
+ import LIMIT_MAX from "../../../../../Types/Database/LimitMax";
26
+ import ObjectID from "../../../../../Types/ObjectID";
27
+ import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
28
+
29
+ /*
30
+ * The scanner is the watch loop's orchestration and must uphold the quiet,
31
+ * opt-in posture: only projects with enableAiInsights=true are ever
32
+ * scanned (the flag query IS the gate), every layer fails in isolation (one
33
+ * broken project, detector or insight never stops the rest), only NEWLY
34
+ * created insights are routed — refreshed rows keep their status — and a
35
+ * new insight always leaves the defensive Detected state in the same tick:
36
+ * FixOpened + fixAiRunId when fix routing opened one, ActionRequired
37
+ * otherwise, with triage enqueued for fix and non-fix insights alike.
38
+ *
39
+ * Plus the self-heal sweep: a row an earlier tick created but never routed
40
+ * (pod death, failed status write) is stranded in Detected forever — the
41
+ * refresh path never re-routes it, and its fingerprint stays pinned. The
42
+ * sweep re-routes it on the next tick, runs BEFORE the detectors (so this
43
+ * tick's own rows cannot be routed twice), never breaks the scan when it
44
+ * fails, and cannot double-create work (the fix/triage dedupes hold).
45
+ */
46
+
47
+ const projectId: ObjectID = ObjectID.generate();
48
+
49
+ function fakeProject(overrides?: { id?: ObjectID }): Project {
50
+ return {
51
+ id: overrides?.id ?? projectId,
52
+ enableAiInsights: true,
53
+ enableInsightFixTasks: false,
54
+ enableAi: true,
55
+ } as unknown as Project;
56
+ }
57
+
58
+ function makeCandidate(
59
+ overrides?: Partial<InsightCandidate>,
60
+ ): InsightCandidate {
61
+ return {
62
+ insightType: AIInsightType.NewException,
63
+ fingerprint: "new-exception:abc",
64
+ title: "New exception in checkout",
65
+ detailMarkdown: "**3 occurrences** in the last 24 hours.",
66
+ severity: AIInsightSeverity.Medium,
67
+ evidence: { exception: { recentOccurrenceCount: 3 } },
68
+ ...overrides,
69
+ };
70
+ }
71
+
72
+ function fakeInsight(): AIInsight {
73
+ return {
74
+ id: ObjectID.generate(),
75
+ projectId,
76
+ status: AIInsightStatus.Detected,
77
+ } as unknown as AIInsight;
78
+ }
79
+
80
+ function fakeDetector(data: {
81
+ insightType?: AIInsightType;
82
+ candidates?: Array<InsightCandidate>;
83
+ error?: Error;
84
+ }): InsightDetector {
85
+ return {
86
+ insightType: data.insightType ?? AIInsightType.NewException,
87
+ detect: (
88
+ _context: InsightScanContext,
89
+ ): Promise<Array<InsightCandidate>> => {
90
+ if (data.error) {
91
+ return Promise.reject(data.error);
92
+ }
93
+ return Promise.resolve(data.candidates || []);
94
+ },
95
+ };
96
+ }
97
+
98
+ function emptyUpsertResult(
99
+ overrides?: Partial<UpsertCandidatesResult>,
100
+ ): UpsertCandidatesResult {
101
+ return {
102
+ created: [],
103
+ refreshed: 0,
104
+ suppressed: 0,
105
+ droppedByCap: 0,
106
+ ...overrides,
107
+ };
108
+ }
109
+
110
+ describe("InsightScanner.scanAllProjects — flag gating and isolation", () => {
111
+ afterEach(() => {
112
+ jest.restoreAllMocks();
113
+ });
114
+
115
+ test("queries ONLY opted-in projects (enableAiInsights: true) as root and scans each", async () => {
116
+ const projectA: Project = fakeProject({ id: ObjectID.generate() });
117
+ const projectB: Project = fakeProject({ id: ObjectID.generate() });
118
+ const findBy: jest.SpyInstance = jest
119
+ .spyOn(ProjectService, "findBy")
120
+ .mockResolvedValue([projectA, projectB]);
121
+ const scanProject: jest.SpyInstance = jest
122
+ .spyOn(InsightScanner, "scanProjectForInsights")
123
+ .mockResolvedValue(undefined);
124
+
125
+ await InsightScanner.scanAllProjects();
126
+
127
+ expect(findBy).toHaveBeenCalledWith(
128
+ expect.objectContaining({
129
+ query: expect.objectContaining({ enableAiInsights: true }),
130
+ select: expect.objectContaining({
131
+ _id: true,
132
+ enableInsightFixTasks: true,
133
+ enableAi: true,
134
+ }),
135
+ limit: LIMIT_MAX,
136
+ props: expect.objectContaining({ isRoot: true }),
137
+ }),
138
+ );
139
+ expect(scanProject).toHaveBeenCalledTimes(2);
140
+ expect(scanProject).toHaveBeenCalledWith(projectA);
141
+ expect(scanProject).toHaveBeenCalledWith(projectB);
142
+ });
143
+
144
+ test("no opted-in projects → no scans", async () => {
145
+ jest.spyOn(ProjectService, "findBy").mockResolvedValue([]);
146
+ const scanProject: jest.SpyInstance = jest
147
+ .spyOn(InsightScanner, "scanProjectForInsights")
148
+ .mockResolvedValue(undefined);
149
+
150
+ await InsightScanner.scanAllProjects();
151
+
152
+ expect(scanProject).not.toHaveBeenCalled();
153
+ });
154
+
155
+ test("one project's scan failure does not stop the sweep — the rest still scan", async () => {
156
+ const projectA: Project = fakeProject({ id: ObjectID.generate() });
157
+ const projectB: Project = fakeProject({ id: ObjectID.generate() });
158
+ jest
159
+ .spyOn(ProjectService, "findBy")
160
+ .mockResolvedValue([projectA, projectB]);
161
+ const scanProject: jest.SpyInstance = jest
162
+ .spyOn(InsightScanner, "scanProjectForInsights")
163
+ .mockRejectedValueOnce(new Error("clickhouse down for tenant A"))
164
+ .mockResolvedValueOnce(undefined);
165
+
166
+ await expect(InsightScanner.scanAllProjects()).resolves.toBeUndefined();
167
+
168
+ expect(scanProject).toHaveBeenCalledTimes(2);
169
+ expect(scanProject).toHaveBeenCalledWith(projectB);
170
+ });
171
+
172
+ test("never throws into the cron caller — even the project listing failing resolves quietly", async () => {
173
+ jest
174
+ .spyOn(ProjectService, "findBy")
175
+ .mockRejectedValue(new Error("db down"));
176
+ const scanProject: jest.SpyInstance = jest
177
+ .spyOn(InsightScanner, "scanProjectForInsights")
178
+ .mockResolvedValue(undefined);
179
+
180
+ await expect(InsightScanner.scanAllProjects()).resolves.toBeUndefined();
181
+
182
+ expect(scanProject).not.toHaveBeenCalled();
183
+ });
184
+ });
185
+
186
+ describe("InsightScanner.scanProjectForInsights — detectors and the store", () => {
187
+ beforeEach(() => {
188
+ // Nothing stranded: the self-heal sweep is a no-op for these tests.
189
+ jest.spyOn(AIInsightService, "findBy").mockResolvedValue([]);
190
+ });
191
+
192
+ afterEach(() => {
193
+ jest.restoreAllMocks();
194
+ });
195
+
196
+ test("one throwing detector does not prevent the others — the survivors' candidates still reach the store", async () => {
197
+ const survivorCandidate: InsightCandidate = makeCandidate({
198
+ insightType: AIInsightType.ErrorLogSpike,
199
+ fingerprint: "error-log-spike:svc",
200
+ });
201
+ jest.spyOn(InsightDetectors, "getAllDetectors").mockReturnValue([
202
+ fakeDetector({
203
+ insightType: AIInsightType.NewException,
204
+ error: new Error("query timeout"),
205
+ }),
206
+ fakeDetector({
207
+ insightType: AIInsightType.ErrorLogSpike,
208
+ candidates: [survivorCandidate],
209
+ }),
210
+ ]);
211
+ const upsert: jest.SpyInstance = jest
212
+ .spyOn(InsightStore, "upsertCandidates")
213
+ .mockResolvedValue(emptyUpsertResult());
214
+
215
+ await expect(
216
+ InsightScanner.scanProjectForInsights(fakeProject()),
217
+ ).resolves.toBeUndefined();
218
+
219
+ expect(upsert).toHaveBeenCalledTimes(1);
220
+ expect(upsert).toHaveBeenCalledWith(
221
+ expect.objectContaining({
222
+ projectId,
223
+ candidates: [survivorCandidate],
224
+ now: expect.any(Date),
225
+ }),
226
+ );
227
+ });
228
+
229
+ test("every detector gets the projectId and the tick's single clock", async () => {
230
+ const seenContexts: Array<InsightScanContext> = [];
231
+
232
+ function recordingDetector(insightType: AIInsightType): InsightDetector {
233
+ return {
234
+ insightType,
235
+ detect: (
236
+ context: InsightScanContext,
237
+ ): Promise<Array<InsightCandidate>> => {
238
+ seenContexts.push(context);
239
+ return Promise.resolve([]);
240
+ },
241
+ };
242
+ }
243
+
244
+ jest
245
+ .spyOn(InsightDetectors, "getAllDetectors")
246
+ .mockReturnValue([
247
+ recordingDetector(AIInsightType.NewException),
248
+ recordingDetector(AIInsightType.MetricDrift),
249
+ ]);
250
+ jest
251
+ .spyOn(InsightStore, "upsertCandidates")
252
+ .mockResolvedValue(emptyUpsertResult());
253
+
254
+ await InsightScanner.scanProjectForInsights(fakeProject());
255
+
256
+ expect(seenContexts).toHaveLength(2);
257
+ expect(seenContexts[0]?.projectId).toBe(projectId);
258
+ expect(seenContexts[0]?.now).toBeInstanceOf(Date);
259
+ // Same clock for every detector — windows must line up across sensors.
260
+ expect(seenContexts[1]?.now).toBe(seenContexts[0]?.now);
261
+ });
262
+
263
+ test("no candidates → the store is never touched", async () => {
264
+ jest
265
+ .spyOn(InsightDetectors, "getAllDetectors")
266
+ .mockReturnValue([fakeDetector({ candidates: [] })]);
267
+ const upsert: jest.SpyInstance = jest.spyOn(
268
+ InsightStore,
269
+ "upsertCandidates",
270
+ );
271
+
272
+ await InsightScanner.scanProjectForInsights(fakeProject());
273
+
274
+ expect(upsert).not.toHaveBeenCalled();
275
+ });
276
+ });
277
+
278
+ describe("InsightScanner — routing newly created insights", () => {
279
+ let updateOneById: jest.SpyInstance;
280
+ let routeInsightFix: jest.SpyInstance;
281
+ let enqueueInsightTriage: jest.SpyInstance;
282
+
283
+ beforeEach(() => {
284
+ jest
285
+ .spyOn(InsightDetectors, "getAllDetectors")
286
+ .mockReturnValue([fakeDetector({ candidates: [makeCandidate()] })]);
287
+ // Nothing stranded: the self-heal sweep is a no-op for these tests.
288
+ jest.spyOn(AIInsightService, "findBy").mockResolvedValue([]);
289
+ updateOneById = jest
290
+ .spyOn(AIInsightService, "updateOneById")
291
+ .mockResolvedValue(undefined);
292
+ routeInsightFix = jest
293
+ .spyOn(InsightFixRouting, "routeInsightFix")
294
+ .mockResolvedValue({});
295
+ enqueueInsightTriage = jest
296
+ .spyOn(InsightTriage, "enqueueInsightTriage")
297
+ .mockResolvedValue({});
298
+ });
299
+
300
+ afterEach(() => {
301
+ jest.restoreAllMocks();
302
+ });
303
+
304
+ test("fix routing returned a run → status FixOpened + fixAiRunId persisted", async () => {
305
+ const insight: AIInsight = fakeInsight();
306
+ const fixAiRunId: ObjectID = ObjectID.generate();
307
+ jest
308
+ .spyOn(InsightStore, "upsertCandidates")
309
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
310
+ routeInsightFix.mockResolvedValue({ fixAiRunId });
311
+
312
+ await InsightScanner.scanProjectForInsights(fakeProject());
313
+
314
+ expect(updateOneById).toHaveBeenCalledWith(
315
+ expect.objectContaining({
316
+ id: insight.id,
317
+ data: expect.objectContaining({
318
+ status: AIInsightStatus.FixOpened,
319
+ fixAiRunId,
320
+ }),
321
+ props: expect.objectContaining({ isRoot: true }),
322
+ }),
323
+ );
324
+ });
325
+
326
+ test("no fix opened → status ActionRequired", async () => {
327
+ const insight: AIInsight = fakeInsight();
328
+ jest
329
+ .spyOn(InsightStore, "upsertCandidates")
330
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
331
+
332
+ await InsightScanner.scanProjectForInsights(fakeProject());
333
+
334
+ expect(updateOneById).toHaveBeenCalledWith(
335
+ expect.objectContaining({
336
+ id: insight.id,
337
+ data: expect.objectContaining({
338
+ status: AIInsightStatus.ActionRequired,
339
+ }),
340
+ props: expect.objectContaining({ isRoot: true }),
341
+ }),
342
+ );
343
+ });
344
+
345
+ test("the project (carrying its enableInsightFixTasks flag) is handed to fix routing — the flag gate lives there", async () => {
346
+ const insight: AIInsight = fakeInsight();
347
+ const project: Project = fakeProject();
348
+ jest
349
+ .spyOn(InsightStore, "upsertCandidates")
350
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
351
+
352
+ await InsightScanner.scanProjectForInsights(project);
353
+
354
+ expect(routeInsightFix).toHaveBeenCalledWith({ insight, project });
355
+ });
356
+
357
+ test("triage run enqueued → triageAiRunId persisted", async () => {
358
+ const insight: AIInsight = fakeInsight();
359
+ const triageAiRunId: ObjectID = ObjectID.generate();
360
+ jest
361
+ .spyOn(InsightStore, "upsertCandidates")
362
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
363
+ enqueueInsightTriage.mockResolvedValue({ triageAiRunId });
364
+
365
+ await InsightScanner.scanProjectForInsights(fakeProject());
366
+
367
+ expect(enqueueInsightTriage).toHaveBeenCalledWith({ insight });
368
+ expect(updateOneById).toHaveBeenCalledWith(
369
+ expect.objectContaining({
370
+ id: insight.id,
371
+ data: expect.objectContaining({ triageAiRunId }),
372
+ props: expect.objectContaining({ isRoot: true }),
373
+ }),
374
+ );
375
+ });
376
+
377
+ test("no triage run enqueued (quiet skip) → no triageAiRunId write", async () => {
378
+ const insight: AIInsight = fakeInsight();
379
+ jest
380
+ .spyOn(InsightStore, "upsertCandidates")
381
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
382
+
383
+ await InsightScanner.scanProjectForInsights(fakeProject());
384
+
385
+ // Exactly one write: the ActionRequired status routing.
386
+ expect(updateOneById).toHaveBeenCalledTimes(1);
387
+ });
388
+
389
+ test("triage runs for fix and non-fix insights alike — it enriches both", async () => {
390
+ const fixable: AIInsight = fakeInsight();
391
+ const nonFixable: AIInsight = fakeInsight();
392
+ jest
393
+ .spyOn(InsightStore, "upsertCandidates")
394
+ .mockResolvedValue(emptyUpsertResult({ created: [fixable, nonFixable] }));
395
+ routeInsightFix
396
+ .mockResolvedValueOnce({ fixAiRunId: ObjectID.generate() })
397
+ .mockResolvedValueOnce({});
398
+
399
+ await InsightScanner.scanProjectForInsights(fakeProject());
400
+
401
+ expect(enqueueInsightTriage).toHaveBeenCalledTimes(2);
402
+ expect(enqueueInsightTriage).toHaveBeenCalledWith({ insight: fixable });
403
+ expect(enqueueInsightTriage).toHaveBeenCalledWith({ insight: nonFixable });
404
+ });
405
+
406
+ test("refresh-only upsert result routes NOTHING: no fix, no triage, no status writes, no creates", async () => {
407
+ jest
408
+ .spyOn(InsightStore, "upsertCandidates")
409
+ .mockResolvedValue(emptyUpsertResult({ refreshed: 3 }));
410
+ const create: jest.SpyInstance = jest.spyOn(AIInsightService, "create");
411
+
412
+ await InsightScanner.scanProjectForInsights(fakeProject());
413
+
414
+ expect(routeInsightFix).not.toHaveBeenCalled();
415
+ expect(enqueueInsightTriage).not.toHaveBeenCalled();
416
+ expect(updateOneById).not.toHaveBeenCalled();
417
+ // The scanner itself never creates rows — only the store does.
418
+ expect(create).not.toHaveBeenCalled();
419
+ });
420
+
421
+ test("fix routing rejecting (contract breach) degrades to ActionRequired and still triages", async () => {
422
+ const insight: AIInsight = fakeInsight();
423
+ jest
424
+ .spyOn(InsightStore, "upsertCandidates")
425
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
426
+ routeInsightFix.mockRejectedValue(new Error("routing blew up"));
427
+
428
+ await expect(
429
+ InsightScanner.scanProjectForInsights(fakeProject()),
430
+ ).resolves.toBeUndefined();
431
+
432
+ expect(updateOneById).toHaveBeenCalledWith(
433
+ expect.objectContaining({
434
+ data: expect.objectContaining({
435
+ status: AIInsightStatus.ActionRequired,
436
+ }),
437
+ }),
438
+ );
439
+ expect(enqueueInsightTriage).toHaveBeenCalledWith({ insight });
440
+ });
441
+
442
+ test("triage rejecting (contract breach) does not fail the scan — the status was already routed", async () => {
443
+ const insight: AIInsight = fakeInsight();
444
+ jest
445
+ .spyOn(InsightStore, "upsertCandidates")
446
+ .mockResolvedValue(emptyUpsertResult({ created: [insight] }));
447
+ enqueueInsightTriage.mockRejectedValue(new Error("queue down"));
448
+
449
+ await expect(
450
+ InsightScanner.scanProjectForInsights(fakeProject()),
451
+ ).resolves.toBeUndefined();
452
+
453
+ expect(updateOneById).toHaveBeenCalledWith(
454
+ expect.objectContaining({
455
+ data: expect.objectContaining({
456
+ status: AIInsightStatus.ActionRequired,
457
+ }),
458
+ }),
459
+ );
460
+ });
461
+
462
+ test("per-insight isolation: a failing status write on the first insight does not stop routing the second", async () => {
463
+ const first: AIInsight = fakeInsight();
464
+ const second: AIInsight = fakeInsight();
465
+ jest
466
+ .spyOn(InsightStore, "upsertCandidates")
467
+ .mockResolvedValue(emptyUpsertResult({ created: [first, second] }));
468
+ updateOneById
469
+ .mockRejectedValueOnce(new Error("write conflict"))
470
+ .mockResolvedValue(undefined);
471
+
472
+ await expect(
473
+ InsightScanner.scanProjectForInsights(fakeProject()),
474
+ ).resolves.toBeUndefined();
475
+
476
+ expect(routeInsightFix).toHaveBeenCalledTimes(2);
477
+ expect(routeInsightFix).toHaveBeenCalledWith(
478
+ expect.objectContaining({ insight: second }),
479
+ );
480
+ });
481
+ });
482
+
483
+ describe("InsightScanner — the self-heal sweep for insights stranded in Detected", () => {
484
+ let findBy: jest.SpyInstance;
485
+ let updateOneById: jest.SpyInstance;
486
+ let routeInsightFix: jest.SpyInstance;
487
+ let enqueueInsightTriage: jest.SpyInstance;
488
+
489
+ beforeEach(() => {
490
+ // No candidates this tick: only the sweep is under test.
491
+ jest
492
+ .spyOn(InsightDetectors, "getAllDetectors")
493
+ .mockReturnValue([fakeDetector({ candidates: [] })]);
494
+ findBy = jest
495
+ .spyOn(AIInsightService, "findBy")
496
+ .mockResolvedValue([] as unknown as Array<AIInsight>);
497
+ updateOneById = jest
498
+ .spyOn(AIInsightService, "updateOneById")
499
+ .mockResolvedValue(undefined);
500
+ routeInsightFix = jest
501
+ .spyOn(InsightFixRouting, "routeInsightFix")
502
+ .mockResolvedValue({});
503
+ enqueueInsightTriage = jest
504
+ .spyOn(InsightTriage, "enqueueInsightTriage")
505
+ .mockResolvedValue({});
506
+ });
507
+
508
+ afterEach(() => {
509
+ jest.restoreAllMocks();
510
+ });
511
+
512
+ test("a Detected row left over from an earlier tick is swept up and re-routed to ActionRequired (and triaged)", async () => {
513
+ const stranded: AIInsight = fakeInsight();
514
+ findBy.mockResolvedValue([stranded]);
515
+
516
+ await InsightScanner.scanProjectForInsights(fakeProject());
517
+
518
+ // Scoped to this project's unrouted rows only, as root.
519
+ expect(findBy).toHaveBeenCalledWith(
520
+ expect.objectContaining({
521
+ query: expect.objectContaining({
522
+ projectId,
523
+ status: AIInsightStatus.Detected,
524
+ }),
525
+ limit: LIMIT_MAX,
526
+ props: expect.objectContaining({ isRoot: true }),
527
+ }),
528
+ );
529
+ expect(routeInsightFix).toHaveBeenCalledWith(
530
+ expect.objectContaining({ insight: stranded }),
531
+ );
532
+ expect(updateOneById).toHaveBeenCalledWith(
533
+ expect.objectContaining({
534
+ id: stranded.id,
535
+ data: expect.objectContaining({
536
+ status: AIInsightStatus.ActionRequired,
537
+ }),
538
+ props: expect.objectContaining({ isRoot: true }),
539
+ }),
540
+ );
541
+ expect(enqueueInsightTriage).toHaveBeenCalledWith({ insight: stranded });
542
+ });
543
+
544
+ test("a swept row whose fix routing opens a run lands on FixOpened + fixAiRunId — the full routing path, not a status patch", async () => {
545
+ const stranded: AIInsight = fakeInsight();
546
+ const fixAiRunId: ObjectID = ObjectID.generate();
547
+ findBy.mockResolvedValue([stranded]);
548
+ routeInsightFix.mockResolvedValue({ fixAiRunId });
549
+
550
+ await InsightScanner.scanProjectForInsights(fakeProject());
551
+
552
+ expect(updateOneById).toHaveBeenCalledWith(
553
+ expect.objectContaining({
554
+ id: stranded.id,
555
+ data: expect.objectContaining({
556
+ status: AIInsightStatus.FixOpened,
557
+ fixAiRunId,
558
+ }),
559
+ }),
560
+ );
561
+ });
562
+
563
+ test("the sweep runs BEFORE the detectors — so rows created by THIS tick are routed exactly once, never twice", async () => {
564
+ const callOrder: Array<string> = [];
565
+ const created: AIInsight = fakeInsight();
566
+
567
+ findBy.mockImplementation((): Promise<Array<AIInsight>> => {
568
+ callOrder.push("sweep");
569
+ // Nothing stranded: the row below does not exist yet at sweep time.
570
+ return Promise.resolve([]);
571
+ });
572
+ jest.spyOn(InsightDetectors, "getAllDetectors").mockReturnValue([
573
+ {
574
+ insightType: AIInsightType.NewException,
575
+ detect: (): Promise<Array<InsightCandidate>> => {
576
+ callOrder.push("detect");
577
+ return Promise.resolve([makeCandidate()]);
578
+ },
579
+ },
580
+ ]);
581
+ jest
582
+ .spyOn(InsightStore, "upsertCandidates")
583
+ .mockResolvedValue(emptyUpsertResult({ created: [created] }));
584
+
585
+ await InsightScanner.scanProjectForInsights(fakeProject());
586
+
587
+ expect(callOrder).toEqual(["sweep", "detect"]);
588
+ expect(routeInsightFix).toHaveBeenCalledTimes(1);
589
+ expect(routeInsightFix).toHaveBeenCalledWith(
590
+ expect.objectContaining({ insight: created }),
591
+ );
592
+ });
593
+
594
+ test("a sweep failure never breaks the scan — detectors still run and this tick's new insights still route", async () => {
595
+ const created: AIInsight = fakeInsight();
596
+ findBy.mockRejectedValue(new Error("db down"));
597
+ jest
598
+ .spyOn(InsightDetectors, "getAllDetectors")
599
+ .mockReturnValue([fakeDetector({ candidates: [makeCandidate()] })]);
600
+ const upsert: jest.SpyInstance = jest
601
+ .spyOn(InsightStore, "upsertCandidates")
602
+ .mockResolvedValue(emptyUpsertResult({ created: [created] }));
603
+
604
+ await expect(
605
+ InsightScanner.scanProjectForInsights(fakeProject()),
606
+ ).resolves.toBeUndefined();
607
+
608
+ expect(upsert).toHaveBeenCalledTimes(1);
609
+ expect(routeInsightFix).toHaveBeenCalledWith(
610
+ expect.objectContaining({ insight: created }),
611
+ );
612
+ });
613
+
614
+ test("one stranded row failing to re-route does not stop the next one", async () => {
615
+ const first: AIInsight = fakeInsight();
616
+ const second: AIInsight = fakeInsight();
617
+ findBy.mockResolvedValue([first, second]);
618
+ updateOneById
619
+ .mockRejectedValueOnce(new Error("write conflict"))
620
+ .mockResolvedValue(undefined);
621
+
622
+ await expect(
623
+ InsightScanner.scanProjectForInsights(fakeProject()),
624
+ ).resolves.toBeUndefined();
625
+
626
+ expect(routeInsightFix).toHaveBeenCalledTimes(2);
627
+ expect(routeInsightFix).toHaveBeenCalledWith(
628
+ expect.objectContaining({ insight: second }),
629
+ );
630
+ });
631
+ });
632
+
633
+ /*
634
+ * The safety proof for the sweep: re-routing a row that ALREADY got its fix
635
+ * run (the pod died after the run was created but before the status write)
636
+ * cannot create a second one. The dedupe lives inside the creation path
637
+ * itself — this exercises the REAL InsightFixRouting against it.
638
+ */
639
+ describe("InsightScanner — re-routing a stranded insight cannot double-create a fix run", () => {
640
+ afterEach(() => {
641
+ jest.restoreAllMocks();
642
+ });
643
+
644
+ test("the creation path's per-(exception, recipe) dedupe rejection is a quiet no-fix: no run is stamped, the row lands on ActionRequired", async () => {
645
+ const stranded: AIInsight = {
646
+ id: ObjectID.generate(),
647
+ projectId,
648
+ insightType: AIInsightType.NewException,
649
+ telemetryExceptionId: ObjectID.generate(),
650
+ status: AIInsightStatus.Detected,
651
+ } as unknown as AIInsight;
652
+
653
+ const optedInProject: Project = {
654
+ id: projectId,
655
+ enableAiInsights: true,
656
+ // Fix tasks ON and AI ON: every gate ahead of the dedupe is open.
657
+ enableInsightFixTasks: true,
658
+ enableAi: true,
659
+ } as unknown as Project;
660
+
661
+ const budget: FixRunBudgetDecision = {
662
+ allowed: true,
663
+ limit: 25,
664
+ paused: false,
665
+ runsToday: 0,
666
+ };
667
+
668
+ jest
669
+ .spyOn(InsightDetectors, "getAllDetectors")
670
+ .mockReturnValue([fakeDetector({ candidates: [] })]);
671
+ jest
672
+ .spyOn(AIInsightService, "findBy")
673
+ .mockResolvedValue([stranded] as unknown as Array<AIInsight>);
674
+ const updateOneById: jest.SpyInstance = jest
675
+ .spyOn(AIInsightService, "updateOneById")
676
+ .mockResolvedValue(undefined);
677
+ jest.spyOn(InsightTriage, "enqueueInsightTriage").mockResolvedValue({});
678
+ jest.spyOn(FixRunBudget, "getBudgetStatus").mockResolvedValue(budget);
679
+ jest
680
+ .spyOn(TelemetryExceptionService, "getAIFixReadiness")
681
+ .mockResolvedValue({ ready: true, checks: [] });
682
+
683
+ /*
684
+ * The dedupe: the fix run created before the pod died is still
685
+ * non-terminal, so the creation path refuses a second one.
686
+ */
687
+ const create: jest.SpyInstance = jest
688
+ .spyOn(TelemetryExceptionService, "createCodeFixRunForException")
689
+ .mockRejectedValue(
690
+ new Error(
691
+ "An AI agent task is already in progress for this exception.",
692
+ ),
693
+ );
694
+ const stamp: jest.SpyInstance = jest.spyOn(AIRunService, "updateOneById");
695
+
696
+ await expect(
697
+ InsightScanner.scanProjectForInsights(optedInProject),
698
+ ).resolves.toBeUndefined();
699
+
700
+ // The creation path was asked exactly once — and it refused.
701
+ expect(create).toHaveBeenCalledTimes(1);
702
+ // No second run exists, so nothing was stamped and nothing is FixOpened.
703
+ expect(stamp).not.toHaveBeenCalled();
704
+ expect(updateOneById).toHaveBeenCalledWith(
705
+ expect.objectContaining({
706
+ id: stranded.id,
707
+ data: expect.objectContaining({
708
+ status: AIInsightStatus.ActionRequired,
709
+ }),
710
+ }),
711
+ );
712
+ expect(updateOneById).not.toHaveBeenCalledWith(
713
+ expect.objectContaining({
714
+ data: expect.objectContaining({
715
+ status: AIInsightStatus.FixOpened,
716
+ }),
717
+ }),
718
+ );
719
+ });
720
+ });