@oneuptime/common 11.4.1 → 11.5.1

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 (324) hide show
  1. package/Models/DatabaseModels/AIRun.ts +50 -0
  2. package/Models/DatabaseModels/AlertSeverity.ts +6 -0
  3. package/Models/DatabaseModels/IncidentSeverity.ts +6 -0
  4. package/Models/DatabaseModels/Index.ts +16 -0
  5. package/Models/DatabaseModels/IoTDeviceCredential.ts +471 -0
  6. package/Models/DatabaseModels/LlmLog.ts +56 -0
  7. package/Models/DatabaseModels/LlmProvider.ts +33 -0
  8. package/Models/DatabaseModels/NetworkDevice.ts +1385 -0
  9. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +717 -0
  10. package/Models/DatabaseModels/NetworkDeviceLabelRule.ts +514 -0
  11. package/Models/DatabaseModels/NetworkDeviceOwnerRule.ts +596 -0
  12. package/Models/DatabaseModels/NetworkDeviceOwnerTeam.ts +487 -0
  13. package/Models/DatabaseModels/NetworkDeviceOwnerUser.ts +486 -0
  14. package/Models/DatabaseModels/NetworkInterface.ts +563 -0
  15. package/Models/DatabaseModels/Project.ts +145 -0
  16. package/Models/DatabaseModels/TelemetryEntityRelationship.ts +54 -0
  17. package/Server/API/AIInvestigationAPI.ts +131 -63
  18. package/Server/API/AlertAPI.ts +5 -0
  19. package/Server/API/IncidentAPI.ts +10 -0
  20. package/Server/API/IncidentEpisodeAPI.ts +5 -0
  21. package/Server/API/LlmProviderAPI.ts +4 -0
  22. package/Server/API/ScheduledMaintenanceAPI.ts +5 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1783650000000-AddAdditionalParamsToLlmProvider.ts +19 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1783695782697-AddCacheTokenColumnsToLlmLog.ts +25 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1783701585317-AddAlertInvestigationGating.ts +31 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783702431535-AddAiDailyAutonomousTokenLimitToProject.ts +19 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1783720000000-AddNetworkDeviceTables.ts +105 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1783721121260-AddAttemptCountToAIRun.ts +15 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1783730000000-AddNetworkDeviceOwnersAndRules.ts +192 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1783740000000-AddNetworkDeviceDiscoveryScan.ts +37 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1783750000000-AddLldpNeighborsToNetworkDevice.ts +19 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1783760576655-AddInvestigationTuningToProject.ts +25 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1783762505482-AddTelemetryEntityRelationshipMetrics.ts +31 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1783780000000-AddIoTDeviceCredentialTable.ts +79 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1783790000000-AddSnmpV3AuthColumnsToNetworkDevice.ts +49 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +26 -0
  37. package/Server/Services/AIRunService.ts +58 -0
  38. package/Server/Services/AIService.ts +101 -0
  39. package/Server/Services/IncidentService.ts +14 -41
  40. package/Server/Services/Index.ts +14 -0
  41. package/Server/Services/IoTDeviceCredentialService.ts +351 -0
  42. package/Server/Services/IoTDeviceService.ts +64 -21
  43. package/Server/Services/LlmLogService.ts +26 -0
  44. package/Server/Services/LlmProviderService.ts +3 -0
  45. package/Server/Services/MonitorService.ts +4 -1
  46. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +10 -0
  47. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +204 -0
  48. package/Server/Services/NetworkDeviceLabelRuleService.ts +14 -0
  49. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +222 -0
  50. package/Server/Services/NetworkDeviceOwnerRuleService.ts +14 -0
  51. package/Server/Services/NetworkDeviceOwnerTeamService.ts +10 -0
  52. package/Server/Services/NetworkDeviceOwnerUserService.ts +10 -0
  53. package/Server/Services/NetworkDeviceService.ts +50 -0
  54. package/Server/Services/NetworkInterfaceService.ts +10 -0
  55. package/Server/Services/TelemetryEntityRelationshipService.ts +23 -0
  56. package/Server/Services/TelemetryEntityService.ts +72 -2
  57. package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +244 -12
  58. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +92 -19
  59. package/Server/Utils/AI/Sentinel/InvestigationQueue.ts +493 -0
  60. package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +88 -68
  61. package/Server/Utils/AI/Toolbox/Index.ts +2 -1
  62. package/Server/Utils/AI/Toolbox/MetricTools.ts +391 -0
  63. package/Server/Utils/LLM/LLMService.ts +21 -0
  64. package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +170 -2
  65. package/Server/Utils/Monitor/IoTDeviceAbsenceSeries.ts +101 -0
  66. package/Server/Utils/Monitor/MonitorAlert.ts +15 -3
  67. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +1 -1
  68. package/Server/Utils/Monitor/MonitorIncident.ts +19 -3
  69. package/Server/Utils/Monitor/MonitorMetricUtil.ts +87 -0
  70. package/Server/Utils/Monitor/MonitorResource.ts +77 -21
  71. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -1
  72. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +201 -0
  73. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +215 -0
  74. package/Server/Utils/Monitor/SnmpInterfaceRateUtil.ts +169 -0
  75. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +173 -0
  76. package/Tests/Server/Services/IncidentInternalNoteAnnouncement.test.ts +97 -0
  77. package/Tests/Server/Services/IoTDeviceService.test.ts +54 -0
  78. package/Tests/Server/Utils/AI/BaselineAnomalyTool.test.ts +253 -0
  79. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +44 -0
  80. package/Tests/Server/Utils/AI/SentinelAlertGating.test.ts +360 -0
  81. package/Tests/Server/Utils/AI/SentinelInvestigationQueue.test.ts +295 -0
  82. package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +59 -0
  83. package/Tests/Server/Utils/Monitor/IoTDeviceAbsenceSeries.test.ts +113 -0
  84. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +198 -0
  85. package/Tests/Types/Monitor/MonitorType.test.ts +1 -1
  86. package/Tests/Utils/Monitor/LatencyMatrixUtil.test.ts +111 -0
  87. package/Types/AI/AIRunStatus.ts +9 -0
  88. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +0 -1
  89. package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +8 -3
  90. package/Types/LlmLogStatus.ts +1 -0
  91. package/Types/Monitor/CriteriaFilter.ts +5 -0
  92. package/Types/Monitor/IotAlertTemplates.ts +22 -3
  93. package/Types/Monitor/LatencyMatrix.ts +27 -0
  94. package/Types/Monitor/MonitorCriteriaInstance.ts +2 -2
  95. package/Types/Monitor/MonitorMetricType.ts +10 -0
  96. package/Types/Monitor/MonitorStep.ts +36 -11
  97. package/Types/Monitor/MonitorStepNetworkDeviceMonitor.ts +55 -0
  98. package/Types/Monitor/MonitorStepSnmpMonitor.ts +8 -0
  99. package/Types/Monitor/MonitorType.ts +14 -9
  100. package/Types/Monitor/SnmpMonitor/LldpNeighbor.ts +12 -0
  101. package/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.ts +116 -0
  102. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +29 -0
  103. package/Types/Monitor/SnmpMonitor/SnmpInterface.ts +31 -0
  104. package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +24 -0
  105. package/Types/Monitor/SnmpMonitor/SnmpTrap.ts +20 -0
  106. package/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.ts +173 -0
  107. package/Types/Permission.ts +314 -0
  108. package/Types/Probe/ProbeMonitorResponse.ts +8 -0
  109. package/UI/Components/Icon/Icon.tsx +8 -6
  110. package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +2 -2
  111. package/UI/Components/MoreMenu/MoreMenu.tsx +61 -2
  112. package/UI/Components/Tabs/Tabs.tsx +10 -1
  113. package/UI/Utils/Navigation.ts +10 -1
  114. package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +16 -21
  115. package/Utils/Monitor/LatencyMatrixUtil.ts +162 -0
  116. package/Utils/Monitor/MonitorMetricType.ts +71 -1
  117. package/Utils/Monitor/NetworkTopologyUtil.ts +159 -0
  118. package/Utils/Telemetry/EntityRelationship.ts +11 -0
  119. package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
  120. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/AlertSeverity.js +6 -0
  122. package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
  123. package/build/dist/Models/DatabaseModels/IncidentSeverity.js +6 -0
  124. package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
  125. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  126. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  127. package/build/dist/Models/DatabaseModels/IoTDeviceCredential.js +493 -0
  128. package/build/dist/Models/DatabaseModels/IoTDeviceCredential.js.map +1 -0
  129. package/build/dist/Models/DatabaseModels/LlmLog.js +58 -0
  130. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
  131. package/build/dist/Models/DatabaseModels/LlmProvider.js +33 -0
  132. package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
  133. package/build/dist/Models/DatabaseModels/NetworkDevice.js +1426 -0
  134. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -0
  135. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +738 -0
  136. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -0
  137. package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js +522 -0
  138. package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js.map +1 -0
  139. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js +603 -0
  140. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js.map +1 -0
  141. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerTeam.js +503 -0
  142. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerTeam.js.map +1 -0
  143. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerUser.js +502 -0
  144. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerUser.js.map +1 -0
  145. package/build/dist/Models/DatabaseModels/NetworkInterface.js +589 -0
  146. package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -0
  147. package/build/dist/Models/DatabaseModels/Project.js +147 -0
  148. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js +57 -0
  150. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +1 -1
  151. package/build/dist/Server/API/AIInvestigationAPI.js +95 -55
  152. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  153. package/build/dist/Server/API/AlertAPI.js +5 -0
  154. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  155. package/build/dist/Server/API/IncidentAPI.js +10 -0
  156. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  157. package/build/dist/Server/API/IncidentEpisodeAPI.js +5 -0
  158. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  159. package/build/dist/Server/API/LlmProviderAPI.js +5 -7
  160. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  161. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +5 -0
  162. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783650000000-AddAdditionalParamsToLlmProvider.js +12 -0
  164. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783650000000-AddAdditionalParamsToLlmProvider.js.map +1 -0
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783695782697-AddCacheTokenColumnsToLlmLog.js +14 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783695782697-AddCacheTokenColumnsToLlmLog.js.map +1 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783701585317-AddAlertInvestigationGating.js +18 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783701585317-AddAlertInvestigationGating.js.map +1 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783702431535-AddAiDailyAutonomousTokenLimitToProject.js +12 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783702431535-AddAiDailyAutonomousTokenLimitToProject.js.map +1 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783720000000-AddNetworkDeviceTables.js +51 -0
  172. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783720000000-AddNetworkDeviceTables.js.map +1 -0
  173. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783721121260-AddAttemptCountToAIRun.js +12 -0
  174. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783721121260-AddAttemptCountToAIRun.js.map +1 -0
  175. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783730000000-AddNetworkDeviceOwnersAndRules.js +101 -0
  176. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783730000000-AddNetworkDeviceOwnersAndRules.js.map +1 -0
  177. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783740000000-AddNetworkDeviceDiscoveryScan.js +18 -0
  178. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783740000000-AddNetworkDeviceDiscoveryScan.js.map +1 -0
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783750000000-AddLldpNeighborsToNetworkDevice.js +12 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783750000000-AddLldpNeighborsToNetworkDevice.js.map +1 -0
  181. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783760576655-AddInvestigationTuningToProject.js +14 -0
  182. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783760576655-AddInvestigationTuningToProject.js.map +1 -0
  183. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783762505482-AddTelemetryEntityRelationshipMetrics.js +16 -0
  184. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783762505482-AddTelemetryEntityRelationshipMetrics.js.map +1 -0
  185. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783780000000-AddIoTDeviceCredentialTable.js +38 -0
  186. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783780000000-AddIoTDeviceCredentialTable.js.map +1 -0
  187. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783790000000-AddSnmpV3AuthColumnsToNetworkDevice.js +22 -0
  188. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783790000000-AddSnmpV3AuthColumnsToNetworkDevice.js.map +1 -0
  189. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +26 -0
  190. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  191. package/build/dist/Server/Services/AIRunService.js +33 -0
  192. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  193. package/build/dist/Server/Services/AIService.js +82 -9
  194. package/build/dist/Server/Services/AIService.js.map +1 -1
  195. package/build/dist/Server/Services/IncidentService.js +14 -25
  196. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  197. package/build/dist/Server/Services/Index.js +14 -0
  198. package/build/dist/Server/Services/Index.js.map +1 -1
  199. package/build/dist/Server/Services/IoTDeviceCredentialService.js +321 -0
  200. package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -0
  201. package/build/dist/Server/Services/IoTDeviceService.js +49 -15
  202. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  203. package/build/dist/Server/Services/LlmLogService.js +29 -0
  204. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  205. package/build/dist/Server/Services/LlmProviderService.js +3 -0
  206. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  207. package/build/dist/Server/Services/MonitorService.js +2 -1
  208. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  209. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +9 -0
  210. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -0
  211. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +166 -0
  212. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -0
  213. package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js +13 -0
  214. package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js.map +1 -0
  215. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +186 -0
  216. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -0
  217. package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js +13 -0
  218. package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js.map +1 -0
  219. package/build/dist/Server/Services/NetworkDeviceOwnerTeamService.js +9 -0
  220. package/build/dist/Server/Services/NetworkDeviceOwnerTeamService.js.map +1 -0
  221. package/build/dist/Server/Services/NetworkDeviceOwnerUserService.js +9 -0
  222. package/build/dist/Server/Services/NetworkDeviceOwnerUserService.js.map +1 -0
  223. package/build/dist/Server/Services/NetworkDeviceService.js +52 -0
  224. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -0
  225. package/build/dist/Server/Services/NetworkInterfaceService.js +9 -0
  226. package/build/dist/Server/Services/NetworkInterfaceService.js.map +1 -0
  227. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +22 -0
  228. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  229. package/build/dist/Server/Services/TelemetryEntityService.js +64 -2
  230. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  231. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +189 -9
  232. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -1
  233. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +84 -17
  234. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -1
  235. package/build/dist/Server/Utils/AI/Sentinel/InvestigationQueue.js +428 -0
  236. package/build/dist/Server/Utils/AI/Sentinel/InvestigationQueue.js.map +1 -0
  237. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +63 -47
  238. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -1
  239. package/build/dist/Server/Utils/AI/Toolbox/Index.js +2 -1
  240. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  241. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +300 -0
  242. package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
  243. package/build/dist/Server/Utils/LLM/LLMService.js +16 -0
  244. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  245. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +130 -2
  246. package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
  247. package/build/dist/Server/Utils/Monitor/IoTDeviceAbsenceSeries.js +81 -0
  248. package/build/dist/Server/Utils/Monitor/IoTDeviceAbsenceSeries.js.map +1 -0
  249. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +18 -7
  250. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  251. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
  252. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  253. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +25 -10
  254. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  255. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +69 -3
  256. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  257. package/build/dist/Server/Utils/Monitor/MonitorResource.js +76 -30
  258. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  259. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -1
  260. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  261. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +147 -0
  262. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -0
  263. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +167 -0
  264. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -0
  265. package/build/dist/Server/Utils/Monitor/SnmpInterfaceRateUtil.js +93 -0
  266. package/build/dist/Server/Utils/Monitor/SnmpInterfaceRateUtil.js.map +1 -0
  267. package/build/dist/Types/AI/AIRunStatus.js +9 -0
  268. package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
  269. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +0 -1
  270. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  271. package/build/dist/Types/LlmLogStatus.js +1 -0
  272. package/build/dist/Types/LlmLogStatus.js.map +1 -1
  273. package/build/dist/Types/Monitor/CriteriaFilter.js +5 -0
  274. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  275. package/build/dist/Types/Monitor/IotAlertTemplates.js +7 -7
  276. package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
  277. package/build/dist/Types/Monitor/LatencyMatrix.js +2 -0
  278. package/build/dist/Types/Monitor/LatencyMatrix.js.map +1 -0
  279. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +2 -2
  280. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  281. package/build/dist/Types/Monitor/MonitorMetricType.js +9 -0
  282. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  283. package/build/dist/Types/Monitor/MonitorStep.js +22 -9
  284. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  285. package/build/dist/Types/Monitor/MonitorStepNetworkDeviceMonitor.js +36 -0
  286. package/build/dist/Types/Monitor/MonitorStepNetworkDeviceMonitor.js.map +1 -0
  287. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js +3 -0
  288. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js.map +1 -1
  289. package/build/dist/Types/Monitor/MonitorType.js +14 -9
  290. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  291. package/build/dist/Types/Monitor/SnmpMonitor/LldpNeighbor.js +2 -0
  292. package/build/dist/Types/Monitor/SnmpMonitor/LldpNeighbor.js.map +1 -0
  293. package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js +89 -0
  294. package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js.map +1 -0
  295. package/build/dist/Types/Monitor/SnmpMonitor/NetworkTopology.js +2 -0
  296. package/build/dist/Types/Monitor/SnmpMonitor/NetworkTopology.js.map +1 -0
  297. package/build/dist/Types/Monitor/SnmpMonitor/SnmpInterface.js +2 -0
  298. package/build/dist/Types/Monitor/SnmpMonitor/SnmpInterface.js.map +1 -0
  299. package/build/dist/Types/Monitor/SnmpMonitor/SnmpTrap.js +2 -0
  300. package/build/dist/Types/Monitor/SnmpMonitor/SnmpTrap.js.map +1 -0
  301. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js +137 -0
  302. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js.map +1 -0
  303. package/build/dist/Types/Permission.js +280 -0
  304. package/build/dist/Types/Permission.js.map +1 -1
  305. package/build/dist/UI/Components/Icon/Icon.js +8 -6
  306. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  307. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +2 -2
  308. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
  309. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +44 -4
  310. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  311. package/build/dist/UI/Components/Tabs/Tabs.js +3 -1
  312. package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
  313. package/build/dist/UI/Utils/Navigation.js +11 -1
  314. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  315. package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +17 -20
  316. package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -1
  317. package/build/dist/Utils/Monitor/LatencyMatrixUtil.js +89 -0
  318. package/build/dist/Utils/Monitor/LatencyMatrixUtil.js.map +1 -0
  319. package/build/dist/Utils/Monitor/MonitorMetricType.js +68 -1
  320. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  321. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +112 -0
  322. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -0
  323. package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
  324. package/package.json +1 -1
