@oneuptime/common 14.0.1 → 14.0.2

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 (1090) hide show
  1. package/Models/DatabaseModels/AlertEpisodeMember.ts +8 -1
  2. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +63 -0
  3. package/Models/DatabaseModels/IncidentEpisodeMember.ts +8 -1
  4. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +9 -2
  5. package/Models/DatabaseModels/IncidentPublicNote.ts +9 -2
  6. package/Models/DatabaseModels/KubernetesCluster.ts +429 -0
  7. package/Models/DatabaseModels/Monitor.ts +48 -0
  8. package/Models/DatabaseModels/Runner.ts +5 -4
  9. package/Models/DatabaseModels/RunnerJob.ts +66 -0
  10. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +10 -2
  11. package/Models/DatabaseModels/WorkflowVariable.ts +479 -2
  12. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +79 -25
  13. package/Server/API/AIInvestigationAPI.ts +341 -0
  14. package/Server/API/AutoRemediationAPI.ts +184 -2
  15. package/Server/API/CommonAPI.ts +70 -0
  16. package/Server/API/KubernetesClusterAiAccessAPI.ts +793 -0
  17. package/Server/API/MicrosoftTeamsAPI.ts +770 -315
  18. package/Server/API/MonitorAPI.ts +140 -0
  19. package/Server/API/OnCallScheduleTimelineAPI.ts +5 -21
  20. package/Server/API/SlackAPI.ts +447 -99
  21. package/Server/API/StatusPageAPI.ts +146 -26
  22. package/Server/API/TelemetryAPI.ts +216 -5
  23. package/Server/API/UserTwoFactorBackupCodeAPI.ts +1 -0
  24. package/Server/API/WorkflowVariableAPI.ts +156 -0
  25. package/Server/Infrastructure/GlobalCache.ts +35 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.ts +97 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.ts +129 -0
  28. package/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.ts +25 -0
  29. package/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.ts +20 -0
  30. package/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.ts +38 -0
  31. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  32. package/Server/Middleware/MasterAdminAuthorization.ts +28 -0
  33. package/Server/Middleware/TelemetryIngest.ts +47 -2
  34. package/Server/Middleware/UserAuthorization.ts +56 -2
  35. package/Server/Services/AIAgentService.ts +1 -0
  36. package/Server/Services/AccessTokenService.ts +121 -48
  37. package/Server/Services/AlertEpisodeMemberService.ts +9 -0
  38. package/Server/Services/AlertEpisodeService.ts +14 -1
  39. package/Server/Services/AlertService.ts +134 -22
  40. package/Server/Services/AutoRemediationRuleEngineService.ts +968 -39
  41. package/Server/Services/AutoRemediationRuleService.ts +118 -0
  42. package/Server/Services/BillingService.ts +1 -0
  43. package/Server/Services/InboundEmail/Providers/SendGridInboundProvider.ts +16 -22
  44. package/Server/Services/IncidentEpisodeMemberService.ts +9 -0
  45. package/Server/Services/IncidentEpisodePublicNoteService.ts +66 -0
  46. package/Server/Services/IncidentEpisodeRoleMemberService.ts +27 -0
  47. package/Server/Services/IncidentEpisodeService.ts +14 -1
  48. package/Server/Services/IncidentGroupingEngineService.ts +11 -0
  49. package/Server/Services/IncidentMemberService.ts +21 -0
  50. package/Server/Services/IncidentPublicNoteService.ts +63 -0
  51. package/Server/Services/IncidentService.ts +120 -20
  52. package/Server/Services/IncidentStateTimelineService.ts +15 -0
  53. package/Server/Services/IncidentTemplateService.ts +83 -3
  54. package/Server/Services/KubernetesClusterAiAccessService.ts +1943 -0
  55. package/Server/Services/KubernetesClusterService.ts +1570 -1
  56. package/Server/Services/LogAggregationService.ts +14 -6
  57. package/Server/Services/MonitorService.ts +651 -52
  58. package/Server/Services/MonitorStatusTimelineService.ts +639 -2
  59. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -0
  60. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -0
  61. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -0
  62. package/Server/Services/OnCallDutyPolicyScheduleService.ts +3 -0
  63. package/Server/Services/OnCallNotificationAlertingService.ts +36 -12
  64. package/Server/Services/OnCallSetupReminderService.ts +1 -0
  65. package/Server/Services/ProbeService.ts +7 -1
  66. package/Server/Services/ProjectService.ts +11 -1
  67. package/Server/Services/RunbookCredentialService.ts +26 -1
  68. package/Server/Services/RunbookSecretService.ts +36 -0
  69. package/Server/Services/RunnerJobService.ts +941 -23
  70. package/Server/Services/RunnerService.ts +439 -6
  71. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +66 -0
  72. package/Server/Services/ScheduledMaintenanceService.ts +124 -19
  73. package/Server/Services/ScheduledMaintenanceTemplateService.ts +82 -3
  74. package/Server/Services/ServiceLevelObjectiveService.ts +14 -1
  75. package/Server/Services/StatusPagePrivateUserService.ts +1 -0
  76. package/Server/Services/StatusPageService.ts +301 -74
  77. package/Server/Services/StatusPageSubscriberService.ts +4 -0
  78. package/Server/Services/TeamMemberService.ts +309 -7
  79. package/Server/Services/TraceAggregationService.ts +10 -6
  80. package/Server/Services/UserEmailService.ts +1 -0
  81. package/Server/Services/UserNotificationMethodAdminService.ts +1 -0
  82. package/Server/Services/UserNotificationRuleAdminService.ts +1 -0
  83. package/Server/Services/UserNotificationRuleService.ts +4 -0
  84. package/Server/Services/UserService.ts +122 -1
  85. package/Server/Services/WorkflowVariableService.ts +593 -8
  86. package/Server/Services/WorkspaceNotificationRuleService.ts +588 -8
  87. package/Server/Services/WorkspaceProjectAuthTokenService.ts +170 -1
  88. package/Server/Types/Database/QueryHelper.ts +22 -0
  89. package/Server/Types/Markdown.ts +502 -27
  90. package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +182 -0
  91. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +525 -0
  92. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +854 -0
  93. package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +22 -2
  94. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1916 -307
  95. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1601 -74
  96. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +139 -7
  97. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +58 -1
  98. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +54 -1
  99. package/Server/Utils/AI/Toolbox/AIActionTools.ts +14 -6
  100. package/Server/Utils/AI/Toolbox/ResourceTools.ts +4 -2
  101. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +1341 -150
  102. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +375 -4
  103. package/Server/Utils/Database/AffectedResourceRelations.ts +127 -0
  104. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +240 -1
  105. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +1 -0
  106. package/Server/Utils/Monitor/MonitorAlert.ts +83 -9
  107. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +5 -1
  108. package/Server/Utils/Monitor/MonitorIncident.ts +104 -9
  109. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +124 -7
  110. package/Server/Utils/Monitor/ProbeMonitorsNotification.ts +407 -0
  111. package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +1 -0
  112. package/Server/Utils/PushNotificationUtil.ts +45 -0
  113. package/Server/Utils/Rules/OwnerRuleAssignment.ts +40 -3
  114. package/Server/Utils/Runbook/RunbookExecutePermission.ts +56 -6
  115. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +223 -33
  116. package/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.ts +559 -0
  117. package/Server/Utils/StartServer.ts +4 -3
  118. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +528 -0
  119. package/Server/Utils/TwoFactorBackupCodeNotification.ts +1 -0
  120. package/Server/Utils/UserPermission/UserPermission.ts +16 -0
  121. package/Server/Utils/Workflow/OAuth2TokenClient.ts +704 -0
  122. package/Server/Utils/Workflow/WorkflowVariableOAuthToken.ts +318 -0
  123. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +47 -7
  124. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +57 -16
  125. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +193 -214
  126. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +57 -13
  127. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.ts +54 -17
  128. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +179 -161
  129. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +281 -167
  130. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.ts +114 -0
  131. package/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.ts +895 -0
  132. package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +7 -9
  133. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +96 -227
  134. package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +93 -214
  135. package/Server/Utils/Workspace/Slack/Actions/Auth.ts +30 -3
  136. package/Server/Utils/Workspace/Slack/Actions/Authorization.ts +99 -0
  137. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +117 -283
  138. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +101 -196
  139. package/Server/Utils/Workspace/Slack/Actions/ReactionNote.ts +329 -0
  140. package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +111 -287
  141. package/Server/Utils/Workspace/Slack/Slack.ts +86 -17
  142. package/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
  143. package/Server/Utils/Workspace/Workspace.ts +171 -54
  144. package/Server/Utils/Workspace/WorkspaceActionAuthorization.ts +252 -0
  145. package/Server/Utils/Workspace/WorkspaceBase.ts +39 -0
  146. package/Server/Utils/Workspace/WorkspaceOAuthState.ts +280 -0
  147. package/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.ts +96 -0
  148. package/Server/Utils/Workspace/WorkspaceReactionNote.ts +620 -0
  149. package/Tests/App/Dashboard/AttributesJSONDashboardSurfaces.test.tsx +496 -0
  150. package/Tests/App/Dashboard/AutoRemediationRulesTableCommandRunners.test.tsx +152 -0
  151. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +32 -0
  152. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +794 -0
  153. package/Tests/App/Dashboard/CreateOAuthWorkflowVariableModal.test.tsx +840 -0
  154. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +653 -0
  155. package/Tests/App/Dashboard/DatabaseMonitorStepForm.test.tsx +197 -0
  156. package/Tests/App/Dashboard/DatabaseMonitorView.test.tsx +376 -0
  157. package/Tests/App/Dashboard/DisabledWarningRobustness.test.tsx +262 -0
  158. package/Tests/App/Dashboard/EventNotes.test.tsx +2080 -0
  159. package/Tests/App/Dashboard/EventNotesPages.test.tsx +486 -0
  160. package/Tests/App/Dashboard/EventNotesUtil.test.ts +933 -0
  161. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +218 -0
  162. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +665 -0
  163. package/Tests/App/Dashboard/IncidentEpisodeFeedPublicNoteNotifyDefault.test.tsx +494 -0
  164. package/Tests/App/Dashboard/IncidentEpisodeOverviewNotifyDefault.test.tsx +706 -0
  165. package/Tests/App/Dashboard/IncidentFeedPublicNoteNotifyDefault.test.tsx +472 -0
  166. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +858 -0
  167. package/Tests/App/Dashboard/IncomingEmailAddressSettings.test.tsx +567 -0
  168. package/Tests/App/Dashboard/InvestigationClusterToolFormat.test.ts +241 -0
  169. package/Tests/App/Dashboard/InvestigationEvidenceKubectl.test.tsx +322 -0
  170. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +12 -0
  171. package/Tests/App/Dashboard/InvestigationPanelClusterAccess.test.tsx +476 -0
  172. package/Tests/App/Dashboard/InvestigationRunDetailsKubectl.test.tsx +383 -0
  173. package/Tests/App/Dashboard/KubectlActivitySummary.test.tsx +366 -0
  174. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +2886 -0
  175. package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +2688 -0
  176. package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +915 -0
  177. package/Tests/App/Dashboard/LayerDateTimeFieldTimezoneName.test.tsx +66 -0
  178. package/Tests/App/Dashboard/LogMonitorPreview.test.tsx +174 -0
  179. package/Tests/App/Dashboard/MonitorFeedRefresh.test.tsx +279 -0
  180. package/Tests/App/Dashboard/MonitorMetricQueryConfig.test.ts +244 -0
  181. package/Tests/App/Dashboard/MonitorOpenWorkCard.test.tsx +381 -0
  182. package/Tests/App/Dashboard/MonitorOpenWorkHook.test.tsx +784 -0
  183. package/Tests/App/Dashboard/MonitorOverviewData.test.tsx +2199 -0
  184. package/Tests/App/Dashboard/MonitorOverviewHero.test.tsx +1169 -0
  185. package/Tests/App/Dashboard/MonitorOverviewInput.test.ts +1127 -0
  186. package/Tests/App/Dashboard/MonitorOverviewPage.test.tsx +2136 -0
  187. package/Tests/App/Dashboard/MonitorOverviewSelectReadable.test.ts +368 -0
  188. package/Tests/App/Dashboard/MonitorOverviewStatBar.test.tsx +526 -0
  189. package/Tests/App/Dashboard/MonitorOverviewSupportingCards.test.tsx +542 -0
  190. package/Tests/App/Dashboard/MonitorOwnersCombine.test.ts +299 -0
  191. package/Tests/App/Dashboard/MonitorProbesCard.test.tsx +602 -0
  192. package/Tests/App/Dashboard/MonitorResponseTimeCard.test.tsx +386 -0
  193. package/Tests/App/Dashboard/MonitorSetupAndConnectionCards.test.tsx +612 -0
  194. package/Tests/App/Dashboard/MonitorStatusChangesCard.test.tsx +257 -0
  195. package/Tests/App/Dashboard/MonitorSummaryTestMonitorButton.test.tsx +360 -1
  196. package/Tests/App/Dashboard/MonitorUptimeHistoryCard.test.tsx +835 -0
  197. package/Tests/App/Dashboard/MonitorUptimeSummaryHook.test.tsx +971 -0
  198. package/Tests/App/Dashboard/MonitorViewLayout.test.tsx +580 -0
  199. package/Tests/App/Dashboard/OnCallLayerRestrictionTimesPreserved.test.tsx +441 -0
  200. package/Tests/App/Dashboard/OnCallNavbar.test.tsx +214 -0
  201. package/Tests/App/Dashboard/OnCallSchedulePreviewCalendarDisplay.test.tsx +1389 -0
  202. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +205 -1
  203. package/Tests/App/Dashboard/OnCallScheduleTimeInputsSettingsZone.test.tsx +198 -0
  204. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +524 -0
  205. package/Tests/App/Dashboard/RemediationSuggestionCardApproval.test.tsx +702 -0
  206. package/Tests/App/Dashboard/RemoveUserFromProject.test.tsx +427 -0
  207. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +4 -0
  208. package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +3 -1
  209. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +166 -0
  210. package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +622 -0
  211. package/Tests/App/Dashboard/ScheduledMaintenanceChangeStateNotifyDefault.test.tsx +1491 -0
  212. package/Tests/App/Dashboard/ScheduledMaintenanceFeedPublicNoteNotifyDefault.test.tsx +489 -0
  213. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewNotifyDefault.test.tsx +909 -0
  214. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +14 -0
  215. package/Tests/App/Dashboard/ScheduledMaintenanceWindowStatsTranslation.test.tsx +307 -0
  216. package/Tests/App/Dashboard/SecurityEventAttributeColumnPicker.test.tsx +542 -0
  217. package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +174 -0
  218. package/Tests/App/Dashboard/SecurityEventConnectionsEmptyState.test.tsx +973 -212
  219. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +491 -52
  220. package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +301 -0
  221. package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +158 -1
  222. package/Tests/App/Dashboard/SecurityEventProviderTile.test.tsx +869 -0
  223. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +959 -94
  224. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +2 -0
  225. package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +274 -1
  226. package/Tests/App/Dashboard/TeamMembershipRemoval.test.ts +900 -0
  227. package/Tests/App/Dashboard/TeamMembershipRemovalPages.test.tsx +460 -0
  228. package/Tests/App/Dashboard/TimezoneElements.test.tsx +321 -0
  229. package/Tests/App/Dashboard/TimezoneSelectButton.test.tsx +634 -0
  230. package/Tests/App/Dashboard/TraceExplorer.test.tsx +260 -0
  231. package/Tests/App/Dashboard/TraceMonitorPreview.test.tsx +182 -0
  232. package/Tests/App/Dashboard/UserTimezoneInit.test.tsx +544 -0
  233. package/Tests/App/Dashboard/WorkflowVariableModals.test.tsx +1630 -0
  234. package/Tests/App/Dashboard/WorkflowVariableRoutes.test.tsx +603 -0
  235. package/Tests/App/Dashboard/WorkflowVariableTokenStatus.test.tsx +589 -0
  236. package/Tests/App/Dashboard/WorkflowVariableUtil.test.ts +1543 -0
  237. package/Tests/App/Dashboard/WorkflowVariableView.test.tsx +3227 -0
  238. package/Tests/App/Dashboard/WorkflowVariablesTable.test.tsx +1277 -395
  239. package/Tests/App/StatusPage/StatusPageOverviewMonitorGroupUptime.test.tsx +714 -0
  240. package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.NewYork.test.tsx +1009 -0
  241. package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.test.tsx +1092 -0
  242. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +5 -0
  243. package/Tests/Models/DatabaseModels/KubernetesClusterAiAccessColumns.test.ts +520 -0
  244. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +66 -2
  245. package/Tests/Models/DatabaseModels/WorkspaceProjectAuthTokenAccessControl.test.ts +236 -0
  246. package/Tests/Models/IncidentEpisodePublicNoteNotifyColumnContract.test.ts +103 -0
  247. package/Tests/Models/IncidentPublicNoteNotifyColumnContract.test.ts +96 -0
  248. package/Tests/Models/ScheduledMaintenancePublicNoteNotifyColumnContract.test.ts +111 -0
  249. package/Tests/Server/API/AIInvestigationAPI.test.ts +976 -0
  250. package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +836 -0
  251. package/Tests/Server/API/CommonAPIAssertCanCreateTable.test.ts +565 -0
  252. package/Tests/Server/API/CommonAPIAssertCanReadTable.test.ts +282 -0
  253. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +1744 -0
  254. package/Tests/Server/API/LogHistogramAPI.test.ts +371 -0
  255. package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +125 -30
  256. package/Tests/Server/API/MicrosoftTeamsOAuthState.test.ts +1121 -0
  257. package/Tests/Server/API/MonitorUptimeSummaryAPI.test.ts +685 -0
  258. package/Tests/Server/API/SessionReplayAPI.test.ts +602 -1
  259. package/Tests/Server/API/SlackChannelCacheAPI.test.ts +358 -0
  260. package/Tests/Server/API/SlackOAuthState.test.ts +688 -0
  261. package/Tests/Server/API/StatusPageOverviewMonitorGroupUptime.test.ts +527 -0
  262. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +2 -0
  263. package/Tests/Server/API/StatusPageUptimeAPI.test.ts +427 -0
  264. package/Tests/Server/API/UserProjectsAPI.test.ts +4 -0
  265. package/Tests/Server/API/WorkflowVariableAPI.test.ts +404 -0
  266. package/Tests/Server/API/WorkspaceOAuthTestHelpers.ts +289 -0
  267. package/Tests/Server/API/WorkspaceSendTestNotificationRoutes.test.ts +2064 -0
  268. package/Tests/Server/Infrastructure/GlobalCache.test.ts +86 -0
  269. package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +589 -0
  270. package/Tests/Server/Infrastructure/Postgres/AddOAuth2WorkflowVariablesMigration.test.ts +254 -0
  271. package/Tests/Server/Middleware/BlockedUserMiddleware.test.ts +403 -0
  272. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +13 -0
  273. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +2 -0
  274. package/Tests/Server/Middleware/RemovedProjectMemberAuthorization.test.ts +469 -0
  275. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +77 -1
  276. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +211 -0
  277. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +358 -0
  278. package/Tests/Server/Middleware/UserAuthorization.test.ts +97 -0
  279. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +2 -0
  280. package/Tests/Server/Services/AccessTokenProjectUserGrant.test.ts +14 -2
  281. package/Tests/Server/Services/AccessTokenRemovedMemberAccess.test.ts +1044 -0
  282. package/Tests/Server/Services/AddOwnersIdempotency.test.ts +5 -0
  283. package/Tests/Server/Services/AlertEpisodeMemberCreatePermission.test.ts +139 -0
  284. package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +1361 -0
  285. package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +375 -0
  286. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +2384 -0
  287. package/Tests/Server/Services/FindOwnersProjectMembership.test.ts +267 -0
  288. package/Tests/Server/Services/InboundEmail/SendGridInboundProviderAddress.test.ts +90 -0
  289. package/Tests/Server/Services/IncidentEpisodeMemberCreatePermission.test.ts +143 -0
  290. package/Tests/Server/Services/IncidentEpisodePublicNoteNotifyDefault.test.ts +805 -0
  291. package/Tests/Server/Services/IncidentPublicNoteNotifyDefault.test.ts +724 -0
  292. package/Tests/Server/Services/IncidentRoleProjectMembership.test.ts +233 -0
  293. package/Tests/Server/Services/KubernetesClusterAiAccessAgentRunnerAssignments.test.ts +372 -0
  294. package/Tests/Server/Services/KubernetesClusterAiAccessBindingGuard.test.ts +1876 -0
  295. package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +620 -0
  296. package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +421 -0
  297. package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +3662 -0
  298. package/Tests/Server/Services/KubernetesClusterAiAccessSubjectClusters.test.ts +405 -0
  299. package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +975 -0
  300. package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +497 -0
  301. package/Tests/Server/Services/KubernetesClusterServiceDeleteSettlesRemediation.test.ts +328 -0
  302. package/Tests/Server/Services/LogAggregationService.test.ts +99 -0
  303. package/Tests/Server/Services/MonitorServiceIncomingEmailCustomAddress.test.ts +447 -0
  304. package/Tests/Server/Services/MonitorServiceProbeFlagSync.test.ts +1334 -0
  305. package/Tests/Server/Services/MonitorServiceProbeStatusGrouping.test.ts +1640 -0
  306. package/Tests/Server/Services/MonitorServiceRefreshCurrentStatus.test.ts +225 -0
  307. package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +8 -8
  308. package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +264 -1
  309. package/Tests/Server/Services/MonitorStatusTimelineMergedDowntime.test.ts +284 -0
  310. package/Tests/Server/Services/MonitorUptimeSummaryService.test.ts +1043 -0
  311. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +5 -0
  312. package/Tests/Server/Services/OnCallUndeliverablePageOwnerMembership.test.ts +359 -0
  313. package/Tests/Server/Services/ProbeServiceDashboardLink.test.ts +191 -0
  314. package/Tests/Server/Services/ProjectOwnersProjectMembership.test.ts +221 -0
  315. package/Tests/Server/Services/RuleRun/IncidentAlertMaintenanceRuleEnginesRun.test.ts +5 -0
  316. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +28 -0
  317. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +9 -1
  318. package/Tests/Server/Services/RunnerJobEnqueueAiCommandAgentRunner.test.ts +230 -0
  319. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +978 -0
  320. package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +866 -0
  321. package/Tests/Server/Services/RunnerJobSubmitResultRedaction.test.ts +322 -0
  322. package/Tests/Server/Services/RunnerJobSubmitResultUnstorableText.test.ts +357 -0
  323. package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +227 -0
  324. package/Tests/Server/Services/RunnerServiceAgentName.test.ts +581 -0
  325. package/Tests/Server/Services/ScheduledMaintenancePublicNoteNotifyDefault.test.ts +802 -0
  326. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +53 -0
  327. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +5 -0
  328. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +12 -0
  329. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +1176 -1
  330. package/Tests/Server/Services/TeamMemberBillingSeatSync.test.ts +9 -0
  331. package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +18 -0
  332. package/Tests/Server/Services/TeamMemberProjectMembership.test.ts +255 -0
  333. package/Tests/Server/Services/TeamMemberRemovalRevokesProjectAccess.test.ts +600 -0
  334. package/Tests/Server/Services/TeamMemberResourceCleanup.test.ts +731 -0
  335. package/Tests/Server/Services/TeamMemberWorkspaceAccountCleanup.test.ts +164 -0
  336. package/Tests/Server/Services/TraceAggregationService.test.ts +64 -0
  337. package/Tests/Server/Services/UserServiceBlockedUser.test.ts +293 -0
  338. package/Tests/Server/Services/WorkflowVariableOAuthHooks.test.ts +1016 -0
  339. package/Tests/Server/Services/WorkflowVariableOAuthPostgres.test.ts +543 -0
  340. package/Tests/Server/Services/WorkflowVariableRenameUniqueness.test.ts +16 -5
  341. package/Tests/Server/Services/WorkspaceNotificationRuleNoteDelivery.test.ts +473 -0
  342. package/Tests/Server/Services/WorkspaceNotificationRuleSendTestNotification.test.ts +3690 -0
  343. package/Tests/Server/Services/WorkspaceProjectAuthTokenMiscDataSecrets.test.ts +374 -0
  344. package/Tests/Server/Services/WorkspaceProjectAuthTokenSlackChannelCache.test.ts +143 -0
  345. package/Tests/Server/Types/Database/Permissions/WorkflowVariableOAuthColumnPermission.test.ts +250 -0
  346. package/Tests/Server/Types/Database/QueryHelperJsonContains.test.ts +96 -0
  347. package/Tests/Server/Types/Markdown.test.ts +1033 -0
  348. package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +285 -0
  349. package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +159 -1
  350. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +752 -0
  351. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +1840 -0
  352. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +1067 -0
  353. package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +355 -0
  354. package/Tests/Server/Utils/AI/PostIncidentStatusUpdateTool.test.ts +496 -0
  355. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +305 -0
  356. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +1365 -0
  357. package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +639 -0
  358. package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +1585 -0
  359. package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +1065 -0
  360. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +2138 -1
  361. package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +1035 -0
  362. package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +517 -0
  363. package/Tests/Server/Utils/AI/ResourceTools.test.ts +27 -1
  364. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1371 -24
  365. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorRollbackRace.test.ts +962 -0
  366. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +518 -0
  367. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +939 -2
  368. package/Tests/Server/Utils/Database/ProjectScopedRelationReferences.test.ts +371 -0
  369. package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +64 -0
  370. package/Tests/Server/Utils/Monitor/DatabaseMetricObservationRemediation.test.ts +102 -0
  371. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +362 -0
  372. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +55 -0
  373. package/Tests/Server/Utils/Monitor/MonitorEmailAddressRedaction.test.ts +255 -0
  374. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +347 -0
  375. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +203 -0
  376. package/Tests/Server/Utils/Monitor/ProbeMonitorsNotification.test.ts +1147 -0
  377. package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +79 -0
  378. package/Tests/Server/Utils/ResourceFeed/ResourceFeedUtil.test.ts +21 -1
  379. package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +155 -1
  380. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +288 -0
  381. package/Tests/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.test.ts +304 -0
  382. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +40 -3
  383. package/Tests/Server/Utils/Workflow/OAuth2TokenClient.test.ts +702 -0
  384. package/Tests/Server/Utils/Workflow/OAuth2TokenClientHttpIntegration.test.ts +507 -0
  385. package/Tests/Server/Utils/Workflow/WorkflowVariableOAuthToken.test.ts +831 -0
  386. package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +63 -1
  387. package/Tests/Server/Utils/Workspace/MicrosoftTeamsAppAccessToken.test.ts +256 -0
  388. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +60 -0
  389. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChatActionAuthorization.test.ts +442 -0
  390. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +44 -2
  391. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +55 -0
  392. package/Tests/Server/Utils/Workspace/MicrosoftTeamsReactionNoteSync.test.ts +1522 -0
  393. package/Tests/Server/Utils/Workspace/MicrosoftTeamsServiceUrl.test.ts +130 -0
  394. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +4 -4
  395. package/Tests/Server/Utils/Workspace/Slack/SlackActionAuthorization.test.ts +691 -0
  396. package/Tests/Server/Utils/Workspace/Slack/SlackMessageByTimestamp.test.ts +253 -0
  397. package/Tests/Server/Utils/Workspace/Slack/SlackReactionNote.test.ts +574 -0
  398. package/Tests/Server/Utils/Workspace/WorkspaceActionAuthorization.test.ts +488 -0
  399. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +861 -0
  400. package/Tests/Server/Utils/Workspace/WorkspaceMessageAuthor.test.ts +635 -0
  401. package/Tests/Server/Utils/Workspace/WorkspaceOAuthState.test.ts +548 -0
  402. package/Tests/Server/Utils/Workspace/WorkspaceReactionNote.test.ts +839 -0
  403. package/Tests/Server/Utils/Workspace/WorkspaceSendErrorMessage.test.ts +725 -0
  404. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +283 -9
  405. package/Tests/Types/DateBrowserLocalWallClock.test.ts +280 -0
  406. package/Tests/Types/DateLocalShortMonthNameFallback.test.ts +389 -0
  407. package/Tests/Types/DateUserTimezone.test.ts +177 -4
  408. package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +306 -0
  409. package/Tests/Types/Kubernetes/KubernetesClusterAiAccess.test.ts +283 -0
  410. package/Tests/Types/Kubernetes/KubernetesRunnerPostureWriteScope.test.ts +68 -0
  411. package/Tests/Types/Monitor/DatabaseMonitor/SqlServerPlatform.test.ts +144 -0
  412. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +160 -0
  413. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +53 -0
  414. package/Tests/Types/Runbook/RunbookStepType.test.ts +91 -16
  415. package/Tests/Types/Runner/RunnerLiveStatus.test.ts +14 -3
  416. package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +15 -0
  417. package/Tests/Types/StatusPage/PublicNoteSubscriberNotificationDefault.test.ts +1868 -0
  418. package/Tests/Types/Timezone.test.ts +5 -0
  419. package/Tests/Types/TimezoneAlias.test.ts +1034 -0
  420. package/Tests/Types/Workflow/JiraTemplateFixtures.ts +833 -0
  421. package/Tests/Types/Workflow/JiraTemplateScripts.test.ts +5414 -0
  422. package/Tests/Types/Workflow/JiraTemplateSimulation.test.ts +4803 -0
  423. package/Tests/Types/Workflow/JiraTemplates.test.ts +5429 -0
  424. package/Tests/Types/Workflow/Templates.test.ts +79 -4
  425. package/Tests/Types/Workflow/WorkflowVariableOAuth.test.ts +520 -0
  426. package/Tests/Types/Workspace/WorkspaceNoteReaction.test.ts +177 -0
  427. package/Tests/UI/Components/AttributesJSON/AttributesJSONView.test.tsx +390 -0
  428. package/Tests/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.test.tsx +566 -0
  429. package/Tests/UI/Components/AttributesJSON/JSONHighlight.test.ts +131 -0
  430. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +276 -1
  431. package/Tests/UI/Components/Calendar/Calendar.test.tsx +1611 -0
  432. package/Tests/UI/Components/Calendar/CalendarDayLayout.test.ts +1486 -0
  433. package/Tests/UI/Components/Calendar/CalendarDayLayoutDst.test.ts +632 -0
  434. package/Tests/UI/Components/Calendar/CalendarDst.test.tsx +388 -0
  435. package/Tests/UI/Components/Charts/ChartGroupPlotFloor.test.tsx +170 -0
  436. package/Tests/UI/Components/Charts/HistogramSelection.test.ts +282 -0
  437. package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +6 -0
  438. package/Tests/UI/Components/CustomTimeRangeModalTimezoneTranslation.test.tsx +285 -0
  439. package/Tests/UI/Components/Dropdown.test.tsx +999 -2
  440. package/Tests/UI/Components/Forms/FormFieldCheckboxTranslation.test.tsx +216 -0
  441. package/Tests/UI/Components/Forms/FormFieldTimezoneDescriptionTranslation.test.tsx +461 -0
  442. package/Tests/UI/Components/Forms/IncidentEpisodePublicNoteNotifyPayload.test.tsx +641 -0
  443. package/Tests/UI/Components/Forms/IncidentPublicNoteNotifyPayload.test.tsx +346 -0
  444. package/Tests/UI/Components/Forms/ScheduledMaintenancePublicNoteNotifyPayload.test.tsx +493 -0
  445. package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +911 -2
  446. package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.NewYork.test.tsx +730 -0
  447. package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.Tokyo.test.tsx +506 -0
  448. package/Tests/UI/Components/HistogramClickToZoom.test.tsx +619 -0
  449. package/Tests/UI/Components/LogDetailsPanelAttributesJSON.test.tsx +248 -0
  450. package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +47 -1
  451. package/Tests/UI/Components/LogsViewerSmallScreens.test.tsx +620 -0
  452. package/Tests/UI/Components/ModelPageModelChange.test.tsx +242 -3
  453. package/Tests/UI/Components/ModelTable/BaseModelTableDeleteConfirmation.test.tsx +521 -0
  454. package/Tests/UI/Components/MonitorGraphs/Uptime.test.tsx +406 -2
  455. package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +169 -2
  456. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +266 -0
  457. package/Tests/UI/Components/TelemetryViewer/TelemetryViewerSmallScreens.test.tsx +439 -0
  458. package/Tests/UI/Components/TimezoneDropdownLegacyValues.test.tsx +704 -0
  459. package/Tests/UI/Components/UseHistogramZoom.test.tsx +89 -1
  460. package/Tests/UI/Components/UseLogsHistogram.test.tsx +175 -0
  461. package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +5 -5
  462. package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +28 -0
  463. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +51 -0
  464. package/Tests/UI/Utils/Timezone.test.ts +747 -33
  465. package/Tests/UI/Workspace/SendTestNotificationButton.test.tsx +1854 -0
  466. package/Tests/UI/Workspace/WorkspaceCardsSendTestNotification.test.tsx +1864 -0
  467. package/Tests/Utils/AI/InvestigationReport.test.ts +435 -7
  468. package/Tests/Utils/AiRemediation/KubectlAllowlistValidatorParity.test.ts +1084 -0
  469. package/Tests/Utils/AiRemediation/KubectlOutputRedactor.test.ts +2531 -0
  470. package/Tests/Utils/AiRemediation/KubectlPolicy.test.ts +1895 -0
  471. package/Tests/Utils/AiRemediation/KubectlPolicyAllowlist.test.ts +428 -0
  472. package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +828 -0
  473. package/Tests/Utils/AiRemediation/KubectlPolicyCustomResourceScope.test.ts +573 -0
  474. package/Tests/Utils/AiRemediation/KubectlPolicyDenials.test.ts +1525 -0
  475. package/Tests/Utils/AiRemediation/KubectlPolicyExposeOverrides.test.ts +275 -0
  476. package/Tests/Utils/AiRemediation/KubectlPolicyNodePatch.test.ts +428 -0
  477. package/Tests/Utils/AiRemediation/KubectlPolicyObjectWords.test.ts +195 -0
  478. package/Tests/Utils/AiRemediation/KubectlPolicyPatchParents.test.ts +637 -0
  479. package/Tests/Utils/AiRemediation/KubectlPolicyPodSecurityBaseline.test.ts +322 -0
  480. package/Tests/Utils/AiRemediation/KubectlPolicySafeWriteScope.test.ts +1026 -0
  481. package/Tests/Utils/AiRemediation/KubectlWaitBudget.test.ts +267 -0
  482. package/Tests/Utils/AiRemediation/KubectlWriteScope.test.ts +390 -0
  483. package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +777 -0
  484. package/Tests/Utils/AiRemediation/KubectlWriteScopeClusterScoped.test.ts +613 -0
  485. package/Tests/Utils/AiRemediation/KubectlWriteScopeEntryPoint.test.ts +576 -0
  486. package/Tests/Utils/AiRemediation/KubectlWriteScopeKubectlSpellings.test.ts +752 -0
  487. package/Tests/Utils/AiRemediation/KubectlWriteScopeObjectWords.test.ts +358 -0
  488. package/Tests/Utils/AiRemediation/KubectlWriteScopeOverrides.test.ts +293 -0
  489. package/Tests/Utils/AiRemediation/KubectlWriteScopeParityCases.ts +598 -0
  490. package/Tests/Utils/AiRemediation/KubectlWriteScopePolicyParity.test.ts +403 -0
  491. package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +154 -5
  492. package/Tests/Utils/Monitor/IncomingEmailMonitorAddress.test.ts +478 -0
  493. package/Tests/Utils/Monitor/MonitorCheckScheduleUtil.test.ts +646 -0
  494. package/Tests/Utils/Monitor/MonitorOverviewCriteriaUtil.test.ts +292 -0
  495. package/Tests/Utils/Monitor/MonitorOverviewFamily.test.ts +274 -0
  496. package/Tests/Utils/Monitor/MonitorOverviewPresentationUtil.test.ts +3100 -0
  497. package/Tests/Utils/Monitor/MonitorOverviewProbeUtil.test.ts +1412 -0
  498. package/Tests/Utils/Monitor/MonitorOverviewTargetUtil.test.ts +374 -0
  499. package/Tests/Utils/Monitor/MonitorStatusHistoryUtil.test.ts +332 -0
  500. package/Tests/Utils/Monitor/MonitorUptimeSummaryUtil.test.ts +946 -0
  501. package/Tests/Utils/Rum/SessionIdentity.test.ts +94 -0
  502. package/Tests/Utils/Rum/SessionReplayWafCompatibility.test.ts +840 -0
  503. package/Tests/Utils/Rum/SessionReplayWireEncoding.test.ts +208 -0
  504. package/Tests/Utils/Rum/UserFlow.test.ts +1010 -0
  505. package/Tests/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.test.ts +217 -0
  506. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +1272 -1
  507. package/Tests/Utils/StatusPage/UptimeDailyAggregateTotals.test.ts +298 -0
  508. package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +649 -1
  509. package/Tests/Utils/StatusPage/UptimeDailyAggregateWindow.test.ts +314 -0
  510. package/Tests/Utils/Telemetry/AttributesJSON.test.ts +454 -0
  511. package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +418 -2
  512. package/Tests/Utils/Uptime/DayUptimeGraphUtilMidnightDst.test.ts +160 -0
  513. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Auckland.test.ts +45 -0
  514. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Kolkata.test.ts +40 -0
  515. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.London.test.ts +147 -0
  516. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.NewYork.test.ts +48 -0
  517. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Tokyo.test.ts +39 -0
  518. package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezoneCases.ts +1015 -0
  519. package/Tests/Utils/Uptime/UptimeUtil.test.ts +75 -0
  520. package/Tests/Utils/Uptime/UptimeUtilChronologicalOrder.test.ts +1279 -0
  521. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +203 -5
  522. package/Types/Date.ts +121 -7
  523. package/Types/Email/EmailMessage.ts +8 -0
  524. package/Types/Email/EmailTemplateType.ts +1 -0
  525. package/Types/Exception/ExceptionMessages.ts +1 -0
  526. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +488 -0
  527. package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +54 -0
  528. package/Types/Kubernetes/KubernetesClusterAiAccessToolNames.ts +17 -0
  529. package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +9 -0
  530. package/Types/Monitor/DatabaseMonitor/SqlServerPlatform.ts +117 -0
  531. package/Types/Monitor/KubernetesAlertTemplates.ts +6 -5
  532. package/Types/Monitor/MonitorStepExceptionMonitor.ts +56 -0
  533. package/Types/Monitor/MonitorUptimeSummary.ts +113 -0
  534. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +11 -0
  535. package/Types/Rum/SessionReplay.ts +55 -2
  536. package/Types/Rum/SessionReplayApi.ts +44 -1
  537. package/Types/Rum/UserFlow.ts +187 -0
  538. package/Types/Runbook/RunbookStepType.ts +10 -0
  539. package/Types/Runbook/RunnerJobOrigin.ts +15 -0
  540. package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +6 -2
  541. package/Types/StatusPage/MergedDowntimeTotals.ts +17 -0
  542. package/Types/StatusPage/PublicNoteSubscriberNotificationDefault.ts +148 -0
  543. package/Types/StatusPage/UptimeDailyAggregate.ts +15 -0
  544. package/Types/Telemetry/TelemetryIngestSurface.ts +9 -0
  545. package/Types/Timezone.ts +5 -0
  546. package/Types/TimezoneAlias.ts +385 -0
  547. package/Types/Workflow/Templates.ts +3041 -2
  548. package/Types/Workflow/WorkflowVariableOAuth.ts +476 -0
  549. package/Types/Workspace/WorkspaceNoteReaction.ts +132 -0
  550. package/Typings/Index.d.ts +24 -0
  551. package/UI/Components/AttributesJSON/AttributesJSONPreferences.ts +175 -0
  552. package/UI/Components/AttributesJSON/AttributesJSONView.tsx +138 -0
  553. package/UI/Components/AttributesJSON/AttributesViewToggle.tsx +79 -0
  554. package/UI/Components/AttributesJSON/BracesGlyph.tsx +30 -0
  555. package/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.tsx +491 -0
  556. package/UI/Components/AttributesJSON/JSONHighlight.ts +77 -0
  557. package/UI/Components/Calendar/Calendar.css +83 -0
  558. package/UI/Components/Calendar/Calendar.tsx +136 -7
  559. package/UI/Components/Calendar/CalendarDayLayout.ts +236 -0
  560. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -1
  561. package/UI/Components/Charts/Utils/HistogramSelection.ts +117 -0
  562. package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +269 -0
  563. package/UI/Components/Charts/Utils/useHistogramZoom.ts +26 -2
  564. package/UI/Components/Date/CustomTimeRangeModal.tsx +26 -3
  565. package/UI/Components/Dropdown/Dropdown.tsx +58 -9
  566. package/UI/Components/Forms/Fields/FormField.tsx +49 -14
  567. package/UI/Components/Graphs/DayUptimeGraph.tsx +166 -25
  568. package/UI/Components/Graphs/UptimeBarTooltip.tsx +10 -2
  569. package/UI/Components/Graphs/UptimeDaySummary.tsx +8 -0
  570. package/UI/Components/LogsViewer/LogsViewer.tsx +52 -5
  571. package/UI/Components/LogsViewer/components/ColumnSelector.tsx +13 -2
  572. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +13 -1
  573. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +91 -76
  574. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +15 -1
  575. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +28 -106
  576. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +54 -6
  577. package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +8 -2
  578. package/UI/Components/LogsViewer/useLogsHistogram.ts +39 -6
  579. package/UI/Components/ModelTable/BaseModelTable.tsx +66 -14
  580. package/UI/Components/MonitorGraphs/Uptime.tsx +12 -0
  581. package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +10 -2
  582. package/UI/Components/Page/ModelPage.tsx +59 -3
  583. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +69 -5
  584. package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +12 -1
  585. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +15 -1
  586. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +36 -104
  587. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +9 -1
  588. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +19 -1
  589. package/UI/Utils/Timezone.ts +69 -28
  590. package/Utils/AI/InvestigationReport.ts +195 -25
  591. package/Utils/AiRemediation/KubectlOutputRedactor.ts +1724 -0
  592. package/Utils/AiRemediation/KubectlPolicy.ts +4136 -0
  593. package/Utils/AiRemediation/KubectlWaitBudget.ts +156 -0
  594. package/Utils/AiRemediation/KubectlWriteScope.ts +1964 -0
  595. package/Utils/Dashboard/Components/DashboardClockComponent.ts +7 -6
  596. package/Utils/Monitor/IncomingEmailMonitorAddress.ts +292 -0
  597. package/Utils/Monitor/MonitorCheckScheduleUtil.ts +654 -0
  598. package/Utils/Monitor/MonitorOverviewCriteriaUtil.ts +188 -0
  599. package/Utils/Monitor/MonitorOverviewFamily.ts +263 -0
  600. package/Utils/Monitor/MonitorOverviewPresentationUtil.ts +1958 -0
  601. package/Utils/Monitor/MonitorOverviewProbeUtil.ts +979 -0
  602. package/Utils/Monitor/MonitorOverviewTargetUtil.ts +299 -0
  603. package/Utils/Monitor/MonitorStatusHistoryUtil.ts +201 -0
  604. package/Utils/Monitor/MonitorUptimeSummaryUtil.ts +638 -0
  605. package/Utils/Rum/SessionIdentity.ts +44 -0
  606. package/Utils/Rum/SessionReplayWireEncoding.ts +137 -0
  607. package/Utils/Rum/UserFlow.ts +1527 -0
  608. package/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.ts +107 -0
  609. package/Utils/StatusPage/ResourceUptime.ts +99 -0
  610. package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +197 -0
  611. package/Utils/Telemetry/AttributesJSON.ts +367 -0
  612. package/Utils/Uptime/DayUptimeGraphUtil.ts +165 -0
  613. package/Utils/Uptime/UptimeUtil.ts +122 -38
  614. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +8 -1
  615. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  616. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  617. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  618. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +8 -1
  619. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  620. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +10 -3
  621. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  622. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +10 -3
  623. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  624. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +434 -0
  625. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  626. package/build/dist/Models/DatabaseModels/Monitor.js +49 -0
  627. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  628. package/build/dist/Models/DatabaseModels/Runner.js +4 -4
  629. package/build/dist/Models/DatabaseModels/Runner.js.map +1 -1
  630. package/build/dist/Models/DatabaseModels/RunnerJob.js +66 -0
  631. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  632. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +11 -3
  633. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  634. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +487 -3
  635. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  636. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js +64 -16
  637. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  638. package/build/dist/Server/API/AIInvestigationAPI.js +205 -0
  639. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  640. package/build/dist/Server/API/AutoRemediationAPI.js +115 -2
  641. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  642. package/build/dist/Server/API/CommonAPI.js +49 -0
  643. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  644. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +564 -0
  645. package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -0
  646. package/build/dist/Server/API/MicrosoftTeamsAPI.js +465 -176
  647. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  648. package/build/dist/Server/API/MonitorAPI.js +107 -1
  649. package/build/dist/Server/API/MonitorAPI.js.map +1 -1
  650. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +5 -16
  651. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -1
  652. package/build/dist/Server/API/SlackAPI.js +322 -78
  653. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  654. package/build/dist/Server/API/StatusPageAPI.js +123 -17
  655. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  656. package/build/dist/Server/API/TelemetryAPI.js +144 -5
  657. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  658. package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js +1 -0
  659. package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js.map +1 -1
  660. package/build/dist/Server/API/WorkflowVariableAPI.js +109 -0
  661. package/build/dist/Server/API/WorkflowVariableAPI.js.map +1 -0
  662. package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
  663. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  664. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js +38 -0
  665. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js.map +1 -0
  666. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js +54 -0
  667. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js.map +1 -0
  668. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js +14 -0
  669. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js.map +1 -0
  670. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js +12 -0
  671. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js.map +1 -0
  672. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js +32 -0
  673. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js.map +1 -0
  674. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  675. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  676. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +21 -0
  677. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  678. package/build/dist/Server/Middleware/TelemetryIngest.js +32 -2
  679. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  680. package/build/dist/Server/Middleware/UserAuthorization.js +39 -6
  681. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  682. package/build/dist/Server/Services/AIAgentService.js +1 -0
  683. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  684. package/build/dist/Server/Services/AccessTokenService.js +87 -42
  685. package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
  686. package/build/dist/Server/Services/AlertEpisodeMemberService.js +4 -0
  687. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  688. package/build/dist/Server/Services/AlertEpisodeService.js +12 -1
  689. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  690. package/build/dist/Server/Services/AlertService.js +110 -23
  691. package/build/dist/Server/Services/AlertService.js.map +1 -1
  692. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +716 -30
  693. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  694. package/build/dist/Server/Services/AutoRemediationRuleService.js +100 -0
  695. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
  696. package/build/dist/Server/Services/BillingService.js +1 -0
  697. package/build/dist/Server/Services/BillingService.js.map +1 -1
  698. package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js +11 -16
  699. package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js.map +1 -1
  700. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +4 -0
  701. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  702. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +48 -0
  703. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  704. package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js +21 -0
  705. package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js.map +1 -1
  706. package/build/dist/Server/Services/IncidentEpisodeService.js +12 -1
  707. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  708. package/build/dist/Server/Services/IncidentGroupingEngineService.js +8 -0
  709. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  710. package/build/dist/Server/Services/IncidentMemberService.js +14 -0
  711. package/build/dist/Server/Services/IncidentMemberService.js.map +1 -1
  712. package/build/dist/Server/Services/IncidentPublicNoteService.js +48 -0
  713. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  714. package/build/dist/Server/Services/IncidentService.js +99 -21
  715. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  716. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  717. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  718. package/build/dist/Server/Services/IncidentTemplateService.js +68 -4
  719. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  720. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +1470 -0
  721. package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -0
  722. package/build/dist/Server/Services/KubernetesClusterService.js +1097 -0
  723. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  724. package/build/dist/Server/Services/LogAggregationService.js +14 -6
  725. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  726. package/build/dist/Server/Services/MonitorService.js +487 -48
  727. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  728. package/build/dist/Server/Services/MonitorStatusTimelineService.js +540 -3
  729. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  730. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -0
  731. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  732. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -0
  733. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  734. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -0
  735. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  736. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +3 -0
  737. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  738. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +29 -7
  739. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -1
  740. package/build/dist/Server/Services/OnCallSetupReminderService.js +1 -0
  741. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -1
  742. package/build/dist/Server/Services/ProbeService.js +7 -1
  743. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  744. package/build/dist/Server/Services/ProjectService.js +11 -1
  745. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  746. package/build/dist/Server/Services/RunbookCredentialService.js +24 -0
  747. package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -1
  748. package/build/dist/Server/Services/RunbookSecretService.js +44 -0
  749. package/build/dist/Server/Services/RunbookSecretService.js.map +1 -1
  750. package/build/dist/Server/Services/RunnerJobService.js +641 -21
  751. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  752. package/build/dist/Server/Services/RunnerService.js +344 -5
  753. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  754. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +48 -0
  755. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  756. package/build/dist/Server/Services/ScheduledMaintenanceService.js +102 -20
  757. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  758. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +67 -4
  759. package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
  760. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +12 -1
  761. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  762. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -0
  763. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  764. package/build/dist/Server/Services/StatusPageService.js +230 -32
  765. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  766. package/build/dist/Server/Services/StatusPageSubscriberService.js +4 -0
  767. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  768. package/build/dist/Server/Services/TeamMemberService.js +270 -11
  769. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  770. package/build/dist/Server/Services/TraceAggregationService.js +10 -6
  771. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  772. package/build/dist/Server/Services/UserEmailService.js +1 -0
  773. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  774. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1 -0
  775. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  776. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +1 -0
  777. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  778. package/build/dist/Server/Services/UserNotificationRuleService.js +4 -0
  779. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  780. package/build/dist/Server/Services/UserService.js +107 -1
  781. package/build/dist/Server/Services/UserService.js.map +1 -1
  782. package/build/dist/Server/Services/WorkflowVariableService.js +396 -7
  783. package/build/dist/Server/Services/WorkflowVariableService.js.map +1 -1
  784. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +463 -7
  785. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  786. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +144 -2
  787. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  788. package/build/dist/Server/Types/Database/QueryHelper.js +20 -0
  789. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  790. package/build/dist/Server/Types/Markdown.js +394 -27
  791. package/build/dist/Server/Types/Markdown.js.map +1 -1
  792. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +123 -0
  793. package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -0
  794. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +344 -0
  795. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -0
  796. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +587 -0
  797. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -0
  798. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +18 -2
  799. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -1
  800. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1380 -239
  801. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  802. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +1099 -68
  803. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  804. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +88 -6
  805. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  806. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +45 -11
  807. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  808. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +41 -11
  809. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  810. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +11 -4
  811. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
  812. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +4 -2
  813. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  814. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +997 -141
  815. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  816. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +299 -4
  817. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  818. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +120 -0
  819. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -0
  820. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +160 -1
  821. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  822. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +1 -0
  823. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  824. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +59 -12
  825. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  826. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +5 -1
  827. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  828. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +84 -22
  829. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  830. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +70 -5
  831. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -1
  832. package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js +249 -0
  833. package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js.map +1 -0
  834. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1 -0
  835. package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -1
  836. package/build/dist/Server/Utils/PushNotificationUtil.js +27 -0
  837. package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
  838. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +26 -3
  839. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -1
  840. package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +38 -3
  841. package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -1
  842. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +143 -23
  843. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  844. package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js +406 -0
  845. package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js.map +1 -0
  846. package/build/dist/Server/Utils/StartServer.js +4 -3
  847. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  848. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +382 -0
  849. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -0
  850. package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js +1 -0
  851. package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js.map +1 -1
  852. package/build/dist/Server/Utils/UserPermission/UserPermission.js +10 -0
  853. package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
  854. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +447 -0
  855. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -0
  856. package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js +202 -0
  857. package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js.map +1 -0
  858. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +39 -1
  859. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  860. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +44 -4
  861. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  862. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +150 -191
  863. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  864. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +44 -4
  865. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  866. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js +39 -17
  867. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js.map +1 -1
  868. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +151 -145
  869. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  870. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +221 -127
  871. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  872. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js +91 -0
  873. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js.map +1 -0
  874. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js +673 -0
  875. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js.map +1 -0
  876. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +3 -9
  877. package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
  878. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +65 -166
  879. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  880. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +63 -157
  881. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
  882. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +23 -3
  883. package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
  884. package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js +92 -0
  885. package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js.map +1 -0
  886. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +81 -210
  887. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  888. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +68 -138
  889. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
  890. package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js +240 -0
  891. package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js.map +1 -0
  892. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +85 -212
  893. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js.map +1 -1
  894. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +60 -17
  895. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  896. package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
  897. package/build/dist/Server/Utils/Workspace/Workspace.js +134 -41
  898. package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
  899. package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js +197 -0
  900. package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js.map +1 -0
  901. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +32 -0
  902. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  903. package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js +199 -0
  904. package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js.map +1 -0
  905. package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js +91 -0
  906. package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js.map +1 -0
  907. package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js +450 -0
  908. package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js.map +1 -0
  909. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +103 -5
  910. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  911. package/build/dist/Types/Date.js +97 -7
  912. package/build/dist/Types/Date.js.map +1 -1
  913. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  914. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  915. package/build/dist/Types/Exception/ExceptionMessages.js +1 -0
  916. package/build/dist/Types/Exception/ExceptionMessages.js.map +1 -1
  917. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +245 -0
  918. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -0
  919. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +49 -0
  920. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -0
  921. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js +16 -0
  922. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js.map +1 -0
  923. package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js +104 -0
  924. package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js.map +1 -0
  925. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +6 -5
  926. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  927. package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js +32 -0
  928. package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js.map +1 -1
  929. package/build/dist/Types/Monitor/MonitorUptimeSummary.js +52 -0
  930. package/build/dist/Types/Monitor/MonitorUptimeSummary.js.map +1 -0
  931. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +9 -1
  932. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  933. package/build/dist/Types/Rum/SessionReplay.js +52 -2
  934. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  935. package/build/dist/Types/Rum/SessionReplayApi.js +11 -0
  936. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
  937. package/build/dist/Types/Rum/UserFlow.js +115 -0
  938. package/build/dist/Types/Rum/UserFlow.js.map +1 -0
  939. package/build/dist/Types/Runbook/RunbookStepType.js +10 -0
  940. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  941. package/build/dist/Types/Runbook/RunnerJobOrigin.js +14 -0
  942. package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -1
  943. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +2 -2
  944. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -1
  945. package/build/dist/Types/StatusPage/MergedDowntimeTotals.js +2 -0
  946. package/build/dist/Types/StatusPage/MergedDowntimeTotals.js.map +1 -0
  947. package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js +65 -0
  948. package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js.map +1 -0
  949. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +8 -0
  950. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  951. package/build/dist/Types/Timezone.js +5 -0
  952. package/build/dist/Types/Timezone.js.map +1 -1
  953. package/build/dist/Types/TimezoneAlias.js +340 -0
  954. package/build/dist/Types/TimezoneAlias.js.map +1 -0
  955. package/build/dist/Types/Workflow/Templates.js +2694 -2
  956. package/build/dist/Types/Workflow/Templates.js.map +1 -1
  957. package/build/dist/Types/Workflow/WorkflowVariableOAuth.js +314 -0
  958. package/build/dist/Types/Workflow/WorkflowVariableOAuth.js.map +1 -0
  959. package/build/dist/Types/Workspace/WorkspaceNoteReaction.js +98 -0
  960. package/build/dist/Types/Workspace/WorkspaceNoteReaction.js.map +1 -0
  961. package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js +95 -0
  962. package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js.map +1 -0
  963. package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js +44 -0
  964. package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js.map +1 -0
  965. package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js +27 -0
  966. package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js.map +1 -0
  967. package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js +9 -0
  968. package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js.map +1 -0
  969. package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js +260 -0
  970. package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js.map +1 -0
  971. package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js +56 -0
  972. package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js.map +1 -0
  973. package/build/dist/UI/Components/Calendar/Calendar.js +94 -7
  974. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  975. package/build/dist/UI/Components/Calendar/CalendarDayLayout.js +136 -0
  976. package/build/dist/UI/Components/Calendar/CalendarDayLayout.js.map +1 -0
  977. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +1 -1
  978. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  979. package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js +72 -0
  980. package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js.map +1 -0
  981. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +178 -0
  982. package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -0
  983. package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js +13 -1
  984. package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js.map +1 -1
  985. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +20 -4
  986. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -1
  987. package/build/dist/UI/Components/Dropdown/Dropdown.js +33 -9
  988. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  989. package/build/dist/UI/Components/Forms/Fields/FormField.js +36 -13
  990. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  991. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +117 -11
  992. package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
  993. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -1
  994. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
  995. package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -1
  996. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +36 -8
  997. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  998. package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js +10 -2
  999. package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js.map +1 -1
  1000. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +9 -1
  1001. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
  1002. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +16 -15
  1003. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  1004. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  1005. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  1006. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +14 -74
  1007. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  1008. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +22 -5
  1009. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  1010. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +9 -2
  1011. package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
  1012. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +16 -5
  1013. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -1
  1014. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +23 -8
  1015. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1016. package/build/dist/UI/Components/MonitorGraphs/Uptime.js +7 -1
  1017. package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
  1018. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +5 -1
  1019. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
  1020. package/build/dist/UI/Components/Page/ModelPage.js +37 -3
  1021. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  1022. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +27 -6
  1023. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1024. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +7 -1
  1025. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
  1026. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  1027. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  1028. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +17 -77
  1029. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  1030. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +10 -1
  1031. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  1032. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +15 -1
  1033. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -1
  1034. package/build/dist/UI/Utils/Timezone.js +52 -17
  1035. package/build/dist/UI/Utils/Timezone.js.map +1 -1
  1036. package/build/dist/Utils/AI/InvestigationReport.js +116 -21
  1037. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  1038. package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js +1209 -0
  1039. package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js.map +1 -0
  1040. package/build/dist/Utils/AiRemediation/KubectlPolicy.js +2973 -0
  1041. package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -0
  1042. package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js +99 -0
  1043. package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js.map +1 -0
  1044. package/build/dist/Utils/AiRemediation/KubectlWriteScope.js +1429 -0
  1045. package/build/dist/Utils/AiRemediation/KubectlWriteScope.js.map +1 -0
  1046. package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +7 -6
  1047. package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -1
  1048. package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js +214 -0
  1049. package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js.map +1 -0
  1050. package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js +471 -0
  1051. package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js.map +1 -0
  1052. package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js +107 -0
  1053. package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js.map +1 -0
  1054. package/build/dist/Utils/Monitor/MonitorOverviewFamily.js +191 -0
  1055. package/build/dist/Utils/Monitor/MonitorOverviewFamily.js.map +1 -0
  1056. package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js +1259 -0
  1057. package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js.map +1 -0
  1058. package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js +593 -0
  1059. package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js.map +1 -0
  1060. package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js +195 -0
  1061. package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js.map +1 -0
  1062. package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js +127 -0
  1063. package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js.map +1 -0
  1064. package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js +464 -0
  1065. package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js.map +1 -0
  1066. package/build/dist/Utils/Rum/SessionIdentity.js +31 -1
  1067. package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -1
  1068. package/build/dist/Utils/Rum/SessionReplayWireEncoding.js +128 -0
  1069. package/build/dist/Utils/Rum/SessionReplayWireEncoding.js.map +1 -0
  1070. package/build/dist/Utils/Rum/UserFlow.js +941 -0
  1071. package/build/dist/Utils/Rum/UserFlow.js.map +1 -0
  1072. package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js +82 -0
  1073. package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js.map +1 -0
  1074. package/build/dist/Utils/StatusPage/ResourceUptime.js +46 -0
  1075. package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
  1076. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
  1077. package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -1
  1078. package/build/dist/Utils/Telemetry/AttributesJSON.js +245 -0
  1079. package/build/dist/Utils/Telemetry/AttributesJSON.js.map +1 -0
  1080. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +103 -0
  1081. package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -1
  1082. package/build/dist/Utils/Uptime/UptimeUtil.js +91 -27
  1083. package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
  1084. package/jest.config.json +2 -1
  1085. package/package.json +2 -1
  1086. package/tsconfig.tests.json +36 -0
  1087. package/Tests/UI/Components/EmptyState/EmptyStateGuideLinks.test.tsx +0 -337
  1088. package/UI/Components/EmptyState/EmptyStateGuideLinks.tsx +0 -124
  1089. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js +0 -47
  1090. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js.map +0 -1
