@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
@@ -3,6 +3,7 @@ import DashboardAPI from "../../../Server/API/DashboardAPI";
3
3
  import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
4
4
  import DashboardService from "../../../Server/Services/DashboardService";
5
5
  import CookieUtil from "../../../Server/Utils/Cookie";
6
+ import PasswordHash from "../../../Server/Utils/PasswordHash";
6
7
  import {
7
8
  ExpressRequest,
8
9
  ExpressResponse,
@@ -75,8 +76,12 @@ describe("DashboardAPI master password", () => {
75
76
  dashboard.id = dashboardId;
76
77
  dashboard.isPublicDashboard = true;
77
78
  dashboard.enableMasterPassword = true;
79
+ dashboard.masterPasswordSalt = PasswordHash.generateSalt();
78
80
  dashboard.masterPassword = new HashedString(
79
- await HashedString.hashValue(password, EncryptionSecret),
81
+ await PasswordHash.hash({
82
+ plainValue: password,
83
+ salt: dashboard.masterPasswordSalt,
84
+ }),
80
85
  true,
81
86
  );
82
87
 
@@ -204,6 +209,20 @@ describe("DashboardAPI master password", () => {
204
209
  );
205
210
  });
206
211
 
212
+ it("verifies a current scrypt password without rewriting it", async () => {
213
+ const updateSpy: ReturnType<typeof jest.spyOn> = jest
214
+ .spyOn(DashboardService, "updateColumnsByIdWithoutHooks")
215
+ .mockResolvedValue(undefined);
216
+
217
+ await mockRouter
218
+ .match("post", "/dashboard/master-password/:dashboardId")
219
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
220
+
221
+ expect(nextFunction).not.toHaveBeenCalled();
222
+ expect(updateSpy).not.toHaveBeenCalled();
223
+ expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
224
+ });
225
+
207
226
  it("rejects an incorrect password without issuing a cookie", async () => {
208
227
  mockRequest.body["password"] = "incorrect password";
209
228
 
@@ -222,4 +241,156 @@ describe("DashboardAPI master password", () => {
222
241
  expect(mockResponse.cookie).not.toHaveBeenCalled();
223
242
  expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
224
243
  });
244
+
245
+ it.each([123, { password: "nested" }, [password]])(
246
+ "rejects a non-string password body (%p) before hash verification",
247
+ async (invalidPassword: unknown) => {
248
+ (mockRequest.body as Record<string, unknown>)["password"] =
249
+ invalidPassword;
250
+ const verifySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
251
+ DashboardService,
252
+ "verifyHashedColumnValue",
253
+ );
254
+
255
+ await mockRouter
256
+ .match("post", "/dashboard/master-password/:dashboardId")
257
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
258
+
259
+ expect(nextFunction).toHaveBeenCalledTimes(1);
260
+ const error: unknown = (nextFunction as jest.Mock).mock.calls[0]?.[0];
261
+
262
+ expect(error).toBeInstanceOf(BadDataException);
263
+ expect((error as BadDataException).message).toBe(
264
+ "Master password is required.",
265
+ );
266
+ expect(verifySpy).not.toHaveBeenCalled();
267
+ expect(DashboardService.findOneById).not.toHaveBeenCalled();
268
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
269
+ },
270
+ );
271
+
272
+ it("uses this dashboard's selected salt when checking the password", async () => {
273
+ dashboard.masterPasswordSalt = PasswordHash.generateSalt();
274
+
275
+ await mockRouter
276
+ .match("post", "/dashboard/master-password/:dashboardId")
277
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
278
+
279
+ expect(nextFunction).toHaveBeenCalledTimes(1);
280
+ const error: unknown = (nextFunction as jest.Mock).mock.calls[0]?.[0];
281
+
282
+ expect(error).toBeInstanceOf(BadDataException);
283
+ expect((error as BadDataException).message).toBe(
284
+ DASHBOARD_MASTER_PASSWORD_INVALID_MESSAGE,
285
+ );
286
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
287
+ });
288
+
289
+ it("selects the salt needed for verification but never exposes either credential column", async () => {
290
+ await mockRouter
291
+ .match("post", "/dashboard/master-password/:dashboardId")
292
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
293
+
294
+ expect(DashboardService.findOneById).toHaveBeenCalledWith(
295
+ expect.objectContaining({
296
+ select: expect.objectContaining({
297
+ masterPassword: true,
298
+ masterPasswordSalt: true,
299
+ }),
300
+ }),
301
+ );
302
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
303
+ mockRequest,
304
+ mockResponse,
305
+ );
306
+ expect(Response.sendEntityResponse).not.toHaveBeenCalled();
307
+ expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
308
+ expect(mockResponse.send).not.toHaveBeenCalled();
309
+ expect(mockResponse.json).not.toHaveBeenCalled();
310
+
311
+ const cookieArguments: Array<unknown> = (mockResponse.cookie as jest.Mock)
312
+ .mock.calls[0] as Array<unknown>;
313
+ const serializedCookieArguments: string = JSON.stringify(cookieArguments);
314
+
315
+ expect(serializedCookieArguments).not.toContain(password);
316
+ expect(serializedCookieArguments).not.toContain(
317
+ dashboard.masterPassword!.toString(),
318
+ );
319
+ expect(serializedCookieArguments).not.toContain(
320
+ dashboard.masterPasswordSalt,
321
+ );
322
+ });
323
+
324
+ it("accepts a legacy unsalted SHA-256 password and upgrades it to scrypt", async () => {
325
+ dashboard.masterPassword = new HashedString(
326
+ await HashedString.hashValue(password, EncryptionSecret),
327
+ true,
328
+ );
329
+ delete dashboard.masterPasswordSalt;
330
+
331
+ const updateSpy: ReturnType<typeof jest.spyOn> = jest
332
+ .spyOn(DashboardService, "updateColumnsByIdWithoutHooks")
333
+ .mockResolvedValue(undefined);
334
+
335
+ await mockRouter
336
+ .match("post", "/dashboard/master-password/:dashboardId")
337
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
338
+
339
+ expect(nextFunction).not.toHaveBeenCalled();
340
+ expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
341
+ expect(updateSpy).toHaveBeenCalledTimes(1);
342
+
343
+ const update: {
344
+ id: ObjectID;
345
+ data: Record<string, unknown>;
346
+ expectedData?: Record<string, unknown>;
347
+ skipUpdateDateColumn?: boolean;
348
+ } = updateSpy.mock.calls[0]![0] as unknown as {
349
+ id: ObjectID;
350
+ data: Record<string, unknown>;
351
+ expectedData?: Record<string, unknown>;
352
+ skipUpdateDateColumn?: boolean;
353
+ };
354
+
355
+ expect(update.id.toString()).toBe(dashboardId.toString());
356
+ expect(update.skipUpdateDateColumn).toBe(true);
357
+ expect(update.data["masterPassword"]).toEqual(
358
+ expect.stringMatching(/^scrypt\$/),
359
+ );
360
+ expect(update.data["masterPasswordSalt"]).toEqual(
361
+ expect.stringMatching(/^[0-9a-f]{64}$/),
362
+ );
363
+ expect(update.expectedData).toEqual({
364
+ masterPassword: dashboard.masterPassword!.toString(),
365
+ masterPasswordSalt: null,
366
+ });
367
+ await expect(
368
+ PasswordHash.verify({
369
+ plainValue: password,
370
+ storedValue: update.data["masterPassword"] as string,
371
+ salt: update.data["masterPasswordSalt"] as string,
372
+ }),
373
+ ).resolves.toBe(true);
374
+ });
375
+
376
+ it("does not upgrade a legacy hash when the password is wrong", async () => {
377
+ dashboard.masterPassword = new HashedString(
378
+ await HashedString.hashValue(password, EncryptionSecret),
379
+ true,
380
+ );
381
+ delete dashboard.masterPasswordSalt;
382
+ mockRequest.body["password"] = "incorrect password";
383
+
384
+ const updateSpy: ReturnType<typeof jest.spyOn> = jest
385
+ .spyOn(DashboardService, "updateColumnsByIdWithoutHooks")
386
+ .mockResolvedValue(undefined);
387
+
388
+ await mockRouter
389
+ .match("post", "/dashboard/master-password/:dashboardId")
390
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
391
+
392
+ expect(nextFunction).toHaveBeenCalledTimes(1);
393
+ expect(updateSpy).not.toHaveBeenCalled();
394
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
395
+ });
225
396
  });
