@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,842 @@
1
+ import AllModelTypes, {
2
+ getModelTypeByName,
3
+ } from "../../../Models/DatabaseModels/Index";
4
+ import AutoRemediationSuggestion from "../../../Models/DatabaseModels/AutoRemediationSuggestion";
5
+ import CephCluster from "../../../Models/DatabaseModels/CephCluster";
6
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
7
+ import DatabaseServer from "../../../Models/DatabaseModels/DatabaseServer";
8
+ import DockerHost from "../../../Models/DatabaseModels/DockerHost";
9
+ import DockerSwarmCluster from "../../../Models/DatabaseModels/DockerSwarmCluster";
10
+ import Host from "../../../Models/DatabaseModels/Host";
11
+ import KubernetesAiAgent from "../../../Models/DatabaseModels/KubernetesAiAgent";
12
+ import PodmanHost from "../../../Models/DatabaseModels/PodmanHost";
13
+ import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
14
+ import ResourceAiAgent from "../../../Models/DatabaseModels/ResourceAiAgent";
15
+ import RunnerJob from "../../../Models/DatabaseModels/RunnerJob";
16
+ import VMwareVCenter from "../../../Models/DatabaseModels/VMwareVCenter";
17
+ import DatabaseRequestType from "../../../Server/Types/BaseDatabase/DatabaseRequestType";
18
+ import ColumnPermissions from "../../../Server/Types/Database/Permissions/ColumnPermission";
19
+ import TablePermission from "../../../Server/Types/Database/Permissions/TablePermission";
20
+ import { ColumnAccessControl } from "../../../Types/BaseDatabase/AccessControl";
21
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
22
+ import ColumnLength from "../../../Types/Database/ColumnLength";
23
+ import TableColumnType from "../../../Types/Database/TableColumnType";
24
+ import BadDataException from "../../../Types/Exception/BadDataException";
25
+ import ObjectID from "../../../Types/ObjectID";
26
+ import Permission, {
27
+ UserTenantAccessPermission,
28
+ } from "../../../Types/Permission";
29
+ import AiResourceType, {
30
+ ALL_AI_RESOURCE_TYPES,
31
+ } from "../../../Types/ResourceAiAgent/AiResourceType";
32
+ import { describe, expect, it } from "@jest/globals";
33
+ import { getMetadataArgsStorage } from "typeorm";
34
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
35
+ import type { IndexMetadataArgs } from "typeorm/metadata-args/IndexMetadataArgs";
36
+ import type { RelationMetadataArgs } from "typeorm/metadata-args/RelationMetadataArgs";
37
+
38
+ /*
39
+ * A resource AI agent's row, as the permission layer and the schema builder
40
+ * see it — the resource AI agent twin of KubernetesAiAgentModel.test.ts.
41
+ *
42
+ * - It is server-owned: nobody creates, updates or deletes one through the
43
+ * API, and there is no CRUD endpoint, API documentation or MCP tool for
44
+ * it (the dashboard reads it through the resource's AI access status).
45
+ * - It is polymorphic: one table for every AiResourceType, keyed by
46
+ * (resourceType, resourceId) with NO foreign key on resourceId. So its
47
+ * readers are whoever may read a resource of any of those models — the
48
+ * roles all of them share, plus each model's own Read permission — and
49
+ * the per-resource check is the resource AI access API's.
50
+ * - The key hash is readable, creatable and updatable by no role at all,
51
+ * a project owner included.
52
+ * - One agent per live resource, enforced by a named unique partial index.
53
+ * - Column for column it is the Kubernetes AI agent's row, with the cluster
54
+ * reference replaced by resourceType / resourceId / resourceIdentifier.
55
+ * - RunnerJob.targetResourceAiAgentId, RunnerJob.resourceType/resourceId
56
+ * and AutoRemediationSuggestion.resourceType/resourceId are read like
57
+ * their Kubernetes counterparts and written by nobody but the server.
58
+ */
59
+
60
+ const PROJECT_ID: ObjectID = new ObjectID(
61
+ "11111111-1111-4111-8111-111111111111",
62
+ );
63
+
64
+ type ModelType = { new (): BaseModel; name: string };
65
+
66
+ // The model each AiResourceType's resourceId points into.
67
+ const RESOURCE_MODELS: Record<AiResourceType, ModelType> = {
68
+ [AiResourceType.DockerHost]: DockerHost,
69
+ [AiResourceType.PodmanHost]: PodmanHost,
70
+ [AiResourceType.DockerSwarmCluster]: DockerSwarmCluster,
71
+ [AiResourceType.ProxmoxCluster]: ProxmoxCluster,
72
+ [AiResourceType.VMwareVCenter]: VMwareVCenter,
73
+ [AiResourceType.CephCluster]: CephCluster,
74
+ [AiResourceType.DatabaseServer]: DatabaseServer,
75
+ [AiResourceType.Host]: Host,
76
+ };
77
+
78
+ const UNIQUE_INDEX_NAME: string =
79
+ "IDX_ResourceAiAgent_projectId_resourceType_resourceId";
80
+
81
+ function propsWith(
82
+ permissions: Array<Permission>,
83
+ ): DatabaseCommonInteractionProps {
84
+ const tenantPermission: UserTenantAccessPermission = {
85
+ projectId: PROJECT_ID,
86
+ _type: "UserTenantAccessPermission",
87
+ permissions: permissions.map((permission: Permission) => {
88
+ return {
89
+ _type: "UserPermission",
90
+ permission,
91
+ labelIds: [],
92
+ isBlockPermission: false,
93
+ };
94
+ }),
95
+ };
96
+
97
+ return {
98
+ userId: ObjectID.generate(),
99
+ tenantId: PROJECT_ID,
100
+ userTenantAccessPermission: {
101
+ [PROJECT_ID.toString()]: tenantPermission,
102
+ },
103
+ };
104
+ }
105
+
106
+ function sorted(permissions: ReadonlyArray<Permission>): Array<Permission> {
107
+ return [...permissions].sort();
108
+ }
109
+
110
+ function unique(permissions: ReadonlyArray<Permission>): Array<Permission> {
111
+ return sorted(Array.from(new Set(permissions)));
112
+ }
113
+
114
+ function resourceReadLists(): Array<Array<Permission>> {
115
+ return ALL_AI_RESOURCE_TYPES.map((type: AiResourceType) => {
116
+ return new RESOURCE_MODELS[type]().getReadPermissions();
117
+ });
118
+ }
119
+
120
+ // Whoever may read a resource of at least one AiResourceType model.
121
+ function unionOfResourceReaders(): Array<Permission> {
122
+ return unique(resourceReadLists().flat());
123
+ }
124
+
125
+ // The roles every AiResourceType model's read list shares.
126
+ function intersectionOfResourceReaders(): Array<Permission> {
127
+ const lists: Array<Array<Permission>> = resourceReadLists();
128
+
129
+ return sorted(
130
+ lists[0]!.filter((permission: Permission): boolean => {
131
+ return lists.every((list: Array<Permission>): boolean => {
132
+ return list.includes(permission);
133
+ });
134
+ }),
135
+ );
136
+ }
137
+
138
+ function declaredColumns(model: ModelType): Array<ColumnMetadataArgs> {
139
+ return getMetadataArgsStorage().columns.filter(
140
+ (column: ColumnMetadataArgs): boolean => {
141
+ return column.target === model;
142
+ },
143
+ );
144
+ }
145
+
146
+ function declaredColumn(
147
+ model: ModelType,
148
+ property: string,
149
+ ): ColumnMetadataArgs {
150
+ const declared: ColumnMetadataArgs | undefined = declaredColumns(model).find(
151
+ (column: ColumnMetadataArgs): boolean => {
152
+ return column.propertyName === property;
153
+ },
154
+ );
155
+
156
+ if (!declared) {
157
+ throw new Error(`${model.name} declares no column ${property}`);
158
+ }
159
+
160
+ return declared;
161
+ }
162
+
163
+ function declaredRelations(model: ModelType): Array<RelationMetadataArgs> {
164
+ return getMetadataArgsStorage().relations.filter(
165
+ (relation: RelationMetadataArgs): boolean => {
166
+ return relation.target === model;
167
+ },
168
+ );
169
+ }
170
+
171
+ function declaredRelation(
172
+ model: ModelType,
173
+ property: string,
174
+ ): RelationMetadataArgs {
175
+ const declared: RelationMetadataArgs | undefined = declaredRelations(
176
+ model,
177
+ ).find((relation: RelationMetadataArgs): boolean => {
178
+ return relation.propertyName === property;
179
+ });
180
+
181
+ if (!declared) {
182
+ throw new Error(`${model.name} declares no relation ${property}`);
183
+ }
184
+
185
+ return declared;
186
+ }
187
+
188
+ function hasSingleColumnIndex(model: ModelType, column: string): boolean {
189
+ return getMetadataArgsStorage().indices.some(
190
+ (index: IndexMetadataArgs): boolean => {
191
+ return (
192
+ index.target === model &&
193
+ Array.isArray(index.columns) &&
194
+ index.columns.length === 1 &&
195
+ index.columns[0] === column
196
+ );
197
+ },
198
+ );
199
+ }
200
+
201
+ describe("ResourceAiAgent model", () => {
202
+ const agent: ResourceAiAgent = new ResourceAiAgent();
203
+ const accessControl: Record<string, ColumnAccessControl> =
204
+ agent.getColumnAccessControlForAllColumns();
205
+
206
+ it("is registered with the other database models", () => {
207
+ expect(AllModelTypes).toContain(ResourceAiAgent);
208
+ expect(getModelTypeByName("ResourceAiAgent")).toBe(ResourceAiAgent);
209
+ expect(
210
+ AllModelTypes.filter((model: unknown): boolean => {
211
+ return model === ResourceAiAgent;
212
+ }),
213
+ ).toHaveLength(1);
214
+ });
215
+
216
+ it("names its table and itself as the design says", () => {
217
+ expect(agent.tableName).toBe("ResourceAiAgent");
218
+ expect(agent.singularName).toBe("Resource AI Agent");
219
+ expect(agent.pluralName).toBe("Resource AI Agents");
220
+ expect(agent.tableDescription).toMatch(/not user-writable/);
221
+ });
222
+
223
+ it("is a project's row (tenant column projectId)", () => {
224
+ expect(agent.getTenantColumn()).toBe("projectId");
225
+ });
226
+
227
+ it("has no CRUD API, no API documentation and no MCP tools, like the Kubernetes AI agent", () => {
228
+ const kubernetesAgent: KubernetesAiAgent = new KubernetesAiAgent();
229
+
230
+ expect(agent.crudApiPath).toBeFalsy();
231
+ expect(agent.enableDocumentation).toBeFalsy();
232
+ expect(agent.enableMCP).toBeFalsy();
233
+
234
+ expect(Boolean(agent.crudApiPath)).toBe(
235
+ Boolean(kubernetesAgent.crudApiPath),
236
+ );
237
+ expect(Boolean(agent.enableDocumentation)).toBe(
238
+ Boolean(kubernetesAgent.enableDocumentation),
239
+ );
240
+ expect(Boolean(agent.enableMCP)).toBe(Boolean(kubernetesAgent.enableMCP));
241
+ });
242
+
243
+ it("nobody creates, updates or deletes a row through the API", () => {
244
+ expect(agent.getCreatePermissions()).toEqual([]);
245
+ expect(agent.getUpdatePermissions()).toEqual([]);
246
+ expect(agent.getDeletePermissions()).toEqual([]);
247
+ });
248
+
249
+ it("is readable by exactly the roles that may read a resource of any AiResourceType model", () => {
250
+ expect(sorted(agent.getReadPermissions())).toEqual(
251
+ unionOfResourceReaders(),
252
+ );
253
+ });
254
+
255
+ it.each(
256
+ ALL_AI_RESOURCE_TYPES.map((type: AiResourceType) => {
257
+ return [type];
258
+ }),
259
+ )("whoever may read a %s may read its agent", (type: AiResourceType) => {
260
+ for (const permission of new RESOURCE_MODELS[type]().getReadPermissions()) {
261
+ expect({ type, permission, readable: true }).toEqual({
262
+ type,
263
+ permission,
264
+ readable: agent.getReadPermissions().includes(permission),
265
+ });
266
+ }
267
+ });
268
+
269
+ /*
270
+ * Beyond the roles every resource model shares, the list holds only the
271
+ * models' own Read permissions — never an Edit, Create or Delete one, and
272
+ * nothing a resource model does not itself grant read with.
273
+ */
274
+ it("adds nothing but the resource models' own Read permissions to the roles they all share", () => {
275
+ const shared: Array<Permission> = intersectionOfResourceReaders();
276
+ const extra: Array<Permission> = agent
277
+ .getReadPermissions()
278
+ .filter((permission: Permission): boolean => {
279
+ return !shared.includes(permission);
280
+ });
281
+
282
+ expect(shared.length).toBeGreaterThan(3);
283
+ expect(extra).toHaveLength(ALL_AI_RESOURCE_TYPES.length);
284
+
285
+ for (const permission of extra) {
286
+ expect(String(permission)).toMatch(/^Read/);
287
+ }
288
+ });
289
+
290
+ /*
291
+ * Mirrors the Kubernetes AI agent: its readers are the cluster's roles
292
+ * plus Read Kubernetes Cluster; ours are the same roles plus each resource
293
+ * model's own Read permission.
294
+ */
295
+ it("shares the Kubernetes AI agent's roles, with each model's own Read permission in place of the cluster's", () => {
296
+ const kubernetesReaders: Array<Permission> =
297
+ new KubernetesAiAgent().getReadPermissions();
298
+
299
+ expect(kubernetesReaders).toContain(Permission.ReadKubernetesCluster);
300
+ expect(agent.getReadPermissions()).not.toContain(
301
+ Permission.ReadKubernetesCluster,
302
+ );
303
+ expect(sorted(intersectionOfResourceReaders())).toEqual(
304
+ sorted(
305
+ kubernetesReaders.filter((permission: Permission): boolean => {
306
+ return permission !== Permission.ReadKubernetesCluster;
307
+ }),
308
+ ),
309
+ );
310
+ });
311
+
312
+ it("every column is server-written: create and update ACLs are empty", () => {
313
+ for (const [column, acl] of Object.entries(accessControl)) {
314
+ expect({ column, create: acl.create }).toEqual({ column, create: [] });
315
+ expect({ column, update: acl.update }).toEqual({ column, update: [] });
316
+ }
317
+ });
318
+
319
+ it("every column but the key hash is readable by the resource readers", () => {
320
+ const readable: Array<string> = Object.keys(accessControl).filter(
321
+ (column: string): boolean => {
322
+ return column !== "keyHash";
323
+ },
324
+ );
325
+
326
+ expect(readable.length).toBeGreaterThan(10);
327
+
328
+ for (const column of readable) {
329
+ expect({ column, read: sorted(accessControl[column]!.read) }).toEqual({
330
+ column,
331
+ read: sorted(agent.getReadPermissions()),
332
+ });
333
+ }
334
+ });
335
+
336
+ it("the key hash is readable, creatable and updatable by no role at all", () => {
337
+ expect(accessControl["keyHash"]).toEqual({
338
+ create: [],
339
+ read: [],
340
+ update: [],
341
+ });
342
+ });
343
+
344
+ it.each([DatabaseRequestType.Create, DatabaseRequestType.Update])(
345
+ "a project owner and admin cannot %s the key hash",
346
+ (requestType: DatabaseRequestType) => {
347
+ expect(() => {
348
+ ColumnPermissions.checkDataColumnPermissions(
349
+ ResourceAiAgent,
350
+ { keyHash: "0".repeat(64) } as unknown as ResourceAiAgent,
351
+ propsWith([Permission.ProjectOwner, Permission.ProjectAdmin]),
352
+ requestType,
353
+ );
354
+ }).toThrow(BadDataException);
355
+ },
356
+ );
357
+
358
+ it.each([
359
+ ["resourceType", AiResourceType.DockerHost],
360
+ ["resourceId", ObjectID.generate()],
361
+ ["resourceIdentifier", "docker-host-prod-1"],
362
+ ["connectionStatus", "connected"],
363
+ ["posture", { allowWrites: true }],
364
+ ])(
365
+ "a project owner and admin cannot update %s either",
366
+ (column: string, value: unknown) => {
367
+ expect(() => {
368
+ ColumnPermissions.checkDataColumnPermissions(
369
+ ResourceAiAgent,
370
+ { [column]: value } as unknown as ResourceAiAgent,
371
+ propsWith([Permission.ProjectOwner, Permission.ProjectAdmin]),
372
+ DatabaseRequestType.Update,
373
+ );
374
+ }).toThrow(BadDataException);
375
+ },
376
+ );
377
+
378
+ it("a project owner may read the table but not create, update or delete in it", () => {
379
+ expect(() => {
380
+ TablePermission.checkTableLevelPermissions(
381
+ ResourceAiAgent,
382
+ propsWith([Permission.ProjectOwner]),
383
+ DatabaseRequestType.Read,
384
+ );
385
+ }).not.toThrow();
386
+
387
+ for (const requestType of [
388
+ DatabaseRequestType.Create,
389
+ DatabaseRequestType.Update,
390
+ DatabaseRequestType.Delete,
391
+ ]) {
392
+ expect(() => {
393
+ TablePermission.checkTableLevelPermissions(
394
+ ResourceAiAgent,
395
+ propsWith([Permission.ProjectOwner]),
396
+ requestType,
397
+ );
398
+ }).toThrow();
399
+ }
400
+ });
401
+
402
+ it.each([
403
+ Permission.ReadDockerHost,
404
+ Permission.ReadCephCluster,
405
+ Permission.ReadHost,
406
+ ])("a holder of only %s may read the table", (permission: Permission) => {
407
+ expect(() => {
408
+ TablePermission.checkTableLevelPermissions(
409
+ ResourceAiAgent,
410
+ propsWith([permission]),
411
+ DatabaseRequestType.Read,
412
+ );
413
+ }).not.toThrow();
414
+ });
415
+
416
+ it.each([
417
+ Permission.EditDockerHost,
418
+ Permission.ReadKubernetesCluster,
419
+ Permission.RunbookViewer,
420
+ ])("a holder of only %s may not read the table", (permission: Permission) => {
421
+ expect(() => {
422
+ TablePermission.checkTableLevelPermissions(
423
+ ResourceAiAgent,
424
+ propsWith([permission]),
425
+ DatabaseRequestType.Read,
426
+ );
427
+ }).toThrow();
428
+ });
429
+
430
+ it("a resource's readers never select the key hash", () => {
431
+ const readableColumns: Array<string> =
432
+ ColumnPermissions.getModelColumnsByPermissions(
433
+ ResourceAiAgent,
434
+ propsWith([Permission.ProjectOwner]).userTenantAccessPermission![
435
+ PROJECT_ID.toString()
436
+ ]!.permissions,
437
+ DatabaseRequestType.Read,
438
+ ).columns;
439
+
440
+ expect(readableColumns).toContain("connectionStatus");
441
+ expect(readableColumns).toContain("resourceType");
442
+ expect(readableColumns).toContain("resourceId");
443
+ expect(readableColumns).not.toContain("keyHash");
444
+ });
445
+
446
+ it("allows one agent per live resource, with a named unique partial index", () => {
447
+ const index: IndexMetadataArgs | undefined =
448
+ getMetadataArgsStorage().indices.find(
449
+ (candidate: IndexMetadataArgs): boolean => {
450
+ return (
451
+ candidate.target === ResourceAiAgent &&
452
+ candidate.name === UNIQUE_INDEX_NAME
453
+ );
454
+ },
455
+ );
456
+
457
+ expect(index).toBeDefined();
458
+ expect(index?.columns).toEqual(["projectId", "resourceType", "resourceId"]);
459
+ expect(index?.unique).toBe(true);
460
+ expect(index?.where).toBe('"deletedAt" IS NULL');
461
+ });
462
+
463
+ it("is the only unique index on the table", () => {
464
+ const uniques: Array<IndexMetadataArgs> =
465
+ getMetadataArgsStorage().indices.filter(
466
+ (candidate: IndexMetadataArgs): boolean => {
467
+ return (
468
+ candidate.target === ResourceAiAgent && candidate.unique === true
469
+ );
470
+ },
471
+ );
472
+
473
+ expect(
474
+ uniques.map((candidate: IndexMetadataArgs) => {
475
+ return candidate.name;
476
+ }),
477
+ ).toEqual([UNIQUE_INDEX_NAME]);
478
+ });
479
+
480
+ it.each(["projectId", "resourceType", "resourceId"])(
481
+ "indexes %s on its own, for lookups by project and by resource",
482
+ (column: string) => {
483
+ expect(hasSingleColumnIndex(ResourceAiAgent, column)).toBe(true);
484
+ },
485
+ );
486
+
487
+ it("starts disconnected", () => {
488
+ const metadata: ReturnType<ResourceAiAgent["getTableColumnMetadata"]> =
489
+ agent.getTableColumnMetadata("connectionStatus");
490
+
491
+ expect(metadata.defaultValue).toBe("disconnected");
492
+ expect(metadata.isDefaultValueColumn).toBe(true);
493
+ expect(declaredColumn(ResourceAiAgent, "connectionStatus").options).toEqual(
494
+ expect.objectContaining({ nullable: false, default: "disconnected" }),
495
+ );
496
+ });
497
+
498
+ it("links only to its project, and that relation cascades", () => {
499
+ const relations: Array<RelationMetadataArgs> =
500
+ declaredRelations(ResourceAiAgent);
501
+
502
+ expect(
503
+ relations.map((relation: RelationMetadataArgs) => {
504
+ return relation.propertyName;
505
+ }),
506
+ ).toEqual(["project"]);
507
+ expect(relations[0]!.relationType).toBe("many-to-one");
508
+ expect(relations[0]!.options.onDelete).toBe("CASCADE");
509
+ });
510
+
511
+ /*
512
+ * resourceId points into a different table for every resourceType, so it
513
+ * can have no foreign key: no relation is declared on it, and its API
514
+ * metadata is a plain id, not an entity.
515
+ */
516
+ it("keeps resourceId a plain, required, indexed id with no relation", () => {
517
+ const metadata: ReturnType<ResourceAiAgent["getTableColumnMetadata"]> =
518
+ agent.getTableColumnMetadata("resourceId");
519
+
520
+ expect(metadata.type).toBe(TableColumnType.ObjectID);
521
+ expect(metadata.required).toBe(true);
522
+ expect(metadata.modelType).toBeUndefined();
523
+ expect(declaredColumn(ResourceAiAgent, "resourceId").options.nullable).toBe(
524
+ false,
525
+ );
526
+
527
+ for (const relation of declaredRelations(ResourceAiAgent)) {
528
+ expect(relation.propertyName).not.toMatch(/^resource/);
529
+ }
530
+ });
531
+
532
+ it("stores resourceType as a required short string", () => {
533
+ const metadata: ReturnType<ResourceAiAgent["getTableColumnMetadata"]> =
534
+ agent.getTableColumnMetadata("resourceType");
535
+
536
+ expect(metadata.type).toBe(TableColumnType.ShortText);
537
+ expect(metadata.required).toBe(true);
538
+ expect(declaredColumn(ResourceAiAgent, "resourceType").options).toEqual(
539
+ expect.objectContaining({
540
+ nullable: false,
541
+ length: ColumnLength.ShortText,
542
+ }),
543
+ );
544
+ });
545
+
546
+ it.each(
547
+ ALL_AI_RESOURCE_TYPES.map((type: AiResourceType) => {
548
+ return [type];
549
+ }),
550
+ )("has room for the resource type %s", (type: AiResourceType) => {
551
+ expect(type.length).toBeLessThanOrEqual(ColumnLength.ShortText);
552
+ });
553
+
554
+ it("keeps resourceIdentifier optional and bounded", () => {
555
+ const metadata: ReturnType<ResourceAiAgent["getTableColumnMetadata"]> =
556
+ agent.getTableColumnMetadata("resourceIdentifier");
557
+
558
+ expect(metadata.type).toBe(TableColumnType.LongText);
559
+ expect(metadata.required).toBe(false);
560
+ expect(
561
+ declaredColumn(ResourceAiAgent, "resourceIdentifier").options,
562
+ ).toEqual(
563
+ expect.objectContaining({
564
+ nullable: true,
565
+ length: ColumnLength.LongText,
566
+ }),
567
+ );
568
+ });
569
+
570
+ it("maps every AiResourceType to the model of the same name", () => {
571
+ expect(Object.keys(RESOURCE_MODELS).sort()).toEqual(
572
+ [...ALL_AI_RESOURCE_TYPES].sort(),
573
+ );
574
+
575
+ for (const type of ALL_AI_RESOURCE_TYPES) {
576
+ expect(new RESOURCE_MODELS[type]().tableName).toBe(type);
577
+ expect(getModelTypeByName(type)).toBe(RESOURCE_MODELS[type]);
578
+ }
579
+ });
580
+ });
581
+
582
+ describe("ResourceAiAgent mirrors KubernetesAiAgent column for column", () => {
583
+ const KUBERNETES_ONLY: Array<string> = ["kubernetesClusterId"];
584
+ const RESOURCE_ONLY: Array<string> = [
585
+ "resourceType",
586
+ "resourceId",
587
+ "resourceIdentifier",
588
+ ];
589
+
590
+ function propertyNames(model: ModelType): Array<string> {
591
+ return declaredColumns(model)
592
+ .map((column: ColumnMetadataArgs): string => {
593
+ return column.propertyName;
594
+ })
595
+ .sort();
596
+ }
597
+
598
+ it("has the Kubernetes AI agent's columns, with the cluster replaced by the resource", () => {
599
+ expect(propertyNames(ResourceAiAgent)).toEqual(
600
+ [
601
+ ...propertyNames(KubernetesAiAgent).filter((name: string): boolean => {
602
+ return !KUBERNETES_ONLY.includes(name);
603
+ }),
604
+ ...RESOURCE_ONLY,
605
+ ].sort(),
606
+ );
607
+ });
608
+
609
+ it.each(
610
+ [
611
+ "projectId",
612
+ "keyHash",
613
+ "agentVersion",
614
+ "posture",
615
+ "lastAliveAt",
616
+ "connectionStatus",
617
+ "lastRegisteredAt",
618
+ "registeredWithIngestionKeyId",
619
+ "lastRefusedRegistrationAt",
620
+ "lastRefusedRegistrationReason",
621
+ ].map((property: string) => {
622
+ return [property];
623
+ }),
624
+ )(
625
+ "%s is declared exactly as on the Kubernetes AI agent",
626
+ (property: string) => {
627
+ const ours: ColumnMetadataArgs = declaredColumn(
628
+ ResourceAiAgent,
629
+ property,
630
+ );
631
+ const theirs: ColumnMetadataArgs = declaredColumn(
632
+ KubernetesAiAgent,
633
+ property,
634
+ );
635
+
636
+ expect({
637
+ type: ours.options.type,
638
+ length: ours.options.length,
639
+ nullable: ours.options.nullable,
640
+ default: ours.options.default,
641
+ transformer: Boolean(ours.options.transformer),
642
+ }).toEqual({
643
+ type: theirs.options.type,
644
+ length: theirs.options.length,
645
+ nullable: theirs.options.nullable,
646
+ default: theirs.options.default,
647
+ transformer: Boolean(theirs.options.transformer),
648
+ });
649
+
650
+ const ourMetadata: ReturnType<ResourceAiAgent["getTableColumnMetadata"]> =
651
+ new ResourceAiAgent().getTableColumnMetadata(property);
652
+ const theirMetadata: ReturnType<
653
+ KubernetesAiAgent["getTableColumnMetadata"]
654
+ > = new KubernetesAiAgent().getTableColumnMetadata(property);
655
+
656
+ expect({
657
+ type: ourMetadata.type,
658
+ required: ourMetadata.required,
659
+ title: ourMetadata.title,
660
+ }).toEqual({
661
+ type: theirMetadata.type,
662
+ required: theirMetadata.required,
663
+ title: theirMetadata.title,
664
+ });
665
+ },
666
+ );
667
+
668
+ it("the project relation is declared exactly as on the Kubernetes AI agent", () => {
669
+ const ours: RelationMetadataArgs = declaredRelation(
670
+ ResourceAiAgent,
671
+ "project",
672
+ );
673
+ const theirs: RelationMetadataArgs = declaredRelation(
674
+ KubernetesAiAgent,
675
+ "project",
676
+ );
677
+
678
+ expect(ours.options).toEqual(theirs.options);
679
+ });
680
+ });
681
+
682
+ describe("RunnerJob.targetResourceAiAgentId", () => {
683
+ const job: RunnerJob = new RunnerJob();
684
+ const accessControl: Record<string, ColumnAccessControl> =
685
+ job.getColumnAccessControlForAllColumns();
686
+
687
+ it.each(["targetResourceAiAgent", "targetResourceAiAgentId"])(
688
+ "%s is read like targetAgentId and written by nobody but the server",
689
+ (column: string) => {
690
+ expect(accessControl[column]).toBeDefined();
691
+ expect(accessControl[column]!.create).toEqual([]);
692
+ expect(accessControl[column]!.update).toEqual([]);
693
+ expect(sorted(accessControl[column]!.read)).toEqual(
694
+ sorted(accessControl["targetAgentId"]!.read),
695
+ );
696
+ },
697
+ );
698
+
699
+ it("is an entity relation to ResourceAiAgent, backed by the id column", () => {
700
+ const metadata: ReturnType<RunnerJob["getTableColumnMetadata"]> =
701
+ job.getTableColumnMetadata("targetResourceAiAgent");
702
+
703
+ expect(metadata.modelType).toBe(ResourceAiAgent);
704
+ expect(metadata.manyToOneRelationColumn).toBe("targetResourceAiAgentId");
705
+ expect(job.getTableColumnMetadata("targetResourceAiAgentId").required).toBe(
706
+ false,
707
+ );
708
+ expect(
709
+ declaredColumn(RunnerJob, "targetResourceAiAgentId").options.nullable,
710
+ ).toBe(true);
711
+ });
712
+
713
+ it("is nulled, never cascaded, when the agent row goes — like the Kubernetes AI agent target", () => {
714
+ const ours: RelationMetadataArgs = declaredRelation(
715
+ RunnerJob,
716
+ "targetResourceAiAgent",
717
+ );
718
+ const theirs: RelationMetadataArgs = declaredRelation(
719
+ RunnerJob,
720
+ "targetKubernetesAiAgent",
721
+ );
722
+
723
+ expect(ours.relationType).toBe("many-to-one");
724
+ expect(ours.options.onDelete).toBe("SET NULL");
725
+ expect((ours.type as () => unknown)()).toBe(ResourceAiAgent);
726
+ expect(ours.options).toEqual(theirs.options);
727
+ });
728
+
729
+ it("is indexed, for the agent's claim query", () => {
730
+ expect(hasSingleColumnIndex(RunnerJob, "targetResourceAiAgentId")).toBe(
731
+ true,
732
+ );
733
+ });
734
+ });
735
+
736
+ describe("RunnerJob.resourceType and RunnerJob.resourceId", () => {
737
+ const job: RunnerJob = new RunnerJob();
738
+ const accessControl: Record<string, ColumnAccessControl> =
739
+ job.getColumnAccessControlForAllColumns();
740
+
741
+ it.each(["resourceType", "resourceId"])(
742
+ "%s is read like kubernetesClusterId and written by nobody but the server",
743
+ (column: string) => {
744
+ expect(accessControl[column]).toBeDefined();
745
+ expect(accessControl[column]!.create).toEqual([]);
746
+ expect(accessControl[column]!.update).toEqual([]);
747
+ expect(sorted(accessControl[column]!.read)).toEqual(
748
+ sorted(accessControl["kubernetesClusterId"]!.read),
749
+ );
750
+ },
751
+ );
752
+
753
+ it.each(["resourceType", "resourceId"])(
754
+ "%s is optional (set on ResourceCommand jobs only) and indexed, for a resource's AI page",
755
+ (column: string) => {
756
+ expect(job.getTableColumnMetadata(column).required).toBe(false);
757
+ expect(declaredColumn(RunnerJob, column).options.nullable).toBe(true);
758
+ expect(hasSingleColumnIndex(RunnerJob, column)).toBe(true);
759
+ },
760
+ );
761
+
762
+ it("keeps resourceId a plain id: no relation, no foreign key", () => {
763
+ expect(job.getTableColumnMetadata("resourceId").type).toBe(
764
+ TableColumnType.ObjectID,
765
+ );
766
+ expect(job.getTableColumnMetadata("resourceId").modelType).toBeUndefined();
767
+ expect(
768
+ declaredRelations(RunnerJob).some(
769
+ (relation: RelationMetadataArgs): boolean => {
770
+ return relation.propertyName.startsWith("resource");
771
+ },
772
+ ),
773
+ ).toBe(false);
774
+ });
775
+
776
+ it("stores resourceType as a short string", () => {
777
+ expect(job.getTableColumnMetadata("resourceType").type).toBe(
778
+ TableColumnType.ShortText,
779
+ );
780
+ expect(declaredColumn(RunnerJob, "resourceType").options.length).toBe(
781
+ ColumnLength.ShortText,
782
+ );
783
+ });
784
+ });
785
+
786
+ describe("AutoRemediationSuggestion.resourceType and resourceId", () => {
787
+ const suggestion: AutoRemediationSuggestion = new AutoRemediationSuggestion();
788
+ const accessControl: Record<string, ColumnAccessControl> =
789
+ suggestion.getColumnAccessControlForAllColumns();
790
+
791
+ it.each(["resourceType", "resourceId"])(
792
+ "%s is read like kubernetesClusterId and written by nobody but the server",
793
+ (column: string) => {
794
+ expect(accessControl[column]).toBeDefined();
795
+ expect(accessControl[column]!.create).toEqual([]);
796
+ expect(accessControl[column]!.update).toEqual([]);
797
+ expect(sorted(accessControl[column]!.read)).toEqual(
798
+ sorted(accessControl["kubernetesClusterId"]!.read),
799
+ );
800
+ },
801
+ );
802
+
803
+ it.each(["resourceType", "resourceId"])(
804
+ "%s is optional (set on resource rounds only) and indexed",
805
+ (column: string) => {
806
+ expect(suggestion.getTableColumnMetadata(column).required).toBe(false);
807
+ expect(
808
+ declaredColumn(AutoRemediationSuggestion, column).options.nullable,
809
+ ).toBe(true);
810
+ expect(hasSingleColumnIndex(AutoRemediationSuggestion, column)).toBe(
811
+ true,
812
+ );
813
+ },
814
+ );
815
+
816
+ it("keeps resourceId a plain id: no relation, no foreign key", () => {
817
+ expect(suggestion.getTableColumnMetadata("resourceId").type).toBe(
818
+ TableColumnType.ObjectID,
819
+ );
820
+ expect(
821
+ declaredRelations(AutoRemediationSuggestion).some(
822
+ (relation: RelationMetadataArgs): boolean => {
823
+ return relation.propertyName.startsWith("resource");
824
+ },
825
+ ),
826
+ ).toBe(false);
827
+ });
828
+
829
+ it("a project owner cannot write either column through the API", () => {
830
+ expect(() => {
831
+ ColumnPermissions.checkDataColumnPermissions(
832
+ AutoRemediationSuggestion,
833
+ {
834
+ resourceType: AiResourceType.DockerHost,
835
+ resourceId: ObjectID.generate(),
836
+ } as unknown as AutoRemediationSuggestion,
837
+ propsWith([Permission.ProjectOwner]),
838
+ DatabaseRequestType.Update,
839
+ );
840
+ }).toThrow(BadDataException);
841
+ });
842
+ });