@oneuptime/common 14.0.7 → 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 (1064) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/Index.ts +2 -0
  6. package/Models/DatabaseModels/KubernetesAiAgent.ts +364 -0
  7. package/Models/DatabaseModels/KubernetesCluster.ts +3 -3
  8. package/Models/DatabaseModels/Project.ts +55 -5
  9. package/Models/DatabaseModels/ProxmoxResource.ts +49 -1
  10. package/Models/DatabaseModels/RunnerJob.ts +75 -0
  11. package/Models/DatabaseModels/StatusPage.ts +55 -0
  12. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  13. package/Models/DatabaseModels/TeamComplianceSetting.ts +143 -2
  14. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +17 -0
  15. package/Server/API/AIChatAPI.ts +6 -3
  16. package/Server/API/AIInvestigationAPI.ts +17 -10
  17. package/Server/API/AutoRemediationAPI.ts +207 -25
  18. package/Server/API/IncidentAPI.ts +162 -0
  19. package/Server/API/KubernetesClusterAiAccessAPI.ts +623 -21
  20. package/Server/API/MicrosoftTeamsAPI.ts +93 -14
  21. package/Server/API/ProxmoxResourceAPI.ts +244 -14
  22. package/Server/API/StatusPageAPI.ts +487 -171
  23. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  24. package/Server/Infrastructure/GlobalCache.ts +32 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.ts +99 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.ts +137 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.ts +42 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  36. package/Server/Infrastructure/Semaphore.ts +10 -0
  37. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  38. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  39. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  40. package/Server/Middleware/TelemetryIngest.ts +41 -18
  41. package/Server/Services/AIService.ts +70 -1
  42. package/Server/Services/AlertService.ts +352 -11
  43. package/Server/Services/AlertSeverityService.ts +65 -15
  44. package/Server/Services/AutoRemediationRuleService.ts +1 -1
  45. package/Server/Services/DatabaseService.ts +93 -10
  46. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  47. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  48. package/Server/Services/IncidentEpisodeService.ts +15 -0
  49. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  50. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  51. package/Server/Services/IncidentService.ts +1788 -142
  52. package/Server/Services/IncidentSeverityService.ts +66 -15
  53. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  54. package/Server/Services/IncidentTemplateService.ts +109 -0
  55. package/Server/Services/KubernetesAiAgentJobService.ts +185 -0
  56. package/Server/Services/KubernetesAiAgentService.ts +1634 -0
  57. package/Server/Services/KubernetesClusterAiAccessService.ts +1118 -451
  58. package/Server/Services/KubernetesClusterService.ts +129 -21
  59. package/Server/Services/ProjectService.ts +32 -0
  60. package/Server/Services/ProxmoxResourceService.ts +322 -5
  61. package/Server/Services/RunnerJobService.ts +241 -104
  62. package/Server/Services/RunnerService.ts +1 -1
  63. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  64. package/Server/Services/ScheduledMaintenanceService.ts +805 -19
  65. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +414 -56
  66. package/Server/Services/SmsService.ts +10 -0
  67. package/Server/Services/StatusPageService.ts +65 -8
  68. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  69. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  70. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  71. package/Server/Services/TeamComplianceSettingService.ts +1125 -9
  72. package/Server/Types/Markdown.ts +153 -6
  73. package/Server/Types/Workflow/Components/Email.ts +61 -2
  74. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +67 -9
  75. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +39 -10
  76. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +14 -3
  77. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +37 -15
  78. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +67 -24
  79. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +48 -5
  80. package/Server/Utils/AI/SRE/IncidentPostmortemRunner.ts +40 -8
  81. package/Server/Utils/AI/SRE/InvestigationEligibility.ts +13 -2
  82. package/Server/Utils/AI/SRE/InvestigationGrader.ts +1 -1
  83. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +6 -1
  84. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  85. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  86. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  87. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  88. package/Server/Utils/SSRFProtection.ts +76 -16
  89. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  90. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  91. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  92. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  93. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  94. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  95. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  96. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  97. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  98. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  99. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  100. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  101. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  102. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  103. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  104. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  105. package/Server/Utils/StatusPageResource.ts +51 -20
  106. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +133 -7
  107. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +58 -4
  108. package/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.ts +447 -0
  109. package/Server/Utils/Telemetry/ProxmoxNativePush.ts +903 -0
  110. package/Server/Utils/Telemetry/TelemetryEntity.ts +87 -27
  111. package/Server/Utils/VM/VMAPI.ts +1 -0
  112. package/Server/Utils/VM/VMRunner.ts +10 -0
  113. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  114. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +684 -47
  115. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  116. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  117. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  118. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  119. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  120. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  121. package/Tests/App/AdminDashboard/RowActionPlacementOverrides.test.tsx +278 -0
  122. package/Tests/App/Dashboard/AIChatChartWidgetZoom.test.tsx +738 -0
  123. package/Tests/App/Dashboard/AIFeaturesSettingsPointers.test.tsx +147 -0
  124. package/Tests/App/Dashboard/AIInsightsKubernetesClusterPage.test.tsx +1523 -0
  125. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +463 -3
  126. package/Tests/App/Dashboard/AffectedResourcesPickerDropdownPlacement.test.tsx +958 -0
  127. package/Tests/App/Dashboard/AffectedResourcesPickerFocusLeave.test.tsx +595 -0
  128. package/Tests/App/Dashboard/AskAiAvailability.test.ts +20 -3
  129. package/Tests/App/Dashboard/AskAiDisabled.test.tsx +8 -3
  130. package/Tests/App/Dashboard/CephRateChartReload.test.tsx +1456 -0
  131. package/Tests/App/Dashboard/CephTimeRangeZoom.test.tsx +1229 -0
  132. package/Tests/App/Dashboard/ChartCardTimeRangeZoom.test.tsx +151 -0
  133. package/Tests/App/Dashboard/ChartCardZoomAffordances.test.tsx +257 -0
  134. package/Tests/App/Dashboard/ChartRefetchFrame.test.tsx +141 -0
  135. package/Tests/App/Dashboard/ChartZoomHarness.tsx +249 -0
  136. package/Tests/App/Dashboard/ChartZoomStandIn.tsx +131 -0
  137. package/Tests/App/Dashboard/CloudServerlessSlowLoads.test.tsx +628 -0
  138. package/Tests/App/Dashboard/CloudServerlessTimeRangeZoom.test.tsx +607 -0
  139. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +91 -19
  140. package/Tests/App/Dashboard/CompanionMetricsTabZoom.test.tsx +632 -0
  141. package/Tests/App/Dashboard/ContainerChartZoomStandIn.tsx +195 -0
  142. package/Tests/App/Dashboard/ContainerHostOverviewSlowLoads.test.tsx +542 -0
  143. package/Tests/App/Dashboard/ContainerHostOverviewTimeRangeZoom.test.tsx +848 -0
  144. package/Tests/App/Dashboard/ContainerMetricsTimeRangeZoom.test.tsx +561 -0
  145. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  146. package/Tests/App/Dashboard/DashboardBoardWideZoomShell.test.tsx +1147 -0
  147. package/Tests/App/Dashboard/DashboardEditPermissions.test.tsx +3 -2
  148. package/Tests/App/Dashboard/DashboardHistogramWidgetDragCursor.test.tsx +399 -0
  149. package/Tests/App/Dashboard/DashboardHistogramWidgetZoom.test.tsx +985 -0
  150. package/Tests/App/Dashboard/DashboardHistogramWidgetZoomHint.test.tsx +803 -0
  151. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +1 -0
  152. package/Tests/App/Dashboard/DashboardSettingsPreviewZoom.test.tsx +679 -0
  153. package/Tests/App/Dashboard/DashboardSloAndDataSourceChartZoom.test.tsx +881 -0
  154. package/Tests/App/Dashboard/DashboardTraceStatusWidgets.test.tsx +878 -0
  155. package/Tests/App/Dashboard/DatabaseMetricChartModalTimeRangeZoom.test.tsx +442 -0
  156. package/Tests/App/Dashboard/DatabaseOverviewTimeRangeZoom.test.tsx +831 -0
  157. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +43 -0
  158. package/Tests/App/Dashboard/DockerSwarmInsightsTimeRangeZoom.test.tsx +524 -0
  159. package/Tests/App/Dashboard/DockerSwarmRateChartRefresh.test.tsx +259 -0
  160. package/Tests/App/Dashboard/EdgeDetailPanelHistory.test.tsx +763 -0
  161. package/Tests/App/Dashboard/EdgeDetailPanelTimeRangeZoom.test.tsx +547 -0
  162. package/Tests/App/Dashboard/EdgeHistoryBucketsCases.tsx +565 -0
  163. package/Tests/App/Dashboard/EdgeHistoryBucketsKathmandu.test.tsx +73 -0
  164. package/Tests/App/Dashboard/EdgeHistoryBucketsKolkata.test.tsx +74 -0
  165. package/Tests/App/Dashboard/EdgeHistoryBucketsNewYork.test.tsx +72 -0
  166. package/Tests/App/Dashboard/EdgeHistoryBucketsUtc.test.tsx +72 -0
  167. package/Tests/App/Dashboard/EmbeddedMetricCardRefresh.test.tsx +310 -0
  168. package/Tests/App/Dashboard/EmbeddedMetricCardTimeRangeZoom.test.tsx +536 -0
  169. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +3 -1
  170. package/Tests/App/Dashboard/EscalationRuleRowActions.test.tsx +660 -0
  171. package/Tests/App/Dashboard/EventNotes.test.tsx +778 -2
  172. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  173. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  174. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +828 -17
  175. package/Tests/App/Dashboard/EventOverviewSnapshotExplorerZoom.test.tsx +1139 -0
  176. package/Tests/App/Dashboard/EventOverviewSnapshotZoomAcrossTabs.test.tsx +1057 -0
  177. package/Tests/App/Dashboard/EventOverviewTelemetrySnapshotZoom.test.tsx +757 -0
  178. package/Tests/App/Dashboard/ExceptionOccurrenceTrendZoom.test.tsx +675 -0
  179. package/Tests/App/Dashboard/ExceptionOccurrencesZoomSurvivesViewSwitch.test.tsx +405 -0
  180. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +67 -3
  181. package/Tests/App/Dashboard/ExceptionTablesSpanStatus.test.tsx +515 -0
  182. package/Tests/App/Dashboard/ExplorerOwnZoomRegression.test.tsx +739 -0
  183. package/Tests/App/Dashboard/FlowBandwidthAxisCases.tsx +461 -0
  184. package/Tests/App/Dashboard/FlowBandwidthAxisKathmandu.test.tsx +25 -0
  185. package/Tests/App/Dashboard/FlowBandwidthAxisKolkata.test.tsx +25 -0
  186. package/Tests/App/Dashboard/FlowBandwidthAxisNewYork.test.tsx +25 -0
  187. package/Tests/App/Dashboard/FlowBandwidthAxisUtc.test.tsx +24 -0
  188. package/Tests/App/Dashboard/FlowTopTalkersZoomRefetch.test.tsx +722 -0
  189. package/Tests/App/Dashboard/FlowViewportGuard.test.tsx +2526 -0
  190. package/Tests/App/Dashboard/HostPagesSlowLoads.test.tsx +872 -0
  191. package/Tests/App/Dashboard/HostPagesTimeRangeZoom.test.tsx +1208 -0
  192. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  193. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  194. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  195. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  196. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  197. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  198. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  199. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  200. package/Tests/App/Dashboard/IncidentRootCauseTimeRangeZoom.test.tsx +358 -0
  201. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  202. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  203. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  204. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  205. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +100 -9
  206. package/Tests/App/Dashboard/InfrastructureMetricsTabTimeRangeZoom.test.tsx +485 -0
  207. package/Tests/App/Dashboard/InvestigationDrawerCompanionZoom.test.tsx +655 -0
  208. package/Tests/App/Dashboard/InvestigationDrawerZoom.test.tsx +830 -0
  209. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +338 -2
  210. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +454 -16
  211. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +799 -819
  212. package/Tests/App/Dashboard/KubernetesClusterAiAgentOverviewCard.test.tsx +367 -0
  213. package/Tests/App/Dashboard/KubernetesClusterAiAgentStatus.test.ts +1202 -0
  214. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +106 -0
  215. package/Tests/App/Dashboard/KubernetesClusterAiNavigation.test.tsx +354 -0
  216. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +1884 -2054
  217. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +478 -179
  218. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +50 -0
  219. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +2 -1
  220. package/Tests/App/Dashboard/KubernetesClusterOverviewZoom.test.tsx +1217 -0
  221. package/Tests/App/Dashboard/KubernetesClusterPagesZoom.test.tsx +731 -0
  222. package/Tests/App/Dashboard/KubernetesCostsLoadRecovery.test.tsx +869 -0
  223. package/Tests/App/Dashboard/KubernetesCostsZoom.test.tsx +1214 -0
  224. package/Tests/App/Dashboard/KubernetesCostsZoomExactHours.test.tsx +692 -0
  225. package/Tests/App/Dashboard/KubernetesNetworkThroughputReload.test.tsx +902 -0
  226. package/Tests/App/Dashboard/KubernetesNetworkZoomBucketEdges.test.tsx +571 -0
  227. package/Tests/App/Dashboard/KubernetesOverviewZoomBucketEdges.test.tsx +697 -0
  228. package/Tests/App/Dashboard/KubernetesResourceMetricsTabZoom.test.tsx +633 -0
  229. package/Tests/App/Dashboard/LogsExplorerTimeRangeZoom.test.tsx +470 -0
  230. package/Tests/App/Dashboard/LogsInsightsTimeRangeZoom.test.tsx +948 -0
  231. package/Tests/App/Dashboard/LogsViewerPinnedWindowZoom.test.tsx +486 -0
  232. package/Tests/App/Dashboard/MetricChartsBucketInspectorDoubleClick.test.tsx +1001 -0
  233. package/Tests/App/Dashboard/MetricExplorerInvestigationAutoRefresh.test.tsx +514 -0
  234. package/Tests/App/Dashboard/MetricExplorerSavedViewZoomRoundTrip.test.tsx +581 -0
  235. package/Tests/App/Dashboard/MetricExplorerTimeRangeZoom.test.tsx +1162 -0
  236. package/Tests/App/Dashboard/MetricMonitorPreviewZoomLabel.test.ts +72 -0
  237. package/Tests/App/Dashboard/MetricRowSparklineTimeRangeZoom.test.tsx +204 -0
  238. package/Tests/App/Dashboard/MetricViewFirstResultLands.test.tsx +408 -0
  239. package/Tests/App/Dashboard/MetricViewOwnZoomResetPlacement.test.tsx +952 -0
  240. package/Tests/App/Dashboard/MetricViewTimeRange.test.ts +139 -0
  241. package/Tests/App/Dashboard/MetricViewTimeRangeZoom.test.tsx +556 -0
  242. package/Tests/App/Dashboard/MonitorMetricsChartWrappersZoom.test.tsx +552 -0
  243. package/Tests/App/Dashboard/MonitorMetricsTabsTimeRangeZoom.test.tsx +740 -0
  244. package/Tests/App/Dashboard/MonitorOverviewChartsTimeRangeZoom.test.tsx +735 -0
  245. package/Tests/App/Dashboard/MonitorStepPreviewsTimeRangeZoom.test.tsx +835 -0
  246. package/Tests/App/Dashboard/NetworkDeviceTimeRangeZoom.test.tsx +1115 -0
  247. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +186 -17
  248. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +155 -46
  249. package/Tests/App/Dashboard/PasskeySettings.test.tsx +87 -2
  250. package/Tests/App/Dashboard/ProjectAISettingsPages.test.tsx +884 -0
  251. package/Tests/App/Dashboard/ProxmoxNodeRemove.test.tsx +621 -0
  252. package/Tests/App/Dashboard/ProxmoxOverviewSlowLoads.test.tsx +584 -0
  253. package/Tests/App/Dashboard/ProxmoxPagesTimeRangeZoom.test.tsx +823 -0
  254. package/Tests/App/Dashboard/ProxmoxRateChartReload.test.tsx +1253 -0
  255. package/Tests/App/Dashboard/PublicDashboardBoardWideZoom.test.tsx +605 -0
  256. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  257. package/Tests/App/Dashboard/RealRowsLineChart.tsx +250 -0
  258. package/Tests/App/Dashboard/RechartsHostsPlotCursor.test.tsx +513 -0
  259. package/Tests/App/Dashboard/RowActionPlacementOverrides.test.tsx +865 -0
  260. package/Tests/App/Dashboard/RumOverviewTimeRangeZoom.test.tsx +877 -0
  261. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +20 -4
  262. package/Tests/App/Dashboard/RunnerSupersededByAiAgentNote.test.tsx +296 -0
  263. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +146 -36
  264. package/Tests/App/Dashboard/ServiceDatabaseSlowLoads.test.tsx +1166 -0
  265. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +1243 -104
  266. package/Tests/App/Dashboard/ServiceOverviewTimeRangeZoom.test.tsx +792 -0
  267. package/Tests/App/Dashboard/SloBudgetBurnDownZoom.test.tsx +771 -0
  268. package/Tests/App/Dashboard/SloBudgetBurnDownZoomAffordances.test.tsx +380 -0
  269. package/Tests/App/Dashboard/SloEventMetricsBarChartZoom.test.tsx +369 -0
  270. package/Tests/App/Dashboard/SloHistoryChartsTimeRangeZoom.test.tsx +694 -0
  271. package/Tests/App/Dashboard/SloHistoryChartsZoomAffordances.test.tsx +497 -0
  272. package/Tests/App/Dashboard/SloMetricsTimeRangeZoom.test.tsx +541 -0
  273. package/Tests/App/Dashboard/SlowLoadHarness.ts +123 -0
  274. package/Tests/App/Dashboard/SnapshotExplorerPrimaryZoom.test.tsx +1194 -0
  275. package/Tests/App/Dashboard/SnapshotViewerPinnedWindow.test.tsx +620 -0
  276. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  277. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  278. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  279. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  280. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  281. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  282. package/Tests/App/Dashboard/TelemetrySnapshotPanelTimeRangeZoom.test.tsx +765 -0
  283. package/Tests/App/Dashboard/TelemetrySnapshotZoom.test.tsx +516 -0
  284. package/Tests/App/Dashboard/TelemetrySnapshotZoomAcrossTabs.test.tsx +827 -0
  285. package/Tests/App/Dashboard/TimeRangeZoomPageHarness.tsx +463 -0
  286. package/Tests/App/Dashboard/TraceChartEditorStatusSplit.test.tsx +543 -0
  287. package/Tests/App/Dashboard/TraceStatusPresentation.test.tsx +967 -0
  288. package/Tests/App/Dashboard/TraceTimelineMinimapZoom.test.tsx +315 -0
  289. package/Tests/App/Dashboard/TracesAnalyticsStatusSplit.test.tsx +778 -0
  290. package/Tests/App/Dashboard/TracesAnalyticsViewZoom.test.tsx +349 -0
  291. package/Tests/App/Dashboard/TracesExplorerTimeRangeZoom.test.tsx +551 -0
  292. package/Tests/App/Dashboard/TracesStatusChartAndFacet.test.tsx +687 -0
  293. package/Tests/App/Dashboard/TracesViewerStaleResponses.test.tsx +458 -0
  294. package/Tests/App/Dashboard/UptimeStripsExcludedFromZoom.test.tsx +406 -0
  295. package/Tests/App/Dashboard/VMwareOverviewSlowLoads.test.tsx +535 -0
  296. package/Tests/App/Dashboard/VMwarePagesTimeRangeZoom.test.tsx +606 -0
  297. package/Tests/App/Dashboard/ValueWidgetSparklineStaleHover.test.tsx +1292 -0
  298. package/Tests/App/Dashboard/ValueWidgetSparklineZoom.test.tsx +765 -0
  299. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  300. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +98 -0
  301. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  302. package/Tests/Models/DatabaseModels/KubernetesAiAgentModel.test.ts +295 -0
  303. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +347 -0
  304. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  305. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  306. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  307. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +58 -0
  308. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +1 -1
  309. package/Tests/Server/API/AIInvestigationAPI.test.ts +123 -45
  310. package/Tests/Server/API/AutoRemediationApproveClusterRound.test.ts +1329 -0
  311. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +25 -2
  312. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  313. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  314. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -0
  315. package/Tests/Server/API/KubernetesClusterAiAccessAPIAgent.test.ts +903 -0
  316. package/Tests/Server/API/MicrosoftTeamsChatsRefreshRoute.test.ts +667 -0
  317. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +294 -6
  318. package/Tests/Server/API/ProxmoxResourceAPIRemoveNode.test.ts +1932 -0
  319. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  320. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  321. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  322. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  323. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  324. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  325. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  326. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  327. package/Tests/Server/Infrastructure/GlobalCache.test.ts +195 -0
  328. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  329. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  330. package/Tests/Server/Infrastructure/Postgres/AddKubernetesAiAgentAndAiDefaultsMigration.test.ts +623 -0
  331. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +87 -10
  332. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  333. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleScopeMigration.test.ts +622 -0
  334. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  335. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  336. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  337. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  338. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +61 -4
  339. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +98 -2
  340. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +105 -2
  341. package/Tests/Server/Services/AIServiceBalanceBlocker.test.ts +282 -0
  342. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  343. package/Tests/Server/Services/AlertMonitorClearWritesNull.test.ts +409 -0
  344. package/Tests/Server/Services/AlertMonitorEditFeed.test.ts +474 -0
  345. package/Tests/Server/Services/AlertMonitorEditGuard.test.ts +610 -0
  346. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +2 -0
  347. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +2 -1
  348. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  349. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  350. package/Tests/Server/Services/IncidentAffectedMonitorsEdit.test.ts +1360 -0
  351. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +3 -1
  352. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  353. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  354. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  355. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  356. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  357. package/Tests/Server/Services/IncidentSeverityDeletePausesComplianceRules.test.ts +488 -0
  358. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  359. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  360. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  361. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  362. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  363. package/Tests/Server/Services/KubernetesAiAgentJobService.test.ts +329 -0
  364. package/Tests/Server/Services/KubernetesAiAgentServiceHelpers.test.ts +618 -0
  365. package/Tests/Server/Services/KubernetesAiAgentServiceLifecycle.test.ts +1444 -0
  366. package/Tests/Server/Services/KubernetesAiAgentServiceRegister.test.ts +1666 -0
  367. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +140 -111
  368. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +267 -105
  369. package/Tests/Server/Services/KubernetesClusterAiAccessLegacySuperseded.test.ts +440 -0
  370. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +567 -411
  371. package/Tests/Server/Services/KubernetesClusterAiAccessTarget.test.ts +1168 -0
  372. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +410 -12
  373. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +55 -0
  374. package/Tests/Server/Services/ProjectServiceNewProjectAiDefaults.test.ts +288 -0
  375. package/Tests/Server/Services/ProxmoxResourcePostgres.test.ts +5633 -0
  376. package/Tests/Server/Services/ProxmoxResourceService.test.ts +6458 -5
  377. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  378. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  379. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +48 -15
  380. package/Tests/Server/Services/RunnerJobEnqueueKubectlAiAgent.test.ts +620 -0
  381. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +170 -3
  382. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +72 -0
  383. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +6 -4
  384. package/Tests/Server/Services/ScheduledMaintenanceAttachmentEdit.test.ts +2113 -0
  385. package/Tests/Server/Services/ScheduledMaintenanceMonitorRelease.test.ts +972 -0
  386. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  387. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  388. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  389. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  390. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  391. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  392. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  393. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  394. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  395. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  396. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  397. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  398. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  399. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  400. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  401. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  402. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +2237 -0
  403. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +3988 -138
  404. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  405. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  406. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  407. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +107 -1
  408. package/Tests/Server/Utils/AI/IncidentPostmortemRunner.test.ts +351 -0
  409. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +2 -0
  410. package/Tests/Server/Utils/AI/InvestigationEligibility.test.ts +310 -0
  411. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +70 -0
  412. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +58 -0
  413. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +2 -0
  414. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +2 -0
  415. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +2 -0
  416. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +125 -1
  417. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +252 -3
  418. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +2 -0
  419. package/Tests/Server/Utils/AI/RemediationExecutionRunnerProjectGates.test.ts +240 -0
  420. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +2 -0
  421. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +2 -0
  422. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +2 -0
  423. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +2 -0
  424. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  425. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  426. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  427. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  428. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  429. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  430. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  431. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  432. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  433. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  434. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  435. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  436. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  437. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  438. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  439. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  440. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  441. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  442. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  443. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  444. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  445. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  446. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  447. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  448. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  449. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  450. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  451. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  452. package/Tests/Server/Utils/Telemetry/HostAssetAttributesFromOtlp.test.ts +68 -0
  453. package/Tests/Server/Utils/Telemetry/InventoryMirrorPostgres.test.ts +370 -0
  454. package/Tests/Server/Utils/Telemetry/NetworkDeviceInventoryAttributes.test.ts +522 -0
  455. package/Tests/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.test.ts +155 -0
  456. package/Tests/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.test.ts +8631 -0
  457. package/Tests/Server/Utils/Telemetry/ProxmoxNativePush.test.ts +2852 -0
  458. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +367 -1
  459. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +45 -3
  460. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  461. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  462. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +25 -0
  463. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNames.test.ts +3119 -0
  464. package/Tests/Server/Utils/Workspace/MicrosoftTeamsGroupChatNamesPostgres.test.ts +1721 -0
  465. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  466. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  467. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  468. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  469. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  470. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  471. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +581 -113
  472. package/Tests/Types/Kubernetes/KubernetesAiAgentAccessContract.test.ts +510 -0
  473. package/Tests/Types/SafeHtml.test.ts +102 -0
  474. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  475. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  476. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  477. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  478. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  479. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  480. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  481. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  482. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  483. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  484. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  485. package/Tests/Types/Team/ComplianceRule.test.ts +1029 -0
  486. package/Tests/Types/Team/TeamComplianceStatus.test.ts +176 -0
  487. package/Tests/UI/Components/ActionButton/RowActions.test.tsx +641 -0
  488. package/Tests/UI/Components/ActionButton/RowActionsAccessibility.test.tsx +414 -0
  489. package/Tests/UI/Components/ActionButton/SplitActionButtons.test.ts +565 -0
  490. package/Tests/UI/Components/Charts/BarChartDragToSelect.test.tsx +386 -0
  491. package/Tests/UI/Components/Charts/ChartDoubleClickResetWithoutDblclick.test.tsx +1025 -0
  492. package/Tests/UI/Components/Charts/ChartGroupPageZoomHint.test.tsx +196 -0
  493. package/Tests/UI/Components/Charts/ChartPlaceholdersTakeDoubleClick.test.tsx +1861 -0
  494. package/Tests/UI/Components/Charts/ChartPlotClicksWhileResetArmed.test.tsx +382 -0
  495. package/Tests/UI/Components/Charts/ChartRangeSelection.test.tsx +704 -0
  496. package/Tests/UI/Components/Charts/ChartRangeSelectionDoubleClickReset.test.tsx +1481 -0
  497. package/Tests/UI/Components/Charts/ChartRangeSelectionRealRecharts.test.tsx +918 -0
  498. package/Tests/UI/Components/Charts/ChartRowBucketStart.test.ts +350 -0
  499. package/Tests/UI/Components/Charts/DoubleClickReset.test.tsx +996 -0
  500. package/Tests/UI/Components/Charts/HistogramDoubleClickResetRealRecharts.test.tsx +965 -0
  501. package/Tests/UI/Components/Charts/HistogramDriftThenRefetch.test.tsx +277 -0
  502. package/Tests/UI/Components/Charts/HistogramRangeSelectionDataChange.test.tsx +1757 -0
  503. package/Tests/UI/Components/Charts/HistogramRangeSelectionRealRecharts.test.tsx +560 -0
  504. package/Tests/UI/Components/Charts/TimeRangeZoom/ChartPageTimeRangeZoom.test.tsx +513 -0
  505. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.test.tsx +845 -0
  506. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.test.tsx +119 -0
  507. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHintTailwind.test.tsx +274 -0
  508. package/Tests/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.test.ts +433 -0
  509. package/Tests/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.test.tsx +863 -0
  510. package/Tests/UI/Components/Charts/TimeRangeZoom/ZoomDocsPromises.test.tsx +1147 -0
  511. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  512. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  513. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  514. package/Tests/UI/Components/DisabledButtonTooltip.test.tsx +14 -3
  515. package/Tests/UI/Components/EntityDropdownEscape.test.tsx +454 -0
  516. package/Tests/UI/Components/EntityDropdownFocusLeave.test.tsx +420 -0
  517. package/Tests/UI/Components/Forms/FormFieldDropdownDisabled.test.tsx +233 -0
  518. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  519. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  520. package/Tests/UI/Components/Input.test.tsx +80 -1
  521. package/Tests/UI/Components/LogsAnalyticsViewZoom.test.tsx +947 -0
  522. package/Tests/UI/Components/LogsHistogramDragTooltip.test.tsx +17 -5
  523. package/Tests/UI/Components/LogsViewerAnalyticsZoom.test.tsx +501 -0
  524. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +1 -1
  525. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +6 -4
  526. package/Tests/UI/Components/ModelTable/BaseModelTablePermissionGating.test.tsx +60 -8
  527. package/Tests/UI/Components/ModelTable/BaseModelTableRowActions.test.tsx +476 -0
  528. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  529. package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +7 -5
  530. package/Tests/UI/Components/MoreMenuPortal.test.tsx +623 -0
  531. package/Tests/UI/Components/StatusPage/ResourceGridActions.test.tsx +538 -0
  532. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +14 -4
  533. package/Tests/UI/Components/TableCellWrapping.test.tsx +3 -3
  534. package/Tests/UI/Components/TableRowActions.test.tsx +472 -0
  535. package/Tests/UI/Components/TelemetryHistogramDragTooltip.test.tsx +1 -1
  536. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +1 -1
  537. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerZoomScope.test.tsx +470 -0
  538. package/Tests/UI/Components/TelemetryViewer/UseViewerTimeRangeZoom.test.tsx +571 -0
  539. package/Tests/UI/Components/TelemetryViewer/ViewerDoubleClickResetUnderDataChange.test.tsx +928 -0
  540. package/Tests/UI/Components/TelemetryViewer/ViewerFollowsEnclosingZoom.test.tsx +1569 -0
  541. package/Tests/UI/Components/Toggle.test.tsx +66 -0
  542. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardRefreshNames.test.tsx +1023 -0
  543. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  544. package/Tests/UI/Rum/ChunkLoader.test.ts +201 -0
  545. package/Tests/UI/Rum/FidelityNotices.test.ts +54 -0
  546. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +72 -0
  547. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +1009 -9
  548. package/Tests/UI/Rum/ReplayRecordedAssets.test.ts +1069 -0
  549. package/Tests/UI/Rum/ReplayStage.test.tsx +913 -2
  550. package/Tests/UI/Rum/SessionReplayTable.test.tsx +443 -8
  551. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +439 -9
  552. package/Tests/UI/Rum/UserFlowTablesRowActions.test.tsx +501 -0
  553. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +47 -19
  554. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +2 -0
  555. package/Tests/Utils/AiRemediation/KubernetesAiAgentPolicyCopyParity.test.ts +396 -0
  556. package/Tests/Utils/Dashboard/DashboardTimeRangeZoom.test.ts +104 -1
  557. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  558. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  559. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  560. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  561. package/Types/AI/AIRunType.ts +9 -4
  562. package/Types/AI/InvestigationNotStartedReason.ts +6 -0
  563. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +14 -16
  564. package/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.ts +28 -0
  565. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  566. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  567. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  568. package/Types/CustomField/CustomFieldType.ts +14 -0
  569. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  570. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  571. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  572. package/Types/Dashboard/DashboardComponents/DashboardTraceChartComponent.ts +4 -2
  573. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +263 -32
  574. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +20 -16
  575. package/Types/Kubernetes/KubernetesClusterAiInsights.ts +81 -0
  576. package/Types/Monitor/ProxmoxAlertTemplates.ts +4 -4
  577. package/Types/SafeHtml.ts +93 -0
  578. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  579. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  580. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  581. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  582. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  583. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  584. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  585. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  586. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  587. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  588. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  589. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  590. package/Types/Team/ComplianceNotificationChannel.ts +22 -0
  591. package/Types/Team/ComplianceRule.ts +434 -0
  592. package/Types/Team/ComplianceRuleType.ts +21 -0
  593. package/Types/Team/TeamComplianceStatus.ts +81 -0
  594. package/Types/Telemetry/TelemetryIngestSurface.ts +28 -0
  595. package/UI/Components/ActionButton/ActionButtonSchema.ts +21 -0
  596. package/UI/Components/ActionButton/RowActions.tsx +238 -0
  597. package/UI/Components/ActionButton/SplitActionButtons.ts +173 -0
  598. package/UI/Components/Charts/Area/AreaChart.tsx +32 -2
  599. package/UI/Components/Charts/Bar/BarChart.tsx +40 -3
  600. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +37 -11
  601. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +80 -243
  602. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +122 -31
  603. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +95 -244
  604. package/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.ts +214 -0
  605. package/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.ts +32 -0
  606. package/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.ts +599 -0
  607. package/UI/Components/Charts/Line/LineChart.tsx +32 -2
  608. package/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.tsx +70 -0
  609. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.tsx +226 -0
  610. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.tsx +73 -0
  611. package/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.ts +163 -0
  612. package/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.ts +215 -0
  613. package/UI/Components/Charts/Utils/DataPoint.ts +20 -8
  614. package/UI/Components/Charts/Utils/XAxis.ts +75 -0
  615. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +493 -59
  616. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  617. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  618. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  619. package/UI/Components/EntityDropdown/EntityDropdown.tsx +107 -11
  620. package/UI/Components/Forms/Fields/FormField.tsx +10 -0
  621. package/UI/Components/Forms/ModelForm.tsx +70 -8
  622. package/UI/Components/Forms/Validation.ts +1 -1
  623. package/UI/Components/Input/Input.tsx +90 -57
  624. package/UI/Components/List/ListRow.tsx +8 -74
  625. package/UI/Components/LogsViewer/LogsViewer.tsx +40 -11
  626. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +22 -6
  627. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +209 -6
  628. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +127 -84
  629. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +3 -0
  630. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  631. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  632. package/UI/Components/ModelTable/BaseModelTable.tsx +21 -9
  633. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  634. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  635. package/UI/Components/MoreMenu/MoreMenu.tsx +365 -30
  636. package/UI/Components/MoreMenu/MoreMenuItem.tsx +70 -17
  637. package/UI/Components/OrderedStatesList/Item.tsx +7 -72
  638. package/UI/Components/StatusPage/ResourceGrid.tsx +126 -77
  639. package/UI/Components/Table/TableBody.tsx +8 -2
  640. package/UI/Components/Table/TableRow.tsx +20 -140
  641. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +41 -10
  642. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +47 -9
  643. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +25 -6
  644. package/UI/Components/TelemetryViewer/types.ts +2 -0
  645. package/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.ts +448 -0
  646. package/UI/Components/Toggle/Toggle.tsx +26 -0
  647. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  648. package/UI/Styles/Theme.css +14 -2
  649. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  650. package/Utils/API.ts +247 -2
  651. package/Utils/AiRemediation/CommandPolicy.ts +1 -1
  652. package/Utils/AiRemediation/KubectlPolicy.ts +1 -1
  653. package/Utils/Dashboard/DashboardTimeRangeZoom.ts +46 -2
  654. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  655. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  656. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  657. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  658. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  659. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  660. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  661. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  662. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  663. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  664. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  665. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  666. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  667. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js +385 -0
  668. package/build/dist/Models/DatabaseModels/KubernetesAiAgent.js.map +1 -0
  669. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +3 -3
  670. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  671. package/build/dist/Models/DatabaseModels/Project.js +56 -5
  672. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  673. package/build/dist/Models/DatabaseModels/ProxmoxResource.js +51 -1
  674. package/build/dist/Models/DatabaseModels/ProxmoxResource.js.map +1 -1
  675. package/build/dist/Models/DatabaseModels/RunnerJob.js +74 -0
  676. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  677. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  678. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  679. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  680. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  681. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +134 -3
  682. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  683. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  684. package/build/dist/Server/API/AIChatAPI.js +6 -1
  685. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  686. package/build/dist/Server/API/AIInvestigationAPI.js +17 -10
  687. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  688. package/build/dist/Server/API/AutoRemediationAPI.js +144 -24
  689. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  690. package/build/dist/Server/API/IncidentAPI.js +100 -1
  691. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  692. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +436 -22
  693. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -1
  694. package/build/dist/Server/API/MicrosoftTeamsAPI.js +69 -13
  695. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  696. package/build/dist/Server/API/ProxmoxResourceAPI.js +187 -14
  697. package/build/dist/Server/API/ProxmoxResourceAPI.js.map +1 -1
  698. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  699. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  700. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  701. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  702. package/build/dist/Server/Infrastructure/GlobalCache.js +27 -0
  703. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  704. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  705. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  706. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  707. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  708. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  709. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  710. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  711. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  712. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  713. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  714. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  715. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  716. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  717. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  718. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js +50 -0
  719. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796000000000-AddTeamComplianceRuleScope.js.map +1 -0
  720. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js +84 -0
  721. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796100000000-AddKubernetesAiAgentAndAiDefaults.js.map +1 -0
  722. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js +31 -0
  723. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796200000000-AddProxmoxResourceNativePushColumns.js.map +1 -0
  724. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  725. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  726. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  727. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  728. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  729. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  730. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  731. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  732. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  733. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  734. package/build/dist/Server/Middleware/TelemetryIngest.js +34 -19
  735. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  736. package/build/dist/Server/Services/AIService.js +59 -1
  737. package/build/dist/Server/Services/AIService.js.map +1 -1
  738. package/build/dist/Server/Services/AlertService.js +248 -13
  739. package/build/dist/Server/Services/AlertService.js.map +1 -1
  740. package/build/dist/Server/Services/AlertSeverityService.js +51 -15
  741. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  742. package/build/dist/Server/Services/AutoRemediationRuleService.js +1 -1
  743. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  744. package/build/dist/Server/Services/DatabaseService.js +56 -7
  745. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  746. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  747. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  748. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  749. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  750. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  751. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  752. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  753. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  754. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  755. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  756. package/build/dist/Server/Services/IncidentService.js +1251 -72
  757. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  758. package/build/dist/Server/Services/IncidentSeverityService.js +51 -15
  759. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  760. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  761. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  762. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  763. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  764. package/build/dist/Server/Services/KubernetesAiAgentJobService.js +158 -0
  765. package/build/dist/Server/Services/KubernetesAiAgentJobService.js.map +1 -0
  766. package/build/dist/Server/Services/KubernetesAiAgentService.js +1145 -0
  767. package/build/dist/Server/Services/KubernetesAiAgentService.js.map +1 -0
  768. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +842 -371
  769. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -1
  770. package/build/dist/Server/Services/KubernetesClusterService.js +86 -17
  771. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  772. package/build/dist/Server/Services/ProjectService.js +22 -0
  773. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  774. package/build/dist/Server/Services/ProxmoxResourceService.js +258 -3
  775. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  776. package/build/dist/Server/Services/RunnerJobService.js +178 -92
  777. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  778. package/build/dist/Server/Services/RunnerService.js +1 -1
  779. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  780. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  781. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  782. package/build/dist/Server/Services/ScheduledMaintenanceService.js +582 -20
  783. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  784. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +316 -46
  785. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  786. package/build/dist/Server/Services/SmsService.js +2 -22
  787. package/build/dist/Server/Services/SmsService.js.map +1 -1
  788. package/build/dist/Server/Services/StatusPageService.js +47 -9
  789. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  790. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  791. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  792. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  793. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  794. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  795. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  796. package/build/dist/Server/Services/TeamComplianceSettingService.js +833 -7
  797. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  798. package/build/dist/Server/Types/Markdown.js +110 -6
  799. package/build/dist/Server/Types/Markdown.js.map +1 -1
  800. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  801. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  802. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +52 -9
  803. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -1
  804. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +13 -9
  805. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  806. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +7 -3
  807. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  808. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +29 -15
  809. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  810. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +56 -20
  811. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  812. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +39 -5
  813. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  814. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js +30 -3
  815. package/build/dist/Server/Utils/AI/SRE/IncidentPostmortemRunner.js.map +1 -1
  816. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js +13 -2
  817. package/build/dist/Server/Utils/AI/SRE/InvestigationEligibility.js.map +1 -1
  818. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +1 -1
  819. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +4 -2
  820. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  821. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  822. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  823. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  824. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  825. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  826. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  827. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  828. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  829. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  830. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  831. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  832. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  833. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  834. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  835. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  836. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  837. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  838. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  839. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  840. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  841. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  842. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  843. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  844. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  845. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  846. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  847. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  848. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  849. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  850. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  851. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  852. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  853. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  854. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  855. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  856. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  857. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  858. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  859. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  860. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  861. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  862. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  863. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  864. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  865. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +111 -6
  866. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  867. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +40 -4
  868. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  869. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js +296 -0
  870. package/build/dist/Server/Utils/Telemetry/ProxmoxNativeNodeLiveness.js.map +1 -0
  871. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js +699 -0
  872. package/build/dist/Server/Utils/Telemetry/ProxmoxNativePush.js.map +1 -0
  873. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +77 -19
  874. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  875. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  876. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  877. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  878. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  879. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  880. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +505 -42
  881. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  882. package/build/dist/Types/AI/AIRunType.js +9 -4
  883. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  884. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +10 -16
  885. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  886. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js +28 -0
  887. package/build/dist/Types/AutoRemediation/AiRemediationCommandPolicyVerdict.js.map +1 -0
  888. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  889. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  890. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  891. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  892. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  893. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  894. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  895. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  896. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  897. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  898. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  899. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  900. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  901. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  902. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +72 -16
  903. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  904. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +20 -16
  905. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -1
  906. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js +17 -0
  907. package/build/dist/Types/Kubernetes/KubernetesClusterAiInsights.js.map +1 -0
  908. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +4 -4
  909. package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
  910. package/build/dist/Types/SafeHtml.js +84 -0
  911. package/build/dist/Types/SafeHtml.js.map +1 -0
  912. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  913. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  914. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  915. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  916. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  917. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  918. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  919. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  920. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  921. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  922. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  923. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  924. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  925. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  926. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  927. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  928. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  929. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  930. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  931. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  932. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  933. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  934. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  935. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  936. package/build/dist/Types/Team/ComplianceNotificationChannel.js +23 -0
  937. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -0
  938. package/build/dist/Types/Team/ComplianceRule.js +304 -0
  939. package/build/dist/Types/Team/ComplianceRule.js.map +1 -0
  940. package/build/dist/Types/Team/ComplianceRuleType.js +21 -0
  941. package/build/dist/Types/Team/ComplianceRuleType.js.map +1 -1
  942. package/build/dist/Types/Team/TeamComplianceStatus.js +2 -0
  943. package/build/dist/Types/Team/TeamComplianceStatus.js.map +1 -0
  944. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +25 -0
  945. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  946. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js +20 -1
  947. package/build/dist/UI/Components/ActionButton/ActionButtonSchema.js.map +1 -1
  948. package/build/dist/UI/Components/ActionButton/RowActions.js +113 -0
  949. package/build/dist/UI/Components/ActionButton/RowActions.js.map +1 -0
  950. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js +71 -0
  951. package/build/dist/UI/Components/ActionButton/SplitActionButtons.js.map +1 -0
  952. package/build/dist/UI/Components/Charts/Area/AreaChart.js +16 -1
  953. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  954. package/build/dist/UI/Components/Charts/Bar/BarChart.js +16 -1
  955. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  956. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +27 -5
  957. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  958. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +61 -194
  959. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  960. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +84 -26
  961. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  962. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +75 -196
  963. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  964. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js +133 -0
  965. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/DoubleClick.js.map +1 -1
  966. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js +26 -0
  967. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartBucketsChange.js.map +1 -0
  968. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js +371 -0
  969. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/UseChartRangeSelection.js.map +1 -0
  970. package/build/dist/UI/Components/Charts/Line/LineChart.js +16 -1
  971. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  972. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js +35 -0
  973. package/build/dist/UI/Components/Charts/TimeRangeZoom/ResetTimeRangeZoomButton.js.map +1 -0
  974. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js +107 -0
  975. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomContext.js.map +1 -0
  976. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js +41 -0
  977. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomHint.js.map +1 -0
  978. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js +117 -0
  979. package/build/dist/UI/Components/Charts/TimeRangeZoom/TimeRangeZoomUtil.js.map +1 -0
  980. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js +121 -0
  981. package/build/dist/UI/Components/Charts/TimeRangeZoom/UseTimeRangeZoom.js.map +1 -0
  982. package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -7
  983. package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
  984. package/build/dist/UI/Components/Charts/Utils/XAxis.js +70 -0
  985. package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
  986. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +305 -38
  987. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -1
  988. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  989. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  990. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  991. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  992. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  993. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  994. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +89 -14
  995. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  996. package/build/dist/UI/Components/Forms/Fields/FormField.js +11 -2
  997. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  998. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  999. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  1000. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  1001. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  1002. package/build/dist/UI/Components/Input/Input.js +69 -54
  1003. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  1004. package/build/dist/UI/Components/List/ListRow.js +4 -39
  1005. package/build/dist/UI/Components/List/ListRow.js.map +1 -1
  1006. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +31 -8
  1007. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1008. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +10 -1
  1009. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  1010. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +139 -21
  1011. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  1012. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +30 -8
  1013. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  1014. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
  1015. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  1016. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  1017. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  1018. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +12 -2
  1019. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1020. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  1021. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  1022. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  1023. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  1024. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +253 -14
  1025. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  1026. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +43 -13
  1027. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  1028. package/build/dist/UI/Components/OrderedStatesList/Item.js +2 -38
  1029. package/build/dist/UI/Components/OrderedStatesList/Item.js.map +1 -1
  1030. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +53 -15
  1031. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  1032. package/build/dist/UI/Components/Table/TableBody.js +6 -2
  1033. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  1034. package/build/dist/UI/Components/Table/TableRow.js +12 -75
  1035. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1036. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +32 -7
  1037. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1038. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +27 -9
  1039. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  1040. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +11 -1
  1041. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  1042. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js +272 -0
  1043. package/build/dist/UI/Components/TelemetryViewer/useViewerTimeRangeZoom.js.map +1 -0
  1044. package/build/dist/UI/Components/Toggle/Toggle.js +14 -1
  1045. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  1046. package/build/dist/UI/Components/Tooltip/Tooltip.js +1 -1
  1047. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  1048. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  1049. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  1050. package/build/dist/Utils/API.js +177 -1
  1051. package/build/dist/Utils/API.js.map +1 -1
  1052. package/build/dist/Utils/AiRemediation/CommandPolicy.js +1 -1
  1053. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -1
  1054. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +1 -1
  1055. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -1
  1056. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js +27 -1
  1057. package/build/dist/Utils/Dashboard/DashboardTimeRangeZoom.js.map +1 -1
  1058. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  1059. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  1060. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  1061. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  1062. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  1063. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  1064. package/package.json +1 -1
