@oneuptime/common 12.0.4 → 12.0.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 (374) hide show
  1. package/Models/DatabaseModels/AIRun.ts +38 -1
  2. package/Models/DatabaseModels/AlertFeed.ts +30 -0
  3. package/Models/DatabaseModels/Dashboard.ts +28 -0
  4. package/Models/DatabaseModels/Incident.ts +2 -0
  5. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  7. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  8. package/Models/DatabaseModels/IncidentFeed.ts +30 -0
  9. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  10. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  11. package/Models/DatabaseModels/LogSavedView.ts +33 -0
  12. package/Models/DatabaseModels/Project.ts +231 -9
  13. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  14. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  15. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  16. package/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.ts +4 -4
  17. package/Models/DatabaseModels/StatusPage.ts +28 -0
  18. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  19. package/Server/API/AIAgentDataAPI.ts +44 -55
  20. package/Server/API/AIInvestigationAPI.ts +175 -17
  21. package/Server/API/DashboardAPI.ts +10 -10
  22. package/Server/API/StatusPageAPI.ts +38 -19
  23. package/Server/EnvironmentConfig.ts +16 -0
  24. package/Server/Infrastructure/ClickhouseConfig.ts +15 -1
  25. package/Server/Infrastructure/ClickhouseDatabase.ts +1 -1
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.ts +27 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.ts +89 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.ts +27 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.ts +40 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.ts +26 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  32. package/Server/Services/AIRunService.ts +78 -10
  33. package/Server/Services/AIService.ts +94 -22
  34. package/Server/Services/AlertFeedService.ts +5 -0
  35. package/Server/Services/AnalyticsDatabaseService.ts +29 -0
  36. package/Server/Services/DatabaseService.ts +43 -2
  37. package/Server/Services/IncidentFeedService.ts +5 -0
  38. package/Server/Services/LlmLogService.ts +46 -2
  39. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +6 -0
  40. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +4 -0
  41. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +8 -5
  42. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +90 -16
  43. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +2 -0
  44. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +2 -0
  45. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +223 -22
  46. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +25 -24
  47. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +182 -71
  48. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +252 -43
  49. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +25 -24
  50. package/Server/Utils/AI/SRE/InstrumentationTaskTrigger.ts +47 -13
  51. package/Server/Utils/AI/SRE/InvestigationGrader.ts +9 -5
  52. package/Server/Utils/AI/SRE/InvestigationQueue.ts +141 -24
  53. package/Server/Utils/AI/SRE/InvestigationSubjectLock.ts +66 -0
  54. package/Server/Utils/AI/SRE/PostedRootCause.ts +94 -3
  55. package/Server/Utils/AI/SRE/README.md +1 -1
  56. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +132 -10
  57. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +24 -0
  58. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +29 -0
  59. package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +56 -0
  60. package/Server/Utils/Monitor/MonitorMetricUtil.ts +46 -0
  61. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
  62. package/Server/Utils/PasswordHash.ts +28 -4
  63. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +251 -0
  64. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +747 -0
  65. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +480 -0
  66. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1184 -0
  67. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +500 -0
  68. package/Tests/App/Dashboard/PortMonitorCriteriaFilter.test.ts +93 -0
  69. package/Tests/App/StatusPage/PublicStatusPageAPIErrorHandling.test.ts +117 -0
  70. package/Tests/App/StatusPage/StatusPageModelAPIPolymorphism.test.ts +223 -0
  71. package/Tests/Models/DatabaseModels/PermissionCatalogueCoverage.test.ts +243 -0
  72. package/Tests/Server/API/AIAgentDataFixFromIncidentContext.test.ts +266 -0
  73. package/Tests/Server/API/AIInvestigationAPI.test.ts +594 -0
  74. package/Tests/Server/API/AIInvestigationCreateFixTask.test.ts +80 -0
  75. package/Tests/Server/API/DashboardMasterPasswordAPI.test.ts +172 -1
  76. package/Tests/Server/API/StatusPageMasterPasswordAPI.test.ts +335 -0
  77. package/Tests/Server/Services/AIRunCodeFixClaim.test.ts +59 -5
  78. package/Tests/Server/Services/AIRunHumanVerdict.test.ts +127 -58
  79. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +396 -20
  80. package/Tests/Server/Services/AddMasterPasswordSaltMigration.test.ts +283 -0
  81. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +25 -1
  82. package/Tests/Server/Services/DatabaseServicePerUserPasswordSalt.test.ts +45 -35
  83. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +54 -0
  84. package/Tests/Server/Services/FeedAIRunAssociation.test.ts +125 -0
  85. package/Tests/Server/Services/FixFromIncidentTaskTrigger.test.ts +418 -57
  86. package/Tests/Server/Services/InstrumentationTaskTrigger.test.ts +173 -14
  87. package/Tests/Server/Services/MasterPasswordScrypt.test.ts +567 -0
  88. package/Tests/Server/Services/SeparateIncidentAlertAiSettingsMigration.test.ts +194 -0
  89. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +37 -4
  90. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +330 -29
  91. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +25 -0
  92. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +293 -5
  93. package/Tests/Server/Utils/AI/CodeFixAgentCompletion.test.ts +59 -1
  94. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +342 -19
  95. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +214 -30
  96. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +2 -0
  97. package/Tests/Server/Utils/AI/InvestigationGrader.test.ts +65 -17
  98. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +3 -0
  99. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +313 -20
  100. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  101. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +4 -0
  102. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +2 -0
  103. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +44 -0
  104. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +30 -0
  105. package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +212 -0
  106. package/Tests/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.test.ts +401 -0
  107. package/Tests/Server/Utils/Monitor/MonitorMetricUtilPortTimings.test.ts +243 -0
  108. package/Tests/Server/Utils/PasswordHash.test.ts +30 -0
  109. package/Tests/Types/Dashboard/DashboardTemplates.test.ts +111 -0
  110. package/Tests/Types/Monitor/CephMetricCatalog.test.ts +104 -0
  111. package/Tests/Types/Monitor/CriteriaFilter.test.ts +4 -0
  112. package/Tests/Types/Monitor/DockerMetricCatalog.test.ts +109 -0
  113. package/Tests/Types/Monitor/DockerSwarmMetricCatalog.test.ts +118 -0
  114. package/Tests/Types/Monitor/HostMetricCatalog.test.ts +104 -0
  115. package/Tests/Types/Monitor/IotMetricCatalog.test.ts +104 -0
  116. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +118 -0
  117. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +42 -0
  118. package/Tests/Types/Monitor/MonitorStep.test.ts +38 -0
  119. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +75 -0
  120. package/Tests/Types/Monitor/PodmanMetricCatalog.test.ts +109 -0
  121. package/Tests/Types/Monitor/ProxmoxMetricCatalog.test.ts +107 -0
  122. package/Tests/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.test.ts +151 -0
  123. package/Tests/Types/Permission.test.ts +137 -0
  124. package/Tests/Types/Rum/SessionReplayMaskingMode.test.ts +101 -0
  125. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +5 -5
  126. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +3 -5
  127. package/Tests/UI/Components/ErrorBoundary.test.tsx +158 -0
  128. package/Tests/UI/Components/FeedItemSafeMode.test.tsx +74 -0
  129. package/Tests/UI/Components/MoreMenu.test.tsx +756 -0
  130. package/Tests/UI/Components/StatusPage/ResourceGroupSection.test.tsx +62 -0
  131. package/Tests/UI/Monitor/PortMonitorView.test.tsx +237 -0
  132. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +91 -0
  133. package/Tests/UI/Utils/DownloadFile.test.ts +103 -0
  134. package/Tests/UI/Utils/ErrorSupportBundle.test.ts +582 -0
  135. package/Tests/UI/Utils/Project.test.ts +31 -0
  136. package/Tests/UI/Utils/StatusPageModelAPIInjection.test.ts +245 -0
  137. package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +820 -0
  138. package/Tests/Utils/Dashboard/DashboardViewConfig.test.ts +329 -0
  139. package/Tests/Utils/Dashboard/GridLayout.test.ts +951 -0
  140. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +117 -0
  141. package/Tests/Utils/RecordingRuleExpression.test.ts +327 -0
  142. package/Tests/Utils/StatusPage/GroupNestingLayout.test.ts +186 -0
  143. package/Tests/Utils/StatusPage/GroupTree.test.ts +456 -0
  144. package/Tests/Utils/StatusPage/OverviewGroupHierarchyVisibility.test.ts +588 -0
  145. package/Tests/Utils/Telemetry/SavedViewTimeRange.test.ts +244 -0
  146. package/Tests/Utils/Telemetry/TelemetryQueryTimeRange.test.ts +825 -0
  147. package/Types/AI/AIRunCodeFixRecommendation.ts +17 -0
  148. package/Types/AI/CodeFixTaskContext.ts +63 -3
  149. package/Types/AI/CodeFixTaskType.ts +3 -3
  150. package/Types/HashedString.ts +4 -3
  151. package/Types/Monitor/CriteriaFilter.ts +4 -0
  152. package/Types/Monitor/DomainMonitor/DomainLookupMethod.ts +23 -0
  153. package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +7 -0
  154. package/Types/Monitor/MonitorCriteriaInstance.ts +24 -7
  155. package/Types/Monitor/MonitorMetricType.ts +8 -0
  156. package/Types/Monitor/MonitorStep.ts +10 -1
  157. package/Types/Monitor/MonitorStepDomainMonitor.ts +21 -0
  158. package/Types/Monitor/PortMonitor/PortMonitorTimings.ts +10 -0
  159. package/Types/Permission.ts +23 -175
  160. package/Types/Probe/ProbeMonitorResponse.ts +6 -0
  161. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +2 -3
  162. package/UI/Components/ErrorBoundary.tsx +138 -11
  163. package/UI/Components/Feed/FeedItem.tsx +9 -2
  164. package/UI/Components/ModelDetail/CardModelDetail.tsx +0 -12
  165. package/UI/Components/ModelTable/BaseModelTable.tsx +1 -1
  166. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +12 -2
  167. package/UI/Components/MoreMenu/MoreMenu.tsx +285 -97
  168. package/UI/Components/MoreMenu/MoreMenuItem.tsx +8 -5
  169. package/UI/Utils/DownloadFile.ts +16 -8
  170. package/UI/Utils/ErrorSupportBundle.ts +805 -0
  171. package/UI/Utils/ModelAPI/ModelAPI.ts +19 -8
  172. package/UI/Utils/Project.ts +4 -0
  173. package/UI/Utils/StatusPage.ts +8 -2
  174. package/UI/Utils/UseDashboardGridDnd.ts +917 -0
  175. package/Utils/Dashboard/DashboardViewConfig.ts +80 -36
  176. package/Utils/Dashboard/GridLayout.ts +523 -0
  177. package/Utils/Metrics/RecordingRuleExpression.ts +19 -17
  178. package/Utils/Monitor/MonitorMetricType.ts +44 -2
  179. package/Utils/StatusPage/GroupNestingLayout.ts +69 -0
  180. package/Utils/StatusPage/GroupTree.ts +157 -66
  181. package/Utils/StatusPage/ResourceUptime.ts +37 -2
  182. package/Utils/Telemetry/SavedViewTimeRange.ts +114 -0
  183. package/Utils/Telemetry/TelemetryQueryTimeRange.ts +334 -0
  184. package/build/dist/Models/DatabaseModels/AIRun.js +39 -1
  185. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/AlertFeed.js +32 -0
  187. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/Dashboard.js +29 -0
  189. package/build/dist/Models/DatabaseModels/Dashboard.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  191. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  193. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  195. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  196. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  197. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/IncidentFeed.js +32 -0
  199. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  200. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  201. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  202. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  203. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  204. package/build/dist/Models/DatabaseModels/LogSavedView.js +33 -0
  205. package/build/dist/Models/DatabaseModels/LogSavedView.js.map +1 -1
  206. package/build/dist/Models/DatabaseModels/Project.js +241 -11
  207. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  208. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  209. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  210. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  211. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  212. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  213. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  214. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplateOwnerUser.js +4 -4
  215. package/build/dist/Models/DatabaseModels/StatusPage.js +29 -0
  216. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  218. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  219. package/build/dist/Server/API/AIAgentDataAPI.js +28 -46
  220. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  221. package/build/dist/Server/API/AIInvestigationAPI.js +111 -11
  222. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  223. package/build/dist/Server/API/DashboardAPI.js +8 -5
  224. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  225. package/build/dist/Server/API/StatusPageAPI.js +29 -11
  226. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  227. package/build/dist/Server/EnvironmentConfig.js +14 -0
  228. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  229. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +12 -1
  230. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  231. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +1 -1
  232. package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js +22 -0
  234. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786096660558-AddTimeRangeToLogSavedView.js.map +1 -0
  235. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js +36 -0
  236. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786101798351-MigrationName.js.map +1 -0
  237. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js +18 -0
  238. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786105470826-MigrationName.js.map +1 -0
  239. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js +19 -0
  240. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786400000000-AddMasterPasswordSalt.js.map +1 -0
  241. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js +19 -0
  242. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786500000000-AddInvestigationCodeFixRecommendation.js.map +1 -0
  243. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  244. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  245. package/build/dist/Server/Services/AIRunService.js +58 -12
  246. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  247. package/build/dist/Server/Services/AIService.js +74 -21
  248. package/build/dist/Server/Services/AIService.js.map +1 -1
  249. package/build/dist/Server/Services/AlertFeedService.js +3 -0
  250. package/build/dist/Server/Services/AlertFeedService.js.map +1 -1
  251. package/build/dist/Server/Services/AnalyticsDatabaseService.js +29 -1
  252. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  253. package/build/dist/Server/Services/DatabaseService.js +21 -2
  254. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  255. package/build/dist/Server/Services/IncidentFeedService.js +3 -0
  256. package/build/dist/Server/Services/IncidentFeedService.js.map +1 -1
  257. package/build/dist/Server/Services/LlmLogService.js +22 -1
  258. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  259. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  260. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +4 -0
  262. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +8 -5
  264. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  265. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +63 -24
  266. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  267. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +2 -0
  268. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  269. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +2 -0
  270. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +156 -28
  272. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +21 -19
  274. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +128 -49
  276. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +170 -31
  278. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  279. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +21 -19
  280. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  281. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js +33 -9
  282. package/build/dist/Server/Utils/AI/SRE/InstrumentationTaskTrigger.js.map +1 -1
  283. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +9 -5
  284. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  285. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +121 -41
  286. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  287. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js +46 -0
  288. package/build/dist/Server/Utils/AI/SRE/InvestigationSubjectLock.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +61 -14
  290. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +79 -1
  292. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  293. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +21 -0
  294. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  295. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +20 -3
  296. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  297. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +43 -0
  298. package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
  299. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +33 -0
  300. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  301. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
  302. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  303. package/build/dist/Server/Utils/PasswordHash.js +25 -4
  304. package/build/dist/Server/Utils/PasswordHash.js.map +1 -1
  305. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js +18 -0
  306. package/build/dist/Types/AI/AIRunCodeFixRecommendation.js.map +1 -0
  307. package/build/dist/Types/AI/CodeFixTaskContext.js +23 -3
  308. package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
  309. package/build/dist/Types/AI/CodeFixTaskType.js +3 -3
  310. package/build/dist/Types/HashedString.js +4 -3
  311. package/build/dist/Types/HashedString.js.map +1 -1
  312. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  313. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  314. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js +24 -0
  315. package/build/dist/Types/Monitor/DomainMonitor/DomainLookupMethod.js.map +1 -0
  316. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +24 -7
  317. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  318. package/build/dist/Types/Monitor/MonitorMetricType.js +7 -0
  319. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  320. package/build/dist/Types/Monitor/MonitorStep.js +6 -1
  321. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  322. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +15 -0
  323. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  324. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js +2 -0
  325. package/build/dist/Types/Monitor/PortMonitor/PortMonitorTimings.js.map +1 -0
  326. package/build/dist/Types/Permission.js +23 -155
  327. package/build/dist/Types/Permission.js.map +1 -1
  328. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +2 -5
  329. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  330. package/build/dist/UI/Components/ErrorBoundary.js +67 -8
  331. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  332. package/build/dist/UI/Components/Feed/FeedItem.js +2 -2
  333. package/build/dist/UI/Components/Feed/FeedItem.js.map +1 -1
  334. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +0 -12
  335. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  336. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +1 -1
  337. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  338. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +12 -2
  339. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  340. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +193 -65
  341. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  342. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +1 -1
  343. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  344. package/build/dist/UI/Utils/DownloadFile.js +15 -8
  345. package/build/dist/UI/Utils/DownloadFile.js.map +1 -1
  346. package/build/dist/UI/Utils/ErrorSupportBundle.js +476 -0
  347. package/build/dist/UI/Utils/ErrorSupportBundle.js.map +1 -0
  348. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +18 -8
  349. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  350. package/build/dist/UI/Utils/Project.js +3 -0
  351. package/build/dist/UI/Utils/Project.js.map +1 -1
  352. package/build/dist/UI/Utils/StatusPage.js +4 -4
  353. package/build/dist/UI/Utils/StatusPage.js.map +1 -1
  354. package/build/dist/UI/Utils/UseDashboardGridDnd.js +551 -0
  355. package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -0
  356. package/build/dist/Utils/Dashboard/DashboardViewConfig.js +42 -26
  357. package/build/dist/Utils/Dashboard/DashboardViewConfig.js.map +1 -1
  358. package/build/dist/Utils/Dashboard/GridLayout.js +338 -0
  359. package/build/dist/Utils/Dashboard/GridLayout.js.map +1 -0
  360. package/build/dist/Utils/Metrics/RecordingRuleExpression.js +19 -17
  361. package/build/dist/Utils/Metrics/RecordingRuleExpression.js.map +1 -1
  362. package/build/dist/Utils/Monitor/MonitorMetricType.js +38 -6
  363. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  364. package/build/dist/Utils/StatusPage/GroupNestingLayout.js +46 -0
  365. package/build/dist/Utils/StatusPage/GroupNestingLayout.js.map +1 -1
  366. package/build/dist/Utils/StatusPage/GroupTree.js +79 -34
  367. package/build/dist/Utils/StatusPage/GroupTree.js.map +1 -1
  368. package/build/dist/Utils/StatusPage/ResourceUptime.js +22 -1
  369. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  370. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js +79 -0
  371. package/build/dist/Utils/Telemetry/SavedViewTimeRange.js.map +1 -0
  372. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js +236 -0
  373. package/build/dist/Utils/Telemetry/TelemetryQueryTimeRange.js.map +1 -0
  374. package/package.json +1 -1
