@oneuptime/common 14.0.9 → 14.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1146) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +127 -1
  2. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  3. package/Models/DatabaseModels/CephCluster.ts +225 -0
  4. package/Models/DatabaseModels/CloudResource.ts +12 -0
  5. package/Models/DatabaseModels/DatabaseServer.ts +225 -0
  6. package/Models/DatabaseModels/DockerHost.ts +225 -0
  7. package/Models/DatabaseModels/DockerSwarmCluster.ts +225 -0
  8. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  9. package/Models/DatabaseModels/GlobalOidc.ts +0 -4
  10. package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
  11. package/Models/DatabaseModels/GlobalSso.ts +0 -4
  12. package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
  13. package/Models/DatabaseModels/Host.ts +225 -0
  14. package/Models/DatabaseModels/IncidentForm.ts +594 -0
  15. package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
  16. package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
  17. package/Models/DatabaseModels/Index.ts +16 -0
  18. package/Models/DatabaseModels/IoTFleet.ts +12 -0
  19. package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
  20. package/Models/DatabaseModels/MessageQueue.ts +1102 -0
  21. package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
  22. package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
  23. package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
  24. package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
  25. package/Models/DatabaseModels/PodmanHost.ts +225 -0
  26. package/Models/DatabaseModels/Project.ts +59 -0
  27. package/Models/DatabaseModels/ProjectOidc.ts +0 -4
  28. package/Models/DatabaseModels/ProjectSso.ts +0 -4
  29. package/Models/DatabaseModels/ProxmoxCluster.ts +225 -0
  30. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  31. package/Models/DatabaseModels/RumApplication.ts +12 -0
  32. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  33. package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
  34. package/Models/DatabaseModels/Service.ts +12 -0
  35. package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
  36. package/Models/DatabaseModels/StatusPageSso.ts +0 -4
  37. package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
  38. package/Models/DatabaseModels/VMwareVCenter.ts +225 -0
  39. package/Server/API/AIChatAPI.ts +13 -114
  40. package/Server/API/AIInvestigationAPI.ts +3 -3
  41. package/Server/API/AIInvestigationConversationAPI.ts +266 -0
  42. package/Server/API/AutoRemediationAPI.ts +392 -1
  43. package/Server/API/IncidentFormAPI.ts +376 -0
  44. package/Server/API/ProjectOIDC.ts +16 -25
  45. package/Server/API/ProjectSSO.ts +16 -24
  46. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  47. package/Server/API/StatusPageAPI.ts +42 -77
  48. package/Server/Enterprise/EnterpriseEdition.ts +93 -28
  49. package/Server/Enterprise/EnterpriseFeature.ts +25 -3
  50. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
  51. package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
  52. package/Server/EnvironmentConfig.ts +1 -1
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  54. package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
  55. package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
  56. package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
  57. package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
  58. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  59. package/Server/Infrastructure/Semaphore.ts +22 -0
  60. package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
  61. package/Server/Middleware/TelemetryIngest.ts +15 -5
  62. package/Server/Middleware/UserAuthorization.ts +16 -38
  63. package/Server/Services/AIService.ts +10 -13
  64. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  65. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  66. package/Server/Services/CephClusterService.ts +109 -1
  67. package/Server/Services/DatabaseServerService.ts +116 -1
  68. package/Server/Services/DockerHostService.ts +109 -1
  69. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  70. package/Server/Services/HostService.ts +126 -4
  71. package/Server/Services/IncidentCustomFieldService.ts +112 -2
  72. package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
  73. package/Server/Services/IncidentEpisodeService.ts +10 -1
  74. package/Server/Services/IncidentFormService.ts +1566 -0
  75. package/Server/Services/IncidentFormSubmissionService.ts +97 -0
  76. package/Server/Services/IncidentGroupingEngineService.ts +45 -8
  77. package/Server/Services/IncidentService.ts +123 -5
  78. package/Server/Services/IncidentTemplateService.ts +22 -0
  79. package/Server/Services/Index.ts +14 -0
  80. package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
  81. package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
  82. package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
  83. package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
  84. package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
  85. package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
  86. package/Server/Services/MessageQueueService.ts +1463 -0
  87. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  88. package/Server/Services/OpenTelemetryIngestService.ts +37 -4
  89. package/Server/Services/PodmanHostService.ts +109 -1
  90. package/Server/Services/ProjectService.ts +0 -59
  91. package/Server/Services/ProxmoxClusterService.ts +110 -1
  92. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  93. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  94. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  95. package/Server/Services/RunnerJobService.ts +611 -4
  96. package/Server/Services/StatusPagePrivateUserService.ts +1 -4
  97. package/Server/Services/StatusPageService.ts +1 -51
  98. package/Server/Services/TeamComplianceSettingService.ts +163 -42
  99. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  100. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  101. package/Server/Services/UserNotificationRuleService.ts +8 -1
  102. package/Server/Services/VMwareVCenterService.ts +110 -1
  103. package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
  104. package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
  105. package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
  106. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
  107. package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
  108. package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
  109. package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
  110. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
  111. package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
  112. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
  113. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  114. package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
  115. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
  116. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
  117. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
  118. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
  119. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1442 -34
  120. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +968 -34
  121. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  122. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +622 -0
  123. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  124. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  125. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  126. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  127. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +882 -0
  128. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +125 -12
  129. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +99 -15
  130. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +99 -15
  131. package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
  132. package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
  133. package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
  134. package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
  135. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  136. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  137. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  138. package/Server/Utils/EditionEnforcement.ts +19 -202
  139. package/Server/Utils/LLM/LLMService.ts +259 -63
  140. package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
  141. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  142. package/Server/Utils/SameOriginRequest.ts +203 -0
  143. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  144. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  145. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  146. package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
  147. package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
  148. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
  149. package/Server/Utils/VendorAssets.ts +3 -3
  150. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  151. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  152. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  153. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +301 -225
  154. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +38 -16
  155. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  156. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +376 -144
  157. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  158. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  159. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  160. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  161. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  162. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  163. package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
  164. package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
  165. package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
  166. package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
  167. package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
  168. package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
  169. package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
  170. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
  171. package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
  172. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
  173. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
  174. package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
  175. package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
  176. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
  177. package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
  178. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +267 -12
  179. package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
  180. package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
  181. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
  182. package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
  183. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +23 -6
  184. package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
  185. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
  186. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
  187. package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
  188. package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
  189. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
  190. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
  191. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
  192. package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
  193. package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
  194. package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
  195. package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
  196. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
  197. package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
  198. package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
  199. package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
  200. package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
  201. package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
  202. package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
  203. package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
  204. package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
  205. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
  206. package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
  207. package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
  208. package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
  209. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
  210. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  211. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
  212. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
  213. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
  214. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
  215. package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
  216. package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
  217. package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
  218. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
  219. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
  220. package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
  221. package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
  222. package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
  223. package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
  224. package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
  225. package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
  226. package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
  227. package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
  228. package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
  229. package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
  230. package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
  231. package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
  232. package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
  233. package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
  234. package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
  235. package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
  236. package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
  237. package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
  238. package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
  239. package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
  240. package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
  241. package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
  242. package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
  243. package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
  244. package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
  245. package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
  246. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
  247. package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
  248. package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
  249. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
  250. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
  251. package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
  252. package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
  253. package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
  254. package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
  255. package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
  256. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  257. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  258. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  259. package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
  260. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  261. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +2010 -0
  262. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1455 -0
  263. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  264. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  265. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
  266. package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
  267. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
  268. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
  269. package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
  270. package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
  271. package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
  272. package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
  273. package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
  274. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  275. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
  276. package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
  277. package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
  278. package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
  279. package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
  280. package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
  281. package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
  282. package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
  283. package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
  284. package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
  285. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
  286. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +61 -10
  287. package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
  288. package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
  289. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  290. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  291. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  292. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
  293. package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
  294. package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
  295. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  296. package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
  297. package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
  298. package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
  299. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
  300. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  301. package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
  302. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
  303. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
  304. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
  305. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
  306. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
  307. package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
  308. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  309. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  310. package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
  311. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  312. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
  313. package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
  314. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  315. package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
  316. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  317. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  318. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  319. package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
  320. package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
  321. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
  322. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  323. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  324. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  325. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  326. package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
  327. package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
  328. package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
  329. package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
  330. package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
  331. package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
  332. package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
  333. package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
  334. package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
  335. package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
  336. package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
  337. package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
  338. package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
  339. package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
  340. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
  341. package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
  342. package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
  343. package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
  344. package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
  345. package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
  346. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  347. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
  348. package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
  349. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
  350. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
  351. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
  352. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  353. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  354. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  355. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  356. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  357. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  358. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  359. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  360. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  361. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  362. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
  363. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
  364. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  365. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  366. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  367. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  368. package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
  369. package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
  370. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  371. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
  372. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
  373. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
  374. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  375. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  376. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
  377. package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
  378. package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
  379. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
  380. package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
  381. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
  382. package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
  383. package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
  384. package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
  385. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
  386. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
  387. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
  388. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
  389. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +694 -0
  390. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  391. package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
  392. package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
  393. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
  394. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
  395. package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
  396. package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
  397. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  398. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  399. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
  400. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1251 -0
  401. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  402. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  403. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +1004 -0
  404. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  405. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  406. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  407. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +834 -0
  408. package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
  409. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
  410. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
  411. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
  412. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  413. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  414. package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
  415. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  416. package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
  417. package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
  418. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  419. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  420. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
  421. package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
  422. package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
  423. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
  424. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  425. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  426. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  427. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  428. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  429. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  430. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  431. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  432. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  433. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  434. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  435. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  436. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  437. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  438. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  439. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  440. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  441. package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
  442. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  443. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  444. package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
  445. package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
  446. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
  447. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
  448. package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
  449. package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
  450. package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
  451. package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
  452. package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
  453. package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
  454. package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
  455. package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
  456. package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
  457. package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
  458. package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
  459. package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
  460. package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
  461. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  462. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  463. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  464. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  465. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +519 -0
  466. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  467. package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
  468. package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
  469. package/Tests/Types/XML.test.ts +51 -0
  470. package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
  471. package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
  472. package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
  473. package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
  474. package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
  475. package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
  476. package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
  477. package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
  478. package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
  479. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
  480. package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
  481. package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
  482. package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
  483. package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
  484. package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
  485. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
  486. package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
  487. package/Tests/UI/Components/Icon.test.tsx +32 -0
  488. package/Tests/UI/Components/Input.test.tsx +31 -0
  489. package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
  490. package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
  491. package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
  492. package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
  493. package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
  494. package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
  495. package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
  496. package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
  497. package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
  498. package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
  499. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  500. package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
  501. package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
  502. package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
  503. package/Tests/UI/Components/TextArea.test.tsx +19 -0
  504. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
  505. package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
  506. package/Tests/UI/EsbuildConfig.test.ts +115 -39
  507. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
  508. package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
  509. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  510. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
  511. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
  512. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  513. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  514. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  515. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  516. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  517. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  518. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  519. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  520. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  521. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  522. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  523. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  524. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  525. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  526. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  527. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  528. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  529. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  530. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  531. package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
  532. package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
  533. package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
  534. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  535. package/Types/AI/AIAgentRunLimits.ts +146 -0
  536. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  537. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  538. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  539. package/Types/Code/CodeType.ts +2 -0
  540. package/Types/Code/JsonSyntax.ts +923 -0
  541. package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
  542. package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
  543. package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
  544. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
  545. package/Types/Icon/IconProp.ts +2 -0
  546. package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
  547. package/Types/Incident/IncidentFormPublic.ts +1145 -0
  548. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
  549. package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
  550. package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
  551. package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
  552. package/Types/MessageQueue/MessagingSystem.ts +518 -0
  553. package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
  554. package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
  555. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  556. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  557. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  558. package/Types/Permission.ts +310 -0
  559. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  560. package/Types/ResourceAiAgent/ResourceAiAccess.ts +580 -0
  561. package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
  562. package/Types/Rules/RuleRun.ts +4 -0
  563. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  564. package/Types/Runbook/RunbookStepType.ts +29 -0
  565. package/Types/Team/ComplianceNotificationChannel.ts +4 -3
  566. package/Types/Team/ComplianceRule.ts +71 -18
  567. package/Types/Team/ComplianceRuleType.ts +2 -2
  568. package/Types/Team/TeamComplianceStatus.ts +6 -2
  569. package/Types/Telemetry/EntityType.ts +16 -0
  570. package/Types/Telemetry/LockedFilterDetail.ts +22 -0
  571. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  572. package/Types/XML.ts +26 -0
  573. package/UI/Components/CodeEditor/CodeEditor.css +379 -0
  574. package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
  575. package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
  576. package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
  577. package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
  578. package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
  579. package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
  580. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
  581. package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
  582. package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
  583. package/UI/Components/Forms/Fields/FormField.tsx +8 -0
  584. package/UI/Components/Forms/Types/Field.ts +8 -0
  585. package/UI/Components/Icon/Icon.tsx +18 -0
  586. package/UI/Components/Input/Input.tsx +11 -4
  587. package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
  588. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
  589. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  590. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
  591. package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
  592. package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
  593. package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
  594. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
  595. package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
  596. package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
  597. package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
  598. package/UI/Components/Table/EmptyTableMessage.ts +81 -0
  599. package/UI/Components/Table/Table.tsx +50 -21
  600. package/UI/Components/Table/TableRow.tsx +9 -1
  601. package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
  602. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
  603. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
  604. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
  605. package/UI/Components/TextArea/TextArea.tsx +9 -2
  606. package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
  607. package/UI/Styles/Theme.css +8 -0
  608. package/UI/esbuild-config.js +2 -32
  609. package/Utils/AI/InvestigationReport.ts +121 -15
  610. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  611. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  612. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  613. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  614. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  615. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  616. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  617. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  618. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  619. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  620. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  621. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  622. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  623. package/Utils/LabelRuleImportExport.ts +2 -0
  624. package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
  625. package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
  626. package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
  627. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  628. package/Utils/Telemetry/EntityKey.ts +48 -0
  629. package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
  630. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  631. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  632. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  633. package/build/dist/Models/DatabaseModels/CephCluster.js +231 -0
  634. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  635. package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
  636. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  637. package/build/dist/Models/DatabaseModels/DatabaseServer.js +231 -0
  638. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  639. package/build/dist/Models/DatabaseModels/DockerHost.js +231 -0
  640. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  641. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +231 -0
  642. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  643. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  644. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  645. package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
  646. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  647. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
  648. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  649. package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
  650. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  651. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
  652. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  653. package/build/dist/Models/DatabaseModels/Host.js +231 -0
  654. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  655. package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
  656. package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
  657. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
  658. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
  659. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
  660. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  661. package/build/dist/Models/DatabaseModels/Index.js +16 -0
  662. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  663. package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
  664. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  665. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
  666. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  667. package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
  668. package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
  669. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
  670. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
  671. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
  672. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
  673. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
  674. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
  675. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
  676. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
  677. package/build/dist/Models/DatabaseModels/PodmanHost.js +231 -0
  678. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  679. package/build/dist/Models/DatabaseModels/Project.js +61 -0
  680. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  681. package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
  682. package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
  683. package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
  684. package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
  685. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +231 -0
  686. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  687. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  688. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  689. package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
  690. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  691. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  692. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  693. package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
  694. package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
  695. package/build/dist/Models/DatabaseModels/Service.js +12 -0
  696. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  697. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
  698. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  699. package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
  700. package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
  701. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
  702. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  703. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +231 -0
  704. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  705. package/build/dist/Server/API/AIChatAPI.js +9 -103
  706. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  707. package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
  708. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  709. package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
  710. package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
  711. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  712. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  713. package/build/dist/Server/API/IncidentFormAPI.js +245 -0
  714. package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
  715. package/build/dist/Server/API/ProjectOIDC.js +16 -24
  716. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  717. package/build/dist/Server/API/ProjectSSO.js +16 -24
  718. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  719. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  720. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  721. package/build/dist/Server/API/StatusPageAPI.js +42 -75
  722. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  723. package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
  724. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
  725. package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
  726. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
  727. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
  728. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
  729. package/build/dist/Server/EnvironmentConfig.js +1 -1
  730. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  731. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  732. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
  733. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
  734. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
  735. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
  736. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
  737. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
  738. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
  739. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js.map +1 -0
  740. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  741. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  742. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  743. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  744. package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
  745. package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
  746. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  747. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  748. package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
  749. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  750. package/build/dist/Server/Services/AIService.js +10 -13
  751. package/build/dist/Server/Services/AIService.js.map +1 -1
  752. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  753. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  754. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  755. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  756. package/build/dist/Server/Services/CephClusterService.js +104 -0
  757. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  758. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  759. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  760. package/build/dist/Server/Services/DockerHostService.js +104 -0
  761. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  762. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  763. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  764. package/build/dist/Server/Services/HostService.js +114 -2
  765. package/build/dist/Server/Services/HostService.js.map +1 -1
  766. package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
  767. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  768. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
  769. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  770. package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
  771. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  772. package/build/dist/Server/Services/IncidentFormService.js +1202 -0
  773. package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
  774. package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
  775. package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
  776. package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
  777. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  778. package/build/dist/Server/Services/IncidentService.js +106 -4
  779. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  780. package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
  781. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  782. package/build/dist/Server/Services/Index.js +14 -0
  783. package/build/dist/Server/Services/Index.js.map +1 -1
  784. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
  785. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
  786. package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
  787. package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
  788. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
  789. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
  790. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
  791. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
  792. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
  793. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
  794. package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
  795. package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
  796. package/build/dist/Server/Services/MessageQueueService.js +1140 -0
  797. package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
  798. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  799. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  800. package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
  801. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  802. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  803. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  804. package/build/dist/Server/Services/ProjectService.js +0 -54
  805. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  806. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  807. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  808. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  809. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  810. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  811. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  812. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  813. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  814. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  815. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  816. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
  817. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  818. package/build/dist/Server/Services/StatusPageService.js +0 -46
  819. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  820. package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
  821. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  822. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  823. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  824. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  825. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  826. package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
  827. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  828. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  829. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  830. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
  831. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  832. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
  833. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  834. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
  835. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  836. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
  837. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  838. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
  839. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
  840. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  841. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
  842. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
  843. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
  844. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  845. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
  846. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
  847. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
  848. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  849. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  850. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
  851. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
  852. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
  853. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  854. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
  855. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  856. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
  857. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  858. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
  859. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  860. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1036 -32
  861. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  862. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +661 -41
  863. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  864. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  865. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  866. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +354 -0
  867. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  868. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  869. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  870. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  871. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  872. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  873. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  874. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  875. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  876. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +602 -0
  877. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  878. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +90 -14
  879. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  880. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +73 -10
  881. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  882. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +73 -10
  883. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  884. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
  885. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  886. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
  887. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
  888. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
  889. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
  890. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
  891. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  892. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  893. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  894. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  895. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  896. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  897. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  898. package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
  899. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
  900. package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
  901. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  902. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
  903. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
  904. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  905. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  906. package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
  907. package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
  908. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  909. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  910. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  911. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  912. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  913. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  914. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
  915. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  916. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
  917. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
  918. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
  919. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  920. package/build/dist/Server/Utils/VendorAssets.js +3 -3
  921. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  922. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  923. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  924. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  925. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  926. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  927. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +186 -210
  928. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  929. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +22 -16
  930. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  931. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  932. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  933. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +273 -111
  934. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  935. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  936. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  937. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  938. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  939. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  940. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  941. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  942. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  943. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  944. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  945. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  946. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  947. package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
  948. package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
  949. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  950. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  951. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  952. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  953. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  954. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  955. package/build/dist/Types/Code/CodeType.js +2 -0
  956. package/build/dist/Types/Code/CodeType.js.map +1 -1
  957. package/build/dist/Types/Code/JsonSyntax.js +604 -0
  958. package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
  959. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
  960. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
  961. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
  962. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
  963. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
  964. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
  965. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
  966. package/build/dist/Types/Icon/IconProp.js +2 -0
  967. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  968. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
  969. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
  970. package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
  971. package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
  972. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
  973. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  974. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
  975. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
  976. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
  977. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
  978. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
  979. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
  980. package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
  981. package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
  982. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
  983. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
  984. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
  985. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
  986. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  987. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  988. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  989. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  990. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  991. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  992. package/build/dist/Types/Permission.js +274 -0
  993. package/build/dist/Types/Permission.js.map +1 -1
  994. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  995. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  996. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +281 -0
  997. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  998. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
  999. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  1000. package/build/dist/Types/Rules/RuleRun.js +4 -0
  1001. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  1002. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  1003. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  1004. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  1005. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  1006. package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
  1007. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
  1008. package/build/dist/Types/Team/ComplianceRule.js +48 -14
  1009. package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
  1010. package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
  1011. package/build/dist/Types/Telemetry/EntityType.js +16 -0
  1012. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  1013. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  1014. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  1015. package/build/dist/Types/XML.js +23 -0
  1016. package/build/dist/Types/XML.js.map +1 -1
  1017. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
  1018. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  1019. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
  1020. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
  1021. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
  1022. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
  1023. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
  1024. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
  1025. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
  1026. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
  1027. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
  1028. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
  1029. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
  1030. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
  1031. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
  1032. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  1033. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
  1034. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
  1035. package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
  1036. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  1037. package/build/dist/UI/Components/Icon/Icon.js +8 -0
  1038. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  1039. package/build/dist/UI/Components/Input/Input.js +11 -3
  1040. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  1041. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
  1042. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1043. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
  1044. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  1045. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  1046. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  1047. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
  1048. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  1049. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
  1050. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
  1051. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
  1052. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
  1053. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
  1054. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
  1055. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
  1056. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  1057. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
  1058. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
  1059. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
  1060. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  1061. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
  1062. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
  1063. package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
  1064. package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
  1065. package/build/dist/UI/Components/Table/Table.js +30 -11
  1066. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  1067. package/build/dist/UI/Components/Table/TableRow.js +9 -2
  1068. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1069. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
  1070. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
  1071. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
  1072. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1073. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
  1074. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
  1075. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
  1076. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  1077. package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
  1078. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  1079. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
  1080. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
  1081. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  1082. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  1083. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  1084. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  1085. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  1086. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  1087. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  1088. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  1089. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  1090. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  1091. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  1092. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  1093. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  1094. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  1095. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  1096. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  1097. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  1098. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  1099. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  1100. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  1101. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  1102. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  1103. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  1104. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  1105. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  1106. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  1107. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  1108. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  1109. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  1110. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  1111. package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
  1112. package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
  1113. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
  1114. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
  1115. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
  1116. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
  1117. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  1118. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  1119. package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
  1120. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  1121. package/package.json +1 -3
  1122. package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
  1123. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
  1124. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
  1125. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
  1126. package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
  1127. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
  1128. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
  1129. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
  1130. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
  1131. package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
  1132. package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
  1133. package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
  1134. package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
  1135. package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
  1136. package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
  1137. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
  1138. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
  1139. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
  1140. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
  1141. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
  1142. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
  1143. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
  1144. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
  1145. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
  1146. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
