@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.
Files changed (383) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +2 -0
  9. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  10. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  11. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  12. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  13. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  14. package/Server/API/AutoRemediationAPI.ts +392 -1
  15. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  18. package/Server/Infrastructure/Semaphore.ts +22 -0
  19. package/Server/Middleware/TelemetryIngest.ts +15 -5
  20. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  21. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  22. package/Server/Services/CephClusterService.ts +109 -1
  23. package/Server/Services/DatabaseServerService.ts +116 -1
  24. package/Server/Services/DockerHostService.ts +109 -1
  25. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  26. package/Server/Services/HostService.ts +126 -4
  27. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  28. package/Server/Services/PodmanHostService.ts +109 -1
  29. package/Server/Services/ProxmoxClusterService.ts +110 -1
  30. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  31. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  32. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  33. package/Server/Services/RunnerJobService.ts +611 -4
  34. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  35. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  36. package/Server/Services/VMwareVCenterService.ts +110 -1
  37. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1434 -32
  38. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +935 -21
  39. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  40. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  41. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  42. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  43. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  44. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  45. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  46. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +72 -2
  47. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  48. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  49. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  50. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  51. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  52. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  53. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  54. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  55. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  56. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  57. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  60. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +366 -143
  61. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  63. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  64. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  65. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  66. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  67. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +93 -0
  68. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  69. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  70. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  71. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  72. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  73. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  74. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  75. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  76. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  77. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  78. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  79. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  80. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  81. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  82. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  83. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  84. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  85. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  86. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  87. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  88. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  89. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  90. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  91. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  92. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  93. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  95. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  96. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  97. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  98. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  99. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  100. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  101. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  102. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  103. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  104. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  105. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  106. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  107. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  108. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  109. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  110. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  111. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  112. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  113. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  114. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  115. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  116. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  117. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  118. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  119. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  120. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  121. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  122. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  123. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  124. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  125. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  126. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  127. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  128. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  129. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  130. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  131. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  132. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  133. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  134. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  139. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  140. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  141. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  142. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  143. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  144. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  145. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  146. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  147. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  148. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  149. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  150. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  151. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  152. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  153. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  154. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  155. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  156. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  157. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  158. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  159. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  160. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  161. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  162. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  163. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  164. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  165. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  166. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  167. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  168. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  169. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  170. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  171. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  172. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  173. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  174. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  175. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  176. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  177. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  178. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  179. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  180. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  181. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  182. package/Types/Runbook/RunbookStepType.ts +29 -0
  183. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  184. package/Utils/AI/InvestigationReport.ts +121 -15
  185. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  186. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  187. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  188. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  189. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  190. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  191. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  192. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  193. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  194. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  195. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  196. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  197. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  198. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  199. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  200. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  202. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  204. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  206. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  208. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  210. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  211. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  212. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  214. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  216. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  218. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  219. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  220. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  221. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  222. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  223. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  224. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  225. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  226. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  227. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  228. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  234. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  235. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  236. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  237. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  239. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  240. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  241. package/build/dist/Server/Services/CephClusterService.js +104 -0
  242. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  243. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  244. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  245. package/build/dist/Server/Services/DockerHostService.js +104 -0
  246. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  247. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  248. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  249. package/build/dist/Server/Services/HostService.js +114 -2
  250. package/build/dist/Server/Services/HostService.js.map +1 -1
  251. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  252. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  253. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  254. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  255. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  256. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  257. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  258. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  259. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  260. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  261. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  262. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  263. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  264. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  265. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  266. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  267. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  268. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  269. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  270. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1029 -30
  272. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +633 -28
  274. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  276. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  278. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  279. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  280. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  281. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  282. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  283. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  284. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  285. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  286. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  287. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  288. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +50 -4
  290. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  292. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  293. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  294. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  295. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  296. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  297. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  298. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  299. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  300. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  301. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  302. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  303. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  304. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  314. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  315. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  316. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  317. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +263 -110
  318. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  319. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  320. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  321. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  322. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  323. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  324. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  325. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  326. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  327. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  328. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  329. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  330. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  331. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  332. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  333. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  334. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  335. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  336. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  337. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  338. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  339. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  340. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  341. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  342. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  343. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  344. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  345. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  346. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  347. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  348. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  349. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  350. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  351. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  352. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  353. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  354. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  355. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  356. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  357. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  358. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  359. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  360. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  361. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  362. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  363. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  364. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  365. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  366. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  367. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  368. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  369. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  370. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  371. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  372. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  373. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  374. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  375. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  376. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  377. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  378. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  379. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  380. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  381. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  382. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  383. package/package.json +1 -1