@@ -1,41 +1,86 @@
1
1
  import FixFromIncidentTaskTrigger from "../../../Server/Utils/AI/SRE/FixFromIncidentTaskTrigger";
2
+ import PostedRootCause from "../../../Server/Utils/AI/SRE/PostedRootCause";
2
3
  import FixRunBudget from "../../../Server/Utils/AI/CodeFix/FixRunBudget";
3
4
  import CodeRepositoryService from "../../../Server/Services/CodeRepositoryService";
4
5
  import AIRunService from "../../../Server/Services/AIRunService";
6
+ import Semaphore from "../../../Server/Infrastructure/Semaphore";
7
+ import {
8
+ getInvestigationSubjectLockKey,
9
+ INVESTIGATION_SUBJECT_LOCK_NAMESPACE,
10
+ } from "../../../Server/Utils/AI/SRE/InvestigationSubjectLock";
5
11
  import AIRun from "../../../Models/DatabaseModels/AIRun";
6
12
  import AIRunType from "../../../Types/AI/AIRunType";
7
13
  import AIRunStatus from "../../../Types/AI/AIRunStatus";
14
+ import AIRunCodeFixRecommendation from "../../../Types/AI/AIRunCodeFixRecommendation";
15
+ import CodeFixTaskContext from "../../../Types/AI/CodeFixTaskContext";
8
16
  import CodeFixTaskType from "../../../Types/AI/CodeFixTaskType";