@@ -0,0 +1,173 @@
1
+ import AIService, {
2
+ AUTONOMOUS_AI_FEATURES,
3
+ AutonomousBudgetStatus,
4
+ } from "../../../Server/Services/AIService";
5
+ import SentinelInvestigationQueue from "../../../Server/Utils/AI/Sentinel/InvestigationQueue";
6
+ import LlmLogService from "../../../Server/Services/LlmLogService";
7
+ import ProjectService from "../../../Server/Services/ProjectService";
8
+ import AIRunService from "../../../Server/Services/AIRunService";
9
+ import Project from "../../../Models/DatabaseModels/Project";
10
+ import ObjectID from "../../../Types/ObjectID";
11
+ import PositiveNumber from "../../../Types/PositiveNumber";
12
+ import { describe, expect, test, afterEach } from "@jest/globals";
13
+
14
+ /*
15
+ * G4 daily budget: Project.aiDailyAutonomousTokenLimit bounds the tokens the
16
+ * AUTONOMOUS_AI_FEATURES may consume per UTC day. The engine skips new
17
+ * investigations quietly when the budget is exhausted (no Error run created);
18
+ * AIService.executeWithLogging is the hard backstop mid-run. Interactive chat
19
+ * is never blocked — its features are not in AUTONOMOUS_AI_FEATURES.
20
+ *
21
+ * These tests mock the persistence layer and lock in:
22
+ * (a) no limit configured => never exhausted, and no usage query is run;
23
+ * (b) usage below the limit => not exhausted;
24
+ * (c) usage at/over the limit => exhausted, with the numbers reported;
25
+ * (d) the engine skips run creation when the budget is exhausted.
26
+ */
27
+
28
+ function fakeProject(limit: number | undefined): Project {
29
+ return {
30
+ id: ObjectID.generate(),
31
+ aiDailyAutonomousTokenLimit: limit,
32
+ } as unknown as Project;
33
+ }
34
+
35
+ describe("AIService.getAutonomousDailyBudgetStatus", () => {
36
+ const projectId: ObjectID = ObjectID.generate();
37
+
38
+ afterEach(() => {
39
+ jest.restoreAllMocks();
40
+ });
41
+
42
+ test("no configured limit means never exhausted and no usage query", async () => {
43
+ jest
44
+ .spyOn(ProjectService, "findOneById")
45
+ .mockResolvedValue(fakeProject(undefined));
46
+ const getTokens: jest.SpyInstance = jest.spyOn(
47
+ LlmLogService,
48
+ "getTotalTokensUsedSince",
49
+ );
50
+
51
+ const status: AutonomousBudgetStatus =
52
+ await AIService.getAutonomousDailyBudgetStatus(projectId);
53
+
54
+ expect(status.exhausted).toBe(false);
55
+ expect(status.limitInTokens).toBeNull();
56
+ expect(getTokens).not.toHaveBeenCalled();
57
+ });
58
+
59
+ test("a limit of 0 pauses autonomous runs without querying usage", async () => {
60
+ jest.spyOn(ProjectService, "findOneById").mockResolvedValue(fakeProject(0));
61
+ const getTokens: jest.SpyInstance = jest.spyOn(
62
+ LlmLogService,
63
+ "getTotalTokensUsedSince",
64
+ );
65
+
66
+ const status: AutonomousBudgetStatus =
67
+ await AIService.getAutonomousDailyBudgetStatus(projectId);
68
+
69
+ expect(status.exhausted).toBe(true);
70
+ expect(status.limitInTokens).toBe(0);
71
+ expect(getTokens).not.toHaveBeenCalled();
72
+ });
73
+
74
+ test("usage below the limit is not exhausted", async () => {
75
+ jest
76
+ .spyOn(ProjectService, "findOneById")
77
+ .mockResolvedValue(fakeProject(100_000));
78
+ jest
79
+ .spyOn(LlmLogService, "getTotalTokensUsedSince")
80
+ .mockResolvedValue(99_999);
81
+
82
+ const status: AutonomousBudgetStatus =
83
+ await AIService.getAutonomousDailyBudgetStatus(projectId);
84
+
85
+ expect(status.exhausted).toBe(false);
86
+ expect(status.limitInTokens).toBe(100_000);
87
+ expect(status.usedTokensToday).toBe(99_999);
88
+ });
89
+
90
+ test("usage at the limit is exhausted, scoped to autonomous features", async () => {
91
+ jest
92
+ .spyOn(ProjectService, "findOneById")
93
+ .mockResolvedValue(fakeProject(100_000));
94
+ const getTokens: jest.SpyInstance = jest
95
+ .spyOn(LlmLogService, "getTotalTokensUsedSince")
96
+ .mockResolvedValue(100_000);
97
+
98
+ const status: AutonomousBudgetStatus =
99
+ await AIService.getAutonomousDailyBudgetStatus(projectId);
100
+
101
+ expect(status.exhausted).toBe(true);
102
+ expect(getTokens).toHaveBeenCalledWith(
103
+ expect.objectContaining({
104
+ features: AUTONOMOUS_AI_FEATURES,
105
+ }),
106
+ );
107
+ });
108
+ });
109
+
110
+ describe("SentinelInvestigationQueue budget skip", () => {
111
+ afterEach(() => {
112
+ jest.restoreAllMocks();
113
+ });
114
+
115
+ test("does not enqueue when the daily budget is exhausted", async () => {
116
+ jest.spyOn(AIService, "getAutonomousDailyBudgetStatus").mockResolvedValue({
117
+ exhausted: true,
118
+ limitInTokens: 100_000,
119
+ usedTokensToday: 120_000,
120
+ });
121
+ const create: jest.SpyInstance = jest.spyOn(AIRunService, "create");
122
+
123
+ await SentinelInvestigationQueue.enqueue({
124
+ projectId: ObjectID.generate(),
125
+ subjectAlertId: ObjectID.generate(),
126
+ });
127
+
128
+ expect(create).not.toHaveBeenCalled();
129
+ });
130
+
131
+ test("does not enqueue when the budget check itself fails", async () => {
132
+ jest
133
+ .spyOn(AIService, "getAutonomousDailyBudgetStatus")
134
+ .mockRejectedValue(new Error("db down"));
135
+ const create: jest.SpyInstance = jest.spyOn(AIRunService, "create");
136
+
137
+ await SentinelInvestigationQueue.enqueue({
138
+ projectId: ObjectID.generate(),
139
+ subjectAlertId: ObjectID.generate(),
140
+ });
141
+
142
+ expect(create).not.toHaveBeenCalled();
143
+ });
144
+
145
+ test("leaves a queued run unclaimed when the budget is exhausted at claim time", async () => {
146
+ // No per-project cap override; concurrency cap passes…
147
+ jest
148
+ .spyOn(ProjectService, "findOneById")
149
+ .mockResolvedValue(fakeProject(undefined));
150
+ jest
151
+ .spyOn(AIRunService, "countBy")
152
+ .mockResolvedValue(new PositiveNumber(0));
153
+ // …but the budget is exhausted.
154
+ jest.spyOn(AIService, "getAutonomousDailyBudgetStatus").mockResolvedValue({
155
+ exhausted: true,
156
+ limitInTokens: 100_000,
157
+ usedTokensToday: 120_000,
158
+ });
159
+ const claim: jest.SpyInstance = jest.spyOn(
160
+ AIRunService,
161
+ "attemptStatusTransition",
162
+ );
163
+
164
+ await SentinelInvestigationQueue.processRun({
165
+ id: ObjectID.generate(),
166
+ projectId: ObjectID.generate(),
167
+ attemptCount: 0,
168
+ triggeredByAlertId: ObjectID.generate(),
169
+ });
170
+
171
+ expect(claim).not.toHaveBeenCalled();
172
+ });
173
+ });
@@ -0,0 +1,97 @@
1
+ import IncidentInternalNoteService from "../../../Server/Services/IncidentInternalNoteService";
2
+ import IncidentFeedService from "../../../Server/Services/IncidentFeedService";
3
+ import IncidentService from "../../../Server/Services/IncidentService";
4
+ import Model from "../../../Models/DatabaseModels/IncidentInternalNote";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import URL from "../../../Types/API/URL";
7
+ import { describe, expect, test, afterEach } from "@jest/globals";
8
+
9
+ /*
10
+ * IncidentInternalNoteService.onCreateSuccess announces every new note with a
11
+ * "posted private note" feed item + workspace ping — INCLUDING user-less
12
+ * system notes: the SLA note-reminder job (SendNoteReminders.ts) creates notes
13
+ * with no createdByUserId and relies entirely on this hook for its feed entry
14
+ * and Slack/Teams ping (it sets isOwnerNotified itself, so the hook is its
15
+ * only push channel).
16
+ *
17
+ * Callers that must NOT announce (Sentinel's RCA note, which has its own
18
+ * quiet-mode-gated RootCause feed item) opt out explicitly with
19
+ * props.ignoreHooks — see IncidentInvestigationRunner. These tests lock in
20
+ * that the hook announces for both user-authored and user-less notes, so the
21
+ * SLA flow can't be silenced again by a "system notes are quiet" shortcut.
22
+ */
23
+
24
+ function fakeNote(userId?: ObjectID | undefined): Model {
25
+ return {
26
+ id: ObjectID.generate(),
27
+ incidentId: ObjectID.generate(),
28
+ projectId: ObjectID.generate(),
29
+ note: "test note",
30
+ createdByUserId: userId,
31
+ createdByUser: undefined,
32
+ } as unknown as Model;
33
+ }
34
+
35
+ describe("IncidentInternalNoteService.onCreateSuccess", () => {
36
+ afterEach(() => {
37
+ jest.restoreAllMocks();
38
+ });
39
+
40
+ function mockAnnouncementDeps(): jest.SpyInstance {
41
+ const createFeedItem: jest.SpyInstance = jest
42
+ .spyOn(IncidentFeedService, "createIncidentFeedItem")
43
+ .mockResolvedValue(undefined as never);
44
+ jest.spyOn(IncidentService, "getIncidentNumber").mockResolvedValue({
45
+ number: 42,
46
+ numberWithPrefix: "INC-42",
47
+ });
48
+ jest
49
+ .spyOn(IncidentService, "getIncidentLinkInDashboard")
50
+ .mockResolvedValue(URL.fromString("https://oneuptime.example/incident"));
51
+ // No attachments on these notes.
52
+ jest
53
+ .spyOn(IncidentInternalNoteService, "findOneById")
54
+ .mockResolvedValue(null);
55
+ return createFeedItem;
56
+ }
57
+
58
+ test("user-authored note announces with a feed item attributed to the user", async () => {
59
+ const createFeedItem: jest.SpyInstance = mockAnnouncementDeps();
60
+ const userId: ObjectID = ObjectID.generate();
61
+ const note: Model = fakeNote(userId);
62
+
63
+ await IncidentInternalNoteService.onCreateSuccess(
64
+ { createBy: { data: note, props: { isRoot: true } }, carryForward: null },
65
+ note,
66
+ );
67
+
68
+ expect(createFeedItem).toHaveBeenCalledTimes(1);
69
+ expect(createFeedItem).toHaveBeenCalledWith(
70
+ expect.objectContaining({
71
+ userId,
72
+ workspaceNotification: expect.objectContaining({
73
+ sendWorkspaceNotification: true,
74
+ }),
75
+ }),
76
+ );
77
+ });
78
+
79
+ test("user-less system note (e.g. SLA reminder) still announces", async () => {
80
+ const createFeedItem: jest.SpyInstance = mockAnnouncementDeps();
81
+ const note: Model = fakeNote(undefined);
82
+
83
+ await IncidentInternalNoteService.onCreateSuccess(
84
+ { createBy: { data: note, props: { isRoot: true } }, carryForward: null },
85
+ note,
86
+ );
87
+
88
+ expect(createFeedItem).toHaveBeenCalledTimes(1);
89
+ expect(createFeedItem).toHaveBeenCalledWith(
90
+ expect.objectContaining({
91
+ workspaceNotification: expect.objectContaining({
92
+ sendWorkspaceNotification: true,
93
+ }),
94
+ }),
95
+ );
96
+ });
97
+ });
@@ -278,3 +278,57 @@ describe("IoTDeviceService.bulkUpdateLatestMetrics — sanitation matches the up
278
278
  expect(params).not.toContain(10);
279
279
  });