@@ -0,0 +1,1630 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ it,
7
+ jest,
8
+ } from "@jest/globals";
9
+
10
+ /*
11
+ * The rate limiter in front of the public incident form routes.
12
+ *
13
+ * Two buckets with opposite jobs. Reading a form is load control, like
14
+ * reading a public dashboard, and fails open. Submitting one declares an
15
+ * incident and pages on-call, so its counters are sized for people, add a
16
+ * per-form ceiling no number of addresses gets around, and fail closed.
17
+ *
18
+ * What matters is less "does it count" than the handful of properties that
19
+ * decide whether the limit can be walked around or turned against the form:
20
+ * that the address is the one OUR proxy wrote, that rotating share keys buys
21
+ * nothing, that junk keys share one bucket, that no request the middleware
22
+ * counts - refused or not - spends the form's shared allowance (only a
23
+ * submission about to declare an incident does, through
24
+ * reserveFormSubmission), that windows do not slide, and that the two
25
+ * buckets fail in opposite directions when Redis is gone.
26
+ */
27
+
28
+ jest.mock("../../../Server/Infrastructure/Redis", () => {
29
+ return {
30
+ __esModule: true,
31
+ default: {
32
+ getClient: jest.fn(),
33
+ isConnected: jest.fn(),
34
+ },
35
+ };
36
+ });
37
+
38
+ jest.mock("../../../Server/Utils/Logger", () => {
39
+ return {
40
+ __esModule: true,
41
+ default: {
42
+ debug: jest.fn(),
43
+ info: jest.fn(),
44
+ warn: jest.fn(),
45
+ error: jest.fn(),
46
+ },
47
+ getLogAttributesFromRequest: jest.fn().mockReturnValue({}),
48
+ };
49
+ });
50
+
51
+ jest.mock("../../../Server/Utils/Response", () => {
52
+ return {
53
+ __esModule: true,
54
+ default: {
55
+ sendErrorResponse: jest.fn(),
56
+ },
57
+ };
58
+ });
59
+
60
+ import Redis from "../../../Server/Infrastructure/Redis";
61
+ import Response from "../../../Server/Utils/Response";
62
+ import logger from "../../../Server/Utils/Logger";
63
+ import IncidentFormRateLimit, {
64
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
65
+ INCIDENT_FORM_READ_RATE_LIMIT_MESSAGE,
66
+ INCIDENT_FORM_SUBMIT_RATE_LIMIT_MESSAGE,
67
+ INCIDENT_FORM_TOTAL_RATE_LIMIT_MESSAGE,
68
+ IncidentFormCeilingException,
69
+ IncidentFormRateLimitBucket,
70
+ IncidentFormRateLimitBucketConfig,
71
+ IncidentFormRateLimitDecision,
72
+ IncidentFormRateLimitOutcome,
73
+ IncidentFormRateLimitScope,
74
+ } from "../../../Server/Middleware/IncidentFormRateLimit";
75
+ import ExceptionCode from "../../../Types/Exception/ExceptionCode";
76
+ import Exception from "../../../Types/Exception/Exception";
77
+ import ServiceUnavailableException from "../../../Types/Exception/ServiceUnavailableException";
78
+ import TooManyRequestsException from "../../../Types/Exception/TooManyRequestsException";
79
+ import ObjectID from "../../../Types/ObjectID";
80
+ import {
81
+ ExpressRequest,
82
+ ExpressResponse,
83
+ NextFunction,
84
+ } from "../../../Server/Utils/Express";
85
+
86
+ type MockedFn = ReturnType<typeof jest.fn>;
87
+
88
+ const getClientMock: MockedFn = Redis.getClient as unknown as MockedFn;
89
+ const isConnectedMock: MockedFn = Redis.isConnected as unknown as MockedFn;
90
+ const sendErrorResponseMock: MockedFn =
91
+ Response.sendErrorResponse as unknown as MockedFn;
92
+ const loggerWarnMock: MockedFn = logger.warn as unknown as MockedFn;
93
+ const loggerErrorMock: MockedFn = logger.error as unknown as MockedFn;
94
+
95
+ // The defaults baked into the limiter; asserted directly in one test below.
96
+ const READ_WINDOW_SECONDS: number = 60;
97
+ const READ_PER_FORM_AND_IP_LIMIT: number = 120;
98
+ const READ_PER_IP_LIMIT: number = 600;
99
+ const SUBMIT_WINDOW_SECONDS: number = 15 * 60;
100
+ const SUBMIT_PER_FORM_AND_IP_LIMIT: number = 10;
101
+ const SUBMIT_PER_IP_LIMIT: number = 30;
102
+ const SUBMIT_PER_FORM_WINDOW_SECONDS: number = 60 * 60;
103
+ const SUBMIT_PER_FORM_LIMIT: number = 60;
104
+
105
+ const FORM_KEY: string = "k:7c9e6679-7425-40de-944b-e07fc1f90ae7";
106
+ const OTHER_FORM_KEY: string = "k:0f8fad5b-d9cb-469f-a165-70867728950e";
107
+ const CLIENT_IP: string = "203.0.113.7";
108
+
109
+ /*
110
+ * A fake that behaves like a Redis counter store rather than a bag of
111
+ * assertions: INCR really increments and EXPIRE really records a TTL, so the
112
+ * window, reset and pinning tests exercise the limiter's arithmetic instead
113
+ * of restating it.
114
+ */
115
+ interface RecordedExpire {
116
+ key: string;
117
+ ttlSeconds: number;
118
+ }
119
+
120
+ class FakeRedisClient {
121
+ public counters: Map<string, number> = new Map();
122
+ public expires: Array<RecordedExpire> = [];
123
+
124
+ // Set to make the next exec() reject, for the error-path tests.
125
+ public failNextExec: Error | null = null;
126
+
127
+ // Set to make the exec() with this 1-based number reject.
128
+ public failExecNumber: number | null = null;
129
+
130
+ // Set to make the next exec() resolve to something malformed.
131
+ public malformedExecResult: unknown | undefined = undefined;
132
+
133
+ public execCount: number = 0;
134
+ public incrCallCount: number = 0;
135
+
136
+ public pipeline(): FakePipeline {
137
+ return new FakePipeline(this);
138
+ }
139
+
140
+ public expiresForKey(key: string): Array<RecordedExpire> {
141
+ return this.expires.filter((recorded: RecordedExpire) => {
142
+ return recorded.key === key;
143
+ });
144
+ }
145
+
146
+ public keysMatching(fragment: string): Array<string> {
147
+ return Array.from(this.counters.keys()).filter((key: string) => {
148
+ return key.includes(fragment);
149
+ });
150
+ }
151
+ }
152
+
153
+ type QueuedCommand = () => [Error | null, unknown];
154
+
155
+ class FakePipeline {
156
+ private commands: Array<QueuedCommand> = [];
157
+
158
+ public constructor(private client: FakeRedisClient) {}
159
+
160
+ public incr(key: string): FakePipeline {
161
+ this.commands.push((): [Error | null, unknown] => {
162
+ this.client.incrCallCount++;
163
+ const next: number = (this.client.counters.get(key) || 0) + 1;
164
+ this.client.counters.set(key, next);
165
+ return [null, next];
166
+ });
167
+
168
+ return this;
169
+ }
170
+
171
+ public expire(key: string, ttlSeconds: number): FakePipeline {
172
+ this.commands.push((): [Error | null, unknown] => {
173
+ this.client.expires.push({ key, ttlSeconds });
174
+ return [null, 1];
175
+ });
176
+
177
+ return this;
178
+ }
179
+
180
+ public async exec(): Promise<unknown> {
181
+ this.client.execCount++;
182
+
183
+ if (this.client.failNextExec) {
184
+ const error: Error = this.client.failNextExec;
185
+ this.client.failNextExec = null;
186
+ throw error;
187
+ }
188
+
189
+ if (this.client.failExecNumber === this.client.execCount) {
190
+ throw new Error("connection reset");
191
+ }
192
+
193
+ if (typeof this.client.malformedExecResult !== "undefined") {
194
+ const result: unknown = this.client.malformedExecResult;
195
+ this.client.malformedExecResult = undefined;
196
+ return result;
197
+ }
198
+
199
+ return this.commands.map((command: QueuedCommand) => {
200
+ return command();
201
+ });
202
+ }
203
+ }
204
+
205
+ interface BuildRequestOverrides {
206
+ params?: Record<string, unknown>;
207
+ headers?: Record<string, string | Array<string>>;
208
+ socketAddress?: string | undefined;
209
+ ip?: string | undefined;
210
+ }
211
+
212
+ const buildRequest: (overrides?: BuildRequestOverrides) => ExpressRequest = (
213
+ overrides: BuildRequestOverrides = {},
214
+ ) => {
215
+ return {
216
+ params: overrides.params || {},
217
+ body: {},
218
+ headers: overrides.headers || {},
219
+ socket: { remoteAddress: overrides.socketAddress },
220
+ ip: overrides.ip,
221
+ } as unknown as ExpressRequest;
222
+ };
223
+
224
+ interface BuiltResponse {
225
+ response: ExpressResponse;
226
+ headers: Record<string, string>;
227
+ }
228
+
229
+ const buildResponse: () => BuiltResponse = () => {
230
+ const headers: Record<string, string> = {};
231
+
232
+ const response: ExpressResponse = {
233
+ setHeader: (name: string, value: string): void => {
234
+ headers[name] = value;
235
+ },
236
+ } as unknown as ExpressResponse;
237
+
238
+ return { response, headers };
239
+ };
240
+
241
+ // Pinned to the start of an hour, so every window here starts together.
242
+ const HOUR_ALIGNED_TIME: number =
243
+ 1_700_000_000_000 -
244
+ (1_700_000_000_000 % (SUBMIT_PER_FORM_WINDOW_SECONDS * 1000));
245
+
246
+ describe("IncidentFormRateLimit", () => {
247
+ let client: FakeRedisClient;
248
+ let nowSpy: ReturnType<typeof jest.spyOn>;
249
+ let currentTime: number;
250
+
251
+ beforeEach(() => {
252
+ jest.clearAllMocks();
253
+
254
+ client = new FakeRedisClient();
255
+ getClientMock.mockReturnValue(client);
256
+ isConnectedMock.mockReturnValue(true);
257
+
258
+ currentTime = HOUR_ALIGNED_TIME;
259
+ nowSpy = jest.spyOn(Date, "now").mockImplementation(() => {
260
+ return currentTime;
261
+ });
262
+ });
263
+
264
+ afterEach(() => {
265
+ nowSpy.mockRestore();
266
+ });
267
+
268
+ const consume: (data: {
269
+ bucket: IncidentFormRateLimitBucket;
270
+ formKey?: string;
271
+ clientIp?: string;
272
+ }) => Promise<IncidentFormRateLimitDecision> = (data: {
273
+ bucket: IncidentFormRateLimitBucket;
274
+ formKey?: string;
275
+ clientIp?: string;
276
+ }) => {
277
+ return IncidentFormRateLimit.consume({
278
+ formKey: data.formKey || FORM_KEY,
279
+ clientIp: data.clientIp || CLIENT_IP,
280
+ bucket: data.bucket,
281
+ });
282
+ };
283
+
284
+ const consumeRead: (data?: {
285
+ formKey?: string;
286
+ clientIp?: string;
287
+ }) => Promise<IncidentFormRateLimitDecision> = (
288
+ data: { formKey?: string; clientIp?: string } = {},
289
+ ) => {
290
+ return consume({ ...data, bucket: IncidentFormRateLimitBucket.Read });
291
+ };
292
+
293
+ const consumeSubmit: (data?: {
294
+ formKey?: string;
295
+ clientIp?: string;
296
+ }) => Promise<IncidentFormRateLimitDecision> = (
297
+ data: { formKey?: string; clientIp?: string } = {},
298
+ ) => {
299
+ return consume({ ...data, bucket: IncidentFormRateLimitBucket.Submit });
300
+ };
301
+
302
+ // An address per index, all distinct: 10.1.x.y.
303
+ const addressNumber: (index: number) => string = (index: number) => {
304
+ return `10.1.${Math.floor(index / 250)}.${(index % 250) + 1}`;
305
+ };
306
+
307
+ describe("the budgets", () => {
308
+ it("reads at 120 per minute per form and address, 600 per address, failing open", () => {
309
+ const read: IncidentFormRateLimitBucketConfig =
310
+ IncidentFormRateLimit.getBucketConfig(IncidentFormRateLimitBucket.Read);
311
+
312
+ expect(read).toEqual({
313
+ windowSeconds: READ_WINDOW_SECONDS,
314
+ perFormAndIpLimit: READ_PER_FORM_AND_IP_LIMIT,
315
+ perIpLimit: READ_PER_IP_LIMIT,
316
+ perForm: undefined,
317
+ failsClosed: false,
318
+ });
319
+ });
320
+
321
+ it("submits at 10 per 15 minutes per form and address, 30 per address, 60 an hour per form, failing closed", () => {
322
+ const submit: IncidentFormRateLimitBucketConfig =
323
+ IncidentFormRateLimit.getBucketConfig(
324
+ IncidentFormRateLimitBucket.Submit,
325
+ );
326
+
327
+ expect(submit).toEqual({
328
+ windowSeconds: SUBMIT_WINDOW_SECONDS,
329
+ perFormAndIpLimit: SUBMIT_PER_FORM_AND_IP_LIMIT,
330
+ perIpLimit: SUBMIT_PER_IP_LIMIT,
331
+ perForm: {
332
+ windowSeconds: SUBMIT_PER_FORM_WINDOW_SECONDS,
333
+ limit: SUBMIT_PER_FORM_LIMIT,
334
+ },
335
+ failsClosed: true,
336
+ });
337
+ });
338
+
339
+ it("sizes submissions for people, far below the read budget", () => {
340
+ expect(SUBMIT_PER_FORM_AND_IP_LIMIT).toBeLessThan(
341
+ READ_PER_FORM_AND_IP_LIMIT,
342
+ );
343
+ expect(SUBMIT_PER_IP_LIMIT).toBeLessThan(READ_PER_IP_LIMIT);
344
+ });
345
+
346
+ it("hands out copies, so a caller cannot loosen the limits in force", async () => {
347
+ const copy: IncidentFormRateLimitBucketConfig =
348
+ IncidentFormRateLimit.getBucketConfig(
349
+ IncidentFormRateLimitBucket.Submit,
350
+ );
351
+
352
+ copy.perFormAndIpLimit = 100000;
353
+ copy.perForm!.limit = 100000;
354
+
355
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
356
+ await consumeSubmit();
357
+ }
358
+
359
+ expect((await consumeSubmit()).outcome).toBe(
360
+ IncidentFormRateLimitOutcome.RateLimited,
361
+ );
362
+ expect(
363
+ IncidentFormRateLimit.getBucketConfig(
364
+ IncidentFormRateLimitBucket.Submit,
365
+ ).perForm?.limit,
366
+ ).toBe(SUBMIT_PER_FORM_LIMIT);
367
+ });
368
+ });
369
+
370
+ describe("resolveFormKey", () => {
371
+ it("keys on the :shareKey parameter", () => {
372
+ const shareKey: string = ObjectID.generate().toString();
373
+
374
+ expect(
375
+ IncidentFormRateLimit.resolveFormKey(
376
+ buildRequest({ params: { shareKey } }),
377
+ ),
378
+ ).toBe(`k:${shareKey.toLowerCase()}`);
379
+ });
380
+
381
+ /*
382
+ * Casing and padding must not split a bucket, or the limit is bypassed
383
+ * by varying them alone.
384
+ */
385
+ it("gives a differently cased key the same bucket", () => {
386
+ const shareKey: string = ObjectID.generate().toString();
387
+
388
+ expect(
389
+ IncidentFormRateLimit.resolveFormKey(
390
+ buildRequest({ params: { shareKey: shareKey.toUpperCase() } }),
391
+ ),
392
+ ).toBe(
393
+ IncidentFormRateLimit.resolveFormKey(
394
+ buildRequest({ params: { shareKey: shareKey.toLowerCase() } }),
395
+ ),
396
+ );
397
+ });
398
+
399
+ it("ignores surrounding whitespace", () => {
400
+ const shareKey: string = ObjectID.generate().toString();
401
+
402
+ expect(
403
+ IncidentFormRateLimit.resolveFormKey(
404
+ buildRequest({ params: { shareKey: ` ${shareKey}\t` } }),
405
+ ),
406
+ ).toBe(`k:${shareKey.toLowerCase()}`);
407
+ });
408
+
409
+ /*
410
+ * Junk keys all share one bucket. That bounds Redis memory against a
411
+ * caller feeding random garbage, and it is the right grouping: none of
412
+ * those requests can reach a form.
413
+ */
414
+ it.each([
415
+ ["a word", "not-a-share-key"],
416
+ ["percent junk", "%%%%%%"],
417
+ ["a UUID with a character too many", `${ObjectID.generate()}0`],
418
+ ["a UUID with a stray character", "7c9e6679-7425-40de-944b-e07fc1f90aeZ"],
419
+ ["a very long segment", "a".repeat(10000)],
420
+ ["a Redis glob", "*"],
421
+ ])(
422
+ "collapses %s into the one invalid bucket",
423
+ (_label: string, value: string) => {
424
+ expect(
425
+ IncidentFormRateLimit.resolveFormKey(
426
+ buildRequest({ params: { shareKey: value } }),
427
+ ),
428
+ ).toBe("invalid");
429
+ },
430
+ );
431
+
432
+ it("uses a stable key when no share key is named", () => {
433
+ expect(IncidentFormRateLimit.resolveFormKey(buildRequest())).toBe("none");
434
+ expect(
435
+ IncidentFormRateLimit.resolveFormKey(
436
+ buildRequest({ params: { shareKey: " " } }),
437
+ ),
438
+ ).toBe("none");
439
+ });
440
+
441
+ it("ignores a share key that is not text", () => {
442
+ expect(
443
+ IncidentFormRateLimit.resolveFormKey(
444
+ buildRequest({ params: { shareKey: { evil: true } } }),
445
+ ),
446
+ ).toBe("none");
447
+ });
448
+
449
+ it("never reads another parameter", () => {
450
+ expect(
451
+ IncidentFormRateLimit.resolveFormKey(
452
+ buildRequest({
453
+ params: { id: ObjectID.generate().toString() },
454
+ }),
455
+ ),
456
+ ).toBe("none");
457
+ });
458
+ });
459
+
460
+ describe("resolveClientIp", () => {
461
+ /*
462
+ * The security-critical one. Nginx appends the peer to whatever the
463
+ * caller sent, so the LEFT of X-Forwarded-For is the caller's to write.
464
+ * Keying on it would give a forging caller a fresh bucket per request.
465
+ */
466
+ it("bills the hop our own proxy appended, not the one the caller forged", () => {
467
+ expect(
468
+ IncidentFormRateLimit.resolveClientIp(
469
+ buildRequest({
470
+ headers: { "x-forwarded-for": "9.9.9.9, 203.0.113.7" },
471
+ }),
472
+ ),
473
+ ).toBe("203.0.113.7");
474
+ });
475
+
476
+ it("gives two forging callers from one real address the same bucket", () => {
477
+ expect(
478
+ IncidentFormRateLimit.resolveClientIp(
479
+ buildRequest({
480
+ headers: { "x-forwarded-for": "1.1.1.1, 203.0.113.7" },
481
+ }),
482
+ ),
483
+ ).toBe(
484
+ IncidentFormRateLimit.resolveClientIp(
485
+ buildRequest({
486
+ headers: { "x-forwarded-for": "2.2.2.2, 203.0.113.7" },
487
+ }),
488
+ ),
489
+ );
490
+ });
491
+
492
+ it("falls back to the socket address with no forwarding header", () => {
493
+ expect(
494
+ IncidentFormRateLimit.resolveClientIp(
495
+ buildRequest({ socketAddress: "198.51.100.4" }),
496
+ ),
497
+ ).toBe("198.51.100.4");
498
+ });
499
+
500
+ it("puts callers with no address at all in one shared bucket", () => {
501
+ expect(IncidentFormRateLimit.resolveClientIp(buildRequest())).toBe(
502
+ "unknown",
503
+ );
504
+ });
505
+
506
+ it("puts a trusted entry that is not an address in the shared bucket", () => {
507
+ expect(
508
+ IncidentFormRateLimit.resolveClientIp(
509
+ buildRequest({
510
+ headers: { "x-forwarded-for": "1.2.3.4\n\r evil*key" },
511
+ }),
512
+ ),
513
+ ).toBe("unknown");
514
+ });
515
+
516
+ it("keeps IPv6 addresses", () => {
517
+ expect(
518
+ IncidentFormRateLimit.resolveClientIp(
519
+ buildRequest({
520
+ headers: { "x-forwarded-for": "2001:db8::8a2e:370:7334" },
521
+ }),
522
+ ),
523
+ ).toBe("2001:db8::8a2e:370:7334");
524
+ });
525
+ });
526
+
527
+ describe("consume - reading a form", () => {
528
+ it("allows 120 reads a minute from one address", async () => {
529
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT; i++) {
530
+ expect((await consumeRead()).outcome).toBe(
531
+ IncidentFormRateLimitOutcome.Allowed,
532
+ );
533
+ }
534
+ });
535
+
536
+ it("refuses the next one, naming the form and address counter", async () => {
537
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT; i++) {
538
+ await consumeRead();
539
+ }
540
+
541
+ const decision: IncidentFormRateLimitDecision = await consumeRead();
542
+
543
+ expect(decision.outcome).toBe(IncidentFormRateLimitOutcome.RateLimited);
544
+ expect(decision.scope).toBe(IncidentFormRateLimitScope.FormAndIp);
545
+ expect(decision.isFirstRejectionInWindow).toBe(true);
546
+ });
547
+
548
+ it("keeps separate budgets for separate addresses and separate forms", async () => {
549
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT + 5; i++) {
550
+ await consumeRead();
551
+ }
552
+
553
+ expect((await consumeRead({ clientIp: "198.51.100.4" })).outcome).toBe(
554
+ IncidentFormRateLimitOutcome.Allowed,
555
+ );
556
+ expect((await consumeRead({ formKey: OTHER_FORM_KEY })).outcome).toBe(
557
+ IncidentFormRateLimitOutcome.Allowed,
558
+ );
559
+ });
560
+
561
+ /*
562
+ * The bypass the address counter closes: a caller that rotates the share
563
+ * key never fills a form bucket, while every request still costs a
564
+ * Postgres lookup, 404 or not.
565
+ */
566
+ it("stops a caller rotating share keys at the per-address ceiling", async () => {
567
+ let rejected: IncidentFormRateLimitDecision | null = null;
568
+ let rejectedAt: number = -1;
569
+
570
+ for (let i: number = 0; i < READ_PER_IP_LIMIT + 10; i++) {
571
+ const decision: IncidentFormRateLimitDecision = await consumeRead({
572
+ formKey: `k:rotating-${i}`,
573
+ });
574
+
575
+ if (
576
+ decision.outcome === IncidentFormRateLimitOutcome.RateLimited &&
577
+ !rejected
578
+ ) {
579
+ rejected = decision;
580
+ rejectedAt = i;
581
+ }
582
+ }
583
+
584
+ expect(rejectedAt).toBe(READ_PER_IP_LIMIT);
585
+ expect(rejected?.scope).toBe(IncidentFormRateLimitScope.Ip);
586
+ });
587
+
588
+ it("reports the form and address counter first when both are over", async () => {
589
+ for (let i: number = 0; i < READ_PER_IP_LIMIT + 10; i++) {
590
+ await consumeRead();
591
+ }
592
+
593
+ expect((await consumeRead()).scope).toBe(
594
+ IncidentFormRateLimitScope.FormAndIp,
595
+ );
596
+ });
597
+
598
+ it("never counts a read against a form's submission ceiling", async () => {
599
+ await consumeRead();
600
+
601
+ expect(client.keysMatching(":f:")).toEqual([]);
602
+ expect(client.keysMatching("iform:rl:read:fi:")).toHaveLength(1);
603
+ expect(client.keysMatching("iform:rl:read:i:")).toHaveLength(1);
604
+ });
605
+
606
+ it("costs one round trip", async () => {
607
+ await consumeRead();
608
+ const afterFirst: number = client.execCount;
609
+
610
+ await consumeRead();
611
+
612
+ // The first created both keys (a second pipeline set their TTLs).
613
+ expect(afterFirst).toBe(2);
614
+ expect(client.execCount - afterFirst).toBe(1);
615
+ });
616
+ });
617
+
618
+ describe("consume - submitting a form", () => {
619
+ it("allows ten submissions from one address to one form in 15 minutes", async () => {
620
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
621
+ expect((await consumeSubmit()).outcome).toBe(
622
+ IncidentFormRateLimitOutcome.Allowed,
623
+ );
624
+ }
625
+ });
626
+
627
+ it("refuses the eleventh, naming the form and address counter", async () => {
628
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
629
+ await consumeSubmit();
630
+ }
631
+
632
+ const decision: IncidentFormRateLimitDecision = await consumeSubmit();
633
+
634
+ expect(decision.outcome).toBe(IncidentFormRateLimitOutcome.RateLimited);
635
+ expect(decision.scope).toBe(IncidentFormRateLimitScope.FormAndIp);
636
+ });
637
+
638
+ it("caps one address across every form at thirty", async () => {
639
+ let rejectedAt: number = -1;
640
+ let rejected: IncidentFormRateLimitDecision | null = null;
641
+
642
+ for (let i: number = 0; i < SUBMIT_PER_IP_LIMIT + 5; i++) {
643
+ const decision: IncidentFormRateLimitDecision = await consumeSubmit({
644
+ formKey: `k:form-${i}`,
645
+ });
646
+
647
+ if (
648
+ decision.outcome === IncidentFormRateLimitOutcome.RateLimited &&
649
+ !rejected
650
+ ) {
651
+ rejected = decision;
652
+ rejectedAt = i;
653
+ }
654
+ }
655
+
656
+ expect(rejectedAt).toBe(SUBMIT_PER_IP_LIMIT);
657
+ expect(rejected?.scope).toBe(IncidentFormRateLimitScope.Ip);
658
+ });
659
+
660
+ it("tells a caller refused by an address counter to come back when its quarter hour rolls", async () => {
661
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
662
+ await consumeSubmit();
663
+ }
664
+
665
+ currentTime = currentTime + 5 * 60 * 1000;
666
+
667
+ const decision: IncidentFormRateLimitDecision = await consumeSubmit();
668
+
669
+ expect(decision.scope).toBe(IncidentFormRateLimitScope.FormAndIp);
670
+ expect(decision.retryAfterSeconds).toBe(10 * 60);
671
+ });
672
+
673
+ /*
674
+ * The form's shared ceiling is spent only by a submission that passed
675
+ * every check (consumeFormCeiling, from the service) - never by a request
676
+ * the middleware counts, refused or not. Otherwise anyone holding the
677
+ * link could use it up with requests the IP allowlist, the captcha or
678
+ * the answers refuse, and lock the form for everybody.
679
+ */
680
+ it("never counts a submission's address counters against the form's ceiling", async () => {
681
+ for (let i: number = 0; i < 200; i++) {
682
+ await consumeSubmit({ clientIp: addressNumber(i % 20) });
683
+ }
684
+
685
+ expect(client.keysMatching("iform:rl:submit:f:")).toEqual([]);
686
+ expect(client.keysMatching(":f:")).toEqual([]);
687
+ });
688
+
689
+ it("never refuses for the form's ceiling: sixty-one addresses all pass their own counters", async () => {
690
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT + 1; i++) {
691
+ expect(
692
+ (await consumeSubmit({ clientIp: addressNumber(i) })).outcome,
693
+ ).toBe(IncidentFormRateLimitOutcome.Allowed);
694
+ }
695
+ });
696
+
697
+ /*
698
+ * Reading a form and submitting it must not draw on the same counters,
699
+ * in either direction: a busy form page would otherwise block reports,
700
+ * and reports would be laundered through the far larger read budget.
701
+ */
702
+ it("does not share counters with reading", async () => {
703
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT + 5; i++) {
704
+ await consumeSubmit();
705
+ }
706
+
707
+ expect((await consumeRead()).outcome).toBe(
708
+ IncidentFormRateLimitOutcome.Allowed,
709
+ );
710
+
711
+ for (let i: number = 0; i < 100; i++) {
712
+ await consumeRead({ formKey: OTHER_FORM_KEY });
713
+ }
714
+
715
+ expect((await consumeSubmit({ formKey: OTHER_FORM_KEY })).outcome).toBe(
716
+ IncidentFormRateLimitOutcome.Allowed,
717
+ );
718
+ });
719
+ });
720
+
721
+ /*
722
+ * The ceiling that survives address rotation: however many addresses hold
723
+ * the link, the form declares at most this many incidents an hour. Spent
724
+ * by IncidentFormService only for a submission about to declare one.
725
+ */
726
+ describe("consumeFormCeiling - the form's hourly ceiling", () => {
727
+ const consumeCeiling: (
728
+ formKey?: string,
729
+ ) => Promise<IncidentFormRateLimitDecision> = (
730
+ formKey: string = FORM_KEY,
731
+ ) => {
732
+ return IncidentFormRateLimit.consumeFormCeiling({ formKey });
733
+ };
734
+
735
+ it("allows sixty incidents an hour and refuses the next, naming the form ceiling", async () => {
736
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT; i++) {
737
+ expect((await consumeCeiling()).outcome).toBe(
738
+ IncidentFormRateLimitOutcome.Allowed,
739
+ );
740
+ }
741
+
742
+ const decision: IncidentFormRateLimitDecision = await consumeCeiling();
743
+
744
+ expect(decision.outcome).toBe(IncidentFormRateLimitOutcome.RateLimited);
745
+ expect(decision.scope).toBe(IncidentFormRateLimitScope.Form);
746
+ expect(decision.isFirstRejectionInWindow).toBe(true);
747
+ expect((await consumeCeiling()).isFirstRejectionInWindow).toBe(false);
748
+ });
749
+
750
+ it("tells a caller refused by it to come back when the hour rolls", async () => {
751
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT; i++) {
752
+ await consumeCeiling();
753
+ }
754
+
755
+ currentTime = currentTime + 20 * 60 * 1000;
756
+
757
+ const decision: IncidentFormRateLimitDecision = await consumeCeiling();
758
+
759
+ expect(decision.scope).toBe(IncidentFormRateLimitScope.Form);
760
+ // Forty minutes left of the hour, not what is left of 15 minutes.
761
+ expect(decision.retryAfterSeconds).toBe(40 * 60);
762
+ });
763
+
764
+ it("holds for the hour, and starts afresh with the next", async () => {
765
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT; i++) {
766
+ await consumeCeiling();
767
+ }
768
+
769
+ currentTime = currentTime + SUBMIT_WINDOW_SECONDS * 1000;
770
+
771
+ expect((await consumeCeiling()).outcome).toBe(
772
+ IncidentFormRateLimitOutcome.RateLimited,
773
+ );
774
+
775
+ currentTime = HOUR_ALIGNED_TIME + SUBMIT_PER_FORM_WINDOW_SECONDS * 1000;
776
+
777
+ expect((await consumeCeiling()).outcome).toBe(
778
+ IncidentFormRateLimitOutcome.Allowed,
779
+ );
780
+ });
781
+
782
+ it("gives a reset link, which is a new share key, a fresh ceiling", async () => {
783
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT + 5; i++) {
784
+ await consumeCeiling();
785
+ }
786
+
787
+ expect((await consumeCeiling()).scope).toBe(
788
+ IncidentFormRateLimitScope.Form,
789
+ );
790
+ expect((await consumeCeiling(OTHER_FORM_KEY)).outcome).toBe(
791
+ IncidentFormRateLimitOutcome.Allowed,
792
+ );
793
+ });
794
+
795
+ it("counts the form under the key the middleware gives it", async () => {
796
+ await consumeCeiling();
797
+
798
+ expect(client.keysMatching("iform:rl:submit:f:")).toEqual([
799
+ expect.stringMatching(
800
+ new RegExp(`^iform:rl:submit:f:${FORM_KEY}:\\d+$`),
801
+ ),
802
+ ]);
803
+ expect(
804
+ IncidentFormRateLimit.getFormKey(
805
+ " 7C9E6679-7425-40DE-944B-E07FC1F90AE7 ",
806
+ ),
807
+ ).toBe(FORM_KEY);
808
+ expect(
809
+ IncidentFormRateLimit.resolveFormKey(
810
+ buildRequest({
811
+ params: { shareKey: "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
812
+ }),
813
+ ),
814
+ ).toBe(FORM_KEY);
815
+ });
816
+
817
+ it("gives the hour counter a TTL longer than the hour", async () => {
818
+ await consumeCeiling();
819
+
820
+ const formCounter: string = client.keysMatching(
821
+ `iform:rl:submit:f:${FORM_KEY}:`,
822
+ )[0]!;
823
+
824
+ expect(client.expiresForKey(formCounter)).toEqual([
825
+ { key: formCounter, ttlSeconds: SUBMIT_PER_FORM_WINDOW_SECONDS * 2 },
826
+ ]);
827
+ });
828
+
829
+ it.each([
830
+ [
831
+ "Redis has no client",
832
+ (): void => {
833
+ getClientMock.mockReturnValue(null);
834
+ },
835
+ ],
836
+ [
837
+ "Redis is not connected",
838
+ (): void => {
839
+ isConnectedMock.mockReturnValue(false);
840
+ },
841
+ ],
842
+ [
843
+ "the pipeline throws",
844
+ (): void => {
845
+ client.failNextExec = new Error("connection reset");
846
+ },
847
+ ],
848
+ [
849
+ "the pipeline returns nothing",
850
+ (): void => {
851
+ client.malformedExecResult = null;
852
+ },
853
+ ],
854
+ ])(
855
+ "reports unavailable when %s",
856
+ async (_label: string, breakRedis: () => void) => {
857
+ breakRedis();
858
+
859
+ expect((await consumeCeiling()).outcome).toBe(
860
+ IncidentFormRateLimitOutcome.CounterUnavailable,
861
+ );
862
+ },
863
+ );
864
+ });
865
+
866
+ describe("reserveFormSubmission - what the service calls before declaring", () => {
867
+ const SHARE_KEY: string = "7C9E6679-7425-40DE-944B-E07FC1F90AE7";
868
+
869
+ const refusalOf: () => Promise<unknown> = async () => {
870
+ try {
871
+ await IncidentFormRateLimit.reserveFormSubmission({
872
+ shareKey: SHARE_KEY,
873
+ });
874
+ } catch (err) {
875
+ return err;
876
+ }
877
+
878
+ return undefined;
879
+ };
880
+
881
+ it("spends one of the form's incidents, and refuses the sixty-first with when to come back", async () => {
882
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT; i++) {
883
+ expect(await refusalOf()).toBeUndefined();
884
+ }
885
+
886
+ currentTime = currentTime + 15 * 60 * 1000;
887
+
888
+ const error: unknown = await refusalOf();
889
+
890
+ expect(error).toBeInstanceOf(IncidentFormCeilingException);
891
+ expect(error).toBeInstanceOf(TooManyRequestsException);
892
+ expect((error as Exception).code).toBe(429);
893
+ expect((error as Exception).message).toBe(
894
+ INCIDENT_FORM_TOTAL_RATE_LIMIT_MESSAGE,
895
+ );
896
+ expect((error as IncidentFormCeilingException).retryAfterSeconds).toBe(
897
+ 45 * 60,
898
+ );
899
+ expect(
900
+ client.keysMatching(`iform:rl:submit:f:${FORM_KEY}:`),
901
+ ).toHaveLength(1);
902
+ });
903
+
904
+ it("logs the first refusal of the hour, then stays quiet", async () => {
905
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT; i++) {
906
+ await refusalOf();
907
+ }
908
+
909
+ loggerWarnMock.mockClear();
910
+
911
+ for (let i: number = 0; i < 20; i++) {
912
+ await refusalOf();
913
+ }
914
+
915
+ expect(loggerWarnMock).toHaveBeenCalledTimes(1);
916
+ expect(String(loggerWarnMock.mock.calls[0]![0])).toContain("form limit");
917
+ });
918
+
919
+ it("fails closed with a 503 when the counter cannot be reached", async () => {
920
+ isConnectedMock.mockReturnValue(false);
921
+
922
+ const error: unknown = await refusalOf();
923
+
924
+ expect(error).toBeInstanceOf(ServiceUnavailableException);
925
+ expect((error as Exception).code).toBe(503);
926
+ expect((error as Exception).message).toBe(
927
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
928
+ );
929
+ });
930
+
931
+ it("writes the refusal's Retry-After on the route's response, and nothing for any other error", () => {
932
+ const built: BuiltResponse = buildResponse();
933
+
934
+ IncidentFormRateLimit.setRetryAfterFor(
935
+ built.response,
936
+ new TooManyRequestsException("another limiter"),
937
+ );
938
+
939
+ expect(built.headers).toEqual({});
940
+
941
+ IncidentFormRateLimit.setRetryAfterFor(
942
+ built.response,
943
+ new IncidentFormCeilingException(1234),
944
+ );
945
+
946
+ expect(built.headers).toEqual({ "Retry-After": "1234" });
947
+ });
948
+ });
949
+
950
+ describe("consume - window behaviour", () => {
951
+ it("sets a TTL only on the request that created a counter", async () => {
952
+ await consumeRead();
953
+
954
+ const formAndIpKey: string = client.keysMatching(":fi:")[0]!;
955
+
956
+ expect(client.expiresForKey(formAndIpKey)).toHaveLength(1);
957
+
958
+ for (let i: number = 0; i < 20; i++) {
959
+ await consumeRead();
960
+ }
961
+
962
+ /*
963
+ * Re-issuing EXPIRE on every increment would slide the window forward
964
+ * for as long as the load continued, so a client that tripped the
965
+ * limit could never recover.
966
+ */
967
+ expect(client.expiresForKey(formAndIpKey)).toHaveLength(1);
968
+ });
969
+
970
+ it("gives every counter a TTL of two of its windows", async () => {
971
+ await consumeRead();
972
+ await consumeSubmit();
973
+ await IncidentFormRateLimit.consumeFormCeiling({ formKey: FORM_KEY });
974
+
975
+ expect(client.expires.length).toBe(5);
976
+
977
+ for (const recorded of client.expires) {
978
+ const windowSeconds: number = recorded.key.startsWith("iform:rl:read:")
979
+ ? READ_WINDOW_SECONDS
980
+ : recorded.key.includes(":f:")
981
+ ? SUBMIT_PER_FORM_WINDOW_SECONDS
982
+ : SUBMIT_WINDOW_SECONDS;
983
+
984
+ expect(recorded.ttlSeconds).toBe(windowSeconds * 2);
985
+ }
986
+ });
987
+
988
+ it("starts a fresh allowance when the window rolls, and not before", async () => {
989
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT + 1; i++) {
990
+ await consumeRead();
991
+ }
992
+
993
+ currentTime = currentTime + (READ_WINDOW_SECONDS - 1) * 1000;
994
+
995
+ expect((await consumeRead()).outcome).toBe(
996
+ IncidentFormRateLimitOutcome.RateLimited,
997
+ );
998
+
999
+ currentTime = HOUR_ALIGNED_TIME + READ_WINDOW_SECONDS * 1000;
1000
+
1001
+ expect((await consumeRead()).outcome).toBe(
1002
+ IncidentFormRateLimitOutcome.Allowed,
1003
+ );
1004
+ });
1005
+
1006
+ /*
1007
+ * A refused request still counts. Otherwise a client that keeps
1008
+ * hammering is handed a fresh allowance the moment it is refused.
1009
+ */
1010
+ it("counts refused requests too", async () => {
1011
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT; i++) {
1012
+ await consumeRead();
1013
+ }
1014
+
1015
+ const before: number = client.incrCallCount;
1016
+
1017
+ await consumeRead();
1018
+ await consumeRead();
1019
+
1020
+ expect(client.incrCallCount).toBe(before + 4);
1021
+ });
1022
+
1023
+ it("says to retry inside the current window, sooner as it drains, never after zero seconds", async () => {
1024
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT + 1; i++) {
1025
+ await consumeRead();
1026
+ }
1027
+
1028
+ const atStart: IncidentFormRateLimitDecision = await consumeRead();
1029
+
1030
+ expect(atStart.retryAfterSeconds).toBe(READ_WINDOW_SECONDS);
1031
+
1032
+ currentTime = currentTime + 30_000;
1033
+
1034
+ expect((await consumeRead()).retryAfterSeconds).toBe(30);
1035
+
1036
+ currentTime = HOUR_ALIGNED_TIME + READ_WINDOW_SECONDS * 1000 - 1;
1037
+
1038
+ expect((await consumeRead()).retryAfterSeconds).toBe(1);
1039
+ });
1040
+
1041
+ it("marks only the crossing as the first refusal of the window", async () => {
1042
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT; i++) {
1043
+ await consumeRead();
1044
+ }
1045
+
1046
+ expect((await consumeRead()).isFirstRejectionInWindow).toBe(true);
1047
+ expect((await consumeRead()).isFirstRejectionInWindow).toBe(false);
1048
+ });
1049
+ });
1050
+
1051
+ describe("consume - counter unavailable", () => {
1052
+ it.each([
1053
+ [IncidentFormRateLimitBucket.Read],
1054
+ [IncidentFormRateLimitBucket.Submit],
1055
+ ])(
1056
+ "reports unavailable for %s when Redis has no client",
1057
+ async (bucket: IncidentFormRateLimitBucket) => {
1058
+ getClientMock.mockReturnValue(null);
1059
+
1060
+ expect((await consume({ bucket })).outcome).toBe(
1061
+ IncidentFormRateLimitOutcome.CounterUnavailable,
1062
+ );
1063
+ },
1064
+ );
1065
+
1066
+ it("reports unavailable when Redis is not connected", async () => {
1067
+ isConnectedMock.mockReturnValue(false);
1068
+
1069
+ expect((await consumeSubmit()).outcome).toBe(
1070
+ IncidentFormRateLimitOutcome.CounterUnavailable,
1071
+ );
1072
+ });
1073
+
1074
+ it("reports unavailable when the pipeline throws", async () => {
1075
+ client.failNextExec = new Error("connection reset");
1076
+
1077
+ expect((await consumeRead()).outcome).toBe(
1078
+ IncidentFormRateLimitOutcome.CounterUnavailable,
1079
+ );
1080
+ });
1081
+
1082
+ it("reports unavailable when setting a new counter's TTL fails", async () => {
1083
+ // exec 1 counts the address counters, exec 2 sets their TTLs.
1084
+ client.failExecNumber = 2;
1085
+
1086
+ expect((await consumeSubmit()).outcome).toBe(
1087
+ IncidentFormRateLimitOutcome.CounterUnavailable,
1088
+ );
1089
+ });
1090
+
1091
+ it.each([
1092
+ ["returns null", null],
1093
+ ["returns too few results", [[null, 1]]],
1094
+ [
1095
+ "reports a failed command",
1096
+ [
1097
+ [new Error("READONLY"), null],
1098
+ [null, 1],
1099
+ ],
1100
+ ],
1101
+ [
1102
+ "returns a non-numeric count",
1103
+ [
1104
+ [null, "1"],
1105
+ [null, 1],
1106
+ ],
1107
+ ],
1108
+ ])(
1109
+ "reports unavailable when the pipeline %s",
1110
+ async (_label: string, result: unknown) => {
1111
+ client.malformedExecResult = result;
1112
+
1113
+ expect((await consumeSubmit()).outcome).toBe(
1114
+ IncidentFormRateLimitOutcome.CounterUnavailable,
1115
+ );
1116
+ },
1117
+ );
1118
+
1119
+ it("never throws out of consume", async () => {
1120
+ client.failNextExec = new Error("boom");
1121
+
1122
+ await expect(consumeSubmit()).resolves.toBeDefined();
1123
+ });
1124
+ });
1125
+
1126
+ describe("getMiddleware", () => {
1127
+ const runMiddleware: (data: {
1128
+ bucket?: IncidentFormRateLimitBucket;
1129
+ request?: ExpressRequest;
1130
+ response?: ExpressResponse;
1131
+ }) => Promise<{
1132
+ nextCalled: boolean;
1133
+ headers: Record<string, string>;
1134
+ }> = async (data: {
1135
+ bucket?: IncidentFormRateLimitBucket;
1136
+ request?: ExpressRequest;
1137
+ response?: ExpressResponse;
1138
+ }) => {
1139
+ const built: BuiltResponse = buildResponse();
1140
+ let nextCalled: boolean = false;
1141
+
1142
+ await IncidentFormRateLimit.getMiddleware(data.bucket)(
1143
+ data.request ||
1144
+ buildRequest({
1145
+ params: { shareKey: "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
1146
+ headers: { "x-forwarded-for": CLIENT_IP },
1147
+ }),
1148
+ data.response || built.response,
1149
+ (() => {
1150
+ nextCalled = true;
1151
+ }) as unknown as NextFunction,
1152
+ );
1153
+
1154
+ return { nextCalled, headers: built.headers };
1155
+ };
1156
+
1157
+ const lastError: () => Exception = () => {
1158
+ const calls: Array<Array<unknown>> = sendErrorResponseMock.mock
1159
+ .calls as Array<Array<unknown>>;
1160
+
1161
+ return calls[calls.length - 1]![2] as Exception;
1162
+ };
1163
+
1164
+ it("passes an allowed request through", async () => {
1165
+ expect(
1166
+ (await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit }))
1167
+ .nextCalled,
1168
+ ).toBe(true);
1169
+ expect(sendErrorResponseMock).not.toHaveBeenCalled();
1170
+ });
1171
+
1172
+ it("counts on the read bucket unless told otherwise", async () => {
1173
+ await runMiddleware({});
1174
+
1175
+ expect(client.keysMatching("iform:rl:read:")).toHaveLength(2);
1176
+ expect(client.keysMatching("iform:rl:submit:")).toHaveLength(0);
1177
+ });
1178
+
1179
+ it("counts the share key in the path and the trusted client address", async () => {
1180
+ await runMiddleware({
1181
+ bucket: IncidentFormRateLimitBucket.Read,
1182
+ request: buildRequest({
1183
+ params: { shareKey: "7C9E6679-7425-40DE-944B-E07FC1F90AE7" },
1184
+ headers: { "x-forwarded-for": "1.2.3.4, 203.0.113.7" },
1185
+ }),
1186
+ });
1187
+
1188
+ expect(client.keysMatching(":fi:")).toEqual([
1189
+ expect.stringContaining(`:fi:${FORM_KEY}:203.0.113.7:`),
1190
+ ]);
1191
+ });
1192
+
1193
+ /*
1194
+ * The property everything rests on: a refused request must not reach
1195
+ * the handler, or the incident is declared anyway and the limiter is
1196
+ * decoration.
1197
+ */
1198
+ it("stops a refused submission before the handler", async () => {
1199
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
1200
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1201
+ }
1202
+
1203
+ const refused: { nextCalled: boolean; headers: Record<string, string> } =
1204
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1205
+
1206
+ expect(refused.nextCalled).toBe(false);
1207
+ expect(sendErrorResponseMock).toHaveBeenCalledTimes(1);
1208
+ expect(lastError().code).toBe(ExceptionCode.TooManyRequestsException);
1209
+ expect(lastError().code).toBe(429);
1210
+ expect(lastError().message).toBe(INCIDENT_FORM_SUBMIT_RATE_LIMIT_MESSAGE);
1211
+ expect(Number(refused.headers["Retry-After"])).toBe(
1212
+ SUBMIT_WINDOW_SECONDS,
1213
+ );
1214
+ });
1215
+
1216
+ it("words a refused read as a read", async () => {
1217
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT + 1; i++) {
1218
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Read });
1219
+ }
1220
+
1221
+ expect(lastError().code).toBe(429);
1222
+ expect(lastError().message).toBe(INCIDENT_FORM_READ_RATE_LIMIT_MESSAGE);
1223
+ });
1224
+
1225
+ /*
1226
+ * The form's ceiling is the service's to spend, after every check: the
1227
+ * middleware lets sixty-one addresses through to be judged, and counts
1228
+ * none of them against it.
1229
+ */
1230
+ it("never refuses a submission for the form's own ceiling, nor spends it", async () => {
1231
+ for (let i: number = 0; i < SUBMIT_PER_FORM_LIMIT + 1; i++) {
1232
+ expect(
1233
+ (
1234
+ await runMiddleware({
1235
+ bucket: IncidentFormRateLimitBucket.Submit,
1236
+ request: buildRequest({
1237
+ params: { shareKey: "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
1238
+ headers: { "x-forwarded-for": addressNumber(i) },
1239
+ }),
1240
+ })
1241
+ ).nextCalled,
1242
+ ).toBe(true);
1243
+ }
1244
+
1245
+ expect(sendErrorResponseMock).not.toHaveBeenCalled();
1246
+ expect(client.keysMatching("iform:rl:submit:f:")).toEqual([]);
1247
+ });
1248
+
1249
+ it("never tells a refused caller the limit or its count", async () => {
1250
+ const messages: Array<string> = [
1251
+ INCIDENT_FORM_READ_RATE_LIMIT_MESSAGE,
1252
+ INCIDENT_FORM_SUBMIT_RATE_LIMIT_MESSAGE,
1253
+ INCIDENT_FORM_TOTAL_RATE_LIMIT_MESSAGE,
1254
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
1255
+ ];
1256
+
1257
+ for (const message of messages) {
1258
+ expect(message).not.toMatch(/\d/);
1259
+ }
1260
+ });
1261
+
1262
+ it("survives a response object with no setHeader", async () => {
1263
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT + 2; i++) {
1264
+ await expect(
1265
+ runMiddleware({
1266
+ bucket: IncidentFormRateLimitBucket.Submit,
1267
+ response: {} as unknown as ExpressResponse,
1268
+ }),
1269
+ ).resolves.toBeDefined();
1270
+ }
1271
+ });
1272
+
1273
+ /*
1274
+ * Reads fail OPEN when Redis is gone: showing a form's questions is
1275
+ * read-only, and turning every report form into an error page over a
1276
+ * Redis blip is worse than an unbounded window for its duration.
1277
+ */
1278
+ it("serves reads unthrottled when Redis is gone", async () => {
1279
+ isConnectedMock.mockReturnValue(false);
1280
+
1281
+ expect(
1282
+ (await runMiddleware({ bucket: IncidentFormRateLimitBucket.Read }))
1283
+ .nextCalled,
1284
+ ).toBe(true);
1285
+ expect(sendErrorResponseMock).not.toHaveBeenCalled();
1286
+ });
1287
+
1288
+ it("still serves reads when the counter errors", async () => {
1289
+ client.failNextExec = new Error("connection reset");
1290
+
1291
+ expect(
1292
+ (await runMiddleware({ bucket: IncidentFormRateLimitBucket.Read }))
1293
+ .nextCalled,
1294
+ ).toBe(true);
1295
+ });
1296
+
1297
+ /*
1298
+ * Submits fail CLOSED: without the counter nothing bounds how many
1299
+ * incidents - and pages - a link can produce.
1300
+ */
1301
+ it("refuses submissions with a 503 when Redis is gone", async () => {
1302
+ isConnectedMock.mockReturnValue(false);
1303
+
1304
+ const result: { nextCalled: boolean } = await runMiddleware({
1305
+ bucket: IncidentFormRateLimitBucket.Submit,
1306
+ });
1307
+
1308
+ expect(result.nextCalled).toBe(false);
1309
+ expect(lastError().code).toBe(ExceptionCode.ServiceUnavailableException);
1310
+ expect(lastError().code).toBe(503);
1311
+ expect(lastError().message).toBe(
1312
+ INCIDENT_FORM_RATE_LIMIT_UNAVAILABLE_MESSAGE,
1313
+ );
1314
+ });
1315
+
1316
+ it("refuses submissions when the counter errors", async () => {
1317
+ client.failNextExec = new Error("connection reset");
1318
+
1319
+ expect(
1320
+ (await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit }))
1321
+ .nextCalled,
1322
+ ).toBe(false);
1323
+ expect(lastError().code).toBe(503);
1324
+ });
1325
+
1326
+ /*
1327
+ * A limiter exists because the caller keeps knocking. Logging every
1328
+ * refusal would turn their flood into a second one in the log pipeline.
1329
+ */
1330
+ it("logs the moment a caller crosses the line, then stays quiet", async () => {
1331
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
1332
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1333
+ }
1334
+
1335
+ loggerWarnMock.mockClear();
1336
+
1337
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1338
+
1339
+ expect(loggerWarnMock).toHaveBeenCalledTimes(1);
1340
+ expect(String(loggerWarnMock.mock.calls[0]![0])).toContain(
1341
+ "form-and-ip limit",
1342
+ );
1343
+
1344
+ for (let i: number = 0; i < 50; i++) {
1345
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1346
+ }
1347
+
1348
+ expect(loggerWarnMock).toHaveBeenCalledTimes(1);
1349
+ });
1350
+
1351
+ it("logs the crossing again once the window rolls", async () => {
1352
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT + 5; i++) {
1353
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Read });
1354
+ }
1355
+
1356
+ currentTime = currentTime + READ_WINDOW_SECONDS * 1000;
1357
+ loggerWarnMock.mockClear();
1358
+
1359
+ for (let i: number = 0; i < READ_PER_FORM_AND_IP_LIMIT + 5; i++) {
1360
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Read });
1361
+ }
1362
+
1363
+ expect(loggerWarnMock).toHaveBeenCalledTimes(1);
1364
+ });
1365
+
1366
+ /*
1367
+ * An outage puts EVERY request on the unavailable path, so an unguarded
1368
+ * log line there is one line per request for the whole outage.
1369
+ */
1370
+ it("does not log once per request while Redis is down", async () => {
1371
+ isConnectedMock.mockReturnValue(false);
1372
+ currentTime = currentTime + 10 * 60 * 1000;
1373
+ loggerWarnMock.mockClear();
1374
+ loggerErrorMock.mockClear();
1375
+
1376
+ for (let i: number = 0; i < 100; i++) {
1377
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Read });
1378
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1379
+ }
1380
+
1381
+ /*
1382
+ * The read warning plus its attributes line, and one refusal line -
1383
+ * not two hundred. (The clock moved past any earlier test's line.)
1384
+ */
1385
+ expect(loggerWarnMock).toHaveBeenCalledTimes(2);
1386
+ expect(loggerErrorMock).toHaveBeenCalledTimes(1);
1387
+
1388
+ // Every submission was still refused.
1389
+ expect(sendErrorResponseMock).toHaveBeenCalledTimes(100);
1390
+ });
1391
+
1392
+ it("logs the outage again after the throttle interval", async () => {
1393
+ isConnectedMock.mockReturnValue(false);
1394
+ currentTime = currentTime + 20 * 60 * 1000;
1395
+
1396
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1397
+
1398
+ currentTime = currentTime + 60_000;
1399
+ loggerErrorMock.mockClear();
1400
+
1401
+ await runMiddleware({ bucket: IncidentFormRateLimitBucket.Submit });
1402
+
1403
+ expect(loggerErrorMock).toHaveBeenCalledTimes(1);
1404
+ });
1405
+
1406
+ it("keeps one bucket for one real client however the header is forged", async () => {
1407
+ for (let i: number = 0; i < SUBMIT_PER_FORM_AND_IP_LIMIT; i++) {
1408
+ await runMiddleware({
1409
+ bucket: IncidentFormRateLimitBucket.Submit,
1410
+ request: buildRequest({
1411
+ params: { shareKey: "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
1412
+ headers: { "x-forwarded-for": `10.0.0.${i}, ${CLIENT_IP}` },
1413
+ }),
1414
+ });
1415
+ }
1416
+
1417
+ expect(
1418
+ (
1419
+ await runMiddleware({
1420
+ bucket: IncidentFormRateLimitBucket.Submit,
1421
+ request: buildRequest({
1422
+ params: { shareKey: "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
1423
+ headers: { "x-forwarded-for": `1.2.3.4, ${CLIENT_IP}` },
1424
+ }),
1425
+ })
1426
+ ).nextCalled,
1427
+ ).toBe(false);
1428
+ });
1429
+ });
1430
+ });
1431
+
1432
+ /*
1433
+ * The budgets are read from the environment when the module loads, so these
1434
+ * reload it rather than calling into the already configured one.
1435
+ */
1436
+ describe("IncidentFormRateLimit configuration", () => {
1437
+ const originalEnv: NodeJS.ProcessEnv = { ...process.env };
1438
+
1439
+ afterEach(() => {
1440
+ process.env = { ...originalEnv };
1441
+ jest.resetModules();
1442
+ });
1443
+
1444
+ interface ReloadedModule {
1445
+ limiter: typeof IncidentFormRateLimit;
1446
+ client: FakeRedisClient;
1447
+ }
1448
+
1449
+ const reload: () => Promise<ReloadedModule> = async () => {
1450
+ jest.resetModules();
1451
+
1452
+ const redisModule: {
1453
+ default: { getClient: MockedFn; isConnected: MockedFn };
1454
+ } = (await import("../../../Server/Infrastructure/Redis")) as unknown as {
1455
+ default: { getClient: MockedFn; isConnected: MockedFn };
1456
+ };
1457
+
1458
+ const limiterModule: { default: typeof IncidentFormRateLimit } =
1459
+ (await import(
1460
+ "../../../Server/Middleware/IncidentFormRateLimit"
1461
+ )) as unknown as { default: typeof IncidentFormRateLimit };
1462
+
1463
+ const client: FakeRedisClient = new FakeRedisClient();
1464
+
1465
+ redisModule.default.getClient.mockReturnValue(client);
1466
+ redisModule.default.isConnected.mockReturnValue(true);
1467
+
1468
+ return { limiter: limiterModule.default, client };
1469
+ };
1470
+
1471
+ it.each([
1472
+ [
1473
+ "INCIDENT_FORM_RATE_LIMIT_WINDOW_SECONDS",
1474
+ IncidentFormRateLimitBucket.Read,
1475
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1476
+ return config.windowSeconds;
1477
+ },
1478
+ ],
1479
+ [
1480
+ "INCIDENT_FORM_RATE_LIMIT_PER_FORM_AND_IP_PER_WINDOW",
1481
+ IncidentFormRateLimitBucket.Read,
1482
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1483
+ return config.perFormAndIpLimit;
1484
+ },
1485
+ ],
1486
+ [
1487
+ "INCIDENT_FORM_RATE_LIMIT_PER_IP_PER_WINDOW",
1488
+ IncidentFormRateLimitBucket.Read,
1489
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1490
+ return config.perIpLimit;
1491
+ },
1492
+ ],
1493
+ [
1494
+ "INCIDENT_FORM_SUBMIT_RATE_LIMIT_WINDOW_SECONDS",
1495
+ IncidentFormRateLimitBucket.Submit,
1496
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1497
+ return config.windowSeconds;
1498
+ },
1499
+ ],
1500
+ [
1501
+ "INCIDENT_FORM_SUBMIT_RATE_LIMIT_PER_FORM_AND_IP_PER_WINDOW",
1502
+ IncidentFormRateLimitBucket.Submit,
1503
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1504
+ return config.perFormAndIpLimit;
1505
+ },
1506
+ ],
1507
+ [
1508
+ "INCIDENT_FORM_SUBMIT_RATE_LIMIT_PER_IP_PER_WINDOW",
1509
+ IncidentFormRateLimitBucket.Submit,
1510
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1511
+ return config.perIpLimit;
1512
+ },
1513
+ ],
1514
+ [
1515
+ "INCIDENT_FORM_SUBMIT_RATE_LIMIT_PER_FORM_WINDOW_SECONDS",
1516
+ IncidentFormRateLimitBucket.Submit,
1517
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1518
+ return config.perForm?.windowSeconds;
1519
+ },
1520
+ ],
1521
+ [
1522
+ "INCIDENT_FORM_SUBMIT_RATE_LIMIT_PER_FORM_PER_WINDOW",
1523
+ IncidentFormRateLimitBucket.Submit,
1524
+ (config: IncidentFormRateLimitBucketConfig): unknown => {
1525
+ return config.perForm?.limit;
1526
+ },
1527
+ ],
1528
+ ])(
1529
+ "honours %s",
1530
+ async (
1531
+ envKey: string,
1532
+ bucket: IncidentFormRateLimitBucket,
1533
+ read: (config: IncidentFormRateLimitBucketConfig) => unknown,
1534
+ ) => {
1535
+ process.env[envKey] = "7";
1536
+
1537
+ const { limiter } = await reload();
1538
+
1539
+ expect(read(limiter.getBucketConfig(bucket))).toBe(7);
1540
+ },
1541
+ );
1542
+
1543
+ it.each([["not-a-number"], ["0"], ["-5"], [""]])(
1544
+ "falls back to the default for %j",
1545
+ async (value: string) => {
1546
+ process.env["INCIDENT_FORM_SUBMIT_RATE_LIMIT_PER_FORM_PER_WINDOW"] =
1547
+ value;
1548
+ process.env["INCIDENT_FORM_RATE_LIMIT_PER_FORM_AND_IP_PER_WINDOW"] =
1549
+ value;
1550
+
1551
+ const { limiter } = await reload();
1552
+
1553
+ expect(
1554
+ limiter.getBucketConfig(IncidentFormRateLimitBucket.Submit).perForm
1555
+ ?.limit,
1556
+ ).toBe(SUBMIT_PER_FORM_LIMIT);
1557
+ expect(
1558
+ limiter.getBucketConfig(IncidentFormRateLimitBucket.Read)
1559
+ .perFormAndIpLimit,
1560
+ ).toBe(READ_PER_FORM_AND_IP_LIMIT);
1561
+ },
1562
+ );
1563
+
1564
+ it("enforces a configured per-form ceiling", async () => {
1565
+ process.env["INCIDENT_FORM_SUBMIT_RATE_LIMIT_PER_FORM_PER_WINDOW"] = "3";
1566
+
1567
+ const { limiter } = await reload();
1568
+
1569
+ for (let i: number = 0; i < 3; i++) {
1570
+ expect(
1571
+ (await limiter.consumeFormCeiling({ formKey: FORM_KEY })).outcome,
1572
+ ).toBe(IncidentFormRateLimitOutcome.Allowed);
1573
+ }
1574
+
1575
+ const decision: IncidentFormRateLimitDecision =
1576
+ await limiter.consumeFormCeiling({ formKey: FORM_KEY });
1577
+
1578
+ expect(decision.outcome).toBe(IncidentFormRateLimitOutcome.RateLimited);
1579
+ expect(decision.scope).toBe(IncidentFormRateLimitScope.Form);
1580
+ });
1581
+
1582
+ it("enforces a configured per-address read ceiling", async () => {
1583
+ process.env["INCIDENT_FORM_RATE_LIMIT_PER_IP_PER_WINDOW"] = "4";
1584
+
1585
+ const { limiter } = await reload();
1586
+
1587
+ const outcomes: Array<IncidentFormRateLimitScope | undefined> = [];
1588
+
1589
+ for (let i: number = 0; i < 6; i++) {
1590
+ outcomes.push(
1591
+ (
1592
+ await limiter.consume({
1593
+ formKey: `k:rotating-${i}`,
1594
+ clientIp: CLIENT_IP,
1595
+ bucket: IncidentFormRateLimitBucket.Read,
1596
+ })
1597
+ ).scope,
1598
+ );
1599
+ }
1600
+
1601
+ expect(outcomes).toEqual([
1602
+ undefined,
1603
+ undefined,
1604
+ undefined,
1605
+ undefined,
1606
+ IncidentFormRateLimitScope.Ip,
1607
+ IncidentFormRateLimitScope.Ip,
1608
+ ]);
1609
+ });
1610
+
1611
+ /*
1612
+ * Behind an extra load balancer the operator widens the instance-wide
1613
+ * TRUSTED_PROXY_HOPS, which the form's IP allowlist also reads. The
1614
+ * limiter has no private knob that could leave it reading the balancer's
1615
+ * address while the allowlist reads the visitor's.
1616
+ */
1617
+ it("counts back the instance-wide trusted hop count", async () => {
1618
+ process.env["TRUSTED_PROXY_HOPS"] = "2";
1619
+
1620
+ const { limiter } = await reload();
1621
+
1622
+ expect(
1623
+ limiter.resolveClientIp(
1624
+ buildRequest({
1625
+ headers: { "x-forwarded-for": "203.0.113.7, 10.0.0.5, 10.0.0.6" },
1626
+ }),
1627
+ ),
1628
+ ).toBe("10.0.0.5");
1629
+ });
1630
+ });