@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,835 @@
|
|
|
1
|
+
import CommonAPI from "../../../Server/API/CommonAPI";
|
|
2
|
+
import AlertFeedService from "../../../Server/Services/AlertFeedService";
|
|
3
|
+
import AutoRemediationSuggestionService from "../../../Server/Services/AutoRemediationSuggestionService";
|
|
4
|
+
import IncidentFeedService from "../../../Server/Services/IncidentFeedService";
|
|
5
|
+
import KubernetesClusterAiAccessService from "../../../Server/Services/KubernetesClusterAiAccessService";
|
|
6
|
+
import ProjectService from "../../../Server/Services/ProjectService";
|
|
7
|
+
import ResourceAiAccessService from "../../../Server/Services/ResourceAiAccessService";
|
|
8
|
+
import RunnerService from "../../../Server/Services/RunnerService";
|
|
9
|
+
import CommandPlanExecutor from "../../../Server/Utils/AutoRemediation/CommandPlanExecutor";
|
|
10
|
+
import AutoRemediationSuggestion from "../../../Models/DatabaseModels/AutoRemediationSuggestion";
|
|
11
|
+
import Project from "../../../Models/DatabaseModels/Project";
|
|
12
|
+
import Runner from "../../../Models/DatabaseModels/Runner";
|
|
13
|
+
import AutoRemediationSuggestionStatus from "../../../Types/AutoRemediation/AutoRemediationSuggestionStatus";
|
|
14
|
+
import AutoRemediationSuggestionType from "../../../Types/AutoRemediation/AutoRemediationSuggestionType";
|
|
15
|
+
import AiResourceType from "../../../Types/ResourceAiAgent/AiResourceType";
|
|
16
|
+
import {
|
|
17
|
+
ResourceAiAccessStatus,
|
|
18
|
+
ResourceAiAgentPosture,
|
|
19
|
+
ResourceAiRemediationMode,
|
|
20
|
+
} from "../../../Types/ResourceAiAgent/ResourceAiAccess";
|
|
21
|
+
import RunbookStepType from "../../../Types/Runbook/RunbookStepType";
|
|
22
|
+
import {
|
|
23
|
+
ExpressRequest,
|
|
24
|
+
ExpressResponse,
|
|
25
|
+
NextFunction,
|
|
26
|
+
} from "../../../Server/Utils/Express";
|
|
27
|
+
import Response from "../../../Server/Utils/Response";
|
|
28
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
29
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
30
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
31
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
32
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
33
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
34
|
+
import Permission, {
|
|
35
|
+
UserPermission,
|
|
36
|
+
UserTenantAccessPermission,
|
|
37
|
+
} from "../../../Types/Permission";
|
|
38
|
+
import UserType from "../../../Types/UserType";
|
|
39
|
+
import {
|
|
40
|
+
afterEach,
|
|
41
|
+
beforeAll,
|
|
42
|
+
beforeEach,
|
|
43
|
+
describe,
|
|
44
|
+
expect,
|
|
45
|
+
test,
|
|
46
|
+
} from "@jest/globals";
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* ---------------------------------------------------------------------------
|
|
50
|
+
* POST /auto-remediation/approve for RESOURCE rounds and the resource AI
|
|
51
|
+
* agent (Common/Server/API/AutoRemediationAPI.ts) — the resource sibling of
|
|
52
|
+
* AutoRemediationApproveClusterRound.
|
|
53
|
+
*
|
|
54
|
+
* Pinned here:
|
|
55
|
+
* - "Enable AI command execution" stops rule rounds only: a resource round
|
|
56
|
+
* (the suggestion names a resource and no rule) is approvable with it off;
|
|
57
|
+
* Enable AI / auto-remediation still stop it. The lane is read off the
|
|
58
|
+
* row, so resourceType and resourceId must be selected.
|
|
59
|
+
* - A ResourceCommand step names its resource's AI agent where a Runner id
|
|
60
|
+
* would be; the Runner table is never re-read for it. The agent must be
|
|
61
|
+
* the resource's CURRENT agent and online; only ResourceCommand steps may
|
|
62
|
+
* target it, and only for its own resource.
|
|
63
|
+
* - Every ResourceCommand is re-checked live: the resource must exist and be
|
|
64
|
+
* remediation-ready (the gap and its next step named), the command and its
|
|
65
|
+
* rollback must still pass the policy and the agent's reported write scope
|
|
66
|
+
* — with a next step naming the agent's own settings.
|
|
67
|
+
* - Each resource's status is read once per approval.
|
|
68
|
+
* ---------------------------------------------------------------------------
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
type RouterFunction = (
|
|
72
|
+
req: ExpressRequest,
|
|
73
|
+
res: ExpressResponse,
|
|
74
|
+
next: NextFunction,
|
|
75
|
+
) => void | Promise<void>;
|
|
76
|
+
|
|
77
|
+
type MockRoute = {
|
|
78
|
+
method: string;
|
|
79
|
+
uri: string;
|
|
80
|
+
middleware: RouterFunction;
|
|
81
|
+
handlerFunction: RouterFunction;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type MockRouter = {
|
|
85
|
+
get: jest.Mock;
|
|
86
|
+
post: jest.Mock;
|
|
87
|
+
put: jest.Mock;
|
|
88
|
+
delete: jest.Mock;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const mockRoutes: Array<MockRoute> = [];
|
|
92
|
+
|
|
93
|
+
type RegisterRouteFunction = (
|
|
94
|
+
method: string,
|
|
95
|
+
) => (
|
|
96
|
+
uri: string,
|
|
97
|
+
middleware: RouterFunction,
|
|
98
|
+
handlerFunction: RouterFunction,
|
|
99
|
+
) => void;
|
|
100
|
+
|
|
101
|
+
const registerRoute: RegisterRouteFunction = (method: string) => {
|
|
102
|
+
return (
|
|
103
|
+
uri: string,
|
|
104
|
+
middleware: RouterFunction,
|
|
105
|
+
handlerFunction: RouterFunction,
|
|
106
|
+
): void => {
|
|
107
|
+
mockRoutes.push({
|
|
108
|
+
method: method.toUpperCase(),
|
|
109
|
+
uri,
|
|
110
|
+
middleware,
|
|
111
|
+
handlerFunction,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const mockRouter: MockRouter = {
|
|
117
|
+
get: jest.fn().mockImplementation(registerRoute("get")),
|
|
118
|
+
post: jest.fn().mockImplementation(registerRoute("post")),
|
|
119
|
+
put: jest.fn().mockImplementation(registerRoute("put")),
|
|
120
|
+
delete: jest.fn().mockImplementation(registerRoute("delete")),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
jest.mock("../../../Server/Utils/Express", () => {
|
|
124
|
+
const actual: Record<string, unknown> = jest.requireActual(
|
|
125
|
+
"../../../Server/Utils/Express",
|
|
126
|
+
) as Record<string, unknown>;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...actual,
|
|
130
|
+
__esModule: true,
|
|
131
|
+
default: {
|
|
132
|
+
...((actual["default"] as Record<string, unknown>) || {}),
|
|
133
|
+
getRouter: (): MockRouter => {
|
|
134
|
+
return mockRouter;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
jest.mock("../../../Server/Utils/Response", () => {
|
|
141
|
+
return {
|
|
142
|
+
__esModule: true,
|
|
143
|
+
default: {
|
|
144
|
+
sendJsonObjectResponse: jest.fn(),
|
|
145
|
+
sendErrorResponse: jest.fn(),
|
|
146
|
+
sendEmptySuccessResponse: jest.fn(),
|
|
147
|
+
sendEntityResponse: jest.fn(),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
jest.mock("../../../Server/Utils/AutoRemediation/CommandPlanExecutor", () => {
|
|
153
|
+
return {
|
|
154
|
+
__esModule: true,
|
|
155
|
+
default: {
|
|
156
|
+
executeApprovedPlan: jest.fn(),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const executeApprovedPlanMock: jest.Mock =
|
|
162
|
+
CommandPlanExecutor.executeApprovedPlan as unknown as jest.Mock;
|
|
163
|
+
|
|
164
|
+
const sendJsonObjectResponseMock: jest.Mock =
|
|
165
|
+
Response.sendJsonObjectResponse as unknown as jest.Mock;
|
|
166
|
+
|
|
167
|
+
const APPROVE_ROUTE: string = "/auto-remediation/approve";
|
|
168
|
+
|
|
169
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
170
|
+
"11111111-1111-4111-8111-111111111111",
|
|
171
|
+
);
|
|
172
|
+
const SUGGESTION_ID: ObjectID = new ObjectID(
|
|
173
|
+
"22222222-2222-4222-8222-222222222222",
|
|
174
|
+
);
|
|
175
|
+
const USER_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
|
|
176
|
+
const INCIDENT_ID: ObjectID = new ObjectID(
|
|
177
|
+
"44444444-4444-4444-8444-444444444444",
|
|
178
|
+
);
|
|
179
|
+
// The ResourceAiAgent row's id — what a plan stores in runnerId for the agent.
|
|
180
|
+
const AGENT_ID: string = "55555555-5555-4555-8555-555555555555";
|
|
181
|
+
const OTHER_AGENT_ID: string = "56565656-5656-4565-8565-565656565656";
|
|
182
|
+
const RUNNER_ID: string = "66666666-6666-4666-8666-666666666666";
|
|
183
|
+
const RESOURCE_ID: string = "77777777-7777-4777-8777-777777777777";
|
|
184
|
+
const OTHER_RESOURCE_ID: string = "78787878-7878-4787-8787-787878787878";
|
|
185
|
+
const RULE_ID: ObjectID = new ObjectID("99999999-9999-4999-8999-999999999999");
|
|
186
|
+
|
|
187
|
+
type RouteCallResult = {
|
|
188
|
+
thrownToNext: unknown;
|
|
189
|
+
nextCallCount: number;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
function matchRoute(uri: string): MockRoute {
|
|
193
|
+
const route: MockRoute | undefined = mockRoutes.find((route: MockRoute) => {
|
|
194
|
+
return route.method === "POST" && route.uri === uri;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!route) {
|
|
198
|
+
throw new Error(`Route POST ${uri} was never registered`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return route;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function callApprove(): Promise<RouteCallResult> {
|
|
205
|
+
const req: ExpressRequest = {
|
|
206
|
+
params: {},
|
|
207
|
+
query: {},
|
|
208
|
+
body: { suggestionId: SUGGESTION_ID.toString() },
|
|
209
|
+
headers: {},
|
|
210
|
+
} as unknown as ExpressRequest;
|
|
211
|
+
|
|
212
|
+
const res: ExpressResponse = {
|
|
213
|
+
send: jest.fn(),
|
|
214
|
+
json: jest.fn(),
|
|
215
|
+
status: jest.fn().mockReturnThis(),
|
|
216
|
+
} as unknown as ExpressResponse;
|
|
217
|
+
|
|
218
|
+
const next: jest.Mock = jest.fn();
|
|
219
|
+
|
|
220
|
+
await matchRoute(APPROVE_ROUTE).handlerFunction(
|
|
221
|
+
req,
|
|
222
|
+
res,
|
|
223
|
+
next as unknown as NextFunction,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
thrownToNext: next.mock.calls[0] ? next.mock.calls[0][0] : undefined,
|
|
228
|
+
nextCallCount: next.mock.calls.length,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildUserProps(): DatabaseCommonInteractionProps {
|
|
233
|
+
const permissionMap: Dictionary<UserTenantAccessPermission> = {};
|
|
234
|
+
|
|
235
|
+
permissionMap[PROJECT_ID.toString()] = {
|
|
236
|
+
_type: "UserTenantAccessPermission",
|
|
237
|
+
projectId: PROJECT_ID,
|
|
238
|
+
permissions: [Permission.ProjectMember].map((permission: Permission) => {
|
|
239
|
+
const userPermission: UserPermission = {
|
|
240
|
+
_type: "UserPermission",
|
|
241
|
+
permission: permission,
|
|
242
|
+
labelIds: [],
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return userPermission;
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
tenantId: PROJECT_ID,
|
|
251
|
+
userId: USER_ID,
|
|
252
|
+
userType: UserType.User,
|
|
253
|
+
userTenantAccessPermission: permissionMap,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// A resource command as a resource round records it for the resource's AI agent.
|
|
258
|
+
function resourceCommandJson(
|
|
259
|
+
overrides: Partial<Record<string, unknown>> = {},
|
|
260
|
+
): JSONObject {
|
|
261
|
+
return {
|
|
262
|
+
sequence: 1,
|
|
263
|
+
stepType: RunbookStepType.ResourceCommand,
|
|
264
|
+
runnerId: AGENT_ID,
|
|
265
|
+
runnerNameSnapshot: "Docker AI agent",
|
|
266
|
+
resourceType: AiResourceType.DockerHost,
|
|
267
|
+
resourceId: RESOURCE_ID,
|
|
268
|
+
resourceNameSnapshot: "web-1",
|
|
269
|
+
resourceCommandTier: "RiskyWrite",
|
|
270
|
+
command: "docker stop web",
|
|
271
|
+
rollbackCommand: "docker start web",
|
|
272
|
+
timeoutInMs: 30000,
|
|
273
|
+
rationale: "web is leaking memory",
|
|
274
|
+
expectedEffect: "the leak stops",
|
|
275
|
+
policyVerdict: "RequiresApproval",
|
|
276
|
+
...overrides,
|
|
277
|
+
} as JSONObject;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function fakeProject(
|
|
281
|
+
overrides: Partial<Record<string, unknown>> = {},
|
|
282
|
+
): Project {
|
|
283
|
+
return {
|
|
284
|
+
_id: PROJECT_ID.toString(),
|
|
285
|
+
id: PROJECT_ID,
|
|
286
|
+
enableAi: true,
|
|
287
|
+
enableAutoRemediation: true,
|
|
288
|
+
enableAiCommandExecution: false,
|
|
289
|
+
...overrides,
|
|
290
|
+
} as unknown as Project;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function resourceRoundRow(
|
|
294
|
+
commands: Array<JSONObject>,
|
|
295
|
+
overrides: Partial<Record<string, unknown>> = {},
|
|
296
|
+
): Record<string, unknown> {
|
|
297
|
+
return {
|
|
298
|
+
id: new ObjectID(SUGGESTION_ID.toString()),
|
|
299
|
+
_id: SUGGESTION_ID.toString(),
|
|
300
|
+
projectId: PROJECT_ID,
|
|
301
|
+
incidentId: INCIDENT_ID,
|
|
302
|
+
status: AutoRemediationSuggestionStatus.Suggested,
|
|
303
|
+
suggestionType: AutoRemediationSuggestionType.CommandPlan,
|
|
304
|
+
commandPlan: { commands } as JSONObject,
|
|
305
|
+
verificationWindowMinutes: 15,
|
|
306
|
+
resourceType: AiResourceType.DockerHost,
|
|
307
|
+
resourceId: new ObjectID(RESOURCE_ID),
|
|
308
|
+
ruleNameSnapshot: 'AI remediation for Docker host "web-1"',
|
|
309
|
+
...overrides,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function posture(
|
|
314
|
+
overrides: Partial<ResourceAiAgentPosture> = {},
|
|
315
|
+
): ResourceAiAgentPosture {
|
|
316
|
+
return {
|
|
317
|
+
resourceType: AiResourceType.DockerHost,
|
|
318
|
+
resourceIdentifier: "web-1",
|
|
319
|
+
allowWrites: true,
|
|
320
|
+
writeTargets: [],
|
|
321
|
+
protectedTargets: [],
|
|
322
|
+
reachable: true,
|
|
323
|
+
...overrides,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function resourceStatus(
|
|
328
|
+
overrides: Partial<ResourceAiAccessStatus> = {},
|
|
329
|
+
postureOverrides: Partial<ResourceAiAgentPosture> = {},
|
|
330
|
+
): ResourceAiAccessStatus {
|
|
331
|
+
return {
|
|
332
|
+
resourceType: AiResourceType.DockerHost,
|
|
333
|
+
resourceId: RESOURCE_ID,
|
|
334
|
+
resourceName: "web-1",
|
|
335
|
+
isAiInvestigationEnabled: true,
|
|
336
|
+
aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
|
|
337
|
+
aiCommandAllowlist: [],
|
|
338
|
+
agent: {
|
|
339
|
+
agentId: AGENT_ID,
|
|
340
|
+
connectionStatus: "connected",
|
|
341
|
+
isOnline: true,
|
|
342
|
+
posture: posture(postureOverrides),
|
|
343
|
+
},
|
|
344
|
+
gaps: [],
|
|
345
|
+
isInvestigationReady: true,
|
|
346
|
+
isRemediationReady: true,
|
|
347
|
+
...overrides,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
describe("POST /auto-remediation/approve — resource rounds and the resource AI agent", () => {
|
|
352
|
+
let suggestionFindSpy: jest.SpyInstance;
|
|
353
|
+
let casSpy: jest.SpyInstance;
|
|
354
|
+
let projectFindSpy: jest.SpyInstance;
|
|
355
|
+
let runnerFindSpy: jest.SpyInstance;
|
|
356
|
+
let statusSpy: jest.SpyInstance;
|
|
357
|
+
let clusterStatusSpy: jest.SpyInstance;
|
|
358
|
+
let mayChangeSpy: jest.SpyInstance;
|
|
359
|
+
|
|
360
|
+
beforeAll(async () => {
|
|
361
|
+
// Routes register at module scope, after the mock router exists.
|
|
362
|
+
await import("../../../Server/API/AutoRemediationAPI");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
function mockRow(row: Record<string, unknown>): void {
|
|
366
|
+
suggestionFindSpy.mockImplementation(
|
|
367
|
+
async (args: unknown): Promise<AutoRemediationSuggestion> => {
|
|
368
|
+
const select: Record<string, unknown> = (
|
|
369
|
+
args as { select: Record<string, unknown> }
|
|
370
|
+
).select;
|
|
371
|
+
const picked: Record<string, unknown> = { id: row["id"] };
|
|
372
|
+
for (const key of Object.keys(select)) {
|
|
373
|
+
if (key in row) {
|
|
374
|
+
picked[key] = row[key];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return picked as unknown as AutoRemediationSuggestion;
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
beforeEach(() => {
|
|
383
|
+
jest.clearAllMocks();
|
|
384
|
+
|
|
385
|
+
executeApprovedPlanMock.mockResolvedValue(undefined);
|
|
386
|
+
|
|
387
|
+
jest
|
|
388
|
+
.spyOn(CommonAPI, "getDatabaseCommonInteractionProps")
|
|
389
|
+
.mockResolvedValue(buildUserProps());
|
|
390
|
+
|
|
391
|
+
suggestionFindSpy = jest.spyOn(
|
|
392
|
+
AutoRemediationSuggestionService,
|
|
393
|
+
"findOneById",
|
|
394
|
+
);
|
|
395
|
+
mockRow(resourceRoundRow([resourceCommandJson()]));
|
|
396
|
+
|
|
397
|
+
casSpy = jest
|
|
398
|
+
.spyOn(AutoRemediationSuggestionService, "attemptStatusTransition")
|
|
399
|
+
.mockResolvedValue(1);
|
|
400
|
+
|
|
401
|
+
projectFindSpy = jest
|
|
402
|
+
.spyOn(ProjectService, "findOneById")
|
|
403
|
+
.mockResolvedValue(fakeProject());
|
|
404
|
+
|
|
405
|
+
runnerFindSpy = jest.spyOn(RunnerService, "findOneBy").mockResolvedValue({
|
|
406
|
+
_id: RUNNER_ID,
|
|
407
|
+
name: "web-runner-1",
|
|
408
|
+
} as unknown as Runner);
|
|
409
|
+
|
|
410
|
+
statusSpy = jest
|
|
411
|
+
.spyOn(ResourceAiAccessService, "getStatusForResource")
|
|
412
|
+
.mockResolvedValue(resourceStatus());
|
|
413
|
+
|
|
414
|
+
// The approver may edit every resource unless a test says otherwise.
|
|
415
|
+
mayChangeSpy = jest
|
|
416
|
+
.spyOn(ResourceAiAccessService, "assertCallerMayChangeResource")
|
|
417
|
+
.mockResolvedValue(undefined);
|
|
418
|
+
|
|
419
|
+
clusterStatusSpy = jest.spyOn(
|
|
420
|
+
KubernetesClusterAiAccessService,
|
|
421
|
+
"getStatusForCluster",
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
jest
|
|
425
|
+
.spyOn(IncidentFeedService, "createIncidentFeedItem")
|
|
426
|
+
.mockResolvedValue(undefined as never);
|
|
427
|
+
jest
|
|
428
|
+
.spyOn(AlertFeedService, "createAlertFeedItem")
|
|
429
|
+
.mockResolvedValue(undefined as never);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
afterEach(() => {
|
|
433
|
+
jest.restoreAllMocks();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
function expectRefusedBeforeClaim(result: RouteCallResult): BadDataException {
|
|
437
|
+
expect(result.nextCallCount).toBe(1);
|
|
438
|
+
expect(result.thrownToNext).toBeInstanceOf(BadDataException);
|
|
439
|
+
expect(casSpy).not.toHaveBeenCalled();
|
|
440
|
+
expect(executeApprovedPlanMock).not.toHaveBeenCalled();
|
|
441
|
+
expect(sendJsonObjectResponseMock).not.toHaveBeenCalled();
|
|
442
|
+
|
|
443
|
+
return result.thrownToNext as BadDataException;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function expectClaimedAndExecuted(result: RouteCallResult): void {
|
|
447
|
+
expect(result.nextCallCount).toBe(0);
|
|
448
|
+
expect(casSpy).toHaveBeenCalledTimes(1);
|
|
449
|
+
expect(executeApprovedPlanMock).toHaveBeenCalledTimes(1);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
describe("the project switches, by lane", () => {
|
|
453
|
+
test("the root read selects the resource columns that tell a resource round from a rule round", async () => {
|
|
454
|
+
await callApprove();
|
|
455
|
+
|
|
456
|
+
const rootSelect: Record<string, unknown> | undefined = (
|
|
457
|
+
suggestionFindSpy.mock.calls as Array<
|
|
458
|
+
Array<{
|
|
459
|
+
select: Record<string, unknown>;
|
|
460
|
+
props: { isRoot?: boolean };
|
|
461
|
+
}>
|
|
462
|
+
>
|
|
463
|
+
)
|
|
464
|
+
.map(
|
|
465
|
+
(
|
|
466
|
+
call: Array<{
|
|
467
|
+
select: Record<string, unknown>;
|
|
468
|
+
props: { isRoot?: boolean };
|
|
469
|
+
}>,
|
|
470
|
+
) => {
|
|
471
|
+
return call[0]!;
|
|
472
|
+
},
|
|
473
|
+
)
|
|
474
|
+
.find((args: { props: { isRoot?: boolean } }) => {
|
|
475
|
+
return args.props?.isRoot === true;
|
|
476
|
+
})?.select;
|
|
477
|
+
|
|
478
|
+
expect(rootSelect).toEqual(
|
|
479
|
+
expect.objectContaining({
|
|
480
|
+
resourceType: true,
|
|
481
|
+
resourceId: true,
|
|
482
|
+
autoRemediationRuleId: true,
|
|
483
|
+
}),
|
|
484
|
+
);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("a resource round is approvable with AI command execution off, and runs the plan", async () => {
|
|
488
|
+
expectClaimedAndExecuted(await callApprove());
|
|
489
|
+
|
|
490
|
+
// The agent is not a Runner row: the Runner table is never read for it.
|
|
491
|
+
expect(runnerFindSpy).not.toHaveBeenCalled();
|
|
492
|
+
expect(clusterStatusSpy).not.toHaveBeenCalled();
|
|
493
|
+
// One status read, shared by the agent check and the command re-check.
|
|
494
|
+
expect(statusSpy).toHaveBeenCalledTimes(1);
|
|
495
|
+
expect(statusSpy).toHaveBeenCalledWith({
|
|
496
|
+
projectId: PROJECT_ID,
|
|
497
|
+
resourceType: AiResourceType.DockerHost,
|
|
498
|
+
resourceId: new ObjectID(RESOURCE_ID),
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test("a RULE round whose plan has resource commands still needs the opt-in", async () => {
|
|
503
|
+
mockRow(
|
|
504
|
+
resourceRoundRow([resourceCommandJson()], {
|
|
505
|
+
resourceType: undefined,
|
|
506
|
+
resourceId: undefined,
|
|
507
|
+
autoRemediationRuleId: RULE_ID,
|
|
508
|
+
}),
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
512
|
+
await callApprove(),
|
|
513
|
+
);
|
|
514
|
+
expect(error.message).toContain("AI command execution is disabled");
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test.each([
|
|
518
|
+
["AI", { enableAi: false }],
|
|
519
|
+
["auto-remediation", { enableAutoRemediation: false }],
|
|
520
|
+
])(
|
|
521
|
+
"a resource round is stopped when %s is disabled for the project",
|
|
522
|
+
async (_label: string, overrides: Record<string, unknown>) => {
|
|
523
|
+
projectFindSpy.mockResolvedValue(fakeProject(overrides));
|
|
524
|
+
|
|
525
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
526
|
+
await callApprove(),
|
|
527
|
+
);
|
|
528
|
+
expect(error.message).toContain(
|
|
529
|
+
"AI or auto-remediation is disabled for this project",
|
|
530
|
+
);
|
|
531
|
+
},
|
|
532
|
+
);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe("the resource AI agent as the plan's target", () => {
|
|
536
|
+
test("refuses when the agent is offline", async () => {
|
|
537
|
+
statusSpy.mockResolvedValue(
|
|
538
|
+
resourceStatus({
|
|
539
|
+
agent: {
|
|
540
|
+
agentId: AGENT_ID,
|
|
541
|
+
connectionStatus: "connected",
|
|
542
|
+
isOnline: false,
|
|
543
|
+
posture: posture(),
|
|
544
|
+
},
|
|
545
|
+
}),
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
549
|
+
await callApprove(),
|
|
550
|
+
);
|
|
551
|
+
expect(error.message).toContain(
|
|
552
|
+
'The Docker AI agent of Docker host "web-1" is offline',
|
|
553
|
+
);
|
|
554
|
+
expect(error.message).toContain("AI → AI agent");
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test("refuses when the resource is reached through another agent now", async () => {
|
|
558
|
+
statusSpy.mockResolvedValue(
|
|
559
|
+
resourceStatus({
|
|
560
|
+
agent: {
|
|
561
|
+
agentId: OTHER_AGENT_ID,
|
|
562
|
+
connectionStatus: "connected",
|
|
563
|
+
isOnline: true,
|
|
564
|
+
posture: posture(),
|
|
565
|
+
},
|
|
566
|
+
}),
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
570
|
+
await callApprove(),
|
|
571
|
+
);
|
|
572
|
+
expect(error.message).toContain(
|
|
573
|
+
"is no longer reached through the Docker AI agent this plan was composed for",
|
|
574
|
+
);
|
|
575
|
+
expect(runnerFindSpy).not.toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("refuses a Bash step aimed at the resource's agent", async () => {
|
|
579
|
+
mockRow(
|
|
580
|
+
resourceRoundRow([
|
|
581
|
+
resourceCommandJson(),
|
|
582
|
+
{
|
|
583
|
+
sequence: 2,
|
|
584
|
+
stepType: RunbookStepType.Bash,
|
|
585
|
+
runnerId: AGENT_ID,
|
|
586
|
+
runnerNameSnapshot: "Docker AI agent",
|
|
587
|
+
command: "systemctl restart docker",
|
|
588
|
+
timeoutInMs: 30000,
|
|
589
|
+
rationale: "r",
|
|
590
|
+
expectedEffect: "e",
|
|
591
|
+
policyVerdict: "RequiresApproval",
|
|
592
|
+
} as JSONObject,
|
|
593
|
+
]),
|
|
594
|
+
);
|
|
595
|
+
|
|
596
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
597
|
+
await callApprove(),
|
|
598
|
+
);
|
|
599
|
+
expect(error.message).toContain(
|
|
600
|
+
"Command 2 is a Bash step on the Docker AI agent",
|
|
601
|
+
);
|
|
602
|
+
expect(error.message).toContain("runs only ResourceCommand steps");
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
test("refuses a step for another resource sent to the same agent", async () => {
|
|
606
|
+
mockRow(
|
|
607
|
+
resourceRoundRow([
|
|
608
|
+
resourceCommandJson(),
|
|
609
|
+
resourceCommandJson({ sequence: 2, resourceId: OTHER_RESOURCE_ID }),
|
|
610
|
+
]),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
614
|
+
await callApprove(),
|
|
615
|
+
);
|
|
616
|
+
expect(error.message).toContain(
|
|
617
|
+
"Command 2 names a different resource than the Docker AI agent it is sent to serves",
|
|
618
|
+
);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("refuses when the resource was deleted", async () => {
|
|
622
|
+
statusSpy.mockResolvedValue(null);
|
|
623
|
+
|
|
624
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
625
|
+
await callApprove(),
|
|
626
|
+
);
|
|
627
|
+
expect(error.message).toContain(
|
|
628
|
+
'Docker host "web-1" (command 1) no longer exists in this project',
|
|
629
|
+
);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("a Runner step in the same plan is still checked against the Runner table", async () => {
|
|
633
|
+
mockRow(
|
|
634
|
+
resourceRoundRow([
|
|
635
|
+
resourceCommandJson(),
|
|
636
|
+
{
|
|
637
|
+
sequence: 2,
|
|
638
|
+
stepType: RunbookStepType.Bash,
|
|
639
|
+
runnerId: RUNNER_ID,
|
|
640
|
+
runnerNameSnapshot: "web-runner-1",
|
|
641
|
+
command: "systemctl status nginx",
|
|
642
|
+
timeoutInMs: 30000,
|
|
643
|
+
rationale: "r",
|
|
644
|
+
expectedEffect: "e",
|
|
645
|
+
policyVerdict: "RequiresApproval",
|
|
646
|
+
} as JSONObject,
|
|
647
|
+
]),
|
|
648
|
+
);
|
|
649
|
+
runnerFindSpy.mockResolvedValue(null);
|
|
650
|
+
|
|
651
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
652
|
+
await callApprove(),
|
|
653
|
+
);
|
|
654
|
+
expect(error.message).toContain(
|
|
655
|
+
"A Runner this plan targets no longer accepts AI commands",
|
|
656
|
+
);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
describe("the live re-check of every resource command", () => {
|
|
661
|
+
test("refuses when the resource no longer allows remediation, naming the gap and its next step", async () => {
|
|
662
|
+
statusSpy.mockResolvedValue(
|
|
663
|
+
resourceStatus({
|
|
664
|
+
isRemediationReady: false,
|
|
665
|
+
gaps: [
|
|
666
|
+
{
|
|
667
|
+
code: "remediation_write_access_missing",
|
|
668
|
+
title: "The Docker AI agent is read-only",
|
|
669
|
+
nextStep:
|
|
670
|
+
"Set ONEUPTIME_AI_ALLOW_WRITES=true on the Docker AI agent and restart it.",
|
|
671
|
+
blocksInvestigation: false,
|
|
672
|
+
blocksRemediation: true,
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
}),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
679
|
+
await callApprove(),
|
|
680
|
+
);
|
|
681
|
+
expect(error.message).toContain(
|
|
682
|
+
'Docker host "web-1" (command 1) no longer allows AI remediation: The Docker AI agent is read-only — Set ONEUPTIME_AI_ALLOW_WRITES=true',
|
|
683
|
+
);
|
|
684
|
+
expect(error.message).toContain(
|
|
685
|
+
"the Docker host's AI agent page (AI → AI agent)",
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("refuses a command the policy denies now", async () => {
|
|
690
|
+
mockRow(
|
|
691
|
+
resourceRoundRow([
|
|
692
|
+
resourceCommandJson({ command: "docker exec web sh" }),
|
|
693
|
+
]),
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
697
|
+
await callApprove(),
|
|
698
|
+
);
|
|
699
|
+
expect(error.message).toContain(
|
|
700
|
+
"Command 1 is denied by the Docker host command policy",
|
|
701
|
+
);
|
|
702
|
+
expect(error.message).toContain("Nothing ran.");
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("refuses a command the agent's write scope rules out, with the next step", async () => {
|
|
706
|
+
statusSpy.mockResolvedValue(
|
|
707
|
+
resourceStatus({}, { protectedTargets: ["web"] }),
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
711
|
+
await callApprove(),
|
|
712
|
+
);
|
|
713
|
+
expect(error.message).toContain(
|
|
714
|
+
'Command 1 cannot run on Docker host "web-1"',
|
|
715
|
+
);
|
|
716
|
+
expect(error.message).toContain("which the Docker AI agent protects");
|
|
717
|
+
expect(error.message).toContain(
|
|
718
|
+
"change the Docker AI agent's write access (ONEUPTIME_AI_ALLOW_WRITES and ONEUPTIME_AI_WRITE_TARGETS",
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("refuses a plan whose ROLLBACK the agent's write scope rules out", async () => {
|
|
723
|
+
mockRow(
|
|
724
|
+
resourceRoundRow([
|
|
725
|
+
resourceCommandJson({
|
|
726
|
+
command: "docker stop web",
|
|
727
|
+
rollbackCommand: "docker start api",
|
|
728
|
+
}),
|
|
729
|
+
]),
|
|
730
|
+
);
|
|
731
|
+
statusSpy.mockResolvedValue(
|
|
732
|
+
resourceStatus({}, { writeTargets: ["web"] }),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
const error: BadDataException = expectRefusedBeforeClaim(
|
|
736
|
+
await callApprove(),
|
|
737
|
+
);
|
|
738
|
+
expect(error.message).toContain(
|
|
739
|
+
'Command 1\'s rollback cannot run on Docker host "web-1"',
|
|
740
|
+
);
|
|
741
|
+
expect(error.message).toContain("ONEUPTIME_AI_WRITE_TARGETS=web");
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("reads each resource once, however many commands target it", async () => {
|
|
745
|
+
mockRow(
|
|
746
|
+
resourceRoundRow([
|
|
747
|
+
resourceCommandJson(),
|
|
748
|
+
resourceCommandJson({ sequence: 2, command: "docker restart web" }),
|
|
749
|
+
]),
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
expectClaimedAndExecuted(await callApprove());
|
|
753
|
+
expect(statusSpy).toHaveBeenCalledTimes(1);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
/*
|
|
758
|
+
* The plan runs on the resource as root, so being allowed to start
|
|
759
|
+
* runbooks is not enough: the approver must be allowed to EDIT every
|
|
760
|
+
* resource the plan changes (its update ACL, label-scoped blocks
|
|
761
|
+
* included — ResourceAiAccessService.assertCallerMayChangeResource).
|
|
762
|
+
*/
|
|
763
|
+
describe("the approver may change the resource", () => {
|
|
764
|
+
test("checks the approver's own props against each resource the plan changes, once", async () => {
|
|
765
|
+
mockRow(
|
|
766
|
+
resourceRoundRow([
|
|
767
|
+
resourceCommandJson(),
|
|
768
|
+
resourceCommandJson({ sequence: 2, command: "docker restart web" }),
|
|
769
|
+
]),
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
expectClaimedAndExecuted(await callApprove());
|
|
773
|
+
|
|
774
|
+
expect(mayChangeSpy).toHaveBeenCalledTimes(1);
|
|
775
|
+
const args: {
|
|
776
|
+
props: DatabaseCommonInteractionProps;
|
|
777
|
+
projectId: ObjectID;
|
|
778
|
+
resourceType: AiResourceType;
|
|
779
|
+
resourceId: ObjectID;
|
|
780
|
+
} = mayChangeSpy.mock.calls[0]![0];
|
|
781
|
+
expect(args.props.userId?.toString()).toBe(USER_ID.toString());
|
|
782
|
+
expect(args.props.isRoot).toBeFalsy();
|
|
783
|
+
expect(args.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
784
|
+
expect(args.resourceType).toBe(AiResourceType.DockerHost);
|
|
785
|
+
expect(args.resourceId.toString()).toBe(RESOURCE_ID);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test("refuses before claiming the plan when the approver may not edit the resource", async () => {
|
|
789
|
+
mayChangeSpy.mockRejectedValue(
|
|
790
|
+
new NotAuthorizedException(
|
|
791
|
+
"You do not have permission to edit this database server, so you cannot approve an AI plan that changes it.",
|
|
792
|
+
),
|
|
793
|
+
);
|
|
794
|
+
|
|
795
|
+
const result: RouteCallResult = await callApprove();
|
|
796
|
+
|
|
797
|
+
expect(result.nextCallCount).toBe(1);
|
|
798
|
+
expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
|
|
799
|
+
expect(casSpy).not.toHaveBeenCalled();
|
|
800
|
+
expect(executeApprovedPlanMock).not.toHaveBeenCalled();
|
|
801
|
+
expect(sendJsonObjectResponseMock).not.toHaveBeenCalled();
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("a plan with no resource command checks no resource", async () => {
|
|
805
|
+
mockRow(
|
|
806
|
+
resourceRoundRow(
|
|
807
|
+
[
|
|
808
|
+
{
|
|
809
|
+
sequence: 1,
|
|
810
|
+
stepType: RunbookStepType.Bash,
|
|
811
|
+
runnerId: RUNNER_ID,
|
|
812
|
+
runnerNameSnapshot: "web-runner-1",
|
|
813
|
+
command: "systemctl restart nginx",
|
|
814
|
+
timeoutInMs: 30000,
|
|
815
|
+
rationale: "r",
|
|
816
|
+
expectedEffect: "e",
|
|
817
|
+
policyVerdict: "RequiresApproval",
|
|
818
|
+
} as JSONObject,
|
|
819
|
+
],
|
|
820
|
+
{
|
|
821
|
+
resourceType: undefined,
|
|
822
|
+
resourceId: undefined,
|
|
823
|
+
autoRemediationRuleId: RULE_ID,
|
|
824
|
+
},
|
|
825
|
+
),
|
|
826
|
+
);
|
|
827
|
+
projectFindSpy.mockResolvedValue(
|
|
828
|
+
fakeProject({ enableAiCommandExecution: true }),
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
expectClaimedAndExecuted(await callApprove());
|
|
832
|
+
expect(mayChangeSpy).not.toHaveBeenCalled();
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
});
|