280
280
  });
281
+
282
+ describe("IoTDeviceService.deleteStaleForFleet — registered devices survive", () => {
283
+ const CUTOFF: Date = new Date("2026-06-13T00:00:00.000Z");
284
+
285
+ test("marks registered devices offline and deletes only unregistered ones", async () => {
286
+ const query: jest.Mock = mockQueryRunner();
287
+ query
288
+ .mockResolvedValueOnce([[], 2]) // UPDATE (registered -> isUp=false)
289
+ .mockResolvedValueOnce([[], 5]); // DELETE (unregistered)
290
+
291
+ const result: { deleted: number; markedOffline: number } =
292
+ await IoTDeviceService.deleteStaleForFleet({
293
+ iotFleetId: FLEET_ID,
294
+ olderThan: CUTOFF,
295
+ });
296
+
297
+ expect(query).toHaveBeenCalledTimes(2);
298
+
299
+ const [updateSql, updateParams] = query.mock.calls[0] as QueryCall;
300
+ expect(updateSql).toContain('UPDATE "IoTDevice" SET "isUp" = false');
301
+ // Registered = credential on the same project + fleet + externalId.
302
+ expect(updateSql).toContain('EXISTS (SELECT 1 FROM "IoTDeviceCredential"');
303
+ expect(updateSql).toContain('c."externalId" = "IoTDevice"."externalId"');
304
+ expect(updateSql).toContain('c."projectId" = "IoTDevice"."projectId"');
305
+ // The credential correlation itself is kind-agnostic.
306
+ expect(updateSql).not.toContain('c."kind"');
307
+ // Reconnect recovery depends on lastSeenAt staying untouched.
308
+ expect(updateSql).not.toContain('"lastSeenAt" =');
309
+ // Idempotent across cron ticks: already-down rows are not rewritten.
310
+ expect(updateSql).toContain('"isUp" IS DISTINCT FROM false');
311
+ // Stale duplicate-kind rows self-heal instead of being pinned offline.
312
+ expect(updateSql).toContain("NOT EXISTS");
313
+ expect(updateParams).toEqual([FLEET_ID.toString(), CUTOFF]);
314
+
315
+ const [deleteSql, deleteParams] = query.mock.calls[1] as QueryCall;
316
+ expect(deleteSql).toContain('DELETE FROM "IoTDevice"');
317
+ expect(deleteSql).toContain("NOT EXISTS");
318
+ expect(deleteParams).toEqual([FLEET_ID.toString(), CUTOFF]);
319
+
320
+ expect(result).toEqual({ deleted: 5, markedOffline: 2 });
321
+ });
322
+
323
+ test("normalizes a driver result without an affected count to zero", async () => {
324
+ mockQueryRunner([]);
325
+
326
+ const result: { deleted: number; markedOffline: number } =
327
+ await IoTDeviceService.deleteStaleForFleet({
328
+ iotFleetId: FLEET_ID,
329
+ olderThan: CUTOFF,
330
+ });
331
+
332
+ expect(result).toEqual({ deleted: 0, markedOffline: 0 });
333
+ });
334
+ });
@@ -0,0 +1,253 @@
1
+ import { BaselineAnomalyTool } from "../../../../Server/Utils/AI/Toolbox/MetricTools";
2
+ import { ToolContext } from "../../../../Server/Utils/AI/Toolbox/ToolTypes";
3
+ import MetricBaselineService from "../../../../Server/Services/MetricBaselineService";
4
+ import MetricService from "../../../../Server/Services/MetricService";
5
+ import ModelPermission from "../../../../Server/Types/AnalyticsDatabase/ModelPermission";
6
+ import ObjectID from "../../../../Types/ObjectID";
7
+ import AggregatedResult from "../../../../Types/BaseDatabase/AggregatedResult";
8
+ import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
9
+
10
+ /*
11
+ * baseline_anomaly judges a metric's recent average against its learned
12
+ * hour-of-week baseline band (mean ± sigma·stddev — the same math the anomaly
13
+ * monitors use via sigmaForSensitivity). These tests mock the baseline and
14
+ * metric services (no ClickHouse) to lock in:
15
+ * (a) missing/unreliable baselines report "insufficient baseline data" and
16
+ * never render a normal/anomalous verdict (a missing baseline must not
17
+ * read as "not anomalous");
18
+ * (b) above-band / below-band / within-band classification at Medium (3σ);
19
+ * (c) a metric with no recent data points is flagged as possibly having
20
+ * stopped reporting, not judged normal;
21
+ * (d) a zero-stddev baseline still classifies (band collapses to the mean).
22
+ */
23
+
24
+ const ctx: ToolContext = {
25
+ projectId: ObjectID.generate(),
26
+ props: { isRoot: true },
27
+ };
28
+
29
+ function mockBaseline(data: {
30
+ mean: number;
31
+ stddev: number;
32
+ sampleCount?: number;
33
+ isReliable?: boolean;
34
+ }): void {
35
+ jest.spyOn(MetricBaselineService, "getBaseline").mockResolvedValue({
36
+ sampleCount: data.sampleCount ?? 50,
37
+ mean: data.mean,
38
+ stddev: data.stddev,
39
+ median: data.mean,
40
+ p95: data.mean + 2 * data.stddev,
41
+ minObserved: data.mean - 3 * data.stddev,
42
+ maxObserved: data.mean + 3 * data.stddev,
43
+ isReliable: data.isReliable ?? true,
44
+ windowDays: 14,
45
+ hourOfWeek: 10,
46
+ });
47
+ }
48
+
49
+ function mockCurrentValues(values: Array<number>): void {
50
+ jest.spyOn(MetricService, "aggregateBy").mockResolvedValue({
51
+ data: values.map((value: number) => {
52
+ return { timestamp: new Date(), value };
53
+ }),
54
+ } as unknown as AggregatedResult);
55
+ }
56
+
57
+ function mockEmptyBand(): void {
58
+ jest.spyOn(MetricBaselineService, "getBandSeries").mockResolvedValue([]);
59
+ }
60
+
61
+ describe("BaselineAnomalyTool", () => {
62
+ beforeEach(() => {
63
+ // Default: project-wide telemetry access (null = unrestricted).
64
+ jest
65
+ .spyOn(ModelPermission, "getAccessibleServiceIdsForAnalyticsModel")
66
+ .mockResolvedValue(null);
67
+ });
68
+
69
+ afterEach(() => {
70
+ jest.restoreAllMocks();
71
+ });
72
+
73
+ test("requires metricName", async () => {
74
+ await expect(BaselineAnomalyTool.execute({}, ctx)).rejects.toThrow(
75
+ "metricName is required",
76
+ );
77
+ });
78
+
79
+ test("rejects an unparseable atTime", async () => {
80
+ await expect(
81
+ BaselineAnomalyTool.execute(
82
+ { metricName: "cpu.usage", atTime: "yesterday-ish" },
83
+ ctx,
84
+ ),
85
+ ).rejects.toThrow("not a valid ISO 8601 timestamp");
86
+ });
87
+
88
+ test("no baseline reports insufficient data and never queries the metric", async () => {
89
+ jest.spyOn(MetricBaselineService, "getBaseline").mockResolvedValue(null);
90
+ jest.spyOn(MetricBaselineService, "getCoverage").mockResolvedValue({
91
+ totalSamples: 3,
92
+ oldestDay: new Date(),
93
+ });
94
+ const aggregateBy: jest.SpyInstance = jest.spyOn(
95
+ MetricService,
96
+ "aggregateBy",
97
+ );
98
+
99
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
100
+ { metricName: "cpu.usage" },
101
+ ctx,
102
+ );
103
+
104
+ expect(result.dataForLlm).toContain("insufficient baseline data");
105
+ expect(result.dataForLlm).not.toContain("ANOMALOUS");
106
+ expect(aggregateBy).not.toHaveBeenCalled();
107
+ });
108
+
109
+ test("unreliable baseline (too few samples) also reports insufficient data", async () => {
110
+ mockBaseline({ mean: 100, stddev: 10, sampleCount: 2, isReliable: false });
111
+ jest.spyOn(MetricBaselineService, "getCoverage").mockResolvedValue({
112
+ totalSamples: 2,
113
+ oldestDay: null,
114
+ });
115
+
116
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
117
+ { metricName: "cpu.usage" },
118
+ ctx,
119
+ );
120
+
121
+ expect(result.dataForLlm).toContain("insufficient baseline data");
122
+ expect(result.dataForLlm).toContain("only 2 sample(s)");
123
+ });
124
+
125
+ test("value above the 3-sigma band is anomalous (above)", async () => {
126
+ mockBaseline({ mean: 100, stddev: 10 });
127
+ mockCurrentValues([150]);
128
+ mockEmptyBand();
129
+
130
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
131
+ { metricName: "cpu.usage" },
132
+ ctx,
133
+ );
134
+
135
+ expect(result.dataForLlm).toContain("ANOMALOUS — above");
136
+ });
137
+
138
+ test("value below the 3-sigma band is anomalous (below)", async () => {
139
+ mockBaseline({ mean: 100, stddev: 10 });
140
+ mockCurrentValues([30]);
141
+ mockEmptyBand();
142
+
143
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
144
+ { metricName: "cpu.usage" },
145
+ ctx,
146
+ );
147
+
148
+ expect(result.dataForLlm).toContain("ANOMALOUS — below");
149
+ });
150
+
151
+ test("value inside the band is normal", async () => {
152
+ mockBaseline({ mean: 100, stddev: 10 });
153
+ mockCurrentValues([120]);
154
+ mockEmptyBand();
155
+
156
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
157
+ { metricName: "cpu.usage" },
158
+ ctx,
159
+ );
160
+
161
+ expect(result.dataForLlm).toContain("normal — within the expected range");
162
+ });
163
+
164
+ test("High sensitivity (2 sigma) flags what Medium would call normal", async () => {
165
+ mockBaseline({ mean: 100, stddev: 10 });
166
+ mockCurrentValues([125]); // z = 2.5: inside 3σ, outside 2σ
167
+ mockEmptyBand();
168
+
169
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
170
+ { metricName: "cpu.usage", sensitivity: "High" },
171
+ ctx,
172
+ );
173
+
174
+ expect(result.dataForLlm).toContain("ANOMALOUS — above");
175
+ });
176
+
177
+ test("no recent data points is flagged as possibly stopped reporting", async () => {
178
+ mockBaseline({ mean: 100, stddev: 10 });
179
+ mockCurrentValues([]);
180
+ mockEmptyBand();
181
+
182
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
183
+ { metricName: "cpu.usage" },
184
+ ctx,
185
+ );
186
+
187
+ expect(result.dataForLlm).toContain("STOPPED reporting");
188
+ expect(result.dataForLlm).not.toContain("normal — within");
189
+ });
190
+
191
+ test("zero-stddev baseline refuses to classify, matching the anomaly monitors", async () => {
192
+ mockBaseline({ mean: 100, stddev: 0 });
193
+ mockCurrentValues([101]);
194
+ mockEmptyBand();
195
+
196
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
197
+ { metricName: "cpu.usage" },
198
+ ctx,
199
+ );
200
+
201
+ expect(result.dataForLlm).toContain("cannot judge");
202
+ expect(result.dataForLlm).toContain("zero variance");
203
+ expect(result.dataForLlm).not.toContain("ANOMALOUS");
204
+ });
205
+
206
+ test("a label-scoped user without entityId is refused (baseline SQL skips the owned-scope filter)", async () => {
207
+ jest
208
+ .spyOn(ModelPermission, "getAccessibleServiceIdsForAnalyticsModel")
209
+ .mockResolvedValue([ObjectID.generate()]);
210
+ const getBaseline: jest.SpyInstance = jest.spyOn(
211
+ MetricBaselineService,
212
+ "getBaseline",
213
+ );
214
+
215
+ await expect(
216
+ BaselineAnomalyTool.execute({ metricName: "cpu.usage" }, ctx),
217
+ ).rejects.toThrow("scoped to specific services");
218
+ expect(getBaseline).not.toHaveBeenCalled();
219
+ });
220
+
221
+ test("a label-scoped user asking about another service's entity is refused", async () => {
222
+ jest
223
+ .spyOn(ModelPermission, "getAccessibleServiceIdsForAnalyticsModel")
224
+ .mockResolvedValue([ObjectID.generate()]);
225
+
226
+ await expect(
227
+ BaselineAnomalyTool.execute(
228
+ {
229
+ metricName: "cpu.usage",
230
+ entityId: ObjectID.generate().toString(),
231
+ },
232
+ ctx,
233
+ ),
234
+ ).rejects.toThrow("scoped to specific services");
235
+ });
236
+
237
+ test("a label-scoped user pinned to their own service proceeds", async () => {
238
+ const ownServiceId: ObjectID = ObjectID.generate();
239
+ jest
240
+ .spyOn(ModelPermission, "getAccessibleServiceIdsForAnalyticsModel")
241
+ .mockResolvedValue([ownServiceId]);
242
+ mockBaseline({ mean: 100, stddev: 10 });
243
+ mockCurrentValues([120]);
244
+ mockEmptyBand();
245
+
246
+ const result: { dataForLlm: string } = await BaselineAnomalyTool.execute(
247
+ { metricName: "cpu.usage", entityId: ownServiceId.toString() },
248
+ ctx,
249
+ );
250
+
251
+ expect(result.dataForLlm).toContain("normal — within the expected range");
252
+ });
253
+ });
@@ -384,3 +384,47 @@ describe("LLMService — prompt caching", () => {
384
384
  expect(response.usage!.totalTokens).toBe(55);
385
385
  });