@@ -0,0 +1,1744 @@
1
+ import CommonAPI from "../../../Server/API/CommonAPI";
2
+ import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
3
+ import Semaphore, {
4
+ SemaphoreMutex,
5
+ } from "../../../Server/Infrastructure/Semaphore";
6
+ import KubernetesClusterAiAccessService from "../../../Server/Services/KubernetesClusterAiAccessService";
7
+ import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
8
+ import RunnerJobService from "../../../Server/Services/RunnerJobService";
9
+ import KubectlJobRunner from "../../../Server/Utils/AI/ClusterAccess/KubectlJobRunner";
10
+ import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
11
+ import RunnerJob from "../../../Models/DatabaseModels/RunnerJob";
12
+ import {
13
+ KubernetesAiAccessGap,
14
+ KubernetesAiRemediationMode,
15
+ KubernetesClusterAiAccessStatus,
16
+ } from "../../../Types/Kubernetes/KubernetesClusterAiAccess";
17
+ import {
18
+ ExpressRequest,
19
+ ExpressResponse,
20
+ NextFunction,
21
+ } from "../../../Server/Utils/Express";
22
+ import Response from "../../../Server/Utils/Response";
23
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
24
+ import Dictionary from "../../../Types/Dictionary";
25
+ import BadDataException from "../../../Types/Exception/BadDataException";
26
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
27
+ import ServiceUnavailableException from "../../../Types/Exception/ServiceUnavailableException";
28
+ import TooManyRequestsException from "../../../Types/Exception/TooManyRequestsException";
29
+ import { JSONObject } from "../../../Types/JSON";
30
+ import ObjectID from "../../../Types/ObjectID";
31
+ import Permission, {
32
+ UserPermission,
33
+ UserTenantAccessPermission,
34
+ } from "../../../Types/Permission";
35
+ import PositiveNumber from "../../../Types/PositiveNumber";
36
+ import RunnerJobOrigin from "../../../Types/Runbook/RunnerJobOrigin";
37
+ import RunnerJobStatus from "../../../Types/Runbook/RunnerJobStatus";
38
+ import UserType from "../../../Types/UserType";
39
+ import {
40
+ afterEach,
41
+ beforeAll,
42
+ beforeEach,
43
+ describe,
44
+ expect,
45
+ test,
46
+ } from "@jest/globals";
47
+
48
+ /*
49
+ * ---------------------------------------------------------------------------
50
+ * The cluster AI page's two custom routes
51
+ * (Common/Server/API/KubernetesClusterAiAccessAPI.ts).
52
+ *
53
+ * POST /kubernetes-cluster/ai-access/status — the readiness checklist.
54
+ * POST /kubernetes-cluster/ai-access/test — `kubectl version` and
55
+ * `kubectl auth can-i --list` through the bound Runner.
56
+ *
57
+ * Pinned here:
58
+ * - both routes read the cluster under the USER's props, scoped to the
59
+ * tenant; a cluster id from another project is reported exactly like a
60
+ * missing one, and nothing past the lookup runs;
61
+ * - /status needs only read access; /test needs edit access to the cluster
62
+ * (a block row is a denial, not a grant) and says so in its own words;
63
+ * - /test has its own limits — one test at a time per cluster (an atomic
64
+ * reservation, so concurrent requests cannot all start), a few per minute
65
+ * and a ceiling per hour per cluster, and a few per minute per user
66
+ * (counted under a per-user lock) — checked before anything is enqueued;
67
+ * - /status and /test show the credential's name only to a caller who may
68
+ * read credentials;
69
+ * - /test runs each command through KubectlJobRunner.run, like every AI
70
+ * kubectl command: an access-test command that times out reads in
71
+ * kubectl's words (never ran, or unknown for a job a Runner took and went
72
+ * silent on), and only a success or an ACCESS failure becomes the
73
+ * cluster's Last verified / Last error;
74
+ * - /test enqueues through the kubectl chokepoint AS AN ACCESS TEST, so it
75
+ * is exempt from the investigation switch and never spends the project's
76
+ * investigation brake; it keeps going only while commands succeed, and a
77
+ * gap that blocks the transport stops it before any job exists.
78
+ * ---------------------------------------------------------------------------
79
+ */
80
+
81
+ type RouterFunction = (
82
+ req: ExpressRequest,
83
+ res: ExpressResponse,
84
+ next: NextFunction,
85
+ ) => void | Promise<void>;
86
+
87
+ type MockRoute = {
88
+ method: string;
89
+ uri: string;
90
+ middleware: RouterFunction;
91
+ handlerFunction: RouterFunction;
92
+ };
93
+
94
+ type MockRouter = {
95
+ get: jest.Mock;
96
+ post: jest.Mock;
97
+ put: jest.Mock;
98
+ delete: jest.Mock;
99
+ };
100
+
101
+ const mockRoutes: Array<MockRoute> = [];
102
+
103
+ type RegisterRouteFunction = (
104
+ method: string,
105
+ ) => (
106
+ uri: string,
107
+ middleware: RouterFunction,
108
+ handlerFunction: RouterFunction,
109
+ ) => void;
110
+
111
+ const registerRoute: RegisterRouteFunction = (method: string) => {
112
+ return (
113
+ uri: string,
114
+ middleware: RouterFunction,
115
+ handlerFunction: RouterFunction,
116
+ ): void => {
117
+ mockRoutes.push({
118
+ method: method.toUpperCase(),
119
+ uri,
120
+ middleware,
121
+ handlerFunction,
122
+ });
123
+ };
124
+ };
125
+
126
+ const mockRouter: MockRouter = {
127
+ get: jest.fn().mockImplementation(registerRoute("get")),
128
+ post: jest.fn().mockImplementation(registerRoute("post")),
129
+ put: jest.fn().mockImplementation(registerRoute("put")),
130
+ delete: jest.fn().mockImplementation(registerRoute("delete")),
131
+ };
132
+
133
+ jest.mock("../../../Server/Utils/Express", () => {
134
+ const actual: Record<string, unknown> = jest.requireActual(
135
+ "../../../Server/Utils/Express",
136
+ ) as Record<string, unknown>;
137
+
138
+ return {
139
+ ...actual,
140
+ __esModule: true,
141
+ default: {
142
+ ...((actual["default"] as Record<string, unknown>) || {}),
143
+ getRouter: (): MockRouter => {
144
+ return mockRouter;
145
+ },
146
+ },
147
+ };
148
+ });
149
+
150
+ jest.mock("../../../Server/Utils/Response", () => {
151
+ return {
152
+ __esModule: true,
153
+ default: {
154
+ sendJsonObjectResponse: jest.fn(),
155
+ sendErrorResponse: jest.fn(),
156
+ sendEmptySuccessResponse: jest.fn(),
157
+ sendEntityResponse: jest.fn(),
158
+ },
159
+ };
160
+ });
161
+
162
+ const sendJsonObjectResponseMock: jest.Mock =
163
+ Response.sendJsonObjectResponse as unknown as jest.Mock;
164
+
165
+ const STATUS_ROUTE: string = "/kubernetes-cluster/ai-access/status";
166
+ const TEST_ROUTE: string = "/kubernetes-cluster/ai-access/test";
167
+
168
+ const PROJECT_ID: ObjectID = new ObjectID(
169
+ "11111111-1111-4111-8111-111111111111",
170
+ );
171
+ const OTHER_PROJECT_ID: ObjectID = new ObjectID(
172
+ "99999999-9999-4999-8999-999999999999",
173
+ );
174
+ const USER_ID: ObjectID = new ObjectID("22222222-2222-4222-8222-222222222222");
175
+ const CLUSTER_ID: ObjectID = new ObjectID(
176
+ "33333333-3333-4333-8333-333333333333",
177
+ );
178
+ const RUNNER_ID: ObjectID = new ObjectID(
179
+ "44444444-4444-4444-8444-444444444444",
180
+ );
181
+ const CREDENTIAL_ID: string = "55555555-5555-4555-8555-555555555555";
182
+
183
+ type RouteCallResult = {
184
+ thrownToNext: unknown;
185
+ nextCallCount: number;
186
+ };
187
+
188
+ function matchRoute(uri: string): MockRoute {
189
+ const route: MockRoute | undefined = mockRoutes.find((route: MockRoute) => {
190
+ return route.method === "POST" && route.uri === uri;
191
+ });
192
+
193
+ if (!route) {
194
+ throw new Error(`Route POST ${uri} was never registered`);
195
+ }
196
+
197
+ return route;
198
+ }
199
+
200
+ async function callRoute(
201
+ uri: string,
202
+ body: JSONObject = { clusterId: CLUSTER_ID.toString() },
203
+ ): Promise<RouteCallResult> {
204
+ const req: ExpressRequest = {
205
+ params: {},
206
+ query: {},
207
+ body,
208
+ headers: {},
209
+ } as unknown as ExpressRequest;
210
+
211
+ const res: ExpressResponse = {
212
+ send: jest.fn(),
213
+ json: jest.fn(),
214
+ status: jest.fn().mockReturnThis(),
215
+ } as unknown as ExpressResponse;
216
+
217
+ const next: jest.Mock = jest.fn();
218
+
219
+ await matchRoute(uri).handlerFunction(
220
+ req,
221
+ res,
222
+ next as unknown as NextFunction,
223
+ );
224
+
225
+ return {
226
+ thrownToNext: next.mock.calls[0] ? next.mock.calls[0][0] : undefined,
227
+ nextCallCount: next.mock.calls.length,
228
+ };
229
+ }
230
+
231
+ /*
232
+ * The per-cluster hourly count and the per-minute count differ only in how
233
+ * far back createdAt reaches: an hour, or a minute.
234
+ */
235
+ function isWithinTheLastHourOnly(query: Record<string, unknown>): boolean {
236
+ const filter: { objectLiteralParameters?: Record<string, unknown> } =
237
+ (query["createdAt"] as {
238
+ objectLiteralParameters?: Record<string, unknown>;
239
+ }) || {};
240
+ const since: unknown = Object.values(filter.objectLiteralParameters || {})[0];
241
+
242
+ return since instanceof Date && Date.now() - since.getTime() > 10 * 60 * 1000;
243
+ }
244
+
245
+ function lastResponse(): JSONObject {
246
+ const calls: Array<Array<unknown>> = sendJsonObjectResponseMock.mock
247
+ .calls as Array<Array<unknown>>;
248
+ expect(calls.length).toBeGreaterThan(0);
249
+ return calls[calls.length - 1]![2] as JSONObject;
250
+ }
251
+
252
+ function userProps(data: {
253
+ permissions: Array<Permission>;
254
+ blocked?: Array<Permission> | undefined;
255
+ userId?: ObjectID | undefined;
256
+ isMasterAdmin?: boolean | undefined;
257
+ }): DatabaseCommonInteractionProps {
258
+ const permissionMap: Dictionary<UserTenantAccessPermission> = {};
259
+
260
+ const grants: Array<UserPermission> = data.permissions.map(
261
+ (permission: Permission) => {
262
+ return {
263
+ _type: "UserPermission",
264
+ permission,
265
+ labelIds: [],
266
+ } as UserPermission;
267
+ },
268
+ );
269
+
270
+ const blocks: Array<UserPermission> = (data.blocked || []).map(
271
+ (permission: Permission) => {
272
+ return {
273
+ _type: "UserPermission",
274
+ permission,
275
+ labelIds: [],
276
+ isBlockPermission: true,
277
+ } as UserPermission;
278
+ },
279
+ );
280
+
281
+ permissionMap[PROJECT_ID.toString()] = {
282
+ _type: "UserTenantAccessPermission",
283
+ projectId: PROJECT_ID,
284
+ permissions: [...grants, ...blocks],
285
+ };
286
+
287
+ return {
288
+ tenantId: PROJECT_ID,
289
+ userId: data.userId === undefined ? USER_ID : data.userId,
290
+ userType: UserType.User,
291
+ userTenantAccessPermission: permissionMap,
292
+ ...(data.isMasterAdmin ? { isMasterAdmin: true } : {}),
293
+ } as DatabaseCommonInteractionProps;
294
+ }
295
+
296
+ function readyStatus(
297
+ overrides: Partial<KubernetesClusterAiAccessStatus> = {},
298
+ ): KubernetesClusterAiAccessStatus {
299
+ return {
300
+ clusterId: CLUSTER_ID.toString(),
301
+ clusterName: "prod-us",
302
+ clusterIdentifier: "prod-us",
303
+ runner: {
304
+ id: RUNNER_ID.toString(),
305
+ name: "office-runner",
306
+ isOnline: true,
307
+ canRunAiCommands: true,
308
+ },
309
+ accessMethod: "credential",
310
+ credentialId: CREDENTIAL_ID,
311
+ credentialName: "prod-us kubeconfig",
312
+ kubectlAllowlist: [],
313
+ isInvestigationEnabled: false,
314
+ isInvestigationReady: false,
315
+ remediationMode: KubernetesAiRemediationMode.Disabled,
316
+ isRemediationReady: false,
317
+ // The switches are off: the test must run anyway.
318
+ gaps: [
319
+ {
320
+ code: "investigation_disabled",
321
+ title: "AI investigation is turned off for this cluster",
322
+ description: "",
323
+ nextStep: "",
324
+ blocks: "investigation",
325
+ },
326
+ {
327
+ code: "remediation_disabled",
328
+ title: "AI remediation is turned off for this cluster",
329
+ description: "",
330
+ nextStep: "",
331
+ blocks: "remediation",
332
+ },
333
+ ],
334
+ evaluatedAt: "2026-01-01T00:00:00.000Z",
335
+ ...overrides,
336
+ };
337
+ }
338
+
339
+ function gap(
340
+ code: KubernetesAiAccessGap["code"],
341
+ blocks: KubernetesAiAccessGap["blocks"] = "both",
342
+ ): KubernetesAiAccessGap {
343
+ return {
344
+ code,
345
+ title: `${code} title`,
346
+ description: "",
347
+ nextStep: `${code} next step`,
348
+ blocks,
349
+ };
350
+ }
351
+
352
+ function clusterRow(projectId: ObjectID = PROJECT_ID): KubernetesCluster {
353
+ return {
354
+ id: CLUSTER_ID,
355
+ _id: CLUSTER_ID.toString(),
356
+ projectId,
357
+ name: "prod-us",
358
+ } as unknown as KubernetesCluster;
359
+ }
360
+
361
+ describe("KubernetesClusterAiAccessAPI", () => {
362
+ let propsSpy: jest.SpyInstance;
363
+ let clusterFind: jest.SpyInstance;
364
+ let statusSpy: jest.SpyInstance;
365
+ let enqueueSpy: jest.SpyInstance;
366
+ let pollSpy: jest.SpyInstance;
367
+ let countSpy: jest.SpyInstance;
368
+ let outcomeSpy: jest.SpyInstance;
369
+
370
+ // What the access-test counters answer, by which query asks.
371
+ let inFlightTests: number;
372
+ let recentTestsForCluster: number;
373
+ let testsForClusterThisHour: number;
374
+ let recentTestsByUser: number;
375
+
376
+ /*
377
+ * An in-memory stand-in for Redis: SET NX and compare-and-delete with the
378
+ * semantics GlobalCache gives them, and a mutex per key that queues its
379
+ * waiters, as redis-semaphore's does.
380
+ */
381
+ let cacheKeys: Map<string, string>;
382
+ let reserveSpy: jest.SpyInstance;
383
+ let releaseSpy: jest.SpyInstance;
384
+ let lockSpy: jest.SpyInstance;
385
+ let heldLocks: Map<string, Promise<void>>;
386
+
387
+ beforeAll(async () => {
388
+ /*
389
+ * The router registers its routes at module scope, so the import is
390
+ * deferred until the mock router above is fully constructed.
391
+ */
392
+ await import("../../../Server/API/KubernetesClusterAiAccessAPI");
393
+ });
394
+
395
+ beforeEach(() => {
396
+ jest.clearAllMocks();
397
+
398
+ inFlightTests = 0;
399
+ recentTestsForCluster = 0;
400
+ testsForClusterThisHour = 0;
401
+ recentTestsByUser = 0;
402
+
403
+ cacheKeys = new Map<string, string>();
404
+ reserveSpy = jest
405
+ .spyOn(GlobalCache, "setStringIfNotExists")
406
+ .mockImplementation(
407
+ async (
408
+ namespace: string,
409
+ key: string,
410
+ value: string,
411
+ ): Promise<boolean> => {
412
+ const cacheKey: string = `${namespace}-${key}`;
413
+ if (cacheKeys.has(cacheKey)) {
414
+ return false;
415
+ }
416
+ cacheKeys.set(cacheKey, value);
417
+ return true;
418
+ },
419
+ );
420
+ releaseSpy = jest
421
+ .spyOn(GlobalCache, "deleteKeyIfValue")
422
+ .mockImplementation(
423
+ async (
424
+ namespace: string,
425
+ key: string,
426
+ value: string,
427
+ ): Promise<boolean> => {
428
+ const cacheKey: string = `${namespace}-${key}`;
429
+ if (cacheKeys.get(cacheKey) !== value) {
430
+ return false;
431
+ }
432
+ cacheKeys.delete(cacheKey);
433
+ return true;
434
+ },
435
+ );
436
+
437
+ heldLocks = new Map<string, Promise<void>>();
438
+ lockSpy = jest
439
+ .spyOn(Semaphore, "lock")
440
+ .mockImplementation(
441
+ async (data: {
442
+ key: string;
443
+ namespace: string;
444
+ }): Promise<SemaphoreMutex> => {
445
+ const lockKey: string = `${data.namespace}-${data.key}`;
446
+
447
+ while (heldLocks.has(lockKey)) {
448
+ await heldLocks.get(lockKey);
449
+ }
450
+
451
+ let unlock: () => void = (): void => {
452
+ return undefined;
453
+ };
454
+ heldLocks.set(
455
+ lockKey,
456
+ new Promise<void>((resolve: () => void) => {
457
+ unlock = resolve;
458
+ }),
459
+ );
460
+
461
+ return {
462
+ lockKey,
463
+ unlock: (): void => {
464
+ heldLocks.delete(lockKey);
465
+ unlock();
466
+ },
467
+ } as unknown as SemaphoreMutex;
468
+ },
469
+ );
470
+ jest
471
+ .spyOn(Semaphore, "release")
472
+ .mockImplementation(async (mutex: SemaphoreMutex): Promise<void> => {
473
+ (mutex as unknown as { unlock: () => void }).unlock();
474
+ });
475
+
476
+ propsSpy = jest
477
+ .spyOn(CommonAPI, "getDatabaseCommonInteractionProps")
478
+ .mockResolvedValue(
479
+ userProps({ permissions: [Permission.EditKubernetesCluster] }),
480
+ );
481
+
482
+ clusterFind = jest
483
+ .spyOn(KubernetesClusterService, "findOneBy")
484
+ .mockResolvedValue(clusterRow());
485
+
486
+ statusSpy = jest
487
+ .spyOn(KubernetesClusterAiAccessService, "getStatusForCluster")
488
+ .mockResolvedValue(readyStatus());
489
+
490
+ let jobCounter: number = 0;
491
+ enqueueSpy = jest
492
+ .spyOn(RunnerJobService, "enqueueAiKubectlCommand")
493
+ .mockImplementation(async (data: unknown): Promise<RunnerJob> => {
494
+ jobCounter++;
495
+ return {
496
+ id: new ObjectID(
497
+ `aaaaaaaa-aaaa-4aaa-8aaa-${String(jobCounter).padStart(12, "0")}`,
498
+ ),
499
+ payload: {
500
+ displayCommand: (data as { command: string }).command,
501
+ },
502
+ } as unknown as RunnerJob;
503
+ });
504
+
505
+ pollSpy = jest
506
+ .spyOn(RunnerJobService, "pollUntilTerminal")
507
+ .mockResolvedValue({
508
+ status: RunnerJobStatus.Succeeded,
509
+ exitCode: 0,
510
+ output: "Client Version: v1.31.4",
511
+ } as unknown as RunnerJob);
512
+
513
+ countSpy = jest
514
+ .spyOn(RunnerJobService, "countBy")
515
+ .mockImplementation(async (args: unknown): Promise<PositiveNumber> => {
516
+ const query: Record<string, unknown> = (
517
+ args as { query: Record<string, unknown> }
518
+ ).query;
519
+
520
+ if (query["status"]) {
521
+ return new PositiveNumber(inFlightTests);
522
+ }
523
+
524
+ if (query["kubernetesClusterId"]) {
525
+ return new PositiveNumber(
526
+ isWithinTheLastHourOnly(query)
527
+ ? testsForClusterThisHour
528
+ : recentTestsForCluster,
529
+ );
530
+ }
531
+
532
+ return new PositiveNumber(recentTestsByUser);
533
+ });
534
+
535
+ outcomeSpy = jest
536
+ .spyOn(KubernetesClusterAiAccessService, "recordCommandOutcome")
537
+ .mockResolvedValue(undefined);
538
+ });
539
+
540
+ afterEach(() => {
541
+ jest.restoreAllMocks();
542
+ });
543
+
544
+ describe("tenant isolation and the cluster lookup (both routes)", () => {
545
+ function testTenantIsolation(uri: string): void {
546
+ test(`${uri}: reads the cluster under the user's props, scoped to the tenant`, async () => {
547
+ await callRoute(uri);
548
+
549
+ const args: {
550
+ query: Record<string, unknown>;
551
+ props: DatabaseCommonInteractionProps;
552
+ } = clusterFind.mock.calls[0]![0] as {
553
+ query: Record<string, unknown>;
554
+ props: DatabaseCommonInteractionProps;
555
+ };
556
+
557
+ expect(args.query["_id"]).toBe(CLUSTER_ID.toString());
558
+ expect(args.query["projectId"]!.toString()).toBe(PROJECT_ID.toString());
559
+ expect(args.props.isRoot).toBeFalsy();
560
+ expect(args.props.userId?.toString()).toBe(USER_ID.toString());
561
+ expect(args.props.tenantId?.toString()).toBe(PROJECT_ID.toString());
562
+ });
563
+
564
+ test(`${uri}: a cluster id from another project reads exactly like a missing one, and nothing else runs`, async () => {
565
+ // What the tenant-scoped read answers for another project's id.
566
+ clusterFind.mockResolvedValue(null);
567
+
568
+ const missing: RouteCallResult = await callRoute(uri);
569
+
570
+ expect(missing.thrownToNext).toBeInstanceOf(BadDataException);
571
+ const missingMessage: string = (missing.thrownToNext as Error).message;
572
+
573
+ // Belt and braces: a row that somehow came back from elsewhere.
574
+ clusterFind.mockResolvedValue(clusterRow(OTHER_PROJECT_ID));
575
+
576
+ const foreign: RouteCallResult = await callRoute(uri);
577
+
578
+ expect(foreign.thrownToNext).toBeInstanceOf(BadDataException);
579
+ expect((foreign.thrownToNext as Error).message).toBe(missingMessage);
580
+ expect(missingMessage).toContain("not found");
581
+
582
+ expect(statusSpy).not.toHaveBeenCalled();
583
+ expect(countSpy).not.toHaveBeenCalled();
584
+ expect(enqueueSpy).not.toHaveBeenCalled();
585
+ expect(sendJsonObjectResponseMock).not.toHaveBeenCalled();
586
+ });
587
+
588
+ test(`${uri}: rejects a missing or malformed clusterId before any lookup`, async () => {
589
+ for (const body of [{}, { clusterId: "not-a-uuid" }]) {
590
+ const result: RouteCallResult = await callRoute(
591
+ uri,
592
+ body as JSONObject,
593
+ );
594
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
595
+ }
596
+
597
+ expect(clusterFind).not.toHaveBeenCalled();
598
+ });
599
+
600
+ test(`${uri}: requires a logged-in user`, async () => {
601
+ propsSpy.mockResolvedValue({
602
+ ...userProps({ permissions: [Permission.ProjectOwner] }),
603
+ userId: undefined,
604
+ });
605
+
606
+ const result: RouteCallResult = await callRoute(uri);
607
+
608
+ expect(result.nextCallCount).toBe(1);
609
+ expect(clusterFind).not.toHaveBeenCalled();
610
+ });
611
+ }
612
+
613
+ for (const uri of [STATUS_ROUTE, TEST_ROUTE]) {
614
+ testTenantIsolation(uri);
615
+ }
616
+ });
617
+
618
+ describe("POST /kubernetes-cluster/ai-access/status", () => {
619
+ test("returns the status for a user who may only read the cluster", async () => {
620
+ propsSpy.mockResolvedValue(
621
+ userProps({ permissions: [Permission.ReadKubernetesCluster] }),
622
+ );
623
+
624
+ const result: RouteCallResult = await callRoute(STATUS_ROUTE);
625
+
626
+ expect(result.nextCallCount).toBe(0);
627
+ const call: { clusterId: ObjectID; projectId: ObjectID } = statusSpy.mock
628
+ .calls[0]![0] as { clusterId: ObjectID; projectId: ObjectID };
629
+ expect(call.clusterId.toString()).toBe(CLUSTER_ID.toString());
630
+ expect(call.projectId.toString()).toBe(PROJECT_ID.toString());
631
+ expect(lastResponse()["clusterId"]).toBe(CLUSTER_ID.toString());
632
+ // Reading the status never runs anything.
633
+ expect(enqueueSpy).not.toHaveBeenCalled();
634
+ });
635
+ });
636
+
637
+ describe("POST /kubernetes-cluster/ai-access/test — who may run it", () => {
638
+ test("refuses a user who may only read the cluster, in the test's own words", async () => {
639
+ propsSpy.mockResolvedValue(
640
+ userProps({ permissions: [Permission.ReadKubernetesCluster] }),
641
+ );
642
+
643
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
644
+
645
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
646
+ expect((result.thrownToNext as Error).message).toContain(
647
+ "permission to edit this Kubernetes cluster to run its AI access test",
648
+ );
649
+ expect(countSpy).not.toHaveBeenCalled();
650
+ expect(enqueueSpy).not.toHaveBeenCalled();
651
+ });
652
+
653
+ test("a block row is a denial, not a grant", async () => {
654
+ propsSpy.mockResolvedValue(
655
+ userProps({
656
+ permissions: [Permission.ReadKubernetesCluster],
657
+ blocked: [Permission.EditKubernetesCluster],
658
+ }),
659
+ );
660
+
661
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
662
+
663
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
664
+ expect(enqueueSpy).not.toHaveBeenCalled();
665
+ });
666
+
667
+ test("admits every role that may edit the cluster, and a master admin", async () => {
668
+ for (const props of [
669
+ userProps({ permissions: [Permission.ProjectMember] }),
670
+ userProps({ permissions: [Permission.EditKubernetesCluster] }),
671
+ userProps({ permissions: [], isMasterAdmin: true }),
672
+ ]) {
673
+ jest.clearAllMocks();
674
+ propsSpy.mockResolvedValue(props);
675
+
676
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
677
+
678
+ expect(result.nextCallCount).toBe(0);
679
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
680
+ }
681
+ });
682
+ });
683
+
684
+ describe("POST /kubernetes-cluster/ai-access/test — the happy path", () => {
685
+ test("runs both commands through the chokepoint as an access test, through the bound Runner and credential", async () => {
686
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
687
+
688
+ expect(result.nextCallCount).toBe(0);
689
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
690
+
691
+ const commands: Array<string> = [];
692
+
693
+ for (const [index, call] of enqueueSpy.mock.calls.entries()) {
694
+ const data: Record<string, unknown> = call[0] as Record<
695
+ string,
696
+ unknown
697
+ >;
698
+
699
+ commands.push(data["command"] as string);
700
+ expect(data["isAccessTest"]).toBe(true);
701
+ expect(data["origin"]).toBe(RunnerJobOrigin.AiInvestigation);
702
+ expect(data["aiRunId"]).toBeUndefined();
703
+ expect(data["projectId"]!.toString()).toBe(PROJECT_ID.toString());
704
+ expect(data["kubernetesClusterId"]!.toString()).toBe(
705
+ CLUSTER_ID.toString(),
706
+ );
707
+ expect(data["targetAgentId"]!.toString()).toBe(RUNNER_ID.toString());
708
+ expect(data["credentialId"]).toBe(CREDENTIAL_ID);
709
+ // Each test's commands name the user who ran it, first command first.
710
+ expect(data["stepId"]).toBe(
711
+ `ai-access-test-${index + 1}-${USER_ID.toString()}`,
712
+ );
713
+ }
714
+
715
+ expect(commands).toEqual([
716
+ "kubectl version",
717
+ "kubectl auth can-i --list",
718
+ ]);
719
+
720
+ const response: JSONObject = lastResponse();
721
+ expect(response["ok"]).toBe(true);
722
+ expect((response["results"] as Array<JSONObject>).length).toBe(2);
723
+ expect((response["results"] as Array<JSONObject>)[0]).toMatchObject({
724
+ command: "kubectl version",
725
+ succeeded: true,
726
+ exitCode: 0,
727
+ output: "Client Version: v1.31.4",
728
+ });
729
+ expect(outcomeSpy).toHaveBeenCalledTimes(2);
730
+ });
731
+
732
+ test("shapes the output with the shared kubectl redaction before anyone sees it", async () => {
733
+ const redact: jest.SpyInstance = jest.spyOn(
734
+ KubectlJobRunner,
735
+ "redactAndCap",
736
+ );
737
+
738
+ await callRoute(TEST_ROUTE);
739
+
740
+ expect(redact).toHaveBeenCalledWith("Client Version: v1.31.4");
741
+ });
742
+
743
+ test("still runs when only the project AI switch or the LLM provider is missing (neither is transport)", async () => {
744
+ statusSpy.mockResolvedValue(
745
+ readyStatus({
746
+ gaps: [gap("project_ai_disabled"), gap("llm_provider_missing")],
747
+ }),
748
+ );
749
+
750
+ await callRoute(TEST_ROUTE);
751
+
752
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
753
+ expect(lastResponse()["ok"]).toBe(true);
754
+ });
755
+
756
+ test("stops after the first failed command and reports one result", async () => {
757
+ pollSpy.mockResolvedValue({
758
+ status: RunnerJobStatus.Failed,
759
+ exitCode: 1,
760
+ output: "",
761
+ errorMessage: "error: You must be logged in to the server",
762
+ } as unknown as RunnerJob);
763
+
764
+ await callRoute(TEST_ROUTE);
765
+
766
+ expect(enqueueSpy).toHaveBeenCalledTimes(1);
767
+ const response: JSONObject = lastResponse();
768
+ expect(response["ok"]).toBe(false);
769
+ expect((response["results"] as Array<JSONObject>).length).toBe(1);
770
+ expect((response["results"] as Array<JSONObject>)[0]).toMatchObject({
771
+ succeeded: false,
772
+ exitCode: 1,
773
+ });
774
+ expect(
775
+ String((response["results"] as Array<JSONObject>)[0]!["errorMessage"]),
776
+ ).toContain("logged in");
777
+ });
778
+
779
+ test("reports a refusal from the chokepoint as a failed command, and stops", async () => {
780
+ enqueueSpy.mockRejectedValue(
781
+ new BadDataException(
782
+ 'Cluster "prod-us" is no longer reached through this Runner.',
783
+ ),
784
+ );
785
+
786
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
787
+
788
+ expect(result.nextCallCount).toBe(0);
789
+ expect(enqueueSpy).toHaveBeenCalledTimes(1);
790
+ const response: JSONObject = lastResponse();
791
+ expect(response["ok"]).toBe(false);
792
+ expect(
793
+ String((response["results"] as Array<JSONObject>)[0]!["errorMessage"]),
794
+ ).toContain("no longer reached through this Runner");
795
+ });
796
+
797
+ test("does not start when a gap blocks the transport (the Runner is offline)", async () => {
798
+ statusSpy.mockResolvedValue(
799
+ readyStatus({ gaps: [gap("runner_offline")] }),
800
+ );
801
+
802
+ await callRoute(TEST_ROUTE);
803
+
804
+ expect(enqueueSpy).not.toHaveBeenCalled();
805
+ const response: JSONObject = lastResponse();
806
+ expect(response["ok"]).toBe(false);
807
+ expect(response["message"]).toContain("runner_offline next step");
808
+ expect(response["results"]).toEqual([]);
809
+ });
810
+
811
+ test("does not start without a bound Runner", async () => {
812
+ statusSpy.mockResolvedValue(readyStatus({ runner: null, gaps: [] }));
813
+
814
+ await callRoute(TEST_ROUTE);
815
+
816
+ expect(enqueueSpy).not.toHaveBeenCalled();
817
+ expect(lastResponse()["ok"]).toBe(false);
818
+ });
819
+ });
820
+
821
+ describe("POST /kubernetes-cluster/ai-access/test — its own limits", () => {
822
+ test("refuses a second test while one is still running for the cluster", async () => {
823
+ inFlightTests = 1;
824
+
825
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
826
+
827
+ expect(result.thrownToNext).toBeInstanceOf(TooManyRequestsException);
828
+ expect((result.thrownToNext as Error).message).toContain(
829
+ "already running",
830
+ );
831
+ expect(enqueueSpy).not.toHaveBeenCalled();
832
+ expect(statusSpy).not.toHaveBeenCalled();
833
+
834
+ // The in-flight count looks at this cluster's live access-test jobs.
835
+ const query: Record<string, unknown> = (
836
+ countSpy.mock.calls[0]![0] as { query: Record<string, unknown> }
837
+ ).query;
838
+ expect(query["projectId"]!.toString()).toBe(PROJECT_ID.toString());
839
+ expect(query["kubernetesClusterId"]!.toString()).toBe(
840
+ CLUSTER_ID.toString(),
841
+ );
842
+ expect(query["status"]).toBeDefined();
843
+ expect(query["createdAt"]).toBeDefined();
844
+ });
845
+
846
+ test("refuses once the cluster was tested 3 times in the last minute; admits at 2", async () => {
847
+ recentTestsForCluster = 3;
848
+
849
+ const refused: RouteCallResult = await callRoute(TEST_ROUTE);
850
+
851
+ expect(refused.thrownToNext).toBeInstanceOf(TooManyRequestsException);
852
+ expect((refused.thrownToNext as Error).message).toContain(
853
+ "This cluster's AI access was tested 3 times",
854
+ );
855
+ expect(enqueueSpy).not.toHaveBeenCalled();
856
+
857
+ jest.clearAllMocks();
858
+ recentTestsForCluster = 2;
859
+
860
+ const admitted: RouteCallResult = await callRoute(TEST_ROUTE);
861
+
862
+ expect(admitted.nextCallCount).toBe(0);
863
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
864
+ });
865
+
866
+ test("refuses once the user ran 6 tests in the last minute, across clusters; admits at 5", async () => {
867
+ recentTestsByUser = 6;
868
+
869
+ const refused: RouteCallResult = await callRoute(TEST_ROUTE);
870
+
871
+ expect(refused.thrownToNext).toBeInstanceOf(TooManyRequestsException);
872
+ expect((refused.thrownToNext as Error).message).toContain(
873
+ "You ran 6 AI access tests",
874
+ );
875
+ expect(enqueueSpy).not.toHaveBeenCalled();
876
+
877
+ // The per-user count is not scoped to one cluster.
878
+ const userQuery: Record<string, unknown> | undefined = countSpy.mock.calls
879
+ .map((call: Array<unknown>) => {
880
+ return (call[0] as { query: Record<string, unknown> }).query;
881
+ })
882
+ .find((query: Record<string, unknown>) => {
883
+ return !query["kubernetesClusterId"];
884
+ });
885
+ expect(userQuery).toBeDefined();
886
+ const stepFilter: { objectLiteralParameters?: Record<string, unknown> } =
887
+ userQuery!["stepId"] as {
888
+ objectLiteralParameters?: Record<string, unknown>;
889
+ };
890
+ expect(Object.values(stepFilter.objectLiteralParameters || {})).toEqual([
891
+ `ai-access-test-1-${USER_ID.toString()}%`,
892
+ ]);
893
+
894
+ jest.clearAllMocks();
895
+ recentTestsByUser = 5;
896
+
897
+ const admitted: RouteCallResult = await callRoute(TEST_ROUTE);
898
+
899
+ expect(admitted.nextCallCount).toBe(0);
900
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
901
+ });
902
+
903
+ test("counts tests (their first command), not commands, per cluster", async () => {
904
+ await callRoute(TEST_ROUTE);
905
+
906
+ const clusterQuery: Record<string, unknown> | undefined =
907
+ countSpy.mock.calls
908
+ .map((call: Array<unknown>) => {
909
+ return (call[0] as { query: Record<string, unknown> }).query;
910
+ })
911
+ .find((query: Record<string, unknown>) => {
912
+ return query["kubernetesClusterId"] && !query["status"];
913
+ });
914
+
915
+ const stepFilter: { objectLiteralParameters?: Record<string, unknown> } =
916
+ clusterQuery!["stepId"] as {
917
+ objectLiteralParameters?: Record<string, unknown>;
918
+ };
919
+ expect(Object.values(stepFilter.objectLiteralParameters || {})).toEqual([
920
+ "ai-access-test-1-%",
921
+ ]);
922
+ });
923
+
924
+ test("refuses once the cluster was tested 30 times in the last hour; admits at 29", async () => {
925
+ testsForClusterThisHour = 30;
926
+
927
+ const refused: RouteCallResult = await callRoute(TEST_ROUTE);
928
+
929
+ expect(refused.thrownToNext).toBeInstanceOf(TooManyRequestsException);
930
+ expect((refused.thrownToNext as Error).message).toContain(
931
+ "30 times in the last hour",
932
+ );
933
+ expect(enqueueSpy).not.toHaveBeenCalled();
934
+
935
+ jest.clearAllMocks();
936
+ testsForClusterThisHour = 29;
937
+
938
+ const admitted: RouteCallResult = await callRoute(TEST_ROUTE);
939
+
940
+ expect(admitted.nextCallCount).toBe(0);
941
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
942
+ });
943
+
944
+ /*
945
+ * Access-test jobs have no AI run, so the project's investigation brake
946
+ * (which counts rows WITH one) never sees them — the hourly ceiling
947
+ * above is theirs.
948
+ */
949
+ test("the chokepoint is told it is an access test, so it stays out of the investigation brake", async () => {
950
+ await callRoute(TEST_ROUTE);
951
+
952
+ for (const call of enqueueSpy.mock.calls) {
953
+ expect((call[0] as Record<string, unknown>)["isAccessTest"]).toBe(true);
954
+ expect((call[0] as Record<string, unknown>)["aiRunId"]).toBeUndefined();
955
+ }
956
+ });
957
+ });
958
+
959
+ /*
960
+ * SIA-5: the limits used to be count-then-enqueue with nothing reserved,
961
+ * so concurrent requests all read "nothing running" and all started.
962
+ */
963
+ describe("POST /kubernetes-cluster/ai-access/test — the limits are atomic", () => {
964
+ function deferred(): {
965
+ promise: Promise<RunnerJob>;
966
+ resolve: (job: RunnerJob) => void;
967
+ } {
968
+ let resolve: (job: RunnerJob) => void = (): void => {
969
+ return undefined;
970
+ };
971
+ const promise: Promise<RunnerJob> = new Promise<RunnerJob>(
972
+ (done: (job: RunnerJob) => void) => {
973
+ resolve = done;
974
+ },
975
+ );
976
+ return { promise, resolve };
977
+ }
978
+
979
+ const SUCCEEDED: RunnerJob = {
980
+ status: RunnerJobStatus.Succeeded,
981
+ exitCode: 0,
982
+ output: "ok",
983
+ } as unknown as RunnerJob;
984
+
985
+ test("two tests started together for one cluster: exactly one runs, the other is told one is running", async () => {
986
+ const held: {
987
+ promise: Promise<RunnerJob>;
988
+ resolve: (job: RunnerJob) => void;
989
+ } = deferred();
990
+ pollSpy.mockReturnValue(held.promise);
991
+
992
+ const first: Promise<RouteCallResult> = callRoute(TEST_ROUTE);
993
+ const second: Promise<RouteCallResult> = callRoute(TEST_ROUTE);
994
+
995
+ const secondResult: RouteCallResult = await second;
996
+ expect(secondResult.thrownToNext).toBeInstanceOf(
997
+ TooManyRequestsException,
998
+ );
999
+ expect((secondResult.thrownToNext as Error).message).toContain(
1000
+ "already running",
1001
+ );
1002
+
1003
+ held.resolve(SUCCEEDED);
1004
+ pollSpy.mockResolvedValue(SUCCEEDED);
1005
+ const firstResult: RouteCallResult = await first;
1006
+
1007
+ expect(firstResult.nextCallCount).toBe(0);
1008
+ // Both of the ONE test's commands, and nothing from the other.
1009
+ expect(enqueueSpy).toHaveBeenCalledTimes(2);
1010
+ });
1011
+
1012
+ /*
1013
+ * Negative control: with a reservation that always succeeds (what
1014
+ * round one had — no reservation at all), the same two requests both
1015
+ * start, because the row counts read zero for both. This is what the
1016
+ * test above guards against.
1017
+ */
1018
+ test("negative control: without the reservation both would run", async () => {
1019
+ reserveSpy.mockResolvedValue(true);
1020
+ const held: {
1021
+ promise: Promise<RunnerJob>;
1022
+ resolve: (job: RunnerJob) => void;
1023
+ } = deferred();
1024
+ pollSpy.mockReturnValue(held.promise);
1025
+
1026
+ const first: Promise<RouteCallResult> = callRoute(TEST_ROUTE);
1027
+ const second: Promise<RouteCallResult> = callRoute(TEST_ROUTE);
1028
+
1029
+ // Let both reach their first command.
1030
+ for (let i: number = 0; i < 50 && enqueueSpy.mock.calls.length < 2; i++) {
1031
+ await new Promise<void>((resolve: () => void) => {
1032
+ setTimeout(resolve, 0);
1033
+ });
1034
+ }
1035
+
1036
+ held.resolve(SUCCEEDED);
1037
+ pollSpy.mockResolvedValue(SUCCEEDED);
1038
+ await Promise.all([first, second]);
1039
+
1040
+ expect(enqueueSpy.mock.calls.length).toBeGreaterThan(2);
1041
+ });
1042
+
1043
+ test("releases the reservation when the test ends, so the next one is admitted", async () => {
1044
+ await callRoute(TEST_ROUTE);
1045
+
1046
+ expect(reserveSpy).toHaveBeenCalledTimes(1);
1047
+ const [namespace, key, token] = reserveSpy.mock.calls[0] as [
1048
+ string,
1049
+ string,
1050
+ string,
1051
+ ];
1052
+ expect(key).toBe(CLUSTER_ID.toString());
1053
+ expect(releaseSpy).toHaveBeenCalledWith(namespace, key, token);
1054
+ expect(cacheKeys.size).toBe(0);
1055
+
1056
+ const again: RouteCallResult = await callRoute(TEST_ROUTE);
1057
+ expect(again.nextCallCount).toBe(0);
1058
+ });
1059
+
1060
+ test("releases it after a failed command, a refused limit and a transport gap too", async () => {
1061
+ pollSpy.mockResolvedValue({
1062
+ status: RunnerJobStatus.Failed,
1063
+ exitCode: 1,
1064
+ errorMessage: "error: You must be logged in to the server",
1065
+ } as unknown as RunnerJob);
1066
+ await callRoute(TEST_ROUTE);
1067
+ expect(cacheKeys.size).toBe(0);
1068
+
1069
+ recentTestsForCluster = 3;
1070
+ const refused: RouteCallResult = await callRoute(TEST_ROUTE);
1071
+ expect(refused.thrownToNext).toBeInstanceOf(TooManyRequestsException);
1072
+ expect(cacheKeys.size).toBe(0);
1073
+
1074
+ recentTestsForCluster = 0;
1075
+ statusSpy.mockResolvedValue(
1076
+ readyStatus({ gaps: [gap("runner_offline")] }),
1077
+ );
1078
+ await callRoute(TEST_ROUTE);
1079
+ expect(cacheKeys.size).toBe(0);
1080
+ });
1081
+
1082
+ test("a reservation that cannot be checked fails closed, and nothing is enqueued", async () => {
1083
+ reserveSpy.mockRejectedValue(new Error("Cache is not connected"));
1084
+
1085
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
1086
+
1087
+ expect(result.thrownToNext).toBeInstanceOf(ServiceUnavailableException);
1088
+ expect(countSpy).not.toHaveBeenCalled();
1089
+ expect(enqueueSpy).not.toHaveBeenCalled();
1090
+ });
1091
+
1092
+ test("a held reservation refuses the test before anything is counted", async () => {
1093
+ cacheKeys.set(
1094
+ `kubernetes-ai-access-test-${CLUSTER_ID.toString()}`,
1095
+ "someone-else",
1096
+ );
1097
+
1098
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
1099
+
1100
+ expect(result.thrownToNext).toBeInstanceOf(TooManyRequestsException);
1101
+ expect(countSpy).not.toHaveBeenCalled();
1102
+ // Never releases someone else's reservation.
1103
+ expect(cacheKeys.size).toBe(1);
1104
+ });
1105
+
1106
+ /*
1107
+ * The per-user limit spans clusters, so the cluster reservation does
1108
+ * not serialize it: the user's count is read and the first job created
1109
+ * under a per-user lock.
1110
+ */
1111
+ function userAtFiveOfSixOnTwoClusters(): ObjectID {
1112
+ const OTHER_CLUSTER: ObjectID = new ObjectID(
1113
+ "66666666-6666-4666-8666-666666666666",
1114
+ );
1115
+ clusterFind.mockImplementation(
1116
+ async (args: unknown): Promise<KubernetesCluster> => {
1117
+ const id: string = String(
1118
+ (args as { query: Record<string, unknown> }).query["_id"],
1119
+ );
1120
+ return {
1121
+ id: new ObjectID(id),
1122
+ _id: id,
1123
+ projectId: PROJECT_ID,
1124
+ name: id,
1125
+ } as unknown as KubernetesCluster;
1126
+ },
1127
+ );
1128
+
1129
+ // The user's count: 5 before, plus every first command created since.
1130
+ countSpy.mockImplementation(
1131
+ async (args: unknown): Promise<PositiveNumber> => {
1132
+ const query: Record<string, unknown> = (
1133
+ args as { query: Record<string, unknown> }
1134
+ ).query;
1135
+ if (query["kubernetesClusterId"] || query["status"]) {
1136
+ return new PositiveNumber(0);
1137
+ }
1138
+ const firstCommands: number = enqueueSpy.mock.calls.filter(
1139
+ (call: Array<unknown>) => {
1140
+ return String(
1141
+ (call[0] as Record<string, unknown>)["stepId"],
1142
+ ).startsWith("ai-access-test-1-");
1143
+ },
1144
+ ).length;
1145
+ return new PositiveNumber(5 + firstCommands);
1146
+ },
1147
+ );
1148
+
1149
+ return OTHER_CLUSTER;
1150
+ }
1151
+
1152
+ test("a user at 5 of 6 starting tests on two clusters at once gets exactly one", async () => {
1153
+ const OTHER_CLUSTER: ObjectID = userAtFiveOfSixOnTwoClusters();
1154
+
1155
+ const results: Array<RouteCallResult> = await Promise.all([
1156
+ callRoute(TEST_ROUTE, { clusterId: CLUSTER_ID.toString() }),
1157
+ callRoute(TEST_ROUTE, { clusterId: OTHER_CLUSTER.toString() }),
1158
+ ]);
1159
+
1160
+ const refused: Array<RouteCallResult> = results.filter(
1161
+ (result: RouteCallResult) => {
1162
+ return result.thrownToNext instanceof TooManyRequestsException;
1163
+ },
1164
+ );
1165
+ expect(refused).toHaveLength(1);
1166
+ expect((refused[0]!.thrownToNext as Error).message).toContain(
1167
+ "You ran 6 AI access tests",
1168
+ );
1169
+ expect(
1170
+ lockSpy.mock.calls.every((call: Array<unknown>) => {
1171
+ return (call[0] as { key: string }).key === USER_ID.toString();
1172
+ }),
1173
+ ).toBe(true);
1174
+ });
1175
+
1176
+ // Negative control: without the per-user lock both read 5 and both run.
1177
+ test("negative control: without the per-user lock both would start", async () => {
1178
+ const OTHER_CLUSTER: ObjectID = userAtFiveOfSixOnTwoClusters();
1179
+ lockSpy.mockResolvedValue({
1180
+ unlock: (): void => {
1181
+ return undefined;
1182
+ },
1183
+ } as unknown as SemaphoreMutex);
1184
+
1185
+ const results: Array<RouteCallResult> = await Promise.all([
1186
+ callRoute(TEST_ROUTE, { clusterId: CLUSTER_ID.toString() }),
1187
+ callRoute(TEST_ROUTE, { clusterId: OTHER_CLUSTER.toString() }),
1188
+ ]);
1189
+
1190
+ expect(
1191
+ results.filter((result: RouteCallResult) => {
1192
+ return result.thrownToNext instanceof TooManyRequestsException;
1193
+ }),
1194
+ ).toHaveLength(0);
1195
+ });
1196
+
1197
+ test("a user lock that cannot be had refuses with 429, and nothing is enqueued", async () => {
1198
+ lockSpy.mockRejectedValue(new Error("lock timeout"));
1199
+
1200
+ const result: RouteCallResult = await callRoute(TEST_ROUTE);
1201
+
1202
+ expect(result.thrownToNext).toBeInstanceOf(TooManyRequestsException);
1203
+ expect(enqueueSpy).not.toHaveBeenCalled();
1204
+ // And the cluster's reservation was released on the way out.
1205
+ expect(cacheKeys.size).toBe(0);
1206
+ });
1207
+ });
1208
+
1209
+ /*
1210
+ * IP-3: an access-test command that nobody picked up used to read "No
1211
+ * runbook agent picked up this step … then try again" — in the results
1212
+ * and as the cluster's Last error. It now runs through KubectlJobRunner,
1213
+ * which words a timeout from what the row says about the claim.
1214
+ */
1215
+ describe("POST /kubernetes-cluster/ai-access/test — timeouts read in kubectl's words", () => {
1216
+ test("an unclaimed first command says nothing was run, in the results and the recorded error", async () => {
1217
+ pollSpy.mockRestore();
1218
+ jest.spyOn(RunnerJobService, "findOneById").mockResolvedValue({
1219
+ status: RunnerJobStatus.Pending,
1220
+ origin: RunnerJobOrigin.AiInvestigation,
1221
+ stepType: "Kubectl",
1222
+ claimDeadlineAt: new Date(Date.now() - 1000),
1223
+ } as unknown as RunnerJob);
1224
+ const timeoutSpy: jest.SpyInstance = jest
1225
+ .spyOn(RunnerJobService, "timeoutJob")
1226
+ .mockImplementation(
1227
+ async (data: { reason: string }): Promise<RunnerJob> => {
1228
+ return {
1229
+ status: RunnerJobStatus.TimedOut,
1230
+ errorMessage: data.reason,
1231
+ } as unknown as RunnerJob;
1232
+ },
1233
+ );
1234
+
1235
+ await callRoute(TEST_ROUTE);
1236
+
1237
+ expect(timeoutSpy).toHaveBeenCalledTimes(1);
1238
+ const result: JSONObject = (
1239
+ lastResponse()["results"] as Array<JSONObject>
1240
+ )[0]!;
1241
+ expect(result["succeeded"]).toBe(false);
1242
+ expect(String(result["errorMessage"])).toContain("Nothing was run");
1243
+ expect(String(result["errorMessage"])).toMatch(/kubectl/);
1244
+ expect(String(result["errorMessage"])).not.toMatch(/runbook/i);
1245
+ expect(String(result["errorMessage"])).not.toMatch(/\bstep\b/i);
1246
+
1247
+ const recorded: { errorMessage?: string } = outcomeSpy.mock
1248
+ .calls[0]![0] as { errorMessage?: string };
1249
+ expect(recorded.errorMessage).toBe(result["errorMessage"]);
1250
+ });
1251
+
1252
+ test("negative control: a command that ran and failed keeps kubectl's own error", async () => {
1253
+ pollSpy.mockResolvedValue({
1254
+ status: RunnerJobStatus.Failed,
1255
+ exitCode: 1,
1256
+ errorMessage: "error: You must be logged in to the server",
1257
+ } as unknown as RunnerJob);
1258
+
1259
+ await callRoute(TEST_ROUTE);
1260
+
1261
+ const result: JSONObject = (
1262
+ lastResponse()["results"] as Array<JSONObject>
1263
+ )[0]!;
1264
+ expect(result["errorMessage"]).toBe(
1265
+ "error: You must be logged in to the server",
1266
+ );
1267
+ });
1268
+ });
1269
+
1270
+ /*
1271
+ * Round three: the access test used to wait on its jobs itself
1272
+ * (pollUntilTerminal) and record every outcome as the cluster's Last
1273
+ * verified / Last error. It now runs each command through
1274
+ * KubectlJobRunner.run, so it reads a finished job exactly like an
1275
+ * investigation does: never ran, ran, or unknown; kubectl-worded
1276
+ * timeouts; and only a success or an ACCESS failure recorded on the
1277
+ * cluster. Its limits and its response shape are unchanged.
1278
+ */
1279
+ describe("POST /kubernetes-cluster/ai-access/test — runs like every AI kubectl command", () => {
1280
+ const CLAIMED_AT: Date = new Date("2026-09-22T10:00:00.000Z");
1281
+
1282
+ // The row a TimedOut job leaves, as the claim-state read sees it.
1283
+ function timedOutRow(claim: {
1284
+ claimedAt?: Date | undefined;
1285
+ assignedAgentId?: ObjectID | undefined;
1286
+ }): RunnerJob {
1287
+ return {
1288
+ status: RunnerJobStatus.TimedOut,
1289
+ ...claim,
1290
+ } as unknown as RunnerJob;
1291
+ }
1292
+
1293
+ function results(): Array<JSONObject> {
1294
+ return lastResponse()["results"] as Array<JSONObject>;
1295
+ }
1296
+
1297
+ function recordedOutcomes(): Array<{
1298
+ succeeded: boolean;
1299
+ errorMessage?: string | undefined;
1300
+ }> {
1301
+ return outcomeSpy.mock.calls.map((call: Array<unknown>) => {
1302
+ return call[0] as { succeeded: boolean; errorMessage?: string };
1303
+ });
1304
+ }
1305
+
1306
+ test("hands each command to KubectlJobRunner.run as an access test with no AI run", async () => {
1307
+ const runSpy: jest.SpyInstance = jest.spyOn(KubectlJobRunner, "run");
1308
+
1309
+ await callRoute(TEST_ROUTE);
1310
+
1311
+ expect(runSpy).toHaveBeenCalledTimes(2);
1312
+
1313
+ for (const [index, call] of runSpy.mock.calls.entries()) {
1314
+ const data: Record<string, unknown> = call[0] as Record<
1315
+ string,
1316
+ unknown
1317
+ >;
1318
+ expect(data["isAccessTest"]).toBe(true);
1319
+ expect(data["aiRunId"]).toBeUndefined();
1320
+ expect(data["origin"]).toBe(RunnerJobOrigin.AiInvestigation);
1321
+ expect(data["projectId"]!.toString()).toBe(PROJECT_ID.toString());
1322
+ expect(data["kubernetesClusterId"]!.toString()).toBe(
1323
+ CLUSTER_ID.toString(),
1324
+ );
1325
+ expect(data["targetRunnerId"]!.toString()).toBe(RUNNER_ID.toString());
1326
+ expect(data["credentialId"]).toBe(CREDENTIAL_ID);
1327
+ expect(data["stepId"]).toBe(
1328
+ `ai-access-test-${index + 1}-${USER_ID.toString()}`,
1329
+ );
1330
+ // The same windows as before: a minute to claim, 30 s to run.
1331
+ expect(data["claimTimeoutInMs"]).toBe(60_000);
1332
+ expect(data["timeoutInMs"]).toBe(30_000);
1333
+ }
1334
+ });
1335
+
1336
+ test("keeps the response shape: five fields per command, nothing more", async () => {
1337
+ await callRoute(TEST_ROUTE);
1338
+
1339
+ const response: JSONObject = lastResponse();
1340
+ expect(Object.keys(response).sort()).toEqual(
1341
+ ["message", "ok", "results", "status"].sort(),
1342
+ );
1343
+
1344
+ for (const result of results()) {
1345
+ expect(Object.keys(result).sort()).toEqual(
1346
+ ["command", "errorMessage", "exitCode", "output", "succeeded"].sort(),
1347
+ );
1348
+ }
1349
+
1350
+ expect(results()[1]).toEqual({
1351
+ command: "kubectl auth can-i --list",
1352
+ succeeded: true,
1353
+ exitCode: 0,
1354
+ output: "Client Version: v1.31.4",
1355
+ errorMessage: null,
1356
+ });
1357
+ });
1358
+
1359
+ test("a first command no Runner picked up says nothing was run, and is recorded as the access failure it is", async () => {
1360
+ pollSpy.mockResolvedValue(timedOutRow({}));
1361
+ const claimRead: jest.SpyInstance = jest
1362
+ .spyOn(RunnerJobService, "findOneById")
1363
+ .mockResolvedValue(timedOutRow({}));
1364
+
1365
+ await callRoute(TEST_ROUTE);
1366
+
1367
+ // Whether a Runner took it is read back from the row.
1368
+ expect(claimRead).toHaveBeenCalledTimes(1);
1369
+ expect(results()).toHaveLength(1);
1370
+ const errorMessage: string = String(results()[0]!["errorMessage"]);
1371
+ expect(errorMessage).toContain("did not pick up this kubectl command");
1372
+ expect(errorMessage).toContain("within 60s");
1373
+ expect(errorMessage).toContain("Nothing was run on the cluster.");
1374
+ expect(errorMessage).not.toMatch(/runbook/i);
1375
+ expect(errorMessage).not.toMatch(/\bstep\b/i);
1376
+ expect(results()[0]!["exitCode"]).toBeNull();
1377
+
1378
+ expect(recordedOutcomes()).toEqual([
1379
+ expect.objectContaining({ succeeded: false, errorMessage }),
1380
+ ]);
1381
+ });
1382
+
1383
+ test("a command a Runner took and went silent on reads as unknown, never as 'nothing was run'", async () => {
1384
+ pollSpy.mockResolvedValue(timedOutRow({}));
1385
+ jest
1386
+ .spyOn(RunnerJobService, "findOneById")
1387
+ .mockResolvedValue(
1388
+ timedOutRow({ claimedAt: CLAIMED_AT, assignedAgentId: RUNNER_ID }),
1389
+ );
1390
+
1391
+ await callRoute(TEST_ROUTE);
1392
+
1393
+ const errorMessage: string = String(results()[0]!["errorMessage"]);
1394
+ expect(errorMessage).toContain("The Runner took this kubectl command");
1395
+ expect(errorMessage).toContain("is unknown");
1396
+ expect(errorMessage).not.toContain("Nothing was run");
1397
+ expect(errorMessage).not.toMatch(/runbook/i);
1398
+
1399
+ // A Runner that stops answering is still about the access.
1400
+ expect(recordedOutcomes()).toEqual([
1401
+ expect.objectContaining({ succeeded: false, errorMessage }),
1402
+ ]);
1403
+ });
1404
+
1405
+ test("a claim that cannot be read back is unknown too, never 'nothing was run'", async () => {
1406
+ pollSpy.mockResolvedValue(timedOutRow({}));
1407
+ jest
1408
+ .spyOn(RunnerJobService, "findOneById")
1409
+ .mockRejectedValue(new Error("database is restarting"));
1410
+
1411
+ await callRoute(TEST_ROUTE);
1412
+
1413
+ const errorMessage: string = String(results()[0]!["errorMessage"]);
1414
+ expect(errorMessage).toContain("could not be read");
1415
+ expect(errorMessage).not.toContain("Nothing was run");
1416
+ });
1417
+
1418
+ test("the second command's timeout reads in kubectl's words too", async () => {
1419
+ pollSpy
1420
+ .mockResolvedValueOnce({
1421
+ status: RunnerJobStatus.Succeeded,
1422
+ exitCode: 0,
1423
+ output: "Client Version: v1.31.4",
1424
+ } as unknown as RunnerJob)
1425
+ .mockResolvedValueOnce(timedOutRow({}));
1426
+ jest
1427
+ .spyOn(RunnerJobService, "findOneById")
1428
+ .mockResolvedValue(timedOutRow({}));
1429
+
1430
+ await callRoute(TEST_ROUTE);
1431
+
1432
+ expect(results()).toHaveLength(2);
1433
+ expect(results()[0]!["succeeded"]).toBe(true);
1434
+ expect(String(results()[1]!["errorMessage"])).toContain(
1435
+ "Nothing was run on the cluster.",
1436
+ );
1437
+ expect(lastResponse()["ok"]).toBe(false);
1438
+ });
1439
+
1440
+ test("a refusal before kubectl was spawned is an access failure: reported and recorded", async () => {
1441
+ pollSpy.mockResolvedValue({
1442
+ status: RunnerJobStatus.Failed,
1443
+ errorMessage:
1444
+ "This Runner does not run kubectl for this cluster any more. It was not run.",
1445
+ } as unknown as RunnerJob);
1446
+
1447
+ await callRoute(TEST_ROUTE);
1448
+
1449
+ expect(results()[0]).toMatchObject({
1450
+ succeeded: false,
1451
+ exitCode: null,
1452
+ output: "",
1453
+ });
1454
+ expect(String(results()[0]!["errorMessage"])).toContain("It was not run");
1455
+ expect(recordedOutcomes()).toEqual([
1456
+ expect.objectContaining({ succeeded: false }),
1457
+ ]);
1458
+ });
1459
+
1460
+ test("a command that ran and failed for a reason that is not about access is reported but never becomes the cluster's Last error", async () => {
1461
+ pollSpy.mockResolvedValue({
1462
+ status: RunnerJobStatus.Failed,
1463
+ exitCode: 1,
1464
+ output:
1465
+ "[stderr]\nError from server (NotFound): the server could not find the requested resource",
1466
+ errorMessage:
1467
+ "Exit code 1: Error from server (NotFound): the server could not find the requested resource",
1468
+ } as unknown as RunnerJob);
1469
+
1470
+ await callRoute(TEST_ROUTE);
1471
+
1472
+ expect(results()).toHaveLength(1);
1473
+ expect(results()[0]).toMatchObject({ succeeded: false, exitCode: 1 });
1474
+ expect(String(results()[0]!["errorMessage"])).toContain("NotFound");
1475
+ expect(lastResponse()["ok"]).toBe(false);
1476
+ // Neither set nor cleared.
1477
+ expect(outcomeSpy).not.toHaveBeenCalled();
1478
+ });
1479
+
1480
+ test("negative control: the API server refusing the Runner IS about access, and is recorded", async () => {
1481
+ pollSpy.mockResolvedValue({
1482
+ status: RunnerJobStatus.Failed,
1483
+ exitCode: 1,
1484
+ output:
1485
+ '[stderr]\nError from server (Forbidden): pods is forbidden: User "system:serviceaccount:oneuptime:runner" cannot list resource "pods"',
1486
+ errorMessage:
1487
+ 'Exit code 1: Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:oneuptime:runner" cannot list resource "pods"',
1488
+ } as unknown as RunnerJob);
1489
+
1490
+ await callRoute(TEST_ROUTE);
1491
+
1492
+ expect(recordedOutcomes()).toEqual([
1493
+ expect.objectContaining({
1494
+ succeeded: false,
1495
+ errorMessage: expect.stringContaining("Forbidden"),
1496
+ }),
1497
+ ]);
1498
+ });
1499
+
1500
+ test("a first success clears the Last error and a later non-access failure leaves it cleared", async () => {
1501
+ pollSpy
1502
+ .mockResolvedValueOnce({
1503
+ status: RunnerJobStatus.Succeeded,
1504
+ exitCode: 0,
1505
+ output: "Client Version: v1.31.4",
1506
+ } as unknown as RunnerJob)
1507
+ .mockResolvedValueOnce({
1508
+ status: RunnerJobStatus.Failed,
1509
+ exitCode: 1,
1510
+ output: "[stderr]\nerror: unknown flag: --list",
1511
+ errorMessage: "Exit code 1: error: unknown flag: --list",
1512
+ } as unknown as RunnerJob);
1513
+
1514
+ await callRoute(TEST_ROUTE);
1515
+
1516
+ expect(recordedOutcomes()).toEqual([
1517
+ expect.objectContaining({ succeeded: true }),
1518
+ ]);
1519
+ expect(results()[1]).toMatchObject({ succeeded: false, exitCode: 1 });
1520
+ });
1521
+
1522
+ test("an error message is redacted before anyone sees it or it is recorded", async () => {
1523
+ pollSpy.mockResolvedValue({
1524
+ status: RunnerJobStatus.Failed,
1525
+ exitCode: 1,
1526
+ errorMessage:
1527
+ "Exit code 1: error: You must be logged in to the server (token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJydW5uZXIifQ.c2lnbmF0dXJlc2lnbmF0dXJl)",
1528
+ } as unknown as RunnerJob);
1529
+
1530
+ await callRoute(TEST_ROUTE);
1531
+
1532
+ const errorMessage: string = String(results()[0]!["errorMessage"]);
1533
+ expect(errorMessage).toContain("logged in");
1534
+ expect(errorMessage).not.toContain(
1535
+ "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJydW5uZXIifQ",
1536
+ );
1537
+ expect(recordedOutcomes()[0]!.errorMessage).toBe(errorMessage);
1538
+ });
1539
+
1540
+ /*
1541
+ * KubectlJobRunner.run enqueues and waits in one call, so the per-user
1542
+ * lock is held until the first command has finished: that is what makes
1543
+ * the next test of the same user count this one's job.
1544
+ */
1545
+ test("holds the user's lock until the first command finished, so a second test of the same user counts it", async () => {
1546
+ const OTHER_CLUSTER: ObjectID = new ObjectID(
1547
+ "66666666-6666-4666-8666-666666666666",
1548
+ );
1549
+ clusterFind.mockImplementation(
1550
+ async (args: unknown): Promise<KubernetesCluster> => {
1551
+ const id: string = String(
1552
+ (args as { query: Record<string, unknown> }).query["_id"],
1553
+ );
1554
+ return {
1555
+ id: new ObjectID(id),
1556
+ _id: id,
1557
+ projectId: PROJECT_ID,
1558
+ name: id,
1559
+ } as unknown as KubernetesCluster;
1560
+ },
1561
+ );
1562
+
1563
+ const userCounts: Array<number> = [];
1564
+ countSpy.mockImplementation(
1565
+ async (args: unknown): Promise<PositiveNumber> => {
1566
+ const query: Record<string, unknown> = (
1567
+ args as { query: Record<string, unknown> }
1568
+ ).query;
1569
+ if (query["kubernetesClusterId"] || query["status"]) {
1570
+ return new PositiveNumber(0);
1571
+ }
1572
+ const firstCommands: number = enqueueSpy.mock.calls.filter(
1573
+ (call: Array<unknown>) => {
1574
+ return String(
1575
+ (call[0] as Record<string, unknown>)["stepId"],
1576
+ ).startsWith("ai-access-test-1-");
1577
+ },
1578
+ ).length;
1579
+ userCounts.push(firstCommands);
1580
+ return new PositiveNumber(firstCommands);
1581
+ },
1582
+ );
1583
+
1584
+ let releaseFirstCommand: (job: RunnerJob) => void = (): void => {
1585
+ return undefined;
1586
+ };
1587
+ pollSpy.mockReturnValueOnce(
1588
+ new Promise<RunnerJob>((resolve: (job: RunnerJob) => void) => {
1589
+ releaseFirstCommand = resolve;
1590
+ }),
1591
+ );
1592
+
1593
+ const first: Promise<RouteCallResult> = callRoute(TEST_ROUTE, {
1594
+ clusterId: CLUSTER_ID.toString(),
1595
+ });
1596
+
1597
+ for (let i: number = 0; i < 50 && pollSpy.mock.calls.length < 1; i++) {
1598
+ await new Promise<void>((resolve: () => void) => {
1599
+ setTimeout(resolve, 0);
1600
+ });
1601
+ }
1602
+
1603
+ const second: Promise<RouteCallResult> = callRoute(TEST_ROUTE, {
1604
+ clusterId: OTHER_CLUSTER.toString(),
1605
+ });
1606
+
1607
+ for (let i: number = 0; i < 50; i++) {
1608
+ await new Promise<void>((resolve: () => void) => {
1609
+ setTimeout(resolve, 0);
1610
+ });
1611
+ }
1612
+
1613
+ // The second test waits on the lock: it has not counted yet.
1614
+ expect(userCounts).toEqual([0]);
1615
+ expect(enqueueSpy).toHaveBeenCalledTimes(1);
1616
+
1617
+ releaseFirstCommand({
1618
+ status: RunnerJobStatus.Succeeded,
1619
+ exitCode: 0,
1620
+ output: "ok",
1621
+ } as unknown as RunnerJob);
1622
+
1623
+ const settled: Array<RouteCallResult> = await Promise.all([
1624
+ first,
1625
+ second,
1626
+ ]);
1627
+
1628
+ expect(settled[0]!.nextCallCount).toBe(0);
1629
+ expect(settled[1]!.nextCallCount).toBe(0);
1630
+ // ...and when it does, the first test's job is in its count.
1631
+ expect(userCounts).toEqual([0, 1]);
1632
+ });
1633
+
1634
+ test("releases the user's lock after a first command that failed or could not start", async () => {
1635
+ pollSpy.mockResolvedValue({
1636
+ status: RunnerJobStatus.Failed,
1637
+ exitCode: 1,
1638
+ errorMessage: "error: You must be logged in to the server",
1639
+ } as unknown as RunnerJob);
1640
+ await callRoute(TEST_ROUTE);
1641
+ expect(heldLocks.size).toBe(0);
1642
+
1643
+ enqueueSpy.mockRejectedValue(
1644
+ new BadDataException("kubectl access is not allowed right now."),
1645
+ );
1646
+ await callRoute(TEST_ROUTE);
1647
+ expect(heldLocks.size).toBe(0);
1648
+ expect(String(results()[0]!["errorMessage"])).toContain(
1649
+ "kubectl access is not allowed right now.",
1650
+ );
1651
+ });
1652
+ });
1653
+
1654
+ /*
1655
+ * The cluster's AI page shows the credential's NAME only to someone who
1656
+ * may read credentials — the rule its credential picker and the
1657
+ * investigation panel apply. Reading the cluster is not enough.
1658
+ */
1659
+ describe("the credential is shown only to callers who may read credentials", () => {
1660
+ const CREDENTIAL_GAP: KubernetesAiAccessGap = {
1661
+ code: "credential_missing",
1662
+ title: "The Kubernetes credential is not usable by the Runner",
1663
+ description:
1664
+ '"prod-us kubeconfig" is not assigned to Runner "office-runner".',
1665
+ nextStep: "Assign it.",
1666
+ blocks: "both",
1667
+ };
1668
+
1669
+ test("/status withholds the credential's id and name, and the gap text that names it", async () => {
1670
+ propsSpy.mockResolvedValue(
1671
+ userProps({ permissions: [Permission.ReadKubernetesCluster] }),
1672
+ );
1673
+ statusSpy.mockResolvedValue(readyStatus({ gaps: [CREDENTIAL_GAP] }));
1674
+
1675
+ await callRoute(STATUS_ROUTE);
1676
+
1677
+ const response: JSONObject = lastResponse();
1678
+ expect(response).not.toHaveProperty("credentialId");
1679
+ expect(response).not.toHaveProperty("credentialName");
1680
+ expect(JSON.stringify(response)).not.toContain("prod-us kubeconfig");
1681
+ const gaps: Array<JSONObject> = response["gaps"] as Array<JSONObject>;
1682
+ expect(gaps[0]!["description"]).toContain(
1683
+ "Someone who can view Runner credentials",
1684
+ );
1685
+ // Everything else a cluster reader may see is still there.
1686
+ expect(response["accessMethod"]).toBe("credential");
1687
+ expect(response["clusterId"]).toBe(CLUSTER_ID.toString());
1688
+ });
1689
+
1690
+ test("a block row for credential read is a denial even with a grant", async () => {
1691
+ propsSpy.mockResolvedValue(
1692
+ userProps({
1693
+ permissions: [
1694
+ Permission.ReadKubernetesCluster,
1695
+ Permission.ReadRunbookCredential,
1696
+ ],
1697
+ blocked: [Permission.ReadRunbookCredential],
1698
+ }),
1699
+ );
1700
+
1701
+ await callRoute(STATUS_ROUTE);
1702
+
1703
+ expect(lastResponse()).not.toHaveProperty("credentialName");
1704
+ });
1705
+
1706
+ test("negative control: a caller who may read credentials sees them", async () => {
1707
+ propsSpy.mockResolvedValue(
1708
+ userProps({
1709
+ permissions: [
1710
+ Permission.ReadKubernetesCluster,
1711
+ Permission.ReadRunbookCredential,
1712
+ ],
1713
+ }),
1714
+ );
1715
+ statusSpy.mockResolvedValue(readyStatus({ gaps: [CREDENTIAL_GAP] }));
1716
+
1717
+ await callRoute(STATUS_ROUTE);
1718
+
1719
+ const response: JSONObject = lastResponse();
1720
+ expect(response["credentialId"]).toBe(CREDENTIAL_ID);
1721
+ expect(response["credentialName"]).toBe("prod-us kubeconfig");
1722
+ expect(
1723
+ (response["gaps"] as Array<JSONObject>)[0]!["description"],
1724
+ ).toContain("prod-us kubeconfig");
1725
+ });
1726
+
1727
+ test("/test withholds them from its status too, yet still runs through the bound credential", async () => {
1728
+ propsSpy.mockResolvedValue(
1729
+ userProps({ permissions: [Permission.EditKubernetesCluster] }),
1730
+ );
1731
+
1732
+ await callRoute(TEST_ROUTE);
1733
+
1734
+ expect(
1735
+ (enqueueSpy.mock.calls[0]![0] as Record<string, unknown>)[
1736
+ "credentialId"
1737
+ ],
1738
+ ).toBe(CREDENTIAL_ID);
1739
+ const status: JSONObject = lastResponse()["status"] as JSONObject;
1740
+ expect(status).not.toHaveProperty("credentialName");
1741
+ expect(status).not.toHaveProperty("credentialId");
1742
+ });
1743
+ });
1744
+ });