@oneuptime/common 14.0.8 → 14.0.9

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 (639) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  3. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  4. package/Models/DatabaseModels/Project.ts +55 -5
  5. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  6. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  7. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  8. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  9. package/Server/API/AIChatAPI.ts +6 -3
  10. package/Server/API/AIInvestigationAPI.ts +17 -10
  11. package/Server/API/AutoRemediationAPI.ts +207 -25
  12. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  13. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  14. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  15. package/Server/Infrastructure/GlobalCache.ts +32 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  20. package/Server/Middleware/TelemetryIngest.ts +41 -18
  21. package/Server/Services/AIService.ts +70 -1
  22. package/Server/Services/AlertService.ts +352 -11
  23. package/Server/Services/AlertSeverityService.ts +65 -15
  24. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  25. package/Server/Services/IncidentService.ts +280 -104
  26. package/Server/Services/IncidentSeverityService.ts +66 -15
  27. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  28. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  29. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  30. package/Server/Services/KubernetesClusterService.ts +129 -21
  31. package/Server/Services/ProjectService.ts +32 -0
  32. package/Server/Services/ProxmoxResourceService.ts +322 -5
  33. package/Server/Services/RunnerJobService.ts +241 -104
  34. package/Server/Services/RunnerService.ts +1 -1
  35. package/Server/Services/ScheduledMaintenanceService.ts +737 -1
  36. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  37. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  38. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  39. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  40. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  41. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  42. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  43. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  44. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  45. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  46. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  47. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  48. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  49. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  50. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  51. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  52. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  53. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  54. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  55. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  56. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  57. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  58. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  59. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  60. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  61. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  62. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  63. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  64. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  65. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  66. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  67. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  68. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  69. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  70. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  71. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  72. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  73. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  74. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  75. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  76. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  77. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  78. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  79. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  80. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  81. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  82. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  83. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  84. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  85. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  86. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  87. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  88. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  89. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  90. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  91. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  92. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  93. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  94. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  95. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  96. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  97. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  98. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  99. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  100. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  101. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  102. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  103. package/Tests/App/Dashboard/EventNotes.test.tsx +2 -2
  104. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +587 -13
  105. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  106. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  107. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  108. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  109. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  110. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  111. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  112. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  113. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  114. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  115. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  116. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  117. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  118. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  119. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  120. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  121. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  122. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  123. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  124. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  125. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  126. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  127. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  128. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  129. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  130. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  131. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  132. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  133. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  134. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  135. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  136. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  137. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  138. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  139. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  140. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  141. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  142. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  143. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  144. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  145. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  146. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  147. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  148. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  149. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  150. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  151. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  152. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  153. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  154. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  155. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  156. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  157. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  158. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  159. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  160. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  161. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  162. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  163. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  164. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  165. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  166. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  167. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  168. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  169. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  170. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  171. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  172. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  173. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  174. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  175. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  176. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  177. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  178. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  179. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  180. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  181. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  182. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  183. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  184. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  185. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  186. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  187. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  188. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  189. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  190. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  191. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  192. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  193. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  194. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  195. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  196. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  197. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  198. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  199. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  200. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  201. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  202. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  203. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  204. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  205. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  206. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  207. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  208. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  209. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  210. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  211. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  212. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  213. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  214. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  215. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  216. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  217. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  218. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  219. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  220. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  221. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  222. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  223. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  224. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  225. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  226. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  227. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  228. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  229. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  230. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  231. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  232. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  233. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  234. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  235. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  236. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  237. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  238. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  239. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  240. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  241. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  242. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  243. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  244. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  245. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  246. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  247. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  248. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  249. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  250. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  251. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  252. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  253. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  254. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  255. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  256. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  257. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  258. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  259. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  260. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  261. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  262. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  263. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  264. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  265. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  266. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  267. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  268. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  269. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  270. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  271. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  272. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  273. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  274. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  275. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  276. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  277. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  278. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  279. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  280. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  281. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  282. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  283. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  284. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  285. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  286. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  287. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  288. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  289. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  290. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  291. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  292. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  293. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  294. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  295. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  296. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  297. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  298. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  299. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  300. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  301. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  302. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  303. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  304. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  305. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  306. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  307. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  308. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  309. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  310. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  311. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  312. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  313. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  314. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  315. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  316. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  317. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  318. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  319. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  320. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  321. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  322. package/Tests/UI/Components/Input.test.tsx +80 -1
  323. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  324. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  325. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  326. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  327. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  328. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  329. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  330. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  331. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  332. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  333. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  334. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  335. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  336. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  337. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  338. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  339. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  340. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  341. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  342. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  343. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  344. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  345. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  346. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  347. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  348. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  349. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  350. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  351. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  352. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  353. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  354. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  355. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  356. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  357. package/Types/AI/AIRunType.ts +9 -4
  358. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  359. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  360. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  361. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  362. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  363. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  364. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  365. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  366. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  367. package/Types/Team/ComplianceRule.ts +434 -0
  368. package/Types/Team/ComplianceRuleType.ts +21 -0
  369. package/Types/Team/TeamComplianceStatus.ts +81 -0
  370. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  371. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  372. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  373. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  374. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  375. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  376. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  377. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  378. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  379. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  380. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  381. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  382. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  383. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  384. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  385. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  386. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  387. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  388. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  389. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  390. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  391. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  392. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  393. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  394. package/UI/Components/Input/Input.tsx +90 -57
  395. package/UI/Components/List/ListRow.tsx +8 -74
  396. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  397. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  398. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  399. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  400. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  401. package/UI/Components/ModelTable/BaseModelTable.tsx +12 -1
  402. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  403. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  404. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  405. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  406. package/UI/Components/Table/TableBody.tsx +8 -2
  407. package/UI/Components/Table/TableRow.tsx +20 -140
  408. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  409. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  410. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  411. package/UI/Components/TelemetryViewer/types.ts +2 -0
  412. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  413. package/UI/Components/Toggle/Toggle.tsx +26 -0
  414. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  415. package/UI/Styles/Theme.css +14 -2
  416. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  417. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  418. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  419. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  420. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  421. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  422. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  423. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  424. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  425. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  426. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  427. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  428. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  429. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  430. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  431. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  432. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  433. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  434. package/build/dist/Server/API/AIChatAPI.js +6 -1
  435. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  436. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  437. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  438. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  439. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  440. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  441. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  442. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  443. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  444. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  445. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  446. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  447. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  448. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  449. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  450. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  451. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  452. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  453. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  454. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  455. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  456. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  457. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  458. package/build/dist/Server/Services/AIService.js +59 -1
  459. package/build/dist/Server/Services/AIService.js.map +1 -1
  460. package/build/dist/Server/Services/AlertService.js +248 -13
  461. package/build/dist/Server/Services/AlertService.js.map +1 -1
  462. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  463. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  464. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  465. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  466. package/build/dist/Server/Services/IncidentService.js +199 -72
  467. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  468. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  469. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  470. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  471. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  472. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  473. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  474. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  475. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  476. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  477. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  478. package/build/dist/Server/Services/ProjectService.js +22 -0
  479. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  480. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  481. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  482. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  483. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  484. package/build/dist/Server/Services/RunnerService.js +1 -1
  485. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  486. package/build/dist/Server/Services/ScheduledMaintenanceService.js +535 -3
  487. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  488. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  489. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  490. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  491. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  492. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  493. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  494. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  495. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  496. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  497. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  498. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  499. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  500. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  501. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  502. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  503. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  504. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  505. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  506. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  507. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  508. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  509. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  510. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  511. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  512. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  513. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  514. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  515. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  516. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  517. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  518. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  519. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  520. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  521. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  522. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  523. package/build/dist/Types/AI/AIRunType.js +9 -4
  524. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  525. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  526. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  527. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  528. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  529. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  530. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  531. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  532. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  533. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  534. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  535. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  536. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  537. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  538. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  539. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  540. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  541. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  542. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  543. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  544. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  545. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  546. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  547. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  548. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  549. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  550. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  551. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  552. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  553. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  554. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  555. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  556. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  557. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  558. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  559. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  560. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  561. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  562. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  563. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  564. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  565. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  566. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  567. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  568. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  569. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  570. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  571. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  572. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  573. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  574. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  575. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  576. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  577. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  578. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  579. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  580. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  581. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  582. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  583. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  584. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  585. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  586. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  587. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  588. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  589. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  590. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  591. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  592. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  593. package/build/dist/UI/Components/Input/Input.js +69 -54
  594. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  595. package/build/dist/UI/Components/List/ListRow.js +4 -39
  596. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  597. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  598. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  599. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  600. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  601. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  602. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  603. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  604. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  605. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  606. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  607. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +10 -0
  608. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  609. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  610. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  611. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  612. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  613. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  614. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  615. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  616. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  617. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  618. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  619. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  620. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  621. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  622. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  623. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  624. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  625. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  626. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  627. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  628. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  629. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  630. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  631. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  632. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  633. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  634. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  635. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  636. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  637. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  638. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  639. package/package.json +1 -1