386
386
  });
387
+
388
+ describe("LLMService — additionalParams (per-provider overrides)", () => {
389
+ test("merges provider additionalParams into the request body", async () => {
390
+ const spy: PostSpy = mockPostResponse({
391
+ choices: [{ message: { content: "OK" } }],
392
+ });
393
+
394
+ await LLMService.getCompletion({
395
+ llmProviderConfig: { llmType: LlmType.OpenAI, apiKey: "test-key" },
396
+ messages: [{ role: "user", content: "hi" }],
397
+ maxTokens: 1024,
398
+ additionalParams: { temperature: 0.2, top_p: 0.9 },
399
+ });
400
+
401
+ const requestBody: JSONObject = (
402
+ spy.mock.calls[0]![0] as { data: JSONObject }
403
+ ).data;
404
+ // additionalParams override defaults and are added to the body...
405
+ expect(requestBody["temperature"]).toBe(0.2);
406
+ expect(requestBody["top_p"]).toBe(0.9);
407
+ // ...while leaving the default max_tokens intact for legacy providers.
408
+ expect(requestBody["max_tokens"]).toBe(1024);
409
+ });
410
+
411
+ test("max_completion_tokens in additionalParams drops the legacy max_tokens", async () => {
412
+ const spy: PostSpy = mockPostResponse({
413
+ choices: [{ message: { content: "OK" } }],
414
+ });
415
+
416
+ await LLMService.getCompletion({
417
+ llmProviderConfig: { llmType: LlmType.OpenAI, apiKey: "test-key" },
418
+ messages: [{ role: "user", content: "hi" }],
419
+ maxTokens: 1024,
420
+ // gpt-5 / o1 / o3 reject max_tokens and require max_completion_tokens.
421
+ additionalParams: { max_completion_tokens: 2048 },
422
+ });
423
+
424
+ const requestBody: JSONObject = (
425
+ spy.mock.calls[0]![0] as { data: JSONObject }
426
+ ).data;
427
+ expect(requestBody["max_completion_tokens"]).toBe(2048);
428
+ expect(requestBody["max_tokens"]).toBeUndefined();
429
+ });
430
+ });