@oneuptime/common 11.4.1 → 11.5.0
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.
- package/Models/DatabaseModels/AIRun.ts +50 -0
- package/Models/DatabaseModels/AlertSeverity.ts +6 -0
- package/Models/DatabaseModels/IncidentSeverity.ts +6 -0
- package/Models/DatabaseModels/Index.ts +16 -0
- package/Models/DatabaseModels/IoTDeviceCredential.ts +471 -0
- package/Models/DatabaseModels/LlmLog.ts +56 -0
- package/Models/DatabaseModels/LlmProvider.ts +33 -0
- package/Models/DatabaseModels/NetworkDevice.ts +1385 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +717 -0
- package/Models/DatabaseModels/NetworkDeviceLabelRule.ts +514 -0
- package/Models/DatabaseModels/NetworkDeviceOwnerRule.ts +596 -0
- package/Models/DatabaseModels/NetworkDeviceOwnerTeam.ts +487 -0
- package/Models/DatabaseModels/NetworkDeviceOwnerUser.ts +486 -0
- package/Models/DatabaseModels/NetworkInterface.ts +563 -0
- package/Models/DatabaseModels/Project.ts +145 -0
- package/Models/DatabaseModels/TelemetryEntityRelationship.ts +54 -0
- package/Server/API/AIInvestigationAPI.ts +131 -63
- package/Server/API/AlertAPI.ts +5 -0
- package/Server/API/IncidentAPI.ts +10 -0
- package/Server/API/IncidentEpisodeAPI.ts +5 -0
- package/Server/API/LlmProviderAPI.ts +4 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +5 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783650000000-AddAdditionalParamsToLlmProvider.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783695782697-AddCacheTokenColumnsToLlmLog.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783701585317-AddAlertInvestigationGating.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783702431535-AddAiDailyAutonomousTokenLimitToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783720000000-AddNetworkDeviceTables.ts +105 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783721121260-AddAttemptCountToAIRun.ts +15 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783730000000-AddNetworkDeviceOwnersAndRules.ts +192 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783740000000-AddNetworkDeviceDiscoveryScan.ts +37 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783750000000-AddLldpNeighborsToNetworkDevice.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783760576655-AddInvestigationTuningToProject.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783762505482-AddTelemetryEntityRelationshipMetrics.ts +31 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783780000000-AddIoTDeviceCredentialTable.ts +79 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783790000000-AddSnmpV3AuthColumnsToNetworkDevice.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +24 -0
- package/Server/Services/AIRunService.ts +58 -0
- package/Server/Services/AIService.ts +101 -0
- package/Server/Services/IncidentService.ts +14 -41
- package/Server/Services/Index.ts +14 -0
- package/Server/Services/IoTDeviceCredentialService.ts +351 -0
- package/Server/Services/IoTDeviceService.ts +64 -21
- package/Server/Services/LlmLogService.ts +26 -0
- package/Server/Services/LlmProviderService.ts +3 -0
- package/Server/Services/MonitorService.ts +4 -1
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +10 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +204 -0
- package/Server/Services/NetworkDeviceLabelRuleService.ts +14 -0
- package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +222 -0
- package/Server/Services/NetworkDeviceOwnerRuleService.ts +14 -0
- package/Server/Services/NetworkDeviceOwnerTeamService.ts +10 -0
- package/Server/Services/NetworkDeviceOwnerUserService.ts +10 -0
- package/Server/Services/NetworkDeviceService.ts +50 -0
- package/Server/Services/NetworkInterfaceService.ts +10 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +23 -0
- package/Server/Services/TelemetryEntityService.ts +72 -2
- package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +244 -12
- package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +92 -19
- package/Server/Utils/AI/Sentinel/InvestigationQueue.ts +493 -0
- package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +88 -68
- package/Server/Utils/AI/Toolbox/Index.ts +2 -1
- package/Server/Utils/AI/Toolbox/MetricTools.ts +391 -0
- package/Server/Utils/LLM/LLMService.ts +21 -0
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +170 -2
- package/Server/Utils/Monitor/IoTDeviceAbsenceSeries.ts +101 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +15 -3
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +1 -1
- package/Server/Utils/Monitor/MonitorIncident.ts +19 -3
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +87 -0
- package/Server/Utils/Monitor/MonitorResource.ts +77 -21
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -1
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +201 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +215 -0
- package/Server/Utils/Monitor/SnmpInterfaceRateUtil.ts +169 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +173 -0
- package/Tests/Server/Services/IncidentInternalNoteAnnouncement.test.ts +97 -0
- package/Tests/Server/Services/IoTDeviceService.test.ts +54 -0
- package/Tests/Server/Utils/AI/BaselineAnomalyTool.test.ts +253 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +44 -0
- package/Tests/Server/Utils/AI/SentinelAlertGating.test.ts +360 -0
- package/Tests/Server/Utils/AI/SentinelInvestigationQueue.test.ts +295 -0
- package/Tests/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.test.ts +59 -0
- package/Tests/Server/Utils/Monitor/IoTDeviceAbsenceSeries.test.ts +113 -0
- package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +198 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +1 -1
- package/Tests/Utils/Monitor/LatencyMatrixUtil.test.ts +111 -0
- package/Types/AI/AIRunStatus.ts +9 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +0 -1
- package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +8 -3
- package/Types/LlmLogStatus.ts +1 -0
- package/Types/Monitor/CriteriaFilter.ts +5 -0
- package/Types/Monitor/IotAlertTemplates.ts +22 -3
- package/Types/Monitor/LatencyMatrix.ts +27 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +2 -2
- package/Types/Monitor/MonitorMetricType.ts +10 -0
- package/Types/Monitor/MonitorStep.ts +36 -11
- package/Types/Monitor/MonitorStepNetworkDeviceMonitor.ts +55 -0
- package/Types/Monitor/MonitorStepSnmpMonitor.ts +8 -0
- package/Types/Monitor/MonitorType.ts +14 -9
- package/Types/Monitor/SnmpMonitor/LldpNeighbor.ts +12 -0
- package/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.ts +116 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +29 -0
- package/Types/Monitor/SnmpMonitor/SnmpInterface.ts +31 -0
- package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +24 -0
- package/Types/Monitor/SnmpMonitor/SnmpTrap.ts +20 -0
- package/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.ts +173 -0
- package/Types/Permission.ts +314 -0
- package/Types/Probe/ProbeMonitorResponse.ts +8 -0
- package/UI/Components/Icon/Icon.tsx +8 -6
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +2 -2
- package/UI/Components/Tabs/Tabs.tsx +10 -1
- package/UI/Utils/Navigation.ts +10 -1
- package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +16 -21
- package/Utils/Monitor/LatencyMatrixUtil.ts +162 -0
- package/Utils/Monitor/MonitorMetricType.ts +71 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +159 -0
- package/Utils/Telemetry/EntityRelationship.ts +11 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertSeverity.js +6 -0
- package/build/dist/Models/DatabaseModels/AlertSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js +6 -0
- package/build/dist/Models/DatabaseModels/IncidentSeverity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +16 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IoTDeviceCredential.js +493 -0
- package/build/dist/Models/DatabaseModels/IoTDeviceCredential.js.map +1 -0
- package/build/dist/Models/DatabaseModels/LlmLog.js +58 -0
- package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LlmProvider.js +33 -0
- package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +1426 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +738 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkInterface.js +589 -0
- package/build/dist/Models/DatabaseModels/NetworkInterface.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Project.js +147 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js +57 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +95 -55
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AlertAPI.js +5 -0
- package/build/dist/Server/API/AlertAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +10 -0
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/IncidentEpisodeAPI.js +5 -0
- package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +5 -7
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +5 -0
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783650000000-AddAdditionalParamsToLlmProvider.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783650000000-AddAdditionalParamsToLlmProvider.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783695782697-AddCacheTokenColumnsToLlmLog.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783695782697-AddCacheTokenColumnsToLlmLog.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783701585317-AddAlertInvestigationGating.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783701585317-AddAlertInvestigationGating.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783702431535-AddAiDailyAutonomousTokenLimitToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783702431535-AddAiDailyAutonomousTokenLimitToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783720000000-AddNetworkDeviceTables.js +51 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783720000000-AddNetworkDeviceTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783721121260-AddAttemptCountToAIRun.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783721121260-AddAttemptCountToAIRun.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783730000000-AddNetworkDeviceOwnersAndRules.js +101 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783730000000-AddNetworkDeviceOwnersAndRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783740000000-AddNetworkDeviceDiscoveryScan.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783740000000-AddNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783750000000-AddLldpNeighborsToNetworkDevice.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783750000000-AddLldpNeighborsToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783760576655-AddInvestigationTuningToProject.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783760576655-AddInvestigationTuningToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783762505482-AddTelemetryEntityRelationshipMetrics.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783762505482-AddTelemetryEntityRelationshipMetrics.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783780000000-AddIoTDeviceCredentialTable.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783780000000-AddIoTDeviceCredentialTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783790000000-AddSnmpV3AuthColumnsToNetworkDevice.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783790000000-AddSnmpV3AuthColumnsToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +33 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +82 -9
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +14 -25
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +14 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +321 -0
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -0
- package/build/dist/Server/Services/IoTDeviceService.js +49 -15
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +29 -0
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +3 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +2 -1
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +166 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js +13 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +186 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerTeamService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerUserService.js +9 -0
- package/build/dist/Server/Services/NetworkDeviceOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceService.js +52 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -0
- package/build/dist/Server/Services/NetworkInterfaceService.js +9 -0
- package/build/dist/Server/Services/NetworkInterfaceService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +22 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +64 -2
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +189 -9
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +84 -17
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/InvestigationQueue.js +428 -0
- package/build/dist/Server/Utils/AI/Sentinel/InvestigationQueue.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +63 -47
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +2 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js +300 -0
- package/build/dist/Server/Utils/AI/Toolbox/MetricTools.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +16 -0
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +130 -2
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/IoTDeviceAbsenceSeries.js +81 -0
- package/build/dist/Server/Utils/Monitor/IoTDeviceAbsenceSeries.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +18 -7
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +25 -10
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +69 -3
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +76 -30
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +147 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +167 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/SnmpInterfaceRateUtil.js +93 -0
- package/build/dist/Server/Utils/Monitor/SnmpInterfaceRateUtil.js.map +1 -0
- package/build/dist/Types/AI/AIRunStatus.js +9 -0
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +0 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/LlmLogStatus.js +1 -0
- package/build/dist/Types/LlmLogStatus.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +5 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/IotAlertTemplates.js +7 -7
- package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/LatencyMatrix.js +2 -0
- package/build/dist/Types/Monitor/LatencyMatrix.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +2 -2
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorMetricType.js +9 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +22 -9
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepNetworkDeviceMonitor.js +36 -0
- package/build/dist/Types/Monitor/MonitorStepNetworkDeviceMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js +3 -0
- package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +14 -9
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/LldpNeighbor.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/LldpNeighbor.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js +89 -0
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkDeviceAlertPack.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkTopology.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/NetworkTopology.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpInterface.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpInterface.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpTrap.js +2 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpTrap.js.map +1 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js +137 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVendorTemplate.js.map +1 -0
- package/build/dist/Types/Permission.js +280 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +8 -6
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +2 -2
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/Tabs/Tabs.js +3 -1
- package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +11 -1
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +17 -20
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -1
- package/build/dist/Utils/Monitor/LatencyMatrixUtil.js +89 -0
- package/build/dist/Utils/Monitor/LatencyMatrixUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js +68 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +112 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import SentinelAlertInvestigationRunner, {
|
|
2
|
+
AlertGateDecision,
|
|
3
|
+
} from "../../../../Server/Utils/AI/Sentinel/AlertInvestigationRunner";
|
|
4
|
+
import SentinelInvestigationQueue from "../../../../Server/Utils/AI/Sentinel/InvestigationQueue";
|
|
5
|
+
import AlertService from "../../../../Server/Services/AlertService";
|
|
6
|
+
import AlertSeverityService from "../../../../Server/Services/AlertSeverityService";
|
|
7
|
+
import ProjectService from "../../../../Server/Services/ProjectService";
|
|
8
|
+
import AIRunService from "../../../../Server/Services/AIRunService";
|
|
9
|
+
import Alert from "../../../../Models/DatabaseModels/Alert";
|
|
10
|
+
import AlertSeverity from "../../../../Models/DatabaseModels/AlertSeverity";
|
|
11
|
+
import Project from "../../../../Models/DatabaseModels/Project";
|
|
12
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
13
|
+
import PositiveNumber from "../../../../Types/PositiveNumber";
|
|
14
|
+
import { describe, expect, test, afterEach } from "@jest/globals";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Cost gates for autonomous alert investigations (Phase 1 / G4):
|
|
18
|
+
* - severity floor: explicit per-project minimum severity, defaulting to the
|
|
19
|
+
* project's top two tiers (lowest two `order` values; lower order = higher
|
|
20
|
+
* severity);
|
|
21
|
+
* - per-monitor dedupe window: a monitor already investigated within the
|
|
22
|
+
* window is not re-investigated;
|
|
23
|
+
* - per-project concurrency cap in the shared engine.
|
|
24
|
+
*
|
|
25
|
+
* These tests mock the persistence layer (no Postgres) to lock in the gate
|
|
26
|
+
* decisions and their fail directions: unknown severity PASSES the severity
|
|
27
|
+
* gate (it only filters known-low-severity noise), while a failed cap check
|
|
28
|
+
* SKIPS (a cost gate fails cheap).
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
function fakeAlert(data: {
|
|
32
|
+
monitorId?: ObjectID | undefined;
|
|
33
|
+
severityOrder?: number | undefined;
|
|
34
|
+
}): Alert {
|
|
35
|
+
return {
|
|
36
|
+
id: ObjectID.generate(),
|
|
37
|
+
monitorId: data.monitorId,
|
|
38
|
+
alertSeverity:
|
|
39
|
+
data.severityOrder !== undefined
|
|
40
|
+
? ({ order: data.severityOrder } as AlertSeverity)
|
|
41
|
+
: undefined,
|
|
42
|
+
} as unknown as Alert;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function fakeProject(
|
|
46
|
+
minimumSeverityId?: ObjectID | undefined,
|
|
47
|
+
dedupeWindowMinutes?: number | undefined,
|
|
48
|
+
): Project {
|
|
49
|
+
return {
|
|
50
|
+
id: ObjectID.generate(),
|
|
51
|
+
alertInvestigationMinimumSeverityId: minimumSeverityId,
|
|
52
|
+
alertInvestigationDedupeWindowMinutes: dedupeWindowMinutes,
|
|
53
|
+
} as unknown as Project;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function fakeSeverities(orders: Array<number>): Array<AlertSeverity> {
|
|
57
|
+
return orders.map((order: number) => {
|
|
58
|
+
return { order } as AlertSeverity;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("SentinelAlertInvestigationRunner.shouldInvestigateAlert", () => {
|
|
63
|
+
const alertId: ObjectID = ObjectID.generate();
|
|
64
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
jest.restoreAllMocks();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
function mockGates(data: {
|
|
71
|
+
alert: Alert | null;
|
|
72
|
+
project?: Project;
|
|
73
|
+
explicitFloorSeverity?: AlertSeverity | null;
|
|
74
|
+
topTiers?: Array<AlertSeverity>;
|
|
75
|
+
recentRunCount?: number;
|
|
76
|
+
}): void {
|
|
77
|
+
jest.spyOn(AlertService, "findOneById").mockResolvedValue(data.alert);
|
|
78
|
+
jest
|
|
79
|
+
.spyOn(ProjectService, "findOneById")
|
|
80
|
+
.mockResolvedValue(data.project || fakeProject());
|
|
81
|
+
jest
|
|
82
|
+
.spyOn(AlertSeverityService, "findOneById")
|
|
83
|
+
.mockResolvedValue(data.explicitFloorSeverity ?? null);
|
|
84
|
+
jest
|
|
85
|
+
.spyOn(AlertSeverityService, "findBy")
|
|
86
|
+
.mockResolvedValue(data.topTiers || []);
|
|
87
|
+
jest
|
|
88
|
+
.spyOn(AIRunService, "countBy")
|
|
89
|
+
.mockResolvedValue(new PositiveNumber(data.recentRunCount || 0));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
test("skips when severity is below an explicitly configured floor", async () => {
|
|
93
|
+
const floorSeverityId: ObjectID = ObjectID.generate();
|
|
94
|
+
mockGates({
|
|
95
|
+
alert: fakeAlert({ severityOrder: 3 }),
|
|
96
|
+
project: fakeProject(floorSeverityId),
|
|
97
|
+
explicitFloorSeverity: { order: 2 } as AlertSeverity,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const decision: AlertGateDecision =
|
|
101
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
102
|
+
alertId,
|
|
103
|
+
projectId,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(decision.investigate).toBe(false);
|
|
107
|
+
expect(decision.reason).toContain("below the investigation floor");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("investigates when severity meets the explicit floor and no recent run exists", async () => {
|
|
111
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
112
|
+
const floorSeverityId: ObjectID = ObjectID.generate();
|
|
113
|
+
mockGates({
|
|
114
|
+
alert: fakeAlert({ severityOrder: 2, monitorId }),
|
|
115
|
+
project: fakeProject(floorSeverityId),
|
|
116
|
+
explicitFloorSeverity: { order: 2 } as AlertSeverity,
|
|
117
|
+
recentRunCount: 0,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const decision: AlertGateDecision =
|
|
121
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
122
|
+
alertId,
|
|
123
|
+
projectId,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(decision.investigate).toBe(true);
|
|
127
|
+
expect(decision.monitorId).toBe(monitorId);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("default floor is the top two tiers: third tier is skipped", async () => {
|
|
131
|
+
mockGates({
|
|
132
|
+
alert: fakeAlert({ severityOrder: 3 }),
|
|
133
|
+
topTiers: fakeSeverities([1, 2]),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const decision: AlertGateDecision =
|
|
137
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
138
|
+
alertId,
|
|
139
|
+
projectId,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(decision.investigate).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("default floor: second tier is investigated", async () => {
|
|
146
|
+
mockGates({
|
|
147
|
+
alert: fakeAlert({ severityOrder: 2 }),
|
|
148
|
+
topTiers: fakeSeverities([1, 2]),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const decision: AlertGateDecision =
|
|
152
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
153
|
+
alertId,
|
|
154
|
+
projectId,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(decision.investigate).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("a deleted explicit floor severity falls back to the top-two default", async () => {
|
|
161
|
+
// Project points at a severity that no longer exists (findOneById → null).
|
|
162
|
+
mockGates({
|
|
163
|
+
alert: fakeAlert({ severityOrder: 3 }),
|
|
164
|
+
project: fakeProject(ObjectID.generate()),
|
|
165
|
+
explicitFloorSeverity: null,
|
|
166
|
+
topTiers: fakeSeverities([1, 2]),
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const decision: AlertGateDecision =
|
|
170
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
171
|
+
alertId,
|
|
172
|
+
projectId,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(decision.investigate).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("unknown alert severity passes the severity gate", async () => {
|
|
179
|
+
mockGates({
|
|
180
|
+
alert: fakeAlert({ severityOrder: undefined }),
|
|
181
|
+
topTiers: fakeSeverities([1, 2]),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const decision: AlertGateDecision =
|
|
185
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
186
|
+
alertId,
|
|
187
|
+
projectId,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(decision.investigate).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("skips when the monitor was already investigated within the window", async () => {
|
|
194
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
195
|
+
const countBy: jest.SpyInstance = jest.spyOn(AIRunService, "countBy");
|
|
196
|
+
|
|
197
|
+
mockGates({
|
|
198
|
+
alert: fakeAlert({ severityOrder: 1, monitorId }),
|
|
199
|
+
topTiers: fakeSeverities([1, 2]),
|
|
200
|
+
recentRunCount: 1,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const decision: AlertGateDecision =
|
|
204
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
205
|
+
alertId,
|
|
206
|
+
projectId,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(decision.investigate).toBe(false);
|
|
210
|
+
expect(decision.reason).toContain("already investigated");
|
|
211
|
+
expect(countBy).toHaveBeenCalledWith(
|
|
212
|
+
expect.objectContaining({
|
|
213
|
+
query: expect.objectContaining({
|
|
214
|
+
monitorId,
|
|
215
|
+
}),
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("alerts without a monitor skip the dedupe check entirely", async () => {
|
|
221
|
+
const countBy: jest.SpyInstance = jest.spyOn(AIRunService, "countBy");
|
|
222
|
+
|
|
223
|
+
mockGates({
|
|
224
|
+
alert: fakeAlert({ severityOrder: 1 }),
|
|
225
|
+
topTiers: fakeSeverities([1, 2]),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const decision: AlertGateDecision =
|
|
229
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
230
|
+
alertId,
|
|
231
|
+
projectId,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(decision.investigate).toBe(true);
|
|
235
|
+
expect(countBy).not.toHaveBeenCalled();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("a cooldown of 0 disables the dedupe check entirely", async () => {
|
|
239
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
240
|
+
const countBy: jest.SpyInstance = jest.spyOn(AIRunService, "countBy");
|
|
241
|
+
|
|
242
|
+
mockGates({
|
|
243
|
+
alert: fakeAlert({ severityOrder: 1, monitorId }),
|
|
244
|
+
project: fakeProject(undefined, 0),
|
|
245
|
+
topTiers: fakeSeverities([1, 2]),
|
|
246
|
+
recentRunCount: 1,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const decision: AlertGateDecision =
|
|
250
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
251
|
+
alertId,
|
|
252
|
+
projectId,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(decision.investigate).toBe(true);
|
|
256
|
+
expect(countBy).not.toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("a custom cooldown is used for the dedupe window", async () => {
|
|
260
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
261
|
+
|
|
262
|
+
mockGates({
|
|
263
|
+
alert: fakeAlert({ severityOrder: 1, monitorId }),
|
|
264
|
+
project: fakeProject(undefined, 45),
|
|
265
|
+
topTiers: fakeSeverities([1, 2]),
|
|
266
|
+
recentRunCount: 1,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const decision: AlertGateDecision =
|
|
270
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
271
|
+
alertId,
|
|
272
|
+
projectId,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(decision.investigate).toBe(false);
|
|
276
|
+
expect(decision.reason).toContain("within the last 45 minutes");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("skips when the alert cannot be found", async () => {
|
|
280
|
+
mockGates({ alert: null });
|
|
281
|
+
|
|
282
|
+
const decision: AlertGateDecision =
|
|
283
|
+
await SentinelAlertInvestigationRunner.shouldInvestigateAlert({
|
|
284
|
+
alertId,
|
|
285
|
+
projectId,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(decision.investigate).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("SentinelInvestigationQueue concurrency cap", () => {
|
|
293
|
+
beforeEach(() => {
|
|
294
|
+
// No per-project override => the default cap of 3 applies.
|
|
295
|
+
jest.spyOn(ProjectService, "findOneById").mockResolvedValue(fakeProject());
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
afterEach(() => {
|
|
299
|
+
jest.restoreAllMocks();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("leaves the run queued when the per-project cap is reached", async () => {
|
|
303
|
+
jest
|
|
304
|
+
.spyOn(AIRunService, "countBy")
|
|
305
|
+
.mockResolvedValue(new PositiveNumber(3));
|
|
306
|
+
const claim: jest.SpyInstance = jest.spyOn(
|
|
307
|
+
AIRunService,
|
|
308
|
+
"attemptStatusTransition",
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
await SentinelInvestigationQueue.processRun({
|
|
312
|
+
id: ObjectID.generate(),
|
|
313
|
+
projectId: ObjectID.generate(),
|
|
314
|
+
attemptCount: 0,
|
|
315
|
+
triggeredByAlertId: ObjectID.generate(),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
expect(claim).not.toHaveBeenCalled();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("leaves the run queued when the cap check itself fails", async () => {
|
|
322
|
+
jest.spyOn(AIRunService, "countBy").mockRejectedValue(new Error("db down"));
|
|
323
|
+
const claim: jest.SpyInstance = jest.spyOn(
|
|
324
|
+
AIRunService,
|
|
325
|
+
"attemptStatusTransition",
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
await SentinelInvestigationQueue.processRun({
|
|
329
|
+
id: ObjectID.generate(),
|
|
330
|
+
projectId: ObjectID.generate(),
|
|
331
|
+
attemptCount: 0,
|
|
332
|
+
triggeredByAlertId: ObjectID.generate(),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(claim).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("a per-project cap override of 1 blocks the second concurrent run", async () => {
|
|
339
|
+
jest.spyOn(ProjectService, "findOneById").mockResolvedValue({
|
|
340
|
+
id: ObjectID.generate(),
|
|
341
|
+
aiMaxConcurrentInvestigations: 1,
|
|
342
|
+
} as unknown as Project);
|
|
343
|
+
jest
|
|
344
|
+
.spyOn(AIRunService, "countBy")
|
|
345
|
+
.mockResolvedValue(new PositiveNumber(1));
|
|
346
|
+
const claim: jest.SpyInstance = jest.spyOn(
|
|
347
|
+
AIRunService,
|
|
348
|
+
"attemptStatusTransition",
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
await SentinelInvestigationQueue.processRun({
|
|
352
|
+
id: ObjectID.generate(),
|
|
353
|
+
projectId: ObjectID.generate(),
|
|
354
|
+
attemptCount: 0,
|
|
355
|
+
triggeredByAlertId: ObjectID.generate(),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(claim).not.toHaveBeenCalled();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import SentinelInvestigationQueue, {
|
|
2
|
+
MAX_INVESTIGATION_ATTEMPTS,
|
|
3
|
+
} from "../../../../Server/Utils/AI/Sentinel/InvestigationQueue";
|
|
4
|
+
import SentinelIncidentInvestigationRunner from "../../../../Server/Utils/AI/Sentinel/IncidentInvestigationRunner";
|
|
5
|
+
import SentinelAlertInvestigationRunner from "../../../../Server/Utils/AI/Sentinel/AlertInvestigationRunner";
|
|
6
|
+
import AIRunService from "../../../../Server/Services/AIRunService";
|
|
7
|
+
import AIService from "../../../../Server/Services/AIService";
|
|
8
|
+
import ProjectService from "../../../../Server/Services/ProjectService";
|
|
9
|
+
import Project from "../../../../Models/DatabaseModels/Project";
|
|
10
|
+
import AIRun from "../../../../Models/DatabaseModels/AIRun";
|
|
11
|
+
import AIRunStatus from "../../../../Types/AI/AIRunStatus";
|
|
12
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
13
|
+
import PositiveNumber from "../../../../Types/PositiveNumber";
|
|
14
|
+
import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The durable investigation queue (Phase 2's first item — replaces detached
|
|
18
|
+
* fire-and-forget investigations that a pod restart could orphan, D2).
|
|
19
|
+
*
|
|
20
|
+
* The invariants these tests lock in:
|
|
21
|
+
* (a) enqueue records a Queued AIRun (the durable intent) before any
|
|
22
|
+
* expensive work, and kicks inline processing;
|
|
23
|
+
* (b) the claim is a status-guarded CAS: a lost claim never executes,
|
|
24
|
+
* a won claim increments attemptCount and dispatches to the right
|
|
25
|
+
* subject runner;
|
|
26
|
+
* (c) the retry policy (G9): transient failures requeue while attempts
|
|
27
|
+
* remain; permanent failures and exhausted attempts finalize as
|
|
28
|
+
* Error — and the CAS guard means an already-Completed run is never
|
|
29
|
+
* clobbered;
|
|
30
|
+
* (d) heartbeat-stale runs requeue while attempts remain, else go Stale;
|
|
31
|
+
* (e) the poller expires runs that queued past their usefulness window.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
function mockBudgetOk(): void {
|
|
35
|
+
jest.spyOn(AIService, "getAutonomousDailyBudgetStatus").mockResolvedValue({
|
|
36
|
+
exhausted: false,
|
|
37
|
+
limitInTokens: null,
|
|
38
|
+
usedTokensToday: 0,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe("SentinelInvestigationQueue", () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
mockBudgetOk();
|
|
45
|
+
// No per-project cap override => default of 3.
|
|
46
|
+
jest
|
|
47
|
+
.spyOn(ProjectService, "findOneById")
|
|
48
|
+
.mockResolvedValue({ id: ObjectID.generate() } as unknown as Project);
|
|
49
|
+
// No investigations currently running (cap check passes).
|
|
50
|
+
jest
|
|
51
|
+
.spyOn(AIRunService, "countBy")
|
|
52
|
+
.mockResolvedValue(new PositiveNumber(0));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
jest.restoreAllMocks();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("enqueue records a Queued run with the subject and kicks processing", async () => {
|
|
60
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
61
|
+
const incidentId: ObjectID = ObjectID.generate();
|
|
62
|
+
const createdId: ObjectID = ObjectID.generate();
|
|
63
|
+
|
|
64
|
+
const create: jest.SpyInstance = jest
|
|
65
|
+
.spyOn(AIRunService, "create")
|
|
66
|
+
.mockResolvedValue({ id: createdId } as unknown as AIRun);
|
|
67
|
+
const processRun: jest.SpyInstance = jest
|
|
68
|
+
.spyOn(SentinelInvestigationQueue, "processRun")
|
|
69
|
+
.mockResolvedValue(undefined);
|
|
70
|
+
|
|
71
|
+
await SentinelInvestigationQueue.enqueue({
|
|
72
|
+
projectId,
|
|
73
|
+
subjectIncidentId: incidentId,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(create).toHaveBeenCalledWith(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
data: expect.objectContaining({
|
|
79
|
+
status: AIRunStatus.Queued,
|
|
80
|
+
triggeredByIncidentId: incidentId,
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
// The inline kick is detached; give the microtask a beat.
|
|
85
|
+
await new Promise((resolve: (value: unknown) => void) => {
|
|
86
|
+
setTimeout(resolve, 0);
|
|
87
|
+
});
|
|
88
|
+
expect(processRun).toHaveBeenCalledWith(
|
|
89
|
+
expect.objectContaining({
|
|
90
|
+
id: createdId,
|
|
91
|
+
attemptCount: 0,
|
|
92
|
+
triggeredByIncidentId: incidentId,
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("a lost claim never executes the investigation", async () => {
|
|
98
|
+
jest.spyOn(AIRunService, "attemptStatusTransition").mockResolvedValue(0);
|
|
99
|
+
const execute: jest.SpyInstance = jest.spyOn(
|
|
100
|
+
SentinelIncidentInvestigationRunner,
|
|
101
|
+
"executeInvestigation",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
await SentinelInvestigationQueue.processRun({
|
|
105
|
+
id: ObjectID.generate(),
|
|
106
|
+
projectId: ObjectID.generate(),
|
|
107
|
+
attemptCount: 0,
|
|
108
|
+
triggeredByIncidentId: ObjectID.generate(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(execute).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a won claim increments attemptCount and dispatches to the incident runner", async () => {
|
|
115
|
+
const runId: ObjectID = ObjectID.generate();
|
|
116
|
+
const incidentId: ObjectID = ObjectID.generate();
|
|
117
|
+
const claim: jest.SpyInstance = jest
|
|
118
|
+
.spyOn(AIRunService, "attemptStatusTransition")
|
|
119
|
+
.mockResolvedValue(1);
|
|
120
|
+
const execute: jest.SpyInstance = jest
|
|
121
|
+
.spyOn(SentinelIncidentInvestigationRunner, "executeInvestigation")
|
|
122
|
+
.mockResolvedValue(undefined);
|
|
123
|
+
|
|
124
|
+
await SentinelInvestigationQueue.processRun({
|
|
125
|
+
id: runId,
|
|
126
|
+
projectId: ObjectID.generate(),
|
|
127
|
+
attemptCount: 0,
|
|
128
|
+
triggeredByIncidentId: incidentId,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(claim).toHaveBeenCalledWith(
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
aiRunId: runId,
|
|
134
|
+
fromStatus: AIRunStatus.Queued,
|
|
135
|
+
// Guards against stale queue snapshots re-claiming or re-numbering.
|
|
136
|
+
expectedAttemptCount: 0,
|
|
137
|
+
set: expect.objectContaining({
|
|
138
|
+
status: AIRunStatus.Running,
|
|
139
|
+
attemptCount: 1,
|
|
140
|
+
}),
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
expect(execute).toHaveBeenCalledWith(
|
|
144
|
+
expect.objectContaining({
|
|
145
|
+
aiRunId: runId,
|
|
146
|
+
incidentId,
|
|
147
|
+
attemptCount: 1,
|
|
148
|
+
}),
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("a won claim dispatches alert runs to the alert runner", async () => {
|
|
153
|
+
const alertId: ObjectID = ObjectID.generate();
|
|
154
|
+
jest.spyOn(AIRunService, "attemptStatusTransition").mockResolvedValue(1);
|
|
155
|
+
const execute: jest.SpyInstance = jest
|
|
156
|
+
.spyOn(SentinelAlertInvestigationRunner, "executeInvestigation")
|
|
157
|
+
.mockResolvedValue(undefined);
|
|
158
|
+
|
|
159
|
+
await SentinelInvestigationQueue.processRun({
|
|
160
|
+
id: ObjectID.generate(),
|
|
161
|
+
projectId: ObjectID.generate(),
|
|
162
|
+
attemptCount: 0,
|
|
163
|
+
triggeredByAlertId: alertId,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(execute).toHaveBeenCalledWith(
|
|
167
|
+
expect.objectContaining({ alertId, attemptCount: 1 }),
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("a transient failure on the first attempt requeues the run", async () => {
|
|
172
|
+
const runId: ObjectID = ObjectID.generate();
|
|
173
|
+
const update: jest.SpyInstance = jest
|
|
174
|
+
.spyOn(AIRunService, "attemptStatusTransition")
|
|
175
|
+
.mockResolvedValue(1);
|
|
176
|
+
|
|
177
|
+
await SentinelInvestigationQueue.failOrRequeue({
|
|
178
|
+
aiRunId: runId,
|
|
179
|
+
attemptCount: 1,
|
|
180
|
+
errorMessage: "LLM provider timed out",
|
|
181
|
+
isPermanent: false,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(update).toHaveBeenCalledWith(
|
|
185
|
+
expect.objectContaining({
|
|
186
|
+
aiRunId: runId,
|
|
187
|
+
fromStatus: AIRunStatus.Running,
|
|
188
|
+
set: expect.objectContaining({
|
|
189
|
+
status: AIRunStatus.Queued,
|
|
190
|
+
}),
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("a transient failure on the final attempt finalizes as Error", async () => {
|
|
196
|
+
const update: jest.SpyInstance = jest
|
|
197
|
+
.spyOn(AIRunService, "attemptStatusTransition")
|
|
198
|
+
.mockResolvedValue(1);
|
|
199
|
+
|
|
200
|
+
await SentinelInvestigationQueue.failOrRequeue({
|
|
201
|
+
aiRunId: ObjectID.generate(),
|
|
202
|
+
attemptCount: MAX_INVESTIGATION_ATTEMPTS,
|
|
203
|
+
errorMessage: "LLM provider timed out",
|
|
204
|
+
isPermanent: false,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(update).toHaveBeenCalledWith(
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
set: expect.objectContaining({
|
|
210
|
+
status: AIRunStatus.Error,
|
|
211
|
+
}),
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("a permanent failure never retries, even with attempts remaining", async () => {
|
|
217
|
+
const update: jest.SpyInstance = jest
|
|
218
|
+
.spyOn(AIRunService, "attemptStatusTransition")
|
|
219
|
+
.mockResolvedValue(1);
|
|
220
|
+
|
|
221
|
+
await SentinelInvestigationQueue.failOrRequeue({
|
|
222
|
+
aiRunId: ObjectID.generate(),
|
|
223
|
+
attemptCount: 1,
|
|
224
|
+
errorMessage: "No LLM provider configured for this project.",
|
|
225
|
+
isPermanent: true,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(update).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(update).toHaveBeenCalledWith(
|
|
230
|
+
expect.objectContaining({
|
|
231
|
+
set: expect.objectContaining({
|
|
232
|
+
status: AIRunStatus.Error,
|
|
233
|
+
}),
|
|
234
|
+
}),
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("a heartbeat-stale run requeues while attempts remain", async () => {
|
|
239
|
+
jest.spyOn(AIRunService, "attemptStatusTransition").mockResolvedValue(1);
|
|
240
|
+
|
|
241
|
+
const outcome: "requeued" | "stale" =
|
|
242
|
+
await SentinelInvestigationQueue.requeueOrMarkStale({
|
|
243
|
+
id: ObjectID.generate(),
|
|
244
|
+
attemptCount: 1,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(outcome).toBe("requeued");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("a heartbeat-stale run out of attempts is marked Stale", async () => {
|
|
251
|
+
const update: jest.SpyInstance = jest
|
|
252
|
+
.spyOn(AIRunService, "attemptStatusTransition")
|
|
253
|
+
.mockResolvedValue(1);
|
|
254
|
+
|
|
255
|
+
const outcome: "requeued" | "stale" =
|
|
256
|
+
await SentinelInvestigationQueue.requeueOrMarkStale({
|
|
257
|
+
id: ObjectID.generate(),
|
|
258
|
+
attemptCount: MAX_INVESTIGATION_ATTEMPTS,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(outcome).toBe("stale");
|
|
262
|
+
expect(update).toHaveBeenCalledWith(
|
|
263
|
+
expect.objectContaining({
|
|
264
|
+
set: expect.objectContaining({
|
|
265
|
+
status: AIRunStatus.Stale,
|
|
266
|
+
}),
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("the poller expires runs queued past the usefulness window", async () => {
|
|
272
|
+
const expiredId: ObjectID = ObjectID.generate();
|
|
273
|
+
const findBy: jest.SpyInstance = jest
|
|
274
|
+
.spyOn(AIRunService, "findBy")
|
|
275
|
+
// First call: expired runs. Second call: nothing left to drain.
|
|
276
|
+
.mockResolvedValueOnce([{ id: expiredId } as unknown as AIRun])
|
|
277
|
+
.mockResolvedValueOnce([]);
|
|
278
|
+
const update: jest.SpyInstance = jest
|
|
279
|
+
.spyOn(AIRunService, "attemptStatusTransition")
|
|
280
|
+
.mockResolvedValue(1);
|
|
281
|
+
|
|
282
|
+
await SentinelInvestigationQueue.processQueuedRuns();
|
|
283
|
+
|
|
284
|
+
expect(findBy).toHaveBeenCalledTimes(2);
|
|
285
|
+
expect(update).toHaveBeenCalledWith(
|
|
286
|
+
expect.objectContaining({
|
|
287
|
+
aiRunId: expiredId,
|
|
288
|
+
fromStatus: AIRunStatus.Queued,
|
|
289
|
+
set: expect.objectContaining({
|
|
290
|
+
status: AIRunStatus.Cancelled,
|
|
291
|
+
}),
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
});
|