@oneuptime/common 11.7.4 → 12.0.0

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 (872) hide show
  1. package/Models/AnalyticsModels/ExceptionInstance.ts +67 -0
  2. package/Models/AnalyticsModels/Index.ts +10 -0
  3. package/Models/AnalyticsModels/Log.ts +60 -0
  4. package/Models/AnalyticsModels/RumSession.ts +1401 -0
  5. package/Models/AnalyticsModels/RumSessionChunk.ts +798 -0
  6. package/Models/AnalyticsModels/Span.ts +60 -0
  7. package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +52 -0
  8. package/Models/DatabaseModels/AIRun.ts +31 -0
  9. package/Models/DatabaseModels/Alert.ts +29 -0
  10. package/Models/DatabaseModels/AlertCustomField.ts +1 -1
  11. package/Models/DatabaseModels/AlertFeed.ts +1 -0
  12. package/Models/DatabaseModels/AutoRemediationRule.ts +989 -0
  13. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +874 -0
  14. package/Models/DatabaseModels/CodeRepository.ts +129 -0
  15. package/Models/DatabaseModels/GlobalConfig.ts +312 -0
  16. package/Models/DatabaseModels/Incident.ts +29 -0
  17. package/Models/DatabaseModels/IncidentCustomField.ts +1 -1
  18. package/Models/DatabaseModels/IncidentFeed.ts +1 -0
  19. package/Models/DatabaseModels/Index.ts +21 -8
  20. package/Models/DatabaseModels/InstanceHealthLog.ts +8 -0
  21. package/Models/DatabaseModels/MonitorCustomField.ts +1 -1
  22. package/Models/DatabaseModels/MonitorStatus.ts +1 -1
  23. package/Models/DatabaseModels/NetworkDeviceLabelRule.ts +2 -2
  24. package/Models/DatabaseModels/NetworkDeviceOwnerRule.ts +2 -2
  25. package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +1 -1
  26. package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +1 -1
  27. package/Models/DatabaseModels/Project.ts +231 -0
  28. package/Models/DatabaseModels/RumApplication.ts +906 -0
  29. package/Models/DatabaseModels/RumSessionErasureRequest.ts +561 -0
  30. package/Models/DatabaseModels/RumSessionPin.ts +457 -0
  31. package/Models/DatabaseModels/RumSessionReplayView.ts +406 -0
  32. package/Models/DatabaseModels/RunbookCredential.ts +673 -0
  33. package/Models/DatabaseModels/RunbookSecret.ts +7 -7
  34. package/Models/DatabaseModels/{RunbookAgent.ts → Runner.ts} +206 -46
  35. package/Models/DatabaseModels/{RunbookAgentJob.ts → RunnerJob.ts} +151 -18
  36. package/Models/DatabaseModels/{RunbookAgentOwnerTeam.ts → RunnerOwnerTeam.ts} +41 -43
  37. package/Models/DatabaseModels/{RunbookAgentOwnerUser.ts → RunnerOwnerUser.ts} +41 -43
  38. package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +1 -1
  39. package/Models/DatabaseModels/StatusPageCustomField.ts +1 -1
  40. package/Models/DatabaseModels/TeamCustomField.ts +1 -1
  41. package/Models/DatabaseModels/TeamMemberCustomField.ts +1 -1
  42. package/Server/API/AIAgentDataAPI.ts +179 -48
  43. package/Server/API/AIAgentTaskAPI.ts +36 -37
  44. package/Server/API/AIAgentTaskLogAPI.ts +19 -33
  45. package/Server/API/AutoRemediationAPI.ts +557 -0
  46. package/Server/API/BaseAnalyticsAPI.ts +4 -1
  47. package/Server/API/CommonAPI.ts +35 -0
  48. package/Server/API/DashboardAPI.ts +340 -15
  49. package/Server/API/TelemetryAPI.ts +1622 -3
  50. package/Server/API/UserIncomingCallNumberAPI.ts +31 -0
  51. package/Server/DatabaseConfig.ts +26 -1
  52. package/Server/EnvironmentConfig.ts +13 -0
  53. package/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.ts +293 -0
  54. package/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.ts +67 -0
  55. package/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.ts +33 -0
  56. package/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.ts +40 -0
  57. package/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.ts +54 -0
  58. package/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.ts +104 -0
  59. package/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.ts +349 -0
  60. package/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.ts +67 -0
  61. package/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.ts +23 -0
  62. package/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.ts +42 -0
  63. package/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.ts +84 -0
  64. package/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.ts +152 -0
  65. package/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.ts +81 -0
  66. package/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.ts +151 -0
  67. package/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.ts +597 -0
  68. package/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.ts +71 -0
  69. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +32 -0
  70. package/Server/Services/AIRunService.ts +10 -0
  71. package/Server/Services/AIService.ts +30 -0
  72. package/Server/Services/AlertService.ts +41 -4
  73. package/Server/Services/AlertSeverityService.ts +25 -11
  74. package/Server/Services/AlertStateService.ts +25 -11
  75. package/Server/Services/AutoRemediationRuleEngineService.ts +1101 -0
  76. package/Server/Services/{RunbookAgentOwnerTeamService.ts → AutoRemediationRuleService.ts} +1 -1
  77. package/Server/Services/AutoRemediationSuggestionService.ts +183 -0
  78. package/Server/Services/BillingService.ts +50 -1
  79. package/Server/Services/DatabaseService.ts +92 -4
  80. package/Server/Services/IncidentService.ts +56 -4
  81. package/Server/Services/IncidentSeverityService.ts +25 -11
  82. package/Server/Services/IncidentStateService.ts +25 -11
  83. package/Server/Services/Index.ts +19 -0
  84. package/Server/Services/MonitorService.ts +24 -0
  85. package/Server/Services/MonitorStatusService.ts +25 -11
  86. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +18 -19
  87. package/Server/Services/NetworkDeviceLabelRuleService.ts +39 -0
  88. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +18 -19
  89. package/Server/Services/NetworkDeviceOwnerRuleService.ts +37 -0
  90. package/Server/Services/NetworkDeviceService.ts +172 -21
  91. package/Server/Services/NetworkSiteAssignmentRuleService.ts +92 -12
  92. package/Server/Services/NetworkSiteService.ts +27 -10
  93. package/Server/Services/ProjectService.ts +47 -0
  94. package/Server/Services/RumApplicationService.ts +240 -1
  95. package/Server/Services/RumSessionChunkService.ts +11 -0
  96. package/Server/Services/RumSessionErasureRequestService.ts +197 -0
  97. package/Server/Services/RumSessionPinService.ts +213 -0
  98. package/Server/Services/RumSessionReplayViewService.ts +201 -0
  99. package/Server/Services/RumSessionService.ts +123 -0
  100. package/Server/Services/RunbookCredentialService.ts +76 -0
  101. package/Server/Services/{RunbookAgentJobService.ts → RunnerJobService.ts} +202 -51
  102. package/Server/Services/{RunbookAgentOwnerUserService.ts → RunnerOwnerTeamService.ts} +1 -1
  103. package/Server/Services/RunnerOwnerUserService.ts +10 -0
  104. package/Server/Services/RunnerService.ts +172 -0
  105. package/Server/Services/ScheduledMaintenanceStateService.ts +25 -11
  106. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +15 -1
  107. package/Server/Services/ServiceService.ts +8 -2
  108. package/Server/Services/StatusPageService.ts +240 -36
  109. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +20 -1
  110. package/Server/Services/TelemetryExceptionService.ts +3 -16
  111. package/Server/Services/TelemetryUsageBillingService.ts +33 -2
  112. package/Server/Services/TraceAggregationService.ts +196 -32
  113. package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +17 -0
  114. package/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.ts +10 -0
  115. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +63 -7
  116. package/Server/Utils/AI/CodeFix/CodeFixAgentAuth.ts +128 -0
  117. package/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.ts +54 -0
  118. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +17 -10
  119. package/Server/Utils/AI/CodeFix/CodeFixRunQueue.ts +8 -6
  120. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +943 -0
  121. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +866 -0
  122. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +823 -0
  123. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +103 -12
  124. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +75 -17
  125. package/Server/Utils/AI/SRE/ConfidenceSignal.ts +28 -6
  126. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +202 -12
  127. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +264 -14
  128. package/Server/Utils/AI/SRE/Insights/Triage.ts +2 -7
  129. package/Server/Utils/AI/SRE/InvestigationGrader.ts +5 -24
  130. package/Server/Utils/AI/SRE/InvestigationQueue.ts +259 -20
  131. package/Server/Utils/AI/SRE/PostedRootCause.ts +82 -0
  132. package/Server/Utils/AI/SRE/README.md +3 -2
  133. package/Server/Utils/AI/SRE/RemediationHandoff.ts +82 -0
  134. package/Server/Utils/AI/SRE/SubjectCodeFixRun.ts +6 -19
  135. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +2 -2
  136. package/Server/Utils/Alert/AlertPrivacyFilter.ts +58 -0
  137. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +604 -0
  138. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +619 -0
  139. package/Server/Utils/CorsOptions.ts +52 -0
  140. package/Server/Utils/Database/RelationIdUtil.ts +68 -0
  141. package/Server/Utils/Incident/IncidentPrivacyFilter.ts +60 -0
  142. package/Server/Utils/InstanceHealth/PostgresHealth.ts +286 -0
  143. package/Server/Utils/InstanceHealth/RedisHealth.ts +434 -0
  144. package/Server/Utils/Monitor/MonitorAlert.ts +20 -0
  145. package/Server/Utils/Monitor/MonitorIncident.ts +20 -0
  146. package/Server/Utils/Monitor/MonitorResource.ts +19 -0
  147. package/Server/Utils/Monitor/MonitorSummaryCapture.ts +104 -0
  148. package/Server/Utils/NetworkDevice/RulePatternValidator.ts +44 -0
  149. package/Server/Utils/OpenAPI.ts +2 -1
  150. package/Server/Utils/Runbook/RunbookExecutePermission.ts +94 -0
  151. package/Server/Utils/SessionReplay/SessionReplayErasureTombstone.ts +128 -0
  152. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +586 -0
  153. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +188 -0
  154. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1449 -0
  155. package/Server/Utils/SessionReplay/SessionReplayRetention.ts +114 -0
  156. package/Server/Utils/SessionReplay/SessionReplayTargeting.ts +144 -0
  157. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +92 -0
  158. package/Server/Utils/StartServer.ts +47 -3
  159. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +44 -10
  160. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +557 -0
  161. package/Tests/App/Dashboard/WidgetCatalog.test.ts +479 -0
  162. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +343 -0
  163. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +320 -0
  164. package/Tests/Models/AnalyticsModels/RumSessionSchema.test.ts +455 -0
  165. package/Tests/Models/AnalyticsModels/SessionIdCorrelation.test.ts +158 -0
  166. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +447 -0
  167. package/Tests/Models/RunnerVersionLabel.test.ts +223 -0
  168. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +1287 -0
  169. package/Tests/Server/API/LegacyRunbookAgentRoutes.test.ts +117 -0
  170. package/Tests/Server/API/SessionReplayAPI.test.ts +2093 -0
  171. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +429 -0
  172. package/Tests/Server/API/UserSmsApi.test.ts +68 -2
  173. package/Tests/Server/DatabaseConfig.test.ts +150 -0
  174. package/Tests/Server/Infrastructure/ContainerBootConfiguration.test.ts +2 -2
  175. package/Tests/Server/Infrastructure/ProcessSignalDelivery.test.ts +2 -3
  176. package/Tests/Server/Services/AIRunCodeFixClaimScoping.test.ts +100 -0
  177. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +8 -0
  178. package/Tests/Server/Services/AutoRemediationCommandRuleEngine.test.ts +334 -0
  179. package/Tests/Server/Services/AutoRemediationRuleEngineService.test.ts +517 -0
  180. package/Tests/Server/Services/BillingService.test.ts +158 -1
  181. package/Tests/Server/Services/DatabaseServiceEmptyUpdate.test.ts +24 -2
  182. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +17 -10
  183. package/Tests/Server/Services/DatabaseServiceUpdateColumnsWithoutHooks.test.ts +128 -0
  184. package/Tests/Server/Services/DatabaseServiceUpdateWriteRouting.test.ts +178 -0
  185. package/Tests/Server/Services/DatabaseServiceUpdateWriteRoutingMonitor.test.ts +206 -0
  186. package/Tests/Server/Services/MonitorServiceOperationalStatusSelection.test.ts +145 -0
  187. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +726 -0
  188. package/Tests/Server/Services/NetworkDeviceSiteAssignment.test.ts +570 -9
  189. package/Tests/Server/Services/NetworkSiteAssignmentRuleService.test.ts +94 -1
  190. package/Tests/Server/Services/NetworkSiteService.test.ts +85 -0
  191. package/Tests/Server/Services/RumApplicationReplayHealth.test.ts +114 -0
  192. package/Tests/Server/Services/RunbookCredentialService.test.ts +657 -0
  193. package/Tests/Server/Services/SessionReplayCreatePath.test.ts +376 -0
  194. package/Tests/Server/Services/SessionReplayGateCacheInvalidation.test.ts +211 -0
  195. package/Tests/Server/Services/SessionReplayMetering.test.ts +174 -0
  196. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +555 -0
  197. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +25 -4
  198. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +17 -2
  199. package/Tests/Server/Services/TraceAggregationService.test.ts +48 -0
  200. package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +855 -0
  201. package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +38 -27
  202. package/Tests/Server/Utils/AI/AIConfidenceSignal.test.ts +44 -0
  203. package/Tests/Server/Utils/AI/AIIncidentGating.test.ts +475 -0
  204. package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAuth.test.ts +257 -0
  205. package/Tests/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.test.ts +236 -0
  206. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +44 -6
  207. package/Tests/Server/Utils/AI/CodeFixRunQueue.test.ts +47 -1
  208. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +2 -2
  209. package/Tests/Server/Utils/AI/FixFromIncidentAutoTrigger.test.ts +294 -0
  210. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +2 -2
  211. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +47 -3
  212. package/Tests/Server/Utils/AI/InvestigationQueueRemediationExecution.test.ts +233 -0
  213. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +463 -0
  214. package/Tests/Server/Utils/AI/PostedRootCause.test.ts +254 -0
  215. package/Tests/Server/Utils/AI/RemediationAllowlistNormalization.test.ts +248 -0
  216. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +1008 -0
  217. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +887 -0
  218. package/Tests/Server/Utils/AI/RemediationHandoff.test.ts +306 -0
  219. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +745 -0
  220. package/Tests/Server/Utils/AI/SubjectCodeFixRunDedupe.test.ts +291 -0
  221. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutor.test.ts +1050 -0
  222. package/Tests/Server/Utils/AutoRemediation/CommandPlanVerification.test.ts +350 -0
  223. package/Tests/Server/Utils/AutoRemediation/RemediationVerifier.test.ts +341 -0
  224. package/Tests/Server/Utils/CorsOptions.test.ts +119 -0
  225. package/Tests/Server/Utils/Database/RelationIdUtil.test.ts +120 -0
  226. package/Tests/Server/Utils/Database/ResolveReferenceId.test.ts +60 -0
  227. package/Tests/Server/Utils/InstanceHealth/PostgresHealth.test.ts +337 -0
  228. package/Tests/Server/Utils/InstanceHealth/RedisHealth.test.ts +671 -0
  229. package/Tests/Server/Utils/Monitor/MonitorSummaryCapture.test.ts +205 -0
  230. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +313 -0
  231. package/Tests/Server/Utils/PushNotificationUtil.test.ts +228 -0
  232. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +177 -0
  233. package/Tests/Server/Utils/SessionReplay/SessionReplayOriginAllowlist.test.ts +108 -0
  234. package/Tests/Server/Utils/SessionReplay/SessionReplayRetention.test.ts +229 -0
  235. package/Tests/Server/Utils/SessionReplay/SessionReplayTargeting.test.ts +246 -0
  236. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +110 -0
  237. package/Tests/Types/AI/AIRunStatus.test.ts +25 -0
  238. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +1063 -0
  239. package/Tests/Types/BaseDatabase/AggregationType.test.ts +56 -0
  240. package/Tests/Types/CustomField/CustomFieldDropdownOption.test.ts +227 -0
  241. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +811 -0
  242. package/Tests/Types/Dashboard/RumDashboardTemplate.test.ts +1238 -0
  243. package/Tests/Types/Day/DayOfWeek.test.ts +46 -0
  244. package/Tests/Types/Docker/DockerInventoryExtractor.test.ts +249 -0
  245. package/Tests/Types/Kubernetes/KubernetesInventoryExtractor.test.ts +231 -0
  246. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +323 -0
  247. package/Tests/Types/Metrics/MetricDownsamplingRetentionDays.test.ts +77 -0
  248. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +42 -14
  249. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +2 -2
  250. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +3 -2
  251. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +1 -1
  252. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +6 -3
  253. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +285 -0
  254. package/Tests/Types/Podman/PodmanInventoryExtractor.test.ts +159 -0
  255. package/Tests/Types/Runbook/RunbookExecutionDeadline.test.ts +268 -0
  256. package/Tests/Types/Runbook/RunbookStepType.test.ts +242 -0
  257. package/Tests/Types/Telemetry/TelemetryRetentionConfig.test.ts +225 -0
  258. package/Tests/Types/Text.test.ts +60 -0
  259. package/Tests/UI/Components/AutocompleteTextInputMenu.test.tsx +555 -0
  260. package/Tests/UI/Components/BulkLabelActions.test.tsx +1502 -0
  261. package/Tests/UI/Components/BulkOwnerActions.test.tsx +1255 -0
  262. package/Tests/UI/Components/CustomFields/DropdownOptionsInput.test.tsx +228 -0
  263. package/Tests/UI/Components/Detail/DropdownColors.test.tsx +128 -0
  264. package/Tests/UI/Components/Dropdown/DropdownValueBadge.test.tsx +60 -0
  265. package/Tests/UI/Components/FilterModalOperatorMenu.test.tsx +674 -0
  266. package/Tests/UI/Components/Forms/ColorPicker.test.tsx +48 -0
  267. package/Tests/UI/Components/Forms/ColorPickerPopup.test.tsx +222 -0
  268. package/Tests/UI/Components/Forms/IconPickerPopup.test.tsx +270 -0
  269. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +125 -6
  270. package/Tests/UI/Components/OperatorSelectorFilterTypes.test.tsx +1595 -0
  271. package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +851 -0
  272. package/Tests/UI/Components/OperatorSelectorMenu.test.tsx +1229 -0
  273. package/Tests/UI/Components/Pagination.test.tsx +85 -0
  274. package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +248 -0
  275. package/Tests/UI/Rum/ChunkLoader.test.ts +790 -0
  276. package/Tests/UI/Rum/ReplayScrubber.test.tsx +236 -0
  277. package/Tests/UI/Rum/ReplayStage.test.tsx +606 -0
  278. package/Tests/UI/Utils/Timezone.test.ts +86 -0
  279. package/Tests/UI/Utils/UseClockTick.test.tsx +276 -0
  280. package/Tests/Utils/AiRemediation/CommandPolicy.test.ts +1063 -0
  281. package/Tests/Utils/Dashboard/ClockWidgetFormat.test.ts +1374 -0
  282. package/Tests/Utils/Dashboard/DashboardClockComponent.test.ts +362 -0
  283. package/Tests/Utils/Dashboard/DashboardHtmlComponent.test.ts +217 -0
  284. package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +656 -0
  285. package/Tests/Utils/Dashboard/ValueWidgetLayout.test.ts +1007 -0
  286. package/Tests/Utils/Metrics/MetricSeriesScope.test.ts +1197 -0
  287. package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +839 -0
  288. package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +69 -0
  289. package/Tests/Utils/Rules/RulePatternMatchUtil.test.ts +274 -0
  290. package/Tests/Utils/Rum/ChunkMath.test.ts +286 -0
  291. package/Tests/Utils/Rum/Masking.test.ts +186 -0
  292. package/Tests/Utils/Rum/SessionIdentity.test.ts +272 -0
  293. package/Tests/Utils/Rum/SessionSampling.test.ts +164 -0
  294. package/Tests/Utils/Rum/UrlScrubber.test.ts +175 -0
  295. package/Tests/Utils/StatusPage/Report.test.ts +708 -0
  296. package/Tests/Utils/ValueFormatter.test.ts +299 -1
  297. package/Types/AI/AIRunStatus.ts +18 -7
  298. package/Types/AI/AIRunType.ts +18 -0
  299. package/Types/AI/FixVerificationStatus.ts +21 -0
  300. package/Types/AnalyticsDatabase/AnalyticsTableName.ts +16 -0
  301. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +304 -0
  302. package/Types/AutoRemediation/AutoRemediationExecutionMode.ts +17 -0
  303. package/Types/AutoRemediation/AutoRemediationSuggestionStatus.ts +35 -0
  304. package/Types/AutoRemediation/AutoRemediationSuggestionType.ts +17 -0
  305. package/Types/AutoRemediation/AutoRemediationTriggerEntity.ts +11 -0
  306. package/Types/AutoRemediation/AutoRemediationVerificationStatus.ts +21 -0
  307. package/Types/CustomField/CustomFieldDropdownOption.ts +190 -0
  308. package/Types/Dashboard/DashboardComponentType.ts +2 -0
  309. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +4 -0
  310. package/Types/Dashboard/DashboardComponents/DashboardClockComponent.ts +45 -0
  311. package/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.ts +27 -0
  312. package/Types/Dashboard/DashboardComponents/DashboardValueComponent.ts +8 -0
  313. package/Types/Dashboard/DashboardTemplates.ts +516 -0
  314. package/Types/Date.ts +6 -0
  315. package/Types/Email/EmailTemplateType.ts +2 -0
  316. package/Types/Kubernetes/KubernetesRightSizing.ts +353 -0
  317. package/Types/MeteredPlan/ProductType.ts +1 -0
  318. package/Types/Monitor/MonitorStepMetricMonitor.ts +27 -1
  319. package/Types/Monitor/MonitorSteps.ts +5 -0
  320. package/Types/Monitor/MonitorSummarySnapshot.ts +88 -0
  321. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +52 -10
  322. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +7 -5
  323. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +101 -20
  324. package/Types/Monitor/RumAlertTemplates.ts +386 -0
  325. package/Types/Permission.ts +204 -24
  326. package/Types/Rum/SessionReplay.ts +452 -0
  327. package/Types/Rum/SessionReplayCaptureTrigger.ts +26 -0
  328. package/Types/Rum/SessionReplayConsentMode.ts +27 -0
  329. package/Types/Rum/SessionReplayMaskingMode.ts +86 -0
  330. package/Types/Rum/SessionReplayTriggerReason.ts +31 -0
  331. package/Types/Runbook/RunbookCredentialType.ts +6 -0
  332. package/Types/Runbook/RunbookStep.ts +66 -1
  333. package/Types/Runbook/RunbookStepType.ts +26 -0
  334. package/Types/Runbook/RunnerJobOrigin.ts +20 -0
  335. package/Types/Runbook/{RunbookAgentJobStatus.ts → RunnerJobStatus.ts} +2 -2
  336. package/Types/StatusPage/StatusPageReport.ts +99 -0
  337. package/Types/Telemetry/TelemetryRetentionConfig.ts +18 -1
  338. package/Types/Text.ts +33 -0
  339. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +260 -75
  340. package/UI/Components/BulkUpdate/BulkLabelActions.tsx +99 -9
  341. package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +11 -0
  342. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +19 -14
  343. package/UI/Components/CustomFields/DropdownOptionsInput.tsx +144 -54
  344. package/UI/Components/Detail/Detail.tsx +9 -22
  345. package/UI/Components/Dropdown/Dropdown.tsx +2 -5
  346. package/UI/Components/Dropdown/DropdownMenuZIndex.ts +7 -0
  347. package/UI/Components/Dropdown/DropdownValueBadge.tsx +61 -0
  348. package/UI/Components/Filters/OperatorSelector.tsx +350 -39
  349. package/UI/Components/Forms/Fields/ColorPicker.tsx +60 -27
  350. package/UI/Components/Forms/Fields/IconPicker.tsx +89 -60
  351. package/UI/Components/Forms/Validation.ts +9 -4
  352. package/UI/Components/List/List.tsx +1 -0
  353. package/UI/Components/ModelTable/CustomFieldColumns.tsx +41 -32
  354. package/UI/Components/Pagination/Pagination.tsx +33 -10
  355. package/UI/Components/Table/Table.tsx +1 -0
  356. package/UI/Types/UseAnchoredFieldPopup.ts +321 -0
  357. package/UI/Utils/Dropdown.ts +26 -0
  358. package/UI/Utils/Timezone.ts +18 -2
  359. package/UI/Utils/UseClockTick.ts +78 -0
  360. package/Utils/AiRemediation/CommandPolicy.ts +398 -0
  361. package/Utils/Dashboard/ClockWidgetFormat.ts +604 -0
  362. package/Utils/Dashboard/Components/DashboardClockComponent.ts +181 -0
  363. package/Utils/Dashboard/Components/DashboardHtmlComponent.ts +162 -0
  364. package/Utils/Dashboard/Components/DashboardValueComponent.ts +12 -1
  365. package/Utils/Dashboard/Components/Index.ts +14 -0
  366. package/Utils/Dashboard/HtmlWidgetDocument.ts +309 -0
  367. package/Utils/Dashboard/ValueWidgetLayout.ts +471 -0
  368. package/Utils/Metrics/MetricSeriesScope.ts +301 -0
  369. package/Utils/Monitor/MonitorSummarySnapshotUtil.ts +593 -0
  370. package/Utils/NetworkSite/CidrMatchUtil.ts +30 -17
  371. package/Utils/Rules/RulePatternMatchUtil.ts +103 -0
  372. package/Utils/Rum/ChunkMath.ts +211 -0
  373. package/Utils/Rum/Masking.ts +168 -0
  374. package/Utils/Rum/SessionIdentity.ts +230 -0
  375. package/Utils/Rum/SessionSampling.ts +108 -0
  376. package/Utils/Rum/UrlScrubber.ts +185 -0
  377. package/Utils/Schema/ModelSchema.ts +27 -3
  378. package/Utils/StatusPage/Report.ts +304 -0
  379. package/Utils/ValueFormatter.ts +300 -54
  380. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +63 -0
  381. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  382. package/build/dist/Models/AnalyticsModels/Index.js +10 -0
  383. package/build/dist/Models/AnalyticsModels/Index.js.map +1 -1
  384. package/build/dist/Models/AnalyticsModels/Log.js +56 -0
  385. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  386. package/build/dist/Models/AnalyticsModels/RumSession.js +1159 -0
  387. package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -0
  388. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +670 -0
  389. package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -0
  390. package/build/dist/Models/AnalyticsModels/Span.js +56 -0
  391. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  392. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +54 -0
  393. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  394. package/build/dist/Models/DatabaseModels/AIRun.js +32 -0
  395. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  396. package/build/dist/Models/DatabaseModels/Alert.js +30 -0
  397. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  398. package/build/dist/Models/DatabaseModels/AlertCustomField.js +1 -1
  399. package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
  400. package/build/dist/Models/DatabaseModels/AlertFeed.js +1 -0
  401. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  402. package/build/dist/Models/DatabaseModels/AutoRemediationRule.js +998 -0
  403. package/build/dist/Models/DatabaseModels/AutoRemediationRule.js.map +1 -0
  404. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +917 -0
  405. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -0
  406. package/build/dist/Models/DatabaseModels/CodeRepository.js +132 -0
  407. package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
  408. package/build/dist/Models/DatabaseModels/GlobalConfig.js +327 -0
  409. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  410. package/build/dist/Models/DatabaseModels/Incident.js +30 -0
  411. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  412. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +1 -1
  413. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  414. package/build/dist/Models/DatabaseModels/IncidentFeed.js +1 -0
  415. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  416. package/build/dist/Models/DatabaseModels/Index.js +20 -8
  417. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  418. package/build/dist/Models/DatabaseModels/InstanceHealthLog.js +8 -0
  419. package/build/dist/Models/DatabaseModels/InstanceHealthLog.js.map +1 -1
  420. package/build/dist/Models/DatabaseModels/MonitorCustomField.js +1 -1
  421. package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
  422. package/build/dist/Models/DatabaseModels/MonitorStatus.js +1 -1
  423. package/build/dist/Models/DatabaseModels/MonitorStatus.js.map +1 -1
  424. package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js +2 -2
  425. package/build/dist/Models/DatabaseModels/NetworkDeviceLabelRule.js.map +1 -1
  426. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js +2 -2
  427. package/build/dist/Models/DatabaseModels/NetworkDeviceOwnerRule.js.map +1 -1
  428. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +1 -1
  429. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -1
  430. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +1 -1
  431. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
  432. package/build/dist/Models/DatabaseModels/Project.js +235 -0
  433. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  434. package/build/dist/Models/DatabaseModels/RumApplication.js +930 -0
  435. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  436. package/build/dist/Models/DatabaseModels/RumSessionErasureRequest.js +591 -0
  437. package/build/dist/Models/DatabaseModels/RumSessionErasureRequest.js.map +1 -0
  438. package/build/dist/Models/DatabaseModels/RumSessionPin.js +483 -0
  439. package/build/dist/Models/DatabaseModels/RumSessionPin.js.map +1 -0
  440. package/build/dist/Models/DatabaseModels/RumSessionReplayView.js +433 -0
  441. package/build/dist/Models/DatabaseModels/RumSessionReplayView.js.map +1 -0
  442. package/build/dist/Models/DatabaseModels/RunbookCredential.js +693 -0
  443. package/build/dist/Models/DatabaseModels/RunbookCredential.js.map +1 -0
  444. package/build/dist/Models/DatabaseModels/RunbookSecret.js +8 -8
  445. package/build/dist/Models/DatabaseModels/RunbookSecret.js.map +1 -1
  446. package/build/dist/Models/DatabaseModels/{RunbookAgent.js → Runner.js} +235 -70
  447. package/build/dist/Models/DatabaseModels/Runner.js.map +1 -0
  448. package/build/dist/Models/DatabaseModels/{RunbookAgentJob.js → RunnerJob.js} +173 -40
  449. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -0
  450. package/build/dist/Models/DatabaseModels/{RunbookAgentOwnerTeam.js → RunnerOwnerTeam.js} +57 -57
  451. package/build/dist/Models/DatabaseModels/RunnerOwnerTeam.js.map +1 -0
  452. package/build/dist/Models/DatabaseModels/{RunbookAgentOwnerUser.js → RunnerOwnerUser.js} +57 -57
  453. package/build/dist/Models/DatabaseModels/RunnerOwnerUser.js.map +1 -0
  454. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +1 -1
  455. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
  456. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +1 -1
  457. package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
  458. package/build/dist/Models/DatabaseModels/TeamCustomField.js +1 -1
  459. package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
  460. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +1 -1
  461. package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
  462. package/build/dist/Server/API/AIAgentDataAPI.js +130 -45
  463. package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
  464. package/build/dist/Server/API/AIAgentTaskAPI.js +26 -36
  465. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  466. package/build/dist/Server/API/AIAgentTaskLogAPI.js +11 -28
  467. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  468. package/build/dist/Server/API/AutoRemediationAPI.js +416 -0
  469. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -0
  470. package/build/dist/Server/API/BaseAnalyticsAPI.js +4 -1
  471. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  472. package/build/dist/Server/API/CommonAPI.js +24 -0
  473. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  474. package/build/dist/Server/API/DashboardAPI.js +265 -6
  475. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  476. package/build/dist/Server/API/TelemetryAPI.js +940 -3
  477. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  478. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +18 -0
  479. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  480. package/build/dist/Server/DatabaseConfig.js +23 -4
  481. package/build/dist/Server/DatabaseConfig.js.map +1 -1
  482. package/build/dist/Server/EnvironmentConfig.js +8 -0
  483. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  484. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.js +106 -0
  485. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785417351021-AddSessionReplay.js.map +1 -0
  486. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.js +37 -0
  487. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785491583874-EnableSessionReplayByDefault.js.map +1 -0
  488. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.js +18 -0
  489. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785496223183-AddMonitorSummaryToIncidentAndAlert.js.map +1 -0
  490. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.js +29 -0
  491. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785516996695-AddSessionReplayReliabilityColumns.js.map +1 -0
  492. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.js +35 -0
  493. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785533806494-AddSessionReplayCorrelationColumns.js.map +1 -0
  494. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.js +40 -0
  495. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785749674203-AddRedisAndPostgresHealthNotificationColumns.js.map +1 -0
  496. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.js +128 -0
  497. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785763818197-AddAutoRemediation.js.map +1 -0
  498. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.js +28 -0
  499. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785768089408-AddRemediationVerification.js.map +1 -0
  500. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.js +14 -0
  501. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785776960660-AddRunnerCapabilities.js.map +1 -0
  502. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.js +27 -0
  503. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785790000000-AddIncidentInvestigationGating.js.map +1 -0
  504. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.js +44 -0
  505. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785800000000-AddRunbookCredential.js.map +1 -0
  506. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.js +88 -0
  507. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785810000000-RenameRunbookAgentToRunner.js.map +1 -0
  508. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.js +59 -0
  509. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785860000000-SessionReplayDefaultToSensitiveInputMasking.js.map +1 -0
  510. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.js +76 -0
  511. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785870000000-AddAiCommandRemediation.js.map +1 -0
  512. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.js +214 -0
  513. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785880000000-AlignRunnerSchemaWithEntities.js.map +1 -0
  514. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.js +44 -0
  515. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785890000000-AddFixVerificationAndAutomaticCodeFixes.js.map +1 -0
  516. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +32 -0
  517. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  518. package/build/dist/Server/Services/AIRunService.js +1 -4
  519. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  520. package/build/dist/Server/Services/AIService.js +26 -0
  521. package/build/dist/Server/Services/AIService.js.map +1 -1
  522. package/build/dist/Server/Services/AlertService.js +37 -4
  523. package/build/dist/Server/Services/AlertService.js.map +1 -1
  524. package/build/dist/Server/Services/AlertSeverityService.js +24 -11
  525. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  526. package/build/dist/Server/Services/AlertStateService.js +24 -11
  527. package/build/dist/Server/Services/AlertStateService.js.map +1 -1
  528. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +917 -0
  529. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -0
  530. package/build/dist/Server/Services/{RunbookAgentOwnerTeamService.js → AutoRemediationRuleService.js} +2 -2
  531. package/build/dist/Server/Services/AutoRemediationRuleService.js.map +1 -0
  532. package/build/dist/Server/Services/AutoRemediationSuggestionService.js +127 -0
  533. package/build/dist/Server/Services/AutoRemediationSuggestionService.js.map +1 -0
  534. package/build/dist/Server/Services/BillingService.js +45 -1
  535. package/build/dist/Server/Services/BillingService.js.map +1 -1
  536. package/build/dist/Server/Services/DatabaseService.js +72 -4
  537. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  538. package/build/dist/Server/Services/IncidentService.js +52 -4
  539. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  540. package/build/dist/Server/Services/IncidentSeverityService.js +24 -11
  541. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  542. package/build/dist/Server/Services/IncidentStateService.js +24 -11
  543. package/build/dist/Server/Services/IncidentStateService.js.map +1 -1
  544. package/build/dist/Server/Services/Index.js +18 -0
  545. package/build/dist/Server/Services/Index.js.map +1 -1
  546. package/build/dist/Server/Services/MonitorService.js +24 -0
  547. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  548. package/build/dist/Server/Services/MonitorStatusService.js +25 -11
  549. package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
  550. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +12 -11
  551. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  552. package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js +43 -0
  553. package/build/dist/Server/Services/NetworkDeviceLabelRuleService.js.map +1 -1
  554. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +12 -11
  555. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
  556. package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js +41 -0
  557. package/build/dist/Server/Services/NetworkDeviceOwnerRuleService.js.map +1 -1
  558. package/build/dist/Server/Services/NetworkDeviceService.js +129 -20
  559. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  560. package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js +67 -10
  561. package/build/dist/Server/Services/NetworkSiteAssignmentRuleService.js.map +1 -1
  562. package/build/dist/Server/Services/NetworkSiteService.js +19 -8
  563. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  564. package/build/dist/Server/Services/ProjectService.js +43 -0
  565. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  566. package/build/dist/Server/Services/RumApplicationService.js +202 -0
  567. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  568. package/build/dist/Server/Services/RumSessionChunkService.js +9 -0
  569. package/build/dist/Server/Services/RumSessionChunkService.js.map +1 -0
  570. package/build/dist/Server/Services/RumSessionErasureRequestService.js +190 -0
  571. package/build/dist/Server/Services/RumSessionErasureRequestService.js.map +1 -0
  572. package/build/dist/Server/Services/RumSessionPinService.js +208 -0
  573. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -0
  574. package/build/dist/Server/Services/RumSessionReplayViewService.js +180 -0
  575. package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -0
  576. package/build/dist/Server/Services/RumSessionService.js +102 -0
  577. package/build/dist/Server/Services/RumSessionService.js.map +1 -0
  578. package/build/dist/Server/Services/RunbookCredentialService.js +67 -0
  579. package/build/dist/Server/Services/RunbookCredentialService.js.map +1 -0
  580. package/build/dist/Server/Services/{RunbookAgentJobService.js → RunnerJobService.js} +158 -46
  581. package/build/dist/Server/Services/RunnerJobService.js.map +1 -0
  582. package/build/dist/Server/Services/{RunbookAgentOwnerUserService.js → RunnerOwnerTeamService.js} +2 -2
  583. package/build/dist/Server/Services/RunnerOwnerTeamService.js.map +1 -0
  584. package/build/dist/Server/Services/RunnerOwnerUserService.js +9 -0
  585. package/build/dist/Server/Services/RunnerOwnerUserService.js.map +1 -0
  586. package/build/dist/Server/Services/RunnerService.js +168 -0
  587. package/build/dist/Server/Services/RunnerService.js.map +1 -0
  588. package/build/dist/Server/Services/ScheduledMaintenanceStateService.js +24 -11
  589. package/build/dist/Server/Services/ScheduledMaintenanceStateService.js.map +1 -1
  590. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +15 -1
  591. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  592. package/build/dist/Server/Services/ServiceService.js +8 -2
  593. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  594. package/build/dist/Server/Services/StatusPageService.js +173 -16
  595. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  596. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  597. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  598. package/build/dist/Server/Services/TelemetryExceptionService.js +3 -16
  599. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  600. package/build/dist/Server/Services/TelemetryUsageBillingService.js +31 -3
  601. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  602. package/build/dist/Server/Services/TraceAggregationService.js +106 -2
  603. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  604. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +16 -0
  605. package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
  606. package/build/dist/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.js +9 -0
  607. package/build/dist/Server/Types/Billing/MeteredPlan/ServerMeteredPlan.js.map +1 -1
  608. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +38 -6
  609. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  610. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js +104 -0
  611. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAuth.js.map +1 -0
  612. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.js +54 -0
  613. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentAvailability.js.map +1 -0
  614. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +11 -7
  615. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  616. package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js +3 -4
  617. package/build/dist/Server/Utils/AI/CodeFix/CodeFixRunQueue.js.map +1 -1
  618. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +724 -0
  619. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -0
  620. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +668 -0
  621. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -0
  622. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +657 -0
  623. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -0
  624. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +52 -7
  625. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  626. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +53 -5
  627. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  628. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js +25 -6
  629. package/build/dist/Server/Utils/AI/SRE/ConfidenceSignal.js.map +1 -1
  630. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js +131 -17
  631. package/build/dist/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.js.map +1 -1
  632. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +200 -5
  633. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  634. package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js +2 -7
  635. package/build/dist/Server/Utils/AI/SRE/Insights/Triage.js.map +1 -1
  636. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js +4 -17
  637. package/build/dist/Server/Utils/AI/SRE/InvestigationGrader.js.map +1 -1
  638. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +203 -19
  639. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  640. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js +89 -0
  641. package/build/dist/Server/Utils/AI/SRE/PostedRootCause.js.map +1 -0
  642. package/build/dist/Server/Utils/AI/SRE/RemediationHandoff.js +82 -0
  643. package/build/dist/Server/Utils/AI/SRE/RemediationHandoff.js.map +1 -0
  644. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js +4 -19
  645. package/build/dist/Server/Utils/AI/SRE/SubjectCodeFixRun.js.map +1 -1
  646. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +2 -2
  647. package/build/dist/Server/Utils/Alert/AlertPrivacyFilter.js +36 -0
  648. package/build/dist/Server/Utils/Alert/AlertPrivacyFilter.js.map +1 -1
  649. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +498 -0
  650. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -0
  651. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +492 -0
  652. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -0
  653. package/build/dist/Server/Utils/CorsOptions.js +47 -0
  654. package/build/dist/Server/Utils/CorsOptions.js.map +1 -0
  655. package/build/dist/Server/Utils/Database/RelationIdUtil.js +51 -0
  656. package/build/dist/Server/Utils/Database/RelationIdUtil.js.map +1 -0
  657. package/build/dist/Server/Utils/Incident/IncidentPrivacyFilter.js +38 -0
  658. package/build/dist/Server/Utils/Incident/IncidentPrivacyFilter.js.map +1 -1
  659. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js +194 -0
  660. package/build/dist/Server/Utils/InstanceHealth/PostgresHealth.js.map +1 -0
  661. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js +292 -0
  662. package/build/dist/Server/Utils/InstanceHealth/RedisHealth.js.map +1 -0
  663. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +9 -0
  664. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  665. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +9 -0
  666. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  667. package/build/dist/Server/Utils/Monitor/MonitorResource.js +16 -0
  668. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  669. package/build/dist/Server/Utils/Monitor/MonitorSummaryCapture.js +92 -0
  670. package/build/dist/Server/Utils/Monitor/MonitorSummaryCapture.js.map +1 -0
  671. package/build/dist/Server/Utils/NetworkDevice/RulePatternValidator.js +27 -0
  672. package/build/dist/Server/Utils/NetworkDevice/RulePatternValidator.js.map +1 -0
  673. package/build/dist/Server/Utils/OpenAPI.js +2 -1
  674. package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
  675. package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js +67 -0
  676. package/build/dist/Server/Utils/Runbook/RunbookExecutePermission.js.map +1 -0
  677. package/build/dist/Server/Utils/SessionReplay/SessionReplayErasureTombstone.js +98 -0
  678. package/build/dist/Server/Utils/SessionReplay/SessionReplayErasureTombstone.js.map +1 -0
  679. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +378 -0
  680. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -0
  681. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +137 -0
  682. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -0
  683. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +998 -0
  684. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -0
  685. package/build/dist/Server/Utils/SessionReplay/SessionReplayRetention.js +59 -0
  686. package/build/dist/Server/Utils/SessionReplay/SessionReplayRetention.js.map +1 -0
  687. package/build/dist/Server/Utils/SessionReplay/SessionReplayTargeting.js +98 -0
  688. package/build/dist/Server/Utils/SessionReplay/SessionReplayTargeting.js.map +1 -0
  689. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +69 -0
  690. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -0
  691. package/build/dist/Server/Utils/StartServer.js +40 -3
  692. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  693. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +27 -6
  694. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
  695. package/build/dist/Types/AI/AIRunStatus.js +17 -5
  696. package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
  697. package/build/dist/Types/AI/AIRunType.js +18 -0
  698. package/build/dist/Types/AI/AIRunType.js.map +1 -1
  699. package/build/dist/Types/AI/FixVerificationStatus.js +22 -0
  700. package/build/dist/Types/AI/FixVerificationStatus.js.map +1 -0
  701. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +16 -0
  702. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
  703. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +192 -0
  704. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -0
  705. package/build/dist/Types/AutoRemediation/AutoRemediationExecutionMode.js +18 -0
  706. package/build/dist/Types/AutoRemediation/AutoRemediationExecutionMode.js.map +1 -0
  707. package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionStatus.js +31 -0
  708. package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionStatus.js.map +1 -0
  709. package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionType.js +18 -0
  710. package/build/dist/Types/AutoRemediation/AutoRemediationSuggestionType.js.map +1 -0
  711. package/build/dist/Types/AutoRemediation/AutoRemediationTriggerEntity.js +12 -0
  712. package/build/dist/Types/AutoRemediation/AutoRemediationTriggerEntity.js.map +1 -0
  713. package/build/dist/Types/AutoRemediation/AutoRemediationVerificationStatus.js +22 -0
  714. package/build/dist/Types/AutoRemediation/AutoRemediationVerificationStatus.js.map +1 -0
  715. package/build/dist/Types/CustomField/CustomFieldDropdownOption.js +110 -0
  716. package/build/dist/Types/CustomField/CustomFieldDropdownOption.js.map +1 -0
  717. package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
  718. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  719. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +4 -0
  720. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  721. package/build/dist/Types/Dashboard/DashboardComponents/DashboardClockComponent.js +19 -0
  722. package/build/dist/Types/Dashboard/DashboardComponents/DashboardClockComponent.js.map +1 -0
  723. package/build/dist/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.js +2 -0
  724. package/build/dist/Types/Dashboard/DashboardComponents/DashboardHtmlComponent.js.map +1 -0
  725. package/build/dist/Types/Dashboard/DashboardTemplates.js +487 -0
  726. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  727. package/build/dist/Types/Date.js +6 -0
  728. package/build/dist/Types/Date.js.map +1 -1
  729. package/build/dist/Types/Email/EmailTemplateType.js +2 -0
  730. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  731. package/build/dist/Types/Kubernetes/KubernetesRightSizing.js +229 -0
  732. package/build/dist/Types/Kubernetes/KubernetesRightSizing.js.map +1 -0
  733. package/build/dist/Types/MeteredPlan/ProductType.js +1 -0
  734. package/build/dist/Types/MeteredPlan/ProductType.js.map +1 -1
  735. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +12 -2
  736. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  737. package/build/dist/Types/Monitor/MonitorSteps.js +5 -0
  738. package/build/dist/Types/Monitor/MonitorSteps.js.map +1 -1
  739. package/build/dist/Types/Monitor/MonitorSummarySnapshot.js +13 -0
  740. package/build/dist/Types/Monitor/MonitorSummarySnapshot.js.map +1 -0
  741. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +40 -8
  742. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  743. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +4 -3
  744. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  745. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +50 -10
  746. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  747. package/build/dist/Types/Monitor/RumAlertTemplates.js +297 -0
  748. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -0
  749. package/build/dist/Types/Permission.js +184 -24
  750. package/build/dist/Types/Permission.js.map +1 -1
  751. package/build/dist/Types/Rum/SessionReplay.js +155 -0
  752. package/build/dist/Types/Rum/SessionReplay.js.map +1 -0
  753. package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +26 -0
  754. package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -0
  755. package/build/dist/Types/Rum/SessionReplayConsentMode.js +27 -0
  756. package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -0
  757. package/build/dist/Types/Rum/SessionReplayMaskingMode.js +79 -0
  758. package/build/dist/Types/Rum/SessionReplayMaskingMode.js.map +1 -0
  759. package/build/dist/Types/Rum/SessionReplayTriggerReason.js +28 -0
  760. package/build/dist/Types/Rum/SessionReplayTriggerReason.js.map +1 -0
  761. package/build/dist/Types/Runbook/RunbookCredentialType.js +7 -0
  762. package/build/dist/Types/Runbook/RunbookCredentialType.js.map +1 -0
  763. package/build/dist/Types/Runbook/RunbookStep.js +20 -1
  764. package/build/dist/Types/Runbook/RunbookStep.js.map +1 -1
  765. package/build/dist/Types/Runbook/RunbookStepType.js +24 -0
  766. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  767. package/build/dist/Types/Runbook/RunnerJobOrigin.js +21 -0
  768. package/build/dist/Types/Runbook/RunnerJobOrigin.js.map +1 -0
  769. package/build/dist/Types/Runbook/RunnerJobStatus.js +22 -0
  770. package/build/dist/Types/Runbook/RunnerJobStatus.js.map +1 -0
  771. package/build/dist/Types/StatusPage/StatusPageReport.js +22 -0
  772. package/build/dist/Types/StatusPage/StatusPageReport.js.map +1 -0
  773. package/build/dist/Types/Telemetry/TelemetryRetentionConfig.js.map +1 -1
  774. package/build/dist/Types/Text.js +29 -0
  775. package/build/dist/Types/Text.js.map +1 -1
  776. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +143 -28
  777. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  778. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js +82 -9
  779. package/build/dist/UI/Components/BulkUpdate/BulkLabelActions.js.map +1 -1
  780. package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +11 -0
  781. package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -1
  782. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +11 -13
  783. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  784. package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +76 -45
  785. package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -1
  786. package/build/dist/UI/Components/Detail/Detail.js +3 -8
  787. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  788. package/build/dist/UI/Components/Dropdown/Dropdown.js +2 -5
  789. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  790. package/build/dist/UI/Components/Dropdown/DropdownMenuZIndex.js +7 -0
  791. package/build/dist/UI/Components/Dropdown/DropdownMenuZIndex.js.map +1 -0
  792. package/build/dist/UI/Components/Dropdown/DropdownValueBadge.js +32 -0
  793. package/build/dist/UI/Components/Dropdown/DropdownValueBadge.js.map +1 -0
  794. package/build/dist/UI/Components/Filters/OperatorSelector.js +210 -20
  795. package/build/dist/UI/Components/Filters/OperatorSelector.js.map +1 -1
  796. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +26 -10
  797. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
  798. package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -13
  799. package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
  800. package/build/dist/UI/Components/Forms/Validation.js +9 -5
  801. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  802. package/build/dist/UI/Components/List/List.js +1 -1
  803. package/build/dist/UI/Components/List/List.js.map +1 -1
  804. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +21 -19
  805. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  806. package/build/dist/UI/Components/Pagination/Pagination.js +14 -3
  807. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  808. package/build/dist/UI/Components/Table/Table.js +1 -1
  809. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  810. package/build/dist/UI/Types/UseAnchoredFieldPopup.js +197 -0
  811. package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -0
  812. package/build/dist/UI/Utils/Dropdown.js +18 -0
  813. package/build/dist/UI/Utils/Dropdown.js.map +1 -1
  814. package/build/dist/UI/Utils/Timezone.js +13 -2
  815. package/build/dist/UI/Utils/Timezone.js.map +1 -1
  816. package/build/dist/UI/Utils/UseClockTick.js +41 -0
  817. package/build/dist/UI/Utils/UseClockTick.js.map +1 -0
  818. package/build/dist/Utils/AiRemediation/CommandPolicy.js +303 -0
  819. package/build/dist/Utils/AiRemediation/CommandPolicy.js.map +1 -0
  820. package/build/dist/Utils/Dashboard/ClockWidgetFormat.js +232 -0
  821. package/build/dist/Utils/Dashboard/ClockWidgetFormat.js.map +1 -0
  822. package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js +146 -0
  823. package/build/dist/Utils/Dashboard/Components/DashboardClockComponent.js.map +1 -0
  824. package/build/dist/Utils/Dashboard/Components/DashboardHtmlComponent.js +133 -0
  825. package/build/dist/Utils/Dashboard/Components/DashboardHtmlComponent.js.map +1 -0
  826. package/build/dist/Utils/Dashboard/Components/DashboardValueComponent.js +10 -1
  827. package/build/dist/Utils/Dashboard/Components/DashboardValueComponent.js.map +1 -1
  828. package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
  829. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  830. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +198 -0
  831. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -0
  832. package/build/dist/Utils/Dashboard/ValueWidgetLayout.js +276 -0
  833. package/build/dist/Utils/Dashboard/ValueWidgetLayout.js.map +1 -0
  834. package/build/dist/Utils/Metrics/MetricSeriesScope.js +203 -0
  835. package/build/dist/Utils/Metrics/MetricSeriesScope.js.map +1 -0
  836. package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js +382 -0
  837. package/build/dist/Utils/Monitor/MonitorSummarySnapshotUtil.js.map +1 -0
  838. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +13 -7
  839. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -1
  840. package/build/dist/Utils/Rules/RulePatternMatchUtil.js +87 -0
  841. package/build/dist/Utils/Rules/RulePatternMatchUtil.js.map +1 -0
  842. package/build/dist/Utils/Rum/ChunkMath.js +147 -0
  843. package/build/dist/Utils/Rum/ChunkMath.js.map +1 -0
  844. package/build/dist/Utils/Rum/Masking.js +143 -0
  845. package/build/dist/Utils/Rum/Masking.js.map +1 -0
  846. package/build/dist/Utils/Rum/SessionIdentity.js +161 -0
  847. package/build/dist/Utils/Rum/SessionIdentity.js.map +1 -0
  848. package/build/dist/Utils/Rum/SessionSampling.js +91 -0
  849. package/build/dist/Utils/Rum/SessionSampling.js.map +1 -0
  850. package/build/dist/Utils/Rum/UrlScrubber.js +144 -0
  851. package/build/dist/Utils/Rum/UrlScrubber.js.map +1 -0
  852. package/build/dist/Utils/Schema/ModelSchema.js +28 -3
  853. package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
  854. package/build/dist/Utils/StatusPage/Report.js +194 -0
  855. package/build/dist/Utils/StatusPage/Report.js.map +1 -0
  856. package/build/dist/Utils/ValueFormatter.js +239 -46
  857. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  858. package/jest.config.json +3 -0
  859. package/package.json +1 -1
  860. package/tsconfig.json +19 -1
  861. package/Server/Services/RunbookAgentService.ts +0 -93
  862. package/build/dist/Models/DatabaseModels/RunbookAgent.js.map +0 -1
  863. package/build/dist/Models/DatabaseModels/RunbookAgentJob.js.map +0 -1
  864. package/build/dist/Models/DatabaseModels/RunbookAgentOwnerTeam.js.map +0 -1
  865. package/build/dist/Models/DatabaseModels/RunbookAgentOwnerUser.js.map +0 -1
  866. package/build/dist/Server/Services/RunbookAgentJobService.js.map +0 -1
  867. package/build/dist/Server/Services/RunbookAgentOwnerTeamService.js.map +0 -1
  868. package/build/dist/Server/Services/RunbookAgentOwnerUserService.js.map +0 -1
  869. package/build/dist/Server/Services/RunbookAgentService.js +0 -91
  870. package/build/dist/Server/Services/RunbookAgentService.js.map +0 -1
  871. package/build/dist/Types/Runbook/RunbookAgentJobStatus.js +0 -22
  872. package/build/dist/Types/Runbook/RunbookAgentJobStatus.js.map +0 -1