@@ -1,4 +1,9 @@
1
1
  import KubernetesClusterAiAccessService, {
2
+ AI_AGENT_INSTALL_COMMAND,
3
+ AI_AGENT_NOT_CONNECTED_NEXT_STEP,
4
+ AI_BALANCE_INSUFFICIENT_NEXT_STEP,
5
+ CREDENTIAL_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
6
+ LEGACY_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
2
7
  KubernetesAgentRegistrationRefusedException,
3
8
  KubernetesClusterAiAccessProjectGates,
4
9
  MAX_KUBERNETES_AGENT_RUNNERS_PER_PROJECT,
@@ -9,6 +14,8 @@ import KubernetesClusterAiAccessService, {
9
14
  getKubernetesAgentRunnerNameForCluster,
10
15
  getPreviousInstanceRetryAfterSeconds,
11
16
  } from "../../../Server/Services/KubernetesClusterAiAccessService";
17
+ import AIService from "../../../Server/Services/AIService";
18
+ import KubernetesAiAgentService from "../../../Server/Services/KubernetesAiAgentService";
12
19
  import KubernetesClusterFeedService from "../../../Server/Services/KubernetesClusterFeedService";
13
20
  import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
14
21
  import LlmProviderService from "../../../Server/Services/LlmProviderService";
@@ -17,6 +24,7 @@ import RunbookCredentialService from "../../../Server/Services/RunbookCredential
17
24
  import RunbookSecretService from "../../../Server/Services/RunbookSecretService";
18
25
  import RunnerService from "../../../Server/Services/RunnerService";
19
26
  import logger from "../../../Server/Utils/Logger";
27
+ import KubernetesAiAgent from "../../../Models/DatabaseModels/KubernetesAiAgent";
20
28
  import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
21
29
  import LlmProvider from "../../../Models/DatabaseModels/LlmProvider";
22
30
  import Project from "../../../Models/DatabaseModels/Project";
@@ -133,6 +141,32 @@ function fakeRunner(overrides: Partial<Record<string, unknown>> = {}): Runner {
133
141
  } as unknown as Runner;
134
142
  }
