@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,1168 @@
1
+ import KubernetesClusterAiAccessService, {
2
+ KubernetesAiAccessTarget,
3
+ KubernetesClusterAiAccessProjectGates,
4
+ LoadedKubernetesAiAccessTarget,
5
+ REMEDIATION_WRITE_ACCESS_NEXT_STEP,
6
+ getKubernetesAiAccessTargetId,
7
+ resolveKubernetesAiAccessTarget,
8
+ } from "../../../Server/Services/KubernetesClusterAiAccessService";
9
+ import KubernetesAiAgentService from "../../../Server/Services/KubernetesAiAgentService";
10
+ import KubernetesClusterFeedService from "../../../Server/Services/KubernetesClusterFeedService";
11
+ import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
12
+ import RunbookCredentialService from "../../../Server/Services/RunbookCredentialService";
13
+ import RunnerService from "../../../Server/Services/RunnerService";
14
+ import logger from "../../../Server/Utils/Logger";
15
+ import KubernetesAiAgent from "../../../Models/DatabaseModels/KubernetesAiAgent";
16
+ import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
17
+ import RunbookCredential from "../../../Models/DatabaseModels/RunbookCredential";
18
+ import Runner, {
19
+ RunnerConnectionStatus,
20
+ } from "../../../Models/DatabaseModels/Runner";
21
+ import OneUptimeDate from "../../../Types/Date";
22
+ import ObjectID from "../../../Types/ObjectID";
23
+ import PositiveNumber from "../../../Types/PositiveNumber";
24
+ import {
25
+ KUBERNETES_AI_AGENT_DISPLAY_NAME,
26
+ KubernetesAiAccessGap,
27
+ KubernetesAiAccessGapCode,
28
+ KubernetesAiRemediationMode,
29
+ KubernetesClusterAiAccessStatus,
30
+ } from "../../../Types/Kubernetes/KubernetesClusterAiAccess";
31
+ import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
32
+
33
+ /*
34
+ * Contract under test — ONE access target per cluster, resolved from
35
+ * liveness (resolveKubernetesAiAccessTarget), and the status built from it:
36
+ *
37
+ * 1. a bound Runner that is not a kubernetes-agent row: the advanced Runner
38
+ * an operator chose (it wins over everything the chart installs);
39
+ * 2. the cluster's Kubernetes AI agent, when online;
40
+ * 3. the bound previous in-cluster Runner of THIS cluster, when online —
41
+ * the rollback case: a helm rollback stops the agent and the old Runner
42
+ * serves again with no dashboard step;
43
+ * 4. the agent, offline (ai_agent_offline);
44
+ * 5. the bound previous in-cluster Runner, offline;
45
+ * 6. nothing (ai_agent_not_connected).
46
+ *
47
+ * The status always carries aiAgent (the agent row whether or not it is the
48
+ * target) and automaticInvestigation, and names the target's kind so the
49
+ * agent and the previous in-cluster Runner — both accessMethod
50
+ * "in_cluster" — are told apart.
51
+ */
52
+
53
+ const PROJECT_ID: ObjectID = new ObjectID(
54
+ "22222222-2222-4222-8222-222222222222",
55
+ );
56
+ const CLUSTER_ID: ObjectID = new ObjectID(
57
+ "33333333-3333-4333-8333-333333333333",
58
+ );
59
+ const RUNNER_ID: ObjectID = new ObjectID(
60
+ "44444444-4444-4444-8444-444444444444",
61
+ );
62
+ const CREDENTIAL_ID: ObjectID = new ObjectID(
63
+ "55555555-5555-4555-8555-555555555555",
64
+ );
65
+ const AGENT_ID: ObjectID = new ObjectID("66666666-6666-4666-8666-666666666666");
66
+
67
+ const READY_GATES: KubernetesClusterAiAccessProjectGates = {
68
+ isAiEnabled: true,
69
+ isAutoRemediationEnabled: true,
70
+ isAiCommandExecutionEnabled: true,
71
+ hasLlmProvider: true,
72
+ aiBalanceBlocker: null,
73
+ automaticInvestigation: { incidents: true, alerts: false },
74
+ };
75
+
76
+ function cluster(overrides: Record<string, unknown> = {}): KubernetesCluster {
77
+ return {
78
+ id: CLUSTER_ID,
79
+ _id: CLUSTER_ID.toString(),
80
+ projectId: PROJECT_ID,
81
+ name: "prod-us",
82
+ clusterIdentifier: "prod-us",
83
+ aiAccessRunnerId: undefined,
84
+ aiAccessCredentialId: undefined,
85
+ isAiInvestigationEnabled: true,
86
+ aiRemediationMode: KubernetesAiRemediationMode.RequireApproval,
87
+ ...overrides,
88
+ } as unknown as KubernetesCluster;
89
+ }
90
+
91
+ function agent(overrides: Record<string, unknown> = {}): KubernetesAiAgent {
92
+ return {
93
+ id: AGENT_ID,
94
+ _id: AGENT_ID.toString(),
95
+ projectId: PROJECT_ID,
96
+ kubernetesClusterId: CLUSTER_ID,
97
+ connectionStatus: "connected",
98
+ lastAliveAt: OneUptimeDate.getCurrentDate(),
99
+ lastRegisteredAt: OneUptimeDate.getSomeMinutesAgo(90),
100
+ agentVersion: "14.1.0",
101
+ posture: {
102
+ inCluster: true,
103
+ allowWrites: true,
104
+ clusterIdentifier: "prod-us",
105
+ podNamespace: "oneuptime-agent",
106
+ writeNamespaces: ["web"],
107
+ allowNodeOperations: false,
108
+ kubectlVersion: "v1.36.4",
109
+ },
110
+ ...overrides,
111
+ } as unknown as KubernetesAiAgent;
112
+ }
113
+
114
+ // This cluster's previous in-cluster Runner (the chart's kubernetes-agent row).
115
+ function legacyRunner(overrides: Record<string, unknown> = {}): Runner {
116
+ return {
117
+ id: RUNNER_ID,
118
+ _id: RUNNER_ID.toString(),
119
+ name: "kubernetes-agent/prod-us",
120
+ lastAlive: OneUptimeDate.getCurrentDate(),
121
+ connectionStatus: RunnerConnectionStatus.Connected,
122
+ canRunAiCommands: true,
123
+ canRunRunbooks: false,
124
+ canRunCodeFixTasks: false,
125
+ hostInfo: {
126
+ kubernetes: {
127
+ inCluster: true,
128
+ allowWrites: true,
129
+ clusterIdentifier: "prod-us",
130
+ },
131
+ },
132
+ ...overrides,
133
+ } as unknown as Runner;
134
+ }
135
+
136
+ // A Runner an operator created in the dashboard (reaches with a credential).
137
+ function advancedRunner(overrides: Record<string, unknown> = {}): Runner {
138
+ return legacyRunner({
139
+ name: "platform-ops-runner",
140
+ hostInfo: {},
141
+ ...overrides,
142
+ });
143
+ }
144
+
145
+ function gapCodes(
146
+ status: KubernetesClusterAiAccessStatus,
147
+ ): Array<KubernetesAiAccessGapCode> {
148
+ return status.gaps.map((gap: KubernetesAiAccessGap) => {
149
+ return gap.code;
150
+ });
151
+ }
152
+
153
+ function resolve(data: {
154
+ bound?: Runner | null;
155
+ boundOnline?: boolean;
156
+ aiAgent?: KubernetesAiAgent | null;
157
+ agentOnline?: boolean;
158
+ aiAccessRunnerId?: ObjectID | null;
159
+ clusterIdentifier?: string;
160
+ }): KubernetesAiAccessTarget {
161
+ const bound: Runner | null = data.bound === undefined ? null : data.bound;
162
+
163
+ return resolveKubernetesAiAccessTarget({
164
+ clusterIdentifier:
165
+ data.clusterIdentifier === undefined ? "prod-us" : data.clusterIdentifier,
166
+ aiAccessRunnerId:
167
+ data.aiAccessRunnerId === undefined
168
+ ? bound
169
+ ? RUNNER_ID
170
+ : null
171
+ : data.aiAccessRunnerId,
172
+ boundRunner: bound,
173
+ isBoundRunnerOnline: data.boundOnline === true,
174
+ agent: data.aiAgent === undefined ? null : data.aiAgent,
175
+ isAgentOnline: data.agentOnline === true,
176
+ });
177
+ }
178
+
179
+ describe("resolveKubernetesAiAccessTarget — the six rules", () => {
180
+ it("rule 1: an advanced Runner an operator bound wins, even over an online agent", () => {
181
+ const target: KubernetesAiAccessTarget = resolve({
182
+ bound: advancedRunner(),
183
+ boundOnline: true,
184
+ aiAgent: agent(),
185
+ agentOnline: true,
186
+ });
187
+
188
+ expect(target.type).toBe("advanced_runner");
189
+ expect(getKubernetesAiAccessTargetId(target)).toBe(RUNNER_ID.toString());
190
+ });
191
+
192
+ it("rule 1: an offline advanced Runner is still the target (an operator's choice is not second-guessed by liveness)", () => {
193
+ const target: KubernetesAiAccessTarget = resolve({
194
+ bound: advancedRunner(),
195
+ boundOnline: false,
196
+ aiAgent: agent(),
197
+ agentOnline: true,
198
+ });
199
+
200
+ expect(target).toMatchObject({ type: "advanced_runner", isOnline: false });
201
+ });
202
+
203
+ it("rule 2: the online agent wins over the online previous in-cluster Runner (the upgrade window)", () => {
204
+ const target: KubernetesAiAccessTarget = resolve({
205
+ bound: legacyRunner(),
206
+ boundOnline: true,
207
+ aiAgent: agent(),
208
+ agentOnline: true,
209
+ });
210
+
211
+ expect(target).toMatchObject({ type: "ai_agent", isOnline: true });
212
+ expect(getKubernetesAiAccessTargetId(target)).toBe(AGENT_ID.toString());
213
+ });
214
+
215
+ it("rule 2: an online agent with nothing bound", () => {
216
+ expect(resolve({ aiAgent: agent(), agentOnline: true })).toMatchObject({
217
+ type: "ai_agent",
218
+ isOnline: true,
219
+ });
220
+ });
221
+
222
+ it("rule 3 (rollback): the agent offline and the previous in-cluster Runner online → the previous Runner", () => {
223
+ const target: KubernetesAiAccessTarget = resolve({
224
+ bound: legacyRunner(),
225
+ boundOnline: true,
226
+ aiAgent: agent(),
227
+ agentOnline: false,
228
+ });
229
+
230
+ expect(target).toMatchObject({ type: "legacy_runner", isOnline: true });
231
+ expect(getKubernetesAiAccessTargetId(target)).toBe(RUNNER_ID.toString());
232
+ });
233
+
234
+ it("rule 3: the previous in-cluster Runner online and no agent at all", () => {
235
+ expect(resolve({ bound: legacyRunner(), boundOnline: true })).toMatchObject(
236
+ { type: "legacy_runner", isOnline: true },
237
+ );
238
+ });
239
+
240
+ it("rule 4: the agent offline beats the previous Runner offline", () => {
241
+ expect(
242
+ resolve({
243
+ bound: legacyRunner(),
244
+ boundOnline: false,
245
+ aiAgent: agent(),
246
+ agentOnline: false,
247
+ }),
248
+ ).toMatchObject({ type: "ai_agent", isOnline: false });
249
+ });
250
+
251
+ it("rule 4: the agent offline with nothing bound", () => {
252
+ expect(resolve({ aiAgent: agent(), agentOnline: false })).toMatchObject({
253
+ type: "ai_agent",
254
+ isOnline: false,
255
+ });
256
+ });
257
+
258
+ it("rule 5: the previous in-cluster Runner offline and no agent", () => {
259
+ expect(
260
+ resolve({ bound: legacyRunner(), boundOnline: false }),
261
+ ).toMatchObject({ type: "legacy_runner", isOnline: false });
262
+ });
263
+
264
+ it("rule 6: nothing", () => {
265
+ const target: KubernetesAiAccessTarget = resolve({});
266
+
267
+ expect(target).toEqual({ type: "none" });
268
+ expect(getKubernetesAiAccessTargetId(target)).toBeNull();
269
+ });
270
+
271
+ it("a kubernetes-agent Runner of ANOTHER cluster is never a target", () => {
272
+ const otherClusters: Runner = legacyRunner({
273
+ name: "kubernetes-agent/prod-eu",
274
+ hostInfo: {
275
+ kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
276
+ },
277
+ });
278
+
279
+ expect(resolve({ bound: otherClusters, boundOnline: true })).toEqual({
280
+ type: "none",
281
+ });
282
+ expect(
283
+ resolve({
284
+ bound: otherClusters,
285
+ boundOnline: true,
286
+ aiAgent: agent(),
287
+ agentOnline: false,
288
+ }),
289
+ ).toMatchObject({ type: "ai_agent", isOnline: false });
290
+ });
291
+
292
+ it("a Runner that is this cluster's agent by posture only is the previous in-cluster Runner", () => {
293
+ expect(
294
+ resolve({
295
+ bound: legacyRunner({ name: "renamed-in-cluster-runner" }),
296
+ boundOnline: true,
297
+ }),
298
+ ).toMatchObject({ type: "legacy_runner" });
299
+ });
300
+
301
+ it("a bound id whose Runner row is gone reads as unbound", () => {
302
+ expect(resolve({ bound: null, aiAccessRunnerId: RUNNER_ID })).toEqual({
303
+ type: "none",
304
+ });
305
+ expect(
306
+ resolve({
307
+ bound: null,
308
+ aiAccessRunnerId: RUNNER_ID,
309
+ aiAgent: agent(),
310
+ agentOnline: true,
311
+ }),
312
+ ).toMatchObject({ type: "ai_agent" });
313
+ });
314
+
315
+ it("a Runner row handed in without a binding on the cluster is ignored", () => {
316
+ expect(
317
+ resolve({
318
+ bound: advancedRunner(),
319
+ boundOnline: true,
320
+ aiAccessRunnerId: null,
321
+ }),
322
+ ).toEqual({ type: "none" });
323
+ });
324
+
325
+ it("a cluster without an identifier has no previous in-cluster Runner", () => {
326
+ expect(
327
+ resolve({
328
+ bound: legacyRunner(),
329
+ boundOnline: true,
330
+ clusterIdentifier: "",
331
+ }),
332
+ ).toEqual({ type: "none" });
333
+ });
334
+
335
+ it("getKubernetesAiAccessTargetId names the agent row or the Runner row", () => {
336
+ expect(
337
+ getKubernetesAiAccessTargetId({
338
+ type: "ai_agent",
339
+ agent: agent(),
340
+ isOnline: true,
341
+ }),
342
+ ).toBe(AGENT_ID.toString());
343
+ expect(
344
+ getKubernetesAiAccessTargetId({
345
+ type: "advanced_runner",
346
+ runner: advancedRunner(),
347
+ isOnline: true,
348
+ }),
349
+ ).toBe(RUNNER_ID.toString());
350
+ expect(
351
+ getKubernetesAiAccessTargetId({
352
+ type: "legacy_runner",
353
+ runner: legacyRunner(),
354
+ isOnline: false,
355
+ }),
356
+ ).toBe(RUNNER_ID.toString());
357
+ expect(getKubernetesAiAccessTargetId({ type: "none" })).toBeNull();
358
+ });
359
+ });
360
+
361
+ describe("KubernetesClusterAiAccessService liveness", () => {
362
+ it("isRunnerOnline: heartbeated recently and not signed off", () => {
363
+ expect(
364
+ KubernetesClusterAiAccessService.isRunnerOnline(legacyRunner()),
365
+ ).toBe(true);
366
+ expect(
367
+ KubernetesClusterAiAccessService.isRunnerOnline(
368
+ legacyRunner({ connectionStatus: RunnerConnectionStatus.Disconnected }),
369
+ ),
370
+ ).toBe(false);
371
+ expect(
372
+ KubernetesClusterAiAccessService.isRunnerOnline(
373
+ legacyRunner({ lastAlive: OneUptimeDate.getSomeMinutesAgo(30) }),
374
+ ),
375
+ ).toBe(false);
376
+ expect(
377
+ KubernetesClusterAiAccessService.isRunnerOnline(
378
+ legacyRunner({ lastAlive: undefined }),
379
+ ),
380
+ ).toBe(false);
381
+ });
382
+
383
+ it("resolveAccessTarget reads each row's liveness the module's way (rollback regression)", () => {
384
+ // The agent signed off (helm rollback); the previous Runner is back.
385
+ const rolledBack: KubernetesAiAccessTarget =
386
+ KubernetesClusterAiAccessService.resolveAccessTarget(
387
+ { aiAccessRunnerId: RUNNER_ID, clusterIdentifier: "prod-us" },
388
+ {
389
+ boundRunner: legacyRunner(),
390
+ agentRow: agent({ connectionStatus: "disconnected" }),
391
+ },
392
+ );
393
+
394
+ expect(rolledBack).toMatchObject({
395
+ type: "legacy_runner",
396
+ isOnline: true,
397
+ });
398
+
399
+ // The agent's heartbeat aged out: the same.
400
+ expect(
401
+ KubernetesClusterAiAccessService.resolveAccessTarget(
402
+ { aiAccessRunnerId: RUNNER_ID, clusterIdentifier: "prod-us" },
403
+ {
404
+ boundRunner: legacyRunner(),
405
+ agentRow: agent({ lastAliveAt: OneUptimeDate.getSomeMinutesAgo(30) }),
406
+ },
407
+ ),
408
+ ).toMatchObject({ type: "legacy_runner" });
409
+
410
+ // The agent is back: it takes over again.
411
+ expect(
412
+ KubernetesClusterAiAccessService.resolveAccessTarget(
413
+ { aiAccessRunnerId: RUNNER_ID, clusterIdentifier: "prod-us" },
414
+ { boundRunner: legacyRunner(), agentRow: agent() },
415
+ ),
416
+ ).toMatchObject({ type: "ai_agent", isOnline: true });
417
+ });
418
+ });
419
+
420
+ describe("KubernetesClusterAiAccessService.loadAccessTarget", () => {
421
+ afterEach(() => {
422
+ jest.restoreAllMocks();
423
+ });
424
+
425
+ it("reads the agent row by (project, cluster) and the bound Runner in this project", async () => {
426
+ const agentLookup: jest.SpyInstance = jest
427
+ .spyOn(KubernetesAiAgentService, "findForCluster")
428
+ .mockResolvedValue(agent());
429
+ const runnerLookup: jest.SpyInstance = jest
430
+ .spyOn(RunnerService, "findOneBy")
431
+ .mockResolvedValue(legacyRunner());
432
+
433
+ const loaded: LoadedKubernetesAiAccessTarget =
434
+ await KubernetesClusterAiAccessService.loadAccessTarget({
435
+ cluster: cluster({ aiAccessRunnerId: RUNNER_ID }),
436
+ });
437
+
438
+ expect(agentLookup).toHaveBeenCalledWith({
439
+ projectId: PROJECT_ID,
440
+ kubernetesClusterId: CLUSTER_ID,
441
+ });
442
+ const call: {
443
+ query: Record<string, unknown>;
444
+ select: Record<string, unknown>;
445
+ } = runnerLookup.mock.calls[0]![0] as {
446
+ query: Record<string, unknown>;
447
+ select: Record<string, unknown>;
448
+ };
449
+ expect(call.query).toEqual({
450
+ _id: RUNNER_ID.toString(),
451
+ projectId: PROJECT_ID,
452
+ });
453
+ // What liveness and the "is an agent row" rule read.
454
+ expect(call.select).toMatchObject({
455
+ name: true,
456
+ lastAlive: true,
457
+ connectionStatus: true,
458
+ hostInfo: true,
459
+ });
460
+ expect(loaded.target.type).toBe("ai_agent");
461
+ expect(loaded.boundRunner?.id).toBe(RUNNER_ID);
462
+ expect(loaded.agent?.id).toBe(AGENT_ID);
463
+ });
464
+
465
+ it("does not read the agent again when the caller already has it (null included)", async () => {
466
+ const agentLookup: jest.SpyInstance = jest.spyOn(
467
+ KubernetesAiAgentService,
468
+ "findForCluster",
469
+ );
470
+
471
+ const loaded: LoadedKubernetesAiAccessTarget =
472
+ await KubernetesClusterAiAccessService.loadAccessTarget({
473
+ cluster: cluster(),
474
+ agentRow: null,
475
+ });
476
+
477
+ expect(agentLookup).not.toHaveBeenCalled();
478
+ expect(loaded.target).toEqual({ type: "none" });
479
+ });
480
+
481
+ it("reads no Runner for a cluster with nothing bound", async () => {
482
+ jest
483
+ .spyOn(KubernetesAiAgentService, "findForCluster")
484
+ .mockResolvedValue(null);
485
+ const runnerLookup: jest.SpyInstance = jest.spyOn(
486
+ RunnerService,
487
+ "findOneBy",
488
+ );
489
+
490
+ await KubernetesClusterAiAccessService.loadAccessTarget({
491
+ cluster: cluster(),
492
+ });
493
+
494
+ expect(runnerLookup).not.toHaveBeenCalled();
495
+ });
496
+
497
+ it("fails closed: a failed lookup propagates (the enqueue chokepoint must not guess)", async () => {
498
+ jest
499
+ .spyOn(KubernetesAiAgentService, "findForCluster")
500
+ .mockRejectedValue(new Error("db down"));
501
+
502
+ await expect(
503
+ KubernetesClusterAiAccessService.loadAccessTarget({
504
+ cluster: cluster(),
505
+ }),
506
+ ).rejects.toThrow("db down");
507
+ });
508
+
509
+ it("refuses a cluster row without its id or project", async () => {
510
+ await expect(
511
+ KubernetesClusterAiAccessService.loadAccessTarget({
512
+ cluster: cluster({ projectId: undefined }),
513
+ agentRow: null,
514
+ }),
515
+ ).rejects.toThrow("resolve its AI access target");
516
+ });
517
+ });
518
+
519
+ describe("the status of a cluster reached through its Kubernetes AI agent", () => {
520
+ let agentLookup: jest.SpyInstance;
521
+ let runnerLookup: jest.SpyInstance;
522
+
523
+ beforeEach(() => {
524
+ jest.spyOn(logger, "error").mockImplementation((): void => {
525
+ return undefined;
526
+ });
527
+ agentLookup = jest
528
+ .spyOn(KubernetesAiAgentService, "findForCluster")
529
+ .mockResolvedValue(agent());
530
+ runnerLookup = jest
531
+ .spyOn(RunnerService, "findOneBy")
532
+ .mockResolvedValue(null);
533
+ });
534
+
535
+ afterEach(() => {
536
+ jest.restoreAllMocks();
537
+ });
538
+
539
+ async function statusOf(
540
+ clusterOverrides: Record<string, unknown> = {},
541
+ gates: KubernetesClusterAiAccessProjectGates = READY_GATES,
542
+ ): Promise<KubernetesClusterAiAccessStatus> {
543
+ return KubernetesClusterAiAccessService.getStatusForClusterModel({
544
+ cluster: cluster(clusterOverrides),
545
+ gates,
546
+ });
547
+ }
548
+
549
+ it("is ready for both with an online agent that can write, and names it as the target", async () => {
550
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
551
+
552
+ expect(status.gaps).toEqual([]);
553
+ expect(status.isInvestigationReady).toBe(true);
554
+ expect(status.isRemediationReady).toBe(true);
555
+ expect(status.accessMethod).toBe("in_cluster");
556
+ expect(status.credentialId).toBeUndefined();
557
+ expect(status.runner).toEqual({
558
+ id: AGENT_ID.toString(),
559
+ name: KUBERNETES_AI_AGENT_DISPLAY_NAME,
560
+ kind: "ai_agent",
561
+ isOnline: true,
562
+ lastAliveAt: expect.any(String),
563
+ canRunAiCommands: true,
564
+ posture: expect.objectContaining({
565
+ writeNamespaces: ["web"],
566
+ podNamespace: "oneuptime-agent",
567
+ allowNodeOperations: false,
568
+ }),
569
+ });
570
+ // No Runner is read for a cluster with nothing bound.
571
+ expect(runnerLookup).not.toHaveBeenCalled();
572
+ });
573
+
574
+ it("always sets aiAgent: the summary of the row, never its key hash", async () => {
575
+ agentLookup.mockResolvedValue(
576
+ agent({ keyHash: "a".repeat(64) } as unknown as Record<string, unknown>),
577
+ );
578
+
579
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
580
+
581
+ expect(status.aiAgent).toMatchObject({
582
+ id: AGENT_ID.toString(),
583
+ isOnline: true,
584
+ connectionStatus: "connected",
585
+ agentVersion: "14.1.0",
586
+ });
587
+ expect(status.aiAgent?.posture?.kubectlVersion).toBe("v1.36.4");
588
+ expect(JSON.stringify(status)).not.toContain("a".repeat(64));
589
+ expect(JSON.stringify(status)).not.toContain("keyHash");
590
+ });
591
+
592
+ it("always sets aiAgent to null when the cluster has no agent row", async () => {
593
+ agentLookup.mockResolvedValue(null);
594
+
595
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
596
+
597
+ expect(status.aiAgent).toBeNull();
598
+ expect("aiAgent" in status).toBe(true);
599
+ });
600
+
601
+ it("always sets automaticInvestigation from the project's two opt-ins", async () => {
602
+ expect((await statusOf()).automaticInvestigation).toEqual({
603
+ incidents: true,
604
+ alerts: false,
605
+ });
606
+
607
+ // Gates built without them (older callers) read as both off, not absent.
608
+ const withoutOptIns: KubernetesClusterAiAccessProjectGates = {
609
+ isAiEnabled: true,
610
+ isAutoRemediationEnabled: true,
611
+ isAiCommandExecutionEnabled: true,
612
+ hasLlmProvider: true,
613
+ };
614
+
615
+ expect((await statusOf({}, withoutOptIns)).automaticInvestigation).toEqual({
616
+ incidents: false,
617
+ alerts: false,
618
+ });
619
+ });
620
+
621
+ it("reports the agent row even when an advanced Runner is the target", async () => {
622
+ runnerLookup.mockResolvedValue(advancedRunner());
623
+ jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
624
+ id: CREDENTIAL_ID,
625
+ _id: CREDENTIAL_ID.toString(),
626
+ name: "prod kubeconfig",
627
+ credentialType: "Kubernetes",
628
+ runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
629
+ } as unknown as RunbookCredential);
630
+
631
+ const status: KubernetesClusterAiAccessStatus = await statusOf({
632
+ aiAccessRunnerId: RUNNER_ID,
633
+ aiAccessCredentialId: CREDENTIAL_ID,
634
+ });
635
+
636
+ expect(status.runner?.kind).toBe("runner");
637
+ expect(status.runner?.name).toBe("platform-ops-runner");
638
+ expect(status.accessMethod).toBe("credential");
639
+ expect(status.credentialId).toBe(CREDENTIAL_ID.toString());
640
+ expect(status.aiAgent?.id).toBe(AGENT_ID.toString());
641
+ });
642
+
643
+ it("the upgrade window: an online agent is the target while the previous Runner is still bound and online", async () => {
644
+ runnerLookup.mockResolvedValue(legacyRunner());
645
+
646
+ const status: KubernetesClusterAiAccessStatus = await statusOf({
647
+ aiAccessRunnerId: RUNNER_ID,
648
+ });
649
+
650
+ expect(status.gaps).toEqual([]);
651
+ expect(status.runner?.kind).toBe("ai_agent");
652
+ expect(status.runner?.id).toBe(AGENT_ID.toString());
653
+ });
654
+
655
+ it("the rollback: the agent stopped and the previous Runner is back online — it serves, with no gap", async () => {
656
+ agentLookup.mockResolvedValue(agent({ connectionStatus: "disconnected" }));
657
+ runnerLookup.mockResolvedValue(legacyRunner());
658
+
659
+ const status: KubernetesClusterAiAccessStatus = await statusOf({
660
+ aiAccessRunnerId: RUNNER_ID,
661
+ });
662
+
663
+ expect(status.gaps).toEqual([]);
664
+ expect(status.runner?.kind).toBe("runner");
665
+ expect(status.runner?.name).toBe("kubernetes-agent/prod-us");
666
+ expect(status.accessMethod).toBe("in_cluster");
667
+ expect(status.aiAgent?.isOnline).toBe(false);
668
+ expect(status.isInvestigationReady).toBe(true);
669
+ });
670
+
671
+ it("ai_agent_offline: blocks both and names the logs command in the agent's own namespace", async () => {
672
+ agentLookup.mockResolvedValue(
673
+ agent({ lastAliveAt: OneUptimeDate.getSomeMinutesAgo(30) }),
674
+ );
675
+
676
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
677
+
678
+ expect(gapCodes(status)).toEqual(["ai_agent_offline"]);
679
+ expect(status.gaps[0]?.blocks).toBe("both");
680
+ expect(status.gaps[0]?.title).toBe("The Kubernetes AI agent is offline");
681
+ expect(status.gaps[0]?.description).toContain("last reported in at");
682
+ expect(status.gaps[0]?.nextStep).toContain(
683
+ "kubectl logs -n oneuptime-agent -l component=ai-agent --tail=100",
684
+ );
685
+ expect(status.runner?.kind).toBe("ai_agent");
686
+ expect(status.runner?.isOnline).toBe(false);
687
+ expect(status.isInvestigationReady).toBe(false);
688
+ expect(status.isRemediationReady).toBe(false);
689
+ });
690
+
691
+ it("ai_agent_offline for an agent that signed off says so", async () => {
692
+ agentLookup.mockResolvedValue(agent({ connectionStatus: "disconnected" }));
693
+
694
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
695
+
696
+ expect(gapCodes(status)).toEqual(["ai_agent_offline"]);
697
+ expect(status.gaps[0]?.description).toContain("signed off");
698
+ expect(status.gaps[0]?.description).toContain("aiAgent.enabled=false");
699
+ });
700
+
701
+ it("ai_agent_offline with no reported namespace leaves a placeholder", async () => {
702
+ agentLookup.mockResolvedValue(
703
+ agent({
704
+ lastAliveAt: OneUptimeDate.getSomeMinutesAgo(30),
705
+ posture: { inCluster: true, clusterIdentifier: "prod-us" },
706
+ }),
707
+ );
708
+
709
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
710
+
711
+ expect(status.gaps[0]?.nextStep).toContain(
712
+ "kubectl logs -n <namespace> -l component=ai-agent --tail=100",
713
+ );
714
+ });
715
+
716
+ it("a read-only agent blocks only remediation, with the aiAgent write step", async () => {
717
+ agentLookup.mockResolvedValue(
718
+ agent({
719
+ posture: {
720
+ inCluster: true,
721
+ allowWrites: false,
722
+ clusterIdentifier: "prod-us",
723
+ },
724
+ }),
725
+ );
726
+
727
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
728
+
729
+ expect(gapCodes(status)).toEqual(["remediation_write_access_missing"]);
730
+ expect(status.gaps[0]?.title).toBe("The Kubernetes AI agent is read-only");
731
+ expect(status.gaps[0]?.blocks).toBe("remediation");
732
+ expect(status.gaps[0]?.nextStep).toBe(REMEDIATION_WRITE_ACCESS_NEXT_STEP);
733
+ expect(status.isInvestigationReady).toBe(true);
734
+ expect(status.isRemediationReady).toBe(false);
735
+ });
736
+
737
+ it("negative control: a read-only agent with fixes off has only the fixes-off gap", async () => {
738
+ agentLookup.mockResolvedValue(
739
+ agent({
740
+ posture: {
741
+ inCluster: true,
742
+ allowWrites: false,
743
+ clusterIdentifier: "prod-us",
744
+ },
745
+ }),
746
+ );
747
+
748
+ const status: KubernetesClusterAiAccessStatus = await statusOf({
749
+ aiRemediationMode: KubernetesAiRemediationMode.Disabled,
750
+ });
751
+
752
+ expect(gapCodes(status)).toEqual(["remediation_disabled"]);
753
+ expect(status.gaps[0]?.nextStep).toContain(
754
+ "the cluster's AI agent page (AI → Agent)",
755
+ );
756
+ });
757
+
758
+ it("an agent whose report does not name this cluster is not used (the enqueue chokepoint refuses it too)", async () => {
759
+ agentLookup.mockResolvedValue(
760
+ agent({
761
+ posture: { inCluster: true, allowWrites: true, clusterIdentifier: "x" },
762
+ }),
763
+ );
764
+
765
+ const status: KubernetesClusterAiAccessStatus = await statusOf();
766
+
767
+ expect(gapCodes(status)).toEqual(["runner_cluster_mismatch"]);
768
+ expect(status.gaps[0]?.title).toBe(
769
+ "The Kubernetes AI agent has not reported this cluster",
770
+ );
771
+ expect(status.gaps[0]?.nextStep).toContain(
772
+ "Reset the agent on the cluster's AI agent page (AI → Agent)",
773
+ );
774
+ expect(status.accessMethod).toBe("none");
775
+ expect(status.isInvestigationReady).toBe(false);
776
+ });
777
+
778
+ it("a previous in-cluster Runner whose report dropped its posture is not used; the step is the upgrade", async () => {
779
+ agentLookup.mockResolvedValue(null);
780
+ runnerLookup.mockResolvedValue(legacyRunner({ hostInfo: {} }));
781
+ const credentialLookup: jest.SpyInstance = jest.spyOn(
782
+ RunbookCredentialService,
783
+ "findOneBy",
784
+ );
785
+
786
+ const status: KubernetesClusterAiAccessStatus = await statusOf({
787
+ aiAccessRunnerId: RUNNER_ID,
788
+ aiAccessCredentialId: CREDENTIAL_ID,
789
+ });
790
+
791
+ expect(gapCodes(status)).toEqual(["runner_cluster_mismatch"]);
792
+ expect(status.gaps[0]?.title).toBe(
793
+ "The previous in-cluster Runner has not reported this cluster",
794
+ );
795
+ expect(status.gaps[0]?.nextStep).toContain(
796
+ "Upgrade the Kubernetes agent chart",
797
+ );
798
+ expect(status.accessMethod).toBe("none");
799
+ expect(status.credentialId).toBeUndefined();
800
+ // Never handed a credential, so the credential is not even read.
801
+ expect(credentialLookup).not.toHaveBeenCalled();
802
+ });
803
+
804
+ it("never reads or uses a credential selected on a cluster the agent reaches", async () => {
805
+ const credentialLookup: jest.SpyInstance = jest.spyOn(
806
+ RunbookCredentialService,
807
+ "findOneBy",
808
+ );
809
+
810
+ const status: KubernetesClusterAiAccessStatus = await statusOf({
811
+ aiAccessCredentialId: CREDENTIAL_ID,
812
+ });
813
+
814
+ expect(status.gaps).toEqual([]);
815
+ expect(status.credentialId).toBeUndefined();
816
+ expect(status.credentialName).toBeUndefined();
817
+ expect(credentialLookup).not.toHaveBeenCalled();
818
+ });
819
+
820
+ it("the agent never gets the runner_ai_commands_disabled or the command-execution gap", async () => {
821
+ const status: KubernetesClusterAiAccessStatus = await statusOf(
822
+ {},
823
+ { ...READY_GATES, isAiCommandExecutionEnabled: false },
824
+ );
825
+
826
+ expect(gapCodes(status)).not.toContain("runner_ai_commands_disabled");
827
+ expect(gapCodes(status)).not.toContain(
828
+ "project_ai_command_execution_disabled",
829
+ );
830
+ expect(status.runner?.canRunAiCommands).toBe(true);
831
+ });
832
+ });
833
+
834
+ describe("KubernetesClusterAiAccessService.getStatusesForSubject reads every cluster's agent in one query", () => {
835
+ const OTHER_CLUSTER_ID: ObjectID = new ObjectID(
836
+ "88888888-8888-4888-8888-888888888888",
837
+ );
838
+
839
+ beforeEach(() => {
840
+ jest.spyOn(logger, "error").mockImplementation((): void => {
841
+ return undefined;
842
+ });
843
+ jest
844
+ .spyOn(KubernetesClusterAiAccessService, "getClustersForSubject")
845
+ .mockResolvedValue([
846
+ cluster(),
847
+ cluster({
848
+ id: OTHER_CLUSTER_ID,
849
+ _id: OTHER_CLUSTER_ID.toString(),
850
+ name: "prod-eu",
851
+ clusterIdentifier: "prod-eu",
852
+ }),
853
+ ]);
854
+ jest
855
+ .spyOn(KubernetesClusterAiAccessService, "getProjectGates")
856
+ .mockResolvedValue(READY_GATES);
857
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(null);
858
+ });
859
+
860
+ afterEach(() => {
861
+ jest.restoreAllMocks();
862
+ });
863
+
864
+ it("uses findForClusters once and never findForCluster", async () => {
865
+ const batch: jest.SpyInstance = jest
866
+ .spyOn(KubernetesAiAgentService, "findForClusters")
867
+ .mockResolvedValue(
868
+ new Map<string, KubernetesAiAgent>([[CLUSTER_ID.toString(), agent()]]),
869
+ );
870
+ const single: jest.SpyInstance = jest.spyOn(
871
+ KubernetesAiAgentService,
872
+ "findForCluster",
873
+ );
874
+
875
+ const statuses: Array<KubernetesClusterAiAccessStatus> =
876
+ await KubernetesClusterAiAccessService.getStatusesForSubject({
877
+ projectId: PROJECT_ID,
878
+ incidentId: ObjectID.generate(),
879
+ });
880
+
881
+ expect(batch).toHaveBeenCalledTimes(1);
882
+ const call: {
883
+ projectId: ObjectID;
884
+ kubernetesClusterIds: Array<ObjectID>;
885
+ } = batch.mock.calls[0]![0] as {
886
+ projectId: ObjectID;
887
+ kubernetesClusterIds: Array<ObjectID>;
888
+ };
889
+ expect(call.projectId).toBe(PROJECT_ID);
890
+ expect(
891
+ call.kubernetesClusterIds.map((id: ObjectID) => {
892
+ return id.toString();
893
+ }),
894
+ ).toEqual([CLUSTER_ID.toString(), OTHER_CLUSTER_ID.toString()]);
895
+ expect(single).not.toHaveBeenCalled();
896
+
897
+ expect(statuses[0]?.runner?.kind).toBe("ai_agent");
898
+ expect(statuses[0]?.aiAgent?.id).toBe(AGENT_ID.toString());
899
+ expect(gapCodes(statuses[1]!)).toEqual(["ai_agent_not_connected"]);
900
+ expect(statuses[1]?.aiAgent).toBeNull();
901
+ });
902
+
903
+ it("falls back to one read per cluster when the batch read fails", async () => {
904
+ jest
905
+ .spyOn(KubernetesAiAgentService, "findForClusters")
906
+ .mockRejectedValue(new Error("db down"));
907
+ const single: jest.SpyInstance = jest
908
+ .spyOn(KubernetesAiAgentService, "findForCluster")
909
+ .mockResolvedValue(agent());
910
+
911
+ const statuses: Array<KubernetesClusterAiAccessStatus> =
912
+ await KubernetesClusterAiAccessService.getStatusesForSubject({
913
+ projectId: PROJECT_ID,
914
+ alertId: ObjectID.generate(),
915
+ });
916
+
917
+ expect(single).toHaveBeenCalledTimes(2);
918
+ expect(statuses).toHaveLength(2);
919
+ });
920
+
921
+ it("returns [] without reading anything for a subject with no clusters", async () => {
922
+ (
923
+ KubernetesClusterAiAccessService.getClustersForSubject as unknown as jest.SpyInstance
924
+ ).mockResolvedValue([]);
925
+ const batch: jest.SpyInstance = jest.spyOn(
926
+ KubernetesAiAgentService,
927
+ "findForClusters",
928
+ );
929
+
930
+ expect(
931
+ await KubernetesClusterAiAccessService.getStatusesForSubject({
932
+ projectId: PROJECT_ID,
933
+ incidentId: ObjectID.generate(),
934
+ }),
935
+ ).toEqual([]);
936
+ expect(batch).not.toHaveBeenCalled();
937
+ });
938
+ });
939
+
940
+ describe("KubernetesClusterAiAccessService.getLegacyAgentRunnerForCluster", () => {
941
+ let runnerLookup: jest.SpyInstance;
942
+ let clusterLookup: jest.SpyInstance;
943
+
944
+ beforeEach(() => {
945
+ jest.spyOn(logger, "error").mockImplementation((): void => {
946
+ return undefined;
947
+ });
948
+ runnerLookup = jest.spyOn(RunnerService, "findOneBy");
949
+ clusterLookup = jest.spyOn(KubernetesClusterService, "findOneBy");
950
+ });
951
+
952
+ afterEach(() => {
953
+ jest.restoreAllMocks();
954
+ });
955
+
956
+ function find(clusterIdentifier: string | undefined): Promise<Runner | null> {
957
+ return KubernetesClusterAiAccessService.getLegacyAgentRunnerForCluster({
958
+ projectId: PROJECT_ID,
959
+ kubernetesClusterId: CLUSTER_ID,
960
+ clusterIdentifier,
961
+ });
962
+ }
963
+
964
+ it("finds the row by this cluster's agent name, case-insensitively, in this project", async () => {
965
+ runnerLookup.mockResolvedValue(legacyRunner());
966
+
967
+ const runner: Runner | null = await find("prod-us");
968
+
969
+ expect(runner?.id).toBe(RUNNER_ID);
970
+ const query: Record<string, unknown> = (
971
+ runnerLookup.mock.calls[0]![0] as { query: Record<string, unknown> }
972
+ ).query;
973
+ expect(query["projectId"]).toBe(PROJECT_ID);
974
+ // A case-insensitive matcher, not the literal name.
975
+ expect(typeof query["name"]).not.toBe("string");
976
+ expect(query["name"]).toBeDefined();
977
+ expect(clusterLookup).not.toHaveBeenCalled();
978
+ });
979
+
980
+ it("refuses the row by that name when its posture names another cluster", async () => {
981
+ runnerLookup.mockResolvedValue(
982
+ legacyRunner({
983
+ hostInfo: {
984
+ kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
985
+ },
986
+ }),
987
+ );
988
+
989
+ expect(await find("prod-us")).toBeNull();
990
+ });
991
+
992
+ it("falls back to the cluster's bound Runner when it is this cluster's agent by posture", async () => {
993
+ const byPosture: Runner = legacyRunner({ name: "renamed-runner" });
994
+ runnerLookup.mockResolvedValueOnce(null).mockResolvedValueOnce(byPosture);
995
+ clusterLookup.mockResolvedValue(cluster({ aiAccessRunnerId: RUNNER_ID }));
996
+
997
+ expect(await find("prod-us")).toBe(byPosture);
998
+ });
999
+
1000
+ it("does not return a bound Runner that is not this cluster's agent", async () => {
1001
+ runnerLookup
1002
+ .mockResolvedValueOnce(null)
1003
+ .mockResolvedValueOnce(advancedRunner());
1004
+ clusterLookup.mockResolvedValue(cluster({ aiAccessRunnerId: RUNNER_ID }));
1005
+
1006
+ expect(await find("prod-us")).toBeNull();
1007
+ });
1008
+
1009
+ it("returns null for a cluster with nothing by that name and nothing bound", async () => {
1010
+ runnerLookup.mockResolvedValue(null);
1011
+ clusterLookup.mockResolvedValue(cluster());
1012
+
1013
+ expect(await find("prod-us")).toBeNull();
1014
+ expect(runnerLookup).toHaveBeenCalledTimes(1);
1015
+ });
1016
+
1017
+ it("looks nothing up for a cluster without an identifier", async () => {
1018
+ expect(await find(undefined)).toBeNull();
1019
+ expect(await find(" ")).toBeNull();
1020
+ expect(runnerLookup).not.toHaveBeenCalled();
1021
+ });
1022
+
1023
+ /*
1024
+ * The AI agent's registration asks this to keep a live Runner's cluster:
1025
+ * "could not tell" must not read as "no Runner", or a database hiccup
1026
+ * would admit an agent that then outranks a Runner still working.
1027
+ */
1028
+ it("a failed lookup by name propagates instead of reading as none", async () => {
1029
+ runnerLookup.mockRejectedValue(new Error("db down"));
1030
+
1031
+ await expect(find("prod-us")).rejects.toThrow("db down");
1032
+ });
1033
+
1034
+ it("a failed read of the cluster's binding propagates", async () => {
1035
+ runnerLookup.mockResolvedValue(null);
1036
+ clusterLookup.mockRejectedValue(new Error("db down"));
1037
+
1038
+ await expect(find("prod-us")).rejects.toThrow("db down");
1039
+ });
1040
+
1041
+ it("a failed read of the bound Runner propagates", async () => {
1042
+ runnerLookup
1043
+ .mockResolvedValueOnce(null)
1044
+ .mockRejectedValueOnce(new Error("db down"));
1045
+ clusterLookup.mockResolvedValue(cluster({ aiAccessRunnerId: RUNNER_ID }));
1046
+
1047
+ await expect(find("prod-us")).rejects.toThrow("db down");
1048
+ });
1049
+ });
1050
+
1051
+ describe("a legacy Runner lookup that fails never lets the AI agent in", () => {
1052
+ let agentWrites: Array<jest.SpyInstance>;
1053
+ let feed: jest.SpyInstance;
1054
+ let runnerDeletes: Array<jest.SpyInstance>;
1055
+
1056
+ beforeEach(() => {
1057
+ for (const level of ["info", "warn", "error", "debug"] as const) {
1058
+ jest.spyOn(logger, level).mockImplementation((): void => {
1059
+ return undefined;
1060
+ });
1061
+ }
1062
+
1063
+ jest
1064
+ .spyOn(KubernetesClusterService, "findOrCreateByClusterIdentifier")
1065
+ .mockResolvedValue(cluster());
1066
+ // The registration's re-read of the cluster succeeds...
1067
+ jest
1068
+ .spyOn(KubernetesClusterService, "findOneBy")
1069
+ .mockResolvedValue(cluster({ aiAccessRunnerId: RUNNER_ID }));
1070
+ // ...but the Runner table cannot be read.
1071
+ jest
1072
+ .spyOn(RunnerService, "findOneBy")
1073
+ .mockRejectedValue(new Error("db down"));
1074
+ jest.spyOn(KubernetesAiAgentService, "findOneBy").mockResolvedValue(null);
1075
+ // Nothing else would stop a new agent row: the caps have room.
1076
+ jest
1077
+ .spyOn(KubernetesAiAgentService, "countBy")
1078
+ .mockResolvedValue(new PositiveNumber(0));
1079
+ jest
1080
+ .spyOn(KubernetesClusterService, "updateOneBy")
1081
+ .mockResolvedValue(1 as never);
1082
+ jest
1083
+ .spyOn(KubernetesClusterService, "updateOneById")
1084
+ .mockResolvedValue(undefined as never);
1085
+
1086
+ agentWrites = (
1087
+ [
1088
+ "create",
1089
+ "updateOneById",
1090
+ "updateOneBy",
1091
+ "updateColumnsByIdWithoutHooks",
1092
+ ] as const
1093
+ ).map((method: string) => {
1094
+ return jest
1095
+ .spyOn(KubernetesAiAgentService, method as never)
1096
+ .mockResolvedValue(undefined as never);
1097
+ });
1098
+ runnerDeletes = (
1099
+ ["deleteBy", "deleteOneBy", "deleteOneById", "hardDeleteBy"] as const
1100
+ ).map((method: string) => {
1101
+ return jest
1102
+ .spyOn(RunnerService, method as never)
1103
+ .mockResolvedValue(0 as never);
1104
+ });
1105
+ feed = jest
1106
+ .spyOn(KubernetesClusterFeedService, "createKubernetesClusterFeedItem")
1107
+ .mockResolvedValue(undefined as never);
1108
+ });
1109
+
1110
+ afterEach(() => {
1111
+ jest.restoreAllMocks();
1112
+ });
1113
+
1114
+ it("registration fails (the agent retries) and writes no agent row, key or feed item", async () => {
1115
+ await expect(
1116
+ KubernetesAiAgentService.register({
1117
+ projectId: PROJECT_ID,
1118
+ clusterName: "prod-us",
1119
+ posture: { inCluster: true, allowWrites: true },
1120
+ }),
1121
+ ).rejects.toThrow("db down");
1122
+
1123
+ for (const write of agentWrites) {
1124
+ expect(write).not.toHaveBeenCalled();
1125
+ }
1126
+ expect(feed).not.toHaveBeenCalled();
1127
+ });
1128
+
1129
+ it("negative control: the same registration is admitted once the Runner table reads as empty", async () => {
1130
+ (RunnerService.findOneBy as unknown as jest.SpyInstance).mockResolvedValue(
1131
+ null,
1132
+ );
1133
+ (
1134
+ KubernetesAiAgentService.create as unknown as jest.SpyInstance
1135
+ ).mockImplementation(async (args: unknown): Promise<KubernetesAiAgent> => {
1136
+ const row: KubernetesAiAgent = (args as { data: KubernetesAiAgent }).data;
1137
+ row.id = AGENT_ID;
1138
+ return row;
1139
+ });
1140
+
1141
+ const result: { agentId: ObjectID } =
1142
+ await KubernetesAiAgentService.register({
1143
+ projectId: PROJECT_ID,
1144
+ clusterName: "prod-us",
1145
+ posture: { inCluster: true, allowWrites: true },
1146
+ });
1147
+
1148
+ expect(result.agentId.toString()).toBe(AGENT_ID.toString());
1149
+ });
1150
+
1151
+ it("the lazy retirement reads it as a failed check and deletes nothing", async () => {
1152
+ const outcome: string =
1153
+ await KubernetesAiAgentService.retireLegacyRunnerIfUnused({
1154
+ // A fresh id: the check is throttled per agent per process.
1155
+ agent: agent({
1156
+ id: ObjectID.generate(),
1157
+ createdAt: OneUptimeDate.getSomeDaysAgo(3),
1158
+ }),
1159
+ cluster: cluster(),
1160
+ });
1161
+
1162
+ expect(outcome).toBe("failed");
1163
+ for (const remove of runnerDeletes) {
1164
+ expect(remove).not.toHaveBeenCalled();
1165
+ }
1166
+ expect(feed).not.toHaveBeenCalled();
1167
+ });
1168
+ });