@oneuptime/common 14.0.9 → 14.0.10
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/AutoRemediationSuggestion.ts +60 -0
- package/Models/DatabaseModels/CephCluster.ts +213 -0
- package/Models/DatabaseModels/DatabaseServer.ts +213 -0
- package/Models/DatabaseModels/DockerHost.ts +213 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
- package/Models/DatabaseModels/GlobalConfig.ts +1 -1
- package/Models/DatabaseModels/Host.ts +213 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/PodmanHost.ts +213 -0
- package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
- package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
- package/Models/DatabaseModels/RunnerJob.ts +144 -0
- package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
- package/Server/API/AutoRemediationAPI.ts +392 -1
- package/Server/API/ResourceAiAccessAPI.ts +1669 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Semaphore.ts +22 -0
- package/Server/Middleware/TelemetryIngest.ts +15 -5
- package/Server/Services/AnalyticsDatabaseService.ts +45 -1
- package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
- package/Server/Services/CephClusterService.ts +109 -1
- package/Server/Services/DatabaseServerService.ts +116 -1
- package/Server/Services/DockerHostService.ts +109 -1
- package/Server/Services/DockerSwarmClusterService.ts +113 -1
- package/Server/Services/HostService.ts +126 -4
- package/Server/Services/MetricRecordingRuleService.ts +47 -0
- package/Server/Services/PodmanHostService.ts +109 -1
- package/Server/Services/ProxmoxClusterService.ts +110 -1
- package/Server/Services/ResourceAiAccessService.ts +1439 -0
- package/Server/Services/ResourceAiAgentJobService.ts +202 -0
- package/Server/Services/ResourceAiAgentService.ts +2432 -0
- package/Server/Services/RunnerJobService.ts +611 -4
- package/Server/Services/TelemetryUsageBillingService.ts +28 -0
- package/Server/Services/TraceRecordingRuleService.ts +33 -0
- package/Server/Services/VMwareVCenterService.ts +110 -1
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1434 -32
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +935 -21
- package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
- package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
- package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
- package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +72 -2
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
- package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
- package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +366 -143
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +93 -0
- package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
- package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
- package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
- package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
- package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
- package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
- package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
- package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
- package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
- package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
- package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
- package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
- package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
- package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
- package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
- package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
- package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
- package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
- package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
- package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
- package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
- package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
- package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
- package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
- package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
- package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
- package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
- package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
- package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
- package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
- package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
- package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
- package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
- package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
- package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
- package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
- package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
- package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
- package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
- package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
- package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
- package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
- package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
- package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
- package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
- package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
- package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
- package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
- package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
- package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
- package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
- package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
- package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
- package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
- package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
- package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
- package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
- package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
- package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
- package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
- package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
- package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
- package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
- package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
- package/Types/AI/ResourceAiAccessApi.ts +140 -0
- package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
- package/Types/Monitor/RumAlertTemplates.ts +351 -4
- package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
- package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
- package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
- package/Types/Runbook/RunbookStepType.ts +29 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
- package/Utils/AI/InvestigationReport.ts +121 -15
- package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
- package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
- package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
- package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
- package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
- package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
- package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
- package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
- package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
- package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
- package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
- package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
- package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
- package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
- package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +219 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
- package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
- package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +104 -0
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseServerService.js +99 -0
- package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +104 -0
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +114 -2
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
- package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +104 -0
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
- package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
- package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
- package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
- package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
- package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
- package/build/dist/Server/Services/RunnerJobService.js +344 -5
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
- package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
- package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
- package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1029 -30
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +633 -28
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
- package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +50 -4
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +263 -110
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
- package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
- package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
- package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
- package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
- package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
- package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
- package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
- package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/Utils/AI/InvestigationReport.js +71 -11
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
- package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
- package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
- package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
- package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
- package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
- package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
- package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
- package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
- package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
- package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
import RunnerJobService, {
|
|
2
|
+
MAX_AI_INVESTIGATION_RESOURCE_COMMAND_JOBS_PER_PROJECT_PER_HOUR,
|
|
3
|
+
MAX_AI_RESOURCE_COMMAND_JOBS_PER_PROJECT_PER_HOUR,
|
|
4
|
+
ResourceCommandEnqueueRefusalCode,
|
|
5
|
+
ResourceCommandEnqueueRefusedException,
|
|
6
|
+
Service as RunnerJobServiceClass,
|
|
7
|
+
} from "../../../Server/Services/RunnerJobService";
|
|
8
|
+
import AIRunService from "../../../Server/Services/AIRunService";
|
|
9
|
+
import DatabaseServerService from "../../../Server/Services/DatabaseServerService";
|
|
10
|
+
import DockerHostService from "../../../Server/Services/DockerHostService";
|
|
11
|
+
import HostService from "../../../Server/Services/HostService";
|
|
12
|
+
import ResourceAiAgentService from "../../../Server/Services/ResourceAiAgentService";
|
|
13
|
+
import { ResourceAiAccessRow } from "../../../Server/Services/ResourceAiAccessService";
|
|
14
|
+
import logger from "../../../Server/Utils/Logger";
|
|
15
|
+
import AIRun from "../../../Models/DatabaseModels/AIRun";
|
|
16
|
+
import ResourceAiAgent from "../../../Models/DatabaseModels/ResourceAiAgent";
|
|
17
|
+
import RunnerJob from "../../../Models/DatabaseModels/RunnerJob";
|
|
18
|
+
import AIRunType from "../../../Types/AI/AIRunType";
|
|
19
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
20
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
21
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
22
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
23
|
+
import AiResourceType from "../../../Types/ResourceAiAgent/AiResourceType";
|
|
24
|
+
import {
|
|
25
|
+
ResourceAiRemediationMode,
|
|
26
|
+
ResourceCommandJobPayload,
|
|
27
|
+
ResourceCommandTier,
|
|
28
|
+
} from "../../../Types/ResourceAiAgent/ResourceAiAccess";
|
|
29
|
+
import RunbookStepType from "../../../Types/Runbook/RunbookStepType";
|
|
30
|
+
import RunnerJobOrigin from "../../../Types/Runbook/RunnerJobOrigin";
|
|
31
|
+
import RunnerJobStatus from "../../../Types/Runbook/RunnerJobStatus";
|
|
32
|
+
import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Contract under test — RunnerJobService.enqueueAiResourceCommand, the
|
|
36
|
+
* server-side chokepoint between an LLM's output and an infrastructure
|
|
37
|
+
* resource (a Docker host, a database server, a host, ...), mirroring
|
|
38
|
+
* enqueueAiKubectlCommand's order:
|
|
39
|
+
*
|
|
40
|
+
* 1. the call itself (type, id, origin, suggestion, run, access test);
|
|
41
|
+
* 2. the resource command policy, re-run: Denied never enqueues and an
|
|
42
|
+
* investigation must be Read — whatever the tool layer decided;
|
|
43
|
+
* 3. the hourly brake, counted on ResourceCommand rows only (240
|
|
44
|
+
* investigation / 30 remediation per project);
|
|
45
|
+
* 4. the resource must be this project's;
|
|
46
|
+
* 5. the target is the resource's ONLINE agent right now (and the one the
|
|
47
|
+
* caller named, if any);
|
|
48
|
+
* 6. the switch the job needs (investigation, or fixes) is still on;
|
|
49
|
+
* 7. no credential ever travels;
|
|
50
|
+
* 8. the agent's posture names this resource, and a write fits its write
|
|
51
|
+
* scope (ONEUPTIME_AI_ALLOW_WRITES, write targets, protected targets).
|
|
52
|
+
*
|
|
53
|
+
* The row it writes targets ONLY the agent (targetResourceAiAgentId),
|
|
54
|
+
* never a Runner or a Kubernetes AI agent, and carries an argv payload.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
58
|
+
"22222222-2222-4222-8222-222222222222",
|
|
59
|
+
);
|
|
60
|
+
const RESOURCE_ID: ObjectID = new ObjectID(
|
|
61
|
+
"33333333-3333-4333-8333-333333333333",
|
|
62
|
+
);
|
|
63
|
+
const AGENT_ID: ObjectID = new ObjectID("66666666-6666-4666-8666-666666666666");
|
|
64
|
+
const OTHER_AGENT_ID: ObjectID = new ObjectID(
|
|
65
|
+
"67676767-6767-4676-8676-676767676767",
|
|
66
|
+
);
|
|
67
|
+
const RUN_ID: ObjectID = new ObjectID("88888888-8888-4888-8888-888888888888");
|
|
68
|
+
const SUGGESTION_ID: ObjectID = new ObjectID(
|
|
69
|
+
"77777777-7777-4777-8777-777777777777",
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
type EnqueueArgs = Parameters<
|
|
73
|
+
typeof RunnerJobService.enqueueAiResourceCommand
|
|
74
|
+
>[0];
|
|
75
|
+
|
|
76
|
+
function dockerHostRow(
|
|
77
|
+
overrides: Record<string, unknown> = {},
|
|
78
|
+
): Record<string, unknown> {
|
|
79
|
+
return {
|
|
80
|
+
_id: RESOURCE_ID.toString(),
|
|
81
|
+
projectId: PROJECT_ID,
|
|
82
|
+
name: "web-1",
|
|
83
|
+
hostIdentifier: "web-1",
|
|
84
|
+
isArchived: false,
|
|
85
|
+
isAiInvestigationEnabled: true,
|
|
86
|
+
aiRemediationMode: ResourceAiRemediationMode.Automatic,
|
|
87
|
+
aiCommandAllowlist: [],
|
|
88
|
+
...overrides,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function agent(
|
|
93
|
+
overrides: Record<string, unknown> = {},
|
|
94
|
+
postureOverrides: Record<string, unknown> = {},
|
|
95
|
+
): ResourceAiAgent {
|
|
96
|
+
return {
|
|
97
|
+
id: AGENT_ID,
|
|
98
|
+
_id: AGENT_ID.toString(),
|
|
99
|
+
projectId: PROJECT_ID,
|
|
100
|
+
resourceType: AiResourceType.DockerHost,
|
|
101
|
+
resourceId: RESOURCE_ID,
|
|
102
|
+
resourceIdentifier: "web-1",
|
|
103
|
+
connectionStatus: "connected",
|
|
104
|
+
lastAliveAt: OneUptimeDate.getCurrentDate(),
|
|
105
|
+
posture: {
|
|
106
|
+
resourceType: AiResourceType.DockerHost,
|
|
107
|
+
resourceIdentifier: "web-1",
|
|
108
|
+
allowWrites: true,
|
|
109
|
+
writeTargets: [],
|
|
110
|
+
protectedTargets: ["oneuptime-ai-agent"],
|
|
111
|
+
reachable: true,
|
|
112
|
+
...postureOverrides,
|
|
113
|
+
},
|
|
114
|
+
...overrides,
|
|
115
|
+
} as unknown as ResourceAiAgent;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function args(overrides: Partial<EnqueueArgs> = {}): EnqueueArgs {
|
|
119
|
+
return {
|
|
120
|
+
projectId: PROJECT_ID,
|
|
121
|
+
aiRunId: RUN_ID,
|
|
122
|
+
origin: RunnerJobOrigin.AiInvestigation,
|
|
123
|
+
resourceType: AiResourceType.DockerHost,
|
|
124
|
+
resourceId: RESOURCE_ID,
|
|
125
|
+
stepId: "ai-investigation-resource-1",
|
|
126
|
+
targetResourceAiAgentId: AGENT_ID,
|
|
127
|
+
command: "docker ps -a",
|
|
128
|
+
timeoutInMs: 30000,
|
|
129
|
+
...overrides,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function remediationArgs(overrides: Partial<EnqueueArgs> = {}): EnqueueArgs {
|
|
134
|
+
return args({
|
|
135
|
+
origin: RunnerJobOrigin.AiRemediation,
|
|
136
|
+
autoRemediationSuggestionId: SUGGESTION_ID,
|
|
137
|
+
command: "docker restart web",
|
|
138
|
+
stepId: "ai-command-1",
|
|
139
|
+
...overrides,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function refusalOf(
|
|
144
|
+
data: EnqueueArgs,
|
|
145
|
+
): Promise<ResourceCommandEnqueueRefusedException> {
|
|
146
|
+
try {
|
|
147
|
+
await RunnerJobService.enqueueAiResourceCommand(data);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
expect(error).toBeInstanceOf(ResourceCommandEnqueueRefusedException);
|
|
150
|
+
expect(error).toBeInstanceOf(BadDataException);
|
|
151
|
+
return error as ResourceCommandEnqueueRefusedException;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
throw new Error("enqueueAiResourceCommand did not refuse");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
describe("RunnerJobService.enqueueAiResourceCommand", () => {
|
|
158
|
+
let createdRows: Array<RunnerJob>;
|
|
159
|
+
let countBy: jest.SpyInstance;
|
|
160
|
+
let resourceLookup: jest.SpyInstance;
|
|
161
|
+
let agentLookup: jest.SpyInstance;
|
|
162
|
+
let runLookup: jest.SpyInstance;
|
|
163
|
+
|
|
164
|
+
beforeEach(() => {
|
|
165
|
+
createdRows = [];
|
|
166
|
+
// @CaptureSpan logs the expected throws at error level.
|
|
167
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
168
|
+
return undefined;
|
|
169
|
+
});
|
|
170
|
+
countBy = jest
|
|
171
|
+
.spyOn(RunnerJobService, "countBy")
|
|
172
|
+
.mockResolvedValue(new PositiveNumber(0));
|
|
173
|
+
runLookup = jest.spyOn(AIRunService, "findOneBy").mockResolvedValue({
|
|
174
|
+
id: RUN_ID,
|
|
175
|
+
runType: AIRunType.Investigation,
|
|
176
|
+
} as unknown as AIRun);
|
|
177
|
+
jest
|
|
178
|
+
.spyOn(RunnerJobService, "create")
|
|
179
|
+
.mockImplementation(async (data: unknown): Promise<RunnerJob> => {
|
|
180
|
+
const row: RunnerJob = (data as { data: RunnerJob }).data;
|
|
181
|
+
createdRows.push(row);
|
|
182
|
+
return row;
|
|
183
|
+
});
|
|
184
|
+
resourceLookup = jest
|
|
185
|
+
.spyOn(DockerHostService, "findBy")
|
|
186
|
+
.mockResolvedValue([dockerHostRow()] as never);
|
|
187
|
+
agentLookup = jest
|
|
188
|
+
.spyOn(ResourceAiAgentService, "findOnlineAgentForResource")
|
|
189
|
+
.mockResolvedValue(agent());
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
afterEach(() => {
|
|
193
|
+
jest.restoreAllMocks();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("the row it writes", () => {
|
|
197
|
+
it("targets only the resource's agent, with an argv payload", async () => {
|
|
198
|
+
const job: RunnerJob =
|
|
199
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
200
|
+
|
|
201
|
+
expect(createdRows).toHaveLength(1);
|
|
202
|
+
expect(job.stepType).toBe(RunbookStepType.ResourceCommand);
|
|
203
|
+
expect(job.status).toBe(RunnerJobStatus.Pending);
|
|
204
|
+
expect(job.origin).toBe(RunnerJobOrigin.AiInvestigation);
|
|
205
|
+
expect(job.aiRunId?.toString()).toBe(RUN_ID.toString());
|
|
206
|
+
expect(job.targetResourceAiAgentId?.toString()).toBe(AGENT_ID.toString());
|
|
207
|
+
expect(job.targetAgentId).toBeUndefined();
|
|
208
|
+
expect(job.targetKubernetesAiAgentId).toBeUndefined();
|
|
209
|
+
expect(job.kubernetesClusterId).toBeUndefined();
|
|
210
|
+
expect(job.resourceType).toBe(AiResourceType.DockerHost);
|
|
211
|
+
expect(job.resourceId?.toString()).toBe(RESOURCE_ID.toString());
|
|
212
|
+
expect(job.script).toBe("");
|
|
213
|
+
expect(job.stepId).toBe("ai-investigation-resource-1");
|
|
214
|
+
expect(job.timeoutInMs).toBe(30000);
|
|
215
|
+
|
|
216
|
+
const payload: ResourceCommandJobPayload =
|
|
217
|
+
job.payload as unknown as ResourceCommandJobPayload;
|
|
218
|
+
|
|
219
|
+
expect(payload).toEqual({
|
|
220
|
+
resourceType: AiResourceType.DockerHost,
|
|
221
|
+
resourceId: RESOURCE_ID.toString(),
|
|
222
|
+
resourceIdentifier: "web-1",
|
|
223
|
+
program: "docker",
|
|
224
|
+
args: ["ps", "-a"],
|
|
225
|
+
displayCommand: "docker ps -a",
|
|
226
|
+
tier: ResourceCommandTier.Read,
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("never carries a credential or a shell line in the payload", async () => {
|
|
231
|
+
const job: RunnerJob =
|
|
232
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
233
|
+
const payload: Record<string, unknown> = job.payload as Record<
|
|
234
|
+
string,
|
|
235
|
+
unknown
|
|
236
|
+
>;
|
|
237
|
+
|
|
238
|
+
expect(Object.keys(payload).sort()).toEqual(
|
|
239
|
+
[
|
|
240
|
+
"args",
|
|
241
|
+
"displayCommand",
|
|
242
|
+
"program",
|
|
243
|
+
"resourceId",
|
|
244
|
+
"resourceIdentifier",
|
|
245
|
+
"resourceType",
|
|
246
|
+
"tier",
|
|
247
|
+
].sort(),
|
|
248
|
+
);
|
|
249
|
+
expect(payload["credentialId"]).toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("sets the claim deadline from the claim window", async () => {
|
|
253
|
+
const before: number = Date.now();
|
|
254
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
255
|
+
args({ claimTimeoutInMs: 20_000 }),
|
|
256
|
+
);
|
|
257
|
+
const deadline: number = job.claimDeadlineAt!.getTime();
|
|
258
|
+
|
|
259
|
+
expect(deadline).toBeGreaterThanOrEqual(before + 19_000);
|
|
260
|
+
expect(deadline).toBeLessThanOrEqual(Date.now() + 21_000);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("defaults the claim window to two minutes", async () => {
|
|
264
|
+
const job: RunnerJob =
|
|
265
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
266
|
+
|
|
267
|
+
expect(job.claimDeadlineAt!.getTime()).toBeGreaterThan(
|
|
268
|
+
Date.now() + 110_000,
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("writes a remediation job with its suggestion and the write's tier", async () => {
|
|
273
|
+
const job: RunnerJob =
|
|
274
|
+
await RunnerJobService.enqueueAiResourceCommand(remediationArgs());
|
|
275
|
+
|
|
276
|
+
expect(job.origin).toBe(RunnerJobOrigin.AiRemediation);
|
|
277
|
+
expect(job.autoRemediationSuggestionId?.toString()).toBe(
|
|
278
|
+
SUGGESTION_ID.toString(),
|
|
279
|
+
);
|
|
280
|
+
expect((job.payload as unknown as ResourceCommandJobPayload).tier).toBe(
|
|
281
|
+
ResourceCommandTier.SafeWrite,
|
|
282
|
+
);
|
|
283
|
+
expect(
|
|
284
|
+
(job.payload as unknown as ResourceCommandJobPayload).args,
|
|
285
|
+
).toEqual(["restart", "web"]);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("reads the resource and its online agent in the caller's project", async () => {
|
|
289
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
290
|
+
|
|
291
|
+
const query: Record<string, unknown> = (
|
|
292
|
+
resourceLookup.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
293
|
+
).query;
|
|
294
|
+
expect(query["projectId"]).toBe(PROJECT_ID);
|
|
295
|
+
expect(agentLookup).toHaveBeenCalledWith(
|
|
296
|
+
expect.objectContaining({
|
|
297
|
+
projectId: PROJECT_ID,
|
|
298
|
+
resourceType: AiResourceType.DockerHost,
|
|
299
|
+
}),
|
|
300
|
+
);
|
|
301
|
+
expect(
|
|
302
|
+
(
|
|
303
|
+
agentLookup.mock.calls[0]![0] as { resourceId: ObjectID }
|
|
304
|
+
).resourceId.toString(),
|
|
305
|
+
).toBe(RESOURCE_ID.toString());
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("works without a named agent: whichever agent is the resource's online one", async () => {
|
|
309
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
310
|
+
args({ targetResourceAiAgentId: undefined }),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
expect(job.targetResourceAiAgentId?.toString()).toBe(AGENT_ID.toString());
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("carries the identity the agent registered with, else its posture's", async () => {
|
|
317
|
+
agentLookup.mockResolvedValue(agent({ resourceIdentifier: undefined }));
|
|
318
|
+
|
|
319
|
+
const job: RunnerJob =
|
|
320
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
321
|
+
|
|
322
|
+
expect(
|
|
323
|
+
(job.payload as unknown as ResourceCommandJobPayload)
|
|
324
|
+
.resourceIdentifier,
|
|
325
|
+
).toBe("web-1");
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe("1. the call itself", () => {
|
|
330
|
+
it.each<[string, Partial<EnqueueArgs>]>([
|
|
331
|
+
["an unknown resource type", { resourceType: "Kubernetes" as never }],
|
|
332
|
+
["no resource id", { resourceId: undefined as never }],
|
|
333
|
+
["a runbook origin", { origin: RunnerJobOrigin.Runbook as never }],
|
|
334
|
+
[
|
|
335
|
+
"a remediation job without its suggestion",
|
|
336
|
+
{
|
|
337
|
+
origin: RunnerJobOrigin.AiRemediation,
|
|
338
|
+
autoRemediationSuggestionId: undefined,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
["an investigation job without its run", { aiRunId: undefined }],
|
|
342
|
+
["an access test with a run", { isAccessTest: true, aiRunId: RUN_ID }],
|
|
343
|
+
[
|
|
344
|
+
"an access test for remediation",
|
|
345
|
+
{
|
|
346
|
+
isAccessTest: true,
|
|
347
|
+
aiRunId: undefined,
|
|
348
|
+
origin: RunnerJobOrigin.AiRemediation,
|
|
349
|
+
autoRemediationSuggestionId: SUGGESTION_ID,
|
|
350
|
+
},
|
|
351
|
+
],
|
|
352
|
+
])(
|
|
353
|
+
"refuses %s before reading anything",
|
|
354
|
+
async (_label: string, overrides: Partial<EnqueueArgs>) => {
|
|
355
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
356
|
+
args(overrides),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
expect(refusal.refusalCode).toBe("invalid_request");
|
|
360
|
+
expect(countBy).not.toHaveBeenCalled();
|
|
361
|
+
expect(resourceLookup).not.toHaveBeenCalled();
|
|
362
|
+
expect(createdRows).toHaveLength(0);
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("2. the policy, re-run", () => {
|
|
368
|
+
it.each<[string, string]>([
|
|
369
|
+
["a denied program verb", "docker rm web"],
|
|
370
|
+
["exec", "docker exec web sh"],
|
|
371
|
+
["a pipe", "docker ps -a | grep web"],
|
|
372
|
+
["sudo", "sudo docker ps"],
|
|
373
|
+
["another type's program", "systemctl status nginx"],
|
|
374
|
+
["an empty command", ""],
|
|
375
|
+
])(
|
|
376
|
+
"never enqueues %s, whatever the origin",
|
|
377
|
+
async (_label: string, command: string) => {
|
|
378
|
+
for (const data of [args({ command }), remediationArgs({ command })]) {
|
|
379
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
380
|
+
await refusalOf(data);
|
|
381
|
+
expect(refusal.refusalCode).toBe("policy_denied");
|
|
382
|
+
expect(refusal.message).toContain(
|
|
383
|
+
"Denied by the Docker host command policy",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
expect(countBy).not.toHaveBeenCalled();
|
|
388
|
+
expect(createdRows).toHaveLength(0);
|
|
389
|
+
},
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
it.each<[string, string]>([
|
|
393
|
+
["a safe write", "docker restart web"],
|
|
394
|
+
["a risky write", "docker stop web"],
|
|
395
|
+
])(
|
|
396
|
+
"refuses %s for an investigation",
|
|
397
|
+
async (_label: string, command: string) => {
|
|
398
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
399
|
+
args({ command }),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
expect(refusal.refusalCode).toBe("not_read_only");
|
|
403
|
+
expect(refusal.message).toContain(
|
|
404
|
+
"An investigation may only run read-only commands",
|
|
405
|
+
);
|
|
406
|
+
expect(createdRows).toHaveLength(0);
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
it("lets a remediation run a read", async () => {
|
|
411
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
412
|
+
remediationArgs({ command: "docker ps -a" }),
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
expect((job.payload as unknown as ResourceCommandJobPayload).tier).toBe(
|
|
416
|
+
ResourceCommandTier.Read,
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("3. the hourly brake", () => {
|
|
422
|
+
it("counts only this project's investigation ResourceCommand rows with a run", async () => {
|
|
423
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
424
|
+
|
|
425
|
+
const query: Record<string, unknown> = (
|
|
426
|
+
countBy.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
427
|
+
).query;
|
|
428
|
+
|
|
429
|
+
expect(query["projectId"]).toBe(PROJECT_ID);
|
|
430
|
+
expect(query["origin"]).toBe(RunnerJobOrigin.AiInvestigation);
|
|
431
|
+
expect(query["stepType"]).toBe(RunbookStepType.ResourceCommand);
|
|
432
|
+
expect(query["createdAt"]).toBeDefined();
|
|
433
|
+
expect(query["aiRunId"]).toBeDefined();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("counts remediation rows on their own, without the run filter", async () => {
|
|
437
|
+
await RunnerJobService.enqueueAiResourceCommand(remediationArgs());
|
|
438
|
+
|
|
439
|
+
const query: Record<string, unknown> = (
|
|
440
|
+
countBy.mock.calls[0]![0] as { query: Record<string, unknown> }
|
|
441
|
+
).query;
|
|
442
|
+
|
|
443
|
+
expect(query["origin"]).toBe(RunnerJobOrigin.AiRemediation);
|
|
444
|
+
expect(query["stepType"]).toBe(RunbookStepType.ResourceCommand);
|
|
445
|
+
expect(query["aiRunId"]).toBeUndefined();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("refuses an investigation command at 240 an hour", async () => {
|
|
449
|
+
countBy.mockResolvedValue(
|
|
450
|
+
new PositiveNumber(
|
|
451
|
+
MAX_AI_INVESTIGATION_RESOURCE_COMMAND_JOBS_PER_PROJECT_PER_HOUR,
|
|
452
|
+
),
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
456
|
+
await refusalOf(args());
|
|
457
|
+
|
|
458
|
+
expect(
|
|
459
|
+
MAX_AI_INVESTIGATION_RESOURCE_COMMAND_JOBS_PER_PROJECT_PER_HOUR,
|
|
460
|
+
).toBe(240);
|
|
461
|
+
expect(refusal.refusalCode).toBe("hourly_limit");
|
|
462
|
+
expect(refusal.message).toContain("(240)");
|
|
463
|
+
expect(resourceLookup).not.toHaveBeenCalled();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("refuses a remediation command at 30 an hour, and allows 29", async () => {
|
|
467
|
+
expect(MAX_AI_RESOURCE_COMMAND_JOBS_PER_PROJECT_PER_HOUR).toBe(30);
|
|
468
|
+
|
|
469
|
+
countBy.mockResolvedValue(new PositiveNumber(29));
|
|
470
|
+
await RunnerJobService.enqueueAiResourceCommand(remediationArgs());
|
|
471
|
+
expect(createdRows).toHaveLength(1);
|
|
472
|
+
|
|
473
|
+
countBy.mockResolvedValue(new PositiveNumber(30));
|
|
474
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
475
|
+
await refusalOf(remediationArgs());
|
|
476
|
+
expect(refusal.refusalCode).toBe("hourly_limit");
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it("never counts the access test against the brake", async () => {
|
|
480
|
+
countBy.mockResolvedValue(new PositiveNumber(10_000));
|
|
481
|
+
|
|
482
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
483
|
+
args({ isAccessTest: true, aiRunId: undefined }),
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
expect(countBy).not.toHaveBeenCalled();
|
|
487
|
+
expect(job.aiRunId).toBeUndefined();
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe("4. the resource", () => {
|
|
492
|
+
it("refuses a resource that is not this project's", async () => {
|
|
493
|
+
resourceLookup.mockResolvedValue([] as never);
|
|
494
|
+
|
|
495
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
496
|
+
await refusalOf(args());
|
|
497
|
+
|
|
498
|
+
expect(refusal.refusalCode).toBe("resource_not_found");
|
|
499
|
+
expect(refusal.message).toBe(
|
|
500
|
+
"Docker host not found or it does not belong to this project.",
|
|
501
|
+
);
|
|
502
|
+
expect(agentLookup).not.toHaveBeenCalled();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it("refuses a row that says it is another project's", async () => {
|
|
506
|
+
resourceLookup.mockResolvedValue([
|
|
507
|
+
dockerHostRow({ projectId: ObjectID.generate() }),
|
|
508
|
+
] as never);
|
|
509
|
+
|
|
510
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
511
|
+
await refusalOf(args());
|
|
512
|
+
|
|
513
|
+
expect(refusal.refusalCode).toBe("resource_not_found");
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("fails closed when the resource cannot be read", async () => {
|
|
517
|
+
resourceLookup.mockRejectedValue(new Error("database unavailable"));
|
|
518
|
+
|
|
519
|
+
await expect(
|
|
520
|
+
RunnerJobService.enqueueAiResourceCommand(args()),
|
|
521
|
+
).rejects.toThrow("database unavailable");
|
|
522
|
+
expect(createdRows).toHaveLength(0);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
describe("5. the target, re-resolved now", () => {
|
|
527
|
+
it("refuses when the resource has no online agent, and says what to install", async () => {
|
|
528
|
+
agentLookup.mockResolvedValue(null);
|
|
529
|
+
|
|
530
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
531
|
+
await refusalOf(args());
|
|
532
|
+
|
|
533
|
+
expect(refusal.refusalCode).toBe("agent_not_online");
|
|
534
|
+
expect(refusal.message).toContain(
|
|
535
|
+
'Docker host "web-1" has no online Docker AI agent',
|
|
536
|
+
);
|
|
537
|
+
expect(refusal.message).toContain("oneuptime/resource-ai-agent");
|
|
538
|
+
expect(refusal.message).toContain("DOCKER_HOST_NAME=web-1");
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("fails closed when the agent cannot be read", async () => {
|
|
542
|
+
agentLookup.mockRejectedValue(new Error("agent read failed"));
|
|
543
|
+
|
|
544
|
+
await expect(
|
|
545
|
+
RunnerJobService.enqueueAiResourceCommand(args()),
|
|
546
|
+
).rejects.toThrow("agent read failed");
|
|
547
|
+
expect(createdRows).toHaveLength(0);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("refuses when the caller named another agent (reset or replaced)", async () => {
|
|
551
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
552
|
+
args({ targetResourceAiAgentId: OTHER_AGENT_ID }),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
expect(refusal.refusalCode).toBe("agent_changed");
|
|
556
|
+
expect(refusal.message).toContain("no longer reached through this");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it.each<[string, Record<string, unknown>]>([
|
|
560
|
+
["another project's", { projectId: ObjectID.generate() }],
|
|
561
|
+
["another resource's", { resourceId: ObjectID.generate() }],
|
|
562
|
+
["another type's", { resourceType: AiResourceType.PodmanHost }],
|
|
563
|
+
])(
|
|
564
|
+
"refuses an agent row that is %s",
|
|
565
|
+
async (_label: string, overrides: Record<string, unknown>) => {
|
|
566
|
+
agentLookup.mockResolvedValue(agent(overrides));
|
|
567
|
+
|
|
568
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
569
|
+
await refusalOf(args());
|
|
570
|
+
|
|
571
|
+
expect(refusal.refusalCode).toBe("agent_changed");
|
|
572
|
+
},
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe("6. the switch", () => {
|
|
577
|
+
it("refuses an investigation read when investigation is off", async () => {
|
|
578
|
+
resourceLookup.mockResolvedValue([
|
|
579
|
+
dockerHostRow({ isAiInvestigationEnabled: false }),
|
|
580
|
+
] as never);
|
|
581
|
+
|
|
582
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
583
|
+
await refusalOf(args());
|
|
584
|
+
|
|
585
|
+
expect(refusal.refusalCode).toBe("switch_off");
|
|
586
|
+
expect(refusal.message).toBe(
|
|
587
|
+
'AI investigation is turned off for Docker host "web-1", so this investigation command was not enqueued.',
|
|
588
|
+
);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it("lets the access test read with investigation off", async () => {
|
|
592
|
+
resourceLookup.mockResolvedValue([
|
|
593
|
+
dockerHostRow({
|
|
594
|
+
isAiInvestigationEnabled: false,
|
|
595
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
596
|
+
}),
|
|
597
|
+
] as never);
|
|
598
|
+
|
|
599
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
600
|
+
args({ isAccessTest: true, aiRunId: undefined }),
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
expect(job.stepType).toBe(RunbookStepType.ResourceCommand);
|
|
604
|
+
expect(runLookup).not.toHaveBeenCalled();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("refuses a remediation command when fixes are off", async () => {
|
|
608
|
+
resourceLookup.mockResolvedValue([
|
|
609
|
+
dockerHostRow({
|
|
610
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
611
|
+
}),
|
|
612
|
+
] as never);
|
|
613
|
+
|
|
614
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
615
|
+
await refusalOf(remediationArgs());
|
|
616
|
+
|
|
617
|
+
expect(refusal.refusalCode).toBe("switch_off");
|
|
618
|
+
expect(refusal.message).toContain("AI fixes are turned off");
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("reads an unknown stored mode as fixes off", async () => {
|
|
622
|
+
resourceLookup.mockResolvedValue([
|
|
623
|
+
dockerHostRow({ aiRemediationMode: "Everything" }),
|
|
624
|
+
] as never);
|
|
625
|
+
|
|
626
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
627
|
+
await refusalOf(remediationArgs());
|
|
628
|
+
|
|
629
|
+
expect(refusal.refusalCode).toBe("switch_off");
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("lets a remediation run's reads through with investigation off, and refuses them with fixes off", async () => {
|
|
633
|
+
runLookup.mockResolvedValue({
|
|
634
|
+
id: RUN_ID,
|
|
635
|
+
runType: AIRunType.RemediationExecution,
|
|
636
|
+
} as unknown as AIRun);
|
|
637
|
+
resourceLookup.mockResolvedValue([
|
|
638
|
+
dockerHostRow({ isAiInvestigationEnabled: false }),
|
|
639
|
+
] as never);
|
|
640
|
+
|
|
641
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
642
|
+
expect(createdRows).toHaveLength(1);
|
|
643
|
+
|
|
644
|
+
resourceLookup.mockResolvedValue([
|
|
645
|
+
dockerHostRow({
|
|
646
|
+
isAiInvestigationEnabled: true,
|
|
647
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
648
|
+
}),
|
|
649
|
+
] as never);
|
|
650
|
+
|
|
651
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
652
|
+
await refusalOf(args());
|
|
653
|
+
expect(refusal.refusalCode).toBe("switch_off");
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
describe("7. no credential", () => {
|
|
658
|
+
it("refuses a command that names a credential", async () => {
|
|
659
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
660
|
+
args({ credentialId: ObjectID.generate().toString() }),
|
|
661
|
+
);
|
|
662
|
+
|
|
663
|
+
expect(refusal.refusalCode).toBe("credential_refused");
|
|
664
|
+
expect(refusal.message).toContain("never given one by OneUptime");
|
|
665
|
+
expect(createdRows).toHaveLength(0);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
describe("8. the agent's posture", () => {
|
|
670
|
+
it.each<[string, ResourceAiAgent]>([
|
|
671
|
+
["no posture", agent({ posture: undefined })],
|
|
672
|
+
["an unreadable posture", agent({ posture: { allowWrites: true } })],
|
|
673
|
+
[
|
|
674
|
+
"a posture for another type",
|
|
675
|
+
agent(
|
|
676
|
+
{},
|
|
677
|
+
{
|
|
678
|
+
resourceType: AiResourceType.PodmanHost,
|
|
679
|
+
},
|
|
680
|
+
),
|
|
681
|
+
],
|
|
682
|
+
])(
|
|
683
|
+
"refuses an agent with %s",
|
|
684
|
+
async (_label: string, row: ResourceAiAgent) => {
|
|
685
|
+
agentLookup.mockResolvedValue(row);
|
|
686
|
+
|
|
687
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
688
|
+
await refusalOf(args());
|
|
689
|
+
|
|
690
|
+
expect(refusal.refusalCode).toBe("agent_posture_mismatch");
|
|
691
|
+
expect(refusal.message).toContain(
|
|
692
|
+
"the Docker host's AI agent page (AI → AI agent)",
|
|
693
|
+
);
|
|
694
|
+
},
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
it("refuses every write from a read-only agent, naming the setting", async () => {
|
|
698
|
+
agentLookup.mockResolvedValue(agent({}, { allowWrites: false }));
|
|
699
|
+
|
|
700
|
+
const refusal: ResourceCommandEnqueueRefusedException =
|
|
701
|
+
await refusalOf(remediationArgs());
|
|
702
|
+
|
|
703
|
+
expect(refusal.refusalCode).toBe("write_scope");
|
|
704
|
+
expect(refusal.message).toContain("ONEUPTIME_AI_ALLOW_WRITES=true");
|
|
705
|
+
expect(refusal.message).toContain("It was not enqueued.");
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("still lets a read-only agent run reads", async () => {
|
|
709
|
+
agentLookup.mockResolvedValue(agent({}, { allowWrites: false }));
|
|
710
|
+
|
|
711
|
+
await RunnerJobService.enqueueAiResourceCommand(args());
|
|
712
|
+
await RunnerJobService.enqueueAiResourceCommand(
|
|
713
|
+
remediationArgs({ command: "docker ps" }),
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
expect(createdRows).toHaveLength(2);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("refuses a write on one of the agent's protected targets", async () => {
|
|
720
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
721
|
+
remediationArgs({ command: "docker restart oneuptime-ai-agent" }),
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
expect(refusal.refusalCode).toBe("write_scope");
|
|
725
|
+
expect(refusal.message).toContain("which the Docker AI agent protects");
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it("refuses a write outside the agent's write targets and allows one inside", async () => {
|
|
729
|
+
agentLookup.mockResolvedValue(agent({}, { writeTargets: ["api-*"] }));
|
|
730
|
+
|
|
731
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
732
|
+
remediationArgs({ command: "docker restart web" }),
|
|
733
|
+
);
|
|
734
|
+
expect(refusal.refusalCode).toBe("write_scope");
|
|
735
|
+
expect(refusal.message).toContain("ONEUPTIME_AI_WRITE_TARGETS=api-*");
|
|
736
|
+
|
|
737
|
+
await RunnerJobService.enqueueAiResourceCommand(
|
|
738
|
+
remediationArgs({ command: "docker restart api-1" }),
|
|
739
|
+
);
|
|
740
|
+
expect(createdRows).toHaveLength(1);
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
describe("other resource types", () => {
|
|
745
|
+
it("enqueues a host's systemctl read for the Host AI agent", async () => {
|
|
746
|
+
jest.spyOn(HostService, "findBy").mockResolvedValue([
|
|
747
|
+
{
|
|
748
|
+
_id: RESOURCE_ID.toString(),
|
|
749
|
+
projectId: PROJECT_ID,
|
|
750
|
+
name: "db-host",
|
|
751
|
+
hostIdentifier: "db-host",
|
|
752
|
+
isAiInvestigationEnabled: true,
|
|
753
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
754
|
+
},
|
|
755
|
+
] as never);
|
|
756
|
+
agentLookup.mockResolvedValue(
|
|
757
|
+
agent(
|
|
758
|
+
{
|
|
759
|
+
resourceType: AiResourceType.Host,
|
|
760
|
+
resourceIdentifier: "db-host",
|
|
761
|
+
},
|
|
762
|
+
{ resourceType: AiResourceType.Host, resourceIdentifier: "db-host" },
|
|
763
|
+
),
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
767
|
+
args({
|
|
768
|
+
resourceType: AiResourceType.Host,
|
|
769
|
+
command: "systemctl status nginx --no-pager",
|
|
770
|
+
}),
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
const payload: ResourceCommandJobPayload =
|
|
774
|
+
job.payload as unknown as ResourceCommandJobPayload;
|
|
775
|
+
expect(payload.program).toBe("systemctl");
|
|
776
|
+
expect(payload.resourceType).toBe(AiResourceType.Host);
|
|
777
|
+
expect(payload.resourceIdentifier).toBe("db-host");
|
|
778
|
+
expect(job.resourceType).toBe(AiResourceType.Host);
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it("refuses a docker command for a host (wrong program for the type)", async () => {
|
|
782
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
783
|
+
args({ resourceType: AiResourceType.Host, command: "docker ps" }),
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
expect(refusal.refusalCode).toBe("policy_denied");
|
|
787
|
+
expect(refusal.message).toContain("Denied by the Host command policy");
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
it("enqueues a database catalog read and names the database server in refusals", async () => {
|
|
791
|
+
jest.spyOn(DatabaseServerService, "findBy").mockResolvedValue([
|
|
792
|
+
{
|
|
793
|
+
_id: RESOURCE_ID.toString(),
|
|
794
|
+
projectId: PROJECT_ID,
|
|
795
|
+
name: "orders",
|
|
796
|
+
dbSystem: "postgresql",
|
|
797
|
+
serverAddress: "db.internal",
|
|
798
|
+
serverPort: 5432,
|
|
799
|
+
isAiInvestigationEnabled: false,
|
|
800
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
801
|
+
},
|
|
802
|
+
] as never);
|
|
803
|
+
agentLookup.mockResolvedValue(
|
|
804
|
+
agent(
|
|
805
|
+
{
|
|
806
|
+
resourceType: AiResourceType.DatabaseServer,
|
|
807
|
+
resourceIdentifier: "postgresql|db.internal:5432",
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
resourceType: AiResourceType.DatabaseServer,
|
|
811
|
+
resourceIdentifier: "postgresql|db.internal:5432",
|
|
812
|
+
},
|
|
813
|
+
),
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
const refusal: ResourceCommandEnqueueRefusedException = await refusalOf(
|
|
817
|
+
args({
|
|
818
|
+
resourceType: AiResourceType.DatabaseServer,
|
|
819
|
+
command: "db sessions --limit 5",
|
|
820
|
+
}),
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
expect(refusal.refusalCode).toBe("switch_off");
|
|
824
|
+
expect(refusal.message).toContain('database server "orders"');
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it("carries a database agent's registered endpoint identity", async () => {
|
|
828
|
+
jest.spyOn(DatabaseServerService, "findBy").mockResolvedValue([
|
|
829
|
+
{
|
|
830
|
+
_id: RESOURCE_ID.toString(),
|
|
831
|
+
projectId: PROJECT_ID,
|
|
832
|
+
name: "orders",
|
|
833
|
+
dbSystem: "postgresql",
|
|
834
|
+
serverAddress: "db.internal",
|
|
835
|
+
serverPort: 5432,
|
|
836
|
+
isAiInvestigationEnabled: true,
|
|
837
|
+
},
|
|
838
|
+
] as never);
|
|
839
|
+
agentLookup.mockResolvedValue(
|
|
840
|
+
agent(
|
|
841
|
+
{
|
|
842
|
+
resourceType: AiResourceType.DatabaseServer,
|
|
843
|
+
resourceIdentifier: "postgres|db.internal:5432",
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
resourceType: AiResourceType.DatabaseServer,
|
|
847
|
+
resourceIdentifier: "postgres|db.internal:5432",
|
|
848
|
+
},
|
|
849
|
+
),
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
const job: RunnerJob = await RunnerJobService.enqueueAiResourceCommand(
|
|
853
|
+
args({
|
|
854
|
+
resourceType: AiResourceType.DatabaseServer,
|
|
855
|
+
command: "db sessions --limit 5",
|
|
856
|
+
}),
|
|
857
|
+
);
|
|
858
|
+
const payload: ResourceCommandJobPayload =
|
|
859
|
+
job.payload as unknown as ResourceCommandJobPayload;
|
|
860
|
+
|
|
861
|
+
// The agent compares this to what it registered with, not the row's.
|
|
862
|
+
expect(payload.resourceIdentifier).toBe("postgres|db.internal:5432");
|
|
863
|
+
expect(payload.program).toBe("db");
|
|
864
|
+
expect(payload.args).toEqual(["sessions", "--limit", "5"]);
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
describe("RunnerJobService.getResourceBindingRefusal", () => {
|
|
870
|
+
function row(
|
|
871
|
+
overrides: Partial<ResourceAiAccessRow> = {},
|
|
872
|
+
): ResourceAiAccessRow {
|
|
873
|
+
return {
|
|
874
|
+
resourceType: AiResourceType.CephCluster,
|
|
875
|
+
id: RESOURCE_ID,
|
|
876
|
+
projectId: PROJECT_ID,
|
|
877
|
+
name: "ceph-prod",
|
|
878
|
+
identifier: "ceph-prod",
|
|
879
|
+
isAiInvestigationEnabled: true,
|
|
880
|
+
aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
|
|
881
|
+
aiCommandAllowlist: [],
|
|
882
|
+
isArchived: false,
|
|
883
|
+
...overrides,
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function cephAgent(): ResourceAiAgent {
|
|
888
|
+
return agent({ resourceType: AiResourceType.CephCluster });
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function codeOf(
|
|
892
|
+
refusal: { code: ResourceCommandEnqueueRefusalCode } | null,
|
|
893
|
+
): ResourceCommandEnqueueRefusalCode | null {
|
|
894
|
+
return refusal ? refusal.code : null;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
it("allows the resource's own online agent with the switch on", () => {
|
|
898
|
+
expect(
|
|
899
|
+
RunnerJobServiceClass.getResourceBindingRefusal({
|
|
900
|
+
resource: row(),
|
|
901
|
+
agent: cephAgent(),
|
|
902
|
+
requiredSwitch: "investigation",
|
|
903
|
+
}),
|
|
904
|
+
).toBeNull();
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
it("checks the agent before the switch, and the switch before the credential", () => {
|
|
908
|
+
expect(
|
|
909
|
+
codeOf(
|
|
910
|
+
RunnerJobServiceClass.getResourceBindingRefusal({
|
|
911
|
+
resource: row({ isAiInvestigationEnabled: false }),
|
|
912
|
+
agent: null,
|
|
913
|
+
credentialId: "x",
|
|
914
|
+
requiredSwitch: "investigation",
|
|
915
|
+
}),
|
|
916
|
+
),
|
|
917
|
+
).toBe("agent_not_online");
|
|
918
|
+
|
|
919
|
+
expect(
|
|
920
|
+
codeOf(
|
|
921
|
+
RunnerJobServiceClass.getResourceBindingRefusal({
|
|
922
|
+
resource: row({ isAiInvestigationEnabled: false }),
|
|
923
|
+
agent: cephAgent(),
|
|
924
|
+
credentialId: "x",
|
|
925
|
+
requiredSwitch: "investigation",
|
|
926
|
+
}),
|
|
927
|
+
),
|
|
928
|
+
).toBe("switch_off");
|
|
929
|
+
|
|
930
|
+
expect(
|
|
931
|
+
codeOf(
|
|
932
|
+
RunnerJobServiceClass.getResourceBindingRefusal({
|
|
933
|
+
resource: row(),
|
|
934
|
+
agent: cephAgent(),
|
|
935
|
+
credentialId: "x",
|
|
936
|
+
requiredSwitch: "investigation",
|
|
937
|
+
}),
|
|
938
|
+
),
|
|
939
|
+
).toBe("credential_refused");
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it("needs no switch for the access test", () => {
|
|
943
|
+
expect(
|
|
944
|
+
RunnerJobServiceClass.getResourceBindingRefusal({
|
|
945
|
+
resource: row({
|
|
946
|
+
isAiInvestigationEnabled: false,
|
|
947
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
948
|
+
}),
|
|
949
|
+
agent: cephAgent(),
|
|
950
|
+
requiredSwitch: null,
|
|
951
|
+
}),
|
|
952
|
+
).toBeNull();
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it.each<[ResourceAiRemediationMode]>([
|
|
956
|
+
[ResourceAiRemediationMode.RequireApproval],
|
|
957
|
+
[ResourceAiRemediationMode.Automatic],
|
|
958
|
+
[ResourceAiRemediationMode.BypassApproval],
|
|
959
|
+
])(
|
|
960
|
+
"lets remediation through in mode %s",
|
|
961
|
+
(mode: ResourceAiRemediationMode) => {
|
|
962
|
+
expect(
|
|
963
|
+
RunnerJobServiceClass.getResourceBindingRefusal({
|
|
964
|
+
resource: row({ aiRemediationMode: mode }),
|
|
965
|
+
agent: cephAgent(),
|
|
966
|
+
requiredSwitch: "remediation",
|
|
967
|
+
}),
|
|
968
|
+
).toBeNull();
|
|
969
|
+
},
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
it("words the refusal for the resource's own noun", () => {
|
|
973
|
+
const refusal: {
|
|
974
|
+
code: ResourceCommandEnqueueRefusalCode;
|
|
975
|
+
message: string;
|
|
976
|
+
} | null = RunnerJobServiceClass.getResourceBindingRefusal({
|
|
977
|
+
resource: row({ aiRemediationMode: ResourceAiRemediationMode.Disabled }),
|
|
978
|
+
agent: cephAgent(),
|
|
979
|
+
requiredSwitch: "remediation",
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
expect(refusal?.message).toBe(
|
|
983
|
+
'AI fixes are turned off for Ceph cluster "ceph-prod", so this command was not enqueued.',
|
|
984
|
+
);
|
|
985
|
+
});
|
|
986
|
+
});
|