@@ -0,0 +1,2093 @@
1
+ import CommonAPI from "../../../Server/API/CommonAPI";
2
+ import UserMiddleware from "../../../Server/Middleware/UserAuthorization";
3
+ import ProjectService from "../../../Server/Services/ProjectService";
4
+ import Project from "../../../Models/DatabaseModels/Project";
5
+ import RumApplicationService from "../../../Server/Services/RumApplicationService";
6
+ import RumSessionReplayViewService from "../../../Server/Services/RumSessionReplayViewService";
7
+ import RumSessionService from "../../../Server/Services/RumSessionService";
8
+ import RumSessionChunkService from "../../../Server/Services/RumSessionChunkService";
9
+ import { Statement } from "../../../Server/Utils/AnalyticsDatabase/Statement";
10
+ import RumApplication from "../../../Models/DatabaseModels/RumApplication";
11
+ import RumSessionReplayView from "../../../Models/DatabaseModels/RumSessionReplayView";
12
+ import Label from "../../../Models/DatabaseModels/Label";
13
+ import {
14
+ ExpressRequest,
15
+ ExpressResponse,
16
+ NextFunction,
17
+ } from "../../../Server/Utils/Express";
18
+ import Response from "../../../Server/Utils/Response";
19
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
20
+ import Dictionary from "../../../Types/Dictionary";
21
+ import BadDataException from "../../../Types/Exception/BadDataException";
22
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
23
+ import Exception from "../../../Types/Exception/Exception";
24
+ import { JSONObject } from "../../../Types/JSON";
25
+ import ObjectID from "../../../Types/ObjectID";
26
+ import Permission, {
27
+ UserPermission,
28
+ UserTenantAccessPermission,
29
+ } from "../../../Types/Permission";
30
+ import PermissionScope from "../../../Types/Database/AccessControl/PermissionScope";
31
+ import UserType from "../../../Types/UserType";
32
+ import { MAX_SESSION_REPLAY_CHUNKS_PER_READ } from "../../../Types/Rum/SessionReplay";
33
+ import {
34
+ afterEach,
35
+ beforeAll,
36
+ beforeEach,
37
+ describe,
38
+ expect,
39
+ test,
40
+ } from "@jest/globals";
41
+
42
+ /*
43
+ * The session-replay playback routes are the ONLY reader of the two
44
+ * replay analytics tables: both models omit `crudApiPath`, so
45
+ * ModelPermission is never invoked and the `payload` column's own read
46
+ * ACL is, by itself, decorative. Every guarantee therefore has to be
47
+ * proved at the handler, which is what this file does.
48
+ *
49
+ * A note on status codes. The requirement these tests were written
50
+ * against says an under-privileged caller "gets 401". This repo maps
51
+ * NotAuthenticatedException to 401 (not logged in) and
52
+ * NotAuthorizedException to 422 (logged in, insufficient rights) - see
53
+ * Types/Exception/ExceptionCode.ts - and every permission guard in the
54
+ * codebase, including UserMiddleware.requirePermission, raises the
55
+ * latter. Emitting 401 for an authenticated-but-unauthorized caller on
56
+ * these five routes alone would be both HTTP-incorrect and inconsistent
57
+ * with the rest of the API, so the tests assert the repo's real denial
58
+ * (NotAuthorizedException / 422). The security property being pinned -
59
+ * that a Viewer or ProjectMember cannot reach a recording - is identical.
60
+ */
61
+
62
+ type RouterFunction = (
63
+ req: ExpressRequest,
64
+ res: ExpressResponse,
65
+ next: NextFunction,
66
+ ) => void | Promise<void>;
67
+
68
+ type RecordedRoute = {
69
+ method: string;
70
+ uri: string;
71
+ handlers: Array<RouterFunction>;
72
+ };
73
+
74
+ const recordedRoutes: Array<RecordedRoute> = [];
75
+
76
+ type RecordRouteFunction = (
77
+ method: string,
78
+ ) => (uri: string, ...handlers: Array<RouterFunction>) => void;
79
+
80
+ const recordRoute: RecordRouteFunction = (method: string) => {
81
+ return (uri: string, ...handlers: Array<RouterFunction>): void => {
82
+ recordedRoutes.push({
83
+ method: method.toUpperCase(),
84
+ uri: uri,
85
+ handlers: handlers,
86
+ });
87
+ };
88
+ };
89
+
90
+ const sessionReplayRouter: JSONObject = {
91
+ get: recordRoute("get"),
92
+ post: recordRoute("post"),
93
+ put: recordRoute("put"),
94
+ delete: recordRoute("delete"),
95
+ } as unknown as JSONObject;
96
+
97
+ jest.mock("../../../Server/Utils/Express", () => {
98
+ return {
99
+ getRouter: () => {
100
+ return sessionReplayRouter;
101
+ },
102
+ getClientIp: () => {
103
+ return "203.0.113.7";
104
+ },
105
+ };
106
+ });
107
+
108
+ jest.mock("../../../Server/Utils/Response", () => {
109
+ return {
110
+ sendEntityArrayResponse: jest.fn(),
111
+ sendJsonObjectResponse: jest.fn(),
112
+ sendEmptySuccessResponse: jest.fn(),
113
+ sendEntityResponse: jest.fn(),
114
+ sendErrorResponse: jest.fn(),
115
+ };
116
+ });
117
+
118
+ const LIST_ROUTE: string = "/telemetry/rum/session-replay/list";
119
+ const MANIFEST_ROUTE: string = "/telemetry/rum/session-replay/manifest";
120
+ const CHUNKS_ROUTE: string = "/telemetry/rum/session-replay/chunks";
121
+ const HEARTBEAT_ROUTE: string = "/telemetry/rum/session-replay/heartbeat";
122
+ const FOR_EXCEPTION_ROUTE: string =
123
+ "/telemetry/rum/session-replay/for-exception";
124
+ const INGEST_STATUS_ROUTE: string =
125
+ "/telemetry/rum/session-replay/ingest-status";
126
+
127
+ function findRoute(uri: string): RecordedRoute {
128
+ const route: RecordedRoute | undefined = recordedRoutes.find(
129
+ (candidate: RecordedRoute): boolean => {
130
+ return candidate.method === "POST" && candidate.uri === uri;
131
+ },
132
+ );
133
+
134
+ if (!route) {
135
+ throw new Error(`Route not registered: ${uri}`);
136
+ }
137
+
138
+ return route;
139
+ }
140
+
141
+ type CallResult = {
142
+ /* Exception handed to Response.sendErrorResponse by a guard or handler. */
143
+ deniedWith: Exception | undefined;
144
+ /* Exception thrown out of the handler into express' error path. */
145
+ thrownToNext: unknown;
146
+ /* True when the final route handler was actually entered. */
147
+ reachedHandler: boolean;
148
+ jsonBody: JSONObject | undefined;
149
+ sentBuffer: Buffer | undefined;
150
+ headers: Dictionary<string>;
151
+ };
152
+
153
+ /*
154
+ * Runs a recorded route's middleware chain for real, starting at
155
+ * requireUserAuthentication.
156
+ *
157
+ * Index 0 is UserMiddleware.getUserMiddleware, whose only job is to load
158
+ * the session and populate userType / tenantId / tenant permissions on
159
+ * the request - exactly the fields these fixtures set directly. Its
160
+ * presence is asserted separately rather than executed, because running
161
+ * it would require a live session store and would prove nothing about
162
+ * authorization.
163
+ */
164
+ async function callRoute(data: {
165
+ uri: string;
166
+ request: JSONObject;
167
+ body: JSONObject;
168
+ }): Promise<CallResult> {
169
+ const route: RecordedRoute = findRoute(data.uri);
170
+
171
+ const headers: Dictionary<string> = {};
172
+ let sentBuffer: Buffer | undefined = undefined;
173
+
174
+ const req: ExpressRequest = {
175
+ ...data.request,
176
+ body: data.body,
177
+ params: {},
178
+ query: {},
179
+ headers: { "user-agent": "jest-agent" },
180
+ } as unknown as ExpressRequest;
181
+
182
+ const res: ExpressResponse = {
183
+ setHeader: (key: string, value: string): void => {
184
+ headers[key] = value;
185
+ },
186
+ send: (payload: Buffer): void => {
187
+ sentBuffer = payload;
188
+ },
189
+ status: jest.fn().mockReturnThis(),
190
+ json: jest.fn(),
191
+ } as unknown as ExpressResponse;
192
+
193
+ /*
194
+ * Held in a const container rather than in loop-scoped `let`s so the
195
+ * per-iteration `next` closures never capture a mutable outer binding.
196
+ */
197
+ const outcome: { thrownToNext: unknown; reachedHandler: boolean } = {
198
+ thrownToNext: undefined,
199
+ reachedHandler: false,
200
+ };
201
+
202
+ for (let index: number = 1; index < route.handlers.length; index++) {
203
+ const handler: RouterFunction | undefined = route.handlers[index];
204
+
205
+ if (!handler) {
206
+ break;
207
+ }
208
+
209
+ if (index === route.handlers.length - 1) {
210
+ outcome.reachedHandler = true;
211
+ }
212
+
213
+ const step: { calledNext: boolean } = { calledNext: false };
214
+
215
+ const next: NextFunction = ((error?: unknown): void => {
216
+ step.calledNext = true;
217
+
218
+ if (error) {
219
+ outcome.thrownToNext = error;
220
+ }
221
+ }) as unknown as NextFunction;
222
+
223
+ await handler(req, res, next);
224
+
225
+ /*
226
+ * A guard that answered the request itself never calls next, which
227
+ * is precisely the denial being asserted.
228
+ */
229
+ if (!step.calledNext) {
230
+ break;
231
+ }
232
+ }
233
+
234
+ const sendErrorResponse: jest.Mock =
235
+ Response.sendErrorResponse as unknown as jest.Mock;
236
+ const sendJsonObjectResponse: jest.Mock =
237
+ Response.sendJsonObjectResponse as unknown as jest.Mock;
238
+
239
+ const errorCall: Array<unknown> | undefined = sendErrorResponse.mock
240
+ .calls[0] as Array<unknown> | undefined;
241
+
242
+ const jsonCall: Array<unknown> | undefined = sendJsonObjectResponse.mock
243
+ .calls[0] as Array<unknown> | undefined;
244
+
245
+ return {
246
+ deniedWith: errorCall ? (errorCall[2] as Exception) : undefined,
247
+ thrownToNext: outcome.thrownToNext,
248
+ reachedHandler: outcome.reachedHandler,
249
+ jsonBody: jsonCall ? (jsonCall[2] as JSONObject) : undefined,
250
+ sentBuffer: sentBuffer,
251
+ headers: headers,
252
+ };
253
+ }
254
+
255
+ function buildPrincipal(data: {
256
+ projectId: ObjectID;
257
+ userId: ObjectID;
258
+ permissions: Array<Permission>;
259
+ labelIds?: Array<ObjectID> | undefined;
260
+ /*
261
+ * TeamPermission.scope is a real, admin-settable column (All / Owned /
262
+ * Labels), and Owned is excluded from BOTH PermissionHelper filters -
263
+ * so a fixture that never sets it cannot see the fail-open it causes.
264
+ */
265
+ scope?: PermissionScope | undefined;
266
+ }): {
267
+ request: JSONObject;
268
+ databaseProps: DatabaseCommonInteractionProps;
269
+ } {
270
+ /*
271
+ * isBlockPermission must be an explicit false, not omitted:
272
+ * DatabaseCommonInteractionPropsUtil.getUserPermissions filters tenant
273
+ * permissions with a strict `isBlockPermission === false`, so an
274
+ * undefined here would silently drop every permission the fixture
275
+ * grants - which is exactly what AccessTokenService writes for a real
276
+ * team permission.
277
+ */
278
+ const userPermissions: Array<UserPermission> = data.permissions.map(
279
+ (permission: Permission): UserPermission => {
280
+ return {
281
+ _type: "UserPermission",
282
+ permission: permission,
283
+ labelIds: data.labelIds || [],
284
+ isBlockPermission: false,
285
+ ...(data.scope !== undefined && { scope: data.scope }),
286
+ };
287
+ },
288
+ );
289
+
290
+ const tenantPermission: UserTenantAccessPermission = {
291
+ _type: "UserTenantAccessPermission",
292
+ projectId: data.projectId,
293
+ permissions: userPermissions,
294
+ };
295
+
296
+ const permissionMap: Dictionary<UserTenantAccessPermission> = {};
297
+ permissionMap[data.projectId.toString()] = tenantPermission;
298
+
299
+ const databaseProps: DatabaseCommonInteractionProps = {
300
+ tenantId: data.projectId,
301
+ userId: data.userId,
302
+ userType: UserType.User,
303
+ userTenantAccessPermission: permissionMap,
304
+ };
305
+
306
+ return {
307
+ request: {
308
+ userType: UserType.User,
309
+ tenantId: data.projectId,
310
+ userTenantAccessPermission: permissionMap,
311
+ userAuthorization: { userId: data.userId },
312
+ } as unknown as JSONObject,
313
+ databaseProps: databaseProps,
314
+ };
315
+ }
316
+
317
+ /*
318
+ * Adds an UNSCOPED ReadRumSessionReplay row on top of whatever the
319
+ * principal already holds. The list guard does not accept
320
+ * ReadRumSessionReplayPayload, so a payload-scoped reviewer needs this to
321
+ * get into /list at all - which is precisely the shape that makes the
322
+ * identity column's label scope worth checking separately.
323
+ */
324
+ function grantUnscopedListPermission(
325
+ databaseProps: DatabaseCommonInteractionProps,
326
+ projectId: ObjectID,
327
+ ): void {
328
+ const tenantPermission: UserTenantAccessPermission | undefined =
329
+ databaseProps.userTenantAccessPermission?.[projectId.toString()];
330
+
331
+ if (!tenantPermission) {
332
+ throw new Error("Principal has no tenant permission to extend.");
333
+ }
334
+
335
+ tenantPermission.permissions.push({
336
+ _type: "UserPermission",
337
+ permission: Permission.ReadRumSessionReplay,
338
+ labelIds: [],
339
+ isBlockPermission: false,
340
+ });
341
+ }
342
+
343
+ /* Minimal stand-in for the ClickHouse client's ResultSet. */
344
+ function fakeResultSet(rows: Array<JSONObject>): unknown {
345
+ return {
346
+ json: async (): Promise<JSONObject> => {
347
+ return { data: rows } as unknown as JSONObject;
348
+ },
349
+ };
350
+ }
351
+
352
+ function buildHeaderRow(data: {
353
+ sessionId: string;
354
+ projectId: ObjectID;
355
+ rumApplicationId: ObjectID;
356
+ }): JSONObject {
357
+ return {
358
+ sessionId: data.sessionId,
359
+ headerProjectId: data.projectId.toString(),
360
+ applicationId: data.rumApplicationId.toString(),
361
+ aggStartTime: 1700000000000,
362
+ aggEndTime: 1700000060000,
363
+ aggDurationMs: 60000,
364
+ aggIsFinalized: true,
365
+ aggSealedReason: "final-chunk",
366
+ aggChunkCount: 4,
367
+ aggMaxChunkIndex: 3,
368
+ aggMissingChunkCount: 0,
369
+ aggEventCount: 400,
370
+ aggPayloadBytes: 4096,
371
+ aggHasError: true,
372
+ aggErrorCount: 1,
373
+ aggRageClickCount: 0,
374
+ aggDeadClickCount: 0,
375
+ aggErrorClickCount: 0,
376
+ aggRefreshRageCount: 0,
377
+ aggPageCount: 2,
378
+ aggTriggerReason: "error",
379
+ aggMaskingMode: "MaskAllText",
380
+ aggConsentState: "Granted",
381
+ aggRecorderKind: "dom",
382
+ aggRecorderVersion: "1.0.0",
383
+ aggRrwebVersion: "2.1.1",
384
+ aggSchemaVersion: 1,
385
+ aggWireVersion: 1,
386
+ aggEntryUrl: "https://example.com/",
387
+ aggExitUrl: "https://example.com/checkout",
388
+ aggRoutes: ["/", "/checkout"],
389
+ aggBrowserName: "Chrome",
390
+ aggBrowserVersion: "120",
391
+ aggOsName: "macOS",
392
+ aggDeviceType: "desktop",
393
+ aggCountryCode: "GB",
394
+ aggViewportWidth: 1440,
395
+ aggViewportHeight: 900,
396
+ aggFidelityNotices: ["canvas-not-recorded"],
397
+ aggFullSnapshotChunkIndexes: [0, 2],
398
+ aggTraceIds: [],
399
+ aggExceptionFingerprints: ["fp-1"],
400
+ aggClockSkewMs: 12,
401
+ aggIdentifiedUserKey: "hmac-key",
402
+ aggSamplePercentage: 10,
403
+ };
404
+ }
405
+
406
+ describe("Session replay playback API", () => {
407
+ let projectId: ObjectID;
408
+ let userId: ObjectID;
409
+ let applicationAId: ObjectID;
410
+ let applicationBId: ObjectID;
411
+ let labelAId: ObjectID;
412
+ let labelBId: ObjectID;
413
+
414
+ let headerQuerySpy: jest.SpyInstance;
415
+ let chunkQuerySpy: jest.SpyInstance;
416
+ let findOneBySpy: jest.SpyInstance;
417
+ let findBySpy: jest.SpyInstance;
418
+ let recordViewSpy: jest.SpyInstance;
419
+ let recordSecondsWatchedSpy: jest.SpyInstance;
420
+ let viewFindOneBySpy: jest.SpyInstance;
421
+
422
+ beforeAll(() => {
423
+ recordedRoutes.length = 0;
424
+ /*
425
+ * Loaded lazily rather than with a top-level import. TelemetryAPI
426
+ * calls Express.getRouter() at module scope, so a static import would
427
+ * run the mock factory during the import phase - before the const
428
+ * router above is initialised - and die in the temporal dead zone.
429
+ */
430
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
431
+ require("../../../Server/API/TelemetryAPI");
432
+ });
433
+
434
+ beforeEach(() => {
435
+ jest.clearAllMocks();
436
+
437
+ projectId = ObjectID.generate();
438
+ userId = ObjectID.generate();
439
+ applicationAId = ObjectID.generate();
440
+ applicationBId = ObjectID.generate();
441
+ labelAId = ObjectID.generate();
442
+ labelBId = ObjectID.generate();
443
+
444
+ headerQuerySpy = jest
445
+ .spyOn(RumSessionService, "executeQuery")
446
+ .mockResolvedValue(fakeResultSet([]) as never);
447
+
448
+ chunkQuerySpy = jest
449
+ .spyOn(RumSessionChunkService, "executeQuery")
450
+ .mockResolvedValue(fakeResultSet([]) as never);
451
+
452
+ findOneBySpy = jest.spyOn(RumApplicationService, "findOneBy");
453
+ findBySpy = jest.spyOn(RumApplicationService, "findBy");
454
+
455
+ recordViewSpy = jest.spyOn(RumSessionReplayViewService, "recordView");
456
+ recordSecondsWatchedSpy = jest
457
+ .spyOn(RumSessionReplayViewService, "recordSecondsWatched")
458
+ .mockResolvedValue(undefined);
459
+ viewFindOneBySpy = jest
460
+ .spyOn(RumSessionReplayViewService, "findOneBy")
461
+ .mockResolvedValue(null);
462
+ });
463
+
464
+ afterEach(() => {
465
+ jest.restoreAllMocks();
466
+ });
467
+
468
+ function mockProps(props: DatabaseCommonInteractionProps): void {
469
+ jest
470
+ .spyOn(CommonAPI, "getDatabaseCommonInteractionProps")
471
+ .mockResolvedValue(props);
472
+ }
473
+
474
+ function mockApplication(data: {
475
+ id: ObjectID;
476
+ labelIds: Array<ObjectID>;
477
+ }): void {
478
+ const application: RumApplication = new RumApplication();
479
+ application.id = data.id;
480
+ application.projectId = projectId;
481
+ application.labels = data.labelIds.map((labelId: ObjectID): Label => {
482
+ const label: Label = new Label();
483
+ label.id = labelId;
484
+ return label;
485
+ });
486
+
487
+ findOneBySpy.mockResolvedValue(application);
488
+ findBySpy.mockResolvedValue([application]);
489
+ }
490
+
491
+ function mockSessionHeader(rumApplicationId: ObjectID): void {
492
+ headerQuerySpy.mockResolvedValue(
493
+ fakeResultSet([
494
+ buildHeaderRow({
495
+ sessionId: "session-1",
496
+ projectId: projectId,
497
+ rumApplicationId: rumApplicationId,
498
+ }),
499
+ ]) as never,
500
+ );
501
+ }
502
+
503
+ /*
504
+ * A view row the CALLER owns. The handler queries with viewedByUserId in
505
+ * the predicate, so this stands in for "the lookup matched".
506
+ */
507
+ function mockOwnView(data: {
508
+ viewId: ObjectID;
509
+ rumApplicationId: ObjectID;
510
+ }): void {
511
+ const view: RumSessionReplayView = new RumSessionReplayView();
512
+ view.id = data.viewId;
513
+ view.projectId = projectId;
514
+ view.rumApplicationId = data.rumApplicationId;
515
+ view.viewedByUserId = userId;
516
+
517
+ viewFindOneBySpy.mockResolvedValue(view);
518
+ }
519
+
520
+ function mockRecordedView(): ObjectID {
521
+ const viewId: ObjectID = ObjectID.generate();
522
+ const view: RumSessionReplayView = new RumSessionReplayView();
523
+ view.id = viewId;
524
+ recordViewSpy.mockResolvedValue(view);
525
+ return viewId;
526
+ }
527
+
528
+ describe("guard shape", () => {
529
+ test("all six routes are registered and every one carries the three-middleware guard", () => {
530
+ for (const uri of [
531
+ LIST_ROUTE,
532
+ MANIFEST_ROUTE,
533
+ CHUNKS_ROUTE,
534
+ HEARTBEAT_ROUTE,
535
+ FOR_EXCEPTION_ROUTE,
536
+ INGEST_STATUS_ROUTE,
537
+ ]) {
538
+ const route: RecordedRoute = findRoute(uri);
539
+
540
+ expect(route.handlers).toHaveLength(4);
541
+ expect(route.handlers[0]).toBe(UserMiddleware.getUserMiddleware);
542
+ expect(route.handlers[1]).toBe(
543
+ UserMiddleware.requireUserAuthentication,
544
+ );
545
+ }
546
+ });
547
+
548
+ /*
549
+ * handlers[2] is the permission guard itself, and swapping the payload
550
+ * guard for the list guard on /manifest or /chunks would leave the
551
+ * shape assertion above entirely green. The three payload routes must
552
+ * share ONE guard instance and the two list routes another, and the
553
+ * two must not be the same object.
554
+ */
555
+ test("the payload routes and the list routes carry different permission guards", () => {
556
+ const payloadGuard: RouterFunction | undefined =
557
+ findRoute(MANIFEST_ROUTE).handlers[2];
558
+ const listGuard: RouterFunction | undefined =
559
+ findRoute(LIST_ROUTE).handlers[2];
560
+
561
+ expect(payloadGuard).toBeDefined();
562
+ expect(listGuard).toBeDefined();
563
+ expect(payloadGuard).not.toBe(listGuard);
564
+
565
+ for (const uri of [MANIFEST_ROUTE, CHUNKS_ROUTE, HEARTBEAT_ROUTE]) {
566
+ expect(findRoute(uri).handlers[2]).toBe(payloadGuard);
567
+ }
568
+
569
+ expect(findRoute(FOR_EXCEPTION_ROUTE).handlers[2]).toBe(listGuard);
570
+ });
571
+ });
572
+
573
+ /*
574
+ * The chunk table's replace key is (projectId, sessionId, tabId,
575
+ * chunkIndex) - rumApplicationId is a plain column. sessionId is minted
576
+ * by the browser, so two applications can share one. Authorizing an
577
+ * application and then reading on (projectId, sessionId) alone is a
578
+ * cross-application disclosure.
579
+ */
580
+ describe("chunk reads are pinned to the authorized application", () => {
581
+ test("the manifest query filters on the application resolved from the header", async () => {
582
+ const principal: {
583
+ request: JSONObject;
584
+ databaseProps: DatabaseCommonInteractionProps;
585
+ } = buildPrincipal({
586
+ projectId: projectId,
587
+ userId: userId,
588
+ permissions: [Permission.ReadRumSessionReplayPayload],
589
+ });
590
+
591
+ mockProps(principal.databaseProps);
592
+ mockSessionHeader(applicationAId);
593
+ mockApplication({ id: applicationAId, labelIds: [] });
594
+ mockRecordedView();
595
+
596
+ await callRoute({
597
+ uri: MANIFEST_ROUTE,
598
+ request: principal.request,
599
+ body: { sessionId: "session-1" },
600
+ });
601
+
602
+ const statement: Statement = chunkQuerySpy.mock.calls[0]![0] as Statement;
603
+
604
+ expect(statement.query).toContain("rumApplicationId = ");
605
+ expect(Object.values(statement.query_params)).toContain(
606
+ applicationAId.toString(),
607
+ );
608
+ });
609
+
610
+ test("both chunk queries filter on the application resolved from the header", async () => {
611
+ const principal: {
612
+ request: JSONObject;
613
+ databaseProps: DatabaseCommonInteractionProps;
614
+ } = buildPrincipal({
615
+ projectId: projectId,
616
+ userId: userId,
617
+ permissions: [Permission.ReadRumSessionReplayPayload],
618
+ });
619
+
620
+ mockProps(principal.databaseProps);
621
+ mockSessionHeader(applicationAId);
622
+ mockApplication({ id: applicationAId, labelIds: [] });
623
+
624
+ chunkQuerySpy
625
+ .mockResolvedValueOnce(
626
+ fakeResultSet([{ chunkIndex: 0, chunkStoredBytes: 3 }]) as never,
627
+ )
628
+ .mockResolvedValueOnce(
629
+ fakeResultSet([{ chunkIndex: 0, payload: "[1]" }]) as never,
630
+ );
631
+
632
+ await callRoute({
633
+ uri: CHUNKS_ROUTE,
634
+ request: principal.request,
635
+ body: {
636
+ sessionId: "session-1",
637
+ tabId: "tab-1",
638
+ chunkIndexes: [0],
639
+ /* A caller-supplied application id must change nothing. */
640
+ rumApplicationId: applicationBId.toString(),
641
+ },
642
+ });
643
+
644
+ expect(chunkQuerySpy).toHaveBeenCalledTimes(2);
645
+
646
+ for (const call of chunkQuerySpy.mock.calls) {
647
+ const statement: Statement = call[0] as Statement;
648
+ expect(statement.query).toContain("rumApplicationId = ");
649
+ expect(Object.values(statement.query_params)).toContain(
650
+ applicationAId.toString(),
651
+ );
652
+ expect(Object.values(statement.query_params)).not.toContain(
653
+ applicationBId.toString(),
654
+ );
655
+ }
656
+ });
657
+
658
+ test("a sessionId that exists under two applications is refused rather than resolved to the newest", async () => {
659
+ const principal: {
660
+ request: JSONObject;
661
+ databaseProps: DatabaseCommonInteractionProps;
662
+ } = buildPrincipal({
663
+ projectId: projectId,
664
+ userId: userId,
665
+ permissions: [Permission.ReadRumSessionReplayPayload],
666
+ labelIds: [labelAId],
667
+ });
668
+
669
+ mockProps(principal.databaseProps);
670
+
671
+ /*
672
+ * Application A is the caller's own and sorts first (newest). Under
673
+ * the old LIMIT 1 the label check would pass on A and the chunk
674
+ * reads would then serve B's recording.
675
+ */
676
+ headerQuerySpy.mockResolvedValue(
677
+ fakeResultSet([
678
+ buildHeaderRow({
679
+ sessionId: "session-1",
680
+ projectId: projectId,
681
+ rumApplicationId: applicationAId,
682
+ }),
683
+ buildHeaderRow({
684
+ sessionId: "session-1",
685
+ projectId: projectId,
686
+ rumApplicationId: applicationBId,
687
+ }),
688
+ ]) as never,
689
+ );
690
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
691
+ mockRecordedView();
692
+
693
+ const result: CallResult = await callRoute({
694
+ uri: MANIFEST_ROUTE,
695
+ request: principal.request,
696
+ body: { sessionId: "session-1" },
697
+ });
698
+
699
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
700
+ expect(chunkQuerySpy).not.toHaveBeenCalled();
701
+ expect(recordViewSpy).not.toHaveBeenCalled();
702
+ });
703
+ });
704
+
705
+ /*
706
+ * PermissionScope.Owned is excluded from getNonAccessControlPermissions
707
+ * AND from getAccessControlPermissions, because the ORM enforces it
708
+ * separately via OwnedScopePermission. This bespoke path has no such
709
+ * step, so an Owned grant must be refused, never treated as unscoped.
710
+ */
711
+ describe("PermissionScope.Owned fails closed", () => {
712
+ test("an Owned-scoped payload grant cannot read a recording", async () => {
713
+ const principal: {
714
+ request: JSONObject;
715
+ databaseProps: DatabaseCommonInteractionProps;
716
+ } = buildPrincipal({
717
+ projectId: projectId,
718
+ userId: userId,
719
+ permissions: [Permission.ReadRumSessionReplayPayload],
720
+ scope: PermissionScope.Owned,
721
+ });
722
+
723
+ mockProps(principal.databaseProps);
724
+ mockSessionHeader(applicationAId);
725
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
726
+ mockRecordedView();
727
+
728
+ const result: CallResult = await callRoute({
729
+ uri: MANIFEST_ROUTE,
730
+ request: principal.request,
731
+ body: { sessionId: "session-1" },
732
+ });
733
+
734
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
735
+ expect(recordViewSpy).not.toHaveBeenCalled();
736
+ expect(chunkQuerySpy).not.toHaveBeenCalled();
737
+ });
738
+
739
+ test("an Owned-scoped list grant cannot read the whole project's exceptions", async () => {
740
+ const principal: {
741
+ request: JSONObject;
742
+ databaseProps: DatabaseCommonInteractionProps;
743
+ } = buildPrincipal({
744
+ projectId: projectId,
745
+ userId: userId,
746
+ permissions: [Permission.ReadRumSessionReplay],
747
+ scope: PermissionScope.Owned,
748
+ });
749
+
750
+ mockProps(principal.databaseProps);
751
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
752
+
753
+ const result: CallResult = await callRoute({
754
+ uri: FOR_EXCEPTION_ROUTE,
755
+ request: principal.request,
756
+ body: { fingerprint: "fp-1" },
757
+ });
758
+
759
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
760
+ /* Above all: no unfiltered project-wide query was issued. */
761
+ expect(headerQuerySpy).not.toHaveBeenCalled();
762
+ });
763
+
764
+ test("an Owned-scoped grant cannot list an application's sessions", async () => {
765
+ const principal: {
766
+ request: JSONObject;
767
+ databaseProps: DatabaseCommonInteractionProps;
768
+ } = buildPrincipal({
769
+ projectId: projectId,
770
+ userId: userId,
771
+ permissions: [Permission.ReadRumSessionReplay],
772
+ scope: PermissionScope.Owned,
773
+ });
774
+
775
+ mockProps(principal.databaseProps);
776
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
777
+
778
+ const result: CallResult = await callRoute({
779
+ uri: LIST_ROUTE,
780
+ request: principal.request,
781
+ body: { rumApplicationId: applicationAId.toString() },
782
+ });
783
+
784
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
785
+ expect(headerQuerySpy).not.toHaveBeenCalled();
786
+ });
787
+ });
788
+
789
+ /*
790
+ * The core of the design: the payload guard must NOT be the OR-list
791
+ * createTelemetryReadAccessGuard builds, which already contains
792
+ * ProjectMember, Viewer and TelemetryViewer. If someone ever
793
+ * "simplifies" these routes onto that helper, these four cases fail.
794
+ */
795
+ describe("payload routes reject the roles createTelemetryReadAccessGuard would have admitted", () => {
796
+ const deniedRoles: Array<{ name: string; permission: Permission }> = [
797
+ { name: "Viewer", permission: Permission.Viewer },
798
+ { name: "ProjectMember", permission: Permission.ProjectMember },
799
+ { name: "TelemetryViewer", permission: Permission.TelemetryViewer },
800
+ { name: "TelemetryMember", permission: Permission.TelemetryMember },
801
+ ];
802
+
803
+ test.each(deniedRoles)(
804
+ "a $name-only principal is denied /manifest",
805
+ async (role: { name: string; permission: Permission }) => {
806
+ const principal: {
807
+ request: JSONObject;
808
+ databaseProps: DatabaseCommonInteractionProps;
809
+ } = buildPrincipal({
810
+ projectId: projectId,
811
+ userId: userId,
812
+ permissions: [role.permission],
813
+ });
814
+
815
+ mockProps(principal.databaseProps);
816
+ mockSessionHeader(applicationAId);
817
+ mockApplication({ id: applicationAId, labelIds: [] });
818
+ mockRecordedView();
819
+
820
+ const result: CallResult = await callRoute({
821
+ uri: MANIFEST_ROUTE,
822
+ request: principal.request,
823
+ body: { sessionId: "session-1" },
824
+ });
825
+
826
+ expect(result.deniedWith).toBeInstanceOf(NotAuthorizedException);
827
+ expect(result.deniedWith?.code).toBe(422);
828
+ expect(result.reachedHandler).toBe(false);
829
+ // Nothing was read, and no audit row claims a view that never happened.
830
+ expect(headerQuerySpy).not.toHaveBeenCalled();
831
+ expect(recordViewSpy).not.toHaveBeenCalled();
832
+ },
833
+ );
834
+
835
+ test.each(deniedRoles)(
836
+ "a $name-only principal is denied /chunks",
837
+ async (role: { name: string; permission: Permission }) => {
838
+ const principal: {
839
+ request: JSONObject;
840
+ databaseProps: DatabaseCommonInteractionProps;
841
+ } = buildPrincipal({
842
+ projectId: projectId,
843
+ userId: userId,
844
+ permissions: [role.permission],
845
+ });
846
+
847
+ mockProps(principal.databaseProps);
848
+ mockSessionHeader(applicationAId);
849
+ mockApplication({ id: applicationAId, labelIds: [] });
850
+
851
+ const result: CallResult = await callRoute({
852
+ uri: CHUNKS_ROUTE,
853
+ request: principal.request,
854
+ body: {
855
+ sessionId: "session-1",
856
+ tabId: "tab-1",
857
+ chunkIndexes: [0],
858
+ },
859
+ });
860
+
861
+ expect(result.deniedWith).toBeInstanceOf(NotAuthorizedException);
862
+ expect(result.deniedWith?.code).toBe(422);
863
+ expect(result.reachedHandler).toBe(false);
864
+ expect(chunkQuerySpy).not.toHaveBeenCalled();
865
+ },
866
+ );
867
+
868
+ test("ReadRumSessionReplay alone is not enough to watch a recording", async () => {
869
+ const principal: {
870
+ request: JSONObject;
871
+ databaseProps: DatabaseCommonInteractionProps;
872
+ } = buildPrincipal({
873
+ projectId: projectId,
874
+ userId: userId,
875
+ permissions: [Permission.ReadRumSessionReplay],
876
+ });
877
+
878
+ mockProps(principal.databaseProps);
879
+ mockSessionHeader(applicationAId);
880
+ mockApplication({ id: applicationAId, labelIds: [] });
881
+
882
+ const result: CallResult = await callRoute({
883
+ uri: MANIFEST_ROUTE,
884
+ request: principal.request,
885
+ body: { sessionId: "session-1" },
886
+ });
887
+
888
+ expect(result.deniedWith).toBeInstanceOf(NotAuthorizedException);
889
+ expect(result.reachedHandler).toBe(false);
890
+ });
891
+
892
+ test("ReadRumSessionReplayPayload alone reaches the manifest handler", async () => {
893
+ const principal: {
894
+ request: JSONObject;
895
+ databaseProps: DatabaseCommonInteractionProps;
896
+ } = buildPrincipal({
897
+ projectId: projectId,
898
+ userId: userId,
899
+ permissions: [Permission.ReadRumSessionReplayPayload],
900
+ });
901
+
902
+ mockProps(principal.databaseProps);
903
+ mockSessionHeader(applicationAId);
904
+ mockApplication({ id: applicationAId, labelIds: [] });
905
+ mockRecordedView();
906
+
907
+ const result: CallResult = await callRoute({
908
+ uri: MANIFEST_ROUTE,
909
+ request: principal.request,
910
+ body: { sessionId: "session-1" },
911
+ });
912
+
913
+ expect(result.deniedWith).toBeUndefined();
914
+ expect(result.thrownToNext).toBeUndefined();
915
+ expect(result.reachedHandler).toBe(true);
916
+ expect(result.jsonBody).toBeDefined();
917
+ });
918
+ });
919
+
920
+ describe("application scope", () => {
921
+ test("a caller scoped to application A is refused a session in application B", async () => {
922
+ const principal: {
923
+ request: JSONObject;
924
+ databaseProps: DatabaseCommonInteractionProps;
925
+ } = buildPrincipal({
926
+ projectId: projectId,
927
+ userId: userId,
928
+ permissions: [Permission.ReadRumSessionReplayPayload],
929
+ labelIds: [labelAId],
930
+ });
931
+
932
+ mockProps(principal.databaseProps);
933
+ // The session belongs to application B, which carries a different label.
934
+ mockSessionHeader(applicationBId);
935
+ mockApplication({ id: applicationBId, labelIds: [labelBId] });
936
+ mockRecordedView();
937
+
938
+ const result: CallResult = await callRoute({
939
+ uri: MANIFEST_ROUTE,
940
+ request: principal.request,
941
+ body: { sessionId: "session-1" },
942
+ });
943
+
944
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
945
+ expect((result.thrownToNext as Exception).code).toBe(422);
946
+ // The audit row is written only after authorization succeeds.
947
+ expect(recordViewSpy).not.toHaveBeenCalled();
948
+ });
949
+
950
+ test("the owning application is resolved from the session header, never from the request body", async () => {
951
+ const principal: {
952
+ request: JSONObject;
953
+ databaseProps: DatabaseCommonInteractionProps;
954
+ } = buildPrincipal({
955
+ projectId: projectId,
956
+ userId: userId,
957
+ permissions: [Permission.ReadRumSessionReplayPayload],
958
+ labelIds: [labelAId],
959
+ });
960
+
961
+ mockProps(principal.databaseProps);
962
+ mockSessionHeader(applicationBId);
963
+ mockApplication({ id: applicationBId, labelIds: [labelBId] });
964
+ mockRecordedView();
965
+
966
+ const result: CallResult = await callRoute({
967
+ uri: MANIFEST_ROUTE,
968
+ request: principal.request,
969
+ body: {
970
+ sessionId: "session-1",
971
+ // A caller-supplied application id must be ignored entirely.
972
+ rumApplicationId: applicationAId.toString(),
973
+ },
974
+ });
975
+
976
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
977
+
978
+ const lookupArgs: JSONObject = findOneBySpy.mock
979
+ .calls[0]![0] as JSONObject;
980
+ const query: JSONObject = lookupArgs["query"] as JSONObject;
981
+ expect(query["_id"]).toBe(applicationBId.toString());
982
+ });
983
+
984
+ test("a caller whose label matches the owning application is allowed", async () => {
985
+ const principal: {
986
+ request: JSONObject;
987
+ databaseProps: DatabaseCommonInteractionProps;
988
+ } = buildPrincipal({
989
+ projectId: projectId,
990
+ userId: userId,
991
+ permissions: [Permission.ReadRumSessionReplayPayload],
992
+ labelIds: [labelAId],
993
+ });
994
+
995
+ mockProps(principal.databaseProps);
996
+ mockSessionHeader(applicationAId);
997
+ mockApplication({ id: applicationAId, labelIds: [labelAId, labelBId] });
998
+ mockRecordedView();
999
+
1000
+ const result: CallResult = await callRoute({
1001
+ uri: MANIFEST_ROUTE,
1002
+ request: principal.request,
1003
+ body: { sessionId: "session-1" },
1004
+ });
1005
+
1006
+ expect(result.thrownToNext).toBeUndefined();
1007
+ expect(result.deniedWith).toBeUndefined();
1008
+ expect(recordViewSpy).toHaveBeenCalledTimes(1);
1009
+ });
1010
+
1011
+ test("the tenant comes from databaseProps, not from the body", async () => {
1012
+ const principal: {
1013
+ request: JSONObject;
1014
+ databaseProps: DatabaseCommonInteractionProps;
1015
+ } = buildPrincipal({
1016
+ projectId: projectId,
1017
+ userId: userId,
1018
+ permissions: [Permission.ReadRumSessionReplayPayload],
1019
+ });
1020
+
1021
+ mockProps(principal.databaseProps);
1022
+ mockSessionHeader(applicationAId);
1023
+ mockApplication({ id: applicationAId, labelIds: [] });
1024
+ mockRecordedView();
1025
+
1026
+ const otherProjectId: ObjectID = ObjectID.generate();
1027
+
1028
+ await callRoute({
1029
+ uri: MANIFEST_ROUTE,
1030
+ request: principal.request,
1031
+ body: {
1032
+ sessionId: "session-1",
1033
+ projectId: otherProjectId.toString(),
1034
+ tenantId: otherProjectId.toString(),
1035
+ },
1036
+ });
1037
+
1038
+ const statement: Statement = headerQuerySpy.mock
1039
+ .calls[0]![0] as Statement;
1040
+ const params: Record<string, unknown> = statement.query_params;
1041
+
1042
+ expect(Object.values(params)).toContain(projectId.toString());
1043
+ expect(Object.values(params)).not.toContain(otherProjectId.toString());
1044
+ });
1045
+ });
1046
+
1047
+ describe("list query", () => {
1048
+ test("de-duplicates ReplacingMergeTree rows with argMax over the session identity", async () => {
1049
+ const principal: {
1050
+ request: JSONObject;
1051
+ databaseProps: DatabaseCommonInteractionProps;
1052
+ } = buildPrincipal({
1053
+ projectId: projectId,
1054
+ userId: userId,
1055
+ permissions: [Permission.ReadRumSessionReplay],
1056
+ });
1057
+
1058
+ mockProps(principal.databaseProps);
1059
+ mockApplication({ id: applicationAId, labelIds: [] });
1060
+
1061
+ await callRoute({
1062
+ uri: LIST_ROUTE,
1063
+ request: principal.request,
1064
+ body: { rumApplicationId: applicationAId.toString() },
1065
+ });
1066
+
1067
+ expect(headerQuerySpy).toHaveBeenCalledTimes(1);
1068
+
1069
+ const statement: Statement = headerQuerySpy.mock
1070
+ .calls[0]![0] as Statement;
1071
+ const query: string = statement.query;
1072
+
1073
+ expect(query).toContain("argMax(");
1074
+ expect(query).toContain("argMax(startTime, version)");
1075
+ expect(query).toContain(
1076
+ "GROUP BY projectId, rumApplicationId, sessionId",
1077
+ );
1078
+ // No FINAL support exists in this repo; it must never appear.
1079
+ expect(query).not.toContain(" FINAL");
1080
+ // A read must never resurrect rows past their retention date.
1081
+ expect(query).toContain("retentionDate >= now()");
1082
+ /*
1083
+ * 'break' silently returns partial results; the whole point of not
1084
+ * using BaseAnalyticsAPI is to avoid that.
1085
+ */
1086
+ expect(query).toContain("timeout_overflow_mode = 'throw'");
1087
+ });
1088
+
1089
+ test("omits identifiedUserLabel for a caller without the narrower identity permission", async () => {
1090
+ const principal: {
1091
+ request: JSONObject;
1092
+ databaseProps: DatabaseCommonInteractionProps;
1093
+ } = buildPrincipal({
1094
+ projectId: projectId,
1095
+ userId: userId,
1096
+ permissions: [Permission.ReadRumSessionReplay],
1097
+ });
1098
+
1099
+ mockProps(principal.databaseProps);
1100
+ mockApplication({ id: applicationAId, labelIds: [] });
1101
+
1102
+ await callRoute({
1103
+ uri: LIST_ROUTE,
1104
+ request: principal.request,
1105
+ body: { rumApplicationId: applicationAId.toString() },
1106
+ });
1107
+
1108
+ const statement: Statement = headerQuerySpy.mock
1109
+ .calls[0]![0] as Statement;
1110
+
1111
+ expect(statement.query).not.toContain("identifiedUserLabel");
1112
+ });
1113
+
1114
+ /*
1115
+ * The strictest column in the schema. A caller can hold
1116
+ * ReadRumSessionReplay unscoped (so the list route admits them for
1117
+ * every application) while their identity grant -
1118
+ * ReadRumSessionReplayPayload - is scoped to one label. Checking only
1119
+ * that the permission NAME is present hands them named end users for
1120
+ * applications outside that label.
1121
+ */
1122
+ test("omits identifiedUserLabel when the identity grant is scoped to a different application", async () => {
1123
+ const principal: {
1124
+ request: JSONObject;
1125
+ databaseProps: DatabaseCommonInteractionProps;
1126
+ } = buildPrincipal({
1127
+ projectId: projectId,
1128
+ userId: userId,
1129
+ permissions: [Permission.ReadRumSessionReplayPayload],
1130
+ labelIds: [labelAId],
1131
+ });
1132
+
1133
+ grantUnscopedListPermission(principal.databaseProps, projectId);
1134
+
1135
+ mockProps(principal.databaseProps);
1136
+ /* Application B is outside the identity grant's label. */
1137
+ mockApplication({ id: applicationBId, labelIds: [labelBId] });
1138
+
1139
+ await callRoute({
1140
+ uri: LIST_ROUTE,
1141
+ request: principal.request,
1142
+ body: { rumApplicationId: applicationBId.toString() },
1143
+ });
1144
+
1145
+ const statement: Statement = headerQuerySpy.mock
1146
+ .calls[0]![0] as Statement;
1147
+
1148
+ expect(statement.query).not.toContain("identifiedUserLabel");
1149
+ });
1150
+
1151
+ test("selects identifiedUserLabel when the identity grant covers this application", async () => {
1152
+ const principal: {
1153
+ request: JSONObject;
1154
+ databaseProps: DatabaseCommonInteractionProps;
1155
+ } = buildPrincipal({
1156
+ projectId: projectId,
1157
+ userId: userId,
1158
+ permissions: [Permission.ReadRumSessionReplayPayload],
1159
+ labelIds: [labelAId],
1160
+ });
1161
+
1162
+ grantUnscopedListPermission(principal.databaseProps, projectId);
1163
+
1164
+ mockProps(principal.databaseProps);
1165
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
1166
+
1167
+ await callRoute({
1168
+ uri: LIST_ROUTE,
1169
+ request: principal.request,
1170
+ body: { rumApplicationId: applicationAId.toString() },
1171
+ });
1172
+
1173
+ const statement: Statement = headerQuerySpy.mock
1174
+ .calls[0]![0] as Statement;
1175
+
1176
+ expect(statement.query).toContain(
1177
+ "argMax(identifiedUserLabel, version) AS aggIdentifiedUserLabel",
1178
+ );
1179
+ });
1180
+
1181
+ test("a malformed rumApplicationId is a bad request, not a database error", async () => {
1182
+ const principal: {
1183
+ request: JSONObject;
1184
+ databaseProps: DatabaseCommonInteractionProps;
1185
+ } = buildPrincipal({
1186
+ projectId: projectId,
1187
+ userId: userId,
1188
+ permissions: [Permission.ReadRumSessionReplay],
1189
+ });
1190
+
1191
+ mockProps(principal.databaseProps);
1192
+
1193
+ const result: CallResult = await callRoute({
1194
+ uri: LIST_ROUTE,
1195
+ request: principal.request,
1196
+ body: { rumApplicationId: "nope" },
1197
+ });
1198
+
1199
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1200
+ expect(findOneBySpy).not.toHaveBeenCalled();
1201
+ expect(headerQuerySpy).not.toHaveBeenCalled();
1202
+ });
1203
+
1204
+ test("selects identifiedUserLabel for a ProjectAdmin", async () => {
1205
+ const principal: {
1206
+ request: JSONObject;
1207
+ databaseProps: DatabaseCommonInteractionProps;
1208
+ } = buildPrincipal({
1209
+ projectId: projectId,
1210
+ userId: userId,
1211
+ permissions: [Permission.ProjectAdmin],
1212
+ });
1213
+
1214
+ mockProps(principal.databaseProps);
1215
+ mockApplication({ id: applicationAId, labelIds: [] });
1216
+
1217
+ await callRoute({
1218
+ uri: LIST_ROUTE,
1219
+ request: principal.request,
1220
+ body: { rumApplicationId: applicationAId.toString() },
1221
+ });
1222
+
1223
+ const statement: Statement = headerQuerySpy.mock
1224
+ .calls[0]![0] as Statement;
1225
+
1226
+ expect(statement.query).toContain(
1227
+ "argMax(identifiedUserLabel, version) AS aggIdentifiedUserLabel",
1228
+ );
1229
+ });
1230
+
1231
+ test("the frustration filter is applied SERVER-side, over the argMax aliases", async () => {
1232
+ const principal: {
1233
+ request: JSONObject;
1234
+ databaseProps: DatabaseCommonInteractionProps;
1235
+ } = buildPrincipal({
1236
+ projectId: projectId,
1237
+ userId: userId,
1238
+ permissions: [Permission.ProjectAdmin],
1239
+ });
1240
+
1241
+ mockProps(principal.databaseProps);
1242
+ mockApplication({ id: applicationAId, labelIds: [] });
1243
+
1244
+ await callRoute({
1245
+ uri: LIST_ROUTE,
1246
+ request: principal.request,
1247
+ body: {
1248
+ rumApplicationId: applicationAId.toString(),
1249
+ filters: { hasFrustration: true },
1250
+ },
1251
+ });
1252
+
1253
+ const statement: Statement = headerQuerySpy.mock
1254
+ .calls[0]![0] as Statement;
1255
+
1256
+ /*
1257
+ * The old client-side version filtered only the fetched page, so a
1258
+ * project whose frustrated sessions sat on page 2 showed an empty
1259
+ * list under a label that promised otherwise. The aliases matter
1260
+ * too: raw columns would sum across ReplacingMergeTree versions.
1261
+ */
1262
+ expect(statement.query).toContain(
1263
+ "(aggRageClickCount + aggDeadClickCount + aggErrorClickCount + aggRefreshRageCount) > 0",
1264
+ );
1265
+ });
1266
+ });
1267
+
1268
+ describe("manifest", () => {
1269
+ test("never names the payload column and writes the read audit row", async () => {
1270
+ const principal: {
1271
+ request: JSONObject;
1272
+ databaseProps: DatabaseCommonInteractionProps;
1273
+ } = buildPrincipal({
1274
+ projectId: projectId,
1275
+ userId: userId,
1276
+ permissions: [Permission.ReadRumSessionReplayPayload],
1277
+ });
1278
+
1279
+ mockProps(principal.databaseProps);
1280
+ mockSessionHeader(applicationAId);
1281
+ mockApplication({ id: applicationAId, labelIds: [] });
1282
+ const viewId: ObjectID = mockRecordedView();
1283
+
1284
+ chunkQuerySpy.mockResolvedValue(
1285
+ fakeResultSet([
1286
+ {
1287
+ tabId: "tab-1",
1288
+ chunkIndex: 0,
1289
+ chunkStartOffsetMs: 0,
1290
+ chunkEndOffsetMs: 15000,
1291
+ eventCount: 100,
1292
+ hasFullSnapshot: true,
1293
+ chunkPayloadBytes: 1024,
1294
+ errorCount: 2,
1295
+ rageClickCount: 1,
1296
+ deadClickCount: 4,
1297
+ errorClickCount: 6,
1298
+ refreshRageCount: 5,
1299
+ routeCount: 3,
1300
+ },
1301
+ {
1302
+ tabId: "tab-1",
1303
+ chunkIndex: 3,
1304
+ chunkStartOffsetMs: 45000,
1305
+ chunkEndOffsetMs: 60000,
1306
+ eventCount: 100,
1307
+ hasFullSnapshot: false,
1308
+ chunkPayloadBytes: 1024,
1309
+ errorCount: 0,
1310
+ rageClickCount: 0,
1311
+ deadClickCount: 0,
1312
+ errorClickCount: 0,
1313
+ refreshRageCount: 0,
1314
+ routeCount: 0,
1315
+ },
1316
+ ]) as never,
1317
+ );
1318
+
1319
+ const result: CallResult = await callRoute({
1320
+ uri: MANIFEST_ROUTE,
1321
+ request: principal.request,
1322
+ body: { sessionId: "session-1", accessReason: "incident triage" },
1323
+ });
1324
+
1325
+ const statement: Statement = chunkQuerySpy.mock.calls[0]![0] as Statement;
1326
+
1327
+ expect(statement.query).not.toContain("payload,");
1328
+ expect(statement.query).not.toMatch(/\bpayload\b(?!Bytes)/);
1329
+ expect(statement.query).toContain("LIMIT 1 BY tabId, chunkIndex");
1330
+
1331
+ /*
1332
+ * The signal counters must be projected: the player's error,
1333
+ * frustration and route timeline lanes and the "next error" jump are
1334
+ * fed exclusively by these per-chunk columns. Dropping one from the
1335
+ * SELECT silently blanks a lane — it parses as 0 client-side.
1336
+ */
1337
+ for (const counterColumn of [
1338
+ "errorCount",
1339
+ "rageClickCount",
1340
+ "deadClickCount",
1341
+ "errorClickCount",
1342
+ "refreshRageCount",
1343
+ "routeCount",
1344
+ ]) {
1345
+ expect(statement.query).toContain(counterColumn);
1346
+ }
1347
+
1348
+ expect(recordViewSpy).toHaveBeenCalledTimes(1);
1349
+ const auditArgs: JSONObject = recordViewSpy.mock
1350
+ .calls[0]![0] as JSONObject;
1351
+ expect((auditArgs["projectId"] as ObjectID).toString()).toBe(
1352
+ projectId.toString(),
1353
+ );
1354
+ expect((auditArgs["rumApplicationId"] as ObjectID).toString()).toBe(
1355
+ applicationAId.toString(),
1356
+ );
1357
+ expect(auditArgs["sessionId"]).toBe("session-1");
1358
+ expect(auditArgs["accessReason"]).toBe("incident triage");
1359
+ expect(auditArgs["ipAddress"]).toBe("203.0.113.7");
1360
+
1361
+ const body: JSONObject = result.jsonBody as JSONObject;
1362
+ expect(body["viewId"]).toBe(viewId.toString());
1363
+
1364
+ const tabs: Array<JSONObject> = body[
1365
+ "tabs"
1366
+ ] as unknown as Array<JSONObject>;
1367
+ expect(tabs).toHaveLength(1);
1368
+ expect(tabs[0]!["chunkIndexes"]).toEqual([0, 3]);
1369
+ expect(tabs[0]!["fullSnapshotChunkIndexes"]).toEqual([0]);
1370
+
1371
+ /*
1372
+ * Every counter gets a DISTINCT nonzero value, asserted individually:
1373
+ * the client parses missing keys to 0, so a counter dropped from the
1374
+ * SELECT (or two counters cross-wired in the row mapping) fails
1375
+ * loudly here instead of silently blanking a timeline lane.
1376
+ */
1377
+ const manifestChunks: Array<JSONObject> = tabs[0]![
1378
+ "chunks"
1379
+ ] as unknown as Array<JSONObject>;
1380
+ expect(manifestChunks[0]!["errorCount"]).toBe(2);
1381
+ expect(manifestChunks[0]!["rageClickCount"]).toBe(1);
1382
+ expect(manifestChunks[0]!["deadClickCount"]).toBe(4);
1383
+ expect(manifestChunks[0]!["errorClickCount"]).toBe(6);
1384
+ expect(manifestChunks[0]!["refreshRageCount"]).toBe(5);
1385
+ expect(manifestChunks[0]!["routeCount"]).toBe(3);
1386
+ expect(manifestChunks[1]!["errorCount"]).toBe(0);
1387
+
1388
+ /*
1389
+ * The hole between chunk 0 and chunk 3 must be reported. Playback
1390
+ * that silently crosses a gap renders a DOM the user never saw.
1391
+ */
1392
+ const gaps: Array<JSONObject> = tabs[0]![
1393
+ "gaps"
1394
+ ] as unknown as Array<JSONObject>;
1395
+ expect(gaps).toHaveLength(1);
1396
+ expect(gaps[0]!["fromIndex"]).toBe(0);
1397
+ expect(gaps[0]!["toIndex"]).toBe(3);
1398
+ expect(gaps[0]!["missingMs"]).toBe(30000);
1399
+ });
1400
+
1401
+ test("a session that does not exist in this tenant is refused before any audit row", async () => {
1402
+ const principal: {
1403
+ request: JSONObject;
1404
+ databaseProps: DatabaseCommonInteractionProps;
1405
+ } = buildPrincipal({
1406
+ projectId: projectId,
1407
+ userId: userId,
1408
+ permissions: [Permission.ProjectOwner],
1409
+ });
1410
+
1411
+ mockProps(principal.databaseProps);
1412
+ headerQuerySpy.mockResolvedValue(fakeResultSet([]) as never);
1413
+
1414
+ const result: CallResult = await callRoute({
1415
+ uri: MANIFEST_ROUTE,
1416
+ request: principal.request,
1417
+ body: { sessionId: "does-not-exist" },
1418
+ });
1419
+
1420
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1421
+ expect(recordViewSpy).not.toHaveBeenCalled();
1422
+ });
1423
+ });
1424
+
1425
+ describe("chunks", () => {
1426
+ function payloadPrincipal(): {
1427
+ request: JSONObject;
1428
+ databaseProps: DatabaseCommonInteractionProps;
1429
+ } {
1430
+ return buildPrincipal({
1431
+ projectId: projectId,
1432
+ userId: userId,
1433
+ permissions: [Permission.TelemetryAdmin],
1434
+ });
1435
+ }
1436
+
1437
+ test(`rejects more than ${MAX_SESSION_REPLAY_CHUNKS_PER_READ} chunks without touching ClickHouse`, async () => {
1438
+ const principal: {
1439
+ request: JSONObject;
1440
+ databaseProps: DatabaseCommonInteractionProps;
1441
+ } = payloadPrincipal();
1442
+
1443
+ mockProps(principal.databaseProps);
1444
+ mockSessionHeader(applicationAId);
1445
+ mockApplication({ id: applicationAId, labelIds: [] });
1446
+
1447
+ const tooManyIndexes: Array<number> = Array.from(
1448
+ { length: MAX_SESSION_REPLAY_CHUNKS_PER_READ + 1 },
1449
+ (_unused: unknown, index: number): number => {
1450
+ return index;
1451
+ },
1452
+ );
1453
+
1454
+ const result: CallResult = await callRoute({
1455
+ uri: CHUNKS_ROUTE,
1456
+ request: principal.request,
1457
+ body: {
1458
+ sessionId: "session-1",
1459
+ tabId: "tab-1",
1460
+ chunkIndexes: tooManyIndexes,
1461
+ },
1462
+ });
1463
+
1464
+ expect(result.deniedWith).toBeInstanceOf(BadDataException);
1465
+ expect(result.deniedWith?.message).toContain(
1466
+ `${MAX_SESSION_REPLAY_CHUNKS_PER_READ}`,
1467
+ );
1468
+ expect(chunkQuerySpy).not.toHaveBeenCalled();
1469
+ // The cap is checked before the session is even resolved.
1470
+ expect(headerQuerySpy).not.toHaveBeenCalled();
1471
+ });
1472
+
1473
+ test("rejects a total payload larger than the read byte cap before returning any bytes", async () => {
1474
+ const principal: {
1475
+ request: JSONObject;
1476
+ databaseProps: DatabaseCommonInteractionProps;
1477
+ } = payloadPrincipal();
1478
+
1479
+ mockProps(principal.databaseProps);
1480
+ mockSessionHeader(applicationAId);
1481
+ mockApplication({ id: applicationAId, labelIds: [] });
1482
+
1483
+ chunkQuerySpy.mockResolvedValue(
1484
+ fakeResultSet([
1485
+ { chunkIndex: 0, chunkStoredBytes: 5 * 1024 * 1024 },
1486
+ { chunkIndex: 1, chunkStoredBytes: 5 * 1024 * 1024 },
1487
+ ]) as never,
1488
+ );
1489
+
1490
+ const result: CallResult = await callRoute({
1491
+ uri: CHUNKS_ROUTE,
1492
+ request: principal.request,
1493
+ body: {
1494
+ sessionId: "session-1",
1495
+ tabId: "tab-1",
1496
+ chunkIndexes: [0, 1],
1497
+ },
1498
+ });
1499
+
1500
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1501
+ /* Refused on the pre-check: the payload read never ran. */
1502
+ expect(chunkQuerySpy).toHaveBeenCalledTimes(1);
1503
+ });
1504
+
1505
+ /*
1506
+ * The cap has to bound the bytes this endpoint actually returns.
1507
+ * `payloadBytes` is the POST-GZIP wire size the recorder uploaded,
1508
+ * while the `payload` column holds the DECOMPRESSED JSON that is
1509
+ * served. Measuring the former let 8 chunks that pass a 8 MiB check
1510
+ * decompress into tens of megabytes of response.
1511
+ */
1512
+ test("the pre-check measures the stored (decompressed) size, not the compressed wire size", async () => {
1513
+ const principal: {
1514
+ request: JSONObject;
1515
+ databaseProps: DatabaseCommonInteractionProps;
1516
+ } = payloadPrincipal();
1517
+
1518
+ mockProps(principal.databaseProps);
1519
+ mockSessionHeader(applicationAId);
1520
+ mockApplication({ id: applicationAId, labelIds: [] });
1521
+
1522
+ chunkQuerySpy
1523
+ .mockResolvedValueOnce(
1524
+ fakeResultSet([{ chunkIndex: 0, chunkStoredBytes: 4 }]) as never,
1525
+ )
1526
+ .mockResolvedValueOnce(
1527
+ fakeResultSet([{ chunkIndex: 0, payload: "[1]" }]) as never,
1528
+ );
1529
+
1530
+ await callRoute({
1531
+ uri: CHUNKS_ROUTE,
1532
+ request: principal.request,
1533
+ body: {
1534
+ sessionId: "session-1",
1535
+ tabId: "tab-1",
1536
+ chunkIndexes: [0],
1537
+ },
1538
+ });
1539
+
1540
+ const precheck: Statement = chunkQuerySpy.mock.calls[0]![0] as Statement;
1541
+
1542
+ expect(precheck.query).toContain("length(payload)");
1543
+ expect(precheck.query).not.toContain("toFloat64(payloadBytes)");
1544
+ });
1545
+
1546
+ test("refuses to serve more bytes than the cap even when the pre-check said the chunks were small", async () => {
1547
+ const principal: {
1548
+ request: JSONObject;
1549
+ databaseProps: DatabaseCommonInteractionProps;
1550
+ } = payloadPrincipal();
1551
+
1552
+ mockProps(principal.databaseProps);
1553
+ mockSessionHeader(applicationAId);
1554
+ mockApplication({ id: applicationAId, labelIds: [] });
1555
+
1556
+ /* 10 MiB of real payload behind a pre-check that reported 8 bytes. */
1557
+ const fatPayload: string = "a".repeat(5 * 1024 * 1024);
1558
+
1559
+ chunkQuerySpy
1560
+ .mockResolvedValueOnce(
1561
+ fakeResultSet([
1562
+ { chunkIndex: 0, chunkStoredBytes: 4 },
1563
+ { chunkIndex: 1, chunkStoredBytes: 4 },
1564
+ ]) as never,
1565
+ )
1566
+ .mockResolvedValueOnce(
1567
+ fakeResultSet([
1568
+ { chunkIndex: 0, payload: fatPayload },
1569
+ { chunkIndex: 1, payload: fatPayload },
1570
+ ]) as never,
1571
+ );
1572
+
1573
+ const result: CallResult = await callRoute({
1574
+ uri: CHUNKS_ROUTE,
1575
+ request: principal.request,
1576
+ body: {
1577
+ sessionId: "session-1",
1578
+ tabId: "tab-1",
1579
+ chunkIndexes: [0, 1],
1580
+ },
1581
+ });
1582
+
1583
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1584
+ /* Nothing was served. */
1585
+ expect(result.sentBuffer).toBeUndefined();
1586
+ });
1587
+
1588
+ test("returns length-prefixed binary frames and de-duplicates by version", async () => {
1589
+ const principal: {
1590
+ request: JSONObject;
1591
+ databaseProps: DatabaseCommonInteractionProps;
1592
+ } = payloadPrincipal();
1593
+
1594
+ mockProps(principal.databaseProps);
1595
+ mockSessionHeader(applicationAId);
1596
+ mockApplication({ id: applicationAId, labelIds: [] });
1597
+
1598
+ chunkQuerySpy
1599
+ .mockResolvedValueOnce(
1600
+ fakeResultSet([
1601
+ { chunkIndex: 0, chunkStoredBytes: 3 },
1602
+ { chunkIndex: 1, chunkStoredBytes: 4 },
1603
+ ]) as never,
1604
+ )
1605
+ .mockResolvedValueOnce(
1606
+ fakeResultSet([
1607
+ { chunkIndex: 0, payload: "[1]" },
1608
+ { chunkIndex: 1, payload: "[22]" },
1609
+ ]) as never,
1610
+ );
1611
+
1612
+ const result: CallResult = await callRoute({
1613
+ uri: CHUNKS_ROUTE,
1614
+ request: principal.request,
1615
+ body: {
1616
+ sessionId: "session-1",
1617
+ tabId: "tab-1",
1618
+ chunkIndexes: [0, 1],
1619
+ },
1620
+ });
1621
+
1622
+ const buffer: Buffer = result.sentBuffer as unknown as Buffer;
1623
+ expect(buffer).toBeDefined();
1624
+
1625
+ expect(buffer.readUInt32LE(0)).toBe(0);
1626
+ expect(buffer.readUInt32LE(4)).toBe(3);
1627
+ expect(buffer.subarray(8, 11).toString("utf8")).toBe("[1]");
1628
+ expect(buffer.readUInt32LE(11)).toBe(1);
1629
+ expect(buffer.readUInt32LE(15)).toBe(4);
1630
+ expect(buffer.subarray(19, 23).toString("utf8")).toBe("[22]");
1631
+
1632
+ expect(result.headers["Content-Type"]).toBe("application/octet-stream");
1633
+ // A recording is personal data - it must never enter a shared cache.
1634
+ expect(result.headers["Cache-Control"]).toBe("no-store");
1635
+
1636
+ const payloadStatement: Statement = chunkQuerySpy.mock
1637
+ .calls[1]![0] as Statement;
1638
+ expect(payloadStatement.query).toContain(
1639
+ "ORDER BY chunkIndex ASC, version DESC LIMIT 1 BY chunkIndex",
1640
+ );
1641
+ });
1642
+
1643
+ test("rejects a non-integer chunk index", async () => {
1644
+ const principal: {
1645
+ request: JSONObject;
1646
+ databaseProps: DatabaseCommonInteractionProps;
1647
+ } = payloadPrincipal();
1648
+
1649
+ mockProps(principal.databaseProps);
1650
+
1651
+ const result: CallResult = await callRoute({
1652
+ uri: CHUNKS_ROUTE,
1653
+ request: principal.request,
1654
+ body: {
1655
+ sessionId: "session-1",
1656
+ tabId: "tab-1",
1657
+ chunkIndexes: [0, "1; DROP TABLE"],
1658
+ },
1659
+ });
1660
+
1661
+ expect(result.deniedWith).toBeInstanceOf(BadDataException);
1662
+ expect(chunkQuerySpy).not.toHaveBeenCalled();
1663
+ });
1664
+ });
1665
+
1666
+ describe("heartbeat", () => {
1667
+ test("floors seconds watched to the 15s cadence and scopes the row to the tenant", async () => {
1668
+ const principal: {
1669
+ request: JSONObject;
1670
+ databaseProps: DatabaseCommonInteractionProps;
1671
+ } = buildPrincipal({
1672
+ projectId: projectId,
1673
+ userId: userId,
1674
+ permissions: [Permission.ReadRumSessionReplayPayload],
1675
+ });
1676
+
1677
+ mockProps(principal.databaseProps);
1678
+ mockApplication({ id: applicationAId, labelIds: [] });
1679
+
1680
+ const viewId: ObjectID = ObjectID.generate();
1681
+ mockOwnView({ viewId: viewId, rumApplicationId: applicationAId });
1682
+
1683
+ await callRoute({
1684
+ uri: HEARTBEAT_ROUTE,
1685
+ request: principal.request,
1686
+ body: { viewId: viewId.toString(), secondsWatched: 44 },
1687
+ });
1688
+
1689
+ expect(recordSecondsWatchedSpy).toHaveBeenCalledTimes(1);
1690
+ const args: JSONObject = recordSecondsWatchedSpy.mock
1691
+ .calls[0]![0] as JSONObject;
1692
+ expect(args["secondsWatched"]).toBe(30);
1693
+ expect((args["projectId"] as ObjectID).toString()).toBe(
1694
+ projectId.toString(),
1695
+ );
1696
+ expect((args["viewId"] as ObjectID).toString()).toBe(viewId.toString());
1697
+ });
1698
+
1699
+ test("a Viewer cannot reach the heartbeat route at all", async () => {
1700
+ const principal: {
1701
+ request: JSONObject;
1702
+ databaseProps: DatabaseCommonInteractionProps;
1703
+ } = buildPrincipal({
1704
+ projectId: projectId,
1705
+ userId: userId,
1706
+ permissions: [Permission.Viewer],
1707
+ });
1708
+
1709
+ mockProps(principal.databaseProps);
1710
+
1711
+ const result: CallResult = await callRoute({
1712
+ uri: HEARTBEAT_ROUTE,
1713
+ request: principal.request,
1714
+ body: { viewId: ObjectID.generate().toString(), secondsWatched: 30 },
1715
+ });
1716
+
1717
+ expect(result.deniedWith).toBeInstanceOf(NotAuthorizedException);
1718
+ expect(recordSecondsWatchedSpy).not.toHaveBeenCalled();
1719
+ });
1720
+
1721
+ /*
1722
+ * secondsWatched is a privacy control shown on the player, not a
1723
+ * counter. A payload-permitted principal must not be able to inflate
1724
+ * a colleague's audit row - the row is looked up as the caller's own,
1725
+ * so somebody else's viewId matches nothing.
1726
+ */
1727
+ test("a viewId belonging to another user is refused and never written", async () => {
1728
+ const principal: {
1729
+ request: JSONObject;
1730
+ databaseProps: DatabaseCommonInteractionProps;
1731
+ } = buildPrincipal({
1732
+ projectId: projectId,
1733
+ userId: userId,
1734
+ permissions: [Permission.ReadRumSessionReplayPayload],
1735
+ });
1736
+
1737
+ mockProps(principal.databaseProps);
1738
+ mockApplication({ id: applicationAId, labelIds: [] });
1739
+
1740
+ const someoneElsesViewId: ObjectID = ObjectID.generate();
1741
+
1742
+ /*
1743
+ * The service is asked for a row owned by the caller; the row in
1744
+ * the table belongs to somebody else, so nothing comes back.
1745
+ */
1746
+ viewFindOneBySpy.mockResolvedValue(null);
1747
+
1748
+ const result: CallResult = await callRoute({
1749
+ uri: HEARTBEAT_ROUTE,
1750
+ request: principal.request,
1751
+ body: {
1752
+ viewId: someoneElsesViewId.toString(),
1753
+ secondsWatched: 600,
1754
+ },
1755
+ });
1756
+
1757
+ expect(result.deniedWith).toBeInstanceOf(NotAuthorizedException);
1758
+ expect(recordSecondsWatchedSpy).not.toHaveBeenCalled();
1759
+
1760
+ /* The ownership predicate is the thing being pinned. */
1761
+ const lookupArgs: JSONObject = viewFindOneBySpy.mock
1762
+ .calls[0]![0] as JSONObject;
1763
+ const query: JSONObject = lookupArgs["query"] as JSONObject;
1764
+ expect((query["viewedByUserId"] as ObjectID).toString()).toBe(
1765
+ userId.toString(),
1766
+ );
1767
+ expect((query["projectId"] as ObjectID).toString()).toBe(
1768
+ projectId.toString(),
1769
+ );
1770
+ });
1771
+
1772
+ test("a caller outside the view's application label scope cannot advance it", async () => {
1773
+ const principal: {
1774
+ request: JSONObject;
1775
+ databaseProps: DatabaseCommonInteractionProps;
1776
+ } = buildPrincipal({
1777
+ projectId: projectId,
1778
+ userId: userId,
1779
+ permissions: [Permission.ReadRumSessionReplayPayload],
1780
+ labelIds: [labelAId],
1781
+ });
1782
+
1783
+ mockProps(principal.databaseProps);
1784
+ /* The view points at application B, which carries a foreign label. */
1785
+ mockApplication({ id: applicationBId, labelIds: [labelBId] });
1786
+
1787
+ const viewId: ObjectID = ObjectID.generate();
1788
+ mockOwnView({ viewId: viewId, rumApplicationId: applicationBId });
1789
+
1790
+ const result: CallResult = await callRoute({
1791
+ uri: HEARTBEAT_ROUTE,
1792
+ request: principal.request,
1793
+ body: { viewId: viewId.toString(), secondsWatched: 30 },
1794
+ });
1795
+
1796
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
1797
+ expect(recordSecondsWatchedSpy).not.toHaveBeenCalled();
1798
+ });
1799
+
1800
+ test("a malformed viewId is a bad request, not a database error", async () => {
1801
+ const principal: {
1802
+ request: JSONObject;
1803
+ databaseProps: DatabaseCommonInteractionProps;
1804
+ } = buildPrincipal({
1805
+ projectId: projectId,
1806
+ userId: userId,
1807
+ permissions: [Permission.ReadRumSessionReplayPayload],
1808
+ });
1809
+
1810
+ mockProps(principal.databaseProps);
1811
+
1812
+ const result: CallResult = await callRoute({
1813
+ uri: HEARTBEAT_ROUTE,
1814
+ request: principal.request,
1815
+ body: { viewId: "nope", secondsWatched: 30 },
1816
+ });
1817
+
1818
+ expect(result.thrownToNext).toBeInstanceOf(BadDataException);
1819
+ expect(viewFindOneBySpy).not.toHaveBeenCalled();
1820
+ expect(recordSecondsWatchedSpy).not.toHaveBeenCalled();
1821
+ });
1822
+ });
1823
+
1824
+ describe("for-exception", () => {
1825
+ test("filters to the applications a label-scoped caller can reach", async () => {
1826
+ const principal: {
1827
+ request: JSONObject;
1828
+ databaseProps: DatabaseCommonInteractionProps;
1829
+ } = buildPrincipal({
1830
+ projectId: projectId,
1831
+ userId: userId,
1832
+ permissions: [Permission.ReadRumSessionReplay],
1833
+ labelIds: [labelAId],
1834
+ });
1835
+
1836
+ mockProps(principal.databaseProps);
1837
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
1838
+
1839
+ await callRoute({
1840
+ uri: FOR_EXCEPTION_ROUTE,
1841
+ request: principal.request,
1842
+ body: { fingerprint: "fp-1" },
1843
+ });
1844
+
1845
+ const statement: Statement = headerQuerySpy.mock
1846
+ .calls[0]![0] as Statement;
1847
+
1848
+ expect(statement.query).toContain("hasAny(exceptionFingerprints,");
1849
+ expect(statement.query).toContain("rumApplicationId IN (");
1850
+ expect(Object.values(statement.query_params)).toContainEqual([
1851
+ applicationAId.toString(),
1852
+ ]);
1853
+ });
1854
+
1855
+ test("a label-scoped caller that reaches no application gets no rows and issues no query", async () => {
1856
+ const principal: {
1857
+ request: JSONObject;
1858
+ databaseProps: DatabaseCommonInteractionProps;
1859
+ } = buildPrincipal({
1860
+ projectId: projectId,
1861
+ userId: userId,
1862
+ permissions: [Permission.ReadRumSessionReplay],
1863
+ labelIds: [labelAId],
1864
+ });
1865
+
1866
+ mockProps(principal.databaseProps);
1867
+ findBySpy.mockResolvedValue([]);
1868
+
1869
+ const result: CallResult = await callRoute({
1870
+ uri: FOR_EXCEPTION_ROUTE,
1871
+ request: principal.request,
1872
+ body: { fingerprint: "fp-1" },
1873
+ });
1874
+
1875
+ expect(headerQuerySpy).not.toHaveBeenCalled();
1876
+ expect(result.jsonBody?.["sessions"]).toEqual([]);
1877
+ });
1878
+
1879
+ test("an unscoped caller queries the whole project without an application filter", async () => {
1880
+ const principal: {
1881
+ request: JSONObject;
1882
+ databaseProps: DatabaseCommonInteractionProps;
1883
+ } = buildPrincipal({
1884
+ projectId: projectId,
1885
+ userId: userId,
1886
+ permissions: [Permission.ProjectOwner],
1887
+ });
1888
+
1889
+ mockProps(principal.databaseProps);
1890
+
1891
+ await callRoute({
1892
+ uri: FOR_EXCEPTION_ROUTE,
1893
+ request: principal.request,
1894
+ body: { fingerprint: "fp-1" },
1895
+ });
1896
+
1897
+ const statement: Statement = headerQuerySpy.mock
1898
+ .calls[0]![0] as Statement;
1899
+
1900
+ expect(statement.query).not.toContain("rumApplicationId IN (");
1901
+ expect(findBySpy).not.toHaveBeenCalled();
1902
+ });
1903
+
1904
+ test("projects the frustration counters and masking mode the replay card renders", async () => {
1905
+ const principal: {
1906
+ request: JSONObject;
1907
+ databaseProps: DatabaseCommonInteractionProps;
1908
+ } = buildPrincipal({
1909
+ projectId: projectId,
1910
+ userId: userId,
1911
+ permissions: [Permission.ProjectOwner],
1912
+ });
1913
+
1914
+ mockProps(principal.databaseProps);
1915
+
1916
+ headerQuerySpy.mockResolvedValue(
1917
+ fakeResultSet([
1918
+ {
1919
+ sessionId: "session-9",
1920
+ applicationId: applicationAId.toString(),
1921
+ aggStartTime: "2026-07-30 10:00:00.000",
1922
+ aggEndTime: "2026-07-30 10:01:00.000",
1923
+ aggDurationMs: 60000,
1924
+ aggHasError: 1,
1925
+ aggErrorCount: 2,
1926
+ aggRageClickCount: 3,
1927
+ aggDeadClickCount: 4,
1928
+ aggErrorClickCount: 6,
1929
+ aggRefreshRageCount: 5,
1930
+ aggMaskingMode: "MaskAllText",
1931
+ aggTriggerReason: "error",
1932
+ aggEntryUrl: "https://shop.example.com/checkout",
1933
+ aggBrowserName: "Chrome",
1934
+ aggOsName: "macOS",
1935
+ aggDeviceType: "desktop",
1936
+ aggIsFinalized: 1,
1937
+ },
1938
+ ]) as never,
1939
+ );
1940
+
1941
+ const result: CallResult = await callRoute({
1942
+ uri: FOR_EXCEPTION_ROUTE,
1943
+ request: principal.request,
1944
+ body: { fingerprint: "fp-1" },
1945
+ });
1946
+
1947
+ /*
1948
+ * The card's signals line ("3 rage clicks before the error") and its
1949
+ * up-front masking disclosure are fed exclusively by these aliases.
1950
+ * The client parses a missing key to 0/"", so dropping one from the
1951
+ * SELECT silently degrades the card — it must fail here instead.
1952
+ */
1953
+ const statement: Statement = headerQuerySpy.mock
1954
+ .calls[0]![0] as Statement;
1955
+
1956
+ for (const alias of [
1957
+ "aggRageClickCount",
1958
+ "aggDeadClickCount",
1959
+ "aggErrorClickCount",
1960
+ "aggRefreshRageCount",
1961
+ "aggMaskingMode",
1962
+ ]) {
1963
+ expect(statement.query).toContain(alias);
1964
+ }
1965
+
1966
+ const sessions: Array<JSONObject> = result.jsonBody?.[
1967
+ "sessions"
1968
+ ] as unknown as Array<JSONObject>;
1969
+
1970
+ expect(sessions).toHaveLength(1);
1971
+ expect(sessions[0]!["rumApplicationId"]).toBe(applicationAId.toString());
1972
+ expect(sessions[0]!["rageClickCount"]).toBe(3);
1973
+ expect(sessions[0]!["deadClickCount"]).toBe(4);
1974
+ expect(sessions[0]!["errorClickCount"]).toBe(6);
1975
+ expect(sessions[0]!["refreshRageCount"]).toBe(5);
1976
+ expect(sessions[0]!["maskingMode"]).toBe("MaskAllText");
1977
+ });
1978
+ });
1979
+
1980
+ describe("ingest-status", () => {
1981
+ function mockConfiguredApplication(): void {
1982
+ const application: RumApplication = new RumApplication();
1983
+ application.id = applicationAId;
1984
+ application.projectId = projectId;
1985
+ application.labels = [];
1986
+ application.appIdentifier = "checkout-web";
1987
+ application.isSessionReplayEnabled = true;
1988
+ application.sessionReplayAllowedOrigins = ["https://shop.example.com"];
1989
+ application.sessionReplaySamplePercentage = 25;
1990
+ application.sessionReplayMonthlyBudgetInGB = 2;
1991
+
1992
+ findOneBySpy.mockResolvedValue(application);
1993
+ findBySpy.mockResolvedValue([application]);
1994
+ }
1995
+
1996
+ function mockProject(isAllowed: boolean): void {
1997
+ const project: Project = new Project();
1998
+ project.id = projectId;
1999
+ project.isSessionReplayAllowed = isAllowed;
2000
+
2001
+ jest.spyOn(ProjectService, "findOneBy").mockResolvedValue(project);
2002
+ }
2003
+
2004
+ test("a label-scoped caller whose labels do not reach the application is refused before any disclosure", async () => {
2005
+ const principal: {
2006
+ request: JSONObject;
2007
+ databaseProps: DatabaseCommonInteractionProps;
2008
+ } = buildPrincipal({
2009
+ projectId: projectId,
2010
+ userId: userId,
2011
+ permissions: [Permission.ReadRumSessionReplay],
2012
+ labelIds: [labelBId],
2013
+ });
2014
+
2015
+ mockProps(principal.databaseProps);
2016
+ mockApplication({ id: applicationAId, labelIds: [labelAId] });
2017
+ mockProject(true);
2018
+
2019
+ const result: CallResult = await callRoute({
2020
+ uri: INGEST_STATUS_ROUTE,
2021
+ request: principal.request,
2022
+ body: { rumApplicationId: applicationAId.toString() },
2023
+ });
2024
+
2025
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2026
+ expect(result.jsonBody).toBeUndefined();
2027
+ });
2028
+
2029
+ test("an application outside this tenant is refused identically to a missing one", async () => {
2030
+ const principal: {
2031
+ request: JSONObject;
2032
+ databaseProps: DatabaseCommonInteractionProps;
2033
+ } = buildPrincipal({
2034
+ projectId: projectId,
2035
+ userId: userId,
2036
+ permissions: [Permission.ProjectOwner],
2037
+ });
2038
+
2039
+ mockProps(principal.databaseProps);
2040
+ findOneBySpy.mockResolvedValue(null);
2041
+ mockProject(true);
2042
+
2043
+ const result: CallResult = await callRoute({
2044
+ uri: INGEST_STATUS_ROUTE,
2045
+ request: principal.request,
2046
+ body: { rumApplicationId: applicationAId.toString() },
2047
+ });
2048
+
2049
+ expect(result.thrownToNext).toBeInstanceOf(NotAuthorizedException);
2050
+ expect(result.jsonBody).toBeUndefined();
2051
+ });
2052
+
2053
+ test("returns the switches, health timestamps and budget figures the panel renders", async () => {
2054
+ const principal: {
2055
+ request: JSONObject;
2056
+ databaseProps: DatabaseCommonInteractionProps;
2057
+ } = buildPrincipal({
2058
+ projectId: projectId,
2059
+ userId: userId,
2060
+ permissions: [Permission.ProjectOwner],
2061
+ });
2062
+
2063
+ mockProps(principal.databaseProps);
2064
+ mockConfiguredApplication();
2065
+ mockProject(true);
2066
+
2067
+ const result: CallResult = await callRoute({
2068
+ uri: INGEST_STATUS_ROUTE,
2069
+ request: principal.request,
2070
+ body: { rumApplicationId: applicationAId.toString() },
2071
+ });
2072
+
2073
+ const body: JSONObject = result.jsonBody as JSONObject;
2074
+
2075
+ expect(body["isProjectAllowed"]).toBe(true);
2076
+ expect(body["isApplicationEnabled"]).toBe(true);
2077
+ expect(body["appIdentifier"]).toBe("checkout-web");
2078
+ expect(body["allowedOrigins"]).toEqual(["https://shop.example.com"]);
2079
+ expect(body["samplePercentage"]).toBe(25);
2080
+ expect(body["monthlyBudgetInGB"]).toBe(2);
2081
+ expect(body["lastChunkReceivedAt"]).toBeNull();
2082
+ expect(body["budgetExceededAt"]).toBeNull();
2083
+ /*
2084
+ * Jest runs without Redis, so the usage readers answer null —
2085
+ * "unknown", which the panel must render as unknown, never as 0.
2086
+ * The daily limit is config, not a counter, so it is always known.
2087
+ */
2088
+ expect(body["projectBytesUsedToday"]).toBeNull();
2089
+ expect(body["applicationBytesUsedThisMonth"]).toBeNull();
2090
+ expect(body["dailyByteLimit"]).toBeGreaterThan(0);
2091
+ });
2092
+ });
2093
+ });