17
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
9
18
  import BadDataException from "../../../Types/Exception/BadDataException";
10
19
  import ObjectID from "../../../Types/ObjectID";
11
20
  import PositiveNumber from "../../../Types/PositiveNumber";
12
- import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
21
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
13
22
 
14
23
  /*
15
- * The FixFromIncident trigger: the user clicks "Open Fix PR from this
16
- * analysis" on the investigation panel after a AI investigation
17
- * completes, and the agent turns the posted analysis into a fix pull
18
- * request. Human-triggered so there is no project opt-in flag; the gates
19
- * are: a COMPLETED investigation must exist for the subject (its analysis
20
- * is the task's entire context), a GitHub-App repository must exist for the
21
- * PR, and at most one non-terminal FixFromIncident run per subject.
22
- * Unlike the ImproveInstrumentation sibling this runs inside a user-facing
23
- * endpoint and must THROW clear messages, not swallow.
24
+ * The human FixFromIncident trigger is authorized by the exact investigation
25
+ * displayed in the panel. The latest subject run must be Completed and
26
+ * Recommended, its immutable snapshot must match its exact posted report,
27
+ * and repository/dedupe checks culminate in a lock-protected revalidation.
24
28
  */
25
29
 
26
30
  const projectId: ObjectID = ObjectID.generate();