135
143
 
144
+ const AGENT_ID: ObjectID = new ObjectID("77777777-7777-4777-8777-777777777777");
145
+
146
+ // This cluster's Kubernetes AI agent row, online and able to write.
147
+ function fakeAgent(
148
+ overrides: Partial<Record<string, unknown>> = {},
149
+ ): KubernetesAiAgent {
150
+ return {
151
+ id: AGENT_ID,
152
+ _id: AGENT_ID.toString(),
153
+ projectId: PROJECT_ID,
154
+ kubernetesClusterId: CLUSTER_ID,
155
+ connectionStatus: "connected",
156
+ lastAliveAt: OneUptimeDate.getCurrentDate(),
157
+ lastRegisteredAt: OneUptimeDate.getSomeMinutesAgo(60),
158
+ agentVersion: "14.1.0",
159
+ posture: {
160
+ inCluster: true,
161
+ allowWrites: true,
162
+ clusterIdentifier: "prod-us",
163
+ podNamespace: "oneuptime-agent",
164
+ kubectlVersion: "v1.36.4",
165
+ },
166
+ ...overrides,
167
+ } as unknown as KubernetesAiAgent;
168
+ }
169
+
136
170
  function gapCodes(
137
171
  status: KubernetesClusterAiAccessStatus,
138
172
  ): Array<KubernetesAiAccessGapCode> {
@@ -150,6 +184,7 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
150
184
  let credentialCountSpy: jest.SpyInstance;
151
185
  let secretCountSpy: jest.SpyInstance;
152
186
  let boundClusterCountSpy: jest.SpyInstance;
187
+ let agentLookup: jest.SpyInstance;
153
188
 
154
189
  beforeEach(() => {
155
190
  jest.spyOn(logger, "error").mockImplementation((): void => {
@@ -164,6 +199,10 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
164
199
  boundClusterCountSpy = jest
165
200
  .spyOn(KubernetesClusterService, "countBy")
166
201
  .mockResolvedValue(new PositiveNumber(0));
202
+ // No Kubernetes AI agent unless a test says otherwise.
203
+ agentLookup = jest
204
+ .spyOn(KubernetesAiAgentService, "findForCluster")
205
+ .mockResolvedValue(null);
167
206
  });
168
207
 
169
208
  afterEach(() => {
@@ -220,11 +259,11 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
220
259
  expect(status.runner?.posture?.allowNodeOperations).toBe(false);
221
260
  });
222
261
 
223
- it("reports no_runner_bound with the install step and blocks both when the cluster has no Runner and none is registered for it", async () => {
262
+ it("reports ai_agent_not_connected with the install command and blocks both when nothing can reach the cluster", async () => {
224
263
  /*
225
- * The status now looks for this cluster's agent Runner row before
226
- * choosing the next step (see the "installed but not selected" block
227
- * below); with none registered, the one-command install is the step.
264
+ * No binding and no agent row: nothing to read but the agent. The
265
+ * status no longer looks for an unbound previous in-cluster Runner —
266
+ * the chart upgrade that installs the agent is the one step.
228
267
  */
229
268
  const findOneBy: jest.SpyInstance = jest
230
269
  .spyOn(RunnerService, "findOneBy")
@@ -236,17 +275,44 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
236
275
  gates: READY_GATES,
237
276
  });
238
277
 
239
- expect(findOneBy).toHaveBeenCalledTimes(1);
240
- expect(gapCodes(status)).toEqual(["no_runner_bound"]);
278
+ expect(findOneBy).not.toHaveBeenCalled();
279
+ expect(agentLookup).toHaveBeenCalledWith({
280
+ projectId: PROJECT_ID,
281
+ kubernetesClusterId: CLUSTER_ID,
282
+ });
283
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
241
284
  expect(status.gaps[0]?.blocks).toBe("both");
242
- expect(status.gaps[0]?.title).toBe("No Runner can reach this cluster");
243
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
285
+ expect(status.gaps[0]?.title).toBe(
286
+ "The Kubernetes AI agent is not connected",
287
+ );
288
+ expect(status.gaps[0]?.nextStep).toBe(AI_AGENT_NOT_CONNECTED_NEXT_STEP);
289
+ expect(status.gaps[0]?.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
290
+ expect(status.gaps[0]?.nextStep).not.toContain("aiAccess");
291
+ // Nothing selected, so nothing said about an ignored Runner.
292
+ expect(status.gaps[0]?.description).toBe(
293
+ "OneUptime AI runs kubectl on this cluster through the Kubernetes AI agent, and no agent has connected for this cluster yet.",
294
+ );
244
295
  expect(status.isInvestigationReady).toBe(false);
245
296
  expect(status.isRemediationReady).toBe(false);
246
297
  expect(status.runner).toBeNull();
298
+ expect(status.aiAgent).toBeNull();
247
299
  expect(status.accessMethod).toBe("none");
248
300
  });
249
301
 
302
+ it("never produces no_runner_bound any more", async () => {
303
+ for (const aiAccessRunnerId of [undefined, RUNNER_ID]) {
304
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(null);
305
+
306
+ const status: KubernetesClusterAiAccessStatus =
307
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
308
+ cluster: fakeCluster({ aiAccessRunnerId }),
309
+ gates: READY_GATES,
310
+ });
311
+
312
+ expect(gapCodes(status)).not.toContain("no_runner_bound");
313
+ }
314
+ });
315
+
250
316
  /*
251
317
  * The FK on aiAccessRunnerId is ON DELETE SET NULL, so deleting the bound
252
318
  * Runner clears the binding in the same statement: the only way to see a
@@ -265,25 +331,37 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
265
331
  expect(gapCodes(status)).toEqual(["runner_missing"]);
266
332
  expect(status.gaps[0]?.title).toBe("The bound Runner was just deleted");
267
333
  /*
268
- * Round three: gap next steps are shown verbatim on incident pages too,
269
- * where "this page" would be the incident. They name the cluster's AI
270
- * page instead (was "Reload this page").
334
+ * Gap next steps are shown verbatim on incident pages too, where "this
335
+ * page" would be the incident. They name the cluster's AI agent page.
271
336
  */
272
- expect(status.gaps[0]?.nextStep).toContain("Reload the cluster's AI page");
337
+ expect(status.gaps[0]?.nextStep).toBe(
338
+ "Reload the cluster's AI agent page (AI → Agent).",
339
+ );
273
340
  expect(status.gaps[0]?.nextStep).not.toContain("this page");
274
341
  expect(status.isInvestigationReady).toBe(false);
275
342
  });