@@ -0,0 +1,335 @@
1
+ import StatusPage from "../../../Models/DatabaseModels/StatusPage";
2
+ import StatusPageAPI from "../../../Server/API/StatusPageAPI";
3
+ import { EncryptionSecret } from "../../../Server/EnvironmentConfig";
4
+ import StatusPageService from "../../../Server/Services/StatusPageService";
5
+ import CookieUtil from "../../../Server/Utils/Cookie";
6
+ import {
7
+ ExpressRequest,
8
+ ExpressResponse,
9
+ NextFunction,
10
+ } from "../../../Server/Utils/Express";
11
+ import PasswordHash from "../../../Server/Utils/PasswordHash";
12
+ import Response from "../../../Server/Utils/Response";
13
+ import BadDataException from "../../../Types/Exception/BadDataException";
14
+ import HashedString from "../../../Types/HashedString";
15
+ import ObjectID from "../../../Types/ObjectID";
16
+ import { MASTER_PASSWORD_INVALID_MESSAGE } from "../../../Types/StatusPage/MasterPassword";
17
+ import { mockRouter } from "./Helpers";
18
+ import {
19
+ afterEach,
20
+ beforeAll,
21
+ beforeEach,
22
+ describe,
23
+ expect,
24
+ it,
25
+ jest,
26
+ } from "@jest/globals";
27
+
28
+ jest.mock("../../../Server/Utils/Express", () => {
29
+ return {
30
+ getRouter: () => {
31
+ return mockRouter;
32
+ },
33
+ };
34
+ });
35
+
36
+ jest.mock("../../../Server/Utils/Response", () => {
37
+ return {
38
+ sendEntityArrayResponse: jest.fn().mockImplementation((...args: []) => {
39
+ return args;
40
+ }),
41
+ sendJsonObjectResponse: jest.fn().mockImplementation((...args: []) => {
42
+ return args;
43
+ }),
44
+ sendEmptySuccessResponse: jest.fn(),
45
+ sendEntityResponse: jest.fn().mockImplementation((...args: []) => {
46
+ return args;
47
+ }),
48
+ sendErrorResponse: jest.fn().mockImplementation((...args: []) => {
49
+ return args;
50
+ }),
51
+ };
52
+ });
53
+
54
+ describe("StatusPageAPI master password", () => {
55
+ const password: string = "correct horse battery staple";
56
+
57
+ let statusPageId: ObjectID;
58
+ let statusPage: StatusPage;
59
+ let mockRequest: ExpressRequest;
60
+ let mockResponse: ExpressResponse;
61
+ let nextFunction: NextFunction;
62
+
63
+ beforeAll(() => {
64
+ mockRouter.routes.length = 0;
65
+ new StatusPageAPI();
66
+ });
67
+
68
+ beforeEach(async () => {
69
+ jest.clearAllMocks();
70
+
71
+ statusPageId = ObjectID.generate();
72
+ statusPage = new StatusPage();
73
+ statusPage.id = statusPageId;
74
+ statusPage.isPublicStatusPage = false;
75
+ statusPage.enableMasterPassword = true;
76
+ statusPage.masterPasswordSalt = PasswordHash.generateSalt();
77
+ statusPage.masterPassword = new HashedString(
78
+ await PasswordHash.hash({
79
+ plainValue: password,
80
+ salt: statusPage.masterPasswordSalt,
81
+ }),
82
+ true,
83
+ );
84
+
85
+ jest.spyOn(StatusPageService, "findOneById").mockResolvedValue(statusPage);
86
+
87
+ mockRequest = {
88
+ params: {
89
+ statusPageId: statusPageId.toString(),
90
+ },
91
+ body: {
92
+ password,
93
+ },
94
+ cookies: {},
95
+ headers: {},
96
+ socket: {},
97
+ ips: [],
98
+ } as unknown as ExpressRequest;
99
+
100
+ mockResponse = {
101
+ cookie: jest.fn(),
102
+ send: jest.fn(),
103
+ json: jest.fn(),
104
+ status: jest.fn().mockReturnThis(),
105
+ } as unknown as ExpressResponse;
106
+
107
+ nextFunction = jest.fn();
108
+ });
109
+
110
+ afterEach(() => {
111
+ jest.restoreAllMocks();
112
+ });
113
+
114
+ it("issues a status-page-scoped cookie for the correct current scrypt password", async () => {
115
+ await mockRouter
116
+ .match("post", "/status-page/master-password/:statusPageId")
117
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
118
+
119
+ expect(nextFunction).not.toHaveBeenCalled();
120
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
121
+ mockRequest,
122
+ mockResponse,
123
+ );
124
+ expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
125
+
126
+ const cookieCall: Array<unknown> = (mockResponse.cookie as jest.Mock).mock
127
+ .calls[0] as Array<unknown>;
128
+ const cookieName: string = cookieCall[0] as string;
129
+ const cookieToken: string = cookieCall[1] as string;
130
+
131
+ expect(cookieName).toBe(
132
+ CookieUtil.getStatusPageMasterPasswordKey(statusPageId),
133
+ );
134
+
135
+ const unlockedRequest: ExpressRequest = {
136
+ cookies: {
137
+ [cookieName]: cookieToken,
138
+ },
139
+ headers: {},
140
+ socket: {},
141
+ ips: [],
142
+ } as unknown as ExpressRequest;
143
+
144
+ await expect(
145
+ StatusPageService.hasReadAccess({
146
+ statusPageId,
147
+ req: unlockedRequest,
148
+ }),
149
+ ).resolves.toEqual({ hasReadAccess: true });
150
+ });
151
+
152
+ it("does not rewrite a hash already using current scrypt parameters", async () => {
153
+ const updateSpy: ReturnType<typeof jest.spyOn> = jest
154
+ .spyOn(StatusPageService, "updateColumnsByIdWithoutHooks")
155
+ .mockResolvedValue(undefined);
156
+
157
+ await mockRouter
158
+ .match("post", "/status-page/master-password/:statusPageId")
159
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
160
+
161
+ expect(nextFunction).not.toHaveBeenCalled();
162
+ expect(updateSpy).not.toHaveBeenCalled();
163
+ expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
164
+ });
165
+
166
+ it("rejects an incorrect password without issuing a cookie", async () => {
167
+ mockRequest.body["password"] = "incorrect password";
168
+
169
+ await mockRouter
170
+ .match("post", "/status-page/master-password/:statusPageId")
171
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
172
+
173
+ expect(nextFunction).toHaveBeenCalledTimes(1);
174
+ const error: unknown = (nextFunction as jest.Mock).mock.calls[0]?.[0];
175
+
176
+ expect(error).toBeInstanceOf(BadDataException);
177
+ expect((error as BadDataException).message).toBe(
178
+ MASTER_PASSWORD_INVALID_MESSAGE,
179
+ );
180
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
181
+ expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
182
+ });
183
+
184
+ it.each([123, { password: "nested" }, [password]])(
185
+ "rejects a non-string password body (%p) before hash verification",
186
+ async (invalidPassword: unknown) => {
187
+ (mockRequest.body as Record<string, unknown>)["password"] =
188
+ invalidPassword;
189
+ const verifySpy: ReturnType<typeof jest.spyOn> = jest.spyOn(
190
+ StatusPageService,
191
+ "verifyHashedColumnValue",
192
+ );
193
+
194
+ await mockRouter
195
+ .match("post", "/status-page/master-password/:statusPageId")
196
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
197
+
198
+ expect(nextFunction).toHaveBeenCalledTimes(1);
199
+ const error: unknown = (nextFunction as jest.Mock).mock.calls[0]?.[0];
200
+
201
+ expect(error).toBeInstanceOf(BadDataException);
202
+ expect((error as BadDataException).message).toBe(
203
+ "Master password is required.",
204
+ );
205
+ expect(verifySpy).not.toHaveBeenCalled();
206
+ expect(StatusPageService.findOneById).not.toHaveBeenCalled();
207
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
208
+ },
209
+ );
210
+
211
+ it("uses this status page's selected salt when checking the password", async () => {
212
+ statusPage.masterPasswordSalt = PasswordHash.generateSalt();
213
+
214
+ await mockRouter
215
+ .match("post", "/status-page/master-password/:statusPageId")
216
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
217
+
218
+ expect(nextFunction).toHaveBeenCalledTimes(1);
219
+ const error: unknown = (nextFunction as jest.Mock).mock.calls[0]?.[0];
220
+
221
+ expect(error).toBeInstanceOf(BadDataException);
222
+ expect((error as BadDataException).message).toBe(
223
+ MASTER_PASSWORD_INVALID_MESSAGE,
224
+ );
225
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
226
+ });
227
+
228
+ it("selects the salt needed for verification but never exposes either credential column", async () => {
229
+ await mockRouter
230
+ .match("post", "/status-page/master-password/:statusPageId")
231
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
232
+
233
+ expect(StatusPageService.findOneById).toHaveBeenCalledWith(
234
+ expect.objectContaining({
235
+ select: expect.objectContaining({
236
+ masterPassword: true,
237
+ masterPasswordSalt: true,
238
+ }),
239
+ }),
240
+ );
241
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
242
+ mockRequest,
243
+ mockResponse,
244
+ );
245
+ expect(Response.sendEntityResponse).not.toHaveBeenCalled();
246
+ expect(Response.sendJsonObjectResponse).not.toHaveBeenCalled();
247
+ expect(mockResponse.send).not.toHaveBeenCalled();
248
+ expect(mockResponse.json).not.toHaveBeenCalled();
249
+
250
+ const cookieArguments: Array<unknown> = (mockResponse.cookie as jest.Mock)
251
+ .mock.calls[0] as Array<unknown>;
252
+ const serializedCookieArguments: string = JSON.stringify(cookieArguments);
253
+
254
+ expect(serializedCookieArguments).not.toContain(password);
255
+ expect(serializedCookieArguments).not.toContain(
256
+ statusPage.masterPassword!.toString(),
257
+ );
258
+ expect(serializedCookieArguments).not.toContain(
259
+ statusPage.masterPasswordSalt,
260
+ );
261
+ });
262
+
263
+ it("accepts a legacy unsalted SHA-256 password and upgrades it to scrypt", async () => {
264
+ statusPage.masterPassword = new HashedString(
265
+ await HashedString.hashValue(password, EncryptionSecret),
266
+ true,
267
+ );
268
+ delete statusPage.masterPasswordSalt;
269
+
270
+ const updateSpy: ReturnType<typeof jest.spyOn> = jest
271
+ .spyOn(StatusPageService, "updateColumnsByIdWithoutHooks")
272
+ .mockResolvedValue(undefined);
273
+
274
+ await mockRouter
275
+ .match("post", "/status-page/master-password/:statusPageId")
276
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
277
+
278
+ expect(nextFunction).not.toHaveBeenCalled();
279
+ expect(mockResponse.cookie).toHaveBeenCalledTimes(1);
280
+ expect(updateSpy).toHaveBeenCalledTimes(1);
281
+
282
+ const update: {
283
+ id: ObjectID;
284
+ data: Record<string, unknown>;
285
+ expectedData?: Record<string, unknown>;
286
+ skipUpdateDateColumn?: boolean;
287
+ } = updateSpy.mock.calls[0]![0] as unknown as {
288
+ id: ObjectID;
289
+ data: Record<string, unknown>;
290
+ expectedData?: Record<string, unknown>;
291
+ skipUpdateDateColumn?: boolean;
292
+ };
293
+
294
+ expect(update.id.toString()).toBe(statusPageId.toString());
295
+ expect(update.skipUpdateDateColumn).toBe(true);
296
+ expect(update.data["masterPassword"]).toEqual(
297
+ expect.stringMatching(/^scrypt\$/),
298
+ );
299
+ expect(update.data["masterPasswordSalt"]).toEqual(
300
+ expect.stringMatching(/^[0-9a-f]{64}$/),
301
+ );
302
+ expect(update.expectedData).toEqual({
303
+ masterPassword: statusPage.masterPassword!.toString(),
304
+ masterPasswordSalt: null,
305
+ });
306
+ await expect(
307
+ PasswordHash.verify({
308
+ plainValue: password,
309
+ storedValue: update.data["masterPassword"] as string,
310
+ salt: update.data["masterPasswordSalt"] as string,
311
+ }),
312
+ ).resolves.toBe(true);
313
+ });
314
+
315
+ it("does not upgrade a legacy hash when the password is wrong", async () => {
316
+ statusPage.masterPassword = new HashedString(
317
+ await HashedString.hashValue(password, EncryptionSecret),
318
+ true,
319
+ );
320
+ delete statusPage.masterPasswordSalt;
321
+ mockRequest.body["password"] = "incorrect password";
322
+
323
+ const updateSpy: ReturnType<typeof jest.spyOn> = jest
324
+ .spyOn(StatusPageService, "updateColumnsByIdWithoutHooks")
325
+ .mockResolvedValue(undefined);
326
+
327
+ await mockRouter
328
+ .match("post", "/status-page/master-password/:statusPageId")
329
+ .handlerFunction(mockRequest, mockResponse, nextFunction);
330
+
331
+ expect(nextFunction).toHaveBeenCalledTimes(1);
332
+ expect(updateSpy).not.toHaveBeenCalled();
333
+ expect(mockResponse.cookie).not.toHaveBeenCalled();
334
+ });
335
+ });
@@ -38,6 +38,14 @@ function fakeQueuedRun(data?: {
38
38
  } as unknown as AIRun;
39
39
  }