27
31
  const incidentId: ObjectID = ObjectID.generate();
28
32
  const alertId: ObjectID = ObjectID.generate();
29
33
  const userId: ObjectID = ObjectID.generate();
34
+ const investigationRunId: ObjectID = ObjectID.generate();
35
+ const investigationCompletedAt: Date = new Date("2026-08-07T12:00:00.000Z");
36
+ const analysisMarkdown: string =
37
+ "## Root cause\n\nA repository code change fixes this regression.";
30
38
 
31
- function fakeRun(): AIRun {
32
- return { id: ObjectID.generate() } as unknown as AIRun;
39
+ function taskSnapshot(
40
+ id: ObjectID = investigationRunId,
41
+ analysis: string = analysisMarkdown,
42
+ ): CodeFixTaskContext {
43
+ return {
44
+ sourceInvestigationRunId: id.toString(),
45
+ sourceInvestigationAnalysisMarkdown: analysis,
46
+ };
47
+ }
48
+
49
+ function fakeRun(
50
+ recommendation: AIRunCodeFixRecommendation = AIRunCodeFixRecommendation.Recommended,
51
+ status: AIRunStatus = AIRunStatus.Completed,
52
+ taskContextOverride?: CodeFixTaskContext | null | undefined,
53
+ id: ObjectID = investigationRunId,
54
+ ): AIRun {
55
+ return {
56
+ id,
57
+ completedAt: investigationCompletedAt,
58
+ codeFixRecommendation: recommendation,
59
+ status,
60
+ taskContext:
61
+ taskContextOverride === null
62
+ ? undefined
63
+ : taskContextOverride || taskSnapshot(id),
64
+ } as unknown as AIRun;
65
+ }
66
+
67
+ function fakeLegacyRunWithoutRecommendation(): AIRun {
68
+ return {
69
+ id: investigationRunId,
70
+ completedAt: investigationCompletedAt,
71
+ status: AIRunStatus.Completed,
72
+ taskContext: taskSnapshot(),
73
+ } as unknown as AIRun;
33
74
  }
34
75
 
35
76
  describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
36
77
  beforeEach(() => {
37
- // The daily fix-run budget has its own suite (FixRunBudget.test.ts).
38
78
  jest.spyOn(FixRunBudget, "assertWithinBudget").mockResolvedValue();
79
+ jest.spyOn(Semaphore, "lock").mockResolvedValue({} as never);
80
+ jest.spyOn(Semaphore, "release").mockResolvedValue();
81
+ jest
82
+ .spyOn(PostedRootCause, "getForInvestigation")
83
+ .mockResolvedValue(analysisMarkdown);
39
84
  });
40
85
 
41
86
  afterEach(() => {
@@ -48,6 +93,7 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
48
93
  await expect(
49
94
  FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
50
95
  projectId,
96
+ investigationRunId,
51
97
  userId,
52
98
  }),
53
99
  ).rejects.toThrow(BadDataException);
@@ -55,8 +101,38 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
55
101
  expect(findOneBy).not.toHaveBeenCalled();
56
102
  });