276
343
 
344
+ it("negative control: a bound Runner that vanished while the cluster has an agent reads as the agent", async () => {
345
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(null);
346
+ agentLookup.mockResolvedValue(fakeAgent());
347
+
348
+ const status: KubernetesClusterAiAccessStatus =
349
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
350
+ cluster: fakeCluster(),
351
+ gates: READY_GATES,
352
+ });
353
+
354
+ expect(gapCodes(status)).toEqual([]);
355
+ expect(status.runner?.kind).toBe("ai_agent");
356
+ });
357
+
277
358
  /*
278
- * A cluster left unbound while its agent Runner already exists (the
279
- * binding was cleared, its Runner was deleted and re-registered, or the
280
- * cluster row was recreated by telemetry). Re-running the helm upgrade
281
- * would change nothing there — a registering Runner never re-binds a
282
- * cluster that was configured before — so the step that works is
283
- * selecting the Runner on the AI page.
359
+ * A previous in-cluster Runner that is registered but not bound (its
360
+ * binding was cleared, or it was deleted and registered again) is not an
361
+ * access target: only a bound Runner is. The cluster uses its AI agent.
284
362
  */
285
- describe("no Runner bound, but this cluster's agent Runner is registered", () => {
286
- it("tells the operator to select that Runner on the cluster's AI page, not to run helm", async () => {
363
+ describe("an unbound previous in-cluster Runner", () => {
364
+ it("is not looked up or used: the install step is the next step", async () => {
287
365
  const findOneBy: jest.SpyInstance = jest
288
366
  .spyOn(RunnerService, "findOneBy")
289
367
  .mockResolvedValue(fakeRunner());
@@ -294,56 +372,13 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
294
372
  gates: READY_GATES,
295
373
  });
296
374
 
297
- expect(gapCodes(status)).toEqual(["no_runner_bound"]);
298
- expect(status.gaps[0]?.title).toBe(
299
- "The in-cluster Runner is installed but not selected",
300
- );
301
- expect(status.gaps[0]?.description).toContain("and online");
302
- expect(status.gaps[0]?.nextStep).toContain(
303
- 'Select the kubernetes-agent Runner "kubernetes-agent/prod-us"',
304
- );
305
- // Round three: named, not "on this page" (shown on incident pages too).
306
- expect(status.gaps[0]?.nextStep).toContain("on the cluster's AI page");
307
- expect(status.gaps[0]?.nextStep).not.toContain("this page");
308
- expect(status.gaps[0]?.nextStep).not.toContain("aiAccess.enabled=true");
309
- expect(status.gaps[0]?.blocks).toBe("both");
310
- expect(status.isInvestigationReady).toBe(false);
375
+ expect(findOneBy).not.toHaveBeenCalled();
376
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
311
377
  expect(status.runner).toBeNull();
312
-
313
- // Looked up in this project, by the agent name, case-insensitively.
314
- const query: Record<string, unknown> = (
315
- findOneBy.mock.calls[0]![0] as { query: Record<string, unknown> }
316
- ).query;
317
- expect(query["projectId"]).toBe(PROJECT_ID);
318
- expect(typeof query["name"]).not.toBe("string");
319
- expect(query["name"]).toBeDefined();
320
- });
321
-
322
- it("says the Runner is not online when it is not, and still points at selecting it", async () => {
323
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
324
- fakeRunner({
325
- lastAlive: OneUptimeDate.getSomeMinutesAgo(30),
326
- }),
327
- );
328
-
329
- const status: KubernetesClusterAiAccessStatus =
330
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
331
- cluster: fakeCluster({ aiAccessRunnerId: undefined }),
332
- gates: READY_GATES,
333
- });
334
-
335
- expect(status.gaps[0]?.description).toContain("not online right now");
336
- expect(status.gaps[0]?.nextStep).toContain("Select the kubernetes-agent");
337
378
  });
338
379
 
339
- it("keeps the install step when the row by that name reports a different cluster", async () => {
340
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
341
- fakeRunner({
342
- hostInfo: {
343
- kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
344
- },
345
- }),
346
- );
380
+ it("never throws: a failed agent lookup reads as no agent", async () => {
381
+ agentLookup.mockRejectedValue(new Error("db down"));
347
382
 
348
383
  const status: KubernetesClusterAiAccessStatus =
349
384
  await KubernetesClusterAiAccessService.getStatusForClusterModel({
@@ -351,52 +386,32 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
351
386
  gates: READY_GATES,
352
387
  });
353
388
 
354
- expect(status.gaps[0]?.title).toBe("No Runner can reach this cluster");
355
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
389
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
390
+ expect(status.aiAgent).toBeNull();
356
391
  });
357
392
 
358
- it("never throws: a failed lookup falls back to the install step", async () => {
359
- jest
360
- .spyOn(RunnerService, "findOneBy")
361
- .mockRejectedValue(new Error("db down"));
362
-
363
- const status: KubernetesClusterAiAccessStatus =
364
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
365
- cluster: fakeCluster({ aiAccessRunnerId: undefined }),
366
- gates: READY_GATES,
367
- });
368
-
369
- expect(gapCodes(status)).toEqual(["no_runner_bound"]);
370
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
371
- });
372
-
373
- it("does not look anything up for a cluster row without an identifier", async () => {
374
- const findOneBy: jest.SpyInstance = jest.spyOn(
375
- RunnerService,
376
- "findOneBy",
377
- );
393
+ it("never throws: with a failed agent lookup a bound, online previous Runner still serves", async () => {
394
+ agentLookup.mockRejectedValue(new Error("db down"));
395
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
378
396
 
379
397
  const status: KubernetesClusterAiAccessStatus =
380
398
  await KubernetesClusterAiAccessService.getStatusForClusterModel({
381
- cluster: fakeCluster({
382
- aiAccessRunnerId: undefined,
383
- clusterIdentifier: " ",
384
- }),
399
+ cluster: fakeCluster(),
385
400
  gates: READY_GATES,
386
401
  });
387
402
 
388
- expect(findOneBy).not.toHaveBeenCalled();
389
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
403
+ expect(gapCodes(status)).toEqual([]);
404
+ expect(status.runner?.kind).toBe("runner");
390
405
  });
391
406
  });
