@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,964 @@
|
|
|
1
|
+
import DatabaseService from "../../../Services/DatabaseService";
|
|
2
|
+
import UserService from "../../../Services/UserService";
|
|
3
|
+
import CreateBy from "../../../Types/Database/CreateBy";
|
|
4
|
+
import { OnUpdate } from "../../../Types/Database/Hooks";
|
|
5
|
+
import QueryHelper from "../../../Types/Database/QueryHelper";
|
|
6
|
+
import UpdateBy from "../../../Types/Database/UpdateBy";
|
|
7
|
+
import logger from "../../Logger";
|
|
8
|
+
import { holdsAnyPermission } from "../../Runbook/RunbookExecutePermission";
|
|
9
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
10
|
+
import BaseModel from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
11
|
+
import {
|
|
12
|
+
RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS,
|
|
13
|
+
RESOURCE_AI_ALLOWLIST_EXAMPLES,
|
|
14
|
+
RESOURCE_AI_REMEDIATION_MODE_LABELS,
|
|
15
|
+
getResourceAiAccessAdminRefusal,
|
|
16
|
+
getResourceSentenceName,
|
|
17
|
+
} from "../../../../Types/AI/ResourceAiAccessPermissions";
|
|
18
|
+
import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
19
|
+
import { Gray500, Yellow500 } from "../../../../Types/BrandColors";
|
|
20
|
+
import Color from "../../../../Types/Color";
|
|
21
|
+
import LIMIT_MAX from "../../../../Types/Database/LimitMax";
|
|
22
|
+
import OneUptimeDate from "../../../../Types/Date";
|
|
23
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
24
|
+
import NotAuthorizedException from "../../../../Types/Exception/NotAuthorizedException";
|
|
25
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
26
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
27
|
+
import AiResourceType, {
|
|
28
|
+
AI_RESOURCE_TYPE_INFO,
|
|
29
|
+
} from "../../../../Types/ResourceAiAgent/AiResourceType";
|
|
30
|
+
import { ResourceAiRemediationMode } from "../../../../Types/ResourceAiAgent/ResourceAiAccess";
|
|
31
|
+
import ResourceCommandPolicy, {
|
|
32
|
+
RESOURCE_ALLOWLIST_MAX_PATTERNS,
|
|
33
|
+
} from "../../../../Utils/AiRemediation/Resource/ResourceCommandPolicy";
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* The rules for an operator's write of a resource's OneUptime AI access
|
|
37
|
+
* settings — shared by every resource a resource AI agent serves (Docker,
|
|
38
|
+
* Podman and Docker Swarm hosts, Proxmox clusters, VMware vCenters, Ceph
|
|
39
|
+
* clusters, database servers and hosts), whose services call it from their
|
|
40
|
+
* create and update hooks. The same rules KubernetesClusterService applies
|
|
41
|
+
* to a cluster (getAiAccessLoosening and friends), minus the Runner and
|
|
42
|
+
* credential bindings a resource AI agent never has:
|
|
43
|
+
*
|
|
44
|
+
* - validation: the remediation mode must be a ResourceAiRemediationMode
|
|
45
|
+
* value, and the command allowlist a list of at most
|
|
46
|
+
* RESOURCE_ALLOWLIST_MAX_PATTERNS entries that
|
|
47
|
+
* ResourceCommandPolicy.describeAllowlistPatternProblem accepts for the
|
|
48
|
+
* resource's type (the ONE definition of a valid entry, which the matcher
|
|
49
|
+
* and the dashboard share). Refused rather than stored: every reader fails
|
|
50
|
+
* safe on an unusable value, so storing one would answer an API or
|
|
51
|
+
* Terraform write with success for a setting that never takes effect;
|
|
52
|
+
* - who may make AI do MORE (any move of the mode up — Off to anything, up to
|
|
53
|
+
* Automatic or Bypass approval — or adding an allowlist pattern): the
|
|
54
|
+
* holders of RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS, the people who may
|
|
55
|
+
* author a FullAuto auto-remediation rule. Making AI do less, and the
|
|
56
|
+
* investigation switch, stay open to every editor of the resource;
|
|
57
|
+
* - the aiAccess* columns (aiAccessConfiguredAt, aiAccessLastVerifiedAt,
|
|
58
|
+
* aiAccessLastError) are the server's own and refused on every write but
|
|
59
|
+
* the server's (root);
|
|
60
|
+
* - after an operator's write of any AI setting succeeded: the resource is
|
|
61
|
+
* marked AI-configured (aiAccessConfiguredAt, never cleared — a resource
|
|
62
|
+
* AI agent registering later then leaves every setting as chosen) and the
|
|
63
|
+
* change is recorded on the resource's feed.
|
|
64
|
+
*
|
|
65
|
+
* Root writes — an agent's first-connection defaults, the outcome
|
|
66
|
+
* bookkeeping after each command — are the server's own and are validated
|
|
67
|
+
* but never gated, marked or recorded here.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
// Every AI access setting an operator can write (dashboard, API, Terraform).
|
|
71
|
+
export const RESOURCE_AI_ACCESS_SETTING_KEYS: ReadonlyArray<string> = [
|
|
72
|
+
"isAiInvestigationEnabled",
|
|
73
|
+
"aiRemediationMode",
|
|
74
|
+
"aiCommandAllowlist",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Written only by the server: refused on every write that is not root.
|
|
78
|
+
export const RESOURCE_AI_ACCESS_SERVER_ONLY_KEYS: ReadonlyArray<string> = [
|
|
79
|
+
"aiAccessConfiguredAt",
|
|
80
|
+
"aiAccessLastVerifiedAt",
|
|
81
|
+
"aiAccessLastError",
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* How much each remediation mode lets OneUptime AI do without a human,
|
|
86
|
+
* least first. Automatic runs a strict subset of what Bypass approval runs
|
|
87
|
+
* (safe changes and allowlisted shapes, never an unlisted riskier change),
|
|
88
|
+
* so moving from Bypass approval to Automatic is a tightening.
|
|
89
|
+
*/
|
|
90
|
+
export const RESOURCE_REMEDIATION_MODES_BY_AUTONOMY: ReadonlyArray<ResourceAiRemediationMode> =
|
|
91
|
+
[
|
|
92
|
+
ResourceAiRemediationMode.Disabled,
|
|
93
|
+
ResourceAiRemediationMode.RequireApproval,
|
|
94
|
+
ResourceAiRemediationMode.Automatic,
|
|
95
|
+
ResourceAiRemediationMode.BypassApproval,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* A resource's AI access settings as they stood before an operator's
|
|
100
|
+
* write: the baseline "does this write loosen anything?" is decided
|
|
101
|
+
* against, and the "before" half of the feed item that records the change.
|
|
102
|
+
* Normalized the way the readers normalize them (an unknown mode is
|
|
103
|
+
* Disabled, an unusable allowlist is empty), because that is what was in
|
|
104
|
+
* effect.
|
|
105
|
+
*/
|
|
106
|
+
export interface ResourceAiAccessSettingsSnapshot {
|
|
107
|
+
projectId?: ObjectID | undefined;
|
|
108
|
+
isAiInvestigationEnabled: boolean;
|
|
109
|
+
aiRemediationMode: ResourceAiRemediationMode;
|
|
110
|
+
aiCommandAllowlist: Array<string>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* What a resource that was never AI-configured has: the column defaults
|
|
115
|
+
* (investigation off, remediation Disabled, no allowlist).
|
|
116
|
+
*/
|
|
117
|
+
export const NEVER_CONFIGURED_RESOURCE_AI_ACCESS: Readonly<ResourceAiAccessSettingsSnapshot> =
|
|
118
|
+
{
|
|
119
|
+
isAiInvestigationEnabled: false,
|
|
120
|
+
aiRemediationMode: ResourceAiRemediationMode.Disabled,
|
|
121
|
+
aiCommandAllowlist: [],
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* Handed from a resource service's onBeforeUpdate to its onUpdateSuccess
|
|
126
|
+
* for an operator's write of an AI access setting, keyed by resource id.
|
|
127
|
+
* Its presence is the signal that the write must mark the resource
|
|
128
|
+
* configured and be recorded on its feed — both happen only after the
|
|
129
|
+
* update succeeded.
|
|
130
|
+
*/
|
|
131
|
+
export interface ResourceAiAccessWriteCarryForward {
|
|
132
|
+
resourceType: AiResourceType;
|
|
133
|
+
previousResourceAiAccessSettings: Record<
|
|
134
|
+
string,
|
|
135
|
+
ResourceAiAccessSettingsSnapshot
|
|
136
|
+
>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// What an operator's write would do to one resource's AI access.
|
|
140
|
+
export interface ResourceAiAccessLoosening {
|
|
141
|
+
loosens: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// One "AI access settings changed" item for a resource's feed.
|
|
145
|
+
export interface ResourceAiAccessFeedItem {
|
|
146
|
+
resourceId: ObjectID;
|
|
147
|
+
projectId: ObjectID;
|
|
148
|
+
displayColor: Color;
|
|
149
|
+
feedInfoInMarkdown: string;
|
|
150
|
+
moreInformationInMarkdown: string;
|
|
151
|
+
userId?: ObjectID | undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// The columns the settings snapshot reads off a resource row.
|
|
155
|
+
const SETTINGS_SELECT: Record<string, boolean> = {
|
|
156
|
+
_id: true,
|
|
157
|
+
projectId: true,
|
|
158
|
+
isAiInvestigationEnabled: true,
|
|
159
|
+
aiRemediationMode: true,
|
|
160
|
+
aiCommandAllowlist: true,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Undefined is an omitted property; null is an explicit clear, so a write.
|
|
164
|
+
function isAnyKeyWritten(
|
|
165
|
+
data: JSONObject,
|
|
166
|
+
keys: ReadonlyArray<string>,
|
|
167
|
+
): boolean {
|
|
168
|
+
return keys.some((key: string): boolean => {
|
|
169
|
+
return data[key] !== undefined;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function describePatternCount(count: number): string {
|
|
174
|
+
if (count === 0) {
|
|
175
|
+
return "none";
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return `${count} pattern${count === 1 ? "" : "s"}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isSameAllowlist(left: Array<string>, right: Array<string>): boolean {
|
|
182
|
+
return (
|
|
183
|
+
left.length === right.length &&
|
|
184
|
+
left.every((pattern: string, index: number): boolean => {
|
|
185
|
+
return pattern === right[index];
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default class ResourceAiAccessSettings {
|
|
191
|
+
// Whether a write carries any AI access setting (null counts: it clears).
|
|
192
|
+
public static isSettingWritten(data: JSONObject): boolean {
|
|
193
|
+
return isAnyKeyWritten(data, RESOURCE_AI_ACCESS_SETTING_KEYS);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* Why a write that is not the server's may not carry one of the aiAccess*
|
|
198
|
+
* columns, or null when it carries none. The column ACLs already refuse
|
|
199
|
+
* them to every role; this also covers the callers those checks let
|
|
200
|
+
* through (a master admin, an API that skips them), because a hand-set
|
|
201
|
+
* aiAccessConfiguredAt would make an agent skip its first-connection
|
|
202
|
+
* defaults and a hand-set "Last verified" would claim access nobody saw
|
|
203
|
+
* work.
|
|
204
|
+
*/
|
|
205
|
+
public static getServerOnlyColumnRefusal(data: JSONObject): string | null {
|
|
206
|
+
const written: Array<string> = RESOURCE_AI_ACCESS_SERVER_ONLY_KEYS.filter(
|
|
207
|
+
(key: string): boolean => {
|
|
208
|
+
return data[key] !== undefined;
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (written.length === 0) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return `${written.join(", ")} ${
|
|
217
|
+
written.length === 1 ? "is" : "are"
|
|
218
|
+
} written by OneUptime itself and cannot be set through the API.`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// A mode as the readers see it: anything unknown is Disabled.
|
|
222
|
+
public static readStoredMode(value: unknown): ResourceAiRemediationMode {
|
|
223
|
+
return RESOURCE_REMEDIATION_MODES_BY_AUTONOMY.includes(
|
|
224
|
+
value as ResourceAiRemediationMode,
|
|
225
|
+
)
|
|
226
|
+
? (value as ResourceAiRemediationMode)
|
|
227
|
+
: ResourceAiRemediationMode.Disabled;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/*
|
|
231
|
+
* A stored allowlist as the readers see it: trimmed non-empty strings, a
|
|
232
|
+
* JSON-encoded array accepted, anything else empty.
|
|
233
|
+
*/
|
|
234
|
+
public static readStoredAllowlist(value: unknown): Array<string> {
|
|
235
|
+
let raw: unknown = value;
|
|
236
|
+
|
|
237
|
+
if (typeof raw === "string") {
|
|
238
|
+
try {
|
|
239
|
+
raw = JSON.parse(raw);
|
|
240
|
+
} catch {
|
|
241
|
+
raw = [value];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!Array.isArray(raw)) {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return raw
|
|
250
|
+
.filter((pattern: unknown): boolean => {
|
|
251
|
+
return typeof pattern === "string" && pattern.trim().length > 0;
|
|
252
|
+
})
|
|
253
|
+
.map((pattern: string): string => {
|
|
254
|
+
return pattern.trim();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public static getAllowlistShapeHint(resourceType: AiResourceType): string {
|
|
259
|
+
const programs: string = (
|
|
260
|
+
AI_RESOURCE_TYPE_INFO[resourceType]?.programs || []
|
|
261
|
+
).join(", ");
|
|
262
|
+
|
|
263
|
+
return `The AI command allowlist must be a JSON array of command patterns for this ${getResourceSentenceName(
|
|
264
|
+
resourceType,
|
|
265
|
+
)}'s AI agent (${programs}), for example ["${
|
|
266
|
+
RESOURCE_AI_ALLOWLIST_EXAMPLES[resourceType] || ""
|
|
267
|
+
}"].`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/*
|
|
271
|
+
* What an operator's allowlist write stores: null (no allowlist) or an
|
|
272
|
+
* array of trimmed patterns the matcher can actually use. Refused, with the
|
|
273
|
+
* entry and the rule it broke:
|
|
274
|
+
*
|
|
275
|
+
* - anything but an array (a JSON-encoded array in a string is accepted,
|
|
276
|
+
* the form the readers already understand, and stored as the array);
|
|
277
|
+
* - an entry that is not text;
|
|
278
|
+
* - more than RESOURCE_ALLOWLIST_MAX_PATTERNS entries — the matcher skips
|
|
279
|
+
* every pattern past that silently;
|
|
280
|
+
* - an entry ResourceCommandPolicy.describeAllowlistPatternProblem says
|
|
281
|
+
* can never pre-approve a change on this resource type (blank, too long,
|
|
282
|
+
* not one command, another program, a read, a Denied command, ...).
|
|
283
|
+
*/
|
|
284
|
+
public static normalizeAllowlistForWrite(data: {
|
|
285
|
+
resourceType: AiResourceType;
|
|
286
|
+
value: unknown;
|
|
287
|
+
}): Array<string> | null {
|
|
288
|
+
let raw: unknown = data.value;
|
|
289
|
+
|
|
290
|
+
if (raw === null) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (typeof raw === "string") {
|
|
295
|
+
if (raw.trim().length === 0) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
raw = JSON.parse(raw);
|
|
301
|
+
} catch {
|
|
302
|
+
throw new BadDataException(
|
|
303
|
+
ResourceAiAccessSettings.getAllowlistShapeHint(data.resourceType),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!Array.isArray(raw)) {
|
|
309
|
+
throw new BadDataException(
|
|
310
|
+
ResourceAiAccessSettings.getAllowlistShapeHint(data.resourceType),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (raw.length > RESOURCE_ALLOWLIST_MAX_PATTERNS) {
|
|
315
|
+
throw new BadDataException(
|
|
316
|
+
`The AI command allowlist can hold at most ${RESOURCE_ALLOWLIST_MAX_PATTERNS} patterns (got ${raw.length}).`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return raw.map((entry: unknown, index: number): string => {
|
|
321
|
+
const position: string = `Pattern ${index + 1} of the AI command allowlist`;
|
|
322
|
+
|
|
323
|
+
if (typeof entry !== "string") {
|
|
324
|
+
throw new BadDataException(
|
|
325
|
+
`${position} must be text (got ${
|
|
326
|
+
entry === null ? "null" : typeof entry
|
|
327
|
+
}). ${ResourceAiAccessSettings.getAllowlistShapeHint(
|
|
328
|
+
data.resourceType,
|
|
329
|
+
)}`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Stored trimmed, so it is judged exactly as it will be read.
|
|
334
|
+
const pattern: string = entry.trim();
|
|
335
|
+
|
|
336
|
+
const problem: string | null =
|
|
337
|
+
ResourceCommandPolicy.describeAllowlistPatternProblem({
|
|
338
|
+
resourceType: data.resourceType,
|
|
339
|
+
pattern,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
if (problem) {
|
|
343
|
+
throw new BadDataException(
|
|
344
|
+
`${position} cannot be used: ${
|
|
345
|
+
problem.endsWith(".") ? problem : `${problem}.`
|
|
346
|
+
} Patterns are compared with the command word by word; * stands for exactly one whole word.`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return pattern;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/*
|
|
355
|
+
* Refuse an unknown mode and store the allowlist in the one shape its
|
|
356
|
+
* readers expect. Runs on every write, the server's included, and
|
|
357
|
+
* normalizes `data` in place.
|
|
358
|
+
*/
|
|
359
|
+
public static validateSettings(data: {
|
|
360
|
+
resourceType: AiResourceType;
|
|
361
|
+
data: JSONObject;
|
|
362
|
+
}): void {
|
|
363
|
+
const mode: unknown = data.data["aiRemediationMode"];
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
mode !== undefined &&
|
|
367
|
+
!RESOURCE_REMEDIATION_MODES_BY_AUTONOMY.includes(
|
|
368
|
+
mode as ResourceAiRemediationMode,
|
|
369
|
+
)
|
|
370
|
+
) {
|
|
371
|
+
throw new BadDataException(
|
|
372
|
+
`AI remediation mode must be one of ${RESOURCE_REMEDIATION_MODES_BY_AUTONOMY.join(
|
|
373
|
+
", ",
|
|
374
|
+
)} (got ${JSON.stringify(mode)}).`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (data.data["aiCommandAllowlist"] !== undefined) {
|
|
379
|
+
data.data["aiCommandAllowlist"] =
|
|
380
|
+
ResourceAiAccessSettings.normalizeAllowlistForWrite({
|
|
381
|
+
resourceType: data.resourceType,
|
|
382
|
+
value: data.data["aiCommandAllowlist"],
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/*
|
|
388
|
+
* Does this write let AI do more on a resource whose settings are
|
|
389
|
+
* `current`? Only a real change counts: the AI agent page posts every
|
|
390
|
+
* field of its form, so an editor who only flips the investigation switch
|
|
391
|
+
* re-posts an unchanged mode and allowlist, and must not be refused for
|
|
392
|
+
* settings someone else chose.
|
|
393
|
+
*
|
|
394
|
+
* - mode: ANY move up — turning fixes on from Off (even to Ask for
|
|
395
|
+
* approval: that is the step that lets AI change the resource at all),
|
|
396
|
+
* or up to Automatic or Bypass approval. Moving down (Bypass approval ->
|
|
397
|
+
* Automatic, anything -> Off) tightens;
|
|
398
|
+
* - allowlist: adding a pattern the resource did not already have
|
|
399
|
+
* (removing patterns, or clearing the list, tightens).
|
|
400
|
+
*
|
|
401
|
+
* `data` has been through validateSettings, so the mode is a known value
|
|
402
|
+
* and the allowlist a trimmed array or null.
|
|
403
|
+
*/
|
|
404
|
+
public static getLoosening(data: {
|
|
405
|
+
data: JSONObject;
|
|
406
|
+
current: ResourceAiAccessSettingsSnapshot;
|
|
407
|
+
}): ResourceAiAccessLoosening {
|
|
408
|
+
let loosens: boolean = false;
|
|
409
|
+
|
|
410
|
+
const mode: unknown = data.data["aiRemediationMode"];
|
|
411
|
+
|
|
412
|
+
if (
|
|
413
|
+
mode !== undefined &&
|
|
414
|
+
mode !== null &&
|
|
415
|
+
RESOURCE_REMEDIATION_MODES_BY_AUTONOMY.indexOf(
|
|
416
|
+
ResourceAiAccessSettings.readStoredMode(mode),
|
|
417
|
+
) >
|
|
418
|
+
RESOURCE_REMEDIATION_MODES_BY_AUTONOMY.indexOf(
|
|
419
|
+
data.current.aiRemediationMode,
|
|
420
|
+
)
|
|
421
|
+
) {
|
|
422
|
+
loosens = true;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (data.data["aiCommandAllowlist"] !== undefined) {
|
|
426
|
+
const patterns: Array<string> =
|
|
427
|
+
ResourceAiAccessSettings.readStoredAllowlist(
|
|
428
|
+
data.data["aiCommandAllowlist"],
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
if (
|
|
432
|
+
patterns.some((pattern: string): boolean => {
|
|
433
|
+
return !data.current.aiCommandAllowlist.includes(pattern);
|
|
434
|
+
})
|
|
435
|
+
) {
|
|
436
|
+
loosens = true;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return { loosens };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/*
|
|
444
|
+
* Why this caller may not make this write, or null when they may. A
|
|
445
|
+
* resource's AI mode does the job of a FullAuto AutoRemediationRule with
|
|
446
|
+
* no rule row, so making AI do MORE takes the same permissions as
|
|
447
|
+
* authoring such a rule (RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS). The
|
|
448
|
+
* column ACLs stay open to every editor of the resource on purpose: a
|
|
449
|
+
* column ACL cannot say "tightening is free", and turning AI remediation
|
|
450
|
+
* off must never need more than editing the resource.
|
|
451
|
+
*
|
|
452
|
+
* `current` holds the settings of every resource the write reaches, and
|
|
453
|
+
* the permission must be held in each one's own project. When it matched
|
|
454
|
+
* none, the write is judged against the never-configured defaults in the
|
|
455
|
+
* caller's tenant, so the answer does not depend on whether the id exists;
|
|
456
|
+
* with no project to check against at all it fails closed.
|
|
457
|
+
*
|
|
458
|
+
* Root and master admins are the caller's business (see checkUpdate).
|
|
459
|
+
*/
|
|
460
|
+
public static getLooseningRefusal(data: {
|
|
461
|
+
resourceType: AiResourceType;
|
|
462
|
+
data: JSONObject;
|
|
463
|
+
props: DatabaseCommonInteractionProps;
|
|
464
|
+
current: Array<ResourceAiAccessSettingsSnapshot>;
|
|
465
|
+
}): string | null {
|
|
466
|
+
const baselines: Array<ResourceAiAccessSettingsSnapshot> =
|
|
467
|
+
data.current.length > 0
|
|
468
|
+
? data.current
|
|
469
|
+
: [
|
|
470
|
+
{
|
|
471
|
+
...NEVER_CONFIGURED_RESOURCE_AI_ACCESS,
|
|
472
|
+
projectId: data.props.tenantId,
|
|
473
|
+
},
|
|
474
|
+
];
|
|
475
|
+
|
|
476
|
+
for (const baseline of baselines) {
|
|
477
|
+
if (
|
|
478
|
+
!ResourceAiAccessSettings.getLoosening({
|
|
479
|
+
data: data.data,
|
|
480
|
+
current: baseline,
|
|
481
|
+
}).loosens
|
|
482
|
+
) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (
|
|
487
|
+
!baseline.projectId ||
|
|
488
|
+
!holdsAnyPermission({
|
|
489
|
+
props: data.props,
|
|
490
|
+
projectId: baseline.projectId,
|
|
491
|
+
allowed: RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS,
|
|
492
|
+
})
|
|
493
|
+
) {
|
|
494
|
+
return getResourceAiAccessAdminRefusal(data.resourceType);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/*
|
|
502
|
+
* The whole update-hook rule, for a resource service's onBeforeUpdate:
|
|
503
|
+
* validate (every caller), refuse the server-only columns and gate a
|
|
504
|
+
* loosening (every caller but root; a master admin is not gated on who
|
|
505
|
+
* may loosen, as on a Kubernetes cluster). Returns the carry-forward for
|
|
506
|
+
* onUpdateSuccess — the settings as they stood — for an operator's write
|
|
507
|
+
* of an AI setting, else null.
|
|
508
|
+
*/
|
|
509
|
+
@CaptureSpan()
|
|
510
|
+
public static async checkUpdate<TBaseModel extends BaseModel>(data: {
|
|
511
|
+
resourceType: AiResourceType;
|
|
512
|
+
service: DatabaseService<TBaseModel>;
|
|
513
|
+
updateBy: UpdateBy<TBaseModel>;
|
|
514
|
+
}): Promise<ResourceAiAccessWriteCarryForward | null> {
|
|
515
|
+
const updateData: JSONObject = (data.updateBy.data ||
|
|
516
|
+
{}) as unknown as JSONObject;
|
|
517
|
+
|
|
518
|
+
ResourceAiAccessSettings.validateSettings({
|
|
519
|
+
resourceType: data.resourceType,
|
|
520
|
+
data: updateData,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
if (data.updateBy.props.isRoot) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const serverOnlyRefusal: string | null =
|
|
528
|
+
ResourceAiAccessSettings.getServerOnlyColumnRefusal(updateData);
|
|
529
|
+
|
|
530
|
+
if (serverOnlyRefusal) {
|
|
531
|
+
throw new NotAuthorizedException(serverOnlyRefusal);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (!ResourceAiAccessSettings.isSettingWritten(updateData)) {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const previousResourceAiAccessSettings: Record<
|
|
539
|
+
string,
|
|
540
|
+
ResourceAiAccessSettingsSnapshot
|
|
541
|
+
> = await ResourceAiAccessSettings.readSettingsForUpdateQuery({
|
|
542
|
+
service: data.service,
|
|
543
|
+
updateBy: data.updateBy,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
if (!data.updateBy.props.isMasterAdmin) {
|
|
547
|
+
const refusal: string | null =
|
|
548
|
+
ResourceAiAccessSettings.getLooseningRefusal({
|
|
549
|
+
resourceType: data.resourceType,
|
|
550
|
+
data: updateData,
|
|
551
|
+
props: data.updateBy.props,
|
|
552
|
+
current: Object.values(previousResourceAiAccessSettings),
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
if (refusal) {
|
|
556
|
+
throw new NotAuthorizedException(refusal);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
resourceType: data.resourceType,
|
|
562
|
+
previousResourceAiAccessSettings,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/*
|
|
567
|
+
* The same rule for a resource service's onBeforeCreate, judged against
|
|
568
|
+
* the never-configured defaults a new resource starts from (as
|
|
569
|
+
* KubernetesClusterService.onBeforeCreate judges a new cluster): validate
|
|
570
|
+
* (every caller, root included), refuse the server-only columns (every
|
|
571
|
+
* caller but root — a master admin included, whom the create column ACLs
|
|
572
|
+
* never check) and gate a loosening (every caller but root and master
|
|
573
|
+
* admins). The create column ACLs of the AI columns are empty, so a
|
|
574
|
+
* user's create cannot carry them today and the gate is defence in depth.
|
|
575
|
+
* Normalizes the allowlist in `createBy.data` in place.
|
|
576
|
+
*/
|
|
577
|
+
public static checkCreate<TBaseModel extends BaseModel>(data: {
|
|
578
|
+
resourceType: AiResourceType;
|
|
579
|
+
createBy: CreateBy<TBaseModel>;
|
|
580
|
+
}): void {
|
|
581
|
+
const createData: JSONObject = (data.createBy.data ||
|
|
582
|
+
{}) as unknown as JSONObject;
|
|
583
|
+
const props: DatabaseCommonInteractionProps = data.createBy.props;
|
|
584
|
+
|
|
585
|
+
ResourceAiAccessSettings.validateSettings({
|
|
586
|
+
resourceType: data.resourceType,
|
|
587
|
+
data: createData,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
if (props.isRoot) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const serverOnlyRefusal: string | null =
|
|
595
|
+
ResourceAiAccessSettings.getServerOnlyColumnRefusal(createData);
|
|
596
|
+
|
|
597
|
+
if (serverOnlyRefusal) {
|
|
598
|
+
throw new NotAuthorizedException(serverOnlyRefusal);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (
|
|
602
|
+
props.isMasterAdmin ||
|
|
603
|
+
!ResourceAiAccessSettings.isSettingWritten(createData)
|
|
604
|
+
) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// The tenant column is stamped from props.tenantId before this hook.
|
|
609
|
+
const projectId: ObjectID | undefined =
|
|
610
|
+
props.tenantId ||
|
|
611
|
+
(createData["projectId"] as ObjectID | undefined) ||
|
|
612
|
+
undefined;
|
|
613
|
+
|
|
614
|
+
const refusal: string | null = ResourceAiAccessSettings.getLooseningRefusal(
|
|
615
|
+
{
|
|
616
|
+
resourceType: data.resourceType,
|
|
617
|
+
data: createData,
|
|
618
|
+
props,
|
|
619
|
+
current: [{ ...NEVER_CONFIGURED_RESOURCE_AI_ACCESS, projectId }],
|
|
620
|
+
},
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
if (refusal) {
|
|
624
|
+
throw new NotAuthorizedException(refusal);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/*
|
|
629
|
+
* The AI access settings of every resource an operator's update reaches,
|
|
630
|
+
* read before the write. onBeforeUpdate runs before the framework scopes
|
|
631
|
+
* the query to the caller's project, so scope it here: a caller must
|
|
632
|
+
* never learn anything about, or be judged against, another project's
|
|
633
|
+
* resource.
|
|
634
|
+
*/
|
|
635
|
+
public static async readSettingsForUpdateQuery<
|
|
636
|
+
TBaseModel extends BaseModel,
|
|
637
|
+
>(data: {
|
|
638
|
+
service: DatabaseService<TBaseModel>;
|
|
639
|
+
updateBy: UpdateBy<TBaseModel>;
|
|
640
|
+
}): Promise<Record<string, ResourceAiAccessSettingsSnapshot>> {
|
|
641
|
+
const rows: Array<TBaseModel> = await data.service.findBy({
|
|
642
|
+
query: {
|
|
643
|
+
...data.updateBy.query,
|
|
644
|
+
...(data.updateBy.props.tenantId
|
|
645
|
+
? { projectId: data.updateBy.props.tenantId }
|
|
646
|
+
: {}),
|
|
647
|
+
} as never,
|
|
648
|
+
select: SETTINGS_SELECT as never,
|
|
649
|
+
limit: LIMIT_MAX,
|
|
650
|
+
skip: 0,
|
|
651
|
+
props: {
|
|
652
|
+
isRoot: true,
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const settings: Record<string, ResourceAiAccessSettingsSnapshot> = {};
|
|
657
|
+
|
|
658
|
+
for (const row of rows) {
|
|
659
|
+
const record: Record<string, unknown> = row as unknown as Record<
|
|
660
|
+
string,
|
|
661
|
+
unknown
|
|
662
|
+
>;
|
|
663
|
+
const resourceId: string | undefined =
|
|
664
|
+
row.id?.toString() || row._id?.toString();
|
|
665
|
+
|
|
666
|
+
if (!resourceId) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
settings[resourceId] = {
|
|
671
|
+
projectId: record["projectId"] as ObjectID | undefined,
|
|
672
|
+
isAiInvestigationEnabled: record["isAiInvestigationEnabled"] === true,
|
|
673
|
+
aiRemediationMode: ResourceAiAccessSettings.readStoredMode(
|
|
674
|
+
record["aiRemediationMode"],
|
|
675
|
+
),
|
|
676
|
+
aiCommandAllowlist: ResourceAiAccessSettings.readStoredAllowlist(
|
|
677
|
+
record["aiCommandAllowlist"],
|
|
678
|
+
),
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return settings;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
public static getWriteCarryForward(
|
|
686
|
+
carryForward: unknown,
|
|
687
|
+
): ResourceAiAccessWriteCarryForward | null {
|
|
688
|
+
if (
|
|
689
|
+
carryForward &&
|
|
690
|
+
typeof carryForward === "object" &&
|
|
691
|
+
"previousResourceAiAccessSettings" in carryForward
|
|
692
|
+
) {
|
|
693
|
+
return carryForward as ResourceAiAccessWriteCarryForward;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/*
|
|
700
|
+
* For a resource service's onUpdateSuccess: after an operator's write of
|
|
701
|
+
* an AI setting, record it on the resource's feed (fire and forget: the
|
|
702
|
+
* write has committed, so who changed what AI may do is recorded even
|
|
703
|
+
* when the marker write below fails) and mark each updated resource
|
|
704
|
+
* AI-configured (awaited: a registering agent reads the marker to tell an
|
|
705
|
+
* operator's "off" from a resource nobody ever configured, so a save that
|
|
706
|
+
* could not record it must not look like one that did).
|
|
707
|
+
*
|
|
708
|
+
* The service supplies its feed: how its resources are linked in
|
|
709
|
+
* markdown, and how an item is written (its own "updated" event type).
|
|
710
|
+
*/
|
|
711
|
+
@CaptureSpan()
|
|
712
|
+
public static async afterUpdate<TBaseModel extends BaseModel>(data: {
|
|
713
|
+
service: DatabaseService<TBaseModel>;
|
|
714
|
+
onUpdate: OnUpdate<TBaseModel>;
|
|
715
|
+
updatedItemIds: Array<ObjectID>;
|
|
716
|
+
getResourceMarkdownLink: (
|
|
717
|
+
projectId: ObjectID,
|
|
718
|
+
resourceId: ObjectID,
|
|
719
|
+
) => Promise<string>;
|
|
720
|
+
createFeedItem: (item: ResourceAiAccessFeedItem) => Promise<void>;
|
|
721
|
+
}): Promise<void> {
|
|
722
|
+
const carryForward: ResourceAiAccessWriteCarryForward | null =
|
|
723
|
+
ResourceAiAccessSettings.getWriteCarryForward(data.onUpdate.carryForward);
|
|
724
|
+
|
|
725
|
+
if (!carryForward || data.updatedItemIds.length === 0) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
ResourceAiAccessSettings.writeSettingsChangedFeed({
|
|
730
|
+
service: data.service,
|
|
731
|
+
carryForward,
|
|
732
|
+
updateData: (data.onUpdate.updateBy.data || {}) as unknown as JSONObject,
|
|
733
|
+
updatedByUserId: data.onUpdate.updateBy.props.userId || undefined,
|
|
734
|
+
updatedItemIds: data.updatedItemIds,
|
|
735
|
+
getResourceMarkdownLink: data.getResourceMarkdownLink,
|
|
736
|
+
createFeedItem: data.createFeedItem,
|
|
737
|
+
}).catch((error: Error) => {
|
|
738
|
+
logger.error(error);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
await ResourceAiAccessSettings.markConfigured({
|
|
742
|
+
service: data.service,
|
|
743
|
+
resourceIds: data.updatedItemIds,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/*
|
|
748
|
+
* Set aiAccessConfiguredAt on each resource that does not have it yet, and
|
|
749
|
+
* never touch one that does: the marker records when AI access was FIRST
|
|
750
|
+
* configured. A server-only column, so it is written here, as root, after
|
|
751
|
+
* the operator's own write succeeded, rather than slipped into that write.
|
|
752
|
+
*/
|
|
753
|
+
public static async markConfigured<TBaseModel extends BaseModel>(data: {
|
|
754
|
+
service: DatabaseService<TBaseModel>;
|
|
755
|
+
resourceIds: Array<ObjectID>;
|
|
756
|
+
}): Promise<void> {
|
|
757
|
+
if (data.resourceIds.length === 0) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
await data.service.updateBy({
|
|
762
|
+
query: {
|
|
763
|
+
_id: QueryHelper.any(data.resourceIds),
|
|
764
|
+
aiAccessConfiguredAt: QueryHelper.isNull(),
|
|
765
|
+
} as never,
|
|
766
|
+
data: {
|
|
767
|
+
aiAccessConfiguredAt: OneUptimeDate.getCurrentDate(),
|
|
768
|
+
} as never,
|
|
769
|
+
limit: LIMIT_MAX,
|
|
770
|
+
skip: 0,
|
|
771
|
+
props: {
|
|
772
|
+
isRoot: true,
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/*
|
|
778
|
+
* One line per AI access setting this write actually changed, old -> new.
|
|
779
|
+
* With no `previous` (the resource was not among those read before the
|
|
780
|
+
* write) every written setting is reported as set, without a "from".
|
|
781
|
+
*/
|
|
782
|
+
public static describeChanges(data: {
|
|
783
|
+
updateData: JSONObject;
|
|
784
|
+
previous: ResourceAiAccessSettingsSnapshot | undefined;
|
|
785
|
+
}): Array<string> {
|
|
786
|
+
const changes: Array<string> = [];
|
|
787
|
+
const { updateData, previous } = data;
|
|
788
|
+
|
|
789
|
+
if (updateData["isAiInvestigationEnabled"] !== undefined) {
|
|
790
|
+
const isEnabled: boolean =
|
|
791
|
+
updateData["isAiInvestigationEnabled"] === true;
|
|
792
|
+
|
|
793
|
+
if (!previous || previous.isAiInvestigationEnabled !== isEnabled) {
|
|
794
|
+
changes.push(
|
|
795
|
+
`AI investigation with read-only commands turned **${
|
|
796
|
+
isEnabled ? "on" : "off"
|
|
797
|
+
}**`,
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (updateData["aiRemediationMode"] !== undefined) {
|
|
803
|
+
const mode: ResourceAiRemediationMode =
|
|
804
|
+
ResourceAiAccessSettings.readStoredMode(
|
|
805
|
+
updateData["aiRemediationMode"],
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
if (!previous) {
|
|
809
|
+
changes.push(
|
|
810
|
+
`AI remediation set to **${RESOURCE_AI_REMEDIATION_MODE_LABELS[mode]}**`,
|
|
811
|
+
);
|
|
812
|
+
} else if (previous.aiRemediationMode !== mode) {
|
|
813
|
+
changes.push(
|
|
814
|
+
`AI remediation changed from **${
|
|
815
|
+
RESOURCE_AI_REMEDIATION_MODE_LABELS[previous.aiRemediationMode]
|
|
816
|
+
}** to **${RESOURCE_AI_REMEDIATION_MODE_LABELS[mode]}**`,
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (updateData["aiCommandAllowlist"] !== undefined) {
|
|
822
|
+
const patterns: Array<string> =
|
|
823
|
+
ResourceAiAccessSettings.readStoredAllowlist(
|
|
824
|
+
updateData["aiCommandAllowlist"],
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
if (
|
|
828
|
+
!previous ||
|
|
829
|
+
!isSameAllowlist(previous.aiCommandAllowlist, patterns)
|
|
830
|
+
) {
|
|
831
|
+
changes.push(
|
|
832
|
+
patterns.length === 0
|
|
833
|
+
? "AI command allowlist cleared"
|
|
834
|
+
: `AI command allowlist changed to ${describePatternCount(
|
|
835
|
+
patterns.length,
|
|
836
|
+
)}${
|
|
837
|
+
previous
|
|
838
|
+
? ` (was ${describePatternCount(
|
|
839
|
+
previous.aiCommandAllowlist.length,
|
|
840
|
+
)})`
|
|
841
|
+
: ""
|
|
842
|
+
}`,
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
return changes;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/*
|
|
851
|
+
* Who changed what OneUptime AI may do on a resource, and when. The
|
|
852
|
+
* generic "was updated" feed item only covers MEANINGFUL_UPDATE_COLUMNS
|
|
853
|
+
* (shared by every resource type and deliberately left alone), so
|
|
854
|
+
* without this an operator switching a production host to Bypass
|
|
855
|
+
* approval — or back off — left no trace.
|
|
856
|
+
*
|
|
857
|
+
* One item per resource that actually changed; a save that re-posts
|
|
858
|
+
* unchanged values (the AI page posts its whole form) records nothing.
|
|
859
|
+
* API-key and Terraform writes carry no user and are attributed to an API
|
|
860
|
+
* key rather than dropped.
|
|
861
|
+
*/
|
|
862
|
+
private static async writeSettingsChangedFeed<
|
|
863
|
+
TBaseModel extends BaseModel,
|
|
864
|
+
>(data: {
|
|
865
|
+
service: DatabaseService<TBaseModel>;
|
|
866
|
+
carryForward: ResourceAiAccessWriteCarryForward;
|
|
867
|
+
updateData: JSONObject;
|
|
868
|
+
updatedByUserId: ObjectID | undefined;
|
|
869
|
+
updatedItemIds: Array<ObjectID>;
|
|
870
|
+
getResourceMarkdownLink: (
|
|
871
|
+
projectId: ObjectID,
|
|
872
|
+
resourceId: ObjectID,
|
|
873
|
+
) => Promise<string>;
|
|
874
|
+
createFeedItem: (item: ResourceAiAccessFeedItem) => Promise<void>;
|
|
875
|
+
}): Promise<void> {
|
|
876
|
+
for (const resourceId of data.updatedItemIds) {
|
|
877
|
+
const previous: ResourceAiAccessSettingsSnapshot | undefined =
|
|
878
|
+
data.carryForward.previousResourceAiAccessSettings[
|
|
879
|
+
resourceId.toString()
|
|
880
|
+
];
|
|
881
|
+
|
|
882
|
+
const changes: Array<string> = ResourceAiAccessSettings.describeChanges({
|
|
883
|
+
updateData: data.updateData,
|
|
884
|
+
previous,
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
if (changes.length === 0) {
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const projectId: ObjectID | undefined =
|
|
892
|
+
previous?.projectId ||
|
|
893
|
+
((
|
|
894
|
+
(await data.service.findOneById({
|
|
895
|
+
id: resourceId,
|
|
896
|
+
select: { projectId: true } as never,
|
|
897
|
+
props: { isRoot: true },
|
|
898
|
+
})) as unknown as Record<string, unknown> | null
|
|
899
|
+
)?.["projectId"] as ObjectID | undefined);
|
|
900
|
+
|
|
901
|
+
if (!projectId) {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const userMarkdown: string = data.updatedByUserId
|
|
906
|
+
? (await UserService.getUserMarkdownString({
|
|
907
|
+
userId: data.updatedByUserId,
|
|
908
|
+
projectId,
|
|
909
|
+
})) || "A user"
|
|
910
|
+
: "";
|
|
911
|
+
|
|
912
|
+
const actor: string = userMarkdown ? `**${userMarkdown}**` : "An API key";
|
|
913
|
+
|
|
914
|
+
/*
|
|
915
|
+
* Yellow when the change lets AI do more (the same test the permission
|
|
916
|
+
* check applies), grey when it tightens or only flips investigation.
|
|
917
|
+
*/
|
|
918
|
+
const displayColor: Color = ResourceAiAccessSettings.getLoosening({
|
|
919
|
+
data: data.updateData,
|
|
920
|
+
current: previous || NEVER_CONFIGURED_RESOURCE_AI_ACCESS,
|
|
921
|
+
}).loosens
|
|
922
|
+
? Yellow500
|
|
923
|
+
: Gray500;
|
|
924
|
+
|
|
925
|
+
const moreInformation: Array<string> = [
|
|
926
|
+
`**Changed by**: ${userMarkdown || "An API key (no user)"}`,
|
|
927
|
+
];
|
|
928
|
+
|
|
929
|
+
const allowlist: Array<string> =
|
|
930
|
+
ResourceAiAccessSettings.readStoredAllowlist(
|
|
931
|
+
data.updateData["aiCommandAllowlist"],
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
if (
|
|
935
|
+
data.updateData["aiCommandAllowlist"] !== undefined &&
|
|
936
|
+
allowlist.length > 0
|
|
937
|
+
) {
|
|
938
|
+
moreInformation.push(
|
|
939
|
+
`**AI command allowlist**:\n\n${allowlist
|
|
940
|
+
.map((pattern: string): string => {
|
|
941
|
+
return `- \`${pattern.replace(/`/g, "'")}\``;
|
|
942
|
+
})
|
|
943
|
+
.join("\n")}`,
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
await data.createFeedItem({
|
|
948
|
+
resourceId,
|
|
949
|
+
projectId,
|
|
950
|
+
displayColor,
|
|
951
|
+
feedInfoInMarkdown: `🤖 ${actor} changed what OneUptime AI may do on ${await data.getResourceMarkdownLink(
|
|
952
|
+
projectId,
|
|
953
|
+
resourceId,
|
|
954
|
+
)}:\n\n${changes
|
|
955
|
+
.map((change: string): string => {
|
|
956
|
+
return `- ${change}`;
|
|
957
|
+
})
|
|
958
|
+
.join("\n")}`,
|
|
959
|
+
moreInformationInMarkdown: moreInformation.join("\n\n"),
|
|
960
|
+
userId: data.updatedByUserId,
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|