57
103
 
58
- test("no completed investigation reject with a clear message, nothing enqueued", async () => {
59
- // The completed-investigation lookup is the first query.
104
+ test("a call carrying both subject types is rejected before any query", async () => {
105
+ const findOneBy: jest.SpyInstance = jest.spyOn(AIRunService, "findOneBy");
106
+
107
+ await expect(
108
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
109
+ projectId,
110
+ incidentId,
111
+ alertId,
112
+ investigationRunId,
113
+ userId,
114
+ }),
115
+ ).rejects.toThrow(/Exactly one/);
116
+
117
+ expect(findOneBy).not.toHaveBeenCalled();
118
+ });
119
+
120
+ test("a missing investigation run id is rejected before any query", async () => {
121
+ const findOneBy: jest.SpyInstance = jest.spyOn(AIRunService, "findOneBy");
122
+
123
+ await expect(
124
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
125
+ projectId,
126
+ incidentId,
127
+ investigationRunId: undefined as unknown as ObjectID,
128
+ userId,
129
+ }),
130
+ ).rejects.toThrow(/investigation run id is required/i);
131
+
132
+ expect(findOneBy).not.toHaveBeenCalled();
133
+ });
134
+
135
+ test("no investigation rejects before report, repository, or enqueue IO", async () => {
60
136
  const findOneBy: jest.SpyInstance = jest
61
137
  .spyOn(AIRunService, "findOneBy")
62
138
  .mockResolvedValue(null);
@@ -70,24 +146,222 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
70
146
  FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
71
147
  projectId,
72
148
  incidentId,
149
+ investigationRunId,
73
150
  userId,
74
151
  }),
75
152
  ).rejects.toThrow(/No completed AI investigation/);
76
153
 
77
154
  expect(findOneBy).toHaveBeenCalledWith(
78
155
  expect.objectContaining({
79
- query: expect.objectContaining({
156
+ query: {
157
+ projectId,
80
158
  runType: AIRunType.Investigation,
81
- status: AIRunStatus.Completed,
82
159
  triggeredByIncidentId: incidentId,
160
+ },
161
+ select: expect.objectContaining({
162
+ _id: true,
163
+ completedAt: true,
164
+ status: true,
165
+ codeFixRecommendation: true,
166
+ taskContext: true,
83
167
  }),
168
+ sort: { createdAt: SortOrder.Descending },
84
169
  }),
85
170
  );
171
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
86
172
  expect(countBy).not.toHaveBeenCalled();
87
173
  expect(create).not.toHaveBeenCalled();
88
174
  });
89
175
 