392
407
 
393
408
  /*
394
409
  * A Runner that signs off (/runner-ingest/disconnect — what a helm
395
- * uninstall or turning aiAccess off does to the agent's pod) is offline
396
- * at once, not "Connected" until its last heartbeat ages out.
410
+ * upgrade or uninstall does to the previous in-cluster Runner's pod) is
411
+ * offline at once, not "Connected" until its last heartbeat ages out.
397
412
  */
398
413
  describe("a Runner that signed off", () => {
399
- it("is offline and blocks both, with the uninstall-aware copy and not the 'check the pod' step", async () => {
414
+ it("the previous in-cluster Runner: offline, blocks both, and the step is the chart upgrade to the AI agent", async () => {
400
415
  const findOneBy: jest.SpyInstance = jest
401
416
  .spyOn(RunnerService, "findOneBy")
402
417
  .mockResolvedValue(
@@ -413,16 +428,18 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
413
428
  });
414
429
 
415
430
  expect(status.runner?.isOnline).toBe(false);
431
+ expect(status.runner?.kind).toBe("runner");
416
432
  expect(status.isInvestigationReady).toBe(false);
417
433
  expect(status.isRemediationReady).toBe(false);
418
434
  expect(gapCodes(status)).toEqual(["runner_offline"]);
419
- expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
420
- expect(status.gaps[0]?.description).toContain("uninstalled");
421
- // Round three: was "clear the Runner on this page".
435
+ expect(status.gaps[0]?.title).toBe(
436
+ "The previous in-cluster Runner signed off",
437
+ );
422
438
  expect(status.gaps[0]?.nextStep).toContain(
423
- "clear the Runner on the cluster's AI page",
439
+ "Upgrade the Kubernetes agent chart: the Kubernetes AI agent replaces this Runner",
424
440
  );
425
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
441
+ expect(status.gaps[0]?.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
442
+ expect(status.gaps[0]?.nextStep).not.toContain("aiAccess.enabled=true");
426
443
  expect(status.gaps[0]?.nextStep).not.toContain("component=ai-runner");
427
444
 
428
445
  // The sign-off is read, not guessed.
@@ -450,6 +467,9 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
450
467
  expect(status.gaps[0]?.code).toBe("runner_offline");
451
468
  expect(status.gaps[0]?.title).toBe("The Runner signed off");
452
469
  expect(status.gaps[0]?.nextStep).toContain("Start the Runner container");
470
+ expect(status.gaps[0]?.nextStep).toContain(
471
+ "clear the Runner on the cluster's AI agent page (AI → Agent) to use the Kubernetes AI agent",
472
+ );
453
473
  });
454
474
 
455
475
  it("negative control: the same Runner Connected is online and ready", async () => {
@@ -475,6 +495,8 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
475
495
  it("negative control: a row that never heartbeated is 'never connected', not 'signed off'", async () => {
476
496
  jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
477
497
  fakeRunner({
498
+ name: "office-runner",
499
+ hostInfo: {},
478
500
  lastAlive: undefined,
479
501
  connectionStatus: RunnerConnectionStatus.Disconnected,
480
502
  }),
@@ -489,152 +511,51 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
489
511
  expect(status.gaps[0]?.title).toBe("The Runner has never connected");
490
512
  });
491
513
 
492
- /*
493
- * A restarted agent pod cannot present the key its predecessor held, so
494
- * a row holding more than the agent defaults is refused on every
495
- * registration — "it reconnects within a minute" would be false.
496
- */
497
- describe("an offline agent Runner that holds more than the defaults", () => {
498
- it('says it cannot re-register and names "Runs Runbooks" instead of promising a reconnect', async () => {
499
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
500
- fakeRunner({
501
- connectionStatus: RunnerConnectionStatus.Disconnected,
502
- canRunRunbooks: true,
503
- }),
504
- );
505
-
506
- const status: KubernetesClusterAiAccessStatus =
507
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
508
- cluster: fakeCluster(),
509
- gates: READY_GATES,
510
- });
511
-
512
- expect(gapCodes(status)).toEqual(["runner_offline"]);
513
- const gap: KubernetesAiAccessGap = status.gaps[0]!;
514
- expect(gap.title).toContain("cannot re-register");
515
- expect(gap.description).toContain('"Runs Runbooks" is on');
516
- expect(gap.nextStep).toContain(
517
- 'on Runner "kubernetes-agent/prod-us": turn off "Runs Runbooks"',
518
- );
519
- expect(gap.nextStep).toContain("delete the Runner");
520
- /*
521
- * Round four: deleting the Runner leaves this cluster unbound (the
522
- * FK nulls the binding and registration never re-binds a cluster
523
- * that had a Runner), so the delete path names its second step.
524
- * Was "... — or delete the Runner, and the in-cluster Runner
525
- * registers a fresh one within a minute."
526
- */
527
- expect(gap.nextStep).toContain(
528
- "select it on the cluster's AI page as its Runner",
529
- );
530
- expect(gap.nextStep).toContain("still bound to this cluster");
531
- expect(gap.nextStep).not.toContain("reconnects within a minute");
532
- expect(gap.nextStep).not.toContain("aiAccess.enabled=true");
533
- expect(gap.blocks).toBe("both");
534
- });
535
-
536
- it("names assigned credentials, secrets and other clusters bound to it, counted for THIS cluster", async () => {
537
- credentialCountSpy.mockResolvedValue(new PositiveNumber(1));
538
- secretCountSpy.mockResolvedValue(new PositiveNumber(2));
539
- boundClusterCountSpy.mockResolvedValue(new PositiveNumber(1));
540
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
541
- fakeRunner({
542
- lastAlive: OneUptimeDate.getSomeMinutesAgo(30),
543
- }),
544
- );
545
-
546
- const status: KubernetesClusterAiAccessStatus =
547
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
548
- cluster: fakeCluster(),
549
- gates: READY_GATES,
550
- });
551
-
552
- const gap: KubernetesAiAccessGap = status.gaps[0]!;
553
- expect(gap.description).toContain("1 Runner credential(s) assigned");
554
- expect(gap.description).toContain("2 runbook secret(s) assigned");
555
- expect(gap.description).toContain(
556
- "the AI access Runner of 1 other cluster(s)",
557
- );
558
- // "Runs Runbooks" is off on this row, so it is not named.
559
- expect(gap.description).not.toContain("Runs Runbooks");
560
-
561
- const clusterQuery: Record<string, unknown> = (
562
- boundClusterCountSpy.mock.calls[0]![0] as {
563
- query: Record<string, unknown>;
564
- }
565
- ).query;
566
- expect(clusterQuery["_id"]).toBeDefined();
567
- expect(typeof clusterQuery["_id"]).not.toBe("string");
568
- });
569
-
570
- it("negative control: a signed-off agent row with nothing extra keeps the reconnect wording", async () => {
571
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
572
- fakeRunner({
573
- connectionStatus: RunnerConnectionStatus.Disconnected,
574
- }),
575
- );
576
-
577
- const status: KubernetesClusterAiAccessStatus =
578
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
579
- cluster: fakeCluster(),
580
- gates: READY_GATES,
581
- });
582
-
583
- expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
584
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
585
- });
586
-
587
- it("negative control: an online agent row is never counted", async () => {
588
- jest
589
- .spyOn(RunnerService, "findOneBy")
590
- .mockResolvedValue(fakeRunner({ canRunRunbooks: true }));
514
+ it("negative control: a previous in-cluster Runner that never heartbeated is offline, not 'signed off'", async () => {
515
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
516
+ fakeRunner({
517
+ lastAlive: undefined,
518
+ connectionStatus: RunnerConnectionStatus.Disconnected,
519
+ }),
520
+ );
591
521
 
522
+ const status: KubernetesClusterAiAccessStatus =
592
523
  await KubernetesClusterAiAccessService.getStatusForClusterModel({
593
524
  cluster: fakeCluster(),
594
525
  gates: READY_GATES,
595
526
  });
596
527
 
597
- expect(credentialCountSpy).not.toHaveBeenCalled();
598
- });
599
-
600
- it("negative control: another cluster's agent row is not judged by this cluster's holdings", async () => {
601
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
602
- fakeRunner({
603
- name: "kubernetes-agent/prod-eu",
604
- connectionStatus: RunnerConnectionStatus.Disconnected,
605
- canRunRunbooks: true,
606
- hostInfo: {
607
- kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
608
- },
609
- }),
610
- );
611
-
612
- const status: KubernetesClusterAiAccessStatus =
613
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
614
- cluster: fakeCluster(),
615
- gates: READY_GATES,
616
- });
617
-
618
- expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
619
- expect(credentialCountSpy).not.toHaveBeenCalled();
620
- });
528
+ expect(status.gaps[0]?.title).toBe(
529
+ "The previous in-cluster Runner is offline",
530
+ );
531
+ });
621
532
 