@@ -0,0 +1,1329 @@
1
+ import CommonAPI from "../../../Server/API/CommonAPI";
2
+ import AlertFeedService from "../../../Server/Services/AlertFeedService";
3
+ import AutoRemediationSuggestionService from "../../../Server/Services/AutoRemediationSuggestionService";
4
+ import IncidentFeedService from "../../../Server/Services/IncidentFeedService";
5
+ import KubernetesClusterAiAccessService from "../../../Server/Services/KubernetesClusterAiAccessService";
6
+ import ProjectService from "../../../Server/Services/ProjectService";
7
+ import RunnerService from "../../../Server/Services/RunnerService";
8
+ import CommandPlanExecutor from "../../../Server/Utils/AutoRemediation/CommandPlanExecutor";
9
+ import AutoRemediationSuggestion from "../../../Models/DatabaseModels/AutoRemediationSuggestion";
10
+ import Project from "../../../Models/DatabaseModels/Project";
11
+ import Runner from "../../../Models/DatabaseModels/Runner";
12
+ import AutoRemediationSuggestionStatus from "../../../Types/AutoRemediation/AutoRemediationSuggestionStatus";
13
+ import AutoRemediationSuggestionType from "../../../Types/AutoRemediation/AutoRemediationSuggestionType";
14
+ import {
15
+ KUBERNETES_AI_AGENT_DISPLAY_NAME,
16
+ KubectlCommandTier,
17
+ KubernetesAiAccessGap,
18
+ KubernetesAiAccessRunnerSummary,
19
+ KubernetesAiRemediationMode,
20
+ KubernetesClusterAiAccessStatus,
21
+ } from "../../../Types/Kubernetes/KubernetesClusterAiAccess";
22
+ import RunbookStepType from "../../../Types/Runbook/RunbookStepType";
23
+ import {
24
+ ExpressRequest,
25
+ ExpressResponse,
26
+ NextFunction,
27
+ } from "../../../Server/Utils/Express";
28
+ import Response from "../../../Server/Utils/Response";
29
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
30
+ import Dictionary from "../../../Types/Dictionary";
31
+ import BadDataException from "../../../Types/Exception/BadDataException";
32
+ import { JSONObject } from "../../../Types/JSON";
33
+ import ObjectID from "../../../Types/ObjectID";
34
+ import Permission, {
35
+ UserPermission,
36
+ UserTenantAccessPermission,
37
+ } from "../../../Types/Permission";
38
+ import UserType from "../../../Types/UserType";
39
+ import {
40
+ afterEach,
41
+ beforeAll,
42
+ beforeEach,
43
+ describe,
44
+ expect,
45
+ test,
46
+ } from "@jest/globals";
47
+
48
+ /*
49
+ * ---------------------------------------------------------------------------
50
+ * POST /auto-remediation/approve for cluster rounds and the Kubernetes AI
51
+ * agent (Common/Server/API/AutoRemediationAPI.ts).
52
+ *
53
+ * Pinned here:
54
+ *
55
+ * The project switches, by lane.
56
+ * - Enable AI and Enable auto-remediation stop every command plan.
57
+ * - "Enable AI command execution" stops RULE rounds only. A cluster round
58
+ * (the suggestion names a cluster and no rule) is approvable with it off;
59
+ * its consent is the cluster's own and is re-checked per kubectl command.
60
+ * - The lane is read off the suggestion row — kubernetesClusterId and
61
+ * autoRemediationRuleId must be selected, or every cluster round would
62
+ * look like a rule round — and NEVER inferred from the plan: a rule round
63
+ * whose plan is all kubectl still needs the opt-in.
64
+ *
65
+ * The Kubernetes AI agent as a plan's target.
66
+ * - A kubectl command composed for a cluster's AI agent names the agent's
67
+ * id where a Runner id would be. The agent is not a Runner row, so for an
68
+ * id the cluster's status resolves to the agent the Runner table is not
69
+ * re-read (it would refuse every agent plan); the agent must be online
70
+ * instead, and Bash/SSH steps aimed at it are refused.
71
+ * - Only the cluster's CURRENT status makes an id the agent: a Runner is
72
+ * never mistaken for it, and an agent that is no longer the cluster's
73
+ * target falls back to the Runner re-read, which refuses it.
74
+ * - Each cluster's status is still read once per approval.
75
+ * ---------------------------------------------------------------------------
76
+ */
77
+
78
+ type RouterFunction = (
79
+ req: ExpressRequest,
80
+ res: ExpressResponse,
81
+ next: NextFunction,
82
+ ) => void | Promise<void>;
83
+
84
+ type MockRoute = {
85
+ method: string;
86
+ uri: string;
87
+ middleware: RouterFunction;
88
+ handlerFunction: RouterFunction;
89
+ };
90
+
91
+ type MockRouter = {
92
+ get: jest.Mock;
93
+ post: jest.Mock;
94
+ put: jest.Mock;
95
+ delete: jest.Mock;
96
+ };
97
+
98
+ const mockRoutes: Array<MockRoute> = [];
99
+
100
+ type RegisterRouteFunction = (
101
+ method: string,
102
+ ) => (
103
+ uri: string,
104
+ middleware: RouterFunction,
105
+ handlerFunction: RouterFunction,
106
+ ) => void;
107
+
108
+ const registerRoute: RegisterRouteFunction = (method: string) => {
109
+ return (
110
+ uri: string,
111
+ middleware: RouterFunction,
112
+ handlerFunction: RouterFunction,
113
+ ): void => {
114
+ mockRoutes.push({
115
+ method: method.toUpperCase(),
116
+ uri,
117
+ middleware,
118
+ handlerFunction,
119
+ });
120
+ };
121
+ };
122
+
123
+ const mockRouter: MockRouter = {
124
+ get: jest.fn().mockImplementation(registerRoute("get")),
125
+ post: jest.fn().mockImplementation(registerRoute("post")),
126
+ put: jest.fn().mockImplementation(registerRoute("put")),
127
+ delete: jest.fn().mockImplementation(registerRoute("delete")),
128
+ };
129
+
130
+ jest.mock("../../../Server/Utils/Express", () => {
131
+ const actual: Record<string, unknown> = jest.requireActual(
132
+ "../../../Server/Utils/Express",
133
+ ) as Record<string, unknown>;
134
+
135
+ return {
136
+ ...actual,
137
+ __esModule: true,
138
+ default: {
139
+ ...((actual["default"] as Record<string, unknown>) || {}),
140
+ getRouter: (): MockRouter => {
141
+ return mockRouter;
142
+ },
143
+ },
144
+ };
145
+ });
146
+
147
+ jest.mock("../../../Server/Utils/Response", () => {
148
+ return {
149
+ __esModule: true,
150
+ default: {
151
+ sendJsonObjectResponse: jest.fn(),
152
+ sendErrorResponse: jest.fn(),
153
+ sendEmptySuccessResponse: jest.fn(),
154
+ sendEntityResponse: jest.fn(),
155
+ },
156
+ };
157
+ });
158
+
159
+ jest.mock("../../../Server/Utils/AutoRemediation/CommandPlanExecutor", () => {
160
+ return {
161
+ __esModule: true,
162
+ default: {
163
+ executeApprovedPlan: jest.fn(),
164
+ },
165
+ };
166
+ });
167
+
168
+ const executeApprovedPlanMock: jest.Mock =
169
+ CommandPlanExecutor.executeApprovedPlan as unknown as jest.Mock;
170
+
171
+ const sendJsonObjectResponseMock: jest.Mock =
172
+ Response.sendJsonObjectResponse as unknown as jest.Mock;
173
+
174
+ const APPROVE_ROUTE: string = "/auto-remediation/approve";
175
+
176
+ const PROJECT_ID: ObjectID = new ObjectID(
177
+ "11111111-1111-4111-8111-111111111111",
178
+ );
179
+ const SUGGESTION_ID: ObjectID = new ObjectID(
180
+ "22222222-2222-4222-8222-222222222222",
181
+ );
182
+ const USER_ID: ObjectID = new ObjectID("33333333-3333-4333-8333-333333333333");
183
+ const INCIDENT_ID: ObjectID = new ObjectID(
184
+ "44444444-4444-4444-8444-444444444444",
185
+ );
186
+ // The KubernetesAiAgent row's id — what a plan stores in runnerId for the agent.
187
+ const AGENT_ID: ObjectID = new ObjectID("55555555-5555-4555-8555-555555555555");
188
+ const RUNNER_ID: ObjectID = new ObjectID(
189
+ "66666666-6666-4666-8666-666666666666",
190
+ );
191
+ const CLUSTER_ID: ObjectID = new ObjectID(
192
+ "77777777-7777-4777-8777-777777777777",
193
+ );
194
+ const OTHER_CLUSTER_ID: ObjectID = new ObjectID(
195
+ "88888888-8888-4888-8888-888888888888",
196
+ );
197
+ const RULE_ID: ObjectID = new ObjectID("99999999-9999-4999-8999-999999999999");
198
+ const OTHER_AGENT_ID: ObjectID = new ObjectID(
199
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
200
+ );
201
+
202
+ type RouteCallResult = {
203
+ thrownToNext: unknown;
204
+ nextCallCount: number;
205
+ };
206
+
207
+ function matchRoute(uri: string): MockRoute {
208
+ const route: MockRoute | undefined = mockRoutes.find((route: MockRoute) => {
209
+ return route.method === "POST" && route.uri === uri;
210
+ });
211
+
212
+ if (!route) {
213
+ throw new Error(`Route POST ${uri} was never registered`);
214
+ }
215
+
216
+ return route;
217
+ }
218
+
219
+ async function callApprove(): Promise<RouteCallResult> {
220
+ const req: ExpressRequest = {
221
+ params: {},
222
+ query: {},
223
+ body: { suggestionId: SUGGESTION_ID.toString() },
224
+ headers: {},
225
+ } as unknown as ExpressRequest;
226
+
227
+ const res: ExpressResponse = {
228
+ send: jest.fn(),
229
+ json: jest.fn(),
230
+ status: jest.fn().mockReturnThis(),
231
+ } as unknown as ExpressResponse;
232
+
233
+ const next: jest.Mock = jest.fn();
234
+
235
+ await matchRoute(APPROVE_ROUTE).handlerFunction(
236
+ req,
237
+ res,
238
+ next as unknown as NextFunction,
239
+ );
240
+
241
+ return {
242
+ thrownToNext: next.mock.calls[0] ? next.mock.calls[0][0] : undefined,
243
+ nextCallCount: next.mock.calls.length,
244
+ };
245
+ }
246
+
247
+ function buildUserProps(): DatabaseCommonInteractionProps {
248
+ const permissionMap: Dictionary<UserTenantAccessPermission> = {};
249
+
250
+ permissionMap[PROJECT_ID.toString()] = {
251
+ _type: "UserTenantAccessPermission",
252
+ projectId: PROJECT_ID,
253
+ permissions: [Permission.ProjectMember].map((permission: Permission) => {
254
+ const userPermission: UserPermission = {
255
+ _type: "UserPermission",
256
+ permission: permission,
257
+ labelIds: [],
258
+ };
259
+
260
+ return userPermission;
261
+ }),
262
+ };
263
+
264
+ return {
265
+ tenantId: PROJECT_ID,
266
+ userId: USER_ID,
267
+ userType: UserType.User,
268
+ userTenantAccessPermission: permissionMap,
269
+ };
270
+ }
271
+
272
+ // A kubectl command as a cluster round records it for the cluster's AI agent.
273
+ function agentKubectlCommandJson(
274
+ overrides: Partial<Record<string, unknown>> = {},
275
+ ): JSONObject {
276
+ return {
277
+ sequence: 1,
278
+ stepType: RunbookStepType.Kubectl,
279
+ runnerId: AGENT_ID.toString(),
280
+ runnerNameSnapshot: KUBERNETES_AI_AGENT_DISPLAY_NAME,
281
+ kubernetesClusterId: CLUSTER_ID.toString(),
282
+ kubernetesClusterNameSnapshot: "prod-us",
283
+ kubectlTier: KubectlCommandTier.SafeWrite,
284
+ command: "kubectl rollout restart deployment/web -n web",
285
+ timeoutInMs: 60000,
286
+ rationale: "web pods are wedged",
287
+ expectedEffect: "fresh pods serve traffic",
288
+ policyVerdict: "RequiresApproval",
289
+ ...overrides,
290
+ } as JSONObject;
291
+ }
292
+
293
+ function bashCommandJson(
294
+ overrides: Partial<Record<string, unknown>> = {},
295
+ ): JSONObject {
296
+ return {
297
+ sequence: 1,
298
+ stepType: RunbookStepType.Bash,
299
+ runnerId: RUNNER_ID.toString(),
300
+ runnerNameSnapshot: "web-runner-1",
301
+ command: "systemctl restart nginx",
302
+ timeoutInMs: 60000,
303
+ rationale: "nginx workers are wedged",
304
+ expectedEffect: "restores HTTP responses",
305
+ policyVerdict: "RequiresApproval",
306
+ ...overrides,
307
+ } as JSONObject;
308
+ }
309
+
310
+ function fakeProject(
311
+ overrides: Partial<Record<string, unknown>> = {},
312
+ ): Project {
313
+ return {
314
+ _id: PROJECT_ID.toString(),
315
+ id: PROJECT_ID,
316
+ enableAi: true,
317
+ enableAutoRemediation: true,
318
+ enableAiCommandExecution: true,
319
+ ...overrides,
320
+ } as unknown as Project;
321
+ }
322
+
323
+ // The suggestion row as the database holds it.
324
+ function clusterRoundRow(
325
+ commands: Array<JSONObject>,
326
+ overrides: Partial<Record<string, unknown>> = {},
327
+ ): Record<string, unknown> {
328
+ return {
329
+ id: SUGGESTION_ID,
330
+ _id: SUGGESTION_ID.toString(),
331
+ projectId: PROJECT_ID,
332
+ incidentId: INCIDENT_ID,
333
+ status: AutoRemediationSuggestionStatus.Suggested,
334
+ suggestionType: AutoRemediationSuggestionType.CommandPlan,
335
+ commandPlan: { commands } as JSONObject,
336
+ verificationWindowMinutes: 15,
337
+ kubernetesClusterId: CLUSTER_ID,
338
+ ruleNameSnapshot: 'AI remediation for cluster "prod-us"',
339
+ ...overrides,
340
+ };
341
+ }
342
+
343
+ function ruleRoundRow(
344
+ commands: Array<JSONObject>,
345
+ overrides: Partial<Record<string, unknown>> = {},
346
+ ): Record<string, unknown> {
347
+ return clusterRoundRow(commands, {
348
+ kubernetesClusterId: undefined,
349
+ autoRemediationRuleId: RULE_ID,
350
+ ruleNameSnapshot: "Web pods wedged",
351
+ ...overrides,
352
+ });
353
+ }
354
+
355
+ function agentSummary(
356
+ overrides: Partial<KubernetesAiAccessRunnerSummary> = {},
357
+ ): KubernetesAiAccessRunnerSummary {
358
+ return {
359
+ id: AGENT_ID.toString(),
360
+ name: KUBERNETES_AI_AGENT_DISPLAY_NAME,
361
+ kind: "ai_agent",
362
+ isOnline: true,
363
+ canRunAiCommands: true,
364
+ posture: { inCluster: true, allowWrites: true },
365
+ ...overrides,
366
+ };
367
+ }
368
+
369
+ // The cluster exactly as the plan was composed for it: ready, reached through its AI agent.
370
+ function agentStatus(
371
+ overrides: Partial<KubernetesClusterAiAccessStatus> = {},
372
+ ): KubernetesClusterAiAccessStatus {
373
+ return {
374
+ clusterId: CLUSTER_ID.toString(),
375
+ clusterName: "prod-us",
376
+ clusterIdentifier: "prod-us",
377
+ runner: agentSummary(),
378
+ accessMethod: "in_cluster",
379
+ aiAgent: null,
380
+ automaticInvestigation: { incidents: false, alerts: false },
381
+ kubectlAllowlist: [],
382
+ isInvestigationEnabled: true,
383
+ isInvestigationReady: true,
384
+ remediationMode: KubernetesAiRemediationMode.RequireApproval,
385
+ isRemediationReady: true,
386
+ gaps: [],
387
+ evaluatedAt: "2026-01-01T00:00:00.000Z",
388
+ ...overrides,
389
+ };
390
+ }
391
+
392
+ describe("POST /auto-remediation/approve — cluster rounds and the Kubernetes AI agent", () => {
393
+ let suggestionFindSpy: jest.SpyInstance;
394
+ let casSpy: jest.SpyInstance;
395
+ let projectFindSpy: jest.SpyInstance;
396
+ let runnerFindSpy: jest.SpyInstance;
397
+ let statusSpy: jest.SpyInstance;
398
+ let getScopeRefusalNextStep: (
399
+ runner: KubernetesClusterAiAccessStatus["runner"],
400
+ ) => string;
401
+
402
+ beforeAll(async () => {
403
+ /*
404
+ * AutoRemediationAPI registers its routes at module scope, so the import
405
+ * is deferred until the mock router above is fully constructed.
406
+ */
407
+ const api: typeof import("../../../Server/API/AutoRemediationAPI") =
408
+ await import("../../../Server/API/AutoRemediationAPI");
409
+ getScopeRefusalNextStep = api.getScopeRefusalNextStep;
410
+ });
411
+
412
+ /*
413
+ * The row the route reads: honours the select, so a column the route
414
+ * forgets to ask for is genuinely absent — exactly what a real
415
+ * findOneById does. The access check under the user's props selects only
416
+ * _id, and gets only that.
417
+ */
418
+ function mockRow(row: Record<string, unknown>): void {
419
+ suggestionFindSpy.mockImplementation(
420
+ async (args: unknown): Promise<AutoRemediationSuggestion> => {
421
+ const select: Record<string, unknown> = (
422
+ args as { select: Record<string, unknown> }
423
+ ).select;
424
+ const picked: Record<string, unknown> = { id: row["id"] };
425
+ for (const key of Object.keys(select)) {
426
+ if (key in row) {
427
+ picked[key] = row[key];
428
+ }
429
+ }
430
+ return picked as unknown as AutoRemediationSuggestion;
431
+ },
432
+ );
433
+ }
434
+
435
+ beforeEach(() => {
436
+ jest.clearAllMocks();
437
+
438
+ executeApprovedPlanMock.mockResolvedValue(undefined);
439
+
440
+ jest
441
+ .spyOn(CommonAPI, "getDatabaseCommonInteractionProps")
442
+ .mockResolvedValue(buildUserProps());
443
+
444
+ suggestionFindSpy = jest.spyOn(
445
+ AutoRemediationSuggestionService,
446
+ "findOneById",
447
+ );
448
+ mockRow(clusterRoundRow([agentKubectlCommandJson()]));
449
+
450
+ casSpy = jest
451
+ .spyOn(AutoRemediationSuggestionService, "attemptStatusTransition")
452
+ .mockResolvedValue(1);
453
+
454
+ projectFindSpy = jest
455
+ .spyOn(ProjectService, "findOneById")
456
+ .mockResolvedValue(fakeProject());
457
+
458
+ runnerFindSpy = jest.spyOn(RunnerService, "findOneBy").mockResolvedValue({
459
+ _id: RUNNER_ID.toString(),
460
+ name: "web-runner-1",
461
+ } as unknown as Runner);
462
+
463
+ statusSpy = jest
464
+ .spyOn(KubernetesClusterAiAccessService, "getStatusForCluster")
465
+ .mockResolvedValue(agentStatus());
466
+
467
+ jest
468
+ .spyOn(IncidentFeedService, "createIncidentFeedItem")
469
+ .mockResolvedValue(undefined as never);
470
+ jest
471
+ .spyOn(AlertFeedService, "createAlertFeedItem")
472
+ .mockResolvedValue(undefined as never);
473
+ });
474
+
475
+ afterEach(() => {
476
+ jest.restoreAllMocks();
477
+ });
478
+
479
+ function expectRefusedBeforeClaim(result: RouteCallResult): BadDataException {
480
+ expect(result.nextCallCount).toBe(1);
481
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
482
+ expect(casSpy).not.toHaveBeenCalled();
483
+ expect(executeApprovedPlanMock).not.toHaveBeenCalled();
484
+ expect(sendJsonObjectResponseMock).not.toHaveBeenCalled();
485
+
486
+ return result.thrownToNext as BadDataException;
487
+ }
488
+
489
+ function expectClaimedAndExecuted(result: RouteCallResult): void {
490
+ expect(result.nextCallCount).toBe(0);
491
+ expect(casSpy).toHaveBeenCalledTimes(1);
492
+ expect(executeApprovedPlanMock).toHaveBeenCalledTimes(1);
493
+ }
494
+
495
+ describe("the project switches, by lane", () => {
496
+ test("the root read selects the two columns that tell a cluster round from a rule round", async () => {
497
+ await callApprove();
498
+
499
+ const rootSelect: Record<string, unknown> | undefined = (
500
+ suggestionFindSpy.mock.calls as Array<
501
+ Array<{
502
+ select: Record<string, unknown>;
503
+ props: { isRoot?: boolean };
504
+ }>
505
+ >
506
+ )
507
+ .map(
508
+ (
509
+ call: Array<{
510
+ select: Record<string, unknown>;
511
+ props: { isRoot?: boolean };
512
+ }>,
513
+ ) => {
514
+ return call[0]!;
515
+ },
516
+ )
517
+ .find((args: { props: { isRoot?: boolean } }) => {
518
+ return args.props?.isRoot === true;
519
+ })?.select;
520
+
521
+ expect(rootSelect).toEqual(
522
+ expect.objectContaining({
523
+ kubernetesClusterId: true,
524
+ autoRemediationRuleId: true,
525
+ }),
526
+ );
527
+ });
528
+
529
+ test.each([
530
+ ["false", false],
531
+ ["never set", undefined],
532
+ ])(
533
+ "a cluster round is approvable with AI command execution %s",
534
+ async (_label: string, value: boolean | undefined) => {
535
+ projectFindSpy.mockResolvedValue(
536
+ fakeProject({ enableAiCommandExecution: value }),
537
+ );
538
+
539
+ expectClaimedAndExecuted(await callApprove());
540
+ },
541
+ );
542
+
543
+ test("a RULE round whose plan is only kubectl is still refused with AI command execution off — the lane decides, not the step types", async () => {
544
+ mockRow(ruleRoundRow([agentKubectlCommandJson()]));
545
+ projectFindSpy.mockResolvedValue(
546
+ fakeProject({ enableAiCommandExecution: false }),
547
+ );
548
+
549
+ const error: BadDataException = expectRefusedBeforeClaim(
550
+ await callApprove(),
551
+ );
552
+
553
+ expect(error.message).toContain("AI command execution is disabled");
554
+ expect(error.message).toContain("Project Settings → AI Features");
555
+ // Refused before any target or cluster is looked at.
556
+ expect(statusSpy).not.toHaveBeenCalled();
557
+ expect(runnerFindSpy).not.toHaveBeenCalled();
558
+ });
559
+
560
+ test("a RULE round with Bash commands is refused with AI command execution never set", async () => {
561
+ mockRow(ruleRoundRow([bashCommandJson()]));
562
+ projectFindSpy.mockResolvedValue(
563
+ fakeProject({ enableAiCommandExecution: undefined }),
564
+ );
565
+
566
+ const error: BadDataException = expectRefusedBeforeClaim(
567
+ await callApprove(),
568
+ );
569
+
570
+ expect(error.message).toContain("AI command execution is disabled");
571
+ expect(runnerFindSpy).not.toHaveBeenCalled();
572
+ });
573
+
574
+ test("negative control: the same rule round is approvable once the project opts in", async () => {
575
+ mockRow(ruleRoundRow([agentKubectlCommandJson()]));
576
+
577
+ expectClaimedAndExecuted(await callApprove());
578
+ });
579
+
580
+ test("a row naming a rule AND a cluster is gated as a rule round — the exemption fails closed", async () => {
581
+ mockRow(
582
+ clusterRoundRow([agentKubectlCommandJson()], {
583
+ autoRemediationRuleId: RULE_ID,
584
+ }),
585
+ );
586
+ projectFindSpy.mockResolvedValue(
587
+ fakeProject({ enableAiCommandExecution: false }),
588
+ );
589
+
590
+ const error: BadDataException = expectRefusedBeforeClaim(
591
+ await callApprove(),
592
+ );
593
+
594
+ expect(error.message).toContain("AI command execution is disabled");
595
+ });
596
+
597
+ test("a round with no cluster and no rule (its cluster was deleted) is not exempt", async () => {
598
+ mockRow(
599
+ clusterRoundRow([agentKubectlCommandJson()], {
600
+ kubernetesClusterId: undefined,
601
+ }),
602
+ );
603
+ projectFindSpy.mockResolvedValue(
604
+ fakeProject({ enableAiCommandExecution: false }),
605
+ );
606
+
607
+ const error: BadDataException = expectRefusedBeforeClaim(
608
+ await callApprove(),
609
+ );
610
+
611
+ expect(error.message).toContain("AI command execution is disabled");
612
+ });
613
+
614
+ test.each([
615
+ ["the project row is gone", null],
616
+ ["Enable AI is off", fakeProject({ enableAi: false })],
617
+ [
618
+ "Enable auto-remediation is off",
619
+ fakeProject({ enableAutoRemediation: false }),
620
+ ],
621
+ ])(
622
+ "a cluster round is still refused when %s — the kill switches cover every lane",
623
+ async (_label: string, project: Project | null) => {
624
+ projectFindSpy.mockResolvedValue(project);
625
+
626
+ const error: BadDataException = expectRefusedBeforeClaim(
627
+ await callApprove(),
628
+ );
629
+
630
+ expect(error.message).toContain("AI or auto-remediation is disabled");
631
+ expect(error.message).toContain("Project Settings → AI Features");
632
+ expect(error.message).not.toContain("AI Credits");
633
+ expect(statusSpy).not.toHaveBeenCalled();
634
+ },
635
+ );
636
+
637
+ test("a cluster round reached through an advanced Runner is held back by the cluster's own gap, not the project check", async () => {
638
+ const gap: KubernetesAiAccessGap = {
639
+ code: "project_ai_command_execution_disabled",
640
+ title: "AI command execution is off for this project",
641
+ description:
642
+ "This cluster is reached through a Runner with a Kubernetes credential.",
643
+ nextStep:
644
+ 'Turn on "Enable AI command execution" under Project Settings → AI Features.',
645
+ blocks: "remediation",
646
+ };
647
+ mockRow(
648
+ clusterRoundRow([
649
+ agentKubectlCommandJson({
650
+ runnerId: RUNNER_ID.toString(),
651
+ runnerNameSnapshot: "ops-runner",
652
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
653
+ }),
654
+ ]),
655
+ );
656
+ projectFindSpy.mockResolvedValue(
657
+ fakeProject({ enableAiCommandExecution: false }),
658
+ );
659
+ runnerFindSpy.mockResolvedValue({
660
+ _id: RUNNER_ID.toString(),
661
+ name: "ops-runner",
662
+ } as unknown as Runner);
663
+ statusSpy.mockResolvedValue(
664
+ agentStatus({
665
+ runner: {
666
+ id: RUNNER_ID.toString(),
667
+ name: "ops-runner",
668
+ kind: "runner",
669
+ isOnline: true,
670
+ canRunAiCommands: true,
671
+ },
672
+ accessMethod: "credential",
673
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
674
+ isRemediationReady: false,
675
+ gaps: [gap],
676
+ }),
677
+ );
678
+
679
+ const error: BadDataException = expectRefusedBeforeClaim(
680
+ await callApprove(),
681
+ );
682
+
683
+ expect(error.message).toContain("no longer allows AI remediation");
684
+ expect(error.message).toContain(gap.title);
685
+ expect(error.message).toContain(
686
+ "Fix that on the cluster's AI agent page (AI → Agent)",
687
+ );
688
+ });
689
+ });
690
+
691
+ describe("the Kubernetes AI agent as a plan's target", () => {
692
+ test("a kubectl plan on the cluster's AI agent proceeds WITHOUT a Runner-table read", async () => {
693
+ const result: RouteCallResult = await callApprove();
694
+
695
+ expectClaimedAndExecuted(result);
696
+ expect(runnerFindSpy).not.toHaveBeenCalled();
697
+ });
698
+
699
+ test("the cluster status is read once, however many agent commands and checks use it", async () => {
700
+ mockRow(
701
+ clusterRoundRow([
702
+ agentKubectlCommandJson({ sequence: 1 }),
703
+ agentKubectlCommandJson({
704
+ sequence: 2,
705
+ command: "kubectl rollout status deployment/web -n web",
706
+ }),
707
+ ]),
708
+ );
709
+
710
+ expectClaimedAndExecuted(await callApprove());
711
+ expect(statusSpy).toHaveBeenCalledTimes(1);
712
+
713
+ const call: { clusterId: ObjectID; projectId: ObjectID } = statusSpy.mock
714
+ .calls[0]![0] as { clusterId: ObjectID; projectId: ObjectID };
715
+ expect(call.clusterId.toString()).toBe(CLUSTER_ID.toString());
716
+ expect(call.projectId.toString()).toBe(PROJECT_ID.toString());
717
+ });
718
+
719
+ test("an offline agent refuses the click, pointing at the AI agent page", async () => {
720
+ statusSpy.mockResolvedValue(
721
+ agentStatus({ runner: agentSummary({ isOnline: false }) }),
722
+ );
723
+
724
+ const error: BadDataException = expectRefusedBeforeClaim(
725
+ await callApprove(),
726
+ );
727
+
728
+ expect(error.message).toContain(
729
+ 'The Kubernetes AI agent of cluster "prod-us" is offline',
730
+ );
731
+ expect(error.message).toContain("AI agent page (AI → Agent)");
732
+ expect(runnerFindSpy).not.toHaveBeenCalled();
733
+ });
734
+
735
+ test("an offline agent is refused for being offline even before the cluster's own readiness gap is named", async () => {
736
+ statusSpy.mockResolvedValue(
737
+ agentStatus({
738
+ runner: agentSummary({ isOnline: false }),
739
+ isRemediationReady: false,
740
+ gaps: [
741
+ {
742
+ code: "ai_agent_offline",
743
+ title: "The Kubernetes AI agent is offline",
744
+ description: "No heartbeat.",
745
+ nextStep: "kubectl logs -n oneuptime-agent -l component=ai-agent",
746
+ blocks: "both",
747
+ },
748
+ ],
749
+ }),
750
+ );
751
+
752
+ const error: BadDataException = expectRefusedBeforeClaim(
753
+ await callApprove(),
754
+ );
755
+
756
+ expect(error.message).toContain("is offline, so this plan cannot be run");
757
+ });
758
+
759
+ test.each([RunbookStepType.Bash, RunbookStepType.SSH])(
760
+ "a %s step aimed at the AI agent is refused — it runs only Kubectl steps",
761
+ async (stepType: RunbookStepType) => {
762
+ mockRow(
763
+ clusterRoundRow([
764
+ agentKubectlCommandJson({ sequence: 1 }),
765
+ bashCommandJson({
766
+ sequence: 2,
767
+ stepType,
768
+ runnerId: AGENT_ID.toString(),
769
+ runnerNameSnapshot: KUBERNETES_AI_AGENT_DISPLAY_NAME,
770
+ // An SSH step only parses with the credential it logs in with.
771
+ ...(stepType === RunbookStepType.SSH
772
+ ? { credentialId: "cccccccc-cccc-4ccc-8ccc-cccccccccccc" }
773
+ : {}),
774
+ }),
775
+ ]),
776
+ );
777
+
778
+ const error: BadDataException = expectRefusedBeforeClaim(
779
+ await callApprove(),
780
+ );
781
+
782
+ expect(error.message).toContain(`Command 2 is a ${stepType} step`);
783
+ expect(error.message).toContain("Kubernetes AI agent");
784
+ expect(error.message).toContain("runs only Kubectl steps");
785
+ expect(runnerFindSpy).not.toHaveBeenCalled();
786
+ },
787
+ );
788
+
789
+ test("a mixed plan: the agent is recognised from the status, the ordinary Runner is still re-read", async () => {
790
+ mockRow(
791
+ ruleRoundRow([
792
+ agentKubectlCommandJson({ sequence: 1 }),
793
+ bashCommandJson({ sequence: 2 }),
794
+ ]),
795
+ );
796
+
797
+ expectClaimedAndExecuted(await callApprove());
798
+
799
+ expect(runnerFindSpy).toHaveBeenCalledTimes(1);
800
+ expect(runnerFindSpy).toHaveBeenCalledWith(
801
+ expect.objectContaining({
802
+ query: expect.objectContaining({
803
+ _id: RUNNER_ID.toString(),
804
+ projectId: PROJECT_ID,
805
+ canRunAiCommands: true,
806
+ }),
807
+ }),
808
+ );
809
+ });
810
+
811
+ test("an ordinary Runner's refusal still wins in a mixed plan", async () => {
812
+ mockRow(
813
+ ruleRoundRow([
814
+ agentKubectlCommandJson({ sequence: 1 }),
815
+ bashCommandJson({ sequence: 2 }),
816
+ ]),
817
+ );
818
+ runnerFindSpy.mockResolvedValue(null);
819
+
820
+ const error: BadDataException = expectRefusedBeforeClaim(
821
+ await callApprove(),
822
+ );
823
+
824
+ expect(error.message).toContain("no longer accepts AI commands");
825
+ });
826
+
827
+ test("a Runner target is never mistaken for the agent: kind runner keeps the Runner re-read", async () => {
828
+ mockRow(
829
+ clusterRoundRow([
830
+ agentKubectlCommandJson({
831
+ runnerId: RUNNER_ID.toString(),
832
+ runnerNameSnapshot: "kubernetes-agent/prod-us",
833
+ }),
834
+ ]),
835
+ );
836
+ statusSpy.mockResolvedValue(
837
+ agentStatus({
838
+ runner: {
839
+ id: RUNNER_ID.toString(),
840
+ name: "kubernetes-agent/prod-us",
841
+ kind: "runner",
842
+ isOnline: true,
843
+ canRunAiCommands: true,
844
+ },
845
+ }),
846
+ );
847
+ runnerFindSpy.mockResolvedValue(null);
848
+
849
+ const error: BadDataException = expectRefusedBeforeClaim(
850
+ await callApprove(),
851
+ );
852
+
853
+ expect(runnerFindSpy).toHaveBeenCalledTimes(1);
854
+ expect(error.message).toContain("no longer accepts AI commands");
855
+ });
856
+
857
+ test("a summary with no kind is a Runner, as documented — the Runner table is re-read", async () => {
858
+ mockRow(
859
+ clusterRoundRow([
860
+ agentKubectlCommandJson({ runnerId: RUNNER_ID.toString() }),
861
+ ]),
862
+ );
863
+ statusSpy.mockResolvedValue(
864
+ agentStatus({
865
+ runner: {
866
+ id: RUNNER_ID.toString(),
867
+ name: "kubernetes-agent/prod-us",
868
+ isOnline: true,
869
+ canRunAiCommands: true,
870
+ },
871
+ }),
872
+ );
873
+
874
+ expectClaimedAndExecuted(await callApprove());
875
+ expect(runnerFindSpy).toHaveBeenCalledTimes(1);
876
+ });
877
+
878
+ test("an agent that is no longer the cluster's target falls back to the Runner re-read, which refuses it", async () => {
879
+ statusSpy.mockResolvedValue(
880
+ agentStatus({
881
+ runner: {
882
+ id: RUNNER_ID.toString(),
883
+ name: "ops-runner",
884
+ kind: "runner",
885
+ isOnline: true,
886
+ canRunAiCommands: true,
887
+ },
888
+ }),
889
+ );
890
+ runnerFindSpy.mockResolvedValue(null);
891
+
892
+ const error: BadDataException = expectRefusedBeforeClaim(
893
+ await callApprove(),
894
+ );
895
+
896
+ expect(runnerFindSpy).toHaveBeenCalledWith(
897
+ expect.objectContaining({
898
+ query: expect.objectContaining({ _id: AGENT_ID.toString() }),
899
+ }),
900
+ );
901
+ expect(error.message).toContain("no longer accepts AI commands");
902
+ });
903
+
904
+ test("another cluster's agent id is not this cluster's agent: the per-command check refuses the mismatch", async () => {
905
+ mockRow(
906
+ clusterRoundRow([
907
+ agentKubectlCommandJson({ sequence: 1 }),
908
+ agentKubectlCommandJson({
909
+ sequence: 2,
910
+ kubernetesClusterId: OTHER_CLUSTER_ID.toString(),
911
+ kubernetesClusterNameSnapshot: "staging-eu",
912
+ }),
913
+ ]),
914
+ );
915
+ statusSpy.mockImplementation(
916
+ async (data: {
917
+ clusterId: ObjectID;
918
+ }): Promise<KubernetesClusterAiAccessStatus> => {
919
+ return data.clusterId.toString() === CLUSTER_ID.toString()
920
+ ? agentStatus()
921
+ : agentStatus({
922
+ clusterId: OTHER_CLUSTER_ID.toString(),
923
+ clusterName: "staging-eu",
924
+ runner: agentSummary({ id: OTHER_AGENT_ID.toString() }),
925
+ });
926
+ },
927
+ );
928
+
929
+ const error: BadDataException = expectRefusedBeforeClaim(
930
+ await callApprove(),
931
+ );
932
+
933
+ expect(error.message).toContain('"staging-eu"');
934
+ expect(error.message).toContain("command 2");
935
+ expect(error.message).toContain("no longer reached through");
936
+ expect(runnerFindSpy).not.toHaveBeenCalled();
937
+ });
938
+
939
+ test("an agent plan on a cluster whose remediation was switched off is refused with the gap, after the agent checks pass", async () => {
940
+ const gap: KubernetesAiAccessGap = {
941
+ code: "remediation_disabled",
942
+ title: "Fixes are off for this cluster",
943
+ description: "An operator switched fixes off.",
944
+ nextStep: "Choose Ask for approval on the cluster's AI agent page.",
945
+ blocks: "remediation",
946
+ };
947
+ statusSpy.mockResolvedValue(
948
+ agentStatus({
949
+ remediationMode: KubernetesAiRemediationMode.Disabled,
950
+ isRemediationReady: false,
951
+ gaps: [gap],
952
+ }),
953
+ );
954
+
955
+ const error: BadDataException = expectRefusedBeforeClaim(
956
+ await callApprove(),
957
+ );
958
+
959
+ expect(error.message).toContain("no longer allows AI remediation");
960
+ expect(error.message).toContain(gap.title);
961
+ expect(error.message).toContain("AI agent page (AI → Agent)");
962
+ expect(error.message).not.toContain("cluster's AI page");
963
+ });
964
+
965
+ test("a command outside the agent's write scope names the AI agent's write access, not a Runner", async () => {
966
+ statusSpy.mockResolvedValue(
967
+ agentStatus({
968
+ runner: agentSummary({
969
+ posture: {
970
+ inCluster: true,
971
+ allowWrites: true,
972
+ writeNamespaces: ["api"],
973
+ },
974
+ }),
975
+ }),
976
+ );
977
+
978
+ const error: BadDataException = expectRefusedBeforeClaim(
979
+ await callApprove(),
980
+ );
981
+
982
+ expect(error.message).toContain(
983
+ 'Command 1 cannot run on cluster "prod-us"',
984
+ );
985
+ expect(error.message).toContain(
986
+ "change the AI agent's write access (aiAgent.remediation.*) on the Kubernetes agent chart and approve again.",
987
+ );
988
+ expect(error.message).not.toContain("Runner");
989
+ });
990
+ });
991
+
992
+ /*
993
+ * Where a write-scope refusal sends the approver, three ways by who runs
994
+ * kubectl for the cluster (§5.3) — the same split as the refusal's own
995
+ * wording (RemediationCommandToolkit.getRunnerScopeSettings), so one
996
+ * message never names two different places. The agent case is above.
997
+ */
998
+ describe("a write-scope refusal names where that target's scope is set", () => {
999
+ // A cluster reached through a Runner row, as the plan was composed for it.
1000
+ function runnerTargetStatus(
1001
+ runner: Partial<KubernetesAiAccessRunnerSummary>,
1002
+ overrides: Partial<KubernetesClusterAiAccessStatus> = {},
1003
+ ): KubernetesClusterAiAccessStatus {
1004
+ return agentStatus({
1005
+ runner: {
1006
+ id: RUNNER_ID.toString(),
1007
+ name: "kubernetes-agent/prod-us",
1008
+ kind: "runner",
1009
+ isOnline: true,
1010
+ canRunAiCommands: true,
1011
+ ...runner,
1012
+ },
1013
+ ...overrides,
1014
+ });
1015
+ }
1016
+
1017
+ beforeEach(() => {
1018
+ mockRow(
1019
+ clusterRoundRow([
1020
+ agentKubectlCommandJson({
1021
+ runnerId: RUNNER_ID.toString(),
1022
+ runnerNameSnapshot: "kubernetes-agent/prod-us",
1023
+ }),
1024
+ ]),
1025
+ );
1026
+ });
1027
+
1028
+ test("the chart's previous in-cluster Runner points at upgrading to the AI agent, not at approving this plan again", async () => {
1029
+ statusSpy.mockResolvedValue(
1030
+ runnerTargetStatus({
1031
+ posture: {
1032
+ inCluster: true,
1033
+ allowWrites: true,
1034
+ writeNamespaces: ["api"],
1035
+ },
1036
+ }),
1037
+ );
1038
+
1039
+ const error: BadDataException = expectRefusedBeforeClaim(
1040
+ await callApprove(),
1041
+ );
1042
+
1043
+ expect(error.message).toContain(
1044
+ 'Command 1 cannot run on cluster "prod-us"',
1045
+ );
1046
+ expect(error.message).toContain(
1047
+ "Nothing ran. Dismiss the suggestion and let a new plan be composed. To allow changes like this, upgrade the Kubernetes agent chart to the AI agent and set aiAgent.remediation.*.",
1048
+ );
1049
+ /*
1050
+ * Upgrading replaces this Runner with the AI agent, so this plan —
1051
+ * composed for the Runner — could not be approved afterwards.
1052
+ */
1053
+ expect(error.message).not.toContain("approve again");
1054
+ expect(error.message).not.toContain("on the Runner's host and approve");
1055
+ });
1056
+
1057
+ test("the previous in-cluster Runner is recognised by its posture when it is not named like one", async () => {
1058
+ runnerFindSpy.mockResolvedValue({
1059
+ _id: RUNNER_ID.toString(),
1060
+ name: "renamed-agent",
1061
+ } as unknown as Runner);
1062
+ statusSpy.mockResolvedValue(
1063
+ runnerTargetStatus({
1064
+ name: "renamed-agent",
1065
+ posture: {
1066
+ inCluster: true,
1067
+ clusterIdentifier: "prod-us",
1068
+ allowWrites: true,
1069
+ writeNamespaces: ["api"],
1070
+ },
1071
+ }),
1072
+ );
1073
+
1074
+ const error: BadDataException = expectRefusedBeforeClaim(
1075
+ await callApprove(),
1076
+ );
1077
+
1078
+ expect(error.message).toContain(
1079
+ "upgrade the Kubernetes agent chart to the AI agent and set aiAgent.remediation.*",
1080
+ );
1081
+ });
1082
+
1083
+ test("a credential Runner points at its own host, never at the Kubernetes agent chart", async () => {
1084
+ mockRow(
1085
+ clusterRoundRow([
1086
+ agentKubectlCommandJson({
1087
+ runnerId: RUNNER_ID.toString(),
1088
+ runnerNameSnapshot: "ops-runner",
1089
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1090
+ }),
1091
+ ]),
1092
+ );
1093
+ runnerFindSpy.mockResolvedValue({
1094
+ _id: RUNNER_ID.toString(),
1095
+ name: "ops-runner",
1096
+ } as unknown as Runner);
1097
+ statusSpy.mockResolvedValue(
1098
+ runnerTargetStatus(
1099
+ {
1100
+ name: "ops-runner",
1101
+ posture: {
1102
+ inCluster: false,
1103
+ allowWrites: true,
1104
+ writeNamespaces: ["api"],
1105
+ },
1106
+ },
1107
+ {
1108
+ accessMethod: "credential",
1109
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1110
+ },
1111
+ ),
1112
+ );
1113
+
1114
+ const error: BadDataException = expectRefusedBeforeClaim(
1115
+ await callApprove(),
1116
+ );
1117
+
1118
+ expect(error.message).toContain(
1119
+ 'Command 1 cannot run on cluster "prod-us"',
1120
+ );
1121
+ // The refusal and the next step name the same place.
1122
+ expect(error.message).toContain(
1123
+ "ONEUPTIME_KUBECTL_WRITE_NAMESPACES on the Runner's host",
1124
+ );
1125
+ expect(error.message).toContain(
1126
+ "Nothing ran. Dismiss the suggestion and let a new plan be composed, or change the Runner's write scope on the Runner's host and approve again.",
1127
+ );
1128
+ expect(error.message).not.toContain("Kubernetes agent chart");
1129
+ expect(error.message).not.toContain("aiAgent.remediation");
1130
+ });
1131
+
1132
+ test("a Runner that merely lives in a pod, with no cluster identity, is treated as a credential Runner", async () => {
1133
+ mockRow(
1134
+ clusterRoundRow([
1135
+ agentKubectlCommandJson({
1136
+ runnerId: RUNNER_ID.toString(),
1137
+ runnerNameSnapshot: "ops-runner",
1138
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1139
+ }),
1140
+ ]),
1141
+ );
1142
+ runnerFindSpy.mockResolvedValue({
1143
+ _id: RUNNER_ID.toString(),
1144
+ name: "ops-runner",
1145
+ } as unknown as Runner);
1146
+ statusSpy.mockResolvedValue(
1147
+ runnerTargetStatus(
1148
+ {
1149
+ name: "ops-runner",
1150
+ posture: {
1151
+ inCluster: true,
1152
+ allowWrites: true,
1153
+ writeNamespaces: ["api"],
1154
+ },
1155
+ },
1156
+ {
1157
+ accessMethod: "credential",
1158
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1159
+ },
1160
+ ),
1161
+ );
1162
+
1163
+ const error: BadDataException = expectRefusedBeforeClaim(
1164
+ await callApprove(),
1165
+ );
1166
+
1167
+ expect(error.message).toContain(
1168
+ "change the Runner's write scope on the Runner's host and approve again.",
1169
+ );
1170
+ expect(error.message).not.toContain("upgrade the Kubernetes agent chart");
1171
+ });
1172
+
1173
+ test("a rollback outside the scope gets the same next step as the command", async () => {
1174
+ mockRow(
1175
+ clusterRoundRow([
1176
+ agentKubectlCommandJson({
1177
+ runnerId: RUNNER_ID.toString(),
1178
+ runnerNameSnapshot: "ops-runner",
1179
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1180
+ command: "kubectl rollout restart deployment/web -n api",
1181
+ rollbackCommand: "kubectl rollout undo deployment/web -n web",
1182
+ }),
1183
+ ]),
1184
+ );
1185
+ runnerFindSpy.mockResolvedValue({
1186
+ _id: RUNNER_ID.toString(),
1187
+ name: "ops-runner",
1188
+ } as unknown as Runner);
1189
+ statusSpy.mockResolvedValue(
1190
+ runnerTargetStatus(
1191
+ {
1192
+ name: "ops-runner",
1193
+ posture: {
1194
+ inCluster: false,
1195
+ allowWrites: true,
1196
+ writeNamespaces: ["api"],
1197
+ },
1198
+ },
1199
+ {
1200
+ accessMethod: "credential",
1201
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1202
+ },
1203
+ ),
1204
+ );
1205
+
1206
+ const error: BadDataException = expectRefusedBeforeClaim(
1207
+ await callApprove(),
1208
+ );
1209
+
1210
+ expect(error.message).toContain("Command 1's rollback cannot run");
1211
+ expect(error.message).toContain(
1212
+ "change the Runner's write scope on the Runner's host and approve again.",
1213
+ );
1214
+ });
1215
+
1216
+ test("negative control: a command inside a credential Runner's scope is approved", async () => {
1217
+ mockRow(
1218
+ clusterRoundRow([
1219
+ agentKubectlCommandJson({
1220
+ runnerId: RUNNER_ID.toString(),
1221
+ runnerNameSnapshot: "ops-runner",
1222
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1223
+ command: "kubectl rollout restart deployment/web -n api",
1224
+ }),
1225
+ ]),
1226
+ );
1227
+ runnerFindSpy.mockResolvedValue({
1228
+ _id: RUNNER_ID.toString(),
1229
+ name: "ops-runner",
1230
+ } as unknown as Runner);
1231
+ statusSpy.mockResolvedValue(
1232
+ runnerTargetStatus(
1233
+ {
1234
+ name: "ops-runner",
1235
+ posture: {
1236
+ inCluster: false,
1237
+ allowWrites: true,
1238
+ writeNamespaces: ["api"],
1239
+ },
1240
+ },
1241
+ {
1242
+ accessMethod: "credential",
1243
+ credentialId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
1244
+ },
1245
+ ),
1246
+ );
1247
+
1248
+ expectClaimedAndExecuted(await callApprove());
1249
+ });
1250
+ });
1251
+
1252
+ describe("getScopeRefusalNextStep", () => {
1253
+ function summary(
1254
+ overrides: Partial<KubernetesAiAccessRunnerSummary>,
1255
+ ): KubernetesAiAccessRunnerSummary {
1256
+ return {
1257
+ id: RUNNER_ID.toString(),
1258
+ name: "ops-runner",
1259
+ kind: "runner",
1260
+ isOnline: true,
1261
+ canRunAiCommands: true,
1262
+ ...overrides,
1263
+ };
1264
+ }
1265
+
1266
+ test("the AI agent: its chart values, and the plan can be approved again", () => {
1267
+ expect(getScopeRefusalNextStep(agentSummary())).toBe(
1268
+ "Dismiss the suggestion and let a new plan be composed, or change the AI agent's write access (aiAgent.remediation.*) on the Kubernetes agent chart and approve again.",
1269
+ );
1270
+ });
1271
+
1272
+ test("an AI agent is recognised by its kind even under a Runner-like name", () => {
1273
+ expect(
1274
+ getScopeRefusalNextStep(
1275
+ agentSummary({ name: "kubernetes-agent/prod-us" }),
1276
+ ),
1277
+ ).toContain("the AI agent's write access (aiAgent.remediation.*)");
1278
+ });
1279
+
1280
+ test("the previous in-cluster Runner, by its server-owned name", () => {
1281
+ expect(
1282
+ getScopeRefusalNextStep(summary({ name: "kubernetes-agent/prod-us" })),
1283
+ ).toBe(
1284
+ "Dismiss the suggestion and let a new plan be composed. To allow changes like this, upgrade the Kubernetes agent chart to the AI agent and set aiAgent.remediation.*.",
1285
+ );
1286
+ });
1287
+
1288
+ test("the previous in-cluster Runner's name is matched case-insensitively", () => {
1289
+ expect(
1290
+ getScopeRefusalNextStep(summary({ name: "Kubernetes-Agent/prod-us" })),
1291
+ ).toContain("upgrade the Kubernetes agent chart to the AI agent");
1292
+ });
1293
+
1294
+ test("the previous in-cluster Runner, by its posture alone", () => {
1295
+ expect(
1296
+ getScopeRefusalNextStep(
1297
+ summary({
1298
+ name: "renamed-agent",
1299
+ posture: { inCluster: true, clusterIdentifier: "prod-us" },
1300
+ }),
1301
+ ),
1302
+ ).toContain("upgrade the Kubernetes agent chart to the AI agent");
1303
+ });
1304
+
1305
+ test("a summary with no kind is a Runner, as the field is documented", () => {
1306
+ expect(
1307
+ getScopeRefusalNextStep(
1308
+ summary({ kind: undefined, name: "ops-runner" }),
1309
+ ),
1310
+ ).toBe(
1311
+ "Dismiss the suggestion and let a new plan be composed, or change the Runner's write scope on the Runner's host and approve again.",
1312
+ );
1313
+ });
1314
+
1315
+ test("a credential Runner in a pod but with no cluster identity keeps the host wording", () => {
1316
+ expect(
1317
+ getScopeRefusalNextStep(
1318
+ summary({ posture: { inCluster: true, clusterIdentifier: " " } }),
1319
+ ),
1320
+ ).toContain("the Runner's write scope on the Runner's host");
1321
+ });
1322
+
1323
+ test("no target at all falls back to the Runner wording instead of throwing", () => {
1324
+ expect(getScopeRefusalNextStep(null)).toContain(
1325
+ "the Runner's write scope on the Runner's host",
1326
+ );
1327
+ });
1328
+ });
1329
+ });