90
- test("no GitHub-App repository → reject, nothing enqueued", async () => {
176
+ test.each([AIRunStatus.Running, AIRunStatus.Error])(
177
+ "latest %s investigation blocks an older Recommended result",
178
+ async (latestStatus: AIRunStatus) => {
179
+ jest
180
+ .spyOn(AIRunService, "findOneBy")
181
+ .mockResolvedValue(
182
+ fakeRun(AIRunCodeFixRecommendation.Recommended, latestStatus),
183
+ );
184
+ const create: jest.SpyInstance = jest.spyOn(AIRunService, "create");
185
+
186
+ await expect(
187
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
188
+ projectId,
189
+ incidentId,
190
+ investigationRunId,
191
+ userId,
192
+ }),
193
+ ).rejects.toThrow(/latest AI investigation.*has not completed/i);
194
+
195
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
196
+ expect(create).not.toHaveBeenCalled();
197
+ },
198
+ );
199
+
200
+ test("latest completed Pending investigation rejects until classification settles", async () => {
201
+ jest
202
+ .spyOn(AIRunService, "findOneBy")
203
+ .mockResolvedValue(fakeRun(AIRunCodeFixRecommendation.Pending));
204
+
205
+ await expect(
206
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
207
+ projectId,
208
+ incidentId,
209
+ investigationRunId,
210
+ userId,
211
+ }),
212
+ ).rejects.toThrow(/still deciding/i);
213
+
214
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
215
+ });
216
+
217
+ test("a stale displayed run id cannot borrow a newer Recommended analysis", async () => {
218
+ jest.spyOn(AIRunService, "findOneBy").mockResolvedValue(fakeRun());
219
+
220
+ await expect(
221
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
222
+ projectId,
223
+ incidentId,
224
+ investigationRunId: ObjectID.generate(),
225
+ userId,
226
+ }),
227
+ ).rejects.toThrow(/newer AI investigation/i);
228
+
229
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
230
+ });
231
+
232
+ test("latest NotRecommended result is authoritative over an older Recommended result", async () => {
233
+ jest
234
+ .spyOn(AIRunService, "findOneBy")
235
+ .mockResolvedValue(fakeRun(AIRunCodeFixRecommendation.NotRecommended));
236
+ const create: jest.SpyInstance = jest.spyOn(AIRunService, "create");
237
+
238
+ await expect(
239
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
240
+ projectId,
241
+ incidentId,
242
+ investigationRunId,
243
+ userId,
244
+ }),
245
+ ).rejects.toThrow(/did not recommend a code fix/i);
246
+
247
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
248
+ expect(create).not.toHaveBeenCalled();
249
+ });
250
+
251
+ test("legacy completed investigation without a recommendation fails closed", async () => {
252
+ jest
253
+ .spyOn(AIRunService, "findOneBy")
254
+ .mockResolvedValue(fakeLegacyRunWithoutRecommendation());
255
+
256
+ await expect(
257
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
258
+ projectId,
259
+ incidentId,
260
+ investigationRunId,
261
+ userId,
262
+ }),
263
+ ).rejects.toThrow(/did not recommend a code fix/i);
264
+ });
265
+
266
+ test.each([
267
+ ["missing", null],
268
+ [
269
+ "missing analysis",
270
+ { sourceInvestigationRunId: investigationRunId.toString() },
271
+ ],
272
+ [
273
+ "missing run id",
274
+ { sourceInvestigationAnalysisMarkdown: analysisMarkdown },
275
+ ],
276
+ ] as Array<[string, CodeFixTaskContext | null]>)(
277
+ "%s stored analysis snapshot fails closed before report and repository IO",
278
+ async (_label: string, taskContext: CodeFixTaskContext | null) => {
279
+ jest
280
+ .spyOn(AIRunService, "findOneBy")
281
+ .mockResolvedValue(fakeRun(undefined, undefined, taskContext));
282
+ const countBy: jest.SpyInstance = jest.spyOn(
283
+ CodeRepositoryService,
284
+ "countBy",
285
+ );
286
+
287
+ await expect(
288
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
289
+ projectId,
290
+ incidentId,
291
+ investigationRunId,
292
+ userId,
293
+ }),
294
+ ).rejects.toThrow(/no complete stored analysis snapshot/i);
295
+
296
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
297
+ expect(countBy).not.toHaveBeenCalled();
298
+ },
299
+ );
300
+
301
+ test("a snapshot naming a different investigation run fails closed", async () => {
302
+ jest
303
+ .spyOn(AIRunService, "findOneBy")
304
+ .mockResolvedValue(
305
+ fakeRun(undefined, undefined, taskSnapshot(ObjectID.generate())),
306
+ );
307
+
308
+ await expect(
309
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
310
+ projectId,
311
+ incidentId,
312
+ investigationRunId,
313
+ userId,
314
+ }),
315
+ ).rejects.toThrow(/no complete stored analysis snapshot/i);
316
+
317
+ expect(PostedRootCause.getForInvestigation).not.toHaveBeenCalled();
318
+ });
319
+
320
+ test("the exact investigation must have a posted report before a task can be frozen", async () => {
321
+ jest.spyOn(AIRunService, "findOneBy").mockResolvedValue(fakeRun());
322
+ jest
323
+ .mocked(PostedRootCause.getForInvestigation)
324
+ .mockResolvedValueOnce(null);
325
+ const countBy: jest.SpyInstance = jest.spyOn(
326
+ CodeRepositoryService,
327
+ "countBy",
328
+ );
329
+
330
+ await expect(
331
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
332
+ projectId,
333
+ incidentId,
334
+ investigationRunId,
335
+ userId,
336
+ }),
337
+ ).rejects.toThrow(/No posted investigation analysis/);
338
+
339
+ expect(PostedRootCause.getForInvestigation).toHaveBeenCalledWith({
340
+ incidentId,
341
+ alertId: undefined,
342
+ aiRunId: investigationRunId,
343
+ runCompletedAt: investigationCompletedAt,
344
+ });
345
+ expect(countBy).not.toHaveBeenCalled();
346
+ });
347
+
348
+ test("a posted report that differs from the Recommended snapshot fails closed", async () => {
349
+ jest.spyOn(AIRunService, "findOneBy").mockResolvedValue(fakeRun());
350
+ jest
351
+ .mocked(PostedRootCause.getForInvestigation)
352
+ .mockResolvedValueOnce("## Different report");
353
+
354
+ await expect(
355
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
356
+ projectId,
357
+ incidentId,
358
+ investigationRunId,
359
+ userId,
360
+ }),
361
+ ).rejects.toThrow(/does not match its durable recommendation snapshot/i);
362
+ });
363
+
364
+ test("no GitHub-App repository rejects without enqueueing", async () => {
91
365
  jest.spyOn(AIRunService, "findOneBy").mockResolvedValue(fakeRun());
92
366
  jest
93
367
  .spyOn(CodeRepositoryService, "countBy")
@@ -98,6 +372,7 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
98
372
  FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
99
373
  projectId,
100
374
  incidentId,
375
+ investigationRunId,
101
376
  userId,
102
377
  }),