622
- it("never throws: a failed holdings lookup falls back to the plain offline gap", async () => {
623
- credentialCountSpy.mockRejectedValue(new Error("db down"));
624
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
625
- fakeRunner({
626
- connectionStatus: RunnerConnectionStatus.Disconnected,
627
- }),
628
- );
533
+ /*
534
+ * An offline previous in-cluster Runner that holds more than the agent
535
+ * defaults could not re-register — but it no longer has to: the chart
536
+ * upgrade installs the AI agent, which needs none of it. So the status
537
+ * no longer counts what the row holds.
538
+ */
539
+ it("never counts what an offline previous in-cluster Runner holds: the step is the chart upgrade", async () => {
540
+ credentialCountSpy.mockResolvedValue(new PositiveNumber(1));
541
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
542
+ fakeRunner({
543
+ connectionStatus: RunnerConnectionStatus.Disconnected,
544
+ canRunRunbooks: true,
545
+ }),
546
+ );
629
547
 
630
- const status: KubernetesClusterAiAccessStatus =
631
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
632
- cluster: fakeCluster(),
633
- gates: READY_GATES,
634
- });
548
+ const status: KubernetesClusterAiAccessStatus =
549
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
550
+ cluster: fakeCluster(),
551
+ gates: READY_GATES,
552
+ });
635
553
 
636
- expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
637
- });
554
+ expect(gapCodes(status)).toEqual(["runner_offline"]);
555
+ expect(status.gaps[0]?.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
556
+ expect(credentialCountSpy).not.toHaveBeenCalled();
557
+ expect(secretCountSpy).not.toHaveBeenCalled();
558
+ expect(boundClusterCountSpy).not.toHaveBeenCalled();
638
559
  });
639
560
 
640
561
  it("still reports the sign-off once the last heartbeat has also aged out", async () => {
@@ -652,15 +573,21 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
652
573
  });
653
574
 
654
575
  // Signed off AND stale: the sign-off is the more precise explanation.
655
- expect(status.gaps[0]?.title).toBe("The in-cluster Runner signed off");
576
+ expect(status.gaps[0]?.title).toBe(
577
+ "The previous in-cluster Runner signed off",
578
+ );
656
579
  expect(status.runner?.isOnline).toBe(false);
657
580
  });
658
581
  });
659
582
 
660
583
  it("reports runner_offline (never connected vs stale) and keeps the Runner summary", async () => {
661
- jest
662
- .spyOn(RunnerService, "findOneBy")
663
- .mockResolvedValue(fakeRunner({ lastAlive: undefined }));
584
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
585
+ fakeRunner({
586
+ name: "office-runner",
587
+ hostInfo: {},
588
+ lastAlive: undefined,
589
+ }),
590
+ );
664
591
 
665
592
  const never: KubernetesClusterAiAccessStatus =
666
593
  await KubernetesClusterAiAccessService.getStatusForClusterModel({
@@ -668,11 +595,10 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
668
595
  gates: READY_GATES,
669
596
  });
670
597
 
671
- expect(gapCodes(never)).toEqual(["runner_offline"]);
598
+ expect(gapCodes(never)).toContain("runner_offline");
672
599
  expect(never.gaps[0]?.title).toContain("never connected");
673
600
  expect(never.runner?.isOnline).toBe(false);
674
601
 
675
- jest.restoreAllMocks();
676
602
  jest
677
603
  .spyOn(RunnerService, "findOneBy")
678
604
  .mockResolvedValue(
@@ -686,8 +612,11 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
686
612
  });
687
613
 
688
614
  expect(gapCodes(stale)).toEqual(["runner_offline"]);
689
- expect(stale.gaps[0]?.title).toBe("The Runner is offline");
690
- expect(stale.gaps[0]?.nextStep).toContain("component=ai-runner");
615
+ expect(stale.gaps[0]?.title).toBe(
616
+ "The previous in-cluster Runner is offline",
617
+ );
618
+ expect(stale.gaps[0]?.description).toContain("last reported in at");
619
+ expect(stale.runner?.name).toBe("kubernetes-agent/prod-us");
691
620
  });
692
621
 
693
622
  it("reports runner_ai_commands_disabled when the capability is off", async () => {
@@ -773,7 +702,14 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
773
702
  * exists only for the Runner whose posture names THIS cluster.
774
703
  */
775
704
  describe("in-cluster access is only the in-cluster Runner of this cluster", () => {
776
- it("refuses the in-cluster Runner of a different cluster with a gap that blocks both", async () => {
705
+ /*
706
+ * A kubernetes-agent Runner of ANOTHER cluster bound here (the binding
707
+ * guard refuses it for new writes; older rows may still hold one) is
708
+ * never an access target: its ServiceAccount reaches only its own
709
+ * cluster, and it is neither the advanced Runner an operator chose nor
710
+ * this cluster's previous in-cluster Runner.
711
+ */
712
+ it("never uses the in-cluster Runner of a different cluster: without an agent nothing reaches this one", async () => {
777
713
  jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
778
714
  fakeRunner({
779
715
  name: "kubernetes-agent/prod-eu",
@@ -793,19 +729,39 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
793
729
  gates: READY_GATES,
794
730
  });
795
731
 
796
- expect(gapCodes(status)).toEqual(["runner_cluster_mismatch"]);
732
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
797
733
  expect(status.gaps[0]?.blocks).toBe("both");
798
- expect(status.gaps[0]?.title).toContain("different cluster");
799
- expect(status.gaps[0]?.description).toContain('"prod-eu"');
800
- expect(status.gaps[0]?.description).toContain('"prod-us"');
801
- expect(status.gaps[0]?.nextStep).toContain("aiAccess.enabled=true");
802
734
  expect(status.accessMethod).toBe("none");
803
735
  expect(status.credentialId).toBeUndefined();
736
+ expect(status.runner).toBeNull();
804
737
  expect(status.isInvestigationReady).toBe(false);
805
738
  expect(status.isRemediationReady).toBe(false);
806
- // The Runner is still described, so the page can say WHICH Runner.
807
- expect(status.runner?.name).toBe("kubernetes-agent/prod-eu");
808
- expect(status.runner?.posture?.clusterIdentifier).toBe("prod-eu");
739
+ });
740
+
741
+ it("with this cluster's AI agent, the other cluster's bound Runner is ignored and the agent serves", async () => {
742
+ agentLookup.mockResolvedValue(fakeAgent());
743
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
744
+ fakeRunner({
745
+ name: "kubernetes-agent/prod-eu",
746
+ hostInfo: {
747
+ kubernetes: {
748
+ inCluster: true,
749
+ allowWrites: true,
750
+ clusterIdentifier: "prod-eu",
751
+ },
752
+ },
753
+ }),
754
+ );
755
+
756
+ const status: KubernetesClusterAiAccessStatus =
757
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
758
+ cluster: fakeCluster(),
759
+ gates: READY_GATES,
760
+ });
761
+
762
+ expect(status.gaps).toEqual([]);
763
+ expect(status.runner?.kind).toBe("ai_agent");
764
+ expect(status.runner?.id).toBe(AGENT_ID.toString());
809
765
  });
810
766
 
811
767
  it("refuses a pod Runner that never said which cluster it runs in", async () => {
@@ -823,7 +779,12 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
823
779
  });
824
780
 
825
781
  expect(gapCodes(status)).toEqual(["runner_cluster_mismatch"]);
826
- expect(status.gaps[0]?.title).toContain("did not report which cluster");
782
+ expect(status.gaps[0]?.title).toBe(
783
+ "The bound Runner did not report which cluster it runs in",
784
+ );
785
+ expect(status.gaps[0]?.description).toBe(
786
+ 'Runner "pod-runner" reports that it runs inside a Kubernetes cluster but not which one, so OneUptime AI cannot tell whether that is this cluster.',
787
+ );
827
788
  expect(status.accessMethod).toBe("none");
828
789
  expect(status.isInvestigationReady).toBe(false);
829
790
  });
@@ -847,7 +808,8 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
847
808
  gates: READY_GATES,
848
809
  });
849
810
 
850
- expect(gapCodes(status)).toEqual(["runner_cluster_mismatch"]);
811
+ // A cluster without an identifier has no previous in-cluster Runner.
812
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
851
813
  expect(status.accessMethod).toBe("none");
852
814
  });
853
815
 
@@ -876,16 +838,16 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
876
838
  });
877
839
 
878
840
  /*
879
- * Changed with the credential-exfiltration fix: this used to be the
880
- * supported "another cluster's agent + an assigned credential" setup
881
- * and read as ready. A kubernetes-agent Runner is minted and re-keyed
882
- * with the telemetry ingestion key, so it is never handed credential
883
- * material — the claim path refuses it — and readiness must say so
884
- * instead of promising access that would fail on every command.
841
+ * A kubernetes-agent Runner is minted and re-keyed with the telemetry
842
+ * ingestion key, so it is never handed credential material. Such a row
843
+ * of another cluster — by its name marker, its posture, or a case
844
+ * variant of the marker — is not an access target at all now, so the
845
+ * credential selected with it is never even read, let alone used.
885
846
  */
886
- it("refuses a credential on another cluster's agent Runner with its own gap, never 'credential' access", async () => {
887
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
888
- fakeRunner({
847
+ it.each([
848
+ [
849
+ "another cluster's agent row",
850
+ {
889
851
  name: "kubernetes-agent/prod-eu",
890
852
  hostInfo: {
891
853
  kubernetes: {
@@ -894,72 +856,17 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
894
856
  clusterIdentifier: "prod-eu",
895
857
  },
896
858
  },
897
- }),
898
- );
899
- const credentialLookup: jest.SpyInstance = jest
900
- .spyOn(RunbookCredentialService, "findOneBy")
901
- .mockResolvedValue({
902
- id: CREDENTIAL_ID,
903
- _id: CREDENTIAL_ID.toString(),
904
- name: "prod-us kubeconfig",
905
- credentialType: "Kubernetes",
906
- runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
907
- } as unknown as RunbookCredential);
908
-
909
- const status: KubernetesClusterAiAccessStatus =
910
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
911
- cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
912
- gates: READY_GATES,
913
- });
914
-
915
- expect(gapCodes(status)).toEqual(["credential_on_agent_runner"]);
916
- expect(status.gaps[0]?.blocks).toBe("both");
917
- expect(status.gaps[0]?.description).toContain(
918
- 'the in-cluster Runner of cluster "prod-eu"',
919
- );
920
- expect(status.gaps[0]?.nextStep).toContain(
921
- "Create a Runner under Project Settings → Runners",
922
- );
923
- expect(status.accessMethod).toBe("none");
924
- expect(status.credentialId).toBeUndefined();
925
- expect(status.isInvestigationReady).toBe(false);
926
- expect(status.isRemediationReady).toBe(false);
927
- // The credential's details are moot: an agent row never carries one.
928
- expect(credentialLookup).not.toHaveBeenCalled();
929
- });
930
-
931
- it("keyed on the server-owned NAME: an agent row whose heartbeat dropped its posture still cannot carry a credential", async () => {
932
- jest
933
- .spyOn(RunnerService, "findOneBy")
934
- .mockResolvedValue(
935
- fakeRunner({ name: "kubernetes-agent/prod-eu", hostInfo: {} }),
936
- );
937
- jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
938
- id: CREDENTIAL_ID,
939
- _id: CREDENTIAL_ID.toString(),
940
- name: "prod-us kubeconfig",
941
- credentialType: "Kubernetes",
942
- runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
943
- } as unknown as RunbookCredential);
944
-
945
- const status: KubernetesClusterAiAccessStatus =
946
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
947
- cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
948
- gates: READY_GATES,
949
- });
950
-
951
- expect(gapCodes(status)).toEqual(["credential_on_agent_runner"]);
952
- expect(status.accessMethod).toBe("none");
953
- });
954
-
955
- /*
956
- * An agent row renamed (only root can rename one now) keeps its agent
957
- * posture, and the posture alone marks it: the one "is an agent row"
958
- * rule fails closed on the name OR the posture.
959
- */
960
- it("keyed on the name OR the posture: a row with another cluster's agent posture but no marker cannot carry a credential", async () => {
961
- jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
962
- fakeRunner({
859
+ },
860
+ ' (it reports cluster "prod-eu")',
861
+ ],
862
+ [
863
+ "an agent row whose heartbeat dropped its posture (the server-owned NAME)",
864
+ { name: "kubernetes-agent/prod-eu", hostInfo: {} },
865
+ "",
866
+ ],
867
+ [
868
+ "a row with another cluster's agent posture but no marker (name OR posture)",
869
+ {
963
870
  name: "prod-eu in-cluster runner",
964
871
  hostInfo: {
965
872
  kubernetes: {
@@ -968,42 +875,57 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
968
875
  clusterIdentifier: "prod-eu",
969
876
  },
970
877
  },
971
- }),
972
- );
973
- jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
974
- id: CREDENTIAL_ID,
975
- _id: CREDENTIAL_ID.toString(),
976
- name: "prod-us kubeconfig",
977
- credentialType: "Kubernetes",
978
- runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
979
- } as unknown as RunbookCredential);
980
-
981
- const status: KubernetesClusterAiAccessStatus =
982
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
983
- cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
984
- gates: READY_GATES,
985
- });
878
+ },
879
+ ' (it reports cluster "prod-eu")',
880
+ ],
881
+ [
882
+ "a case variant of the marker",
883
+ { name: "Kubernetes-Agent/prod-eu", hostInfo: {} },
884
+ "",
885
+ ],
886
+ ])(
887
+ "never uses a credential with %s: it is no access target",
888
+ async (
889
+ _label: string,
890
+ runner: Record<string, unknown>,
891
+ reported: string,
892
+ ) => {
893
+ jest
894
+ .spyOn(RunnerService, "findOneBy")
895
+ .mockResolvedValue(fakeRunner(runner));
896
+ const credentialLookup: jest.SpyInstance = jest
897
+ .spyOn(RunbookCredentialService, "findOneBy")
898
+ .mockResolvedValue({
899
+ id: CREDENTIAL_ID,
900
+ _id: CREDENTIAL_ID.toString(),
901
+ name: "prod-us kubeconfig",
902
+ credentialType: "Kubernetes",
903
+ runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
904
+ } as unknown as RunbookCredential);
986
905
 
987
- expect(gapCodes(status)).toEqual(["credential_on_agent_runner"]);
988
- expect(status.accessMethod).toBe("none");
989
- expect(status.credentialId).toBeUndefined();
990
- });
906
+ const status: KubernetesClusterAiAccessStatus =
907
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
908
+ cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
909
+ gates: READY_GATES,
910
+ });
991
911
 
