@oneuptime/common 14.0.0 → 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.
- package/Models/DatabaseModels/AlertEpisodeMember.ts +8 -1
- package/Models/DatabaseModels/AutoRemediationSuggestion.ts +63 -0
- package/Models/DatabaseModels/IncidentEpisodeMember.ts +8 -1
- package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +9 -2
- package/Models/DatabaseModels/IncidentPublicNote.ts +9 -2
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +429 -0
- package/Models/DatabaseModels/Monitor.ts +48 -0
- package/Models/DatabaseModels/Runner.ts +5 -4
- package/Models/DatabaseModels/RunnerJob.ts +66 -0
- package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +10 -2
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +479 -2
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +79 -25
- package/Server/API/AIInvestigationAPI.ts +341 -0
- package/Server/API/AutoRemediationAPI.ts +184 -2
- package/Server/API/CommonAPI.ts +70 -0
- package/Server/API/KubernetesClusterAiAccessAPI.ts +793 -0
- package/Server/API/MicrosoftTeamsAPI.ts +770 -315
- package/Server/API/MonitorAPI.ts +140 -0
- package/Server/API/OnCallScheduleTimelineAPI.ts +5 -21
- package/Server/API/SlackAPI.ts +447 -99
- package/Server/API/StatusPageAPI.ts +146 -26
- package/Server/API/TelemetryAPI.ts +216 -5
- package/Server/API/UserTwoFactorBackupCodeAPI.ts +1 -0
- package/Server/API/WorkflowVariableAPI.ts +156 -0
- package/Server/Infrastructure/GlobalCache.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.ts +97 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.ts +129 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.ts +20 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +28 -0
- package/Server/Middleware/TelemetryIngest.ts +47 -2
- package/Server/Middleware/UserAuthorization.ts +56 -2
- package/Server/Services/AIAgentService.ts +1 -0
- package/Server/Services/AccessTokenService.ts +121 -48
- package/Server/Services/AlertEpisodeMemberService.ts +9 -0
- package/Server/Services/AlertEpisodeService.ts +14 -1
- package/Server/Services/AlertService.ts +134 -22
- package/Server/Services/AutoRemediationRuleEngineService.ts +968 -39
- package/Server/Services/AutoRemediationRuleService.ts +118 -0
- package/Server/Services/BillingService.ts +1 -0
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/InboundEmail/Providers/SendGridInboundProvider.ts +16 -22
- package/Server/Services/IncidentEpisodeMemberService.ts +9 -0
- package/Server/Services/IncidentEpisodePublicNoteService.ts +66 -0
- package/Server/Services/IncidentEpisodeRoleMemberService.ts +27 -0
- package/Server/Services/IncidentEpisodeService.ts +14 -1
- package/Server/Services/IncidentGroupingEngineService.ts +11 -0
- package/Server/Services/IncidentMemberService.ts +21 -0
- package/Server/Services/IncidentPublicNoteService.ts +63 -0
- package/Server/Services/IncidentService.ts +120 -20
- package/Server/Services/IncidentStateTimelineService.ts +15 -0
- package/Server/Services/IncidentTemplateService.ts +83 -3
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/KubernetesClusterAiAccessService.ts +1943 -0
- package/Server/Services/KubernetesClusterService.ts +1570 -1
- package/Server/Services/LogAggregationService.ts +14 -6
- package/Server/Services/MonitorService.ts +654 -54
- package/Server/Services/MonitorStatusTimelineService.ts +639 -2
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +3 -0
- package/Server/Services/OnCallNotificationAlertingService.ts +36 -12
- package/Server/Services/OnCallSetupReminderService.ts +1 -0
- package/Server/Services/ProbeService.ts +7 -1
- package/Server/Services/ProjectService.ts +11 -1
- package/Server/Services/RunbookCredentialService.ts +26 -1
- package/Server/Services/RunbookSecretService.ts +36 -0
- package/Server/Services/RunnerJobService.ts +941 -23
- package/Server/Services/RunnerService.ts +439 -6
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +66 -0
- package/Server/Services/ScheduledMaintenanceService.ts +124 -19
- package/Server/Services/ScheduledMaintenanceTemplateService.ts +82 -3
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +42 -1
- package/Server/Services/StatusPagePrivateUserService.ts +1 -0
- package/Server/Services/StatusPageService.ts +301 -74
- package/Server/Services/StatusPageSubscriberService.ts +4 -0
- package/Server/Services/TeamMemberService.ts +309 -7
- package/Server/Services/TraceAggregationService.ts +10 -6
- package/Server/Services/UserEmailService.ts +1 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +1 -0
- package/Server/Services/UserNotificationRuleAdminService.ts +1 -0
- package/Server/Services/UserNotificationRuleService.ts +4 -0
- package/Server/Services/UserService.ts +122 -1
- package/Server/Services/WorkflowVariableService.ts +593 -8
- package/Server/Services/WorkspaceNotificationRuleService.ts +588 -8
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +170 -1
- package/Server/Types/Database/QueryHelper.ts +22 -0
- package/Server/Types/Markdown.ts +502 -27
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/ClusterAccess/ClusterAccessContext.ts +182 -0
- package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +525 -0
- package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +854 -0
- package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +22 -2
- package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1916 -307
- package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +1601 -74
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +139 -7
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +58 -1
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +54 -1
- package/Server/Utils/AI/Toolbox/AIActionTools.ts +14 -6
- package/Server/Utils/AI/Toolbox/ResourceTools.ts +4 -2
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +1341 -150
- package/Server/Utils/AutoRemediation/RemediationVerifier.ts +375 -4
- package/Server/Utils/Database/AffectedResourceRelations.ts +127 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +240 -1
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +1 -0
- package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorAlert.ts +83 -9
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +5 -1
- package/Server/Utils/Monitor/MonitorIncident.ts +104 -9
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +124 -7
- package/Server/Utils/Monitor/ProbeMonitorsNotification.ts +407 -0
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/OnCall/OnCallShiftReminderRunner.ts +1 -0
- package/Server/Utils/PushNotificationUtil.ts +45 -0
- package/Server/Utils/Rules/OwnerRuleAssignment.ts +40 -3
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Runbook/RunbookExecutePermission.ts +56 -6
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +223 -33
- package/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.ts +559 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Server/Utils/StartServer.ts +4 -3
- package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +528 -0
- package/Server/Utils/TwoFactorBackupCodeNotification.ts +1 -0
- package/Server/Utils/UserPermission/UserPermission.ts +16 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +704 -0
- package/Server/Utils/Workflow/WorkflowVariableOAuthToken.ts +318 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +47 -7
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +57 -16
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +193 -214
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +57 -13
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.ts +54 -17
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +179 -161
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +281 -167
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.ts +114 -0
- package/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.ts +895 -0
- package/Server/Utils/Workspace/Slack/Actions/ActionTypes.ts +7 -9
- package/Server/Utils/Workspace/Slack/Actions/Alert.ts +96 -227
- package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +93 -214
- package/Server/Utils/Workspace/Slack/Actions/Auth.ts +30 -3
- package/Server/Utils/Workspace/Slack/Actions/Authorization.ts +99 -0
- package/Server/Utils/Workspace/Slack/Actions/Incident.ts +117 -283
- package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +101 -196
- package/Server/Utils/Workspace/Slack/Actions/ReactionNote.ts +329 -0
- package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +111 -287
- package/Server/Utils/Workspace/Slack/Slack.ts +86 -17
- package/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
- package/Server/Utils/Workspace/Workspace.ts +171 -54
- package/Server/Utils/Workspace/WorkspaceActionAuthorization.ts +252 -0
- package/Server/Utils/Workspace/WorkspaceBase.ts +39 -0
- package/Server/Utils/Workspace/WorkspaceOAuthState.ts +280 -0
- package/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.ts +96 -0
- package/Server/Utils/Workspace/WorkspaceReactionNote.ts +620 -0
- package/Tests/App/Dashboard/AttributesJSONDashboardSurfaces.test.tsx +496 -0
- package/Tests/App/Dashboard/AutoRemediationRulesTableCommandRunners.test.tsx +152 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +32 -0
- package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +794 -0
- package/Tests/App/Dashboard/CreateOAuthWorkflowVariableModal.test.tsx +840 -0
- package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +653 -0
- package/Tests/App/Dashboard/DatabaseMonitorStepForm.test.tsx +197 -0
- package/Tests/App/Dashboard/DatabaseMonitorView.test.tsx +376 -0
- package/Tests/App/Dashboard/DisabledWarningRobustness.test.tsx +262 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +2080 -0
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +486 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +933 -0
- package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +218 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +665 -0
- package/Tests/App/Dashboard/IncidentEpisodeFeedPublicNoteNotifyDefault.test.tsx +494 -0
- package/Tests/App/Dashboard/IncidentEpisodeOverviewNotifyDefault.test.tsx +706 -0
- package/Tests/App/Dashboard/IncidentFeedPublicNoteNotifyDefault.test.tsx +472 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +858 -0
- package/Tests/App/Dashboard/IncomingEmailAddressSettings.test.tsx +567 -0
- package/Tests/App/Dashboard/InvestigationClusterToolFormat.test.ts +241 -0
- package/Tests/App/Dashboard/InvestigationEvidenceKubectl.test.tsx +322 -0
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +606 -3
- package/Tests/App/Dashboard/InvestigationPanelClusterAccess.test.tsx +476 -0
- package/Tests/App/Dashboard/InvestigationRunDetailsKubectl.test.tsx +383 -0
- package/Tests/App/Dashboard/KubectlActivitySummary.test.tsx +366 -0
- package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +2886 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPage.test.tsx +2688 -0
- package/Tests/App/Dashboard/KubernetesClusterAiPageServerParity.test.ts +915 -0
- package/Tests/App/Dashboard/LayerDateTimeFieldTimezoneName.test.tsx +66 -0
- package/Tests/App/Dashboard/LogMonitorPreview.test.tsx +174 -0
- package/Tests/App/Dashboard/MonitorFeedRefresh.test.tsx +279 -0
- package/Tests/App/Dashboard/MonitorMetricQueryConfig.test.ts +244 -0
- package/Tests/App/Dashboard/MonitorOpenWorkCard.test.tsx +381 -0
- package/Tests/App/Dashboard/MonitorOpenWorkHook.test.tsx +784 -0
- package/Tests/App/Dashboard/MonitorOverviewData.test.tsx +2199 -0
- package/Tests/App/Dashboard/MonitorOverviewHero.test.tsx +1169 -0
- package/Tests/App/Dashboard/MonitorOverviewInput.test.ts +1127 -0
- package/Tests/App/Dashboard/MonitorOverviewPage.test.tsx +2136 -0
- package/Tests/App/Dashboard/MonitorOverviewSelectReadable.test.ts +368 -0
- package/Tests/App/Dashboard/MonitorOverviewStatBar.test.tsx +526 -0
- package/Tests/App/Dashboard/MonitorOverviewSupportingCards.test.tsx +542 -0
- package/Tests/App/Dashboard/MonitorOwnersCombine.test.ts +299 -0
- package/Tests/App/Dashboard/MonitorProbesCard.test.tsx +602 -0
- package/Tests/App/Dashboard/MonitorResponseTimeCard.test.tsx +386 -0
- package/Tests/App/Dashboard/MonitorSetupAndConnectionCards.test.tsx +612 -0
- package/Tests/App/Dashboard/MonitorStatusChangesCard.test.tsx +257 -0
- package/Tests/App/Dashboard/MonitorSummaryTestMonitorButton.test.tsx +360 -1
- package/Tests/App/Dashboard/MonitorUptimeHistoryCard.test.tsx +835 -0
- package/Tests/App/Dashboard/MonitorUptimeSummaryHook.test.tsx +971 -0
- package/Tests/App/Dashboard/MonitorViewLayout.test.tsx +580 -0
- package/Tests/App/Dashboard/OnCallLayerRestrictionTimesPreserved.test.tsx +441 -0
- package/Tests/App/Dashboard/OnCallNavbar.test.tsx +214 -0
- package/Tests/App/Dashboard/OnCallSchedulePreviewCalendarDisplay.test.tsx +1389 -0
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +205 -1
- package/Tests/App/Dashboard/OnCallScheduleTimeInputsSettingsZone.test.tsx +198 -0
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +524 -0
- package/Tests/App/Dashboard/RemediationSuggestionCardApproval.test.tsx +702 -0
- package/Tests/App/Dashboard/RemoveUserFromProject.test.tsx +427 -0
- package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +4 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +3 -1
- package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +166 -0
- package/Tests/App/Dashboard/RunnerKubernetesAgentForm.test.tsx +622 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceChangeStateNotifyDefault.test.tsx +1491 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedPublicNoteNotifyDefault.test.tsx +489 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewNotifyDefault.test.tsx +909 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +14 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceWindowStatsTranslation.test.tsx +307 -0
- package/Tests/App/Dashboard/SecurityEventAttributeColumnPicker.test.tsx +542 -0
- package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +174 -0
- package/Tests/App/Dashboard/SecurityEventConnectionsEmptyState.test.tsx +973 -212
- package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +491 -52
- package/Tests/App/Dashboard/SecurityEventDetailPanel.test.tsx +301 -0
- package/Tests/App/Dashboard/SecurityEventListRow.test.tsx +158 -1
- package/Tests/App/Dashboard/SecurityEventProviderTile.test.tsx +869 -0
- package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +959 -94
- package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +2 -0
- package/Tests/App/Dashboard/SecurityEventsViewer.test.tsx +274 -1
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/App/Dashboard/TeamMembershipRemoval.test.ts +900 -0
- package/Tests/App/Dashboard/TeamMembershipRemovalPages.test.tsx +460 -0
- package/Tests/App/Dashboard/TimezoneElements.test.tsx +321 -0
- package/Tests/App/Dashboard/TimezoneSelectButton.test.tsx +634 -0
- package/Tests/App/Dashboard/TraceExplorer.test.tsx +260 -0
- package/Tests/App/Dashboard/TraceMonitorPreview.test.tsx +182 -0
- package/Tests/App/Dashboard/UserTimezoneInit.test.tsx +544 -0
- package/Tests/App/Dashboard/WorkflowVariableModals.test.tsx +1630 -0
- package/Tests/App/Dashboard/WorkflowVariableRoutes.test.tsx +603 -0
- package/Tests/App/Dashboard/WorkflowVariableTokenStatus.test.tsx +589 -0
- package/Tests/App/Dashboard/WorkflowVariableUtil.test.ts +1543 -0
- package/Tests/App/Dashboard/WorkflowVariableView.test.tsx +3227 -0
- package/Tests/App/Dashboard/WorkflowVariablesTable.test.tsx +1277 -395
- package/Tests/App/StatusPage/StatusPageOverviewMonitorGroupUptime.test.tsx +714 -0
- package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.NewYork.test.tsx +1009 -0
- package/Tests/App/StatusPage/StatusPageOverviewUptimeBars.test.tsx +1092 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +5 -0
- package/Tests/Models/DatabaseModels/KubernetesClusterAiAccessColumns.test.ts +520 -0
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +66 -2
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Models/DatabaseModels/WorkspaceProjectAuthTokenAccessControl.test.ts +236 -0
- package/Tests/Models/IncidentEpisodePublicNoteNotifyColumnContract.test.ts +103 -0
- package/Tests/Models/IncidentPublicNoteNotifyColumnContract.test.ts +96 -0
- package/Tests/Models/ScheduledMaintenancePublicNoteNotifyColumnContract.test.ts +111 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +976 -0
- package/Tests/Server/API/AutoRemediationApproveKubectlRecheck.test.ts +836 -0
- package/Tests/Server/API/CommonAPIAssertCanCreateTable.test.ts +565 -0
- package/Tests/Server/API/CommonAPIAssertCanReadTable.test.ts +282 -0
- package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +1744 -0
- package/Tests/Server/API/LogHistogramAPI.test.ts +371 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +125 -30
- package/Tests/Server/API/MicrosoftTeamsOAuthState.test.ts +1121 -0
- package/Tests/Server/API/MonitorUptimeSummaryAPI.test.ts +685 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +602 -1
- package/Tests/Server/API/SlackChannelCacheAPI.test.ts +358 -0
- package/Tests/Server/API/SlackOAuthState.test.ts +688 -0
- package/Tests/Server/API/StatusPageOverviewMonitorGroupUptime.test.ts +527 -0
- package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +2 -0
- package/Tests/Server/API/StatusPageUptimeAPI.test.ts +427 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +4 -0
- package/Tests/Server/API/WorkflowVariableAPI.test.ts +404 -0
- package/Tests/Server/API/WorkspaceOAuthTestHelpers.ts +289 -0
- package/Tests/Server/API/WorkspaceSendTestNotificationRoutes.test.ts +2064 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +86 -0
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddKubernetesClusterAiAccessMigration.test.ts +589 -0
- package/Tests/Server/Infrastructure/Postgres/AddOAuth2WorkflowVariablesMigration.test.ts +254 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Middleware/BlockedUserMiddleware.test.ts +403 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +13 -0
- package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +2 -0
- package/Tests/Server/Middleware/RemovedProjectMemberAuthorization.test.ts +469 -0
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +77 -1
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +211 -0
- package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +358 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +97 -0
- package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +2 -0
- package/Tests/Server/Services/AccessTokenProjectUserGrant.test.ts +14 -2
- package/Tests/Server/Services/AccessTokenRemovedMemberAccess.test.ts +1044 -0
- package/Tests/Server/Services/AddOwnersIdempotency.test.ts +5 -0
- package/Tests/Server/Services/AlertEpisodeMemberCreatePermission.test.ts +139 -0
- package/Tests/Server/Services/AutoRemediationClusterRuleEngine.test.ts +1361 -0
- package/Tests/Server/Services/AutoRemediationRuleCommandRunners.test.ts +375 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +2384 -0
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/FindOwnersProjectMembership.test.ts +267 -0
- package/Tests/Server/Services/InboundEmail/SendGridInboundProviderAddress.test.ts +90 -0
- package/Tests/Server/Services/IncidentEpisodeMemberCreatePermission.test.ts +143 -0
- package/Tests/Server/Services/IncidentEpisodePublicNoteNotifyDefault.test.ts +805 -0
- package/Tests/Server/Services/IncidentPublicNoteNotifyDefault.test.ts +724 -0
- package/Tests/Server/Services/IncidentRoleProjectMembership.test.ts +233 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessAgentRunnerAssignments.test.ts +372 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessBindingGuard.test.ts +1876 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessDeletedRunnerRemedy.test.ts +620 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessGapNextSteps.test.ts +421 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessService.test.ts +3662 -0
- package/Tests/Server/Services/KubernetesClusterAiAccessSubjectClusters.test.ts +405 -0
- package/Tests/Server/Services/KubernetesClusterAiSettingsPermission.test.ts +975 -0
- package/Tests/Server/Services/KubernetesClusterServiceAiAccessFeed.test.ts +497 -0
- package/Tests/Server/Services/KubernetesClusterServiceDeleteSettlesRemediation.test.ts +328 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +99 -0
- package/Tests/Server/Services/MonitorServiceIncomingEmailCustomAddress.test.ts +447 -0
- package/Tests/Server/Services/MonitorServiceProbeFlagSync.test.ts +1334 -0
- package/Tests/Server/Services/MonitorServiceProbeStatusGrouping.test.ts +1640 -0
- package/Tests/Server/Services/MonitorServiceRefreshCurrentStatus.test.ts +225 -0
- package/Tests/Server/Services/MonitorStatusBridgeProbePath.test.ts +8 -8
- package/Tests/Server/Services/MonitorStatusTimelineDailyAggregates.test.ts +264 -1
- package/Tests/Server/Services/MonitorStatusTimelineMergedDowntime.test.ts +284 -0
- package/Tests/Server/Services/MonitorUptimeSummaryService.test.ts +1043 -0
- package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +5 -0
- package/Tests/Server/Services/OnCallUndeliverablePageOwnerMembership.test.ts +359 -0
- package/Tests/Server/Services/ProbeServiceDashboardLink.test.ts +191 -0
- package/Tests/Server/Services/ProjectOwnersProjectMembership.test.ts +221 -0
- package/Tests/Server/Services/RuleRun/IncidentAlertMaintenanceRuleEnginesRun.test.ts +5 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +28 -0
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +39 -1
- package/Tests/Server/Services/RunnerJobEnqueueAiCommandAgentRunner.test.ts +230 -0
- package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +978 -0
- package/Tests/Server/Services/RunnerJobKubectlWriteScope.test.ts +866 -0
- package/Tests/Server/Services/RunnerJobSubmitResultRedaction.test.ts +322 -0
- package/Tests/Server/Services/RunnerJobSubmitResultUnstorableText.test.ts +357 -0
- package/Tests/Server/Services/RunnerJobTimeoutReason.test.ts +227 -0
- package/Tests/Server/Services/RunnerServiceAgentName.test.ts +581 -0
- package/Tests/Server/Services/ScheduledMaintenancePublicNoteNotifyDefault.test.ts +802 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +53 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1175 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +174 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +1176 -1
- package/Tests/Server/Services/TeamMemberBillingSeatSync.test.ts +9 -0
- package/Tests/Server/Services/TeamMemberOnCallCleanup.test.ts +18 -0
- package/Tests/Server/Services/TeamMemberProjectMembership.test.ts +255 -0
- package/Tests/Server/Services/TeamMemberRemovalRevokesProjectAccess.test.ts +600 -0
- package/Tests/Server/Services/TeamMemberResourceCleanup.test.ts +731 -0
- package/Tests/Server/Services/TeamMemberWorkspaceAccountCleanup.test.ts +164 -0
- package/Tests/Server/Services/TraceAggregationService.test.ts +64 -0
- package/Tests/Server/Services/UserServiceBlockedUser.test.ts +293 -0
- package/Tests/Server/Services/WorkflowVariableOAuthHooks.test.ts +1016 -0
- package/Tests/Server/Services/WorkflowVariableOAuthPostgres.test.ts +543 -0
- package/Tests/Server/Services/WorkflowVariableRenameUniqueness.test.ts +16 -5
- package/Tests/Server/Services/WorkspaceNotificationRuleNoteDelivery.test.ts +473 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleSendTestNotification.test.ts +3690 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenMiscDataSecrets.test.ts +374 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenSlackChannelCache.test.ts +143 -0
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Database/Permissions/WorkflowVariableOAuthColumnPermission.test.ts +250 -0
- package/Tests/Server/Types/Database/QueryHelperJsonContains.test.ts +96 -0
- package/Tests/Server/Types/Markdown.test.ts +1035 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/ClusterAccessContext.test.ts +285 -0
- package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +159 -1
- package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +752 -0
- package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +1840 -0
- package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +1067 -0
- package/Tests/Server/Utils/AI/KubectlJobRunnerFinishedJob.test.ts +355 -0
- package/Tests/Server/Utils/AI/PostIncidentStatusUpdateTool.test.ts +496 -0
- package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +305 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectl.test.ts +1365 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsKubectlOutcome.test.ts +639 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsLiveCluster.test.ts +1585 -0
- package/Tests/Server/Utils/AI/RemediationCommandToolsRunnerScope.test.ts +1065 -0
- package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +2138 -1
- package/Tests/Server/Utils/AI/RemediationExecutionRunnerClusterMode.test.ts +1035 -0
- package/Tests/Server/Utils/AI/RemediationKubectlCopyParity.test.ts +517 -0
- package/Tests/Server/Utils/AI/ResourceTools.test.ts +27 -1
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1371 -24
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorRollbackRace.test.ts +962 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorUnknownRun.test.ts +518 -0
- package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +939 -2
- package/Tests/Server/Utils/Database/ProjectScopedRelationReferences.test.ts +371 -0
- package/Tests/Server/Utils/Monitor/AffectedResourceList.test.ts +64 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/DatabaseMetricObservationRemediation.test.ts +102 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +362 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1156 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/MonitorEmailAddressRedaction.test.ts +255 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +347 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +203 -0
- package/Tests/Server/Utils/Monitor/ProbeMonitorsNotification.test.ts +1147 -0
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/PushNotificationUtilCreators.test.ts +79 -0
- package/Tests/Server/Utils/ResourceFeed/ResourceFeedUtil.test.ts +21 -1
- package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +155 -1
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +288 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.test.ts +304 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +40 -3
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Server/Utils/Workflow/OAuth2TokenClient.test.ts +702 -0
- package/Tests/Server/Utils/Workflow/OAuth2TokenClientHttpIntegration.test.ts +507 -0
- package/Tests/Server/Utils/Workflow/WorkflowVariableOAuthToken.test.ts +831 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +63 -1
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsAppAccessToken.test.ts +256 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +60 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChatActionAuthorization.test.ts +442 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +44 -2
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +55 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsReactionNoteSync.test.ts +1522 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsServiceUrl.test.ts +130 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +4 -4
- package/Tests/Server/Utils/Workspace/Slack/SlackActionAuthorization.test.ts +691 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackMessageByTimestamp.test.ts +253 -0
- package/Tests/Server/Utils/Workspace/Slack/SlackReactionNote.test.ts +574 -0
- package/Tests/Server/Utils/Workspace/WorkspaceActionAuthorization.test.ts +488 -0
- package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +861 -0
- package/Tests/Server/Utils/Workspace/WorkspaceMessageAuthor.test.ts +635 -0
- package/Tests/Server/Utils/Workspace/WorkspaceOAuthState.test.ts +548 -0
- package/Tests/Server/Utils/Workspace/WorkspaceReactionNote.test.ts +839 -0
- package/Tests/Server/Utils/Workspace/WorkspaceSendErrorMessage.test.ts +725 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +283 -9
- package/Tests/Types/DateBrowserLocalWallClock.test.ts +280 -0
- package/Tests/Types/DateLocalShortMonthNameFallback.test.ts +389 -0
- package/Tests/Types/DateUserTimezone.test.ts +177 -4
- package/Tests/Types/Kubernetes/KubernetesAgentChartAiAccess.test.ts +306 -0
- package/Tests/Types/Kubernetes/KubernetesClusterAiAccess.test.ts +283 -0
- package/Tests/Types/Kubernetes/KubernetesRunnerPostureWriteScope.test.ts +68 -0
- package/Tests/Types/Monitor/DatabaseMonitor/SqlServerPlatform.test.ts +144 -0
- package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +160 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +53 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +91 -16
- package/Tests/Types/Runner/RunnerLiveStatus.test.ts +14 -3
- package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +15 -0
- package/Tests/Types/StatusPage/PublicNoteSubscriberNotificationDefault.test.ts +1868 -0
- package/Tests/Types/Timezone.test.ts +5 -0
- package/Tests/Types/TimezoneAlias.test.ts +1034 -0
- package/Tests/Types/Workflow/JiraTemplateFixtures.ts +833 -0
- package/Tests/Types/Workflow/JiraTemplateScripts.test.ts +5414 -0
- package/Tests/Types/Workflow/JiraTemplateSimulation.test.ts +4803 -0
- package/Tests/Types/Workflow/JiraTemplates.test.ts +5429 -0
- package/Tests/Types/Workflow/Templates.test.ts +79 -4
- package/Tests/Types/Workflow/WorkflowVariableOAuth.test.ts +520 -0
- package/Tests/Types/Workspace/WorkspaceNoteReaction.test.ts +177 -0
- package/Tests/UI/Components/AttributesJSON/AttributesJSONView.test.tsx +390 -0
- package/Tests/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.test.tsx +566 -0
- package/Tests/UI/Components/AttributesJSON/JSONHighlight.test.ts +131 -0
- package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +276 -1
- package/Tests/UI/Components/Calendar/Calendar.test.tsx +1611 -0
- package/Tests/UI/Components/Calendar/CalendarDayLayout.test.ts +1486 -0
- package/Tests/UI/Components/Calendar/CalendarDayLayoutDst.test.ts +632 -0
- package/Tests/UI/Components/Calendar/CalendarDst.test.tsx +388 -0
- package/Tests/UI/Components/Charts/ChartGroupPlotFloor.test.tsx +170 -0
- package/Tests/UI/Components/Charts/HistogramSelection.test.ts +282 -0
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +6 -0
- package/Tests/UI/Components/CustomTimeRangeModalTimezoneTranslation.test.tsx +285 -0
- package/Tests/UI/Components/Dropdown.test.tsx +999 -2
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Forms/FormFieldCheckboxTranslation.test.tsx +216 -0
- package/Tests/UI/Components/Forms/FormFieldTimezoneDescriptionTranslation.test.tsx +461 -0
- package/Tests/UI/Components/Forms/IncidentEpisodePublicNoteNotifyPayload.test.tsx +641 -0
- package/Tests/UI/Components/Forms/IncidentPublicNoteNotifyPayload.test.tsx +346 -0
- package/Tests/UI/Components/Forms/ScheduledMaintenancePublicNoteNotifyPayload.test.tsx +493 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +911 -2
- package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.NewYork.test.tsx +730 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraphTimezone.Tokyo.test.tsx +506 -0
- package/Tests/UI/Components/HistogramClickToZoom.test.tsx +619 -0
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/LogDetailsPanelAttributesJSON.test.tsx +248 -0
- package/Tests/UI/Components/LogsViewerHistogramZoom.test.tsx +47 -1
- package/Tests/UI/Components/LogsViewerSmallScreens.test.tsx +620 -0
- package/Tests/UI/Components/ModelPageModelChange.test.tsx +242 -3
- package/Tests/UI/Components/ModelTable/BaseModelTableDeleteConfirmation.test.tsx +521 -0
- package/Tests/UI/Components/MonitorGraphs/Uptime.test.tsx +406 -2
- package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +169 -2
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerHistogramClickZoom.test.tsx +266 -0
- package/Tests/UI/Components/TelemetryViewer/TelemetryViewerSmallScreens.test.tsx +439 -0
- package/Tests/UI/Components/TimezoneDropdownLegacyValues.test.tsx +704 -0
- package/Tests/UI/Components/UseHistogramZoom.test.tsx +89 -1
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +175 -0
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +5 -5
- package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +28 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +51 -0
- package/Tests/UI/Utils/Timezone.test.ts +747 -33
- package/Tests/UI/Workspace/SendTestNotificationButton.test.tsx +1854 -0
- package/Tests/UI/Workspace/WorkspaceCardsSendTestNotification.test.tsx +1864 -0
- package/Tests/Utils/AI/InvestigationReport.test.ts +435 -7
- package/Tests/Utils/AiRemediation/KubectlAllowlistValidatorParity.test.ts +1084 -0
- package/Tests/Utils/AiRemediation/KubectlOutputRedactor.test.ts +2531 -0
- package/Tests/Utils/AiRemediation/KubectlPolicy.test.ts +1895 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyAllowlist.test.ts +428 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyChartRbacParity.test.ts +828 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyCustomResourceScope.test.ts +573 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyDenials.test.ts +1525 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyExposeOverrides.test.ts +275 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyNodePatch.test.ts +428 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyObjectWords.test.ts +195 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyPatchParents.test.ts +637 -0
- package/Tests/Utils/AiRemediation/KubectlPolicyPodSecurityBaseline.test.ts +322 -0
- package/Tests/Utils/AiRemediation/KubectlPolicySafeWriteScope.test.ts +1026 -0
- package/Tests/Utils/AiRemediation/KubectlWaitBudget.test.ts +267 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScope.test.ts +390 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeCallerParity.test.ts +777 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeClusterScoped.test.ts +613 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeEntryPoint.test.ts +576 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeKubectlSpellings.test.ts +752 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeObjectWords.test.ts +358 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeOverrides.test.ts +293 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopeParityCases.ts +598 -0
- package/Tests/Utils/AiRemediation/KubectlWriteScopePolicyParity.test.ts +403 -0
- package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +154 -5
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Monitor/IncomingEmailMonitorAddress.test.ts +478 -0
- package/Tests/Utils/Monitor/MonitorCheckScheduleUtil.test.ts +646 -0
- package/Tests/Utils/Monitor/MonitorOverviewCriteriaUtil.test.ts +292 -0
- package/Tests/Utils/Monitor/MonitorOverviewFamily.test.ts +274 -0
- package/Tests/Utils/Monitor/MonitorOverviewPresentationUtil.test.ts +3100 -0
- package/Tests/Utils/Monitor/MonitorOverviewProbeUtil.test.ts +1412 -0
- package/Tests/Utils/Monitor/MonitorOverviewTargetUtil.test.ts +374 -0
- package/Tests/Utils/Monitor/MonitorStatusHistoryUtil.test.ts +332 -0
- package/Tests/Utils/Monitor/MonitorUptimeSummaryUtil.test.ts +946 -0
- package/Tests/Utils/Rum/SessionIdentity.test.ts +94 -0
- package/Tests/Utils/Rum/SessionReplayWafCompatibility.test.ts +840 -0
- package/Tests/Utils/Rum/SessionReplayWireEncoding.test.ts +208 -0
- package/Tests/Utils/Rum/UserFlow.test.ts +1010 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Tests/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.test.ts +217 -0
- package/Tests/Utils/StatusPage/ResourceUptime.test.ts +1272 -1
- package/Tests/Utils/StatusPage/UptimeDailyAggregateTotals.test.ts +298 -0
- package/Tests/Utils/StatusPage/UptimeDailyAggregateUtil.test.ts +649 -1
- package/Tests/Utils/StatusPage/UptimeDailyAggregateWindow.test.ts +314 -0
- package/Tests/Utils/Telemetry/AttributesJSON.test.ts +454 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +418 -2
- package/Tests/Utils/Uptime/DayUptimeGraphUtilMidnightDst.test.ts +160 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Auckland.test.ts +45 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Kolkata.test.ts +40 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.London.test.ts +147 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.NewYork.test.ts +48 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezone.Tokyo.test.ts +39 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtilTimezoneCases.ts +1015 -0
- package/Tests/Utils/Uptime/UptimeUtil.test.ts +75 -0
- package/Tests/Utils/Uptime/UptimeUtilChronologicalOrder.test.ts +1279 -0
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/AutoRemediation/AiRemediationCommandPlan.ts +203 -5
- package/Types/Date.ts +121 -7
- package/Types/Email/EmailMessage.ts +8 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Exception/ExceptionMessages.ts +1 -0
- package/Types/Kubernetes/KubernetesClusterAiAccess.ts +488 -0
- package/Types/Kubernetes/KubernetesClusterAiAccessPermissions.ts +54 -0
- package/Types/Kubernetes/KubernetesClusterAiAccessToolNames.ts +17 -0
- package/Types/Monitor/DatabaseMonitor/DatabaseMonitorResponse.ts +9 -0
- package/Types/Monitor/DatabaseMonitor/SqlServerPlatform.ts +117 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Monitor/MonitorStepExceptionMonitor.ts +56 -0
- package/Types/Monitor/MonitorUptimeSummary.ts +113 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +11 -0
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/Types/Rum/SessionReplay.ts +55 -2
- package/Types/Rum/SessionReplayApi.ts +44 -1
- package/Types/Rum/UserFlow.ts +187 -0
- package/Types/Runbook/RunbookStepType.ts +10 -0
- package/Types/Runbook/RunnerJobOrigin.ts +15 -0
- package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +6 -2
- package/Types/StatusPage/MergedDowntimeTotals.ts +17 -0
- package/Types/StatusPage/PublicNoteSubscriberNotificationDefault.ts +148 -0
- package/Types/StatusPage/UptimeDailyAggregate.ts +15 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +9 -0
- package/Types/Timezone.ts +5 -0
- package/Types/TimezoneAlias.ts +385 -0
- package/Types/Workflow/Templates.ts +3041 -2
- package/Types/Workflow/WorkflowVariableOAuth.ts +476 -0
- package/Types/Workspace/WorkspaceNoteReaction.ts +132 -0
- package/Typings/Index.d.ts +24 -0
- package/UI/Components/AttributesJSON/AttributesJSONPreferences.ts +175 -0
- package/UI/Components/AttributesJSON/AttributesJSONView.tsx +138 -0
- package/UI/Components/AttributesJSON/AttributesViewToggle.tsx +79 -0
- package/UI/Components/AttributesJSON/BracesGlyph.tsx +30 -0
- package/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.tsx +491 -0
- package/UI/Components/AttributesJSON/JSONHighlight.ts +77 -0
- package/UI/Components/Calendar/Calendar.css +83 -0
- package/UI/Components/Calendar/Calendar.tsx +136 -7
- package/UI/Components/Calendar/CalendarDayLayout.ts +236 -0
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -1
- package/UI/Components/Charts/Utils/HistogramSelection.ts +117 -0
- package/UI/Components/Charts/Utils/useHistogramRangeSelection.ts +269 -0
- package/UI/Components/Charts/Utils/useHistogramZoom.ts +26 -2
- package/UI/Components/Date/CustomTimeRangeModal.tsx +26 -3
- package/UI/Components/Dropdown/Dropdown.tsx +58 -9
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Forms/Fields/FormField.tsx +49 -14
- package/UI/Components/Graphs/DayUptimeGraph.tsx +166 -25
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +10 -2
- package/UI/Components/Graphs/UptimeDaySummary.tsx +8 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +52 -5
- package/UI/Components/LogsViewer/components/ColumnSelector.tsx +13 -2
- package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +13 -1
- package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +91 -76
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +15 -1
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +28 -106
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +54 -6
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +8 -2
- package/UI/Components/LogsViewer/useLogsHistogram.ts +39 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +66 -14
- package/UI/Components/MonitorGraphs/Uptime.tsx +12 -0
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +10 -2
- package/UI/Components/Page/ModelPage.tsx +59 -3
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +69 -5
- package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +12 -1
- package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +15 -1
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +36 -104
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +9 -1
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +19 -1
- package/UI/Utils/Timezone.ts +69 -28
- package/Utils/AI/InvestigationReport.ts +195 -25
- package/Utils/AiRemediation/KubectlOutputRedactor.ts +1724 -0
- package/Utils/AiRemediation/KubectlPolicy.ts +4136 -0
- package/Utils/AiRemediation/KubectlWaitBudget.ts +156 -0
- package/Utils/AiRemediation/KubectlWriteScope.ts +1964 -0
- package/Utils/Dashboard/Components/DashboardClockComponent.ts +7 -6
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/IncomingEmailMonitorAddress.ts +292 -0
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Monitor/MonitorCheckScheduleUtil.ts +654 -0
- package/Utils/Monitor/MonitorOverviewCriteriaUtil.ts +188 -0
- package/Utils/Monitor/MonitorOverviewFamily.ts +263 -0
- package/Utils/Monitor/MonitorOverviewPresentationUtil.ts +1958 -0
- package/Utils/Monitor/MonitorOverviewProbeUtil.ts +979 -0
- package/Utils/Monitor/MonitorOverviewTargetUtil.ts +299 -0
- package/Utils/Monitor/MonitorStatusHistoryUtil.ts +201 -0
- package/Utils/Monitor/MonitorUptimeSummaryUtil.ts +638 -0
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Rum/SessionIdentity.ts +44 -0
- package/Utils/Rum/SessionReplayWireEncoding.ts +137 -0
- package/Utils/Rum/UserFlow.ts +1527 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.ts +107 -0
- package/Utils/StatusPage/ResourceUptime.ts +99 -0
- package/Utils/StatusPage/UptimeDailyAggregateUtil.ts +197 -0
- package/Utils/Telemetry/AttributesJSON.ts +367 -0
- package/Utils/Uptime/DayUptimeGraphUtil.ts +165 -0
- package/Utils/Uptime/UptimeUtil.ts +122 -38
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +8 -1
- package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
- package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +8 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +10 -3
- package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +10 -3
- package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +434 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +49 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Runner.js +4 -4
- package/build/dist/Models/DatabaseModels/Runner.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +66 -0
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +11 -3
- package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +487 -3
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js +64 -16
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +205 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/AutoRemediationAPI.js +115 -2
- package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +49 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js +564 -0
- package/build/dist/Server/API/KubernetesClusterAiAccessAPI.js.map +1 -0
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +465 -176
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/MonitorAPI.js +107 -1
- package/build/dist/Server/API/MonitorAPI.js.map +1 -1
- package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +5 -16
- package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +322 -78
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +123 -17
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +144 -5
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js +1 -0
- package/build/dist/Server/API/UserTwoFactorBackupCodeAPI.js.map +1 -1
- package/build/dist/Server/API/WorkflowVariableAPI.js +109 -0
- package/build/dist/Server/API/WorkflowVariableAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js +29 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794300000000-AddOAuth2WorkflowVariables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js +54 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794400000000-AddKubernetesClusterAiAccess.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794500000000-AddIncomingEmailCustomAddressToMonitor.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794600000000-AddAuthTokenExpiresAtToWorkspaceProjectAuthToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js +32 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794700000000-RemoveAppAccessTokenFromWorkspaceProjectAuthTokenMiscData.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +21 -0
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +32 -2
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +39 -6
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AIAgentService.js +1 -0
- package/build/dist/Server/Services/AIAgentService.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +87 -42
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeMemberService.js +4 -0
- package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/AlertEpisodeService.js +12 -1
- package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +110 -23
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +716 -30
- package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/AutoRemediationRuleService.js +100 -0
- package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +1 -0
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js +11 -16
- package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js +4 -0
- package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +48 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js +21 -0
- package/build/dist/Server/Services/IncidentEpisodeRoleMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +12 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentGroupingEngineService.js +8 -0
- package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentMemberService.js +14 -0
- package/build/dist/Server/Services/IncidentMemberService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +48 -0
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +99 -21
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +68 -4
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js +1470 -0
- package/build/dist/Server/Services/KubernetesClusterAiAccessService.js.map +1 -0
- package/build/dist/Server/Services/KubernetesClusterService.js +1097 -0
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +14 -6
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +490 -50
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +540 -3
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +3 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js +29 -7
- package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -1
- package/build/dist/Server/Services/OnCallSetupReminderService.js +1 -0
- package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +7 -1
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +11 -1
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RunbookCredentialService.js +24 -0
- package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -1
- package/build/dist/Server/Services/RunbookSecretService.js +44 -0
- package/build/dist/Server/Services/RunbookSecretService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +641 -21
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Services/RunnerService.js +344 -5
- package/build/dist/Server/Services/RunnerService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +48 -0
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +102 -20
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js +67 -4
- package/build/dist/Server/Services/ScheduledMaintenanceTemplateService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -0
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +230 -32
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +4 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +270 -11
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +10 -6
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +1 -0
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +1 -0
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +4 -0
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +107 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/WorkflowVariableService.js +396 -7
- package/build/dist/Server/Services/WorkflowVariableService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +463 -7
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +144 -2
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +20 -0
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +394 -27
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js +123 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/ClusterAccessContext.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +344 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +587 -0
- package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +18 -2
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1380 -239
- package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +1099 -68
- package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +88 -6
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +45 -11
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +41 -11
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +11 -4
- package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +4 -2
- package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +997 -141
- package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +299 -4
- package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +120 -0
- package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -0
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +160 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +1 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +59 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +5 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +84 -22
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +70 -5
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js +249 -0
- package/build/dist/Server/Utils/Monitor/ProbeMonitorsNotification.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js +1 -0
- package/build/dist/Server/Utils/OnCall/OnCallShiftReminderRunner.js.map +1 -1
- package/build/dist/Server/Utils/PushNotificationUtil.js +27 -0
- package/build/dist/Server/Utils/PushNotificationUtil.js.map +1 -1
- package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +26 -3
- package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -1
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +38 -3
- package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +143 -23
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js +406 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayUserFlowReadService.js.map +1 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +4 -3
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +382 -0
- package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -0
- package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js +1 -0
- package/build/dist/Server/Utils/TwoFactorBackupCodeNotification.js.map +1 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js +10 -0
- package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +447 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -0
- package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js +202 -0
- package/build/dist/Server/Utils/Workflow/WorkflowVariableOAuthToken.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +39 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +44 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +150 -191
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +44 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js +39 -17
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Monitor.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +151 -145
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +221 -127
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js +91 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsServiceUrl.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js +673 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/ReactionNoteSync.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js +3 -9
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ActionTypes.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +65 -166
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +63 -157
- package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +23 -3
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js +92 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +81 -210
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +68 -138
- package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js +240 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ReactionNote.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +85 -212
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +60 -17
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +2 -0
- package/build/dist/Server/Utils/Workspace/Workspace.js +134 -41
- package/build/dist/Server/Utils/Workspace/Workspace.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js +197 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceActionAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js +32 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js +199 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceOAuthState.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js +91 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceProjectReferenceValidator.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js +450 -0
- package/build/dist/Server/Utils/Workspace/WorkspaceReactionNote.js.map +1 -0
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +103 -5
- package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
- package/build/dist/Types/Date.js +97 -7
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Exception/ExceptionMessages.js +1 -0
- package/build/dist/Types/Exception/ExceptionMessages.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +245 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js +49 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessPermissions.js.map +1 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js +16 -0
- package/build/dist/Types/Kubernetes/KubernetesClusterAiAccessToolNames.js.map +1 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js +104 -0
- package/build/dist/Types/Monitor/DatabaseMonitor/SqlServerPlatform.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +6 -5
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js +32 -0
- package/build/dist/Types/Monitor/MonitorStepExceptionMonitor.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorUptimeSummary.js +52 -0
- package/build/dist/Types/Monitor/MonitorUptimeSummary.js.map +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +9 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +52 -2
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js +11 -0
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
- package/build/dist/Types/Rum/UserFlow.js +115 -0
- package/build/dist/Types/Rum/UserFlow.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +10 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/Types/Runbook/RunnerJobOrigin.js +14 -0
- package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -1
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +2 -2
- package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -1
- package/build/dist/Types/StatusPage/MergedDowntimeTotals.js +2 -0
- package/build/dist/Types/StatusPage/MergedDowntimeTotals.js.map +1 -0
- package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js +65 -0
- package/build/dist/Types/StatusPage/PublicNoteSubscriberNotificationDefault.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +8 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
- package/build/dist/Types/Timezone.js +5 -0
- package/build/dist/Types/Timezone.js.map +1 -1
- package/build/dist/Types/TimezoneAlias.js +340 -0
- package/build/dist/Types/TimezoneAlias.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +2694 -2
- package/build/dist/Types/Workflow/Templates.js.map +1 -1
- package/build/dist/Types/Workflow/WorkflowVariableOAuth.js +314 -0
- package/build/dist/Types/Workflow/WorkflowVariableOAuth.js.map +1 -0
- package/build/dist/Types/Workspace/WorkspaceNoteReaction.js +98 -0
- package/build/dist/Types/Workspace/WorkspaceNoteReaction.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js +95 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONPreferences.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js +44 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesJSONView.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js +27 -0
- package/build/dist/UI/Components/AttributesJSON/AttributesViewToggle.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js +9 -0
- package/build/dist/UI/Components/AttributesJSON/BracesGlyph.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js +260 -0
- package/build/dist/UI/Components/AttributesJSON/CopyAttributesAsJSONButton.js.map +1 -0
- package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js +56 -0
- package/build/dist/UI/Components/AttributesJSON/JSONHighlight.js.map +1 -0
- package/build/dist/UI/Components/Calendar/Calendar.js +94 -7
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Calendar/CalendarDayLayout.js +136 -0
- package/build/dist/UI/Components/Calendar/CalendarDayLayout.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js +72 -0
- package/build/dist/UI/Components/Charts/Utils/HistogramSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js +178 -0
- package/build/dist/UI/Components/Charts/Utils/useHistogramRangeSelection.js.map +1 -0
- package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js +13 -1
- package/build/dist/UI/Components/Charts/Utils/useHistogramZoom.js.map +1 -1
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +20 -4
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +33 -9
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Forms/Fields/FormField.js +36 -13
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +117 -11
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +36 -8
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js +10 -2
- package/build/dist/UI/Components/LogsViewer/components/ColumnSelector.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +9 -1
- package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +16 -15
- package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +14 -74
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +22 -5
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +9 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +16 -5
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +23 -8
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +7 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +5 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/Page/ModelPage.js +37 -3
- package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +27 -6
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +7 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +17 -77
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +10 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +15 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/Timezone.js +52 -17
- package/build/dist/UI/Utils/Timezone.js.map +1 -1
- package/build/dist/Utils/AI/InvestigationReport.js +116 -21
- package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
- package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js +1209 -0
- package/build/dist/Utils/AiRemediation/KubectlOutputRedactor.js.map +1 -0
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js +2973 -0
- package/build/dist/Utils/AiRemediation/KubectlPolicy.js.map +1 -0
- package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js +99 -0
- package/build/dist/Utils/AiRemediation/KubectlWaitBudget.js.map +1 -0
- package/build/dist/Utils/AiRemediation/KubectlWriteScope.js +1429 -0
- package/build/dist/Utils/AiRemediation/KubectlWriteScope.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +7 -6
- package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js +214 -0
- package/build/dist/Utils/Monitor/IncomingEmailMonitorAddress.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js +471 -0
- package/build/dist/Utils/Monitor/MonitorCheckScheduleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js +107 -0
- package/build/dist/Utils/Monitor/MonitorOverviewCriteriaUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewFamily.js +191 -0
- package/build/dist/Utils/Monitor/MonitorOverviewFamily.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js +1259 -0
- package/build/dist/Utils/Monitor/MonitorOverviewPresentationUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js +593 -0
- package/build/dist/Utils/Monitor/MonitorOverviewProbeUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js +195 -0
- package/build/dist/Utils/Monitor/MonitorOverviewTargetUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js +127 -0
- package/build/dist/Utils/Monitor/MonitorStatusHistoryUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js +464 -0
- package/build/dist/Utils/Monitor/MonitorUptimeSummaryUtil.js.map +1 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Rum/SessionIdentity.js +31 -1
- package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayWireEncoding.js +128 -0
- package/build/dist/Utils/Rum/SessionReplayWireEncoding.js.map +1 -0
- package/build/dist/Utils/Rum/UserFlow.js +941 -0
- package/build/dist/Utils/Rum/UserFlow.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js +82 -0
- package/build/dist/Utils/StatusPage/MonitorGroupMergedDowntimeUtil.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js +46 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -1
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js +148 -0
- package/build/dist/Utils/StatusPage/UptimeDailyAggregateUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/AttributesJSON.js +245 -0
- package/build/dist/Utils/Telemetry/AttributesJSON.js.map +1 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +103 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -1
- package/build/dist/Utils/Uptime/UptimeUtil.js +91 -27
- package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
- package/jest.config.json +2 -1
- package/package.json +2 -1
- package/tsconfig.tests.json +36 -0
- package/Tests/UI/Components/EmptyState/EmptyStateGuideLinks.test.tsx +0 -337
- package/UI/Components/EmptyState/EmptyStateGuideLinks.tsx +0 -124
- package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js +0 -47
- package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js.map +0 -1
|
@@ -0,0 +1,1876 @@
|
|
|
1
|
+
import KubernetesClusterService, {
|
|
2
|
+
AI_ACCESS_CREDENTIAL_REFUSAL,
|
|
3
|
+
getAiAccessCredentialRefusal,
|
|
4
|
+
} from "../../../Server/Services/KubernetesClusterService";
|
|
5
|
+
import RunbookCredentialService from "../../../Server/Services/RunbookCredentialService";
|
|
6
|
+
import RunnerService from "../../../Server/Services/RunnerService";
|
|
7
|
+
import DatabaseService from "../../../Server/Services/DatabaseService";
|
|
8
|
+
import CreateBy from "../../../Server/Types/Database/CreateBy";
|
|
9
|
+
import { OnCreate, OnUpdate } from "../../../Server/Types/Database/Hooks";
|
|
10
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
11
|
+
import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
|
|
12
|
+
import RunbookCredential from "../../../Models/DatabaseModels/RunbookCredential";
|
|
13
|
+
import Runner from "../../../Models/DatabaseModels/Runner";
|
|
14
|
+
import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
15
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
16
|
+
import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
|
|
17
|
+
import { KubernetesAiRemediationMode } from "../../../Types/Kubernetes/KubernetesClusterAiAccess";
|
|
18
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
19
|
+
import Permission, {
|
|
20
|
+
UserTenantAccessPermission,
|
|
21
|
+
} from "../../../Types/Permission";
|
|
22
|
+
import RunbookCredentialType from "../../../Types/Runbook/RunbookCredentialType";
|
|
23
|
+
import KubectlPolicy, {
|
|
24
|
+
KUBECTL_ALLOWLIST_MAX_PATTERNS,
|
|
25
|
+
KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH,
|
|
26
|
+
} from "../../../Utils/AiRemediation/KubectlPolicy";
|
|
27
|
+
import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Contract under test — the tenant guard on a cluster's AI access binding,
|
|
31
|
+
* the shape of the AI remediation settings, and the "AI access was
|
|
32
|
+
* configured" marker.
|
|
33
|
+
*
|
|
34
|
+
* aiAccessRunnerId and aiAccessCredentialId are ordinary CRUD columns, and
|
|
35
|
+
* the framework checks only the tenant relation itself — never that a
|
|
36
|
+
* ManyToOne target belongs to the row's project. Without this guard a
|
|
37
|
+
* caller in project A could point their cluster at project B's Runner or
|
|
38
|
+
* credential and then read that Runner's name, liveness and connection
|
|
39
|
+
* status (or the credential's name and type) through the relation join,
|
|
40
|
+
* and probe foreign ids through the foreign key. So KubernetesClusterService's
|
|
41
|
+
* create and update hooks must:
|
|
42
|
+
*
|
|
43
|
+
* - resolve the Runner and the credential scoped to the cluster's project,
|
|
44
|
+
* under BOTH spellings the payload can use (FK column, relation object);
|
|
45
|
+
* - refuse a Runner or credential from another project, or one that does
|
|
46
|
+
* not exist, with the same message MonitorProbeService uses — and give ONE
|
|
47
|
+
* answer for a credential that is missing and one that is not a
|
|
48
|
+
* Kubernetes credential, so the check is not a type oracle;
|
|
49
|
+
* - let a clear (null) through, and do nothing for writes that do not
|
|
50
|
+
* touch the binding;
|
|
51
|
+
* - on update, use the caller's tenant when there is one and otherwise the
|
|
52
|
+
* project of every cluster the query matches, failing closed when a
|
|
53
|
+
* binding cannot be checked against any project.
|
|
54
|
+
*
|
|
55
|
+
* WHO may bind (only the roles that may author a FullAuto remediation rule,
|
|
56
|
+
* plus credential read for a credential) is covered in depth by
|
|
57
|
+
* KubernetesClusterAiSettingsPermission.test.ts; the binding tests here run
|
|
58
|
+
* as a project admin, who may, and this file pins the credential-read half.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
62
|
+
"22222222-2222-4222-8222-222222222222",
|
|
63
|
+
);
|
|
64
|
+
const OTHER_PROJECT_ID: ObjectID = new ObjectID(
|
|
65
|
+
"99999999-9999-4999-8999-999999999999",
|
|
66
|
+
);
|
|
67
|
+
const CLUSTER_ID: ObjectID = new ObjectID(
|
|
68
|
+
"33333333-3333-4333-8333-333333333333",
|
|
69
|
+
);
|
|
70
|
+
const RUNNER_ID: ObjectID = new ObjectID(
|
|
71
|
+
"44444444-4444-4444-8444-444444444444",
|
|
72
|
+
);
|
|
73
|
+
const CREDENTIAL_ID: ObjectID = new ObjectID(
|
|
74
|
+
"55555555-5555-4555-8555-555555555555",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
type Hooks = {
|
|
78
|
+
onBeforeCreate: (
|
|
79
|
+
createBy: CreateBy<KubernetesCluster>,
|
|
80
|
+
) => Promise<OnCreate<KubernetesCluster>>;
|
|
81
|
+
onBeforeUpdate: (
|
|
82
|
+
updateBy: UpdateBy<KubernetesCluster>,
|
|
83
|
+
) => Promise<OnUpdate<KubernetesCluster>>;
|
|
84
|
+
onUpdateSuccess: (
|
|
85
|
+
onUpdate: OnUpdate<KubernetesCluster>,
|
|
86
|
+
updatedItemIds: Array<ObjectID>,
|
|
87
|
+
) => Promise<OnUpdate<KubernetesCluster>>;
|
|
88
|
+
onCreateSuccess: (
|
|
89
|
+
onCreate: OnCreate<KubernetesCluster>,
|
|
90
|
+
createdItem: KubernetesCluster,
|
|
91
|
+
) => Promise<KubernetesCluster>;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function hooks(): Hooks {
|
|
95
|
+
return KubernetesClusterService as unknown as Hooks;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* A signed-in user holding exactly `permissions` (as grants) in `tenantId`.
|
|
100
|
+
* DatabaseCommonInteractionPropsUtil and holdsAnyPermission read only the
|
|
101
|
+
* tenant bucket keyed by project id, so the key must be right or every
|
|
102
|
+
* refusal below would pass for the wrong reason.
|
|
103
|
+
*/
|
|
104
|
+
function userProps(
|
|
105
|
+
tenantId: ObjectID | undefined,
|
|
106
|
+
permissions: Array<Permission>,
|
|
107
|
+
): DatabaseCommonInteractionProps {
|
|
108
|
+
const permissionProjectId: ObjectID = tenantId || PROJECT_ID;
|
|
109
|
+
const tenantPermission: UserTenantAccessPermission = {
|
|
110
|
+
_type: "UserTenantAccessPermission",
|
|
111
|
+
projectId: permissionProjectId,
|
|
112
|
+
permissions: permissions.map((permission: Permission) => {
|
|
113
|
+
return {
|
|
114
|
+
_type: "UserPermission",
|
|
115
|
+
permission,
|
|
116
|
+
labelIds: [],
|
|
117
|
+
isBlockPermission: false,
|
|
118
|
+
};
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
userId: ObjectID.generate(),
|
|
124
|
+
...(tenantId ? { tenantId } : {}),
|
|
125
|
+
userTenantAccessPermission: {
|
|
126
|
+
[permissionProjectId.toString()]: tenantPermission,
|
|
127
|
+
},
|
|
128
|
+
} as DatabaseCommonInteractionProps;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// A project admin: may bind a Runner and read (so bind) credentials.
|
|
132
|
+
function adminProps(
|
|
133
|
+
tenantId: ObjectID | undefined = PROJECT_ID,
|
|
134
|
+
): DatabaseCommonInteractionProps {
|
|
135
|
+
return userProps(tenantId, [Permission.ProjectAdmin]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function rootProps(): DatabaseCommonInteractionProps {
|
|
139
|
+
return { isRoot: true } as DatabaseCommonInteractionProps;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createBy(
|
|
143
|
+
data: Record<string, unknown>,
|
|
144
|
+
props: DatabaseCommonInteractionProps = adminProps(PROJECT_ID),
|
|
145
|
+
): CreateBy<KubernetesCluster> {
|
|
146
|
+
return {
|
|
147
|
+
data: data as unknown as KubernetesCluster,
|
|
148
|
+
props,
|
|
149
|
+
} as CreateBy<KubernetesCluster>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function updateBy(
|
|
153
|
+
data: Record<string, unknown>,
|
|
154
|
+
props: DatabaseCommonInteractionProps = adminProps(PROJECT_ID),
|
|
155
|
+
): UpdateBy<KubernetesCluster> {
|
|
156
|
+
return {
|
|
157
|
+
query: { _id: CLUSTER_ID.toString() },
|
|
158
|
+
data,
|
|
159
|
+
limit: 1,
|
|
160
|
+
skip: 0,
|
|
161
|
+
props,
|
|
162
|
+
} as unknown as UpdateBy<KubernetesCluster>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function lastQuery(spy: jest.SpyInstance): Record<string, unknown> {
|
|
166
|
+
return (
|
|
167
|
+
spy.mock.calls[spy.mock.calls.length - 1]![0] as {
|
|
168
|
+
query: Record<string, unknown>;
|
|
169
|
+
}
|
|
170
|
+
).query;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type FindByArgs = {
|
|
174
|
+
query: Record<string, unknown>;
|
|
175
|
+
select: Record<string, unknown>;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/*
|
|
179
|
+
* KubernetesClusterService.findBy serves two readers in onBeforeUpdate: the
|
|
180
|
+
* binding guard's "which project does this root update reach?" lookup
|
|
181
|
+
* (selects only projectId) and the operator-write settings read (selects
|
|
182
|
+
* the AI columns too). The binding assertions are about the first.
|
|
183
|
+
*/
|
|
184
|
+
function projectOnlyLookups(spy: jest.SpyInstance): Array<FindByArgs> {
|
|
185
|
+
return spy.mock.calls
|
|
186
|
+
.map((call: Array<unknown>) => {
|
|
187
|
+
return call[0] as FindByArgs;
|
|
188
|
+
})
|
|
189
|
+
.filter((args: FindByArgs) => {
|
|
190
|
+
return !("aiRemediationMode" in (args.select || {}));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function settingsReads(spy: jest.SpyInstance): Array<FindByArgs> {
|
|
195
|
+
return spy.mock.calls
|
|
196
|
+
.map((call: Array<unknown>) => {
|
|
197
|
+
return call[0] as FindByArgs;
|
|
198
|
+
})
|
|
199
|
+
.filter((args: FindByArgs) => {
|
|
200
|
+
return "aiRemediationMode" in (args.select || {});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
describe("KubernetesClusterService AI access binding guard", () => {
|
|
205
|
+
let runnerLookup: jest.SpyInstance;
|
|
206
|
+
let credentialLookup: jest.SpyInstance;
|
|
207
|
+
let clusterLookup: jest.SpyInstance;
|
|
208
|
+
|
|
209
|
+
beforeEach(() => {
|
|
210
|
+
runnerLookup = jest
|
|
211
|
+
.spyOn(RunnerService, "findOneBy")
|
|
212
|
+
.mockResolvedValue({ id: RUNNER_ID } as unknown as Runner);
|
|
213
|
+
/*
|
|
214
|
+
* Assigned to RUNNER_ID: an operator's binding of a Runner and a
|
|
215
|
+
* credential together must be a pair that can work (the credential is
|
|
216
|
+
* assigned to the Runner) — see "the Runner and credential bound
|
|
217
|
+
* together must be able to work" below.
|
|
218
|
+
*/
|
|
219
|
+
credentialLookup = jest
|
|
220
|
+
.spyOn(RunbookCredentialService, "findOneBy")
|
|
221
|
+
.mockResolvedValue({
|
|
222
|
+
id: CREDENTIAL_ID,
|
|
223
|
+
credentialType: RunbookCredentialType.Kubernetes,
|
|
224
|
+
runners: [{ id: RUNNER_ID, _id: RUNNER_ID.toString() }],
|
|
225
|
+
} as unknown as RunbookCredential);
|
|
226
|
+
/*
|
|
227
|
+
* The cluster lives in PROJECT_ID. A read scoped to another project
|
|
228
|
+
* (the operator-write settings read adds the caller's tenant) finds
|
|
229
|
+
* nothing, as Postgres would.
|
|
230
|
+
*/
|
|
231
|
+
clusterLookup = jest
|
|
232
|
+
.spyOn(KubernetesClusterService, "findBy")
|
|
233
|
+
.mockImplementation(
|
|
234
|
+
async (args: unknown): Promise<Array<KubernetesCluster>> => {
|
|
235
|
+
const query: Record<string, unknown> = (args as FindByArgs).query;
|
|
236
|
+
if (
|
|
237
|
+
query["projectId"] &&
|
|
238
|
+
(query["projectId"] as ObjectID).toString() !==
|
|
239
|
+
PROJECT_ID.toString()
|
|
240
|
+
) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
id: CLUSTER_ID,
|
|
246
|
+
projectId: PROJECT_ID,
|
|
247
|
+
} as unknown as KubernetesCluster,
|
|
248
|
+
];
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
afterEach(() => {
|
|
254
|
+
jest.restoreAllMocks();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("installs non-stock create and update hooks", () => {
|
|
258
|
+
expect(hooks().onBeforeCreate).not.toBe(
|
|
259
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
260
|
+
(DatabaseService.prototype as any).onBeforeCreate,
|
|
261
|
+
);
|
|
262
|
+
expect(hooks().onBeforeUpdate).not.toBe(
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
264
|
+
(DatabaseService.prototype as any).onBeforeUpdate,
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
describe("on update", () => {
|
|
269
|
+
it("checks the Runner against the caller's project and lets a same-project Runner through", async () => {
|
|
270
|
+
await hooks().onBeforeUpdate(updateBy({ aiAccessRunnerId: RUNNER_ID }));
|
|
271
|
+
|
|
272
|
+
expect(runnerLookup).toHaveBeenCalledTimes(1);
|
|
273
|
+
expect(lastQuery(runnerLookup)["_id"]).toBe(RUNNER_ID.toString());
|
|
274
|
+
expect(lastQuery(runnerLookup)["projectId"]).toBe(PROJECT_ID);
|
|
275
|
+
// The tenant was on the props; no need to look the project up.
|
|
276
|
+
expect(projectOnlyLookups(clusterLookup)).toHaveLength(0);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("refuses a Runner that is not in the caller's project", async () => {
|
|
280
|
+
runnerLookup.mockResolvedValue(null);
|
|
281
|
+
|
|
282
|
+
await expect(
|
|
283
|
+
hooks().onBeforeUpdate(updateBy({ aiAccessRunnerId: RUNNER_ID })),
|
|
284
|
+
).rejects.toThrow(
|
|
285
|
+
"Runner not found or it does not belong to this project.",
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("reads the relation-object spelling the dashboard posts", async () => {
|
|
290
|
+
await hooks().onBeforeUpdate(
|
|
291
|
+
updateBy({ aiAccessRunner: { _id: RUNNER_ID.toString() } }),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
expect(lastQuery(runnerLookup)["_id"]).toBe(RUNNER_ID.toString());
|
|
295
|
+
|
|
296
|
+
runnerLookup.mockResolvedValue(null);
|
|
297
|
+
|
|
298
|
+
await expect(
|
|
299
|
+
hooks().onBeforeUpdate(
|
|
300
|
+
updateBy({ aiAccessRunner: { _id: RUNNER_ID.toString() } }),
|
|
301
|
+
),
|
|
302
|
+
).rejects.toThrow(BadDataException);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("reads a bare id string in the relation slot, and an ObjectID", async () => {
|
|
306
|
+
await hooks().onBeforeUpdate(
|
|
307
|
+
updateBy({ aiAccessRunner: RUNNER_ID.toString() }),
|
|
308
|
+
);
|
|
309
|
+
expect(lastQuery(runnerLookup)["_id"]).toBe(RUNNER_ID.toString());
|
|
310
|
+
|
|
311
|
+
await hooks().onBeforeUpdate(updateBy({ aiAccessRunner: RUNNER_ID }));
|
|
312
|
+
expect(lastQuery(runnerLookup)["_id"]).toBe(RUNNER_ID.toString());
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("refuses a payload whose two spellings point at different Runners", async () => {
|
|
316
|
+
const otherRunnerId: ObjectID = ObjectID.generate();
|
|
317
|
+
|
|
318
|
+
await expect(
|
|
319
|
+
hooks().onBeforeUpdate(
|
|
320
|
+
updateBy({
|
|
321
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
322
|
+
aiAccessRunner: { _id: otherRunnerId.toString() },
|
|
323
|
+
}),
|
|
324
|
+
),
|
|
325
|
+
).rejects.toThrow(/Conflicting AI access Runner references/);
|
|
326
|
+
|
|
327
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("checks the credential against the caller's project and requires a Kubernetes credential", async () => {
|
|
331
|
+
await hooks().onBeforeUpdate(
|
|
332
|
+
updateBy({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
expect(credentialLookup).toHaveBeenCalledTimes(1);
|
|
336
|
+
expect(lastQuery(credentialLookup)["_id"]).toBe(CREDENTIAL_ID.toString());
|
|
337
|
+
expect(lastQuery(credentialLookup)["projectId"]).toBe(PROJECT_ID);
|
|
338
|
+
|
|
339
|
+
credentialLookup.mockResolvedValue(null);
|
|
340
|
+
await expect(
|
|
341
|
+
hooks().onBeforeUpdate(
|
|
342
|
+
updateBy({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
343
|
+
),
|
|
344
|
+
).rejects.toThrow(AI_ACCESS_CREDENTIAL_REFUSAL);
|
|
345
|
+
|
|
346
|
+
credentialLookup.mockResolvedValue({
|
|
347
|
+
id: CREDENTIAL_ID,
|
|
348
|
+
credentialType: RunbookCredentialType.SSH,
|
|
349
|
+
} as unknown as RunbookCredential);
|
|
350
|
+
await expect(
|
|
351
|
+
hooks().onBeforeUpdate(
|
|
352
|
+
updateBy({ aiAccessCredential: { _id: CREDENTIAL_ID.toString() } }),
|
|
353
|
+
),
|
|
354
|
+
).rejects.toThrow(/must be a Kubernetes credential/);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
/*
|
|
358
|
+
* "Not found" and "wrong type" used to be two different messages, so a
|
|
359
|
+
* caller could learn from the refusal whether an id existed in the
|
|
360
|
+
* project and what kind of credential it was.
|
|
361
|
+
*/
|
|
362
|
+
it("gives the same refusal for a missing credential and a credential of another type", async () => {
|
|
363
|
+
const refusalFor: (
|
|
364
|
+
credential: RunbookCredential | null,
|
|
365
|
+
) => Promise<string> = async (
|
|
366
|
+
credential: RunbookCredential | null,
|
|
367
|
+
): Promise<string> => {
|
|
368
|
+
credentialLookup.mockResolvedValue(credential);
|
|
369
|
+
try {
|
|
370
|
+
await hooks().onBeforeUpdate(
|
|
371
|
+
updateBy({ aiAccessCredentialId: CREDENTIAL_ID }),
|
|
372
|
+
);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
expect(error).toBeInstanceOf(BadDataException);
|
|
375
|
+
return (error as Error).message;
|
|
376
|
+
}
|
|
377
|
+
throw new Error("expected the credential to be refused");
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const missing: string = await refusalFor(null);
|
|
381
|
+
const ssh: string = await refusalFor({
|
|
382
|
+
id: CREDENTIAL_ID,
|
|
383
|
+
credentialType: RunbookCredentialType.SSH,
|
|
384
|
+
} as unknown as RunbookCredential);
|
|
385
|
+
|
|
386
|
+
expect(missing).toBe(AI_ACCESS_CREDENTIAL_REFUSAL);
|
|
387
|
+
expect(ssh).toBe(missing);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("checks both when both are written, and reports the Runner first", async () => {
|
|
391
|
+
runnerLookup.mockResolvedValue(null);
|
|
392
|
+
credentialLookup.mockResolvedValue(null);
|
|
393
|
+
|
|
394
|
+
await expect(
|
|
395
|
+
hooks().onBeforeUpdate(
|
|
396
|
+
updateBy({
|
|
397
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
398
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
399
|
+
}),
|
|
400
|
+
),
|
|
401
|
+
).rejects.toThrow(/Runner not found/);
|
|
402
|
+
|
|
403
|
+
runnerLookup.mockResolvedValue({ id: RUNNER_ID } as unknown as Runner);
|
|
404
|
+
|
|
405
|
+
await expect(
|
|
406
|
+
hooks().onBeforeUpdate(
|
|
407
|
+
updateBy({
|
|
408
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
409
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
410
|
+
}),
|
|
411
|
+
),
|
|
412
|
+
).rejects.toThrow(/Credential not found/);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("lets a clear (null) through without a Runner or credential lookup", async () => {
|
|
416
|
+
await hooks().onBeforeUpdate(
|
|
417
|
+
updateBy({ aiAccessRunnerId: null, aiAccessCredentialId: null }),
|
|
418
|
+
);
|
|
419
|
+
await hooks().onBeforeUpdate(
|
|
420
|
+
updateBy({ aiAccessRunner: null, aiAccessCredential: null }),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
424
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
425
|
+
expect(projectOnlyLookups(clusterLookup)).toHaveLength(0);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
/*
|
|
429
|
+
* Clearing only makes AI do less, so it needs no more than editing the
|
|
430
|
+
* cluster: a project member without any AI-access permission may do it.
|
|
431
|
+
*/
|
|
432
|
+
it("lets a caller with no AI-access permission clear the binding", async () => {
|
|
433
|
+
await hooks().onBeforeUpdate(
|
|
434
|
+
updateBy(
|
|
435
|
+
{ aiAccessRunnerId: null, aiAccessCredentialId: null },
|
|
436
|
+
userProps(PROJECT_ID, [Permission.ProjectMember]),
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
441
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
/*
|
|
445
|
+
* Pinned before this change as "does nothing": a user's write of an AI
|
|
446
|
+
* setting now reads the cluster's current settings (the baseline the
|
|
447
|
+
* permission check compares against and the "before" half of the feed
|
|
448
|
+
* item). It still looks up no Runner, credential or project.
|
|
449
|
+
*/
|
|
450
|
+
it("looks up no Runner, credential or project for an update that does not touch the binding", async () => {
|
|
451
|
+
const update: UpdateBy<KubernetesCluster> = updateBy({
|
|
452
|
+
name: "renamed",
|
|
453
|
+
isAiInvestigationEnabled: true,
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const result: OnUpdate<KubernetesCluster> =
|
|
457
|
+
await hooks().onBeforeUpdate(update);
|
|
458
|
+
|
|
459
|
+
expect(result.updateBy).toBe(update);
|
|
460
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
461
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
462
|
+
expect(projectOnlyLookups(clusterLookup)).toHaveLength(0);
|
|
463
|
+
expect(settingsReads(clusterLookup)).toHaveLength(1);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("reads nothing at all for an update that writes no AI access setting", async () => {
|
|
467
|
+
const update: UpdateBy<KubernetesCluster> = updateBy({
|
|
468
|
+
name: "renamed",
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const result: OnUpdate<KubernetesCluster> =
|
|
472
|
+
await hooks().onBeforeUpdate(update);
|
|
473
|
+
|
|
474
|
+
expect(result.updateBy).toBe(update);
|
|
475
|
+
expect(result.carryForward).toBeNull();
|
|
476
|
+
expect(clusterLookup).not.toHaveBeenCalled();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
/*
|
|
480
|
+
* Root/API updates (the in-cluster registration binds as root) carry
|
|
481
|
+
* no tenantId, so the project comes from the clusters the query
|
|
482
|
+
* matches — and the Runner must belong to every one of them.
|
|
483
|
+
*/
|
|
484
|
+
it("falls back to the project of the matched cluster for a root update", async () => {
|
|
485
|
+
await hooks().onBeforeUpdate(
|
|
486
|
+
updateBy({ aiAccessRunnerId: RUNNER_ID }, rootProps()),
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
expect(clusterLookup).toHaveBeenCalledTimes(1);
|
|
490
|
+
expect(
|
|
491
|
+
(clusterLookup.mock.calls[0]![0] as { query: unknown }).query,
|
|
492
|
+
).toEqual({ _id: CLUSTER_ID.toString() });
|
|
493
|
+
expect(lastQuery(runnerLookup)["projectId"]).toBe(PROJECT_ID);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("refuses a root update whose Runner belongs to a different project than the matched cluster", async () => {
|
|
497
|
+
runnerLookup.mockImplementation(
|
|
498
|
+
async (args: unknown): Promise<Runner | null> => {
|
|
499
|
+
const query: Record<string, unknown> = (
|
|
500
|
+
args as { query: Record<string, unknown> }
|
|
501
|
+
).query;
|
|
502
|
+
return (query["projectId"] as ObjectID).toString() ===
|
|
503
|
+
OTHER_PROJECT_ID.toString()
|
|
504
|
+
? ({ id: RUNNER_ID } as unknown as Runner)
|
|
505
|
+
: null;
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
await expect(
|
|
510
|
+
hooks().onBeforeUpdate(
|
|
511
|
+
updateBy({ aiAccessRunnerId: RUNNER_ID }, rootProps()),
|
|
512
|
+
),
|
|
513
|
+
).rejects.toThrow(/Runner not found/);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("checks the Runner against every project a multi-row root update matches", async () => {
|
|
517
|
+
clusterLookup.mockResolvedValue([
|
|
518
|
+
{ id: CLUSTER_ID, projectId: PROJECT_ID },
|
|
519
|
+
{ id: ObjectID.generate(), projectId: OTHER_PROJECT_ID },
|
|
520
|
+
] as unknown as Array<KubernetesCluster>);
|
|
521
|
+
|
|
522
|
+
await hooks().onBeforeUpdate(
|
|
523
|
+
updateBy({ aiAccessRunnerId: RUNNER_ID }, rootProps()),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const projectsChecked: Array<string> = runnerLookup.mock.calls.map(
|
|
527
|
+
(call: Array<unknown>) => {
|
|
528
|
+
return (
|
|
529
|
+
(call[0] as { query: Record<string, unknown> }).query[
|
|
530
|
+
"projectId"
|
|
531
|
+
] as ObjectID
|
|
532
|
+
).toString();
|
|
533
|
+
},
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
expect(projectsChecked.sort()).toEqual(
|
|
537
|
+
[PROJECT_ID.toString(), OTHER_PROJECT_ID.toString()].sort(),
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("fails closed when a binding is written but no project can be resolved", async () => {
|
|
542
|
+
clusterLookup.mockResolvedValue([]);
|
|
543
|
+
|
|
544
|
+
await expect(
|
|
545
|
+
hooks().onBeforeUpdate(
|
|
546
|
+
updateBy({ aiAccessRunnerId: RUNNER_ID }, rootProps()),
|
|
547
|
+
),
|
|
548
|
+
).rejects.toThrow(/could not be resolved/);
|
|
549
|
+
|
|
550
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("still lets a clear through when no project can be resolved", async () => {
|
|
554
|
+
clusterLookup.mockResolvedValue([]);
|
|
555
|
+
|
|
556
|
+
await hooks().onBeforeUpdate(
|
|
557
|
+
updateBy({ aiAccessRunnerId: null }, rootProps()),
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("prefers the caller's tenant over the matched rows when both are available", async () => {
|
|
564
|
+
await hooks().onBeforeUpdate(
|
|
565
|
+
updateBy({ aiAccessRunnerId: RUNNER_ID }, adminProps(OTHER_PROJECT_ID)),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(projectOnlyLookups(clusterLookup)).toHaveLength(0);
|
|
569
|
+
expect(lastQuery(runnerLookup)["projectId"]).toBe(OTHER_PROJECT_ID);
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
/*
|
|
574
|
+
* The dashboard hides the credential picker from users who may not read
|
|
575
|
+
* credentials, but the CRUD API used to accept any credential id from
|
|
576
|
+
* anyone who may edit the cluster (an id is readable off another
|
|
577
|
+
* cluster's aiAccessCredentialId). The server now applies the picker's
|
|
578
|
+
* rule itself.
|
|
579
|
+
*/
|
|
580
|
+
describe("binding a credential needs permission to read credentials", () => {
|
|
581
|
+
it("refuses a project member binding a same-project Kubernetes credential, before any credential lookup", async () => {
|
|
582
|
+
await expect(
|
|
583
|
+
hooks().onBeforeUpdate(
|
|
584
|
+
updateBy(
|
|
585
|
+
{ aiAccessCredentialId: CREDENTIAL_ID },
|
|
586
|
+
userProps(PROJECT_ID, [Permission.ProjectMember]),
|
|
587
|
+
),
|
|
588
|
+
),
|
|
589
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
590
|
+
|
|
591
|
+
// Refused on who is asking, so the lookup is no oracle for them.
|
|
592
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it("refuses a holder of Edit Auto Remediation Rule without credential read, under both spellings", async () => {
|
|
596
|
+
for (const data of [
|
|
597
|
+
{ aiAccessCredentialId: CREDENTIAL_ID },
|
|
598
|
+
{ aiAccessCredential: { _id: CREDENTIAL_ID.toString() } },
|
|
599
|
+
]) {
|
|
600
|
+
await expect(
|
|
601
|
+
hooks().onBeforeUpdate(
|
|
602
|
+
updateBy(
|
|
603
|
+
data,
|
|
604
|
+
userProps(PROJECT_ID, [Permission.EditAutoRemediationRule]),
|
|
605
|
+
),
|
|
606
|
+
),
|
|
607
|
+
).rejects.toThrow(getAiAccessCredentialRefusal());
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("names the permissions that would allow it", async () => {
|
|
614
|
+
await expect(
|
|
615
|
+
hooks().onBeforeUpdate(
|
|
616
|
+
updateBy(
|
|
617
|
+
{ aiAccessCredentialId: CREDENTIAL_ID },
|
|
618
|
+
userProps(PROJECT_ID, [Permission.EditAutoRemediationRule]),
|
|
619
|
+
),
|
|
620
|
+
),
|
|
621
|
+
).rejects.toThrow(
|
|
622
|
+
/Project Owner, Project Admin, Read Runbook Credential/,
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it("accepts the same binding once the caller may read credentials", async () => {
|
|
627
|
+
await hooks().onBeforeUpdate(
|
|
628
|
+
updateBy(
|
|
629
|
+
{ aiAccessCredentialId: CREDENTIAL_ID },
|
|
630
|
+
userProps(PROJECT_ID, [
|
|
631
|
+
Permission.EditAutoRemediationRule,
|
|
632
|
+
Permission.ReadRunbookCredential,
|
|
633
|
+
]),
|
|
634
|
+
),
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
expect(credentialLookup).toHaveBeenCalledTimes(1);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("accepts a project admin and a project owner", async () => {
|
|
641
|
+
await hooks().onBeforeUpdate(
|
|
642
|
+
updateBy({ aiAccessCredentialId: CREDENTIAL_ID }, adminProps()),
|
|
643
|
+
);
|
|
644
|
+
await hooks().onBeforeUpdate(
|
|
645
|
+
updateBy(
|
|
646
|
+
{ aiAccessCredential: { _id: CREDENTIAL_ID.toString() } },
|
|
647
|
+
userProps(PROJECT_ID, [Permission.ProjectOwner]),
|
|
648
|
+
),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
expect(credentialLookup).toHaveBeenCalledTimes(2);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it("does not read a block row for credential read as a grant", async () => {
|
|
655
|
+
const props: DatabaseCommonInteractionProps = userProps(PROJECT_ID, [
|
|
656
|
+
Permission.EditAutoRemediationRule,
|
|
657
|
+
]);
|
|
658
|
+
props.userTenantAccessPermission![
|
|
659
|
+
PROJECT_ID.toString()
|
|
660
|
+
]!.permissions.push({
|
|
661
|
+
_type: "UserPermission",
|
|
662
|
+
permission: Permission.ReadRunbookCredential,
|
|
663
|
+
labelIds: [],
|
|
664
|
+
isBlockPermission: true,
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
await expect(
|
|
668
|
+
hooks().onBeforeUpdate(
|
|
669
|
+
updateBy({ aiAccessCredentialId: CREDENTIAL_ID }, props),
|
|
670
|
+
),
|
|
671
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
it("lets the in-cluster registration (root) bind without any permission rows", async () => {
|
|
675
|
+
await hooks().onBeforeUpdate(
|
|
676
|
+
updateBy(
|
|
677
|
+
{
|
|
678
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
679
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
680
|
+
},
|
|
681
|
+
rootProps(),
|
|
682
|
+
),
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
expect(credentialLookup).toHaveBeenCalledTimes(1);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
/*
|
|
689
|
+
* The AI page posts every field of its form, so a member who only flips
|
|
690
|
+
* the investigation switch re-posts the credential an admin bound.
|
|
691
|
+
* Unchanged is not "binding".
|
|
692
|
+
*/
|
|
693
|
+
it("lets a member re-post the credential that is already bound", async () => {
|
|
694
|
+
clusterLookup.mockResolvedValue([
|
|
695
|
+
{
|
|
696
|
+
id: CLUSTER_ID,
|
|
697
|
+
projectId: PROJECT_ID,
|
|
698
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
699
|
+
} as unknown as KubernetesCluster,
|
|
700
|
+
]);
|
|
701
|
+
|
|
702
|
+
await hooks().onBeforeUpdate(
|
|
703
|
+
updateBy(
|
|
704
|
+
{
|
|
705
|
+
isAiInvestigationEnabled: false,
|
|
706
|
+
aiAccessCredential: { _id: CREDENTIAL_ID.toString() },
|
|
707
|
+
},
|
|
708
|
+
userProps(PROJECT_ID, [Permission.ProjectMember]),
|
|
709
|
+
),
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
/*
|
|
715
|
+
* A Settings Member cannot approve a single kubectl plan, so it must not
|
|
716
|
+
* be able to remove the approval step either. The full role matrix lives
|
|
717
|
+
* in KubernetesClusterAiSettingsPermission.test.ts; this is the shortest
|
|
718
|
+
* statement of the escalation that was closed.
|
|
719
|
+
*/
|
|
720
|
+
describe("only roles that may author a FullAuto rule can make a cluster unattended", () => {
|
|
721
|
+
it.each([
|
|
722
|
+
[{ aiRemediationMode: KubernetesAiRemediationMode.BypassApproval }],
|
|
723
|
+
[{ aiRemediationMode: KubernetesAiRemediationMode.Automatic }],
|
|
724
|
+
/*
|
|
725
|
+
* Was ["*"]. A * where the verb goes is no longer a valid entry
|
|
726
|
+
* (KubectlPolicy.describeAllowlistPatternProblem), and validity is
|
|
727
|
+
* checked before who may loosen, so "*" now reads as a BadDataException
|
|
728
|
+
* for every caller (pinned below). A valid entry that is as broad as
|
|
729
|
+
* one can be still exercises the escalation.
|
|
730
|
+
*/
|
|
731
|
+
[{ aiKubectlCommandAllowlist: ["kubectl delete * * -n *"] }],
|
|
732
|
+
])(
|
|
733
|
+
"refuses %p from a Settings Member and accepts it from a Project Admin",
|
|
734
|
+
async (data: Record<string, unknown>) => {
|
|
735
|
+
await expect(
|
|
736
|
+
hooks().onBeforeUpdate(
|
|
737
|
+
updateBy(data, userProps(PROJECT_ID, [Permission.SettingsMember])),
|
|
738
|
+
),
|
|
739
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
740
|
+
|
|
741
|
+
await expect(
|
|
742
|
+
hooks().onBeforeUpdate(updateBy(data, adminProps())),
|
|
743
|
+
).resolves.toBeDefined();
|
|
744
|
+
},
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
it.each([[["*"]], [["kubectl *"]], [["* * -n web"]]])(
|
|
748
|
+
"refuses the verb-glob allowlist %p as invalid for every caller, before who may loosen is judged",
|
|
749
|
+
async (allowlist: Array<string>) => {
|
|
750
|
+
for (const props of [
|
|
751
|
+
userProps(PROJECT_ID, [Permission.SettingsMember]),
|
|
752
|
+
adminProps(),
|
|
753
|
+
]) {
|
|
754
|
+
await expect(
|
|
755
|
+
hooks().onBeforeUpdate(
|
|
756
|
+
updateBy({ aiKubectlCommandAllowlist: allowlist }, props),
|
|
757
|
+
),
|
|
758
|
+
).rejects.toThrow(BadDataException);
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
describe("on create", () => {
|
|
765
|
+
it("checks the binding against the row's own project", async () => {
|
|
766
|
+
await hooks().onBeforeCreate(
|
|
767
|
+
createBy({
|
|
768
|
+
projectId: PROJECT_ID,
|
|
769
|
+
name: "prod-us",
|
|
770
|
+
clusterIdentifier: "prod-us",
|
|
771
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
772
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
773
|
+
}),
|
|
774
|
+
);
|
|
775
|
+
|
|
776
|
+
expect(lastQuery(runnerLookup)["projectId"]).toBe(PROJECT_ID);
|
|
777
|
+
expect(lastQuery(credentialLookup)["projectId"]).toBe(PROJECT_ID);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it("refuses a foreign Runner and a foreign credential on create", async () => {
|
|
781
|
+
runnerLookup.mockResolvedValue(null);
|
|
782
|
+
|
|
783
|
+
await expect(
|
|
784
|
+
hooks().onBeforeCreate(
|
|
785
|
+
createBy({
|
|
786
|
+
projectId: PROJECT_ID,
|
|
787
|
+
name: "prod-us",
|
|
788
|
+
aiAccessRunner: { _id: RUNNER_ID.toString() },
|
|
789
|
+
}),
|
|
790
|
+
),
|
|
791
|
+
).rejects.toThrow(/Runner not found/);
|
|
792
|
+
|
|
793
|
+
credentialLookup.mockResolvedValue(null);
|
|
794
|
+
|
|
795
|
+
await expect(
|
|
796
|
+
hooks().onBeforeCreate(
|
|
797
|
+
createBy({
|
|
798
|
+
projectId: PROJECT_ID,
|
|
799
|
+
name: "prod-us",
|
|
800
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
801
|
+
}),
|
|
802
|
+
),
|
|
803
|
+
).rejects.toThrow(/Credential not found/);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it("falls back to the project relation and then to the caller's tenant", async () => {
|
|
807
|
+
await hooks().onBeforeCreate(
|
|
808
|
+
createBy({
|
|
809
|
+
project: { id: PROJECT_ID, _id: PROJECT_ID.toString() },
|
|
810
|
+
name: "prod-us",
|
|
811
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
812
|
+
}),
|
|
813
|
+
);
|
|
814
|
+
expect(lastQuery(runnerLookup)["projectId"]).toBe(PROJECT_ID);
|
|
815
|
+
|
|
816
|
+
await hooks().onBeforeCreate(
|
|
817
|
+
createBy(
|
|
818
|
+
{ name: "prod-us", aiAccessRunnerId: RUNNER_ID },
|
|
819
|
+
adminProps(OTHER_PROJECT_ID),
|
|
820
|
+
),
|
|
821
|
+
);
|
|
822
|
+
expect(lastQuery(runnerLookup)["projectId"]).toBe(OTHER_PROJECT_ID);
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
it("fails closed when a binding is given and no project is known", async () => {
|
|
826
|
+
await expect(
|
|
827
|
+
hooks().onBeforeCreate(
|
|
828
|
+
createBy(
|
|
829
|
+
{ name: "prod-us", aiAccessRunnerId: RUNNER_ID },
|
|
830
|
+
rootProps(),
|
|
831
|
+
),
|
|
832
|
+
),
|
|
833
|
+
).rejects.toThrow(/could not be resolved/);
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
it("does nothing for a create without a binding (the ingest path)", async () => {
|
|
837
|
+
const create: CreateBy<KubernetesCluster> = createBy(
|
|
838
|
+
{
|
|
839
|
+
projectId: PROJECT_ID,
|
|
840
|
+
name: "prod-us",
|
|
841
|
+
clusterIdentifier: "prod-us",
|
|
842
|
+
},
|
|
843
|
+
rootProps(),
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
const result: OnCreate<KubernetesCluster> =
|
|
847
|
+
await hooks().onBeforeCreate(create);
|
|
848
|
+
|
|
849
|
+
expect(result.createBy).toBe(create);
|
|
850
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
851
|
+
expect(credentialLookup).not.toHaveBeenCalled();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it("refuses a create that binds a credential for a caller without credential read", async () => {
|
|
855
|
+
await expect(
|
|
856
|
+
hooks().onBeforeCreate(
|
|
857
|
+
createBy(
|
|
858
|
+
{
|
|
859
|
+
projectId: PROJECT_ID,
|
|
860
|
+
name: "prod-us",
|
|
861
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
862
|
+
},
|
|
863
|
+
userProps(PROJECT_ID, [Permission.EditAutoRemediationRule]),
|
|
864
|
+
),
|
|
865
|
+
),
|
|
866
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
/*
|
|
872
|
+
* Known follow-up 9: the dashboard stopped offering these pairs, but the API
|
|
873
|
+
* and Terraform could still bind them. An operator's binding must be a pair
|
|
874
|
+
* that can work, per cluster, judged only where the pair changes (the AI
|
|
875
|
+
* page re-posts unchanged values):
|
|
876
|
+
* - another cluster's kubernetes-agent Runner cannot be bound (by its name
|
|
877
|
+
* for another cluster, or a posture naming another cluster);
|
|
878
|
+
* - a credential never goes with another cluster's agent Runner;
|
|
879
|
+
* - a credential must be assigned to the Runner it is bound with — except
|
|
880
|
+
* with THIS cluster's agent Runner, whose own ServiceAccount wins.
|
|
881
|
+
*/
|
|
882
|
+
describe("KubernetesClusterService: the Runner and credential bound together must be able to work", () => {
|
|
883
|
+
const OTHER_AGENT_ID: ObjectID = new ObjectID(
|
|
884
|
+
"66666666-6666-4666-8666-666666666666",
|
|
885
|
+
);
|
|
886
|
+
const OWN_AGENT_ID: ObjectID = new ObjectID(
|
|
887
|
+
"77777777-7777-4777-8777-777777777777",
|
|
888
|
+
);
|
|
889
|
+
// Another cluster's agent row renamed by root: only its posture marks it.
|
|
890
|
+
const RENAMED_OTHER_AGENT_ID: ObjectID = new ObjectID(
|
|
891
|
+
"88888888-8888-4888-8888-888888888888",
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
const runners: Record<string, Runner> = {
|
|
895
|
+
[RENAMED_OTHER_AGENT_ID.toString()]: {
|
|
896
|
+
id: RENAMED_OTHER_AGENT_ID,
|
|
897
|
+
name: "prod-eu in-cluster runner",
|
|
898
|
+
hostInfo: {
|
|
899
|
+
kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
|
|
900
|
+
},
|
|
901
|
+
} as unknown as Runner,
|
|
902
|
+
[RUNNER_ID.toString()]: {
|
|
903
|
+
id: RUNNER_ID,
|
|
904
|
+
name: "office-runner",
|
|
905
|
+
hostInfo: {},
|
|
906
|
+
} as unknown as Runner,
|
|
907
|
+
[OTHER_AGENT_ID.toString()]: {
|
|
908
|
+
id: OTHER_AGENT_ID,
|
|
909
|
+
name: "kubernetes-agent/prod-eu",
|
|
910
|
+
hostInfo: {
|
|
911
|
+
kubernetes: { inCluster: true, clusterIdentifier: "prod-eu" },
|
|
912
|
+
},
|
|
913
|
+
} as unknown as Runner,
|
|
914
|
+
[OWN_AGENT_ID.toString()]: {
|
|
915
|
+
id: OWN_AGENT_ID,
|
|
916
|
+
name: "kubernetes-agent/prod-us",
|
|
917
|
+
hostInfo: {
|
|
918
|
+
kubernetes: { inCluster: true, clusterIdentifier: "prod-us" },
|
|
919
|
+
},
|
|
920
|
+
} as unknown as Runner,
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
let runnerLookup: jest.SpyInstance;
|
|
924
|
+
let credentialLookup: jest.SpyInstance;
|
|
925
|
+
let current: Record<string, unknown>;
|
|
926
|
+
|
|
927
|
+
function assignedCredential(assignedTo: Array<ObjectID>): RunbookCredential {
|
|
928
|
+
return {
|
|
929
|
+
id: CREDENTIAL_ID,
|
|
930
|
+
credentialType: RunbookCredentialType.Kubernetes,
|
|
931
|
+
runners: assignedTo.map((id: ObjectID) => {
|
|
932
|
+
return { id, _id: id.toString() };
|
|
933
|
+
}),
|
|
934
|
+
} as unknown as RunbookCredential;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
beforeEach(() => {
|
|
938
|
+
current = {
|
|
939
|
+
id: CLUSTER_ID,
|
|
940
|
+
projectId: PROJECT_ID,
|
|
941
|
+
clusterIdentifier: "prod-us",
|
|
942
|
+
};
|
|
943
|
+
runnerLookup = jest
|
|
944
|
+
.spyOn(RunnerService, "findOneBy")
|
|
945
|
+
.mockImplementation(async (args: unknown): Promise<Runner | null> => {
|
|
946
|
+
const query: Record<string, unknown> = (
|
|
947
|
+
args as { query: Record<string, unknown> }
|
|
948
|
+
).query;
|
|
949
|
+
return runners[String(query["_id"])] || null;
|
|
950
|
+
});
|
|
951
|
+
credentialLookup = jest
|
|
952
|
+
.spyOn(RunbookCredentialService, "findOneBy")
|
|
953
|
+
.mockResolvedValue(assignedCredential([RUNNER_ID]));
|
|
954
|
+
jest
|
|
955
|
+
.spyOn(KubernetesClusterService, "findBy")
|
|
956
|
+
.mockImplementation(async (): Promise<Array<KubernetesCluster>> => {
|
|
957
|
+
return [current as unknown as KubernetesCluster];
|
|
958
|
+
});
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
afterEach(() => {
|
|
962
|
+
jest.restoreAllMocks();
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
async function refusalOf(data: Record<string, unknown>): Promise<string> {
|
|
966
|
+
try {
|
|
967
|
+
await hooks().onBeforeUpdate(updateBy(data));
|
|
968
|
+
} catch (error) {
|
|
969
|
+
expect(error).toBeInstanceOf(BadDataException);
|
|
970
|
+
return (error as Error).message;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
throw new Error("expected the binding to be refused");
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
it("refuses binding another cluster's in-cluster Runner", async () => {
|
|
977
|
+
const message: string = await refusalOf({
|
|
978
|
+
aiAccessRunnerId: OTHER_AGENT_ID,
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
expect(message).toContain('Runner "kubernetes-agent/prod-eu"');
|
|
982
|
+
expect(message).toContain("another cluster");
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
it("refuses a renamed agent row of another cluster by its posture", async () => {
|
|
986
|
+
await refusalOf({
|
|
987
|
+
aiAccessRunner: { _id: RENAMED_OTHER_AGENT_ID.toString() },
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it("negative control: THIS cluster's agent Runner and an ordinary Runner bind", async () => {
|
|
992
|
+
await expect(
|
|
993
|
+
hooks().onBeforeUpdate(updateBy({ aiAccessRunnerId: OWN_AGENT_ID })),
|
|
994
|
+
).resolves.toBeDefined();
|
|
995
|
+
await expect(
|
|
996
|
+
hooks().onBeforeUpdate(updateBy({ aiAccessRunnerId: RUNNER_ID })),
|
|
997
|
+
).resolves.toBeDefined();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
it("refuses a credential with another cluster's agent Runner that is already bound", async () => {
|
|
1001
|
+
current["aiAccessRunnerId"] = OTHER_AGENT_ID;
|
|
1002
|
+
|
|
1003
|
+
const message: string = await refusalOf({
|
|
1004
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
expect(message).toContain("never given a credential");
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it("refuses a credential that is not assigned to the chosen Runner", async () => {
|
|
1011
|
+
credentialLookup.mockResolvedValue(assignedCredential([]));
|
|
1012
|
+
|
|
1013
|
+
const message: string = await refusalOf({
|
|
1014
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
1015
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// Never names the credential: the refusal is readable to cluster editors.
|
|
1019
|
+
expect(message).toContain('is not assigned to Runner "office-runner"');
|
|
1020
|
+
expect(message).toContain("Runner Credentials");
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it("refuses switching the Runner while an unassigned credential stays bound", async () => {
|
|
1024
|
+
current["aiAccessRunnerId"] = OWN_AGENT_ID;
|
|
1025
|
+
current["aiAccessCredentialId"] = CREDENTIAL_ID;
|
|
1026
|
+
credentialLookup.mockResolvedValue(assignedCredential([OWN_AGENT_ID]));
|
|
1027
|
+
|
|
1028
|
+
await refusalOf({ aiAccessRunnerId: RUNNER_ID });
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
it("negative control: the same credential assigned to the Runner binds", async () => {
|
|
1032
|
+
await expect(
|
|
1033
|
+
hooks().onBeforeUpdate(
|
|
1034
|
+
updateBy({
|
|
1035
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
1036
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
1037
|
+
}),
|
|
1038
|
+
),
|
|
1039
|
+
).resolves.toBeDefined();
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
it("negative control: with THIS cluster's agent a stale credential is no obstacle (in-cluster access wins)", async () => {
|
|
1043
|
+
current["aiAccessCredentialId"] = CREDENTIAL_ID;
|
|
1044
|
+
credentialLookup.mockResolvedValue(assignedCredential([]));
|
|
1045
|
+
|
|
1046
|
+
await expect(
|
|
1047
|
+
hooks().onBeforeUpdate(updateBy({ aiAccessRunnerId: OWN_AGENT_ID })),
|
|
1048
|
+
).resolves.toBeDefined();
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
it("negative control: re-posting an unchanged pair is not judged again", async () => {
|
|
1052
|
+
current["aiAccessRunnerId"] = OTHER_AGENT_ID;
|
|
1053
|
+
current["aiAccessCredentialId"] = CREDENTIAL_ID;
|
|
1054
|
+
|
|
1055
|
+
await expect(
|
|
1056
|
+
hooks().onBeforeUpdate(
|
|
1057
|
+
updateBy({
|
|
1058
|
+
isAiInvestigationEnabled: true,
|
|
1059
|
+
aiAccessRunnerId: OTHER_AGENT_ID,
|
|
1060
|
+
aiAccessCredentialId: CREDENTIAL_ID,
|
|
1061
|
+
}),
|
|
1062
|
+
),
|
|
1063
|
+
).resolves.toBeDefined();
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
it("negative control: clearing the Runner never needs a pair check", async () => {
|
|
1067
|
+
current["aiAccessRunnerId"] = OTHER_AGENT_ID;
|
|
1068
|
+
current["aiAccessCredentialId"] = CREDENTIAL_ID;
|
|
1069
|
+
|
|
1070
|
+
await expect(
|
|
1071
|
+
hooks().onBeforeUpdate(updateBy({ aiAccessRunnerId: null })),
|
|
1072
|
+
).resolves.toBeDefined();
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it("negative control: the server's own (root) writes are not operator choices", async () => {
|
|
1076
|
+
await expect(
|
|
1077
|
+
hooks().onBeforeUpdate(
|
|
1078
|
+
updateBy(
|
|
1079
|
+
{ aiAccessRunnerId: OTHER_AGENT_ID, aiAccessCredentialId: null },
|
|
1080
|
+
rootProps(),
|
|
1081
|
+
),
|
|
1082
|
+
),
|
|
1083
|
+
).resolves.toBeDefined();
|
|
1084
|
+
expect(runnerLookup).toHaveBeenCalledTimes(1);
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
it("applies to an operator's create too", async () => {
|
|
1088
|
+
await expect(
|
|
1089
|
+
hooks().onBeforeCreate(
|
|
1090
|
+
createBy(
|
|
1091
|
+
{
|
|
1092
|
+
projectId: PROJECT_ID,
|
|
1093
|
+
name: "prod-us",
|
|
1094
|
+
clusterIdentifier: "prod-us",
|
|
1095
|
+
aiAccessRunnerId: OTHER_AGENT_ID,
|
|
1096
|
+
},
|
|
1097
|
+
{ isMasterAdmin: true, userId: ObjectID.generate() },
|
|
1098
|
+
),
|
|
1099
|
+
),
|
|
1100
|
+
).rejects.toThrow(/another cluster/);
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
/*
|
|
1105
|
+
* aiRemediationMode is a plain text column and aiKubectlCommandAllowlist
|
|
1106
|
+
* untyped JSON; their readers map anything unusable to Disabled / "no
|
|
1107
|
+
* pattern". Without write-side validation an API or Terraform write of
|
|
1108
|
+
* "automatic" or of a pattern that can never match answered 200, showed no
|
|
1109
|
+
* drift, and silently did nothing.
|
|
1110
|
+
*/
|
|
1111
|
+
describe("KubernetesClusterService AI remediation settings validation", () => {
|
|
1112
|
+
let runnerLookup: jest.SpyInstance;
|
|
1113
|
+
|
|
1114
|
+
beforeEach(() => {
|
|
1115
|
+
runnerLookup = jest
|
|
1116
|
+
.spyOn(RunnerService, "findOneBy")
|
|
1117
|
+
.mockResolvedValue({ id: RUNNER_ID } as unknown as Runner);
|
|
1118
|
+
jest.spyOn(KubernetesClusterService, "findBy").mockResolvedValue([
|
|
1119
|
+
{
|
|
1120
|
+
id: CLUSTER_ID,
|
|
1121
|
+
projectId: PROJECT_ID,
|
|
1122
|
+
} as unknown as KubernetesCluster,
|
|
1123
|
+
]);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
afterEach(() => {
|
|
1127
|
+
jest.restoreAllMocks();
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
async function update(
|
|
1131
|
+
data: Record<string, unknown>,
|
|
1132
|
+
): Promise<UpdateBy<KubernetesCluster>> {
|
|
1133
|
+
const update: UpdateBy<KubernetesCluster> = updateBy(data);
|
|
1134
|
+
await hooks().onBeforeUpdate(update);
|
|
1135
|
+
return update;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function writtenAllowlist(update: UpdateBy<KubernetesCluster>): unknown {
|
|
1139
|
+
return (update.data as unknown as Record<string, unknown>)[
|
|
1140
|
+
"aiKubectlCommandAllowlist"
|
|
1141
|
+
];
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
describe("remediation mode", () => {
|
|
1145
|
+
it.each([
|
|
1146
|
+
["automatic"],
|
|
1147
|
+
["Automatic "],
|
|
1148
|
+
["Bypass"],
|
|
1149
|
+
["FullAuto"],
|
|
1150
|
+
[""],
|
|
1151
|
+
[null],
|
|
1152
|
+
[3],
|
|
1153
|
+
])("refuses %p", async (mode: unknown) => {
|
|
1154
|
+
await expect(update({ aiRemediationMode: mode })).rejects.toThrow(
|
|
1155
|
+
BadDataException,
|
|
1156
|
+
);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
it("says which values are accepted and what it got", async () => {
|
|
1160
|
+
await expect(update({ aiRemediationMode: "automatic" })).rejects.toThrow(
|
|
1161
|
+
'AI remediation mode must be one of Disabled, RequireApproval, Automatic, BypassApproval (got "automatic").',
|
|
1162
|
+
);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
it.each(Object.values(KubernetesAiRemediationMode))(
|
|
1166
|
+
"accepts %s",
|
|
1167
|
+
async (mode: KubernetesAiRemediationMode) => {
|
|
1168
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1169
|
+
aiRemediationMode: mode,
|
|
1170
|
+
});
|
|
1171
|
+
expect(
|
|
1172
|
+
(written.data as unknown as Record<string, unknown>)[
|
|
1173
|
+
"aiRemediationMode"
|
|
1174
|
+
],
|
|
1175
|
+
).toBe(mode);
|
|
1176
|
+
},
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
it("rejects on the mode even when a valid Runner is written alongside it (the check is not behind the binding guard)", async () => {
|
|
1180
|
+
await expect(
|
|
1181
|
+
update({ aiRemediationMode: "automatic", aiAccessRunnerId: RUNNER_ID }),
|
|
1182
|
+
).rejects.toThrow(/AI remediation mode must be one of/);
|
|
1183
|
+
|
|
1184
|
+
expect(runnerLookup).not.toHaveBeenCalled();
|
|
1185
|
+
});
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
describe("kubectl allowlist", () => {
|
|
1189
|
+
it.each([
|
|
1190
|
+
[{ a: 1 }, /must be a JSON array/],
|
|
1191
|
+
[42, /must be a JSON array/],
|
|
1192
|
+
[true, /must be a JSON array/],
|
|
1193
|
+
["kubectl scale *", /must be a JSON array/],
|
|
1194
|
+
['{"a": 1}', /must be a JSON array/],
|
|
1195
|
+
[[1], /Pattern 1 of the kubectl allowlist must be text \(got number\)/],
|
|
1196
|
+
[
|
|
1197
|
+
["kubectl scale *", null],
|
|
1198
|
+
/Pattern 2 of the kubectl allowlist must be text \(got null\)/,
|
|
1199
|
+
],
|
|
1200
|
+
[[""], /Pattern 1 of the kubectl allowlist cannot be used/],
|
|
1201
|
+
[[" "], /Pattern 1 of the kubectl allowlist cannot be used/],
|
|
1202
|
+
// No verb at all: the matcher can never use it.
|
|
1203
|
+
[["kubectl"], /Pattern 1 of the kubectl allowlist cannot be used/],
|
|
1204
|
+
// Unbalanced quotes: not one kubectl command line.
|
|
1205
|
+
[
|
|
1206
|
+
[
|
|
1207
|
+
"kubectl scale deployment/web --replicas=*",
|
|
1208
|
+
'kubectl patch deployment/web -p \'{"spec":',
|
|
1209
|
+
],
|
|
1210
|
+
/Pattern 2 of the kubectl allowlist cannot be used/,
|
|
1211
|
+
],
|
|
1212
|
+
])("refuses %p", async (value: unknown, message: RegExp) => {
|
|
1213
|
+
await expect(
|
|
1214
|
+
update({ aiKubectlCommandAllowlist: value }),
|
|
1215
|
+
).rejects.toThrow(message);
|
|
1216
|
+
await expect(
|
|
1217
|
+
update({ aiKubectlCommandAllowlist: value }),
|
|
1218
|
+
).rejects.toThrow(BadDataException);
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
it("refuses a pattern that is not one kubectl command line, saying how patterns are matched", async () => {
|
|
1222
|
+
await expect(
|
|
1223
|
+
update({ aiKubectlCommandAllowlist: ["kubectl get\npods"] }),
|
|
1224
|
+
).rejects.toThrow(/cannot be used/);
|
|
1225
|
+
|
|
1226
|
+
let message: string = "";
|
|
1227
|
+
try {
|
|
1228
|
+
await update({ aiKubectlCommandAllowlist: ["kubectl"] });
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
message = (error as Error).message;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
expect(message).toContain("word by word");
|
|
1234
|
+
// The stale rule from the whole-command matcher is gone for good.
|
|
1235
|
+
expect(message).not.toContain("whole command");
|
|
1236
|
+
expect(message).not.toContain('must start with "kubectl "');
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
it(`refuses more than ${KUBECTL_ALLOWLIST_MAX_PATTERNS} patterns`, async () => {
|
|
1240
|
+
const patterns: Array<string> = [];
|
|
1241
|
+
for (let i: number = 0; i <= KUBECTL_ALLOWLIST_MAX_PATTERNS; i++) {
|
|
1242
|
+
patterns.push(`kubectl scale deployment/web-${i} --replicas=*`);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
await expect(
|
|
1246
|
+
update({ aiKubectlCommandAllowlist: patterns }),
|
|
1247
|
+
).rejects.toThrow(
|
|
1248
|
+
`The kubectl allowlist can hold at most ${KUBECTL_ALLOWLIST_MAX_PATTERNS} patterns (got ${KUBECTL_ALLOWLIST_MAX_PATTERNS + 1}).`,
|
|
1249
|
+
);
|
|
1250
|
+
|
|
1251
|
+
// Negative control: exactly the limit is fine.
|
|
1252
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1253
|
+
aiKubectlCommandAllowlist: patterns.slice(
|
|
1254
|
+
0,
|
|
1255
|
+
KUBECTL_ALLOWLIST_MAX_PATTERNS,
|
|
1256
|
+
),
|
|
1257
|
+
});
|
|
1258
|
+
expect(writtenAllowlist(written)).toHaveLength(
|
|
1259
|
+
KUBECTL_ALLOWLIST_MAX_PATTERNS,
|
|
1260
|
+
);
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
it(`refuses a pattern longer than ${KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH} characters`, async () => {
|
|
1264
|
+
const prefix: string = "kubectl set image deployment/";
|
|
1265
|
+
const tooLong: string =
|
|
1266
|
+
prefix +
|
|
1267
|
+
"a".repeat(KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH + 1 - prefix.length);
|
|
1268
|
+
const atLimit: string = tooLong.slice(0, -1);
|
|
1269
|
+
|
|
1270
|
+
await expect(
|
|
1271
|
+
update({ aiKubectlCommandAllowlist: [tooLong] }),
|
|
1272
|
+
).rejects.toThrow(
|
|
1273
|
+
new RegExp(
|
|
1274
|
+
`Pattern 1 of the kubectl allowlist cannot be used: .*${KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH} characters`,
|
|
1275
|
+
),
|
|
1276
|
+
);
|
|
1277
|
+
|
|
1278
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1279
|
+
aiKubectlCommandAllowlist: [atLimit],
|
|
1280
|
+
});
|
|
1281
|
+
expect(writtenAllowlist(written)).toEqual([atLimit]);
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
/*
|
|
1285
|
+
* Round one refused these for not starting with "kubectl " (the old
|
|
1286
|
+
* whole-command glob). The matcher compares word by word and the
|
|
1287
|
+
* leading "kubectl" is optional in any case, so they are real,
|
|
1288
|
+
* matching patterns.
|
|
1289
|
+
*/
|
|
1290
|
+
it("accepts patterns with or without a leading kubectl, in any case, trimmed", async () => {
|
|
1291
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1292
|
+
aiKubectlCommandAllowlist: [
|
|
1293
|
+
" kubectl set image deployment/web * -n web ",
|
|
1294
|
+
"set image deployment/web * -n web",
|
|
1295
|
+
"Kubectl scale deployment/web --replicas=*",
|
|
1296
|
+
"kubectl\tscale deployment/web --replicas=*",
|
|
1297
|
+
],
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
expect(writtenAllowlist(written)).toEqual([
|
|
1301
|
+
"kubectl set image deployment/web * -n web",
|
|
1302
|
+
"set image deployment/web * -n web",
|
|
1303
|
+
"Kubectl scale deployment/web --replicas=*",
|
|
1304
|
+
"kubectl\tscale deployment/web --replicas=*",
|
|
1305
|
+
]);
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
it("stores a JSON-encoded array (a form the readers already accept) as the array", async () => {
|
|
1309
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1310
|
+
aiKubectlCommandAllowlist:
|
|
1311
|
+
'["kubectl scale deployment/web --replicas=*"]',
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
expect(writtenAllowlist(written)).toEqual([
|
|
1315
|
+
"kubectl scale deployment/web --replicas=*",
|
|
1316
|
+
]);
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
it("stores an empty text field and null as no allowlist, and keeps an empty list", async () => {
|
|
1320
|
+
expect(
|
|
1321
|
+
writtenAllowlist(await update({ aiKubectlCommandAllowlist: "" })),
|
|
1322
|
+
).toBeNull();
|
|
1323
|
+
expect(
|
|
1324
|
+
writtenAllowlist(await update({ aiKubectlCommandAllowlist: " " })),
|
|
1325
|
+
).toBeNull();
|
|
1326
|
+
expect(
|
|
1327
|
+
writtenAllowlist(await update({ aiKubectlCommandAllowlist: null })),
|
|
1328
|
+
).toBeNull();
|
|
1329
|
+
expect(
|
|
1330
|
+
writtenAllowlist(await update({ aiKubectlCommandAllowlist: [] })),
|
|
1331
|
+
).toEqual([]);
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
it("skips validation entirely for an update that writes neither setting", async () => {
|
|
1335
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1336
|
+
name: "renamed",
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
expect(written.data).toEqual({ name: "renamed" });
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
it("validates the create path too", async () => {
|
|
1343
|
+
await expect(
|
|
1344
|
+
hooks().onBeforeCreate(
|
|
1345
|
+
createBy(
|
|
1346
|
+
{
|
|
1347
|
+
projectId: PROJECT_ID,
|
|
1348
|
+
name: "prod-us",
|
|
1349
|
+
aiRemediationMode: "automatic",
|
|
1350
|
+
},
|
|
1351
|
+
rootProps(),
|
|
1352
|
+
),
|
|
1353
|
+
),
|
|
1354
|
+
).rejects.toThrow(/AI remediation mode must be one of/);
|
|
1355
|
+
|
|
1356
|
+
await expect(
|
|
1357
|
+
hooks().onBeforeCreate(
|
|
1358
|
+
createBy(
|
|
1359
|
+
{
|
|
1360
|
+
projectId: PROJECT_ID,
|
|
1361
|
+
name: "prod-us",
|
|
1362
|
+
aiKubectlCommandAllowlist: ["kubectl"],
|
|
1363
|
+
},
|
|
1364
|
+
rootProps(),
|
|
1365
|
+
),
|
|
1366
|
+
),
|
|
1367
|
+
).rejects.toThrow(/cannot be used/);
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
/*
|
|
1371
|
+
* The validator and the matcher must not drift apart: every pattern the
|
|
1372
|
+
* validator accepts must be able to match the command it describes, and
|
|
1373
|
+
* the limits must be exactly where the matcher starts ignoring
|
|
1374
|
+
* patterns (past them it skips silently, which is what the validator
|
|
1375
|
+
* exists to prevent).
|
|
1376
|
+
*/
|
|
1377
|
+
/*
|
|
1378
|
+
* The validator and the matcher must not drift apart: the server keeps
|
|
1379
|
+
* exactly the entries KubectlPolicy says the matcher can use, an
|
|
1380
|
+
* accepted pattern matches the command it describes, and the limits are
|
|
1381
|
+
* exactly where the matcher starts ignoring patterns (past them it skips
|
|
1382
|
+
* silently, which is what the validator exists to prevent). Round one
|
|
1383
|
+
* checked this against CommandPolicy — the Bash lane's glob — which is
|
|
1384
|
+
* not the matcher the cluster allowlist runs through.
|
|
1385
|
+
*/
|
|
1386
|
+
describe("agrees with the kubectl matcher (KubectlPolicy)", () => {
|
|
1387
|
+
function argsOf(command: string): Array<string> {
|
|
1388
|
+
return KubectlPolicy.tokenize(command).args || [];
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const SET_IMAGE_ARGS: Array<string> = argsOf(
|
|
1392
|
+
"kubectl set image deployment/web web=nginx:2 -n web",
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
/*
|
|
1396
|
+
* The one definition: every entry below is accepted by the server
|
|
1397
|
+
* exactly when describeAllowlistPatternProblem finds nothing wrong
|
|
1398
|
+
* with it — whatever that function decides, now or later.
|
|
1399
|
+
*/
|
|
1400
|
+
it.each([
|
|
1401
|
+
[""],
|
|
1402
|
+
["kubectl"],
|
|
1403
|
+
["kubectl get\npods"],
|
|
1404
|
+
['kubectl patch deployment/web -p \'{"spec":'],
|
|
1405
|
+
["set image deployment/web * -n web"],
|
|
1406
|
+
["Kubectl set image deployment/web * -n web"],
|
|
1407
|
+
["kubectl set image deployment/web * -n web"],
|
|
1408
|
+
["kubectl scale deployment/web --replicas=*"],
|
|
1409
|
+
["* deployment/web -n web"],
|
|
1410
|
+
["* * -n web"],
|
|
1411
|
+
["*"],
|
|
1412
|
+
["kubectl *"],
|
|
1413
|
+
["kubectl delete * * -n *"],
|
|
1414
|
+
[`kubectl get ${"x ".repeat(70)}`],
|
|
1415
|
+
])(
|
|
1416
|
+
"refuses %p exactly when KubectlPolicy says it cannot be used",
|
|
1417
|
+
async (pattern: string) => {
|
|
1418
|
+
const problem: string | null =
|
|
1419
|
+
KubectlPolicy.describeAllowlistPatternProblem(pattern.trim());
|
|
1420
|
+
|
|
1421
|
+
if (problem) {
|
|
1422
|
+
await expect(
|
|
1423
|
+
update({ aiKubectlCommandAllowlist: [pattern] }),
|
|
1424
|
+
).rejects.toThrow(BadDataException);
|
|
1425
|
+
} else {
|
|
1426
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1427
|
+
aiKubectlCommandAllowlist: [pattern],
|
|
1428
|
+
});
|
|
1429
|
+
expect(writtenAllowlist(written)).toEqual([pattern.trim()]);
|
|
1430
|
+
|
|
1431
|
+
// Whatever the server keeps, the matcher can read.
|
|
1432
|
+
expect(
|
|
1433
|
+
KubectlPolicy.tokenize(pattern.trim()).args?.length,
|
|
1434
|
+
).toBeGreaterThan(0);
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
);
|
|
1438
|
+
|
|
1439
|
+
it.each([
|
|
1440
|
+
["kubectl set image deployment/web * -n web"],
|
|
1441
|
+
["set image deployment/web * -n web"],
|
|
1442
|
+
[" kubectl set image deployment/web web=* -n web "],
|
|
1443
|
+
])(
|
|
1444
|
+
"an accepted %p matches the command it describes",
|
|
1445
|
+
async (pattern: string) => {
|
|
1446
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1447
|
+
aiKubectlCommandAllowlist: [pattern],
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
expect(
|
|
1451
|
+
KubectlPolicy.matchesAllowlist({
|
|
1452
|
+
args: SET_IMAGE_ARGS,
|
|
1453
|
+
allowlistPatterns: writtenAllowlist(written) as Array<string>,
|
|
1454
|
+
}),
|
|
1455
|
+
).toBe(true);
|
|
1456
|
+
},
|
|
1457
|
+
);
|
|
1458
|
+
|
|
1459
|
+
it("negative control: an accepted pattern for another object does not match", async () => {
|
|
1460
|
+
const written: UpdateBy<KubernetesCluster> = await update({
|
|
1461
|
+
aiKubectlCommandAllowlist: [
|
|
1462
|
+
"kubectl set image deployment/api * -n web",
|
|
1463
|
+
],
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
expect(
|
|
1467
|
+
KubectlPolicy.matchesAllowlist({
|
|
1468
|
+
args: SET_IMAGE_ARGS,
|
|
1469
|
+
allowlistPatterns: writtenAllowlist(written) as Array<string>,
|
|
1470
|
+
}),
|
|
1471
|
+
).toBe(false);
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1474
|
+
it(`the matcher ignores a pattern past the ${KUBECTL_ALLOWLIST_MAX_PATTERNS}th, which is why more are refused`, () => {
|
|
1475
|
+
const filler: Array<string> = [];
|
|
1476
|
+
for (let i: number = 0; i < KUBECTL_ALLOWLIST_MAX_PATTERNS; i++) {
|
|
1477
|
+
filler.push(`kubectl scale deployment/other-${i} --replicas=*`);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const pattern: string = "kubectl set image deployment/web * -n web";
|
|
1481
|
+
|
|
1482
|
+
expect(
|
|
1483
|
+
KubectlPolicy.matchesAllowlist({
|
|
1484
|
+
args: SET_IMAGE_ARGS,
|
|
1485
|
+
allowlistPatterns: [...filler, pattern],
|
|
1486
|
+
}),
|
|
1487
|
+
).toBe(false);
|
|
1488
|
+
expect(
|
|
1489
|
+
KubectlPolicy.matchesAllowlist({
|
|
1490
|
+
args: SET_IMAGE_ARGS,
|
|
1491
|
+
allowlistPatterns: [...filler.slice(1), pattern],
|
|
1492
|
+
}),
|
|
1493
|
+
).toBe(true);
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
it(`the matcher ignores a pattern longer than ${KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH} characters, which is why one is refused`, () => {
|
|
1497
|
+
const padded: (length: number) => string = (length: number): string => {
|
|
1498
|
+
const head: string = "kubectl set image deployment/web ";
|
|
1499
|
+
const tail: string = " -n web";
|
|
1500
|
+
return head + "*".repeat(length - head.length - tail.length) + tail;
|
|
1501
|
+
};
|
|
1502
|
+
|
|
1503
|
+
expect(
|
|
1504
|
+
KubectlPolicy.matchesAllowlist({
|
|
1505
|
+
args: SET_IMAGE_ARGS,
|
|
1506
|
+
allowlistPatterns: [
|
|
1507
|
+
padded(KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH + 1),
|
|
1508
|
+
],
|
|
1509
|
+
}),
|
|
1510
|
+
).toBe(false);
|
|
1511
|
+
expect(
|
|
1512
|
+
KubectlPolicy.matchesAllowlist({
|
|
1513
|
+
args: SET_IMAGE_ARGS,
|
|
1514
|
+
allowlistPatterns: [padded(KUBECTL_ALLOWLIST_MAX_PATTERN_LENGTH)],
|
|
1515
|
+
}),
|
|
1516
|
+
).toBe(true);
|
|
1517
|
+
});
|
|
1518
|
+
});
|
|
1519
|
+
});
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
/*
|
|
1523
|
+
* aiAccessConfiguredAt tells the in-cluster Runner's registration "an
|
|
1524
|
+
* operator has configured AI access here" apart from "nobody ever did", so
|
|
1525
|
+
* a Runner deleted and re-registered never re-applies the chart's defaults
|
|
1526
|
+
* over an operator's "off". This file pins the user-write side: every
|
|
1527
|
+
* operator write of an AI setting sets it once, nothing else does, and it
|
|
1528
|
+
* is never cleared.
|
|
1529
|
+
*/
|
|
1530
|
+
describe("KubernetesClusterService AI access configured marker", () => {
|
|
1531
|
+
let updateBySpy: jest.SpyInstance;
|
|
1532
|
+
|
|
1533
|
+
beforeEach(() => {
|
|
1534
|
+
jest
|
|
1535
|
+
.spyOn(RunnerService, "findOneBy")
|
|
1536
|
+
.mockResolvedValue({ id: RUNNER_ID } as unknown as Runner);
|
|
1537
|
+
jest.spyOn(KubernetesClusterService, "findBy").mockResolvedValue([
|
|
1538
|
+
{
|
|
1539
|
+
id: CLUSTER_ID,
|
|
1540
|
+
projectId: PROJECT_ID,
|
|
1541
|
+
} as unknown as KubernetesCluster,
|
|
1542
|
+
]);
|
|
1543
|
+
updateBySpy = jest
|
|
1544
|
+
.spyOn(KubernetesClusterService, "updateBy")
|
|
1545
|
+
.mockResolvedValue(1);
|
|
1546
|
+
// The feed writers are not what this block is about.
|
|
1547
|
+
jest
|
|
1548
|
+
.spyOn(
|
|
1549
|
+
KubernetesClusterService as unknown as {
|
|
1550
|
+
writeAiAccessSettingsChangedFeed: () => Promise<void>;
|
|
1551
|
+
},
|
|
1552
|
+
"writeAiAccessSettingsChangedFeed",
|
|
1553
|
+
)
|
|
1554
|
+
.mockResolvedValue(undefined);
|
|
1555
|
+
jest
|
|
1556
|
+
.spyOn(
|
|
1557
|
+
KubernetesClusterService as unknown as {
|
|
1558
|
+
writeKubernetesClusterUpdatedFeed: () => Promise<void>;
|
|
1559
|
+
},
|
|
1560
|
+
"writeKubernetesClusterUpdatedFeed",
|
|
1561
|
+
)
|
|
1562
|
+
.mockResolvedValue(undefined);
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
afterEach(() => {
|
|
1566
|
+
jest.restoreAllMocks();
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
async function runUpdate(
|
|
1570
|
+
data: Record<string, unknown>,
|
|
1571
|
+
props: DatabaseCommonInteractionProps,
|
|
1572
|
+
updatedItemIds: Array<ObjectID> = [CLUSTER_ID],
|
|
1573
|
+
): Promise<UpdateBy<KubernetesCluster>> {
|
|
1574
|
+
const update: UpdateBy<KubernetesCluster> = updateBy(data, props);
|
|
1575
|
+
const onUpdate: OnUpdate<KubernetesCluster> =
|
|
1576
|
+
await hooks().onBeforeUpdate(update);
|
|
1577
|
+
await hooks().onUpdateSuccess(onUpdate, updatedItemIds);
|
|
1578
|
+
return update;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
function markerWrites(): Array<{
|
|
1582
|
+
query: Record<string, unknown>;
|
|
1583
|
+
data: Record<string, unknown>;
|
|
1584
|
+
props: DatabaseCommonInteractionProps;
|
|
1585
|
+
}> {
|
|
1586
|
+
return updateBySpy.mock.calls.map((call: Array<unknown>) => {
|
|
1587
|
+
return call[0] as {
|
|
1588
|
+
query: Record<string, unknown>;
|
|
1589
|
+
data: Record<string, unknown>;
|
|
1590
|
+
props: DatabaseCommonInteractionProps;
|
|
1591
|
+
};
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
it("marks the cluster when a user turns AI investigation off", async () => {
|
|
1596
|
+
await runUpdate(
|
|
1597
|
+
{ isAiInvestigationEnabled: false },
|
|
1598
|
+
userProps(PROJECT_ID, [Permission.SettingsMember]),
|
|
1599
|
+
);
|
|
1600
|
+
|
|
1601
|
+
expect(markerWrites()).toHaveLength(1);
|
|
1602
|
+
const write: {
|
|
1603
|
+
query: Record<string, unknown>;
|
|
1604
|
+
data: Record<string, unknown>;
|
|
1605
|
+
props: DatabaseCommonInteractionProps;
|
|
1606
|
+
} = markerWrites()[0]!;
|
|
1607
|
+
|
|
1608
|
+
expect(write.data["aiAccessConfiguredAt"]).toBeInstanceOf(Date);
|
|
1609
|
+
expect(Object.keys(write.data)).toEqual(["aiAccessConfiguredAt"]);
|
|
1610
|
+
// Written by the server, as root — the column's update ACL is empty.
|
|
1611
|
+
expect(write.props.isRoot).toBe(true);
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
it("only sets the marker where it is not set yet, so the first-configured time is kept", async () => {
|
|
1615
|
+
await runUpdate(
|
|
1616
|
+
{ aiRemediationMode: KubernetesAiRemediationMode.Disabled },
|
|
1617
|
+
adminProps(),
|
|
1618
|
+
);
|
|
1619
|
+
|
|
1620
|
+
const query: Record<string, unknown> = markerWrites()[0]!.query;
|
|
1621
|
+
|
|
1622
|
+
expect(Object.keys(query).sort()).toEqual(
|
|
1623
|
+
["_id", "aiAccessConfiguredAt"].sort(),
|
|
1624
|
+
);
|
|
1625
|
+
// A FindOperator, never a value: the write is conditional on IS NULL.
|
|
1626
|
+
expect(query["aiAccessConfiguredAt"]).not.toBeInstanceOf(Date);
|
|
1627
|
+
expect(query["aiAccessConfiguredAt"]).toBeTruthy();
|
|
1628
|
+
expect(JSON.stringify(query["_id"])).toContain(CLUSTER_ID.toString());
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
it("does not slip the server-only column into the user's own write", async () => {
|
|
1632
|
+
const update: UpdateBy<KubernetesCluster> = await runUpdate(
|
|
1633
|
+
{ aiRemediationMode: KubernetesAiRemediationMode.RequireApproval },
|
|
1634
|
+
adminProps(),
|
|
1635
|
+
);
|
|
1636
|
+
|
|
1637
|
+
expect(
|
|
1638
|
+
(update.data as unknown as Record<string, unknown>)[
|
|
1639
|
+
"aiAccessConfiguredAt"
|
|
1640
|
+
],
|
|
1641
|
+
).toBeUndefined();
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
it.each([
|
|
1645
|
+
[{ aiAccessRunnerId: null }],
|
|
1646
|
+
[{ aiAccessCredential: null }],
|
|
1647
|
+
[{ aiKubectlCommandAllowlist: [] }],
|
|
1648
|
+
[{ aiRemediationMode: KubernetesAiRemediationMode.Disabled }],
|
|
1649
|
+
[{ isAiInvestigationEnabled: true }],
|
|
1650
|
+
])(
|
|
1651
|
+
"marks the cluster for a user write of %p",
|
|
1652
|
+
async (data: Record<string, unknown>) => {
|
|
1653
|
+
await runUpdate(
|
|
1654
|
+
data,
|
|
1655
|
+
userProps(PROJECT_ID, [Permission.EditKubernetesCluster]),
|
|
1656
|
+
);
|
|
1657
|
+
|
|
1658
|
+
expect(markerWrites()).toHaveLength(1);
|
|
1659
|
+
},
|
|
1660
|
+
);
|
|
1661
|
+
|
|
1662
|
+
it("does not mark the cluster for a user write of an unrelated column", async () => {
|
|
1663
|
+
await runUpdate({ name: "x" }, adminProps());
|
|
1664
|
+
|
|
1665
|
+
expect(markerWrites()).toHaveLength(0);
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
it("does not mark the cluster for a root heartbeat or outcome write", async () => {
|
|
1669
|
+
await runUpdate(
|
|
1670
|
+
{ lastSeenAt: new Date(), otelCollectorStatus: "connected" },
|
|
1671
|
+
rootProps(),
|
|
1672
|
+
);
|
|
1673
|
+
await runUpdate({ aiAccessLastVerifiedAt: new Date() }, rootProps());
|
|
1674
|
+
|
|
1675
|
+
expect(markerWrites()).toHaveLength(0);
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
/*
|
|
1679
|
+
* Registration writes as root and sets the marker on first bind itself;
|
|
1680
|
+
* the operator path must not double up on it.
|
|
1681
|
+
*/
|
|
1682
|
+
it("does not mark the cluster for a root write of AI settings (registration marks its own)", async () => {
|
|
1683
|
+
await runUpdate(
|
|
1684
|
+
{
|
|
1685
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
1686
|
+
isAiInvestigationEnabled: true,
|
|
1687
|
+
aiRemediationMode: KubernetesAiRemediationMode.RequireApproval,
|
|
1688
|
+
},
|
|
1689
|
+
rootProps(),
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1692
|
+
expect(markerWrites()).toHaveLength(0);
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
it("marks nothing when the update matched no row", async () => {
|
|
1696
|
+
await runUpdate({ isAiInvestigationEnabled: false }, adminProps(), []);
|
|
1697
|
+
|
|
1698
|
+
expect(markerWrites()).toHaveLength(0);
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
it("marks nothing when the write is refused", async () => {
|
|
1702
|
+
await expect(
|
|
1703
|
+
runUpdate(
|
|
1704
|
+
{ aiRemediationMode: KubernetesAiRemediationMode.BypassApproval },
|
|
1705
|
+
userProps(PROJECT_ID, [Permission.SettingsMember]),
|
|
1706
|
+
),
|
|
1707
|
+
).rejects.toThrow(NotAuthorizedException);
|
|
1708
|
+
|
|
1709
|
+
expect(markerWrites()).toHaveLength(0);
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
it("never writes the marker as a clear", async () => {
|
|
1713
|
+
await runUpdate({ isAiInvestigationEnabled: false }, adminProps());
|
|
1714
|
+
|
|
1715
|
+
for (const write of markerWrites()) {
|
|
1716
|
+
expect(write.data["aiAccessConfiguredAt"]).not.toBeNull();
|
|
1717
|
+
}
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
/*
|
|
1721
|
+
* Known follow-up 12: registration binds a cluster an operator configured
|
|
1722
|
+
* before installing the chart, and leaves one alone that had a Runner
|
|
1723
|
+
* bound before. "A Runner was bound here" is its own never-cleared
|
|
1724
|
+
* marker, stamped on every bind — here, an operator's.
|
|
1725
|
+
*/
|
|
1726
|
+
describe("the Runner-bound marker", () => {
|
|
1727
|
+
function boundMarkerWrites(): Array<{
|
|
1728
|
+
query: Record<string, unknown>;
|
|
1729
|
+
data: Record<string, unknown>;
|
|
1730
|
+
props: DatabaseCommonInteractionProps;
|
|
1731
|
+
}> {
|
|
1732
|
+
return markerWrites().filter(
|
|
1733
|
+
(write: { data: Record<string, unknown> }) => {
|
|
1734
|
+
return "aiAccessRunnerBoundAt" in write.data;
|
|
1735
|
+
},
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
it.each([
|
|
1740
|
+
[{ aiAccessRunnerId: RUNNER_ID }],
|
|
1741
|
+
[{ aiAccessRunner: { _id: RUNNER_ID.toString() } }],
|
|
1742
|
+
])(
|
|
1743
|
+
"is stamped, where it is not set yet, when an operator binds a Runner: %p",
|
|
1744
|
+
async (data: Record<string, unknown>) => {
|
|
1745
|
+
await runUpdate(data, adminProps());
|
|
1746
|
+
|
|
1747
|
+
const writes: Array<{
|
|
1748
|
+
query: Record<string, unknown>;
|
|
1749
|
+
data: Record<string, unknown>;
|
|
1750
|
+
props: DatabaseCommonInteractionProps;
|
|
1751
|
+
}> = boundMarkerWrites();
|
|
1752
|
+
expect(writes).toHaveLength(1);
|
|
1753
|
+
expect(writes[0]!.data["aiAccessRunnerBoundAt"]).toBeInstanceOf(Date);
|
|
1754
|
+
expect(Object.keys(writes[0]!.data)).toEqual(["aiAccessRunnerBoundAt"]);
|
|
1755
|
+
expect(Object.keys(writes[0]!.query).sort()).toEqual(
|
|
1756
|
+
["_id", "aiAccessRunnerBoundAt"].sort(),
|
|
1757
|
+
);
|
|
1758
|
+
// Conditional on IS NULL: the first bind's time is kept.
|
|
1759
|
+
expect(writes[0]!.query["aiAccessRunnerBoundAt"]).not.toBeInstanceOf(
|
|
1760
|
+
Date,
|
|
1761
|
+
);
|
|
1762
|
+
expect(writes[0]!.props.isRoot).toBe(true);
|
|
1763
|
+
},
|
|
1764
|
+
);
|
|
1765
|
+
|
|
1766
|
+
it("negative control: clearing the Runner, or any other AI setting, does not stamp it", async () => {
|
|
1767
|
+
jest.spyOn(RunbookCredentialService, "findOneBy").mockResolvedValue({
|
|
1768
|
+
id: CREDENTIAL_ID,
|
|
1769
|
+
credentialType: RunbookCredentialType.Kubernetes,
|
|
1770
|
+
runners: [],
|
|
1771
|
+
} as unknown as RunbookCredential);
|
|
1772
|
+
|
|
1773
|
+
await runUpdate({ aiAccessRunnerId: null }, adminProps());
|
|
1774
|
+
await runUpdate(
|
|
1775
|
+
{ aiRemediationMode: KubernetesAiRemediationMode.Automatic },
|
|
1776
|
+
adminProps(),
|
|
1777
|
+
);
|
|
1778
|
+
await runUpdate({ aiAccessCredentialId: CREDENTIAL_ID }, adminProps());
|
|
1779
|
+
|
|
1780
|
+
expect(boundMarkerWrites()).toHaveLength(0);
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
it("negative control: a root write (registration stamps its own) does not stamp it", async () => {
|
|
1784
|
+
await runUpdate({ aiAccessRunnerId: RUNNER_ID }, rootProps());
|
|
1785
|
+
|
|
1786
|
+
expect(boundMarkerWrites()).toHaveLength(0);
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
it("is stamped on an operator's create that binds a Runner", async () => {
|
|
1790
|
+
const create: CreateBy<KubernetesCluster> = createBy(
|
|
1791
|
+
{
|
|
1792
|
+
projectId: PROJECT_ID,
|
|
1793
|
+
name: "prod-us",
|
|
1794
|
+
aiAccessRunnerId: RUNNER_ID,
|
|
1795
|
+
},
|
|
1796
|
+
{ isMasterAdmin: true, userId: ObjectID.generate() },
|
|
1797
|
+
);
|
|
1798
|
+
const onCreate: OnCreate<KubernetesCluster> =
|
|
1799
|
+
await hooks().onBeforeCreate(create);
|
|
1800
|
+
|
|
1801
|
+
jest
|
|
1802
|
+
.spyOn(
|
|
1803
|
+
KubernetesClusterService as unknown as {
|
|
1804
|
+
writeKubernetesClusterCreatedFeed: () => Promise<void>;
|
|
1805
|
+
},
|
|
1806
|
+
"writeKubernetesClusterCreatedFeed",
|
|
1807
|
+
)
|
|
1808
|
+
.mockResolvedValue(undefined);
|
|
1809
|
+
|
|
1810
|
+
await hooks().onCreateSuccess(onCreate, {
|
|
1811
|
+
id: CLUSTER_ID,
|
|
1812
|
+
} as unknown as KubernetesCluster);
|
|
1813
|
+
|
|
1814
|
+
expect(boundMarkerWrites()).toHaveLength(1);
|
|
1815
|
+
});
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
describe("on create", () => {
|
|
1819
|
+
it("marks a cluster a master admin creates with AI settings", async () => {
|
|
1820
|
+
const create: CreateBy<KubernetesCluster> = createBy(
|
|
1821
|
+
{
|
|
1822
|
+
projectId: PROJECT_ID,
|
|
1823
|
+
name: "prod-us",
|
|
1824
|
+
isAiInvestigationEnabled: true,
|
|
1825
|
+
},
|
|
1826
|
+
{ isMasterAdmin: true, userId: ObjectID.generate() },
|
|
1827
|
+
);
|
|
1828
|
+
const onCreate: OnCreate<KubernetesCluster> =
|
|
1829
|
+
await hooks().onBeforeCreate(create);
|
|
1830
|
+
|
|
1831
|
+
jest
|
|
1832
|
+
.spyOn(
|
|
1833
|
+
KubernetesClusterService as unknown as {
|
|
1834
|
+
writeKubernetesClusterCreatedFeed: () => Promise<void>;
|
|
1835
|
+
},
|
|
1836
|
+
"writeKubernetesClusterCreatedFeed",
|
|
1837
|
+
)
|
|
1838
|
+
.mockResolvedValue(undefined);
|
|
1839
|
+
|
|
1840
|
+
await hooks().onCreateSuccess(onCreate, {
|
|
1841
|
+
id: CLUSTER_ID,
|
|
1842
|
+
// The saved row carries column defaults; they must not count.
|
|
1843
|
+
isAiInvestigationEnabled: true,
|
|
1844
|
+
aiRemediationMode: KubernetesAiRemediationMode.Disabled,
|
|
1845
|
+
} as unknown as KubernetesCluster);
|
|
1846
|
+
|
|
1847
|
+
expect(markerWrites()).toHaveLength(1);
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
it("does not mark a cluster created without AI settings, even though the saved row carries their defaults", async () => {
|
|
1851
|
+
const create: CreateBy<KubernetesCluster> = createBy({
|
|
1852
|
+
projectId: PROJECT_ID,
|
|
1853
|
+
name: "prod-us",
|
|
1854
|
+
});
|
|
1855
|
+
const onCreate: OnCreate<KubernetesCluster> =
|
|
1856
|
+
await hooks().onBeforeCreate(create);
|
|
1857
|
+
|
|
1858
|
+
jest
|
|
1859
|
+
.spyOn(
|
|
1860
|
+
KubernetesClusterService as unknown as {
|
|
1861
|
+
writeKubernetesClusterCreatedFeed: () => Promise<void>;
|
|
1862
|
+
},
|
|
1863
|
+
"writeKubernetesClusterCreatedFeed",
|
|
1864
|
+
)
|
|
1865
|
+
.mockResolvedValue(undefined);
|
|
1866
|
+
|
|
1867
|
+
await hooks().onCreateSuccess(onCreate, {
|
|
1868
|
+
id: CLUSTER_ID,
|
|
1869
|
+
isAiInvestigationEnabled: false,
|
|
1870
|
+
aiRemediationMode: KubernetesAiRemediationMode.Disabled,
|
|
1871
|
+
} as unknown as KubernetesCluster);
|
|
1872
|
+
|
|
1873
|
+
expect(markerWrites()).toHaveLength(0);
|
|
1874
|
+
});
|
|
1875
|
+
});
|
|
1876
|
+
});
|