@@ -0,0 +1,1042 @@
1
+ import CephClusterFeedService from "../../../Server/Services/CephClusterFeedService";
2
+ import CephClusterService from "../../../Server/Services/CephClusterService";
3
+ import DatabaseServerFeedService from "../../../Server/Services/DatabaseServerFeedService";
4
+ import DatabaseServerService from "../../../Server/Services/DatabaseServerService";
5
+ import DockerHostFeedService from "../../../Server/Services/DockerHostFeedService";
6
+ import DockerHostService from "../../../Server/Services/DockerHostService";
7
+ import DockerSwarmClusterFeedService from "../../../Server/Services/DockerSwarmClusterFeedService";
8
+ import DockerSwarmClusterService from "../../../Server/Services/DockerSwarmClusterService";
9
+ import HostFeedService from "../../../Server/Services/HostFeedService";
10
+ import HostService from "../../../Server/Services/HostService";
11
+ import PodmanHostFeedService from "../../../Server/Services/PodmanHostFeedService";
12
+ import PodmanHostService from "../../../Server/Services/PodmanHostService";
13
+ import ProxmoxClusterFeedService from "../../../Server/Services/ProxmoxClusterFeedService";
14
+ import ProxmoxClusterService from "../../../Server/Services/ProxmoxClusterService";
15
+ import UserService from "../../../Server/Services/UserService";
16
+ import VMwareVCenterFeedService from "../../../Server/Services/VMwareVCenterFeedService";
17
+ import VMwareVCenterService from "../../../Server/Services/VMwareVCenterService";
18
+ import DatabaseRequestType from "../../../Server/Types/BaseDatabase/DatabaseRequestType";
19
+ import { OnUpdate } from "../../../Server/Types/Database/Hooks";
20
+ import ColumnPermissions from "../../../Server/Types/Database/Permissions/ColumnPermission";
21
+ import ModelPermission from "../../../Server/Types/Database/Permissions/Index";
22
+ import TablePermission from "../../../Server/Types/Database/Permissions/TablePermission";
23
+ import UpdateBy from "../../../Server/Types/Database/UpdateBy";
24
+ import { ResourceAiAccessWriteCarryForward } from "../../../Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings";
25
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
26
+ import CephCluster from "../../../Models/DatabaseModels/CephCluster";
27
+ import { CephClusterFeedEventType } from "../../../Models/DatabaseModels/CephClusterFeed";
28
+ import DatabaseServer from "../../../Models/DatabaseModels/DatabaseServer";
29
+ import { DatabaseServerFeedEventType } from "../../../Models/DatabaseModels/DatabaseServerFeed";
30
+ import DockerHost from "../../../Models/DatabaseModels/DockerHost";
31
+ import { DockerHostFeedEventType } from "../../../Models/DatabaseModels/DockerHostFeed";
32
+ import DockerSwarmCluster from "../../../Models/DatabaseModels/DockerSwarmCluster";
33
+ import { DockerSwarmClusterFeedEventType } from "../../../Models/DatabaseModels/DockerSwarmClusterFeed";
34
+ import Host from "../../../Models/DatabaseModels/Host";
35
+ import { HostFeedEventType } from "../../../Models/DatabaseModels/HostFeed";
36
+ import PodmanHost from "../../../Models/DatabaseModels/PodmanHost";
37
+ import { PodmanHostFeedEventType } from "../../../Models/DatabaseModels/PodmanHostFeed";
38
+ import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
39
+ import { ProxmoxClusterFeedEventType } from "../../../Models/DatabaseModels/ProxmoxClusterFeed";
40
+ import VMwareVCenter from "../../../Models/DatabaseModels/VMwareVCenter";
41
+ import { VMwareVCenterFeedEventType } from "../../../Models/DatabaseModels/VMwareVCenterFeed";
42
+ import {
43
+ RESOURCE_AI_ALLOWLIST_EXAMPLES,
44
+ getResourceAiAccessAdminRefusal,
45
+ } from "../../../Types/AI/ResourceAiAccessPermissions";
46
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
47
+ import BadDataException from "../../../Types/Exception/BadDataException";
48
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
49
+ import ObjectID from "../../../Types/ObjectID";
50
+ import Permission, {
51
+ UserPermission,
52
+ UserTenantAccessPermission,
53
+ } from "../../../Types/Permission";
54
+ import AiResourceType, {
55
+ ALL_AI_RESOURCE_TYPES,
56
+ AI_RESOURCE_TYPE_INFO,
57
+ } from "../../../Types/ResourceAiAgent/AiResourceType";
58
+ import { ResourceAiRemediationMode } from "../../../Types/ResourceAiAgent/ResourceAiAccess";
59
+ import { getJestSpyOn } from "../../Spy";
60
+ import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
61
+
62
+ /*
63
+ * Who may make OneUptime AI do MORE on an infrastructure resource a
64
+ * resource AI agent serves, enforced in each resource's own service (the
65
+ * update hook calls the shared ResourceAiAccessSettings) — the rule
66
+ * KubernetesClusterAiSettingsPermission.test.ts pins for a cluster:
67
+ *
68
+ * - LOOSENING (ANY move of the mode up — Off to anything, up to Automatic
69
+ * or Bypass approval — or adding an allowlist pattern) needs a GRANT of
70
+ * Project Owner, Project Admin or Edit Auto Remediation Rule;
71
+ * - TIGHTENING (Off, moving down, removing patterns) and the investigation
72
+ * switch stay open to everyone who may edit the resource;
73
+ * - an unchanged value re-posted by the AI page's form is not a change;
74
+ * - the aiAccess* columns are the server's alone;
75
+ * - root (an agent's first-connection defaults, the outcome bookkeeping)
76
+ * and master admins are not gated;
77
+ * - an operator's AI write marks the resource AI-configured and is recorded
78
+ * on the resource's feed.
79
+ *
80
+ * The full rule runs against the Docker host, the database server and the
81
+ * host services; a table over all eight services proves each one is wired.
82
+ */
83
+
84
+ const PROJECT_ID: ObjectID = new ObjectID(
85
+ "22222222-2222-4222-8222-222222222222",
86
+ );
87
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
88
+ "99999999-9999-4999-8999-999999999999",
89
+ );
90
+ const RESOURCE_ID: ObjectID = new ObjectID(
91
+ "33333333-3333-4333-8333-333333333333",
92
+ );
93
+ const OTHER_RESOURCE_ID: ObjectID = new ObjectID(
94
+ "66666666-6666-4666-8666-666666666666",
95
+ );
96
+
97
+ // Roles that may make AI do more (RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS).
98
+ const AI_ACCESS_ADMIN_ROLES: Array<Permission> = [
99
+ Permission.ProjectOwner,
100
+ Permission.ProjectAdmin,
101
+ Permission.EditAutoRemediationRule,
102
+ ];
103
+
104
+ type Hooks = {
105
+ onBeforeUpdate: (
106
+ updateBy: UpdateBy<BaseModel>,
107
+ ) => Promise<OnUpdate<BaseModel>>;
108
+ onUpdateSuccess: (
109
+ onUpdate: OnUpdate<BaseModel>,
110
+ updatedItemIds: Array<ObjectID>,
111
+ ) => Promise<OnUpdate<BaseModel>>;
112
+ };
113
+
114
+ /*
115
+ * Everything the table needs to reach one resource type's service, its
116
+ * feed, and the permission that lets someone edit it.
117
+ */
118
+ interface ServiceWiring {
119
+ resourceType: AiResourceType;
120
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
121
+ service: any;
122
+ modelType: { new (): BaseModel };
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ feedService: any;
125
+ feedMethod: string;
126
+ idKey: string;
127
+ eventKey: string;
128
+ updatedEvent: string;
129
+ linkMethod: string;
130
+ editPermission: Permission;
131
+ readPermission: Permission;
132
+ }
133
+
134
+ const WIRING: Array<ServiceWiring> = [
135
+ {
136
+ resourceType: AiResourceType.DockerHost,
137
+ service: DockerHostService,
138
+ modelType: DockerHost,
139
+ feedService: DockerHostFeedService,
140
+ feedMethod: "createDockerHostFeedItem",
141
+ idKey: "dockerHostId",
142
+ eventKey: "dockerHostFeedEventType",
143
+ updatedEvent: DockerHostFeedEventType.DockerHostUpdated,
144
+ linkMethod: "getDockerHostMarkdownLink",
145
+ editPermission: Permission.EditDockerHost,
146
+ readPermission: Permission.ReadDockerHost,
147
+ },
148
+ {
149
+ resourceType: AiResourceType.PodmanHost,
150
+ service: PodmanHostService,
151
+ modelType: PodmanHost,
152
+ feedService: PodmanHostFeedService,
153
+ feedMethod: "createPodmanHostFeedItem",
154
+ idKey: "podmanHostId",
155
+ eventKey: "podmanHostFeedEventType",
156
+ updatedEvent: PodmanHostFeedEventType.PodmanHostUpdated,
157
+ linkMethod: "getPodmanHostMarkdownLink",
158
+ editPermission: Permission.EditPodmanHost,
159
+ readPermission: Permission.ReadPodmanHost,
160
+ },
161
+ {
162
+ resourceType: AiResourceType.DockerSwarmCluster,
163
+ service: DockerSwarmClusterService,
164
+ modelType: DockerSwarmCluster,
165
+ feedService: DockerSwarmClusterFeedService,
166
+ feedMethod: "createDockerSwarmClusterFeedItem",
167
+ idKey: "dockerSwarmClusterId",
168
+ eventKey: "dockerSwarmClusterFeedEventType",
169
+ updatedEvent: DockerSwarmClusterFeedEventType.DockerSwarmClusterUpdated,
170
+ linkMethod: "getDockerSwarmClusterMarkdownLink",
171
+ editPermission: Permission.EditDockerSwarmCluster,
172
+ readPermission: Permission.ReadDockerSwarmCluster,
173
+ },
174
+ {
175
+ resourceType: AiResourceType.ProxmoxCluster,
176
+ service: ProxmoxClusterService,
177
+ modelType: ProxmoxCluster,
178
+ feedService: ProxmoxClusterFeedService,
179
+ feedMethod: "createProxmoxClusterFeedItem",
180
+ idKey: "proxmoxClusterId",
181
+ eventKey: "proxmoxClusterFeedEventType",
182
+ updatedEvent: ProxmoxClusterFeedEventType.ProxmoxClusterUpdated,
183
+ linkMethod: "getProxmoxClusterMarkdownLink",
184
+ editPermission: Permission.EditProxmoxCluster,
185
+ readPermission: Permission.ReadProxmoxCluster,
186
+ },
187
+ {
188
+ resourceType: AiResourceType.VMwareVCenter,
189
+ service: VMwareVCenterService,
190
+ modelType: VMwareVCenter,
191
+ feedService: VMwareVCenterFeedService,
192
+ feedMethod: "createVMwareVCenterFeedItem",
193
+ idKey: "vmwareVCenterId",
194
+ eventKey: "vmwareVCenterFeedEventType",
195
+ updatedEvent: VMwareVCenterFeedEventType.VMwareVCenterUpdated,
196
+ linkMethod: "getVMwareVCenterMarkdownLink",
197
+ editPermission: Permission.EditVMwareVCenter,
198
+ readPermission: Permission.ReadVMwareVCenter,
199
+ },
200
+ {
201
+ resourceType: AiResourceType.CephCluster,
202
+ service: CephClusterService,
203
+ modelType: CephCluster,
204
+ feedService: CephClusterFeedService,
205
+ feedMethod: "createCephClusterFeedItem",
206
+ idKey: "cephClusterId",
207
+ eventKey: "cephClusterFeedEventType",
208
+ updatedEvent: CephClusterFeedEventType.CephClusterUpdated,
209
+ linkMethod: "getCephClusterMarkdownLink",
210
+ editPermission: Permission.EditCephCluster,
211
+ readPermission: Permission.ReadCephCluster,
212
+ },
213
+ {
214
+ resourceType: AiResourceType.DatabaseServer,
215
+ service: DatabaseServerService,
216
+ modelType: DatabaseServer,
217
+ feedService: DatabaseServerFeedService,
218
+ feedMethod: "createDatabaseServerFeedItem",
219
+ idKey: "databaseServerId",
220
+ eventKey: "databaseServerFeedEventType",
221
+ updatedEvent: DatabaseServerFeedEventType.DatabaseServerUpdated,
222
+ linkMethod: "getDatabaseServerMarkdownLink",
223
+ editPermission: Permission.EditDatabaseServer,
224
+ readPermission: Permission.ReadDatabaseServer,
225
+ },
226
+ {
227
+ resourceType: AiResourceType.Host,
228
+ service: HostService,
229
+ modelType: Host,
230
+ feedService: HostFeedService,
231
+ feedMethod: "createHostFeedItem",
232
+ idKey: "hostId",
233
+ eventKey: "hostFeedEventType",
234
+ updatedEvent: HostFeedEventType.HostUpdated,
235
+ linkMethod: "getHostMarkdownLink",
236
+ editPermission: Permission.EditHost,
237
+ readPermission: Permission.ReadHost,
238
+ },
239
+ ];
240
+
241
+ // The three the full rule runs against (the unit's named coverage).
242
+ const FULLY_COVERED: Array<ServiceWiring> = WIRING.filter(
243
+ (wiring: ServiceWiring) => {
244
+ return [
245
+ AiResourceType.DockerHost,
246
+ AiResourceType.DatabaseServer,
247
+ AiResourceType.Host,
248
+ ].includes(wiring.resourceType);
249
+ },
250
+ );
251
+
252
+ function hooks(wiring: ServiceWiring): Hooks {
253
+ return wiring.service as Hooks;
254
+ }
255
+
256
+ function permissionRow(
257
+ permission: Permission,
258
+ isBlockPermission: boolean = false,
259
+ ): UserPermission {
260
+ return {
261
+ _type: "UserPermission",
262
+ permission,
263
+ labelIds: [],
264
+ isBlockPermission,
265
+ };
266
+ }
267
+
268
+ function userProps(
269
+ rows: Array<UserPermission>,
270
+ options: {
271
+ tenantId?: ObjectID | null;
272
+ permissionProjectId?: ObjectID;
273
+ } = {},
274
+ ): DatabaseCommonInteractionProps {
275
+ const tenantId: ObjectID | null =
276
+ options.tenantId === undefined ? PROJECT_ID : options.tenantId;
277
+ const permissionProjectId: ObjectID =
278
+ options.permissionProjectId || tenantId || PROJECT_ID;
279
+
280
+ const tenantPermission: UserTenantAccessPermission = {
281
+ _type: "UserTenantAccessPermission",
282
+ projectId: permissionProjectId,
283
+ permissions: rows,
284
+ };
285
+
286
+ return {
287
+ userId: ObjectID.generate(),
288
+ ...(tenantId ? { tenantId } : {}),
289
+ userTenantAccessPermission: {
290
+ [permissionProjectId.toString()]: tenantPermission,
291
+ },
292
+ } as DatabaseCommonInteractionProps;
293
+ }
294
+
295
+ function propsWith(
296
+ ...permissions: Array<Permission>
297
+ ): DatabaseCommonInteractionProps {
298
+ return userProps(
299
+ permissions.map((permission: Permission) => {
300
+ return permissionRow(permission);
301
+ }),
302
+ );
303
+ }
304
+
305
+ function updateBy(
306
+ data: Record<string, unknown>,
307
+ props: DatabaseCommonInteractionProps,
308
+ ): UpdateBy<BaseModel> {
309
+ return {
310
+ query: { _id: RESOURCE_ID.toString() },
311
+ data,
312
+ limit: 1,
313
+ skip: 0,
314
+ props,
315
+ } as unknown as UpdateBy<BaseModel>;
316
+ }
317
+
318
+ function resource(overrides: Record<string, unknown> = {}): BaseModel {
319
+ return {
320
+ id: RESOURCE_ID,
321
+ projectId: PROJECT_ID,
322
+ isAiInvestigationEnabled: false,
323
+ aiRemediationMode: ResourceAiRemediationMode.Disabled,
324
+ aiCommandAllowlist: null,
325
+ ...overrides,
326
+ } as unknown as BaseModel;
327
+ }
328
+
329
+ // Roles that may edit a resource of this type but may not author a FullAuto rule.
330
+ function editorRoles(wiring: ServiceWiring): Array<Permission> {
331
+ return [
332
+ Permission.SettingsMember,
333
+ Permission.SettingsAdmin,
334
+ Permission.ProjectMember,
335
+ wiring.editPermission,
336
+ ];
337
+ }
338
+
339
+ // Writes that make AI do more on a never-configured resource of this type.
340
+ function looseningWrites(
341
+ wiring: ServiceWiring,
342
+ ): Array<[string, Record<string, unknown>]> {
343
+ const example: string = RESOURCE_AI_ALLOWLIST_EXAMPLES[wiring.resourceType];
344
+
345
+ return [
346
+ [
347
+ "turn AI fixes on to Ask for approval",
348
+ { aiRemediationMode: ResourceAiRemediationMode.RequireApproval },
349
+ ],
350
+ [
351
+ "switch AI remediation to Automatic",
352
+ { aiRemediationMode: ResourceAiRemediationMode.Automatic },
353
+ ],
354
+ [
355
+ "switch AI remediation to Bypass approval",
356
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
357
+ ],
358
+ ["add an allowlist pattern", { aiCommandAllowlist: [example] }],
359
+ [
360
+ "pair Automatic with an allowlist",
361
+ {
362
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
363
+ aiCommandAllowlist: [example],
364
+ },
365
+ ],
366
+ ];
367
+ }
368
+
369
+ // Writes that make AI do less, or only flip the investigation switch.
370
+ const TIGHTENING_WRITES: Array<[string, Record<string, unknown>]> = [
371
+ [
372
+ "turn AI remediation off",
373
+ { aiRemediationMode: ResourceAiRemediationMode.Disabled },
374
+ ],
375
+ ["clear the allowlist to an empty list", { aiCommandAllowlist: [] }],
376
+ ["clear the allowlist to null", { aiCommandAllowlist: null }],
377
+ ["clear the allowlist text field", { aiCommandAllowlist: "" }],
378
+ ["turn AI investigation on", { isAiInvestigationEnabled: true }],
379
+ ["turn AI investigation off", { isAiInvestigationEnabled: false }],
380
+ ["rename the resource", { name: "prod-web-1" }],
381
+ ];
382
+
383
+ describe.each(FULLY_COVERED)(
384
+ "$resourceType AI access: who may make AI do more",
385
+ (wiring: ServiceWiring) => {
386
+ let resourceLookup: jest.SpyInstance;
387
+
388
+ beforeEach(() => {
389
+ resourceLookup = jest
390
+ .spyOn(wiring.service, "findBy")
391
+ .mockResolvedValue([resource()]);
392
+ });
393
+
394
+ afterEach(() => {
395
+ jest.restoreAllMocks();
396
+ });
397
+
398
+ describe("the column layer lets every editor through, so the service is the guard", () => {
399
+ it.each(editorRoles(wiring))(
400
+ "the table and column checks accept Bypass approval from %s",
401
+ (role: Permission) => {
402
+ const props: DatabaseCommonInteractionProps = propsWith(role);
403
+
404
+ expect(() => {
405
+ TablePermission.checkTableLevelPermissions(
406
+ wiring.modelType,
407
+ props,
408
+ DatabaseRequestType.Update,
409
+ );
410
+ ColumnPermissions.checkDataColumnPermissions(
411
+ wiring.modelType,
412
+ {
413
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
414
+ } as unknown as BaseModel,
415
+ props,
416
+ DatabaseRequestType.Update,
417
+ );
418
+ }).not.toThrow();
419
+ },
420
+ );
421
+
422
+ it("while the server-only columns are refused by the column layer too", () => {
423
+ expect(() => {
424
+ ColumnPermissions.checkDataColumnPermissions(
425
+ wiring.modelType,
426
+ {
427
+ aiAccessConfiguredAt: new Date(),
428
+ } as unknown as BaseModel,
429
+ propsWith(Permission.ProjectOwner),
430
+ DatabaseRequestType.Update,
431
+ );
432
+ }).toThrow();
433
+ });
434
+ });
435
+
436
+ describe.each(editorRoles(wiring))(
437
+ "a caller holding only %s",
438
+ (role: Permission) => {
439
+ it.each(looseningWrites(wiring))(
440
+ "may not %s",
441
+ async (_label: string, data: Record<string, unknown>) => {
442
+ await expect(
443
+ hooks(wiring).onBeforeUpdate(updateBy(data, propsWith(role))),
444
+ ).rejects.toThrow(
445
+ getResourceAiAccessAdminRefusal(wiring.resourceType),
446
+ );
447
+ },
448
+ );
449
+
450
+ it.each(TIGHTENING_WRITES)(
451
+ "may %s",
452
+ async (_label: string, data: Record<string, unknown>) => {
453
+ await expect(
454
+ hooks(wiring).onBeforeUpdate(updateBy(data, propsWith(role))),
455
+ ).resolves.toBeDefined();
456
+ },
457
+ );
458
+ },
459
+ );
460
+
461
+ describe.each(AI_ACCESS_ADMIN_ROLES)(
462
+ "a caller holding %s",
463
+ (role: Permission) => {
464
+ it.each(looseningWrites(wiring))(
465
+ "may %s",
466
+ async (_label: string, data: Record<string, unknown>) => {
467
+ const onUpdate: OnUpdate<BaseModel> = await hooks(
468
+ wiring,
469
+ ).onBeforeUpdate(updateBy(data, propsWith(role)));
470
+
471
+ expect(
472
+ (onUpdate.carryForward as ResourceAiAccessWriteCarryForward)
473
+ .resourceType,
474
+ ).toBe(wiring.resourceType);
475
+ },
476
+ );
477
+ },
478
+ );
479
+
480
+ describe("judged against the resource's current settings", () => {
481
+ it("lets an editor step Bypass approval down to Automatic, but not back up", async () => {
482
+ resourceLookup.mockResolvedValue([
483
+ resource({
484
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
485
+ }),
486
+ ]);
487
+
488
+ await expect(
489
+ hooks(wiring).onBeforeUpdate(
490
+ updateBy(
491
+ { aiRemediationMode: ResourceAiRemediationMode.Automatic },
492
+ propsWith(wiring.editPermission),
493
+ ),
494
+ ),
495
+ ).resolves.toBeDefined();
496
+
497
+ resourceLookup.mockResolvedValue([
498
+ resource({ aiRemediationMode: ResourceAiRemediationMode.Automatic }),
499
+ ]);
500
+
501
+ await expect(
502
+ hooks(wiring).onBeforeUpdate(
503
+ updateBy(
504
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
505
+ propsWith(wiring.editPermission),
506
+ ),
507
+ ),
508
+ ).rejects.toThrow(NotAuthorizedException);
509
+ });
510
+
511
+ it("refuses Automatic from Ask for approval", async () => {
512
+ resourceLookup.mockResolvedValue([
513
+ resource({
514
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
515
+ }),
516
+ ]);
517
+
518
+ await expect(
519
+ hooks(wiring).onBeforeUpdate(
520
+ updateBy(
521
+ { aiRemediationMode: ResourceAiRemediationMode.Automatic },
522
+ propsWith(Permission.SettingsMember),
523
+ ),
524
+ ),
525
+ ).rejects.toThrow(NotAuthorizedException);
526
+ });
527
+
528
+ it("lets an editor re-post every unchanged setting while flipping investigation", async () => {
529
+ const example: string =
530
+ RESOURCE_AI_ALLOWLIST_EXAMPLES[wiring.resourceType];
531
+
532
+ resourceLookup.mockResolvedValue([
533
+ resource({
534
+ isAiInvestigationEnabled: true,
535
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
536
+ aiCommandAllowlist: [example],
537
+ }),
538
+ ]);
539
+
540
+ await expect(
541
+ hooks(wiring).onBeforeUpdate(
542
+ updateBy(
543
+ {
544
+ isAiInvestigationEnabled: false,
545
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
546
+ aiCommandAllowlist: [` ${example} `],
547
+ },
548
+ propsWith(Permission.SettingsMember),
549
+ ),
550
+ ),
551
+ ).resolves.toBeDefined();
552
+ });
553
+
554
+ it("lets an editor remove an allowlist pattern but not add one", async () => {
555
+ const example: string =
556
+ RESOURCE_AI_ALLOWLIST_EXAMPLES[wiring.resourceType];
557
+
558
+ resourceLookup.mockResolvedValue([
559
+ resource({ aiCommandAllowlist: [example] }),
560
+ ]);
561
+
562
+ await expect(
563
+ hooks(wiring).onBeforeUpdate(
564
+ updateBy(
565
+ { aiCommandAllowlist: [] },
566
+ propsWith(wiring.editPermission),
567
+ ),
568
+ ),
569
+ ).resolves.toBeDefined();
570
+
571
+ resourceLookup.mockResolvedValue([resource()]);
572
+
573
+ await expect(
574
+ hooks(wiring).onBeforeUpdate(
575
+ updateBy(
576
+ { aiCommandAllowlist: [example] },
577
+ propsWith(wiring.editPermission),
578
+ ),
579
+ ),
580
+ ).rejects.toThrow(NotAuthorizedException);
581
+ });
582
+ });
583
+
584
+ describe("validation, block rows, other projects and the server-only columns", () => {
585
+ it("refuses an unusable allowlist entry for everyone, admins included", async () => {
586
+ await expect(
587
+ hooks(wiring).onBeforeUpdate(
588
+ updateBy(
589
+ { aiCommandAllowlist: ["rm -rf /"] },
590
+ propsWith(Permission.ProjectOwner),
591
+ ),
592
+ ),
593
+ ).rejects.toThrow(BadDataException);
594
+ });
595
+
596
+ it("refuses an unknown mode", async () => {
597
+ await expect(
598
+ hooks(wiring).onBeforeUpdate(
599
+ updateBy(
600
+ { aiRemediationMode: "Yolo" },
601
+ propsWith(Permission.ProjectOwner),
602
+ ),
603
+ ),
604
+ ).rejects.toThrow(BadDataException);
605
+ });
606
+
607
+ it("does not read a block row for Project Admin as a grant", async () => {
608
+ await expect(
609
+ hooks(wiring).onBeforeUpdate(
610
+ updateBy(
611
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
612
+ userProps([
613
+ permissionRow(wiring.editPermission),
614
+ permissionRow(Permission.ProjectAdmin, true),
615
+ ]),
616
+ ),
617
+ ),
618
+ ).rejects.toThrow(NotAuthorizedException);
619
+ });
620
+
621
+ it("does not count an admin grant held in a different project", async () => {
622
+ await expect(
623
+ hooks(wiring).onBeforeUpdate(
624
+ updateBy(
625
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
626
+ userProps([permissionRow(Permission.ProjectAdmin)], {
627
+ tenantId: PROJECT_ID,
628
+ permissionProjectId: OTHER_PROJECT_ID,
629
+ }),
630
+ ),
631
+ ),
632
+ ).rejects.toThrow(NotAuthorizedException);
633
+ });
634
+
635
+ it("refuses the server-only columns to an owner and a master admin", async () => {
636
+ for (const props of [
637
+ propsWith(Permission.ProjectOwner),
638
+ { isMasterAdmin: true, userId: ObjectID.generate() },
639
+ ] as Array<DatabaseCommonInteractionProps>) {
640
+ await expect(
641
+ hooks(wiring).onBeforeUpdate(
642
+ updateBy({ aiAccessLastVerifiedAt: new Date() }, props),
643
+ ),
644
+ ).rejects.toThrow(NotAuthorizedException);
645
+ }
646
+ });
647
+
648
+ it("judges a write that matches no resource against the defaults in the caller's tenant", async () => {
649
+ resourceLookup.mockResolvedValue([]);
650
+
651
+ await expect(
652
+ hooks(wiring).onBeforeUpdate(
653
+ updateBy(
654
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
655
+ propsWith(Permission.SettingsMember),
656
+ ),
657
+ ),
658
+ ).rejects.toThrow(NotAuthorizedException);
659
+
660
+ await expect(
661
+ hooks(wiring).onBeforeUpdate(
662
+ updateBy(
663
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
664
+ propsWith(Permission.ProjectAdmin),
665
+ ),
666
+ ),
667
+ ).resolves.toBeDefined();
668
+ });
669
+
670
+ it("refuses when the write loosens ANY matched resource", async () => {
671
+ resourceLookup.mockResolvedValue([
672
+ resource({
673
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
674
+ }),
675
+ resource({
676
+ id: OTHER_RESOURCE_ID,
677
+ aiRemediationMode: ResourceAiRemediationMode.Disabled,
678
+ }),
679
+ ]);
680
+
681
+ await expect(
682
+ hooks(wiring).onBeforeUpdate(
683
+ updateBy(
684
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
685
+ propsWith(Permission.SettingsMember),
686
+ ),
687
+ ),
688
+ ).rejects.toThrow(NotAuthorizedException);
689
+ });
690
+
691
+ it("scopes the settings read to the caller's project, as root, selecting the AI columns", async () => {
692
+ await hooks(wiring).onBeforeUpdate(
693
+ updateBy(
694
+ { isAiInvestigationEnabled: true },
695
+ propsWith(Permission.SettingsMember),
696
+ ),
697
+ );
698
+
699
+ expect(resourceLookup).toHaveBeenCalledTimes(1);
700
+ const args: {
701
+ query: Record<string, unknown>;
702
+ select: Record<string, unknown>;
703
+ props: DatabaseCommonInteractionProps;
704
+ } = resourceLookup.mock.calls[0]![0] as {
705
+ query: Record<string, unknown>;
706
+ select: Record<string, unknown>;
707
+ props: DatabaseCommonInteractionProps;
708
+ };
709
+
710
+ expect(args.query).toEqual({
711
+ _id: RESOURCE_ID.toString(),
712
+ projectId: PROJECT_ID,
713
+ });
714
+ expect(args.props.isRoot).toBe(true);
715
+ for (const column of [
716
+ "projectId",
717
+ "isAiInvestigationEnabled",
718
+ "aiRemediationMode",
719
+ "aiCommandAllowlist",
720
+ ]) {
721
+ expect(args.select[column]).toBe(true);
722
+ }
723
+ });
724
+ });
725
+
726
+ describe("callers the check does not apply to", () => {
727
+ it("lets the server's own (root) writes set any mode and allowlist without reading settings", async () => {
728
+ const onUpdate: OnUpdate<BaseModel> = await hooks(
729
+ wiring,
730
+ ).onBeforeUpdate(
731
+ updateBy(
732
+ {
733
+ isAiInvestigationEnabled: true,
734
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
735
+ aiCommandAllowlist: [
736
+ RESOURCE_AI_ALLOWLIST_EXAMPLES[wiring.resourceType],
737
+ ],
738
+ aiAccessLastVerifiedAt: new Date(),
739
+ },
740
+ { isRoot: true },
741
+ ),
742
+ );
743
+
744
+ expect(onUpdate.carryForward).toBeNull();
745
+ expect(resourceLookup).not.toHaveBeenCalled();
746
+ });
747
+
748
+ it("lets a master admin loosen without any project permission rows", async () => {
749
+ await expect(
750
+ hooks(wiring).onBeforeUpdate(
751
+ updateBy(
752
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
753
+ { isMasterAdmin: true, userId: ObjectID.generate() },
754
+ ),
755
+ ),
756
+ ).resolves.toBeDefined();
757
+ });
758
+
759
+ it("a write of other columns reads nothing and carries nothing forward", async () => {
760
+ const onUpdate: OnUpdate<BaseModel> = await hooks(
761
+ wiring,
762
+ ).onBeforeUpdate(
763
+ updateBy({ name: "renamed" }, propsWith(wiring.editPermission)),
764
+ );
765
+
766
+ expect(onUpdate.carryForward).toBeNull();
767
+ expect(resourceLookup).not.toHaveBeenCalled();
768
+ });
769
+ });
770
+ },
771
+ );
772
+
773
+ /*
774
+ * Every one of the eight services calls the shared rule from its update
775
+ * hooks: a loosening by an editor is refused, an admin's is let through
776
+ * with the settings as they stood, and after an operator's AI write the
777
+ * resource is marked AI-configured and the change lands on its own feed
778
+ * as an "updated" item.
779
+ */
780
+ describe.each(WIRING)(
781
+ "$resourceType service wiring of the AI access rules",
782
+ (wiring: ServiceWiring) => {
783
+ let resourceLookup: jest.SpyInstance;
784
+ let markerWrite: jest.SpyInstance;
785
+ let feedWrite: jest.SpyInstance;
786
+
787
+ beforeEach(() => {
788
+ resourceLookup = jest
789
+ .spyOn(wiring.service, "findBy")
790
+ .mockResolvedValue([resource()]);
791
+ markerWrite = jest
792
+ .spyOn(wiring.service, "updateBy")
793
+ .mockResolvedValue(1 as never);
794
+ feedWrite = jest
795
+ .spyOn(wiring.feedService, wiring.feedMethod)
796
+ .mockResolvedValue(undefined as never);
797
+ jest
798
+ .spyOn(wiring.service, wiring.linkMethod)
799
+ .mockResolvedValue(`[resource](https://x)` as never);
800
+ jest
801
+ .spyOn(UserService, "getUserMarkdownString")
802
+ .mockResolvedValue("[Jane](https://oneuptime.example/user)");
803
+ });
804
+
805
+ afterEach(() => {
806
+ jest.restoreAllMocks();
807
+ });
808
+
809
+ async function waitFor(predicate: () => boolean): Promise<void> {
810
+ for (let i: number = 0; i < 100 && !predicate(); i++) {
811
+ await new Promise((resolve: (value: unknown) => void) => {
812
+ setTimeout(resolve, 0);
813
+ });
814
+ }
815
+ }
816
+
817
+ it("refuses an editor's Bypass approval in the refusal's own words", async () => {
818
+ await expect(
819
+ hooks(wiring).onBeforeUpdate(
820
+ updateBy(
821
+ { aiRemediationMode: ResourceAiRemediationMode.BypassApproval },
822
+ propsWith(wiring.editPermission),
823
+ ),
824
+ ),
825
+ ).rejects.toThrow(getResourceAiAccessAdminRefusal(wiring.resourceType));
826
+ });
827
+
828
+ it("validates the allowlist against its own type's programs", async () => {
829
+ const otherType: AiResourceType =
830
+ wiring.resourceType === AiResourceType.Host
831
+ ? AiResourceType.CephCluster
832
+ : AiResourceType.Host;
833
+
834
+ await expect(
835
+ hooks(wiring).onBeforeUpdate(
836
+ updateBy(
837
+ { aiCommandAllowlist: [RESOURCE_AI_ALLOWLIST_EXAMPLES[otherType]] },
838
+ propsWith(Permission.ProjectOwner),
839
+ ),
840
+ ),
841
+ ).rejects.toThrow(
842
+ `does not start with a program the ${
843
+ AI_RESOURCE_TYPE_INFO[wiring.resourceType].agentDisplayName
844
+ } runs`,
845
+ );
846
+ });
847
+
848
+ it("lets an admin's write through, marks the resource configured and records it on its feed", async () => {
849
+ const props: DatabaseCommonInteractionProps = propsWith(
850
+ Permission.ProjectAdmin,
851
+ );
852
+
853
+ const before: OnUpdate<BaseModel> = await hooks(wiring).onBeforeUpdate(
854
+ updateBy(
855
+ { aiRemediationMode: ResourceAiRemediationMode.RequireApproval },
856
+ props,
857
+ ),
858
+ );
859
+
860
+ expect(before.carryForward).toEqual({
861
+ resourceType: wiring.resourceType,
862
+ previousResourceAiAccessSettings: {
863
+ [RESOURCE_ID.toString()]: {
864
+ projectId: PROJECT_ID,
865
+ isAiInvestigationEnabled: false,
866
+ aiRemediationMode: ResourceAiRemediationMode.Disabled,
867
+ aiCommandAllowlist: [],
868
+ },
869
+ },
870
+ });
871
+
872
+ await hooks(wiring).onUpdateSuccess(before, [RESOURCE_ID]);
873
+
874
+ const markerCalls: Array<Record<string, unknown>> =
875
+ markerWrite.mock.calls.map((call: Array<unknown>) => {
876
+ return call[0] as Record<string, unknown>;
877
+ });
878
+ const marker: Record<string, unknown> | undefined = markerCalls.find(
879
+ (call: Record<string, unknown>) => {
880
+ return Boolean(
881
+ (call["data"] as Record<string, unknown>)["aiAccessConfiguredAt"],
882
+ );
883
+ },
884
+ );
885
+ expect(marker).toBeDefined();
886
+ expect(marker!["props"]).toEqual({ isRoot: true });
887
+
888
+ await waitFor(() => {
889
+ return feedWrite.mock.calls.length > 0;
890
+ });
891
+
892
+ expect(feedWrite).toHaveBeenCalledTimes(1);
893
+ const item: Record<string, unknown> = feedWrite.mock
894
+ .calls[0]![0] as Record<string, unknown>;
895
+ expect(String(item[wiring.idKey])).toBe(RESOURCE_ID.toString());
896
+ expect(item[wiring.eventKey]).toBe(wiring.updatedEvent);
897
+ expect(item["projectId"]).toBe(PROJECT_ID);
898
+ expect(item["userId"]).toBe(props.userId);
899
+ expect(item["feedInfoInMarkdown"]).toBe(
900
+ "🤖 **[Jane](https://oneuptime.example/user)** changed what OneUptime AI may do on [resource](https://x):\n\n- AI remediation changed from **Off** to **Ask for approval**",
901
+ );
902
+ });
903
+
904
+ it("negative control: a root write is neither marked nor recorded", async () => {
905
+ const before: OnUpdate<BaseModel> = await hooks(wiring).onBeforeUpdate(
906
+ updateBy(
907
+ { aiRemediationMode: ResourceAiRemediationMode.RequireApproval },
908
+ { isRoot: true },
909
+ ),
910
+ );
911
+
912
+ await hooks(wiring).onUpdateSuccess(before, [RESOURCE_ID]);
913
+
914
+ await new Promise((resolve: (value: unknown) => void) => {
915
+ setTimeout(resolve, 0);
916
+ });
917
+
918
+ expect(markerWrite).not.toHaveBeenCalled();
919
+ expect(feedWrite).not.toHaveBeenCalled();
920
+ expect(resourceLookup).not.toHaveBeenCalled();
921
+ });
922
+ },
923
+ );
924
+
925
+ it("the wiring table covers every resource type", () => {
926
+ expect(
927
+ WIRING.map((wiring: ServiceWiring) => {
928
+ return wiring.resourceType;
929
+ }),
930
+ ).toEqual([...ALL_AI_RESOURCE_TYPES]);
931
+ });
932
+
933
+ /*
934
+ * The same rule through updateOneById, the entry point BaseAPI's PUT takes.
935
+ * Only the surfaces that need Postgres are stubbed: the internal find, the
936
+ * repository, and the model-level permission checks (the table/column layer
937
+ * is pinned above and lets these roles through by design).
938
+ */
939
+ describe.each(FULLY_COVERED)(
940
+ "$resourceType AI access through updateOneById",
941
+ (wiring: ServiceWiring) => {
942
+ let repositoryUpdate: jest.Mock;
943
+ let feedWrite: jest.SpyInstance;
944
+
945
+ beforeEach(() => {
946
+ getJestSpyOn(wiring.service, "_findBy").mockResolvedValue([
947
+ resource({
948
+ _id: RESOURCE_ID.toString(),
949
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
950
+ }),
951
+ ]);
952
+ repositoryUpdate = jest.fn().mockResolvedValue({ affected: 1 });
953
+ getJestSpyOn(wiring.service, "getRepository").mockReturnValue({
954
+ update: repositoryUpdate,
955
+ save: jest.fn(),
956
+ });
957
+ jest
958
+ .spyOn(ModelPermission, "checkUpdateQueryPermissions")
959
+ .mockImplementation(((_modelType: unknown, query: unknown) => {
960
+ return Promise.resolve(query);
961
+ }) as never);
962
+ jest
963
+ .spyOn(ModelPermission, "checkUpdatePermissionByModel")
964
+ .mockResolvedValue(undefined);
965
+ feedWrite = jest
966
+ .spyOn(wiring.feedService, wiring.feedMethod)
967
+ .mockResolvedValue(undefined as never);
968
+ jest
969
+ .spyOn(UserService, "getUserMarkdownString")
970
+ .mockResolvedValue("[Jane](https://oneuptime.example/user)");
971
+ jest
972
+ .spyOn(wiring.service, wiring.linkMethod)
973
+ .mockResolvedValue("[resource](https://x)" as never);
974
+ });
975
+
976
+ afterEach(() => {
977
+ jest.restoreAllMocks();
978
+ });
979
+
980
+ async function waitFor(predicate: () => boolean): Promise<void> {
981
+ for (let i: number = 0; i < 100 && !predicate(); i++) {
982
+ await new Promise((resolve: (value: unknown) => void) => {
983
+ setTimeout(resolve, 0);
984
+ });
985
+ }
986
+ }
987
+
988
+ it("refuses an editor's Bypass approval before anything is written", async () => {
989
+ await expect(
990
+ wiring.service.updateOneById({
991
+ id: RESOURCE_ID,
992
+ data: {
993
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
994
+ },
995
+ props: propsWith(wiring.editPermission),
996
+ }),
997
+ ).rejects.toThrow(NotAuthorizedException);
998
+
999
+ expect(repositoryUpdate).not.toHaveBeenCalled();
1000
+ });
1001
+
1002
+ it("writes a Project Admin's Bypass approval, marks the resource configured and records who did it", async () => {
1003
+ const props: DatabaseCommonInteractionProps = propsWith(
1004
+ Permission.ProjectAdmin,
1005
+ );
1006
+
1007
+ const updated: number = await wiring.service.updateOneById({
1008
+ id: RESOURCE_ID,
1009
+ data: {
1010
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
1011
+ },
1012
+ props,
1013
+ });
1014
+
1015
+ expect(updated).toBe(1);
1016
+
1017
+ const writes: Array<Record<string, unknown>> =
1018
+ repositoryUpdate.mock.calls.map((call: Array<unknown>) => {
1019
+ return call[1] as Record<string, unknown>;
1020
+ });
1021
+
1022
+ expect(writes[0]!["aiRemediationMode"]).toBe(
1023
+ ResourceAiRemediationMode.BypassApproval,
1024
+ );
1025
+ expect(writes[0]!["aiAccessConfiguredAt"]).toBeUndefined();
1026
+ // The marker is its own, server-side write after the operator's.
1027
+ expect(writes[1]!["aiAccessConfiguredAt"]).toBeInstanceOf(Date);
1028
+
1029
+ await waitFor(() => {
1030
+ return feedWrite.mock.calls.length > 0;
1031
+ });
1032
+
1033
+ expect(feedWrite).toHaveBeenCalledTimes(1);
1034
+ const item: Record<string, unknown> = feedWrite.mock
1035
+ .calls[0]![0] as Record<string, unknown>;
1036
+ expect(item["userId"]).toBe(props.userId);
1037
+ expect(item["feedInfoInMarkdown"]).toContain(
1038
+ "AI remediation changed from **Ask for approval** to **Bypass approval**",
1039
+ );
1040
+ });
1041
+ },
1042
+ );