992
- it("keyed on a case variant of the marker too", async () => {
993
- jest
994
- .spyOn(RunnerService, "findOneBy")
995
- .mockResolvedValue(
996
- fakeRunner({ name: "Kubernetes-Agent/prod-eu", hostInfo: {} }),
912
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
913
+ expect(gapCodes(status)).not.toContain("credential_on_agent_runner");
914
+ /*
915
+ * The status still says a Runner is selected and why it is not
916
+ * used, rather than reading as if nothing were bound.
917
+ */
918
+ expect(status.gaps[0]?.description).toContain(
919
+ `Runner "${String(runner["name"])}" is still selected for this cluster, but it is not this cluster's in-cluster Runner${reported}, so OneUptime AI does not use it.`,
997
920
  );
998
-
999
- const status: KubernetesClusterAiAccessStatus =
1000
- await KubernetesClusterAiAccessService.getStatusForClusterModel({
1001
- cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
1002
- gates: READY_GATES,
1003
- });
1004
-
1005
- expect(gapCodes(status)).toEqual(["credential_on_agent_runner"]);
1006
- });
921
+ expect(status.gaps[0]?.nextStep).toBe(AI_AGENT_NOT_CONNECTED_NEXT_STEP);
922
+ expect(status.runner).toBeNull();
923
+ expect(status.accessMethod).toBe("none");
924
+ expect(status.credentialId).toBeUndefined();
925
+ expect(status.isInvestigationReady).toBe(false);
926
+ expect(credentialLookup).not.toHaveBeenCalled();
927
+ },
928
+ );
1007
929
 
1008
930
  /*
1009
931
  * Before round two this fixture reported a cluster identity too (an
@@ -1093,7 +1015,7 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
1093
1015
  expect(status.accessMethod).toBe("none");
1094
1016
  });
1095
1017
 
1096
- it("reports both the mismatch and the read-only gap for a read-only agent of another cluster", async () => {
1018
+ it("a row named for this cluster whose posture names another is not this cluster's previous Runner", async () => {
1097
1019
  jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
1098
1020
  fakeRunner({
1099
1021
  hostInfo: {
@@ -1112,16 +1034,13 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
1112
1034
  gates: READY_GATES,
1113
1035
  });
1114
1036
 
1115
- expect(gapCodes(status)).toEqual([
1116
- "runner_cluster_mismatch",
1117
- "remediation_write_access_missing",
1118
- ]);
1037
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
1119
1038
  expect(status.isInvestigationReady).toBe(false);
1120
1039
  expect(status.isRemediationReady).toBe(false);
1121
1040
  });
1122
1041
  });
1123
1042
 
1124
- it("blocks only remediation when the in-cluster Runner is read-only", async () => {
1043
+ it("blocks only remediation when the previous in-cluster Runner is read-only, and says to upgrade to the AI agent", async () => {
1125
1044
  jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
1126
1045
  fakeRunner({
1127
1046
  hostInfo: {
@@ -1142,36 +1061,66 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
1142
1061
 
1143
1062
  expect(gapCodes(status)).toEqual(["remediation_write_access_missing"]);
1144
1063
  expect(status.gaps[0]?.blocks).toBe("remediation");
1064
+ expect(status.gaps[0]?.title).toBe(
1065
+ "The previous in-cluster Runner is read-only",
1066
+ );
1067
+ expect(status.gaps[0]?.nextStep).toBe(
1068
+ LEGACY_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
1069
+ );
1145
1070
  expect(status.gaps[0]?.nextStep).toContain(
1146
- "aiAccess.remediation.enabled=true",
1071
+ "--set aiAgent.remediation.enabled=true",
1147
1072
  );
1148
- expect(status.gaps[0]?.nextStep).toBe(REMEDIATION_WRITE_ACCESS_NEXT_STEP);
1149
1073
  expect(status.isInvestigationReady).toBe(true);
1150
1074
  expect(status.isRemediationReady).toBe(false);
1151
1075
  });
1152
1076
 
1077
+ it("an in-cluster pod Runner outside the chart that cannot write is told to use its own environment", async () => {
1078
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(
1079
+ fakeRunner({
1080
+ name: "pod-runner",
1081
+ hostInfo: { kubernetes: { inCluster: true, allowWrites: false } },
1082
+ }),
1083
+ );
1084
+
1085
+ const status: KubernetesClusterAiAccessStatus =
1086
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1087
+ cluster: fakeCluster(),
1088
+ gates: READY_GATES,
1089
+ });
1090
+
1091
+ const gap: KubernetesAiAccessGap | undefined = status.gaps.find(
1092
+ (candidate: KubernetesAiAccessGap) => {
1093
+ return candidate.code === "remediation_write_access_missing";
1094
+ },
1095
+ );
1096
+
1097
+ expect(gap?.title).toBe("The Runner is read-only");
1098
+ expect(gap?.nextStep).toBe(
1099
+ CREDENTIAL_RUNNER_REMEDIATION_WRITE_ACCESS_NEXT_STEP,
1100
+ );
1101
+ });
1102
+
1153
1103
  /*
1154
- * Round three: the read-only gap used to give only a bare
1155
- * "--set aiAccess.remediation.enabled=true", which grants write RBAC
1156
- * cluster-wide and to every node. It now points at the AI page's
1157
- * write-access section (the complete commands) and names the two values
1158
- * that bound where the write role reaches.
1104
+ * The read-only gap names the value that grants writes and the two that
1105
+ * bound where the write role reaches; the complete command lives on the
1106
+ * cluster's AI agent page, which it names (never "this page").
1159
1107
  */
1160
- it("points the read-only gap at the AI page's write-access section and the values that scope it", () => {
1108
+ it("points the read-only gap at the AI agent page and the aiAgent values that scope it", () => {
1161
1109
  expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
1162
- "--set aiAccess.remediation.enabled=true",
1110
+ "--set aiAgent.remediation.enabled=true",
1163
1111
  );
1164
1112
  expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
1165
- '"Let AI apply fixes (write access)" on the cluster\'s AI page',
1113
+ "the cluster's AI agent page (AI → Agent)",
1166
1114
  );
1167
1115
  expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
1168
- "aiAccess.remediation.namespaces",
1116
+ "aiAgent.remediation.namespaces",
1169
1117
  );
1170
1118
  expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain(
1171
- "aiAccess.remediation.nodeOperations=false",
1119
+ "aiAgent.remediation.nodeOperations=false",
1172
1120
  );
1173
1121
  expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).toContain("cluster-wide");
1174
1122
  expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).not.toContain("this page");
1123
+ expect(REMEDIATION_WRITE_ACCESS_NEXT_STEP).not.toContain("aiAccess");
1175
1124
  });
1176
1125
 
1177
1126
  it("does not raise the read-only gap when remediation is off", async () => {
@@ -1225,19 +1174,139 @@ describe("KubernetesClusterAiAccessService.getStatusForClusterModel", () => {
1225
1174
  isAutoRemediationEnabled: false,
1226
1175
  isAiCommandExecutionEnabled: false,
1227
1176
  hasLlmProvider: false,
1177
+ aiBalanceBlocker: "This project's AI credit balance is used up.",
1228
1178
  },
1229
1179
  });
1230
1180
 
1181
+ /*
1182
+ * The previous in-cluster Runner is a cluster target the command
1183
+ * execution opt-in no longer gates (the cluster's Fixes setting and
1184
+ * the chart's write RBAC do), so there is no command-execution gap.
1185
+ */
1231
1186
  expect(gapCodes(status)).toEqual([
1232
1187
  "project_ai_disabled",
1233
1188
  "llm_provider_missing",
1189
+ "ai_balance_insufficient",
1234
1190
  "project_auto_remediation_disabled",
1235
- "project_ai_command_execution_disabled",
1236
1191
  ]);
1237
1192
  expect(status.isInvestigationReady).toBe(false);
1238
1193
  expect(status.isRemediationReady).toBe(false);
1239
1194
  });
1240
1195
 