40
40
 
41
+ function investigationTaskContext(): CodeFixTaskContext {
42
+ return {
43
+ sourceInvestigationRunId: ObjectID.generate().toString(),
44
+ sourceInvestigationAnalysisMarkdown:
45
+ "## Root cause\n\nA repository code change fixes this regression.",
46
+ };
47
+ }
48
+
41
49
  describe("AIRunService.claimNextQueuedCodeFixRun", () => {
42
50
  afterEach(() => {
43
51
  jest.restoreAllMocks();
@@ -193,15 +201,16 @@ describe("AIRunService.claimNextQueuedCodeFixRun", () => {
193
201
  });
194
202
 
195
203
  /*
196
- * FixFromIncident is the second incident/alert-subject recipe: enqueued
197
- * by the user from the investigation panel, no telemetry exception — the
198
- * same recipe grouping as ImproveInstrumentation in the guard.
204
+ * FixFromIncident is an incident/alert-subject recipe with one additional
205
+ * requirement: the exact Recommended investigation + analysis are pinned
206
+ * in taskContext so a later RootCause cannot change what the worker fixes.
199
207
  */
200
- test("a FixFromIncident run without an exception but with an incident subject is claimed, not errored", async () => {
208
+ test("a FixFromIncident run with an incident and pinned investigation snapshot is claimed", async () => {
201
209
  const run: AIRun = fakeQueuedRun({
202
210
  exceptionId: null,
203
211
  codeFixTaskType: CodeFixTaskType.FixFromIncident,
204
212
  incidentId: ObjectID.generate(),
213
+ taskContext: investigationTaskContext(),
205
214
  });
206
215
 
207
216
  jest.spyOn(AIRunService, "findOneBy").mockResolvedValue(run);
@@ -219,11 +228,12 @@ describe("AIRunService.claimNextQueuedCodeFixRun", () => {
219
228
  expect(transition).toHaveBeenCalledTimes(1);
220
229
  });
221
230
 
222
- test("a FixFromIncident run with an alert subject is claimed too", async () => {
231
+ test("a FixFromIncident run with an alert and pinned investigation snapshot is claimed too", async () => {
223
232
  const run: AIRun = fakeQueuedRun({
224
233
  exceptionId: null,
225
234
  codeFixTaskType: CodeFixTaskType.FixFromIncident,
226
235
  alertId: ObjectID.generate(),
236
+ taskContext: investigationTaskContext(),
227
237
  });
228
238
 
229
239
  jest.spyOn(AIRunService, "findOneBy").mockResolvedValue(run);
@@ -236,6 +246,50 @@ describe("AIRunService.claimNextQueuedCodeFixRun", () => {
236
246
  expect(claimed).toBe(run);
237
247
  });
238
248
 
249
+ test.each([
250
+ ["no", undefined],
251
+ ["a partial", { sourceInvestigationRunId: ObjectID.generate().toString() }],
252
+ ] as Array<[string, CodeFixTaskContext | undefined]>)(
253
+ "a FixFromIncident run with %s pinned snapshot is finalized as Error and skipped",
254
+ async (_label: string, taskContext: CodeFixTaskContext | undefined) => {
255
+ const brokenRun: AIRun = fakeQueuedRun({
256
+ exceptionId: null,
257
+ codeFixTaskType: CodeFixTaskType.FixFromIncident,
258
+ incidentId: ObjectID.generate(),
259
+ ...(taskContext ? { taskContext } : {}),
260
+ });
261
+ const goodRun: AIRun = fakeQueuedRun();
262
+
263
+ jest
264
+ .spyOn(AIRunService, "findOneBy")
265
+ .mockResolvedValueOnce(brokenRun)
266
+ .mockResolvedValueOnce(goodRun);
267
+ const transition: jest.SpyInstance = jest
268
+ .spyOn(AIRunService, "attemptStatusTransition")
269
+ .mockResolvedValue(1);
270
+
271
+ const claimed: AIRun | null =
272
+ await AIRunService.claimNextQueuedCodeFixRun({
273
+ aiAgentId: ObjectID.generate(),
274
+ });
275
+
276
+ expect(claimed).toBe(goodRun);
277
+ expect(transition).toHaveBeenNthCalledWith(
278
+ 2,
279
+ expect.objectContaining({
280
+ aiRunId: brokenRun.id,
281
+ fromStatus: AIRunStatus.Running,
282
+ set: expect.objectContaining({
283
+ status: AIRunStatus.Error,
284
+ errorMessage: expect.stringContaining(
285
+ "pinned investigation analysis snapshot",
286
+ ),
287
+ }),
288
+ }),
289
+ );
290
+ },
291
+ );
292
+
239
293
  test("a FixFromIncident run with NO subject at all is finalized as Error and skipped", async () => {
240
294
  const brokenRun: AIRun = fakeQueuedRun({
241
295
  exceptionId: null,