103
378
  ).rejects.toThrow(/GitHub/);
@@ -105,12 +380,10 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
105
380
  expect(create).not.toHaveBeenCalled();
106
381
  });
107
382
 
108
- test("dedupe: a live FixFromIncident run for the same subject blocks a second one", async () => {
383
+ test("an existing live FixFromIncident run blocks a duplicate", async () => {
109
384
  jest
110
385
  .spyOn(AIRunService, "findOneBy")
111
- // 1st: the completed investigation exists.
112
386
  .mockResolvedValueOnce(fakeRun())
113
- // 2nd: a non-terminal FixFromIncident run already exists.
114
387
  .mockResolvedValueOnce(fakeRun());
115
388
  jest
116
389
  .spyOn(CodeRepositoryService, "countBy")
@@ -121,6 +394,7 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
121
394
  FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
122
395
  projectId,
123
396
  incidentId,
397
+ investigationRunId,
124
398
  userId,
125
399
  }),
126
400
  ).rejects.toThrow(/already queued or running/);
@@ -128,11 +402,72 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
128
402
  expect(create).not.toHaveBeenCalled();
129
403
  });
130
404
 
131
- test("over the daily fix-run budget: the user-facing call REJECTS with the budget message, nothing enqueued", async () => {
405
+ test("the locked second dedupe check closes a concurrent enqueue race", async () => {
406
+ jest
407
+ .spyOn(AIRunService, "findOneBy")
408
+ .mockResolvedValueOnce(fakeRun())
409
+ .mockResolvedValueOnce(null)
410
+ .mockResolvedValueOnce(fakeRun());
411
+ jest
412
+ .spyOn(CodeRepositoryService, "countBy")
413
+ .mockResolvedValue(new PositiveNumber(1));
414
+ const create: jest.SpyInstance = jest.spyOn(AIRunService, "create");
415
+
416
+ await expect(
417
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
418
+ projectId,
419
+ incidentId,
420
+ investigationRunId,
421
+ userId,
422
+ }),
423
+ ).rejects.toThrow(/already queued or running/i);
424
+
425
+ expect(Semaphore.lock).toHaveBeenCalledWith({
426
+ key: getInvestigationSubjectLockKey({ projectId, incidentId }),
427
+ namespace: INVESTIGATION_SUBJECT_LOCK_NAMESPACE,
428
+ lockTimeout: 30 * 1000,
429
+ acquireTimeout: 10 * 1000,
430
+ });
431
+ expect(Semaphore.release).toHaveBeenCalledTimes(1);
432
+ expect(create).not.toHaveBeenCalled();
433
+ });
434
+
435
+ test("a newer investigation inserted before locked revalidation fails closed", async () => {
436
+ const newerInvestigation: AIRun = fakeRun(
437
+ AIRunCodeFixRecommendation.NotRecommended,
438
+ AIRunStatus.Completed,
439
+ undefined,
440
+ ObjectID.generate(),
441
+ );
442
+ jest
443
+ .spyOn(AIRunService, "findOneBy")
444
+ .mockResolvedValueOnce(fakeRun())
445
+ .mockResolvedValueOnce(null)
446
+ .mockResolvedValueOnce(null)
447
+ .mockResolvedValueOnce(newerInvestigation);
448
+ jest
449
+ .spyOn(CodeRepositoryService, "countBy")
450
+ .mockResolvedValue(new PositiveNumber(1));
451
+ const create: jest.SpyInstance = jest.spyOn(AIRunService, "create");
452
+
453
+ await expect(
454
+ FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
455
+ projectId,
456
+ incidentId,
457
+ investigationRunId,
458
+ userId,
459
+ }),
460
+ ).rejects.toThrow(/no longer the latest durably Recommended analysis/i);
461
+
462
+ expect(Semaphore.release).toHaveBeenCalledTimes(1);
463
+ expect(create).not.toHaveBeenCalled();
464
+ });
465
+
466
+ test("over-budget user request rejects without enqueueing", async () => {
132
467
  jest
133
468
  .spyOn(AIRunService, "findOneBy")
134
- .mockResolvedValueOnce(fakeRun()) // completed investigation
135
- .mockResolvedValueOnce(null); // no duplicate run
469
+ .mockResolvedValueOnce(fakeRun())
470
+ .mockResolvedValue(null);
136
471
  jest
137
472
  .spyOn(CodeRepositoryService, "countBy")
138
473
  .mockResolvedValue(new PositiveNumber(1));
@@ -149,6 +484,7 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
149
484
  FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
150
485
  projectId,
151
486
  incidentId,
487
+ investigationRunId,
152
488
  userId,
153
489
  }),
154
490
  ).rejects.toThrow(/daily AI fix task limit/);
@@ -156,15 +492,18 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
156
492
  expect(create).not.toHaveBeenCalled();
157
493
  });
158
494
 