1196
+ it("ai_balance_insufficient carries the reason, blocks both and says where credits are added", async () => {
1197
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
1198
+
1199
+ const status: KubernetesClusterAiAccessStatus =
1200
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1201
+ cluster: fakeCluster(),
1202
+ gates: {
1203
+ ...READY_GATES,
1204
+ aiBalanceBlocker: "This project's AI credit balance is used up.",
1205
+ },
1206
+ });
1207
+
1208
+ expect(gapCodes(status)).toEqual(["ai_balance_insufficient"]);
1209
+ expect(status.gaps[0]).toEqual({
1210
+ code: "ai_balance_insufficient",
1211
+ title: "The project is out of AI credits",
1212
+ description: "This project's AI credit balance is used up.",
1213
+ nextStep:
1214
+ "Add AI credits under Project Settings → AI Credits (or enable auto-recharge).",
1215
+ blocks: "both",
1216
+ });
1217
+ expect(status.gaps[0]?.nextStep).toBe(AI_BALANCE_INSUFFICIENT_NEXT_STEP);
1218
+ expect(status.isInvestigationReady).toBe(false);
1219
+ expect(status.isRemediationReady).toBe(false);
1220
+ });
1221
+
1222
+ it("negative control: no blocker (null or absent) is no balance gap", async () => {
1223
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
1224
+
1225
+ for (const aiBalanceBlocker of [null, undefined, ""]) {
1226
+ const status: KubernetesClusterAiAccessStatus =
1227
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1228
+ cluster: fakeCluster(),
1229
+ gates: { ...READY_GATES, aiBalanceBlocker },
1230
+ });
1231
+
1232
+ expect(gapCodes(status)).toEqual([]);
1233
+ }
1234
+ });
1235
+
1236
+ describe("the project's command-execution opt-in gates only an advanced Runner", () => {
1237
+ const CLOSED_OPT_IN: KubernetesClusterAiAccessProjectGates = {
1238
+ ...READY_GATES,
1239
+ isAiCommandExecutionEnabled: false,
1240
+ };
1241
+
1242
+ it("an advanced Runner with a credential: project_ai_command_execution_disabled blocks remediation", async () => {
1243
+ jest
1244
+ .spyOn(RunnerService, "findOneBy")
1245
+ .mockResolvedValue(
1246
+ fakeRunner({ name: "office-runner", hostInfo: { hostname: "x" } }),
1247
+ );
1248
+ jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
1249
+ id: CREDENTIAL_ID,
1250
+ _id: CREDENTIAL_ID.toString(),
1251
+ name: "prod kubeconfig",
1252
+ credentialType: "Kubernetes",
1253
+ runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
1254
+ } as unknown as RunbookCredential);
1255
+
1256
+ const status: KubernetesClusterAiAccessStatus =
1257
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1258
+ cluster: fakeCluster({ aiAccessCredentialId: CREDENTIAL_ID }),
1259
+ gates: CLOSED_OPT_IN,
1260
+ });
1261
+
1262
+ expect(gapCodes(status)).toEqual([
1263
+ "project_ai_command_execution_disabled",
1264
+ ]);
1265
+ expect(status.gaps[0]?.blocks).toBe("remediation");
1266
+ expect(status.gaps[0]?.nextStep).toBe(
1267
+ 'Turn on "Enable AI Command Execution" under Project Settings → AI Features.',
1268
+ );
1269
+ expect(status.isInvestigationReady).toBe(true);
1270
+ expect(status.isRemediationReady).toBe(false);
1271
+ });
1272
+
1273
+ it("the Kubernetes AI agent: no command-execution gap, remediation ready", async () => {
1274
+ agentLookup.mockResolvedValue(fakeAgent());
1275
+
1276
+ const status: KubernetesClusterAiAccessStatus =
1277
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1278
+ cluster: fakeCluster({ aiAccessRunnerId: undefined }),
1279
+ gates: CLOSED_OPT_IN,
1280
+ });
1281
+
1282
+ expect(gapCodes(status)).toEqual([]);
1283
+ expect(status.isRemediationReady).toBe(true);
1284
+ });
1285
+
1286
+ it("the previous in-cluster Runner: no command-execution gap either", async () => {
1287
+ jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
1288
+
1289
+ const status: KubernetesClusterAiAccessStatus =
1290
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1291
+ cluster: fakeCluster(),
1292
+ gates: CLOSED_OPT_IN,
1293
+ });
1294
+
1295
+ expect(gapCodes(status)).toEqual([]);
1296
+ expect(status.isRemediationReady).toBe(true);
1297
+ });
1298
+
1299
+ it("nothing connected: no command-execution gap, the connection gap says it all", async () => {
1300
+ const status: KubernetesClusterAiAccessStatus =
1301
+ await KubernetesClusterAiAccessService.getStatusForClusterModel({
1302
+ cluster: fakeCluster({ aiAccessRunnerId: undefined }),
1303
+ gates: CLOSED_OPT_IN,
1304
+ });
1305
+
1306
+ expect(gapCodes(status)).toEqual(["ai_agent_not_connected"]);
1307
+ });
1308
+ });
1309
+
1241
1310
  it("is remediation-ready in BypassApproval mode with a write-capable Runner and reports the mode verbatim", async () => {
1242
1311
  jest.spyOn(RunnerService, "findOneBy").mockResolvedValue(fakeRunner());
1243
1312
 
@@ -1412,6 +1481,7 @@ describe("KubernetesClusterAiAccessService.getProjectGates", () => {
1412
1481
  jest
1413
1482
  .spyOn(LlmProviderService, "getLLMProviderForProject")
1414
1483
  .mockResolvedValue({ id: ObjectID.generate() } as unknown as LlmProvider);
1484
+ jest.spyOn(AIService, "getAiBalanceBlocker").mockResolvedValue(null);
1415
1485
 
1416
1486
  const gates: KubernetesClusterAiAccessProjectGates =
1417
1487
  await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
@@ -1421,10 +1491,13 @@ describe("KubernetesClusterAiAccessService.getProjectGates", () => {
1421
1491
  isAutoRemediationEnabled: true,
1422
1492
  isAiCommandExecutionEnabled: false,
1423
1493
  hasLlmProvider: true,
1494
+ aiBalanceBlocker: null,
1495
+ // Absent reads as off: the investigation opt-ins count only when true.
1496
+ automaticInvestigation: { incidents: false, alerts: false },
1424
1497
  });
1425
1498
  });
1426
1499
 
1427
- it("treats a provider lookup failure as no provider", async () => {
1500
+ it("treats a provider lookup failure as no provider, and does not check the balance without one", async () => {
1428
1501
  jest.spyOn(logger, "error").mockImplementation((): void => {
1429
1502
  return undefined;
1430
1503
  });
@@ -1434,12 +1507,85 @@ describe("KubernetesClusterAiAccessService.getProjectGates", () => {
1434
1507
  jest
1435
1508
  .spyOn(LlmProviderService, "getLLMProviderForProject")
1436
1509
  .mockRejectedValue(new Error("boom"));
1510
+ const blocker: jest.SpyInstance = jest
1511
+ .spyOn(AIService, "getAiBalanceBlocker")
1512
+ .mockResolvedValue("out of credits");
1437
1513
 
1438
1514
  const gates: KubernetesClusterAiAccessProjectGates =
1439
1515
  await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
1440
1516
 
1441
1517
  expect(gates.hasLlmProvider).toBe(false);
1442
1518
  expect(gates.isAiCommandExecutionEnabled).toBe(true);
1519
+ // Unknown, so unsaid: not a second (failing) provider lookup.
1520
+ expect(blocker).not.toHaveBeenCalled();
1521
+ expect(gates.aiBalanceBlocker).toBeNull();
1522
+ });
1523
+
1524
+ it("reads the project's automatic-investigation opt-ins (exactly true) for the AI agent page", async () => {
1525
+ jest.spyOn(ProjectService, "findOneById").mockResolvedValue({
1526
+ enableAutomaticIncidentInvestigation: true,
1527
+ enableAutomaticAlertInvestigation: false,
1528
+ } as unknown as Project);
1529
+ jest
1530
+ .spyOn(LlmProviderService, "getLLMProviderForProject")
1531
+ .mockResolvedValue(null);
1532
+ jest.spyOn(AIService, "getAiBalanceBlocker").mockResolvedValue(null);
1533
+
1534
+ const gates: KubernetesClusterAiAccessProjectGates =
1535
+ await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
1536
+
1537
+ expect(gates.automaticInvestigation).toEqual({
1538
+ incidents: true,
1539
+ alerts: false,
1540
+ });
1541
+ });
1542
+
1543
+ it("asks AIService for the AI balance blocker with the provider it already resolved, and carries its reason", async () => {
1544
+ const provider: LlmProvider = {
1545
+ id: ObjectID.generate(),
1546
+ isGlobalLlm: true,
1547
+ costPerMillionTokensInUSDCents: 100,
1548
+ } as unknown as LlmProvider;
1549
+ jest
1550
+ .spyOn(ProjectService, "findOneById")
1551
+ .mockResolvedValue({} as unknown as Project);
1552
+ const providerLookup: jest.SpyInstance = jest
1553
+ .spyOn(LlmProviderService, "getLLMProviderForProject")
1554
+ .mockResolvedValue(provider);
1555
+ const blocker: jest.SpyInstance = jest
1556
+ .spyOn(AIService, "getAiBalanceBlocker")
1557
+ .mockResolvedValue("out of credits");
1558
+
1559
+ const gates: KubernetesClusterAiAccessProjectGates =
1560
+ await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
1561
+
1562
+ expect(blocker).toHaveBeenCalledWith({
1563
+ projectId: PROJECT_ID,
1564
+ llmProvider: provider,
1565
+ });
1566
+ expect(gates.aiBalanceBlocker).toBe("out of credits");
1567
+ expect(providerLookup).toHaveBeenCalledTimes(1);
1568
+ });
1569
+
1570
+ it("treats a balance check that fails as no blocker (the AI call still refuses on its own)", async () => {
1571
+ jest.spyOn(logger, "error").mockImplementation((): void => {
1572
+ return undefined;
1573
+ });
1574
+ jest
1575
+ .spyOn(ProjectService, "findOneById")
1576
+ .mockResolvedValue({} as unknown as Project);
1577
+ jest
1578
+ .spyOn(LlmProviderService, "getLLMProviderForProject")
1579
+ .mockResolvedValue({ id: ObjectID.generate() } as unknown as LlmProvider);
1580
+ jest
1581
+ .spyOn(AIService, "getAiBalanceBlocker")
1582
+ .mockRejectedValue(new Error("db down"));
1583
+
1584
+ const gates: KubernetesClusterAiAccessProjectGates =
1585
+ await KubernetesClusterAiAccessService.getProjectGates(PROJECT_ID);
1586
+
1587
+ expect(gates.aiBalanceBlocker).toBeNull();
1588
+ expect(gates.hasLlmProvider).toBe(true);
1443
1589
  });
1444
1590
  });
1445
1591
 
@@ -1608,6 +1754,10 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
1608
1754
  .mockImplementation(async (args: unknown): Promise<void> => {
1609
1755
  feedItems.push(args as Record<string, unknown>);
1610
1756
  });
1757
+ // No Kubernetes AI agent unless a test says otherwise.
1758
+ jest
1759
+ .spyOn(KubernetesAiAgentService, "findForCluster")
1760
+ .mockResolvedValue(null);
1611
1761
  });
1612
1762
 
1613
1763
  afterEach(() => {
@@ -2672,7 +2822,11 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
2672
2822
  "the binding was cleared by an operator, or the Runner it was bound to was deleted",
2673
2823
  );
2674
2824
  expect(feed).toContain("no AI switch was changed");
2675
- expect(feed).toContain("Select **kubernetes-agent/prod-us**");
2825
+ // The way back is the AI agent, not selecting this Runner.
2826
+ expect(feed).toContain(
2827
+ "Upgrade the Kubernetes agent chart to use the Kubernetes AI agent instead.",
2828
+ );
2829
+ expect(feed).not.toContain("Select **kubernetes-agent/prod-us**");
2676
2830
  });
2677
2831
 
2678
2832
  it("keeps an operator's enabled switches exactly as they are, too", async () => {
@@ -2696,7 +2850,7 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
2696
2850
  expect(clusterUpdates).toHaveLength(0);
2697
2851
  });
2698
2852
 
2699
- it("afterwards, the cluster's status points at selecting the new Runner, not at helm", async () => {
2853
+ it("afterwards, the cluster's status points at installing the AI agent, not at selecting the Runner", async () => {
2700
2854
  jest
2701
2855
  .spyOn(KubernetesClusterService, "findOneBy")
2702
2856
  .mockResolvedValue(clusterAfterRunnerDelete());
@@ -2708,7 +2862,7 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
2708
2862
  posture: { allowWrites: true },
2709
2863
  });
2710
2864
 
2711
- // The row registration just created is what the status now finds.
2865
+ // The row registration just created exists, but nothing binds it.
2712
2866
  mockRunnerLookups({ byName: createdRunner });
2713
2867
 
2714
2868
  const status: KubernetesClusterAiAccessStatus =
@@ -2717,16 +2871,15 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
2717
2871
  gates: READY_GATES,
2718
2872
  });
2719
2873
 
2720
- expect(gapCodes(status)).toContain("no_runner_bound");
2874
+ expect(gapCodes(status)).toContain("ai_agent_not_connected");
2875
+ expect(gapCodes(status)).not.toContain("no_runner_bound");
2721
2876
  const gap: KubernetesAiAccessGap = status.gaps.find(
2722
2877
  (candidate: KubernetesAiAccessGap) => {
2723
- return candidate.code === "no_runner_bound";
2878
+ return candidate.code === "ai_agent_not_connected";
2724
2879
  },
2725
2880
  )!;
2726
- expect(gap.nextStep).toContain(
2727
- 'Select the kubernetes-agent Runner "kubernetes-agent/prod-us"',
2728
- );
2729
- expect(gap.nextStep).not.toContain("aiAccess.enabled=true");
2881
+ expect(gap.nextStep).toContain(AI_AGENT_INSTALL_COMMAND);
2882
+ expect(gap.nextStep).not.toContain("Select the kubernetes-agent Runner");
2730
2883
  });
2731
2884
 
2732
2885
  it("negative control: without either marker (and no other history) the same cluster still first-binds", async () => {
@@ -3034,11 +3187,11 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
3034
3187
  expect(thrown).toBeInstanceOf(ForbiddenException);
3035
3188
  const message: string = (thrown as Error).message;
3036
3189
  expect(message).toContain(holding.expectInMessage);
3037
- expect(message).toContain("delete the Runner");
3038
- // Round four: the delete path names its second step.
3190
+ // The chart upgrade to the AI agent comes first: it needs no Runner.
3039
3191
  expect(message).toContain(
3040
- "select it on the cluster's AI page as its Runner",
3192
+ "Upgrade the Kubernetes agent chart: its Kubernetes AI agent replaces this in-cluster Runner.",
3041
3193
  );
3194
+ expect(message).not.toContain("select it on the cluster's AI page");
3042
3195
  expect(message).not.toContain(CURRENT_KEY);
3043
3196
 
3044
3197
  expect(runnerUpdates).toHaveLength(0);
@@ -3519,11 +3672,15 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
3519
3672
  isTransientKubernetesAgentRegistrationRefusal(refusal.reason),
3520
3673
  ).toBe(false);
3521
3674
  expect(refusal.retryAfterSeconds).toBeUndefined();
3675
+ // The chart upgrade to the AI agent leads: it needs no Runner at all.
3522
3676
  expect(
3523
3677
  refusal.message.startsWith(
3524
- 'Under Project Settings → Runners, on Runner "kubernetes-agent/prod-us": turn off "Runs Runbooks"',
3678
+ "Upgrade the Kubernetes agent chart: its Kubernetes AI agent replaces this in-cluster Runner.",
3525
3679
  ),
3526
3680
  ).toBe(true);
3681
+ expect(refusal.message).toContain(
3682
+ 'Or, under Project Settings → Runners, on Runner "kubernetes-agent/prod-us": turn off "Runs Runbooks"',
3683
+ );
3527
3684
  // Only what it actually holds is asked for.
3528
3685
  expect(refusal.message).not.toContain('turn off "Runs AI Code Fixes"');
3529
3686
  expect(refusal.message).not.toContain("unassign");
@@ -3566,11 +3723,7 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
3566
3723
  );
3567
3724
 
3568
3725
  const head: string = refusal.message.slice(0, 500);
3569
- expect(head).toContain("delete the Runner");
3570
- // Round four: the delete path's second step is part of the instruction.
3571
- expect(head).toContain(
3572
- "select it on the cluster's AI page as its Runner",
3573
- );
3726
+ expect(head).toContain("Upgrade the Kubernetes agent chart");
3574
3727
  expect(head).toContain('"Runs Runbooks"');
3575
3728
  expect(head).toContain('"Runs AI Code Fixes"');
3576
3729
  expect(head).toContain("3 Runner credential(s)");
@@ -3602,15 +3755,18 @@ describe("KubernetesClusterAiAccessService.registerKubernetesAgentRunner", () =>
3602
3755
  expect(refusal.reason).toBe("runner_belongs_to_another_cluster");
3603
3756
  expect(refusal.retryAfterSeconds).toBeUndefined();
3604
3757
  /*
3605
- * Round four: was "Rename or delete Runner ...". A non-root rename of
3606
- * an agent-named Runner is refused (RunnerService), so the one step
3607
- * an operator can take is the delete — and then selecting the fresh
3608
- * Runner, since a deleted Runner's cluster is left unbound.
3758
+ * The step that works is the chart upgrade: the Kubernetes AI agent
3759
+ * replaces the in-cluster Runner and does not use the Runner name.
3609
3760
  */
3610
- expect(refusal.message.startsWith("Delete Runner")).toBe(true);
3761
+ expect(
3762
+ refusal.message.startsWith(
3763
+ 'Upgrade the Kubernetes agent chart of cluster "prod-us"',
3764
+ ),
3765
+ ).toBe(true);
3611
3766
  expect(refusal.message).toContain(
3612
- 'select it on the AI page of cluster "prod-us" as its Runner',
3767
+ 'does not need Runner "kubernetes-agent/prod-us"',
3613
3768
  );
3769
+ expect(refusal.message).not.toContain("AI page");
3614
3770
  });
3615
3771
 
3616
3772
  it("getPreviousInstanceRetryAfterSeconds counts down to the end of the alive window, never below 1s", () => {