@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,921 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS,
4
+ RESOURCE_REMEDIATION_MODES_BY_AUTONOMY,
5
+ RESOURCE_REMEDIATION_MODE_LABELS,
6
+ RESOURCE_REMEDIATION_MODE_SHORT_NAMES,
7
+ RESOURCE_REMEDIATION_MODE_SUMMARIES,
8
+ ResourceAiAccessConfirmation,
9
+ ResourceAiAccessOfferedFields,
10
+ ResourceAiAccessSavedSettings,
11
+ ResourceAiAccessSettingsFormValues,
12
+ capitalizeFirst,
13
+ formatNameList,
14
+ getEveryModeProtectionsSentence,
15
+ getPermissionTitles,
16
+ getResourceAiAccessAdminPermissionTitles,
17
+ getResourceAiAccessConfirmation,
18
+ getResourceAiAccessLooseningChanges,
19
+ getResourceAiAccessOfferedFields,
20
+ getResourceAiAccessSettingsChanges,
21
+ getResourceAiAccessSettingsInitialValues,
22
+ getResourceAiAccessSubmittedFields,
23
+ getResourceAllowlistFieldDescription,
24
+ getResourceAllowlistInEffect,
25
+ getResourceAllowlistRemovalOnlyError,
26
+ getResourceRemediationModeFieldDescription,
27
+ isResourceAllowlistFieldShown,
28
+ isResourceRemediationModeOpenToEveryEditor,
29
+ normalizeSavedResourceAllowlist,
30
+ parseResourceAllowlistText,
31
+ readDropdownId,
32
+ readResourceAiAccessSavedSettings,
33
+ readResourceRemediationMode,
34
+ readStoredResourceAllowlist,
35
+ validateResourceAllowlistText,
36
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiAccessSettingsUtil";
37
+ import {
38
+ ResourceAiAgentDescriptor,
39
+ getResourceAiAgentDescriptor,
40
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiAgentDescriptors";
41
+ import { JSONObject } from "../../../Types/JSON";
42
+ import { RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS as SERVER_RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS } from "../../../Types/AI/ResourceAiAccessPermissions";
43
+ import { KUBERNETES_AI_ACCESS_ADMIN_PERMISSIONS } from "../../../Types/Kubernetes/KubernetesClusterAiAccessPermissions";
44
+ import Permission from "../../../Types/Permission";
45
+ import AiResourceType, {
46
+ ALL_AI_RESOURCE_TYPES,
47
+ } from "../../../Types/ResourceAiAgent/AiResourceType";
48
+ import {
49
+ RESOURCE_AI_ALLOW_WRITES_ENV,
50
+ RESOURCE_AI_WRITE_TARGETS_ENV,
51
+ ResourceAiRemediationMode,
52
+ ResourceCommandTier,
53
+ } from "../../../Types/ResourceAiAgent/ResourceAiAccess";
54
+ import ResourceCommandPolicy, {
55
+ RESOURCE_ALLOWLIST_MAX_PATTERNS,
56
+ RESOURCE_ALLOWLIST_MAX_PATTERN_LENGTH,
57
+ } from "../../../Utils/AiRemediation/Resource/ResourceCommandPolicy";
58
+ import type FormValues from "../../../UI/Components/Forms/Types/FormValues";
59
+
60
+ /*
61
+ * The pure rules behind "What AI may do" on a resource's AI agent page and
62
+ * its Change modal: the words for each mode, who may loosen what (relative
63
+ * to the saved settings, as the server decides), what an edit sends, when a
64
+ * save is confirmed first, and how the command allowlist is read and
65
+ * checked — by the resource type's own command policy, the matcher's rules.
66
+ */
67
+
68
+ const DOCKER: ResourceAiAgentDescriptor = getResourceAiAgentDescriptor(
69
+ AiResourceType.DockerHost,
70
+ );
71
+
72
+ /*
73
+ * Per type: an entry the policy accepts (a riskier change), one that uses
74
+ * a * for the object it touches (broad; none exists for Proxmox, whose
75
+ * paths are written out per guest), and one that is only a read.
76
+ */
77
+ const ENTRIES: Record<
78
+ AiResourceType,
79
+ { valid: string; broad: string | null; read: string }
80
+ > = {
81
+ [AiResourceType.DockerHost]: {
82
+ valid: "docker stop web",
83
+ broad: "docker stop *",
84
+ read: "docker ps -a",
85
+ },
86
+ [AiResourceType.PodmanHost]: {
87
+ valid: "docker stop web",
88
+ broad: "docker stop *",
89
+ read: "docker ps -a",
90
+ },
91
+ [AiResourceType.DockerSwarmCluster]: {
92
+ valid: "docker service update --image nginx:1.27 web",
93
+ broad: "docker service update --image nginx:1.27 *",
94
+ read: "docker service ps web",
95
+ },
96
+ [AiResourceType.ProxmoxCluster]: {
97
+ valid: "pvesh create /nodes/pve1/qemu/100/status/shutdown",
98
+ broad: null,
99
+ read: "pvesh get /version",
100
+ },
101
+ [AiResourceType.VMwareVCenter]: {
102
+ valid: "govc vm.power -off web-01",
103
+ broad: "govc vm.power -off *",
104
+ read: "govc vm.info web-01",
105
+ },
106
+ [AiResourceType.CephCluster]: {
107
+ valid: "ceph osd out 3",
108
+ broad: "ceph osd out *",
109
+ read: "ceph health detail",
110
+ },
111
+ [AiResourceType.DatabaseServer]: {
112
+ valid: "db terminate-session 12345",
113
+ broad: "db terminate-session *",
114
+ read: "db sessions --limit 20",
115
+ },
116
+ [AiResourceType.Host]: {
117
+ valid: "systemctl stop nginx",
118
+ broad: "systemctl stop *",
119
+ read: "systemctl status nginx",
120
+ },
121
+ };
122
+
123
+ function saved(
124
+ overrides: Partial<ResourceAiAccessSavedSettings> = {},
125
+ ): ResourceAiAccessSavedSettings {
126
+ return {
127
+ isAiInvestigationEnabled: true,
128
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
129
+ aiCommandAllowlist: [],
130
+ ...overrides,
131
+ };
132
+ }
133
+
134
+ function values(
135
+ overrides: Partial<ResourceAiAccessSettingsFormValues> = {},
136
+ ): FormValues<ResourceAiAccessSettingsFormValues> {
137
+ return {
138
+ isAiInvestigationEnabled: true,
139
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
140
+ aiCommandAllowlistText: "",
141
+ ...overrides,
142
+ };
143
+ }
144
+
145
+ const ALL_OFFERED: ResourceAiAccessOfferedFields = {
146
+ allowlist: true,
147
+ allowlistRemoveOnly: false,
148
+ };
149
+
150
+ describe("the words for each mode", () => {
151
+ test("every mode has a label, a short name and a summary", () => {
152
+ for (const mode of Object.values(ResourceAiRemediationMode)) {
153
+ expect(RESOURCE_REMEDIATION_MODE_LABELS[mode].length).toBeGreaterThan(0);
154
+ expect(
155
+ RESOURCE_REMEDIATION_MODE_SHORT_NAMES[mode].length,
156
+ ).toBeGreaterThan(0);
157
+ expect(RESOURCE_REMEDIATION_MODE_SUMMARIES[mode].length).toBeGreaterThan(
158
+ 0,
159
+ );
160
+ // The label starts with the short name, so the two always agree.
161
+ expect(RESOURCE_REMEDIATION_MODE_LABELS[mode]).toMatch(
162
+ new RegExp(`^${RESOURCE_REMEDIATION_MODE_SHORT_NAMES[mode]} — `),
163
+ );
164
+ }
165
+ });
166
+
167
+ test("the short names are a Kubernetes cluster's", () => {
168
+ expect(RESOURCE_REMEDIATION_MODE_SHORT_NAMES).toEqual({
169
+ Disabled: "Off",
170
+ RequireApproval: "Ask for approval",
171
+ Automatic: "Automatic",
172
+ BypassApproval: "Bypass approval",
173
+ });
174
+ });
175
+
176
+ test("never speak of Kubernetes or kubectl", () => {
177
+ const words: string = JSON.stringify([
178
+ RESOURCE_REMEDIATION_MODE_LABELS,
179
+ RESOURCE_REMEDIATION_MODE_SUMMARIES,
180
+ ...ALL_AI_RESOURCE_TYPES.map((type: AiResourceType): string => {
181
+ return getResourceRemediationModeFieldDescription(
182
+ getResourceAiAgentDescriptor(type),
183
+ );
184
+ }),
185
+ ]);
186
+ expect(words).not.toMatch(/kubectl|namespace|Kubernetes/i);
187
+ });
188
+
189
+ test("an unknown stored mode reads as Off", () => {
190
+ expect(readResourceRemediationMode("Automatic")).toBe(
191
+ ResourceAiRemediationMode.Automatic,
192
+ );
193
+ for (const value of ["automatic", "", null, undefined, 3, {}]) {
194
+ expect(readResourceRemediationMode(value)).toBe(
195
+ ResourceAiRemediationMode.Disabled,
196
+ );
197
+ }
198
+ });
199
+ });
200
+
201
+ describe("what holds in every mode", () => {
202
+ test.each(ALL_AI_RESOURCE_TYPES)(
203
+ "%s: the protections name the agent, the write switch and the targets",
204
+ (type: AiResourceType) => {
205
+ const descriptor: ResourceAiAgentDescriptor =
206
+ getResourceAiAgentDescriptor(type);
207
+ const sentence: string = getEveryModeProtectionsSentence(descriptor);
208
+
209
+ expect(sentence).toContain(descriptor.agentName);
210
+ expect(sentence).toContain(`${RESOURCE_AI_ALLOW_WRITES_ENV}=true`);
211
+ expect(sentence).toContain(RESOURCE_AI_WRITE_TARGETS_ENV);
212
+ expect(sentence).toContain("never run");
213
+ expect(sentence).toContain(`holds this ${descriptor.noun}`);
214
+ if (descriptor.alwaysHumanExamples) {
215
+ expect(sentence).toContain(
216
+ `changes such as ${descriptor.alwaysHumanExamples} always need a human`,
217
+ );
218
+ } else {
219
+ expect(sentence).not.toContain("always need a human");
220
+ }
221
+
222
+ const field: string =
223
+ getResourceRemediationModeFieldDescription(descriptor);
224
+ expect(field).toContain(sentence);
225
+ expect(field).toContain("Bypass approval");
226
+ },
227
+ );
228
+ });
229
+
230
+ describe("who may loosen", () => {
231
+ test("is the server's set, which is a Kubernetes cluster's", () => {
232
+ expect([...RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS]).toEqual(
233
+ SERVER_RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS,
234
+ );
235
+ expect([...RESOURCE_AI_ACCESS_ADMIN_PERMISSIONS].sort()).toEqual(
236
+ [...KUBERNETES_AI_ACCESS_ADMIN_PERMISSIONS].sort(),
237
+ );
238
+ });
239
+
240
+ test("is named the way the permission table names it", () => {
241
+ expect(getResourceAiAccessAdminPermissionTitles()).toEqual(
242
+ getPermissionTitles([
243
+ Permission.ProjectOwner,
244
+ Permission.ProjectAdmin,
245
+ Permission.EditAutoRemediationRule,
246
+ ]),
247
+ );
248
+ expect(getResourceAiAccessAdminPermissionTitles().join(", ")).toContain(
249
+ "Edit Auto Remediation Rule",
250
+ );
251
+ // Duplicates and unknown permissions add nothing.
252
+ expect(
253
+ getPermissionTitles([
254
+ Permission.ProjectOwner,
255
+ Permission.ProjectOwner,
256
+ "NotAPermission" as Permission,
257
+ ]),
258
+ ).toHaveLength(1);
259
+ });
260
+
261
+ test("modes by autonomy, least first", () => {
262
+ expect(RESOURCE_REMEDIATION_MODES_BY_AUTONOMY).toEqual([
263
+ ResourceAiRemediationMode.Disabled,
264
+ ResourceAiRemediationMode.RequireApproval,
265
+ ResourceAiRemediationMode.Automatic,
266
+ ResourceAiRemediationMode.BypassApproval,
267
+ ]);
268
+ });
269
+
270
+ test("every editor may choose the saved mode or any below it — Off -> on included", () => {
271
+ const open: (
272
+ mode: ResourceAiRemediationMode,
273
+ savedMode: ResourceAiRemediationMode,
274
+ ) => boolean = isResourceRemediationModeOpenToEveryEditor;
275
+
276
+ expect(
277
+ open(
278
+ ResourceAiRemediationMode.RequireApproval,
279
+ ResourceAiRemediationMode.Disabled,
280
+ ),
281
+ ).toBe(false);
282
+ expect(
283
+ open(
284
+ ResourceAiRemediationMode.Disabled,
285
+ ResourceAiRemediationMode.Disabled,
286
+ ),
287
+ ).toBe(true);
288
+ expect(
289
+ open(
290
+ ResourceAiRemediationMode.Automatic,
291
+ ResourceAiRemediationMode.BypassApproval,
292
+ ),
293
+ ).toBe(true);
294
+ expect(
295
+ open(
296
+ ResourceAiRemediationMode.BypassApproval,
297
+ ResourceAiRemediationMode.Automatic,
298
+ ),
299
+ ).toBe(false);
300
+ });
301
+ });
302
+
303
+ describe("the allowlist text", () => {
304
+ test("is one entry per line, blank lines and extra spaces dropped", () => {
305
+ expect(
306
+ parseResourceAllowlistText(
307
+ " docker stop web \n\n\r\ndocker kill -s TERM api\r\n ",
308
+ ),
309
+ ).toEqual(["docker stop web", "docker kill -s TERM api"]);
310
+ expect(parseResourceAllowlistText(undefined)).toEqual([]);
311
+ expect(parseResourceAllowlistText(["docker stop web"])).toEqual([]);
312
+ });
313
+
314
+ test.each(ALL_AI_RESOURCE_TYPES)(
315
+ "%s: accepts its own kind of entry, empty text too",
316
+ (type: AiResourceType) => {
317
+ expect(
318
+ validateResourceAllowlistText(type, ENTRIES[type].valid),
319
+ ).toBeNull();
320
+ expect(validateResourceAllowlistText(type, "")).toBeNull();
321
+ expect(validateResourceAllowlistText(type, "\n \n")).toBeNull();
322
+ if (ENTRIES[type].broad) {
323
+ // Broad entries are valid; saving one is confirmed instead.
324
+ expect(
325
+ validateResourceAllowlistText(type, ENTRIES[type].broad),
326
+ ).toBeNull();
327
+ }
328
+ },
329
+ );
330
+
331
+ test.each(ALL_AI_RESOURCE_TYPES)(
332
+ "%s: refuses a read with the policy's own words, naming the line",
333
+ (type: AiResourceType) => {
334
+ expect(
335
+ ResourceCommandPolicy.evaluateCommand({
336
+ resourceType: type,
337
+ command: ENTRIES[type].read,
338
+ }).tier,
339
+ ).toBe(ResourceCommandTier.Read);
340
+ const problem: string | null = validateResourceAllowlistText(
341
+ type,
342
+ `${ENTRIES[type].valid}\n${ENTRIES[type].read}`,
343
+ );
344
+
345
+ expect(problem).toMatch(/^Entry 2: /);
346
+ expect(problem).toContain("is a read-only command");
347
+ expect(problem).toBe(
348
+ `Entry 2: ${ResourceCommandPolicy.describeAllowlistPatternProblem({
349
+ resourceType: type,
350
+ pattern: ENTRIES[type].read,
351
+ })}`,
352
+ );
353
+ },
354
+ );
355
+
356
+ test("is checked against THIS resource type's programs", () => {
357
+ // A Host entry means nothing on a Docker host.
358
+ expect(
359
+ validateResourceAllowlistText(
360
+ AiResourceType.DockerHost,
361
+ "systemctl stop nginx",
362
+ ),
363
+ ).toContain("does not start with a program the Docker AI agent runs");
364
+ // docker stop is a container change a Swarm cluster never runs.
365
+ expect(
366
+ validateResourceAllowlistText(
367
+ AiResourceType.DockerSwarmCluster,
368
+ "docker stop web",
369
+ ),
370
+ ).toMatch(/^Entry 1: .*can never match a command that runs/);
371
+ expect(
372
+ validateResourceAllowlistText(
373
+ AiResourceType.DockerHost,
374
+ "kubectl delete pod x",
375
+ ),
376
+ ).toMatch(/^Entry 1: /);
377
+ });
378
+
379
+ test("refuses the shapes that can never pre-approve anything", () => {
380
+ for (const entry of [
381
+ "docker",
382
+ "docker stop",
383
+ "docker * web",
384
+ "docker stop web; rm -rf /",
385
+ ]) {
386
+ expect(
387
+ validateResourceAllowlistText(AiResourceType.DockerHost, entry),
388
+ ).toMatch(/^Entry 1: /);
389
+ }
390
+ });
391
+
392
+ test("refuses more than the matcher reads, and entries longer than it reads", () => {
393
+ const many: string = Array.from(
394
+ { length: RESOURCE_ALLOWLIST_MAX_PATTERNS + 1 },
395
+ (_value: unknown, index: number): string => {
396
+ return `docker stop web-${index}`;
397
+ },
398
+ ).join("\n");
399
+
400
+ expect(validateResourceAllowlistText(AiResourceType.DockerHost, many)).toBe(
401
+ `An allowlist can hold at most ${RESOURCE_ALLOWLIST_MAX_PATTERNS} entries (this one has ${RESOURCE_ALLOWLIST_MAX_PATTERNS + 1}).`,
402
+ );
403
+
404
+ const long: string = `docker stop ${"a".repeat(RESOURCE_ALLOWLIST_MAX_PATTERN_LENGTH)}`;
405
+ expect(
406
+ validateResourceAllowlistText(AiResourceType.DockerHost, long),
407
+ ).toMatch(/^Entry 1: An allowlist entry can be at most 500 characters/);
408
+ });
409
+ });
410
+
411
+ describe("the stored allowlist", () => {
412
+ test("is read the way the server reads it", () => {
413
+ expect(readStoredResourceAllowlist([" docker stop web ", "", 3])).toEqual([
414
+ "docker stop web",
415
+ ]);
416
+ expect(readStoredResourceAllowlist('["docker stop web"]')).toEqual([
417
+ "docker stop web",
418
+ ]);
419
+ // A string that is not JSON is ONE entry.
420
+ expect(readStoredResourceAllowlist("docker stop web")).toEqual([
421
+ "docker stop web",
422
+ ]);
423
+ expect(readStoredResourceAllowlist(null)).toEqual([]);
424
+ expect(readStoredResourceAllowlist({ a: 1 })).toEqual([]);
425
+ });
426
+
427
+ test("is shown whitespace-collapsed, and flagged when it is not a clean list", () => {
428
+ expect(normalizeSavedResourceAllowlist(undefined)).toEqual({
429
+ patterns: [],
430
+ isClean: true,
431
+ });
432
+ expect(normalizeSavedResourceAllowlist(["docker stop web"])).toEqual({
433
+ patterns: ["docker stop web"],
434
+ isClean: true,
435
+ });
436
+ expect(normalizeSavedResourceAllowlist(["docker stop web", ""])).toEqual({
437
+ patterns: ["docker stop web"],
438
+ isClean: false,
439
+ });
440
+ expect(normalizeSavedResourceAllowlist("docker stop web")).toEqual({
441
+ patterns: ["docker stop web"],
442
+ isClean: false,
443
+ });
444
+ expect(normalizeSavedResourceAllowlist('["docker stop web"]')).toEqual({
445
+ patterns: ["docker stop web"],
446
+ isClean: true,
447
+ });
448
+ });
449
+
450
+ test("the list in effect is the non-blank strings the status reports", () => {
451
+ expect(
452
+ getResourceAllowlistInEffect([
453
+ "docker stop web",
454
+ " ",
455
+ 7,
456
+ "docker kill -s TERM api",
457
+ ]),
458
+ ).toEqual(["docker stop web", "docker kill -s TERM api"]);
459
+ expect(getResourceAllowlistInEffect("docker stop web")).toEqual([]);
460
+ });
461
+ });
462
+
463
+ describe("the saved settings", () => {
464
+ test("investigation is off unless the resource says it is on", () => {
465
+ expect(readResourceAiAccessSavedSettings({})).toEqual({
466
+ isAiInvestigationEnabled: false,
467
+ aiRemediationMode: ResourceAiRemediationMode.Disabled,
468
+ aiCommandAllowlist: undefined,
469
+ });
470
+ expect(
471
+ readResourceAiAccessSavedSettings({
472
+ isAiInvestigationEnabled: true,
473
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
474
+ aiCommandAllowlist: ["docker stop web"],
475
+ }),
476
+ ).toEqual({
477
+ isAiInvestigationEnabled: true,
478
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
479
+ aiCommandAllowlist: ["docker stop web"],
480
+ });
481
+ });
482
+
483
+ test("seed the form, the allowlist as text", () => {
484
+ expect(
485
+ getResourceAiAccessSettingsInitialValues(
486
+ saved({ aiCommandAllowlist: ["docker stop web", "docker stop api"] }),
487
+ ),
488
+ ).toEqual({
489
+ isAiInvestigationEnabled: true,
490
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
491
+ aiCommandAllowlistText: "docker stop web\ndocker stop api",
492
+ });
493
+ });
494
+ });
495
+
496
+ describe("what the form offers", () => {
497
+ test("an admin edits the allowlist freely", () => {
498
+ expect(
499
+ getResourceAiAccessOfferedFields({
500
+ saved: saved(),
501
+ canConfigureUnattended: true,
502
+ }),
503
+ ).toEqual({ allowlist: true, allowlistRemoveOnly: false });
504
+ });
505
+
506
+ test("an editor gets it only when there is something to remove", () => {
507
+ expect(
508
+ getResourceAiAccessOfferedFields({
509
+ saved: saved(),
510
+ canConfigureUnattended: false,
511
+ }),
512
+ ).toEqual({ allowlist: false, allowlistRemoveOnly: true });
513
+ expect(
514
+ getResourceAiAccessOfferedFields({
515
+ saved: saved({ aiCommandAllowlist: ["docker stop web"] }),
516
+ canConfigureUnattended: false,
517
+ }),
518
+ ).toEqual({ allowlist: true, allowlistRemoveOnly: true });
519
+ // A stored value that is not a clean list can be cleaned up.
520
+ expect(
521
+ getResourceAiAccessOfferedFields({
522
+ saved: saved({ aiCommandAllowlist: "not json" }),
523
+ canConfigureUnattended: false,
524
+ }).allowlist,
525
+ ).toBe(true);
526
+ });
527
+
528
+ test("the allowlist field only shows, and is only sent, in Automatic mode", () => {
529
+ expect(
530
+ isResourceAllowlistFieldShown(
531
+ values({ aiRemediationMode: ResourceAiRemediationMode.Automatic }),
532
+ ),
533
+ ).toBe(true);
534
+ expect(
535
+ isResourceAllowlistFieldShown(
536
+ values({
537
+ aiRemediationMode: {
538
+ value: ResourceAiRemediationMode.Automatic,
539
+ } as unknown as string,
540
+ }),
541
+ ),
542
+ ).toBe(true);
543
+ expect(
544
+ isResourceAllowlistFieldShown(
545
+ values({ aiRemediationMode: ResourceAiRemediationMode.BypassApproval }),
546
+ ),
547
+ ).toBe(false);
548
+ expect(
549
+ getResourceAiAccessSubmittedFields({
550
+ offered: ALL_OFFERED,
551
+ values: values(),
552
+ }),
553
+ ).toEqual({ allowlist: false, allowlistRemoveOnly: false });
554
+ });
555
+
556
+ test("dropdown values are read bare or wrapped", () => {
557
+ expect(readDropdownId("Automatic")).toBe("Automatic");
558
+ expect(readDropdownId({ value: "Automatic", label: "x" })).toBe(
559
+ "Automatic",
560
+ );
561
+ expect(readDropdownId({ value: { value: 3 } })).toBe("3");
562
+ expect(readDropdownId("")).toBeNull();
563
+ expect(readDropdownId(null)).toBeNull();
564
+ });
565
+ });
566
+
567
+ describe("what a save sends", () => {
568
+ test("nothing when nothing changed", () => {
569
+ expect(
570
+ getResourceAiAccessSettingsChanges({
571
+ saved: saved(),
572
+ values: values(),
573
+ offered: ALL_OFFERED,
574
+ }),
575
+ ).toEqual({});
576
+ });
577
+
578
+ test("only the fields the user changed", () => {
579
+ expect(
580
+ getResourceAiAccessSettingsChanges({
581
+ saved: saved({
582
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
583
+ }),
584
+ values: values({
585
+ isAiInvestigationEnabled: false,
586
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
587
+ }),
588
+ offered: { allowlist: false, allowlistRemoveOnly: true },
589
+ }),
590
+ ).toEqual({ isAiInvestigationEnabled: false });
591
+
592
+ expect(
593
+ getResourceAiAccessSettingsChanges({
594
+ saved: saved(),
595
+ values: values({
596
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
597
+ }),
598
+ offered: ALL_OFFERED,
599
+ }),
600
+ ).toEqual({ aiRemediationMode: ResourceAiRemediationMode.BypassApproval });
601
+ });
602
+
603
+ test("never a mode the page does not know", () => {
604
+ expect(
605
+ getResourceAiAccessSettingsChanges({
606
+ saved: saved(),
607
+ values: values({ aiRemediationMode: "FullAuto" }),
608
+ offered: ALL_OFFERED,
609
+ }),
610
+ ).toEqual({});
611
+ });
612
+
613
+ test("the allowlist only when offered, and only when it changed", () => {
614
+ const stored: ResourceAiAccessSavedSettings = saved({
615
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
616
+ aiCommandAllowlist: ["docker stop web"],
617
+ });
618
+
619
+ expect(
620
+ getResourceAiAccessSettingsChanges({
621
+ saved: stored,
622
+ values: values({
623
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
624
+ aiCommandAllowlistText: "docker stop web\ndocker stop api",
625
+ }),
626
+ offered: ALL_OFFERED,
627
+ }),
628
+ ).toEqual({ aiCommandAllowlist: ["docker stop web", "docker stop api"] });
629
+
630
+ expect(
631
+ getResourceAiAccessSettingsChanges({
632
+ saved: stored,
633
+ values: values({
634
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
635
+ aiCommandAllowlistText: "docker stop web",
636
+ }),
637
+ offered: ALL_OFFERED,
638
+ }),
639
+ ).toEqual({});
640
+
641
+ expect(
642
+ getResourceAiAccessSettingsChanges({
643
+ saved: stored,
644
+ values: values({
645
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
646
+ aiCommandAllowlistText: "",
647
+ }),
648
+ offered: { allowlist: false, allowlistRemoveOnly: false },
649
+ }),
650
+ ).toEqual({});
651
+ });
652
+
653
+ test("re-sends a kept entry in the spelling it is stored in", () => {
654
+ expect(
655
+ getResourceAiAccessSettingsChanges({
656
+ saved: saved({
657
+ aiCommandAllowlist: ["docker stop web", "docker stop api"],
658
+ }),
659
+ values: values({ aiCommandAllowlistText: "docker stop web" }),
660
+ offered: ALL_OFFERED,
661
+ }),
662
+ ).toEqual({ aiCommandAllowlist: ["docker stop web"] });
663
+ });
664
+
665
+ test("rewrites a stored value that is not a clean list", () => {
666
+ expect(
667
+ getResourceAiAccessSettingsChanges({
668
+ saved: saved({ aiCommandAllowlist: ["docker stop web", ""] }),
669
+ values: values({ aiCommandAllowlistText: "docker stop web" }),
670
+ offered: ALL_OFFERED,
671
+ }),
672
+ ).toEqual({ aiCommandAllowlist: ["docker stop web"] });
673
+ });
674
+ });
675
+
676
+ describe("what loosens", () => {
677
+ test("any move up, Off -> Ask for approval included", () => {
678
+ expect(
679
+ getResourceAiAccessLooseningChanges({
680
+ saved: saved({ aiRemediationMode: ResourceAiRemediationMode.Disabled }),
681
+ changes: {
682
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
683
+ },
684
+ }),
685
+ ).toEqual(["switching fixes to Ask for approval"]);
686
+ expect(
687
+ getResourceAiAccessLooseningChanges({
688
+ saved: saved(),
689
+ changes: {
690
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
691
+ },
692
+ }),
693
+ ).toEqual(["switching fixes to Bypass approval"]);
694
+ });
695
+
696
+ test("moves down, the investigation switch and removals do not", () => {
697
+ expect(
698
+ getResourceAiAccessLooseningChanges({
699
+ saved: saved({
700
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
701
+ aiCommandAllowlist: ["docker stop web", "docker stop api"],
702
+ }),
703
+ changes: {
704
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
705
+ isAiInvestigationEnabled: true,
706
+ aiCommandAllowlist: ["docker stop web"],
707
+ },
708
+ }),
709
+ ).toEqual([]);
710
+ });
711
+
712
+ test("an entry the stored list does not hold, named", () => {
713
+ expect(
714
+ getResourceAiAccessLooseningChanges({
715
+ saved: saved({ aiCommandAllowlist: ["docker stop web"] }),
716
+ changes: {
717
+ aiCommandAllowlist: ["docker stop web", "docker stop api"],
718
+ },
719
+ }),
720
+ ).toEqual(['adding the allowlist entry "docker stop api"']);
721
+ expect(
722
+ getResourceAiAccessLooseningChanges({
723
+ saved: saved(),
724
+ changes: {
725
+ aiCommandAllowlist: ["docker stop web", "docker stop api"],
726
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
727
+ },
728
+ }),
729
+ ).toEqual([
730
+ "switching fixes to Automatic",
731
+ 'adding the allowlist entries "docker stop web", "docker stop api"',
732
+ ]);
733
+ });
734
+
735
+ test("an editor's allowlist field refuses any line the stored list lacks", () => {
736
+ expect(
737
+ getResourceAllowlistRemovalOnlyError({
738
+ text: "docker stop web",
739
+ storedValue: ["docker stop web", "docker stop api"],
740
+ }),
741
+ ).toBeNull();
742
+ expect(
743
+ getResourceAllowlistRemovalOnlyError({
744
+ text: "",
745
+ storedValue: ["docker stop web"],
746
+ }),
747
+ ).toBeNull();
748
+
749
+ const refusal: string | null = getResourceAllowlistRemovalOnlyError({
750
+ text: "docker stop web\ndocker stop db",
751
+ storedValue: ["docker stop web"],
752
+ });
753
+ expect(refusal).toContain(
754
+ 'Entry 2 ("docker stop db") is not in the saved allowlist.',
755
+ );
756
+ expect(refusal).toContain("Edit Auto Remediation Rule");
757
+ });
758
+ });
759
+
760
+ describe("the confirmation before a save", () => {
761
+ test("Bypass approval is always confirmed, naming what it unlocks", () => {
762
+ const confirmation: ResourceAiAccessConfirmation | null =
763
+ getResourceAiAccessConfirmation({
764
+ descriptor: DOCKER,
765
+ saved: saved(),
766
+ changes: {
767
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
768
+ },
769
+ });
770
+
771
+ expect(confirmation?.title).toBe("Turn on Bypass approval?");
772
+ expect(confirmation?.description).toContain(
773
+ "on this Docker host on its own — riskier changes such as stopping, killing or updating a container included",
774
+ );
775
+ expect(confirmation?.description).toContain(
776
+ getEveryModeProtectionsSentence(DOCKER),
777
+ );
778
+ });
779
+
780
+ test.each(
781
+ ALL_AI_RESOURCE_TYPES.filter((type: AiResourceType): boolean => {
782
+ return ENTRIES[type].broad !== null;
783
+ }),
784
+ )("%s: adding a broad entry is confirmed", (type: AiResourceType) => {
785
+ const descriptor: ResourceAiAgentDescriptor =
786
+ getResourceAiAgentDescriptor(type);
787
+ const confirmation: ResourceAiAccessConfirmation | null =
788
+ getResourceAiAccessConfirmation({
789
+ descriptor,
790
+ saved: saved({
791
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
792
+ }),
793
+ changes: {
794
+ aiCommandAllowlist: [ENTRIES[type].valid, ENTRIES[type].broad!],
795
+ },
796
+ });
797
+
798
+ expect(confirmation?.title).toBe(
799
+ "Let riskier changes run without approval?",
800
+ );
801
+ expect(confirmation?.description).toContain(
802
+ `The allowlist entry "${ENTRIES[type].broad}" uses a * for the object a change touches`,
803
+ );
804
+ expect(confirmation?.description).toContain("In Automatic mode");
805
+ });
806
+
807
+ test.each(ALL_AI_RESOURCE_TYPES)(
808
+ "%s: an entry that names its object needs no confirmation",
809
+ (type: AiResourceType) => {
810
+ expect(
811
+ getResourceAiAccessConfirmation({
812
+ descriptor: getResourceAiAgentDescriptor(type),
813
+ saved: saved({
814
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
815
+ }),
816
+ changes: { aiCommandAllowlist: [ENTRIES[type].valid] },
817
+ }),
818
+ ).toBeNull();
819
+ },
820
+ );
821
+
822
+ test("a broad entry kept while switching to Automatic is confirmed", () => {
823
+ const confirmation: ResourceAiAccessConfirmation | null =
824
+ getResourceAiAccessConfirmation({
825
+ descriptor: DOCKER,
826
+ saved: saved({ aiCommandAllowlist: ["docker stop *"] }),
827
+ changes: { aiRemediationMode: ResourceAiRemediationMode.Automatic },
828
+ });
829
+
830
+ expect(confirmation?.title).toBe(
831
+ "Let riskier changes run without approval?",
832
+ );
833
+ });
834
+
835
+ test("a broad entry added before Automatic is on says when it will apply", () => {
836
+ expect(
837
+ getResourceAiAccessConfirmation({
838
+ descriptor: DOCKER,
839
+ saved: saved(),
840
+ changes: {
841
+ aiCommandAllowlist: ["docker stop *", "docker kill -s TERM *"],
842
+ },
843
+ })?.description,
844
+ ).toContain(
845
+ 'The allowlist entries "docker stop *", "docker kill -s TERM *" use a * for the object a change touches, so they pre-approve a whole class of changes, not one. Once this Docker host is switched to Automatic mode',
846
+ );
847
+ });
848
+
849
+ test("no new risk, no confirmation: stepping down, or keeping what was saved", () => {
850
+ expect(
851
+ getResourceAiAccessConfirmation({
852
+ descriptor: DOCKER,
853
+ saved: saved({
854
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
855
+ aiCommandAllowlist: ["docker stop *"],
856
+ }),
857
+ changes: { aiRemediationMode: ResourceAiRemediationMode.Automatic },
858
+ }),
859
+ ).toBeNull();
860
+ expect(
861
+ getResourceAiAccessConfirmation({
862
+ descriptor: DOCKER,
863
+ saved: saved({
864
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
865
+ aiCommandAllowlist: ["docker stop *"],
866
+ }),
867
+ changes: { isAiInvestigationEnabled: false },
868
+ }),
869
+ ).toBeNull();
870
+ expect(
871
+ getResourceAiAccessConfirmation({
872
+ descriptor: DOCKER,
873
+ saved: saved(),
874
+ changes: { aiRemediationMode: ResourceAiRemediationMode.Disabled },
875
+ }),
876
+ ).toBeNull();
877
+ });
878
+
879
+ test("a Proxmox entry is never broad: its paths are written out per guest", () => {
880
+ const proxmox: ResourceAiAgentDescriptor = getResourceAiAgentDescriptor(
881
+ AiResourceType.ProxmoxCluster,
882
+ );
883
+ const changes: JSONObject = {
884
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
885
+ aiCommandAllowlist: [
886
+ "pvesh create /nodes/pve1/qemu/100/status/shutdown --timeout *",
887
+ ],
888
+ };
889
+ expect(
890
+ getResourceAiAccessConfirmation({
891
+ descriptor: proxmox,
892
+ saved: saved(),
893
+ changes,
894
+ }),
895
+ ).toBeNull();
896
+ });
897
+ });
898
+
899
+ describe("small words", () => {
900
+ test("lists and capitals", () => {
901
+ expect(formatNameList([], "and")).toBe("");
902
+ expect(formatNameList(["a"], "and")).toBe("a");
903
+ expect(formatNameList(["a", "b"], "or")).toBe("a or b");
904
+ expect(formatNameList(["a", "b", "c"], "and")).toBe("a, b and c");
905
+ expect(capitalizeFirst("switching fixes")).toBe("Switching fixes");
906
+ expect(capitalizeFirst("")).toBe("");
907
+ });
908
+
909
+ test.each(ALL_AI_RESOURCE_TYPES)(
910
+ "%s: the allowlist field's help gives its own example and the matcher's rules",
911
+ (type: AiResourceType) => {
912
+ const descriptor: ResourceAiAgentDescriptor =
913
+ getResourceAiAgentDescriptor(type);
914
+ const help: string = getResourceAllowlistFieldDescription(descriptor);
915
+
916
+ expect(help).toContain(`for example: ${descriptor.allowlistPlaceholder}`);
917
+ expect(help).toContain(`at most ${RESOURCE_ALLOWLIST_MAX_PATTERNS}`);
918
+ expect(help).toContain("a * stands for exactly one whole word");
919
+ },
920
+ );
921
+ });