159
- test("happy path (incident): enqueues a Queued FixFromIncident run with user attribution", async () => {
495
+ test("latest Recommended incident report is frozen into the locked task", async () => {
496
+ const latestRecommended: AIRun = fakeRun();
160
497
  const findOneBy: jest.SpyInstance = jest
161
498
  .spyOn(AIRunService, "findOneBy")
162
- .mockResolvedValueOnce(fakeRun()) // completed investigation
163
- .mockResolvedValueOnce(null); // no duplicate run
499
+ .mockResolvedValueOnce(latestRecommended)
500
+ .mockResolvedValueOnce(null)
501
+ .mockResolvedValueOnce(null)
502
+ .mockResolvedValueOnce(latestRecommended);
164
503
  jest
165
504
  .spyOn(CodeRepositoryService, "countBy")
166
505
  .mockResolvedValue(new PositiveNumber(1));
167
- const createdRun: AIRun = fakeRun();
506
+ const createdRun: AIRun = { id: ObjectID.generate() } as unknown as AIRun;
168
507
  const create: jest.SpyInstance = jest
169
508
  .spyOn(AIRunService, "create")
170
509
  .mockResolvedValue(createdRun);
@@ -173,75 +512,97 @@ describe("FixFromIncidentTaskTrigger.createFixTaskFromInvestigation", () => {
173
512
  await FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
174
513
  projectId,
175
514
  incidentId,
515
+ investigationRunId,
176
516
  userId,
177
517
  });
178
518
 
179
519
  expect(run).toBe(createdRun);
180
-
181
- // The dedupe guard queries per (subject, FixFromIncident).
520
+ expect(PostedRootCause.getForInvestigation).toHaveBeenCalledWith({
521
+ incidentId,
522
+ alertId: undefined,
523
+ aiRunId: investigationRunId,
524
+ runCompletedAt: investigationCompletedAt,
525
+ });
526
+ expect(findOneBy).toHaveBeenNthCalledWith(
527
+ 1,
528
+ expect.objectContaining({
529
+ query: {
530
+ projectId,
531
+ runType: AIRunType.Investigation,
532
+ triggeredByIncidentId: incidentId,
533
+ },
534
+ sort: { createdAt: SortOrder.Descending },
535
+ }),
536
+ );
182
537
  expect(findOneBy).toHaveBeenNthCalledWith(
183
- 2,
538
+ 4,
184
539
  expect.objectContaining({
185
540
  query: expect.objectContaining({
186
- runType: AIRunType.CodeFix,
187
- codeFixTaskType: CodeFixTaskType.FixFromIncident,
541
+ projectId,
188
542
  triggeredByIncidentId: incidentId,
189
543
  }),
544
+ sort: { createdAt: SortOrder.Descending },
190
545
  }),
191
546
  );
192
-
547
+ expect(create.mock.invocationCallOrder[0]).toBeGreaterThan(
548
+ findOneBy.mock.invocationCallOrder[3]!,
549
+ );
193
550
  expect(create).toHaveBeenCalledWith(
194
551
  expect.objectContaining({
195
552
  data: expect.objectContaining({
196
- projectId: projectId,
553
+ projectId,
197
554
  runType: AIRunType.CodeFix,
198
555
  codeFixTaskType: CodeFixTaskType.FixFromIncident,
199
556
  status: AIRunStatus.Queued,
200
557
  triggeredByIncidentId: incidentId,
201
- // Attribution: the user who clicked the button.
202
- userId: userId,
558
+ userId,
559
+ taskContext: {
560
+ sourceInvestigationRunId: investigationRunId.toString(),
561
+ sourceInvestigationAnalysisMarkdown: analysisMarkdown,
562
+ },
203
563
  }),
204
- props: expect.objectContaining({ isRoot: true }),
564
+ props: { isRoot: true },
205
565
  }),
206
566
  );
207
567
  });
208
568
 
209
- test("happy path (alert): the run carries triggeredByAlertId", async () => {
210
- const findOneBy: jest.SpyInstance = jest
569
+ test("alert happy path preserves exact report association and alert subject", async () => {
570
+ const latestRecommended: AIRun = fakeRun();
571
+ jest
211
572
  .spyOn(AIRunService, "findOneBy")
212
- .mockResolvedValueOnce(fakeRun())
213
- .mockResolvedValueOnce(null);
573
+ .mockResolvedValueOnce(latestRecommended)
574
+ .mockResolvedValueOnce(null)
575
+ .mockResolvedValueOnce(null)
576
+ .mockResolvedValueOnce(latestRecommended);
214
577
  jest
215
578
  .spyOn(CodeRepositoryService, "countBy")
216
579
  .mockResolvedValue(new PositiveNumber(1));
217
580
  const create: jest.SpyInstance = jest
218
581
  .spyOn(AIRunService, "create")
219
- .mockResolvedValue(fakeRun());
582
+ .mockResolvedValue({ id: ObjectID.generate() } as unknown as AIRun);
220
583
 
221
584
  await FixFromIncidentTaskTrigger.createFixTaskFromInvestigation({
222
585
  projectId,
223
586
  alertId,
587
+ investigationRunId,
224
588
  userId,
225
589
  });
226
590
 
227
- // The completed-investigation gate keys on the alert subject.
228
- expect(findOneBy).toHaveBeenNthCalledWith(
229
- 1,
230
- expect.objectContaining({
231
- query: expect.objectContaining({
232
- runType: AIRunType.Investigation,
233
- status: AIRunStatus.Completed,
234
- triggeredByAlertId: alertId,
235
- }),
236
- }),
237
- );
238
-
591
+ expect(PostedRootCause.getForInvestigation).toHaveBeenCalledWith({
592
+ incidentId: undefined,
593
+ alertId,
594
+ aiRunId: investigationRunId,
595
+ runCompletedAt: investigationCompletedAt,
596
+ });
239
597
  expect(create).toHaveBeenCalledWith(
240
598
  expect.objectContaining({
241
599
  data: expect.objectContaining({
242
- codeFixTaskType: CodeFixTaskType.FixFromIncident,
243
600
  triggeredByAlertId: alertId,
244
- userId: userId,
601
+ userId,
602
+ taskContext: {
603
+ sourceInvestigationRunId: investigationRunId.toString(),
604
+ sourceInvestigationAnalysisMarkdown: analysisMarkdown,
605
+ },
245
606
  }),
246
607
  }),
247
608
  );