@oneuptime/common 13.0.4 → 13.0.6

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 (1589) hide show
  1. package/Models/AnalyticsModels/AuditLog.ts +81 -1
  2. package/Models/AnalyticsModels/SecurityEvent.ts +4 -4
  3. package/Models/DatabaseModels/Alert.ts +62 -0
  4. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  5. package/Models/DatabaseModels/Incident.ts +66 -0
  6. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +74 -0
  7. package/Models/DatabaseModels/IncidentPublicNote.ts +74 -0
  8. package/Models/DatabaseModels/Index.ts +10 -4
  9. package/Models/DatabaseModels/NetworkDevice.ts +67 -0
  10. package/Models/DatabaseModels/NetworkDeviceDiagnostic.ts +746 -0
  11. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +151 -0
  12. package/Models/DatabaseModels/NetworkSiteAssignmentRule.ts +1 -1
  13. package/Models/DatabaseModels/Project.ts +41 -0
  14. package/Models/DatabaseModels/RumApplication.ts +1 -1
  15. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +74 -0
  16. package/Models/DatabaseModels/{GoogleSecOpsConnection.ts → SecurityEventConnection.ts} +61 -51
  17. package/Models/DatabaseModels/{GoogleSecOpsConnectionRun.ts → SecurityEventConnectionRun.ts} +24 -24
  18. package/Models/DatabaseModels/ServiceLevelObjective.ts +185 -8
  19. package/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.ts +746 -43
  20. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +575 -0
  21. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +582 -0
  22. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.ts +16 -0
  23. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.ts +16 -0
  24. package/Models/DatabaseModels/StatusPageAnnouncement.ts +75 -0
  25. package/Models/DatabaseModels/TelemetryIngestionKey.ts +2 -2
  26. package/Models/DatabaseModels/ThreatIntelFeed.ts +1 -1
  27. package/Server/API/AIInvestigationAPI.ts +484 -24
  28. package/Server/API/BaseAPI.ts +17 -0
  29. package/Server/API/BillingInvoiceAPI.ts +343 -30
  30. package/Server/API/BillingPaymentMethodAPI.ts +126 -5
  31. package/Server/API/DashboardAPI.ts +96 -2
  32. package/Server/API/SecurityEventConnectionAPI.ts +380 -0
  33. package/Server/API/StatusPageAPI.ts +308 -199
  34. package/Server/API/TelemetryAPI.ts +402 -265
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.ts +99 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1792800000000-AddNetworkDeviceDiagnostic.ts +83 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1792900000000-AddNetworkDeviceDnsNameAndShortDeviceNames.ts +36 -0
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1793000000000-AddNetbiosLookupToNetworkDeviceDiscoveryScan.ts +29 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1793100000000-SloProductOverhaul.ts +516 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/1793200000000-BackfillSloMonitorRulesAndAffectedResources.ts +98 -0
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1793300000000-AddDataResidencyToProject.ts +27 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1793400000000-AddSubscriberUpdateNotificationStatus.ts +97 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
  44. package/Server/Middleware/TelemetryIngest.ts +101 -6
  45. package/Server/Services/AlertEpisodeLabelRuleEngineService.ts +138 -67
  46. package/Server/Services/AlertEpisodeOwnerRuleEngineService.ts +213 -84
  47. package/Server/Services/AlertEpisodePrivacyRuleEngineService.ts +112 -35
  48. package/Server/Services/AlertLabelRuleEngineService.ts +299 -217
  49. package/Server/Services/AlertOwnerRuleEngineService.ts +572 -396
  50. package/Server/Services/AlertPrivacyRuleEngineService.ts +116 -40
  51. package/Server/Services/AlertService.ts +111 -4
  52. package/Server/Services/AuditLogService.ts +612 -59
  53. package/Server/Services/BillingInvoiceService.ts +112 -102
  54. package/Server/Services/BillingService.ts +539 -11
  55. package/Server/Services/CephClusterLabelRuleEngineService.ts +170 -109
  56. package/Server/Services/CephClusterOwnerRuleEngineService.ts +233 -115
  57. package/Server/Services/CloudResourceLabelRuleEngineService.ts +163 -100
  58. package/Server/Services/CloudResourceOwnerRuleEngineService.ts +236 -115
  59. package/Server/Services/DashboardLabelRuleEngineService.ts +145 -84
  60. package/Server/Services/DashboardOwnerRuleEngineService.ts +213 -95
  61. package/Server/Services/DatabaseService.ts +171 -21
  62. package/Server/Services/DockerHostLabelRuleEngineService.ts +171 -110
  63. package/Server/Services/DockerHostOwnerRuleEngineService.ts +233 -115
  64. package/Server/Services/DockerSwarmClusterLabelRuleEngineService.ts +181 -112
  65. package/Server/Services/DockerSwarmClusterOwnerRuleEngineService.ts +243 -118
  66. package/Server/Services/ExceptionAggregationService.ts +7 -16
  67. package/Server/Services/HostLabelRuleEngineService.ts +167 -106
  68. package/Server/Services/HostOwnerRuleEngineService.ts +229 -111
  69. package/Server/Services/IncidentEpisodeLabelRuleEngineService.ts +141 -67
  70. package/Server/Services/IncidentEpisodeOwnerRuleEngineService.ts +272 -72
  71. package/Server/Services/IncidentEpisodePrivacyRuleEngineService.ts +112 -35
  72. package/Server/Services/IncidentEpisodePublicNoteService.ts +24 -0
  73. package/Server/Services/IncidentLabelRuleEngineService.ts +279 -204
  74. package/Server/Services/IncidentOwnerRuleEngineService.ts +595 -425
  75. package/Server/Services/IncidentPrivacyRuleEngineService.ts +117 -44
  76. package/Server/Services/IncidentPublicNoteService.ts +24 -0
  77. package/Server/Services/IncidentService.ts +114 -3
  78. package/Server/Services/IncomingCallPolicyLabelRuleEngineService.ts +147 -83
  79. package/Server/Services/IncomingCallPolicyOwnerRuleEngineService.ts +221 -100
  80. package/Server/Services/Index.ts +10 -4
  81. package/Server/Services/InventoryItemService.ts +28 -0
  82. package/Server/Services/IoTFleetLabelRuleEngineService.ts +145 -84
  83. package/Server/Services/IoTFleetOwnerRuleEngineService.ts +213 -95
  84. package/Server/Services/KubernetesClusterLabelRuleEngineService.ts +181 -112
  85. package/Server/Services/KubernetesClusterOwnerRuleEngineService.ts +243 -118
  86. package/Server/Services/LabelService.ts +23 -28
  87. package/Server/Services/LogAggregationService.ts +17 -23
  88. package/Server/Services/MetricAggregationService.ts +4 -14
  89. package/Server/Services/MonitorLabelRuleEngineService.ts +152 -92
  90. package/Server/Services/MonitorOwnerRuleEngineService.ts +220 -104
  91. package/Server/Services/MonitorProbeService.ts +20 -2
  92. package/Server/Services/MonitorService.ts +11 -9
  93. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +376 -5
  94. package/Server/Services/NetworkDeviceDiagnosticService.ts +417 -0
  95. package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +147 -83
  96. package/Server/Services/NetworkDeviceOwnerRuleEngineService.ts +218 -97
  97. package/Server/Services/NetworkDeviceService.ts +39 -2
  98. package/Server/Services/OnCallDutyPolicyLabelRuleEngineService.ts +151 -80
  99. package/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.ts +222 -97
  100. package/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.ts +154 -83
  101. package/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.ts +225 -100
  102. package/Server/Services/PodmanHostLabelRuleEngineService.ts +171 -110
  103. package/Server/Services/PodmanHostOwnerRuleEngineService.ts +235 -115
  104. package/Server/Services/ProjectService.ts +111 -10
  105. package/Server/Services/ProxmoxClusterLabelRuleEngineService.ts +174 -110
  106. package/Server/Services/ProxmoxClusterOwnerRuleEngineService.ts +239 -116
  107. package/Server/Services/RumApplicationLabelRuleEngineService.ts +137 -73
  108. package/Server/Services/RumApplicationOwnerRuleEngineService.ts +200 -83
  109. package/Server/Services/RunbookLabelRuleEngineService.ts +146 -84
  110. package/Server/Services/RunbookOwnerRuleEngineService.ts +216 -95
  111. package/Server/Services/ScheduledMaintenanceLabelRuleEngineService.ts +324 -230
  112. package/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.ts +613 -428
  113. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +24 -0
  114. package/Server/Services/ScheduledMaintenanceService.ts +43 -14
  115. package/Server/Services/{GoogleSecOpsConnectionRunService.ts → SecurityEventConnectionRunService.ts} +1 -1
  116. package/Server/Services/SecurityEventConnectionService.ts +590 -0
  117. package/Server/Services/ServerlessFunctionLabelRuleEngineService.ts +143 -74
  118. package/Server/Services/ServerlessFunctionOwnerRuleEngineService.ts +206 -85
  119. package/Server/Services/ServiceLabelRuleEngineService.ts +172 -110
  120. package/Server/Services/ServiceLevelObjectiveBurnRateRuleService.ts +1057 -18
  121. package/Server/Services/ServiceLevelObjectiveFeedService.ts +108 -0
  122. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +793 -97
  123. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +1069 -0
  124. package/Server/Services/ServiceLevelObjectiveOwnerTeamService.ts +240 -1
  125. package/Server/Services/ServiceLevelObjectiveOwnerUserService.ts +257 -1
  126. package/Server/Services/ServiceLevelObjectiveService.ts +1356 -56
  127. package/Server/Services/ServiceOwnerRuleEngineService.ts +238 -116
  128. package/Server/Services/StatusPageAnnouncementService.ts +13 -0
  129. package/Server/Services/StatusPageLabelRuleEngineService.ts +146 -85
  130. package/Server/Services/StatusPageOwnerRuleEngineService.ts +210 -99
  131. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +28 -232
  132. package/Server/Services/StatusPageSubscriberService.ts +6 -0
  133. package/Server/Services/TelemetryUsageBillingService.ts +38 -7
  134. package/Server/Services/ThreatIntelFeedService.ts +1 -1
  135. package/Server/Services/TraceAggregationService.ts +77 -28
  136. package/Server/Services/VMwareVCenterLabelRuleEngineService.ts +173 -110
  137. package/Server/Services/VMwareVCenterOwnerRuleEngineService.ts +238 -116
  138. package/Server/Services/WorkflowLabelRuleEngineService.ts +145 -84
  139. package/Server/Services/WorkflowOwnerRuleEngineService.ts +215 -95
  140. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  141. package/Server/Types/Database/UpdateByID.ts +3 -0
  142. package/Server/Types/Database/UpdateOneBy.ts +7 -0
  143. package/Server/Utils/AI/AlertAIContextBuilder.ts +19 -0
  144. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +13 -1
  145. package/Server/Utils/AI/IncidentAIContextBuilder.ts +39 -0
  146. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +15 -1
  147. package/Server/Utils/AI/SRE/InvestigationEvidence.ts +658 -0
  148. package/Server/Utils/AI/SRE/InvestigationReferences.ts +324 -0
  149. package/Server/Utils/AI/Toolbox/AlertTools.ts +37 -1
  150. package/Server/Utils/AI/Toolbox/IncidentTools.ts +22 -1
  151. package/Server/Utils/AI/Toolbox/Index.ts +38 -0
  152. package/Server/Utils/AI/Toolbox/SloTools.ts +416 -51
  153. package/Server/Utils/AI/Toolbox/TimelineTools.ts +9 -2
  154. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +80 -0
  155. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +151 -13
  156. package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +17 -4
  157. package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +155 -8
  158. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +16 -0
  159. package/Server/Utils/Database/RelationValueUtil.ts +106 -0
  160. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +6 -1
  161. package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +22 -27
  162. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +5 -3
  163. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +3 -1
  164. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +50 -20
  165. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +13 -1
  166. package/Server/Utils/Monitor/NetworkDeviceMetricUtil.ts +6 -11
  167. package/Server/Utils/Rules/MonitorRulePatternValidator.ts +51 -0
  168. package/Server/Utils/Rules/OwnerRuleAssignment.ts +110 -0
  169. package/Server/Utils/Rules/RuleRun/RuleApplication.ts +81 -0
  170. package/Server/Utils/Rules/RuleRun/RuleRunPermission.ts +188 -0
  171. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +803 -0
  172. package/Server/Utils/Rules/RuleRun/RuleRunner.ts +341 -0
  173. package/Server/Utils/SecurityEvent/ConnectorErrorMessage.ts +3 -2
  174. package/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.ts +1108 -0
  175. package/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.ts +732 -0
  176. package/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.ts +497 -0
  177. package/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.ts +689 -0
  178. package/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.ts +767 -0
  179. package/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.ts +571 -0
  180. package/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.ts +827 -0
  181. package/Server/Utils/SecurityEvent/{GoogleSecOps → Connectors/GoogleSecOps}/GoogleSecOpsClient.ts +609 -74
  182. package/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.ts +2101 -0
  183. package/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.ts +610 -0
  184. package/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.ts +549 -0
  185. package/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.ts +830 -0
  186. package/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.ts +684 -0
  187. package/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.ts +487 -0
  188. package/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.ts +778 -0
  189. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.ts +1182 -0
  190. package/Server/Utils/SecurityEvent/{GoogleSecOps/GoogleSecOpsRunExecutor.ts → Connectors/SecurityEventConnectionRunExecutor.ts} +165 -76
  191. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.ts +306 -0
  192. package/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.ts +55 -0
  193. package/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.ts +709 -0
  194. package/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.ts +596 -0
  195. package/Server/Utils/SecurityEvent/Connectors/Types.ts +431 -0
  196. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +1 -1
  197. package/Server/Utils/SecurityEvent/SecurityEventDedupe.ts +81 -0
  198. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +2 -2
  199. package/Server/Utils/SessionReplay/SessionReplayReadService.ts +902 -40
  200. package/Server/Utils/Slo/SloFeedUtil.ts +200 -0
  201. package/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.ts +173 -0
  202. package/Server/Utils/Slo/SloMetricUtil.ts +327 -0
  203. package/Server/Utils/Slo/SloOwnerReferenceValidator.ts +208 -0
  204. package/Server/Utils/Slo/SloRecordReferenceValidator.ts +169 -0
  205. package/Server/Utils/StartServer.ts +10 -0
  206. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +6 -43
  207. package/Server/Utils/StatusPageResource.ts +50 -5
  208. package/Server/Utils/Telemetry/EntityRegistry.ts +8 -0
  209. package/Server/Utils/Telemetry/KubernetesResourceAttributes.ts +108 -0
  210. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +174 -93
  211. package/Server/Utils/Telemetry/ResourceFacetPlanner.ts +251 -0
  212. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +248 -413
  213. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +668 -0
  214. package/Server/Utils/Telemetry/TraceContextPropagation.ts +348 -0
  215. package/Server/Utils/Telemetry.ts +18 -0
  216. package/Tests/App/Dashboard/AIInvestigationHeaderStatus.test.tsx +1063 -1
  217. package/Tests/App/Dashboard/AIRootCauseFeedItem.test.tsx +285 -0
  218. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +4 -1
  219. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +242 -0
  220. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +307 -0
  221. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +601 -15
  222. package/Tests/App/Dashboard/ArchiveResourceCard.test.tsx +289 -0
  223. package/Tests/App/Dashboard/AuditLogsTable.test.tsx +598 -0
  224. package/Tests/App/Dashboard/BillingDefaultPaymentMethod.test.tsx +1046 -0
  225. package/Tests/App/Dashboard/BillingPaidUsageFlow.test.tsx +3 -0
  226. package/Tests/App/Dashboard/BillingPaymentMethodForm.test.tsx +201 -7
  227. package/Tests/App/Dashboard/BooleanSeriesCriteriaFilterText.test.ts +82 -0
  228. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +16 -1
  229. package/Tests/App/Dashboard/ChangeAlertStateAIHeader.test.tsx +943 -0
  230. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +168 -1
  231. package/Tests/App/Dashboard/CloudResourceTelemetryScope.test.ts +41 -0
  232. package/Tests/App/Dashboard/ConnectionTestModal.test.tsx +604 -0
  233. package/Tests/App/Dashboard/ConnectorTestReportView.test.tsx +755 -0
  234. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +1095 -1
  235. package/Tests/App/Dashboard/CorrelateFilterChips.test.tsx +910 -0
  236. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +3747 -25
  237. package/Tests/App/Dashboard/CorrelateGraphHelpers.test.ts +1085 -0
  238. package/Tests/App/Dashboard/CorrelateInspector.test.tsx +1534 -0
  239. package/Tests/App/Dashboard/CorrelateNodeCard.test.tsx +965 -0
  240. package/Tests/App/Dashboard/CorrelateResultSummary.test.tsx +1525 -0
  241. package/Tests/App/Dashboard/CorrelationGraph.test.ts +1030 -0
  242. package/Tests/App/Dashboard/CorrelationGraphLayout.test.ts +526 -0
  243. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +153 -6
  244. package/Tests/App/Dashboard/DeviceDiagnostics.test.tsx +787 -0
  245. package/Tests/App/Dashboard/DeviceLatencyTrend.test.tsx +278 -0
  246. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +279 -0
  247. package/Tests/App/Dashboard/DiscoveryScanEditForm.test.tsx +87 -0
  248. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +443 -8
  249. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +165 -0
  250. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +340 -0
  251. package/Tests/App/Dashboard/EpisodeChangeState.test.tsx +1069 -0
  252. package/Tests/App/Dashboard/EpisodeMembersCard.test.tsx +772 -0
  253. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +2450 -0
  254. package/Tests/App/Dashboard/EventOverviewSkeleton.test.tsx +210 -0
  255. package/Tests/App/Dashboard/EventStatBar.test.tsx +392 -0
  256. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +247 -0
  257. package/Tests/App/Dashboard/ExceptionAIAssistance.test.tsx +606 -0
  258. package/Tests/App/Dashboard/ExceptionBreadcrumbTimeline.test.tsx +273 -0
  259. package/Tests/App/Dashboard/ExceptionDetailSideMenu.test.tsx +176 -0
  260. package/Tests/App/Dashboard/ExceptionOverviewCards.test.tsx +366 -0
  261. package/Tests/App/Dashboard/ExceptionSegmentedControl.test.tsx +1020 -0
  262. package/Tests/App/Dashboard/ExceptionSignalPages.test.tsx +371 -0
  263. package/Tests/App/Dashboard/ExceptionStackFrameViewer.test.tsx +440 -0
  264. package/Tests/App/Dashboard/ExceptionSummary.test.tsx +381 -0
  265. package/Tests/App/Dashboard/ExceptionTriage.test.tsx +306 -0
  266. package/Tests/App/Dashboard/ExceptionsResourceFacets.test.tsx +604 -0
  267. package/Tests/App/Dashboard/ExceptionsViewerEntityChips.test.tsx +706 -0
  268. package/Tests/App/Dashboard/FeedOrdering.test.ts +32 -0
  269. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +618 -13
  270. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +254 -207
  271. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +303 -135
  272. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +1472 -0
  273. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +303 -0
  274. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +1931 -25
  275. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +159 -2
  276. package/Tests/App/Dashboard/InvestigationReferenceLink.test.tsx +177 -0
  277. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +1398 -0
  278. package/Tests/App/Dashboard/InvestigationRunDetails.test.tsx +1166 -0
  279. package/Tests/App/Dashboard/InvoicesPayInvoice.test.tsx +437 -0
  280. package/Tests/App/Dashboard/KubernetesImageReferenceView.test.tsx +114 -0
  281. package/Tests/App/Dashboard/KubernetesLogsTabChips.test.tsx +285 -0
  282. package/Tests/App/Dashboard/LogsEntityChipNames.test.tsx +316 -0
  283. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +361 -0
  284. package/Tests/App/Dashboard/NetworkDeviceBulkDelete.test.tsx +65 -0
  285. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +5 -0
  286. package/Tests/App/Dashboard/ProfileTableEntityNames.test.tsx +432 -0
  287. package/Tests/App/Dashboard/ProfileViewEntityName.test.tsx +250 -0
  288. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +10 -0
  289. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +74 -0
  290. package/Tests/App/Dashboard/ResourceFeed.test.tsx +125 -5
  291. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  292. package/Tests/App/Dashboard/ResourceLogsTabsBareLayout.test.tsx +700 -0
  293. package/Tests/App/Dashboard/ResourcePageEntityNameProps.test.tsx +803 -0
  294. package/Tests/App/Dashboard/RumSessionReplaySideMenu.test.tsx +11 -1
  295. package/Tests/App/Dashboard/ScheduledMaintenanceChangeState.test.tsx +1026 -0
  296. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +436 -0
  297. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +1195 -0
  298. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +1764 -0
  299. package/Tests/App/Dashboard/SecurityEventConnectionFormModal.test.tsx +1958 -0
  300. package/Tests/App/Dashboard/SecurityEventConnectionsEmptyState.test.tsx +448 -0
  301. package/Tests/App/Dashboard/SecurityEventConnectionsTable.test.tsx +1455 -0
  302. package/Tests/App/Dashboard/SecurityEventSeverityPill.test.tsx +233 -0
  303. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +282 -0
  304. package/Tests/App/Dashboard/SecurityEventsSideMenu.test.tsx +247 -0
  305. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +375 -274
  306. package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +410 -0
  307. package/Tests/App/Dashboard/SloActiveBurnEventsCard.test.tsx +538 -0
  308. package/Tests/App/Dashboard/SloBulkActionsArchive.test.tsx +252 -0
  309. package/Tests/App/Dashboard/SloDashboardTemplateWidgets.test.tsx +322 -518
  310. package/Tests/App/Dashboard/SloElement.test.tsx +194 -0
  311. package/Tests/App/Dashboard/SloFeed.test.tsx +289 -0
  312. package/Tests/App/Dashboard/SloHistoryCharts.test.tsx +471 -0
  313. package/Tests/App/Dashboard/SloHistorySeries.test.tsx +607 -0
  314. package/Tests/App/Dashboard/SloListWidget.test.tsx +783 -0
  315. package/Tests/App/Dashboard/SloMetricsPage.test.tsx +346 -0
  316. package/Tests/App/Dashboard/SloMonitorsPage.test.tsx +661 -0
  317. package/Tests/App/Dashboard/SloMonitorsSummaryCard.test.tsx +489 -0
  318. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +437 -0
  319. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +305 -0
  320. package/Tests/App/Dashboard/SloOverviewData.test.tsx +563 -0
  321. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +311 -0
  322. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +426 -0
  323. package/Tests/App/Dashboard/SloOverviewSidebarCards.test.tsx +493 -0
  324. package/Tests/App/Dashboard/SloSettings.test.tsx +633 -0
  325. package/Tests/App/Dashboard/SloStatusSummaryCards.test.tsx +403 -0
  326. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +373 -0
  327. package/Tests/App/Dashboard/SloWidgetVariableBinding.test.tsx +689 -0
  328. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +774 -0
  329. package/Tests/App/Dashboard/TopErrorsPanelResourceNames.test.tsx +316 -0
  330. package/Tests/App/Dashboard/TopologyConnectivitySection.test.tsx +206 -0
  331. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +30 -0
  332. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +88 -24
  333. package/Tests/App/Dashboard/TraceExplorer.test.tsx +1590 -0
  334. package/Tests/App/Dashboard/TraceWaterfall.test.tsx +488 -0
  335. package/Tests/App/Dashboard/TracesAnalyticsSeriesLabels.test.tsx +367 -0
  336. package/Tests/App/Dashboard/TracesEntityNames.test.tsx +617 -0
  337. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +613 -0
  338. package/Tests/App/Dashboard/UseBulkShortenDeviceNames.test.tsx +1312 -0
  339. package/Tests/App/Dashboard/UseServiceNames.test.tsx +359 -0
  340. package/Tests/App/Dashboard/WidgetCatalog.test.ts +9 -1
  341. package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +14 -16
  342. package/Tests/Models/DatabaseModels/AuditLogModelConfiguration.test.ts +442 -0
  343. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +13 -7
  344. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +48 -1
  345. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +835 -0
  346. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +3 -2
  347. package/Tests/Models/DiscoveryScanNetbiosLookupColumn.test.ts +328 -0
  348. package/Tests/Models/DiscoveryScanShortDeviceNamesColumn.test.ts +335 -0
  349. package/Tests/Models/InventoryItemNaming.test.ts +132 -16
  350. package/Tests/Models/NetworkDeviceDiagnosticContract.test.ts +194 -0
  351. package/Tests/Models/NetworkDeviceDnsNameColumn.test.ts +321 -0
  352. package/Tests/Models/ProjectDataResidencyColumn.test.ts +154 -0
  353. package/Tests/Models/SecurityDomainAccessControl.test.ts +197 -22
  354. package/Tests/Server/API/AIInvestigationAPI.test.ts +611 -2
  355. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +1098 -0
  356. package/Tests/Server/API/BaseAPI.test.ts +6 -0
  357. package/Tests/Server/API/BaseAPIUpdateMiscDataProps.test.ts +217 -0
  358. package/Tests/Server/API/BillingInvoiceAPI.test.ts +7 -0
  359. package/Tests/Server/API/BillingInvoiceAPIPaymentStates.test.ts +990 -0
  360. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +498 -0
  361. package/Tests/Server/API/DashboardPublicSloAPI.test.ts +405 -0
  362. package/Tests/Server/API/SecurityEventConnectionAPI.test.ts +1750 -0
  363. package/Tests/Server/API/SessionReplayAPI.test.ts +644 -1
  364. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +857 -0
  365. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +1084 -0
  366. package/Tests/Server/API/TelemetryFacetsAPI.test.ts +810 -0
  367. package/Tests/Server/Infrastructure/E2EClickHouseFixtureAccess.test.ts +240 -0
  368. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +33 -3
  369. package/Tests/Server/Infrastructure/Postgres/AddDataResidencyToProjectMigration.test.ts +202 -0
  370. package/Tests/Server/Infrastructure/Postgres/AddNetbiosLookupToNetworkDeviceDiscoveryScanMigration.test.ts +330 -0
  371. package/Tests/Server/Infrastructure/Postgres/AddNetworkDeviceDnsNameAndShortDeviceNamesMigration.test.ts +340 -0
  372. package/Tests/Server/Infrastructure/Postgres/BackfillSloMonitorRulesAndAffectedResourcesMigration.test.ts +343 -0
  373. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +801 -0
  374. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +229 -0
  375. package/Tests/Server/Services/AddSubscriberUpdateNotificationStatusMigration.test.ts +307 -0
  376. package/Tests/Server/Services/AuditLogService.test.ts +1124 -0
  377. package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +200 -1
  378. package/Tests/Server/Services/BillingInvoiceServiceMutexRelease.test.ts +254 -0
  379. package/Tests/Server/Services/BillingService.test.ts +70 -0
  380. package/Tests/Server/Services/BillingServicePayInvoiceStates.test.ts +723 -0
  381. package/Tests/Server/Services/BillingServicePaymentMethodReads.test.ts +14 -2
  382. package/Tests/Server/Services/BillingServiceSubscriptionPaymentMethodSync.test.ts +1634 -0
  383. package/Tests/Server/Services/DatabaseServiceAuditRelationDiff.test.ts +492 -0
  384. package/Tests/Server/Services/DatabaseServicePasswordFieldSecrets.test.ts +629 -0
  385. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +431 -0
  386. package/Tests/Server/Services/IncidentAlertSloReferenceGuard.test.ts +358 -0
  387. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +66 -0
  388. package/Tests/Server/Services/MonitorProbeServicePastDueMonitoring.test.ts +362 -0
  389. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1460 -1
  390. package/Tests/Server/Services/NetworkDeviceDiagnosticService.test.ts +962 -0
  391. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +106 -0
  392. package/Tests/Server/Services/NetworkDeviceRuleEngines.test.ts +10 -1
  393. package/Tests/Server/Services/NetworkDeviceServicePastDueMonitoring.test.ts +335 -0
  394. package/Tests/Server/Services/NetworkDeviceSiteRuleDnsName.test.ts +568 -0
  395. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
  396. package/Tests/Server/Services/ProjectDataResidencyPostgres.test.ts +344 -0
  397. package/Tests/Server/Services/ProjectServiceAuditLogSettingsCache.test.ts +187 -0
  398. package/Tests/Server/Services/ProjectServiceDataResidency.test.ts +369 -0
  399. package/Tests/Server/Services/ProjectServiceDataResidencySelfHosted.test.ts +176 -0
  400. package/Tests/Server/Services/ProjectServicePastDueMonitoring.test.ts +289 -0
  401. package/Tests/Server/Services/ResourceFeedServices.test.ts +24 -8
  402. package/Tests/Server/Services/RuleRun/IncidentAlertMaintenanceRuleEnginesRun.test.ts +1396 -0
  403. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupARun.test.ts +917 -0
  404. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +1262 -0
  405. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupARun.test.ts +902 -0
  406. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +1163 -0
  407. package/Tests/Server/Services/RuleRun/PrivacyRuleEnginesRun.test.ts +843 -0
  408. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +465 -0
  409. package/Tests/Server/Services/SecurityEventConnectionService.test.ts +1754 -0
  410. package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +77 -23
  411. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleFeedWrites.test.ts +777 -0
  412. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleOptions.test.ts +1399 -0
  413. package/Tests/Server/Services/ServiceLevelObjectiveBurnRateRuleService.test.ts +67 -74
  414. package/Tests/Server/Services/ServiceLevelObjectiveChildRowTenancy.test.ts +906 -0
  415. package/Tests/Server/Services/ServiceLevelObjectiveDeleteTenancy.test.ts +1029 -0
  416. package/Tests/Server/Services/ServiceLevelObjectiveFeedService.test.ts +313 -0
  417. package/Tests/Server/Services/ServiceLevelObjectiveFeedWrites.test.ts +1214 -0
  418. package/Tests/Server/Services/ServiceLevelObjectiveMarkdownLink.test.ts +289 -0
  419. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +1455 -430
  420. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleLegacyLabelAdoption.test.ts +634 -0
  421. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +1193 -0
  422. package/Tests/Server/Services/{SloMonitorLabelRuleHooks.test.ts → ServiceLevelObjectiveMonitorRuleTriggers.test.ts} +135 -41
  423. package/Tests/Server/Services/ServiceLevelObjectiveOwnerFeedWrites.test.ts +671 -0
  424. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +180 -85
  425. package/Tests/Server/Services/ServiceLevelObjectiveServiceArchive.test.ts +537 -0
  426. package/Tests/Server/Services/ServiceLevelObjectiveServiceLegacyMonitorLabelWrite.test.ts +870 -0
  427. package/Tests/Server/Services/ServiceLevelObjectiveServiceMonitorRuleGuard.test.ts +343 -0
  428. package/Tests/Server/Services/ServiceLevelObjectiveServiceReEvaluation.test.ts +246 -0
  429. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +501 -0
  430. package/Tests/Server/Services/SloAffectedResourceLinkContract.test.ts +86 -0
  431. package/Tests/Server/Services/SloAffectedResourceMetricAttributes.test.ts +547 -0
  432. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +10 -4
  433. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +7 -2
  434. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +174 -0
  435. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateServiceVariables.test.ts +40 -0
  436. package/Tests/Server/Services/SubscriberUpdateNotificationHooks.test.ts +541 -0
  437. package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +53 -0
  438. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +218 -0
  439. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +155 -1
  440. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +288 -0
  441. package/Tests/Server/Services/TraceAggregationExceptionScope.test.ts +142 -0
  442. package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +85 -24
  443. package/Tests/Server/TestingUtils/PastDueMonitoringClaimSql.ts +177 -0
  444. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +51 -10
  445. package/Tests/Server/TestingUtils/__mocks__/Stripe.mock.ts +16 -1
  446. package/Tests/Server/Types/Database/Permissions/ProjectDataResidencyColumn.test.ts +386 -0
  447. package/Tests/Server/Types/Database/Permissions/SecurityRoleAccess.test.ts +34 -11
  448. package/Tests/Server/Utils/AI/InvestigationEvidence.test.ts +1395 -0
  449. package/Tests/Server/Utils/AI/InvestigationReferences.test.ts +616 -0
  450. package/Tests/Server/Utils/AI/SloAffectedResourceAIContext.test.ts +445 -0
  451. package/Tests/Server/Utils/AI/SloTools.test.ts +423 -1
  452. package/Tests/Server/Utils/AI/SloToolsArchive.test.ts +294 -0
  453. package/Tests/Server/Utils/AI/SloToolsRuleReadPermissions.test.ts +297 -0
  454. package/Tests/Server/Utils/AI/TimelineTools.test.ts +119 -0
  455. package/Tests/Server/Utils/AI/Toolbox/AIToolbox.test.ts +151 -0
  456. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +65 -0
  457. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorExceptionScope.test.ts +224 -0
  458. package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +255 -10
  459. package/Tests/Server/Utils/Dashboard/SloTemplatePublicDashboard.test.ts +404 -750
  460. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +45 -0
  461. package/Tests/Server/Utils/Database/RelationValueUtil.test.ts +157 -0
  462. package/Tests/Server/Utils/Monitor/Criteria/BooleanSeriesEvaluateOverTime.test.ts +260 -0
  463. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +181 -0
  464. package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +147 -30
  465. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPerSeriesFanout.test.ts +55 -2
  466. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.test.ts +17 -0
  467. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +136 -0
  468. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidatorLoadOrder.test.ts +472 -0
  469. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +48 -2
  470. package/Tests/Server/Utils/Monitor/SloTemplateMetricEmitterContract.test.ts +435 -475
  471. package/Tests/Server/Utils/Rules/MonitorRulePatternValidator.test.ts +77 -0
  472. package/Tests/Server/Utils/Rules/OwnerRuleAssignment.test.ts +177 -0
  473. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +365 -0
  474. package/Tests/Server/Utils/Rules/RuleRun/RuleRunRegistry.test.ts +173 -0
  475. package/Tests/Server/Utils/Rules/RuleRun/RuleRunner.test.ts +675 -0
  476. package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +8 -8
  477. package/Tests/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.test.ts +1240 -0
  478. package/Tests/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.test.ts +1853 -0
  479. package/Tests/Server/Utils/SecurityEvent/Connectors/ConnectorFetchSummary.test.ts +261 -0
  480. package/Tests/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.test.ts +1184 -0
  481. package/Tests/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.test.ts +1066 -0
  482. package/Tests/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.test.ts +1568 -0
  483. package/Tests/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.test.ts +930 -0
  484. package/Tests/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.test.ts +1409 -0
  485. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsAuth.test.ts → Connectors/GoogleSecOps/GoogleSecOpsAuth.test.ts} +5 -5
  486. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsClient.test.ts → Connectors/GoogleSecOps/GoogleSecOpsClient.test.ts} +9 -5
  487. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.test.ts +483 -0
  488. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorCuratedAndLateAlerts.test.ts +676 -0
  489. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorFetchEvents.test.ts +1994 -0
  490. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorFixtures.ts +332 -0
  491. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorHttpIntegration.test.ts +680 -0
  492. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorRequestContract.test.ts +1206 -0
  493. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorTestConnection.test.ts +1136 -0
  494. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsCuratedRuleCounts.test.ts +407 -0
  495. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsErrorHandling.test.ts → Connectors/GoogleSecOps/GoogleSecOpsErrorHandling.test.ts} +5 -5
  496. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingAdaptiveCatchUp.test.ts +730 -0
  497. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsClickhouseIntegration.test.ts → Connectors/GoogleSecOps/GoogleSecOpsPollingClickhouseIntegration.test.ts} +130 -35
  498. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingFailureTaxonomy.test.ts +772 -0
  499. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingFixtures.ts +648 -0
  500. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsHttpIntegration.test.ts → Connectors/GoogleSecOps/GoogleSecOpsPollingHttpIntegration.test.ts} +249 -222
  501. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingIntegration.test.ts +1390 -0
  502. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingLoop.test.ts +981 -0
  503. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsPollingNothingImportedRegression.test.ts +290 -0
  504. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsRequestContract.test.ts → Connectors/GoogleSecOps/GoogleSecOpsRequestContract.test.ts} +2 -2
  505. package/Tests/Server/Utils/SecurityEvent/{GoogleSecOpsResponseParsing.test.ts → Connectors/GoogleSecOps/GoogleSecOpsResponseParsing.test.ts} +5 -5
  506. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsSearchDetections.test.ts +763 -0
  507. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsTesterIntegration.test.ts +421 -0
  508. package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.test.ts +763 -0
  509. package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.test.ts +1296 -0
  510. package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.test.ts +1102 -0
  511. package/Tests/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.test.ts +1174 -0
  512. package/Tests/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.test.ts +1032 -0
  513. package/Tests/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.test.ts +1558 -0
  514. package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.test.ts +4483 -0
  515. package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.test.ts +1178 -0
  516. package/Tests/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.test.ts +1215 -0
  517. package/Tests/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.test.ts +1068 -0
  518. package/Tests/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.test.ts +1004 -0
  519. package/Tests/Server/Utils/SessionReplay/SessionReplayOriginAllowlist.test.ts +41 -0
  520. package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1966 -179
  521. package/Tests/Server/Utils/Slo/SloFeedUtil.test.ts +326 -0
  522. package/Tests/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.test.ts +260 -0
  523. package/Tests/Server/Utils/Slo/SloMetricUtil.test.ts +592 -0
  524. package/Tests/Server/Utils/Slo/SloRecordReferenceValidator.test.ts +311 -0
  525. package/Tests/Server/Utils/StatusPageResource.test.ts +91 -3
  526. package/Tests/Server/Utils/Telemetry/KubernetesResourceAttributes.test.ts +162 -0
  527. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +460 -13
  528. package/Tests/Server/Utils/Telemetry/ResourceFacetPlanner.test.ts +678 -0
  529. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +348 -2
  530. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +525 -0
  531. package/Tests/Server/Utils/Telemetry/TraceContextPropagation.test.ts +420 -0
  532. package/Tests/Types/Billing/SubscriptionStatus.test.ts +176 -0
  533. package/Tests/Types/Billing/SubscriptionStatusPastDueMonitoring.test.ts +187 -0
  534. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +53 -7
  535. package/Tests/Types/Dashboard/SloDashboardTemplate.test.ts +816 -792
  536. package/Tests/Types/Monitor/CriteriaFilter.test.ts +216 -24
  537. package/Tests/Types/Monitor/MonitorStepDnssecMonitor.test.ts +14 -1
  538. package/Tests/Types/Monitor/MonitorStepDomainMonitor.test.ts +14 -1
  539. package/Tests/Types/Monitor/MonitorStepExternalStatusPageMonitor.test.ts +14 -1
  540. package/Tests/Types/Monitor/MonitorStepRetriesParsing.test.ts +201 -0
  541. package/Tests/Types/Rules/RuleRun.test.ts +315 -0
  542. package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +133 -0
  543. package/Tests/Types/Rum/SessionReplayTransportContracts.test.ts +95 -0
  544. package/Tests/Types/SecurityEvent/Connectors/ConnectorDiagnostics.test.ts +82 -0
  545. package/Tests/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.test.ts +339 -0
  546. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +217 -0
  547. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +200 -0
  548. package/Tests/Types/Telemetry/ExceptionSpanScope.test.ts +89 -0
  549. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +96 -0
  550. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +304 -0
  551. package/Tests/UI/Components/ActiveFilterChipsLockedFilters.test.tsx +411 -0
  552. package/Tests/UI/Components/AlertBanner.test.tsx +319 -0
  553. package/Tests/UI/Components/BulkArchiveActionsCopy.test.tsx +310 -0
  554. package/Tests/UI/Components/Card.test.tsx +312 -1
  555. package/Tests/UI/Components/CardModelDetailHeaderLayout.test.tsx +253 -0
  556. package/Tests/UI/Components/CardModelDetailRefresher.test.tsx +342 -0
  557. package/Tests/UI/Components/Checkbox.test.tsx +79 -0
  558. package/Tests/UI/Components/CopyTextButton.test.tsx +142 -0
  559. package/Tests/UI/Components/Detail/DetailIdIsUnique.test.tsx +87 -0
  560. package/Tests/UI/Components/DetailCompactStyle.test.tsx +621 -0
  561. package/Tests/UI/Components/Dropdown/DropdownClearable.test.tsx +393 -0
  562. package/Tests/UI/Components/Dropdown/DropdownMultiSelectChipAccessibleName.test.tsx +64 -0
  563. package/Tests/UI/Components/EmptyState/EmptyState.test.tsx +335 -4
  564. package/Tests/UI/Components/EmptyState/EmptyStateGuideLinks.test.tsx +337 -0
  565. package/Tests/UI/Components/ErrorBoundary.test.tsx +60 -0
  566. package/Tests/UI/Components/FacetSections.test.tsx +581 -0
  567. package/Tests/UI/Components/FacetSidebarControls.test.tsx +812 -0
  568. package/Tests/UI/Components/FacetVisibility.test.ts +429 -0
  569. package/Tests/UI/Components/Forms/ModelFormJsonColumns.test.tsx +21 -20
  570. package/Tests/UI/Components/LabelRuleImportExport.test.tsx +15 -2
  571. package/Tests/UI/Components/LockedFilterChip.test.tsx +788 -0
  572. package/Tests/UI/Components/LogsEntityNames.test.ts +1769 -0
  573. package/Tests/UI/Components/LogsEntityNamesComponents.test.tsx +696 -0
  574. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +600 -0
  575. package/Tests/UI/Components/LogsViewerEntityNames.test.tsx +713 -0
  576. package/Tests/UI/Components/MarkdownInlineReferences.test.tsx +2079 -0
  577. package/Tests/UI/Components/MemberRoleAssignment.test.tsx +446 -0
  578. package/Tests/UI/Components/ModelDetailRefetch.test.tsx +397 -0
  579. package/Tests/UI/Components/ModelListRefetch.test.tsx +233 -0
  580. package/Tests/UI/Components/ModelPageModelChange.test.tsx +409 -0
  581. package/Tests/UI/Components/MoreMenu.test.tsx +133 -0
  582. package/Tests/UI/Components/MoreMenuMotion.test.tsx +84 -3
  583. package/Tests/UI/Components/PaginationIntegration.test.tsx +25 -0
  584. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +172 -0
  585. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  586. package/Tests/UI/Components/RuleRun/RuleDetailFields.test.tsx +336 -0
  587. package/Tests/UI/Components/RuleRun/RuleTable.test.tsx +304 -0
  588. package/Tests/UI/Components/RuleRun/RunRuleNowModal.test.tsx +231 -0
  589. package/Tests/UI/Components/RuleRun/RunRulesBulkAction.test.ts +213 -0
  590. package/Tests/UI/Components/SavedViewsSidebarIntegration.test.tsx +32 -14
  591. package/Tests/UI/Components/TableSortAndMobileColumns.test.tsx +44 -1
  592. package/Tests/UI/Components/TelemetryActiveFilterChipsLockedFilters.test.tsx +367 -0
  593. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +718 -0
  594. package/Tests/UI/Monitor/EvaluationLogList.test.tsx +303 -0
  595. package/Tests/UI/Monitor/MonitorSummarySnapshotRender.test.tsx +55 -1
  596. package/Tests/UI/Rum/FidelityNotices.test.ts +22 -0
  597. package/Tests/UI/Rum/RecordingHealthCard.test.tsx +120 -136
  598. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +988 -0
  599. package/Tests/UI/Rum/ReplayCard.test.tsx +32 -0
  600. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +709 -12
  601. package/Tests/UI/Rum/ReplayEngine.test.ts +154 -0
  602. package/Tests/UI/Rum/ReplayHeader.test.tsx +284 -124
  603. package/Tests/UI/Rum/ReplayPinControl.test.tsx +189 -10
  604. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +286 -12
  605. package/Tests/UI/Rum/ReplayRail.test.tsx +294 -0
  606. package/Tests/UI/Rum/ReplayScrubber.test.tsx +213 -1
  607. package/Tests/UI/Rum/ReplayStage.test.tsx +1228 -173
  608. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +433 -3
  609. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +1391 -0
  610. package/Tests/UI/Rum/ReplayTimeline.test.tsx +222 -0
  611. package/Tests/UI/Rum/ReplayUi.test.tsx +189 -2
  612. package/Tests/UI/Rum/ReplayUserSessionsMenu.test.tsx +91 -0
  613. package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +1 -1
  614. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +585 -0
  615. package/Tests/UI/Rum/SessionReplayTable.test.tsx +844 -51
  616. package/Tests/UI/Rum/SessionReplayUsersTable.test.tsx +29 -1
  617. package/Tests/UI/Telemetry/TelemetryDetailPanel.test.tsx +288 -0
  618. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +31 -1
  619. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +6 -0
  620. package/Tests/UI/Utils/Clipboard.test.ts +154 -0
  621. package/Tests/UI/Utils/DashboardStackingLayers.test.ts +8 -1
  622. package/Tests/UI/Utils/Navigation.test.ts +24 -0
  623. package/Tests/UI/Utils/RuleRunClient.test.ts +303 -0
  624. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +2206 -0
  625. package/Tests/UI/Utils/Telemetry/UseTelemetryEntityNames.test.tsx +844 -0
  626. package/Tests/Utils/AI/InvestigationReport.test.ts +2430 -0
  627. package/Tests/Utils/APIOutgoingRequestTracer.test.ts +137 -0
  628. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +2 -0
  629. package/Tests/Utils/Dashboard/Components/DashboardSloListComponent.test.ts +206 -0
  630. package/Tests/Utils/Dashboard/DashboardSloComponent.test.ts +36 -2
  631. package/Tests/Utils/Dashboard/SloTemplateVariableScoping.test.ts +409 -588
  632. package/Tests/Utils/Dashboard/SloWidgetSource.test.ts +351 -0
  633. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +361 -0
  634. package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +1425 -118
  635. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +188 -3
  636. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +661 -13
  637. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +349 -0
  638. package/Tests/Utils/NetworkDiscovery/RunImportedDeviceNaming.test.ts +656 -0
  639. package/Tests/Utils/NetworkDiscovery/ShortDeviceNamePlanner.test.ts +1020 -0
  640. package/Tests/Utils/NetworkDiscovery/ShortHostnameUtil.test.ts +619 -0
  641. package/Tests/Utils/NetworkSite/CidrMatchUtil.test.ts +317 -0
  642. package/Tests/Utils/Project/DataResidency.test.ts +198 -0
  643. package/Tests/Utils/Rules/RuleRunSummary.test.ts +394 -0
  644. package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +40 -0
  645. package/Tests/Utils/Rum/SessionReplayRecordingEnded.test.ts +619 -0
  646. package/Tests/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.test.ts +631 -0
  647. package/Tests/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.test.ts +479 -0
  648. package/Tests/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.test.ts +507 -0
  649. package/Tests/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.test.ts +686 -0
  650. package/Tests/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.test.ts +474 -0
  651. package/Tests/Utils/SecurityEvent/Connectors/OktaNormalizer.test.ts +736 -0
  652. package/Tests/Utils/SecurityEvent/Connectors/SplunkNormalizer.test.ts +407 -0
  653. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.test.ts +129 -0
  654. package/Tests/Utils/Slo/SloAffectedResourceMarkdown.test.ts +302 -0
  655. package/Tests/Utils/Slo/SloBurnRateTemplate.test.ts +468 -0
  656. package/Tests/Utils/Slo/SloEvaluation.test.ts +20 -4
  657. package/Tests/Utils/Slo/SloFeedMarkdown.test.ts +1185 -0
  658. package/Tests/Utils/Slo/SloHealth.test.ts +262 -0
  659. package/Tests/Utils/Slo/SloHistoryMetricName.test.ts +102 -0
  660. package/Tests/Utils/Slo/SloListWidgetFormat.test.ts +522 -0
  661. package/Tests/Utils/Slo/SloMetricType.test.ts +355 -0
  662. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +387 -0
  663. package/Tests/Utils/Slo/SloMonitorStatusSummary.test.ts +210 -0
  664. package/Tests/Utils/Slo/SloOverviewText.test.ts +226 -0
  665. package/Tests/Utils/Slo/SloProjection.test.ts +1039 -0
  666. package/Tests/Utils/Slo/SloWidgetFormat.test.ts +47 -0
  667. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +57 -1
  668. package/Tests/Utils/Telemetry/EntityRelationship.test.ts +36 -0
  669. package/Tests/Utils/Telemetry/LockedFilterSearch.test.ts +521 -0
  670. package/Tests/Utils/Telemetry/NetworkHost.test.ts +48 -0
  671. package/Tests/Utils/Telemetry/OriginAllowList.test.ts +154 -0
  672. package/Tests/Utils/Traces/CriticalPath.test.ts +25 -0
  673. package/Types/AI/AIChatTypes.ts +8 -0
  674. package/Types/AI/InvestigationEvidence.ts +96 -0
  675. package/Types/BaseDatabase/EnableAuditLogOn.ts +41 -0
  676. package/Types/Billing/SubscriptionStatus.ts +39 -4
  677. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  678. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +5 -0
  679. package/Types/Dashboard/DashboardComponents/DashboardSloComponent.ts +10 -0
  680. package/Types/Dashboard/DashboardComponents/DashboardSloListComponent.ts +29 -0
  681. package/Types/Dashboard/DashboardTemplates.ts +290 -297
  682. package/Types/Database/EnableAuditLog.ts +23 -1
  683. package/Types/Email/EmailTemplateType.ts +4 -0
  684. package/Types/Monitor/CriteriaFilter.ts +55 -8
  685. package/Types/Monitor/MonitorEvaluationSummary.ts +5 -0
  686. package/Types/Monitor/MonitorStep.ts +5 -2
  687. package/Types/Monitor/MonitorStepDnsMonitor.ts +3 -1
  688. package/Types/Monitor/MonitorStepDnssecMonitor.ts +3 -1
  689. package/Types/Monitor/MonitorStepDomainMonitor.ts +3 -1
  690. package/Types/Monitor/MonitorStepExternalStatusPageMonitor.ts +3 -1
  691. package/Types/Monitor/MonitorStepRetries.ts +59 -0
  692. package/Types/Monitor/MonitorStepSnmpMonitor.ts +9 -2
  693. package/Types/NetworkDevice/NetworkDeviceDiagnosticResult.ts +104 -0
  694. package/Types/NetworkDevice/NetworkDeviceDiagnosticStatus.ts +47 -0
  695. package/Types/NetworkDevice/NetworkDeviceDiagnosticType.ts +47 -0
  696. package/Types/NetworkDevice/NetworkDevicePingMetricNames.ts +20 -0
  697. package/Types/Permission.ts +129 -2
  698. package/Types/Rules/RuleCriteriaFieldRegistry.ts +5 -0
  699. package/Types/Rules/RuleRun.ts +518 -0
  700. package/Types/Rum/SessionReplay.ts +109 -0
  701. package/Types/Rum/SessionReplayApi.ts +49 -0
  702. package/Types/SecurityEvent/Connectors/ConnectorDiagnostics.ts +117 -0
  703. package/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.ts +113 -0
  704. package/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.ts +599 -0
  705. package/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.ts +45 -0
  706. package/Types/SecurityEvent/GoogleSecOpsRegion.ts +34 -0
  707. package/Types/ServiceLevelObjective/SloHistoryMetricName.ts +25 -0
  708. package/Types/ServiceLevelObjective/SloMetricType.ts +49 -0
  709. package/Types/StatusPage/StatusPageSubscriberNotificationEventType.ts +4 -0
  710. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +331 -0
  711. package/Types/StatusPage/SubscriberNotificationTrigger.ts +12 -0
  712. package/Types/StatusPage/SubscriberUpdateNotification.ts +89 -0
  713. package/Types/Telemetry/EntityType.ts +14 -0
  714. package/Types/Telemetry/ExceptionSpanScope.ts +83 -0
  715. package/Types/Telemetry/LockedFilterDetail.ts +25 -0
  716. package/Types/Telemetry/ResourceEntityFacet.ts +17 -10
  717. package/Types/Telemetry/ResourceFacetCatalog.ts +171 -0
  718. package/Types/Telemetry/ServiceType.ts +7 -0
  719. package/Typings/Index.d.ts +53 -3
  720. package/UI/Components/AlertBanner/AlertBanner.tsx +85 -27
  721. package/UI/Components/BulkUpdate/BulkArchiveActions.tsx +21 -2
  722. package/UI/Components/Card/Card.tsx +118 -83
  723. package/UI/Components/Checkbox/Checkbox.tsx +8 -5
  724. package/UI/Components/CopyTextButton/CopyTextButton.tsx +7 -1
  725. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +4 -1
  726. package/UI/Components/Detail/Detail.tsx +145 -23
  727. package/UI/Components/Dropdown/Dropdown.tsx +64 -1
  728. package/UI/Components/EmptyState/EmptyState.tsx +27 -3
  729. package/UI/Components/EmptyState/EmptyStateGuideLinks.tsx +124 -0
  730. package/UI/Components/ErrorBoundary.tsx +38 -0
  731. package/UI/Components/Forms/ModelForm.tsx +6 -5
  732. package/UI/Components/LabelRule/LabelRuleTable.tsx +8 -2
  733. package/UI/Components/LogsViewer/LogsEntityNames.ts +912 -0
  734. package/UI/Components/LogsViewer/LogsViewer.tsx +152 -77
  735. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +17 -11
  736. package/UI/Components/LogsViewer/components/FacetSection.tsx +66 -79
  737. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +17 -2
  738. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +45 -5
  739. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +100 -19
  740. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +156 -39
  741. package/UI/Components/LogsViewer/components/LogsTable.tsx +30 -8
  742. package/UI/Components/LogsViewer/types.ts +6 -0
  743. package/UI/Components/Markdown.tsx/InlineReferences.tsx +628 -0
  744. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +385 -312
  745. package/UI/Components/MemberRoleAssignment/MemberRoleAssignment.tsx +50 -22
  746. package/UI/Components/ModelDetail/CardModelDetail.tsx +15 -0
  747. package/UI/Components/ModelDetail/ModelDetail.tsx +64 -19
  748. package/UI/Components/ModelList/ModelList.tsx +33 -8
  749. package/UI/Components/MoreMenu/MoreMenu.tsx +14 -3
  750. package/UI/Components/Page/ModelPage.tsx +119 -54
  751. package/UI/Components/RuleRun/RuleDetailFields.tsx +359 -0
  752. package/UI/Components/RuleRun/RuleTable.tsx +183 -0
  753. package/UI/Components/RuleRun/RuleView.tsx +170 -0
  754. package/UI/Components/RuleRun/RunRuleNowModal.tsx +147 -0
  755. package/UI/Components/RuleRun/RunRulesBulkAction.ts +103 -0
  756. package/UI/Components/Table/Table.tsx +8 -0
  757. package/UI/Components/Table/TableRow.tsx +13 -5
  758. package/UI/Components/TelemetryViewer/FacetVisibility.ts +229 -0
  759. package/UI/Components/TelemetryViewer/ResourceFacetConfigs.ts +46 -0
  760. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +7 -0
  761. package/UI/Components/TelemetryViewer/components/FacetSearchInput.tsx +79 -0
  762. package/UI/Components/TelemetryViewer/components/FacetSectionHeader.tsx +69 -0
  763. package/UI/Components/TelemetryViewer/components/FacetShowMoreButton.tsx +36 -0
  764. package/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.tsx +88 -0
  765. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +430 -0
  766. package/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.tsx +14 -10
  767. package/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.tsx +203 -10
  768. package/UI/Components/TelemetryViewer/components/TelemetryFacetSection.tsx +60 -73
  769. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +94 -7
  770. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +17 -2
  771. package/UI/Components/TelemetryViewer/types.ts +19 -0
  772. package/UI/Components/TelemetryViewer/useFacetSearchExemptions.ts +158 -0
  773. package/UI/Components/TelemetryViewer/useFacetSectionSearch.ts +102 -0
  774. package/UI/Utils/Clipboard.ts +68 -2
  775. package/UI/Utils/Navigation.ts +17 -2
  776. package/UI/Utils/Rules/RuleRunClient.ts +178 -0
  777. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +628 -0
  778. package/UI/Utils/Telemetry/UseTelemetryEntityNames.ts +151 -0
  779. package/Utils/AI/InvestigationReport.ts +1789 -0
  780. package/Utils/API.ts +90 -1
  781. package/Utils/Dashboard/Components/DashboardSloComponent.ts +19 -2
  782. package/Utils/Dashboard/Components/DashboardSloListComponent.ts +126 -0
  783. package/Utils/Dashboard/Components/Index.ts +7 -0
  784. package/Utils/Dashboard/SloWidgetSource.ts +188 -0
  785. package/Utils/Markdown/MarkdownEscape.ts +63 -0
  786. package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +161 -36
  787. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +25 -0
  788. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +146 -0
  789. package/Utils/NetworkDiscovery/RunImportedDeviceNaming.ts +390 -0
  790. package/Utils/NetworkDiscovery/ShortDeviceNamePlanner.ts +291 -0
  791. package/Utils/NetworkDiscovery/ShortHostnameUtil.ts +91 -0
  792. package/Utils/NetworkSite/CidrMatchUtil.ts +37 -12
  793. package/Utils/Project/DataResidency.ts +63 -0
  794. package/Utils/Rules/RuleRunSummary.ts +323 -0
  795. package/Utils/Rum/SessionReplayHealth.ts +11 -0
  796. package/Utils/Rum/SessionReplayRecordingEnded.ts +134 -0
  797. package/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.ts +427 -0
  798. package/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.ts +197 -0
  799. package/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.ts +464 -0
  800. package/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.ts +567 -0
  801. package/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.ts +299 -0
  802. package/Utils/SecurityEvent/Connectors/OktaNormalizer.ts +467 -0
  803. package/Utils/SecurityEvent/Connectors/SplunkNormalizer.ts +439 -0
  804. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +124 -12
  805. package/Utils/Slo/SloAffectedResourceMarkdown.ts +123 -0
  806. package/Utils/Slo/SloBurnRateTemplate.ts +373 -0
  807. package/Utils/Slo/SloFeedMarkdown.ts +1507 -0
  808. package/Utils/Slo/SloHealth.ts +96 -9
  809. package/Utils/Slo/SloListWidgetFormat.ts +370 -0
  810. package/Utils/Slo/SloMetricType.ts +237 -0
  811. package/Utils/Slo/SloMonitorRuleCriteria.ts +364 -0
  812. package/Utils/Slo/SloMonitorStatusSummary.ts +210 -0
  813. package/Utils/Slo/SloOverviewText.ts +265 -0
  814. package/Utils/Slo/SloProjection.ts +967 -0
  815. package/Utils/Slo/SloWidgetFormat.ts +12 -2
  816. package/Utils/Telemetry/CrossSignalScope.ts +22 -8
  817. package/Utils/Telemetry/EntityRelationship.ts +8 -0
  818. package/Utils/Telemetry/LockedFilterSearch.ts +366 -0
  819. package/Utils/Telemetry/NetworkHost.ts +43 -0
  820. package/Utils/Telemetry/OriginAllowList.ts +139 -4
  821. package/Utils/Traces/CriticalPath.ts +10 -4
  822. package/build/dist/Models/AnalyticsModels/AuditLog.js +70 -1
  823. package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
  824. package/build/dist/Models/AnalyticsModels/SecurityEvent.js +4 -4
  825. package/build/dist/Models/DatabaseModels/Alert.js +60 -0
  826. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  827. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  828. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  829. package/build/dist/Models/DatabaseModels/Incident.js +64 -0
  830. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  831. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +75 -0
  832. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  833. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +75 -0
  834. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  835. package/build/dist/Models/DatabaseModels/Index.js +10 -4
  836. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  837. package/build/dist/Models/DatabaseModels/NetworkDevice.js +68 -0
  838. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  839. package/build/dist/Models/DatabaseModels/NetworkDeviceDiagnostic.js +772 -0
  840. package/build/dist/Models/DatabaseModels/NetworkDeviceDiagnostic.js.map +1 -0
  841. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +140 -0
  842. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  843. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js +1 -1
  844. package/build/dist/Models/DatabaseModels/NetworkSiteAssignmentRule.js.map +1 -1
  845. package/build/dist/Models/DatabaseModels/Project.js +42 -0
  846. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  847. package/build/dist/Models/DatabaseModels/RumApplication.js +1 -1
  848. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  849. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +75 -0
  850. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  851. package/build/dist/Models/DatabaseModels/{GoogleSecOpsConnection.js → SecurityEventConnection.js} +87 -75
  852. package/build/dist/Models/DatabaseModels/SecurityEventConnection.js.map +1 -0
  853. package/build/dist/Models/DatabaseModels/{GoogleSecOpsConnectionRun.js → SecurityEventConnectionRun.js} +37 -37
  854. package/build/dist/Models/DatabaseModels/SecurityEventConnectionRun.js.map +1 -0
  855. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +192 -10
  856. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  857. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js +798 -96
  858. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveBurnRateRule.js.map +1 -1
  859. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +596 -0
  860. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -0
  861. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +590 -0
  862. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -0
  863. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js +16 -0
  864. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam.js.map +1 -1
  865. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js +16 -0
  866. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerUser.js.map +1 -1
  867. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +75 -0
  868. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  869. package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +2 -2
  870. package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
  871. package/build/dist/Models/DatabaseModels/ThreatIntelFeed.js +1 -1
  872. package/build/dist/Server/API/AIInvestigationAPI.js +336 -27
  873. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  874. package/build/dist/Server/API/BaseAPI.js +14 -0
  875. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  876. package/build/dist/Server/API/BillingInvoiceAPI.js +233 -19
  877. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  878. package/build/dist/Server/API/BillingPaymentMethodAPI.js +86 -5
  879. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  880. package/build/dist/Server/API/DashboardAPI.js +72 -2
  881. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  882. package/build/dist/Server/API/SecurityEventConnectionAPI.js +263 -0
  883. package/build/dist/Server/API/SecurityEventConnectionAPI.js.map +1 -0
  884. package/build/dist/Server/API/StatusPageAPI.js +236 -154
  885. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  886. package/build/dist/Server/API/TelemetryAPI.js +248 -170
  887. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  888. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.js +40 -0
  889. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792700000000-AddSecurityEventConnections.js.map +1 -0
  890. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792800000000-AddNetworkDeviceDiagnostic.js +34 -0
  891. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792800000000-AddNetworkDeviceDiagnostic.js.map +1 -0
  892. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792900000000-AddNetworkDeviceDnsNameAndShortDeviceNames.js +24 -0
  893. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792900000000-AddNetworkDeviceDnsNameAndShortDeviceNames.js.map +1 -0
  894. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793000000000-AddNetbiosLookupToNetworkDeviceDiscoveryScan.js +21 -0
  895. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793000000000-AddNetbiosLookupToNetworkDeviceDiscoveryScan.js.map +1 -0
  896. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793100000000-SloProductOverhaul.js +207 -0
  897. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793100000000-SloProductOverhaul.js.map +1 -0
  898. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793200000000-BackfillSloMonitorRulesAndAffectedResources.js +72 -0
  899. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793200000000-BackfillSloMonitorRulesAndAffectedResources.js.map +1 -0
  900. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793300000000-AddDataResidencyToProject.js +20 -0
  901. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793300000000-AddDataResidencyToProject.js.map +1 -0
  902. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793400000000-AddSubscriberUpdateNotificationStatus.js +46 -0
  903. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1793400000000-AddSubscriberUpdateNotificationStatus.js.map +1 -0
  904. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
  905. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  906. package/build/dist/Server/Middleware/TelemetryIngest.js +57 -4
  907. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  908. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js +112 -56
  909. package/build/dist/Server/Services/AlertEpisodeLabelRuleEngineService.js.map +1 -1
  910. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js +167 -68
  911. package/build/dist/Server/Services/AlertEpisodeOwnerRuleEngineService.js.map +1 -1
  912. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js +87 -26
  913. package/build/dist/Server/Services/AlertEpisodePrivacyRuleEngineService.js.map +1 -1
  914. package/build/dist/Server/Services/AlertLabelRuleEngineService.js +270 -203
  915. package/build/dist/Server/Services/AlertLabelRuleEngineService.js.map +1 -1
  916. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js +498 -354
  917. package/build/dist/Server/Services/AlertOwnerRuleEngineService.js.map +1 -1
  918. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js +97 -34
  919. package/build/dist/Server/Services/AlertPrivacyRuleEngineService.js.map +1 -1
  920. package/build/dist/Server/Services/AlertService.js +91 -8
  921. package/build/dist/Server/Services/AlertService.js.map +1 -1
  922. package/build/dist/Server/Services/AuditLogService.js +408 -44
  923. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  924. package/build/dist/Server/Services/BillingInvoiceService.js +94 -85
  925. package/build/dist/Server/Services/BillingInvoiceService.js.map +1 -1
  926. package/build/dist/Server/Services/BillingService.js +406 -14
  927. package/build/dist/Server/Services/BillingService.js.map +1 -1
  928. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js +141 -92
  929. package/build/dist/Server/Services/CephClusterLabelRuleEngineService.js.map +1 -1
  930. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js +189 -101
  931. package/build/dist/Server/Services/CephClusterOwnerRuleEngineService.js.map +1 -1
  932. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js +135 -86
  933. package/build/dist/Server/Services/CloudResourceLabelRuleEngineService.js.map +1 -1
  934. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js +189 -100
  935. package/build/dist/Server/Services/CloudResourceOwnerRuleEngineService.js.map +1 -1
  936. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js +117 -69
  937. package/build/dist/Server/Services/DashboardLabelRuleEngineService.js.map +1 -1
  938. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js +171 -83
  939. package/build/dist/Server/Services/DashboardOwnerRuleEngineService.js.map +1 -1
  940. package/build/dist/Server/Services/DatabaseService.js +125 -18
  941. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  942. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js +141 -92
  943. package/build/dist/Server/Services/DockerHostLabelRuleEngineService.js.map +1 -1
  944. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js +189 -101
  945. package/build/dist/Server/Services/DockerHostOwnerRuleEngineService.js.map +1 -1
  946. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js +145 -91
  947. package/build/dist/Server/Services/DockerSwarmClusterLabelRuleEngineService.js.map +1 -1
  948. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js +191 -101
  949. package/build/dist/Server/Services/DockerSwarmClusterOwnerRuleEngineService.js.map +1 -1
  950. package/build/dist/Server/Services/ExceptionAggregationService.js +7 -16
  951. package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
  952. package/build/dist/Server/Services/HostLabelRuleEngineService.js +141 -92
  953. package/build/dist/Server/Services/HostLabelRuleEngineService.js.map +1 -1
  954. package/build/dist/Server/Services/HostOwnerRuleEngineService.js +190 -102
  955. package/build/dist/Server/Services/HostOwnerRuleEngineService.js.map +1 -1
  956. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js +112 -56
  957. package/build/dist/Server/Services/IncidentEpisodeLabelRuleEngineService.js.map +1 -1
  958. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js +220 -63
  959. package/build/dist/Server/Services/IncidentEpisodeOwnerRuleEngineService.js.map +1 -1
  960. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js +87 -26
  961. package/build/dist/Server/Services/IncidentEpisodePrivacyRuleEngineService.js.map +1 -1
  962. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  963. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  964. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js +258 -192
  965. package/build/dist/Server/Services/IncidentLabelRuleEngineService.js.map +1 -1
  966. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js +519 -376
  967. package/build/dist/Server/Services/IncidentOwnerRuleEngineService.js.map +1 -1
  968. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js +95 -32
  969. package/build/dist/Server/Services/IncidentPrivacyRuleEngineService.js.map +1 -1
  970. package/build/dist/Server/Services/IncidentPublicNoteService.js +24 -0
  971. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  972. package/build/dist/Server/Services/IncidentService.js +90 -5
  973. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  974. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js +113 -65
  975. package/build/dist/Server/Services/IncomingCallPolicyLabelRuleEngineService.js.map +1 -1
  976. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js +171 -83
  977. package/build/dist/Server/Services/IncomingCallPolicyOwnerRuleEngineService.js.map +1 -1
  978. package/build/dist/Server/Services/Index.js +10 -4
  979. package/build/dist/Server/Services/Index.js.map +1 -1
  980. package/build/dist/Server/Services/InventoryItemService.js +22 -0
  981. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  982. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js +117 -69
  983. package/build/dist/Server/Services/IoTFleetLabelRuleEngineService.js.map +1 -1
  984. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js +171 -83
  985. package/build/dist/Server/Services/IoTFleetOwnerRuleEngineService.js.map +1 -1
  986. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js +145 -91
  987. package/build/dist/Server/Services/KubernetesClusterLabelRuleEngineService.js.map +1 -1
  988. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js +191 -101
  989. package/build/dist/Server/Services/KubernetesClusterOwnerRuleEngineService.js.map +1 -1
  990. package/build/dist/Server/Services/LabelService.js +20 -25
  991. package/build/dist/Server/Services/LabelService.js.map +1 -1
  992. package/build/dist/Server/Services/LogAggregationService.js +8 -17
  993. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  994. package/build/dist/Server/Services/MetricAggregationService.js +4 -14
  995. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  996. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js +123 -77
  997. package/build/dist/Server/Services/MonitorLabelRuleEngineService.js.map +1 -1
  998. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js +167 -82
  999. package/build/dist/Server/Services/MonitorOwnerRuleEngineService.js.map +1 -1
  1000. package/build/dist/Server/Services/MonitorProbeService.js +18 -2
  1001. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  1002. package/build/dist/Server/Services/MonitorService.js +11 -9
  1003. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  1004. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +287 -5
  1005. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  1006. package/build/dist/Server/Services/NetworkDeviceDiagnosticService.js +333 -0
  1007. package/build/dist/Server/Services/NetworkDeviceDiagnosticService.js.map +1 -0
  1008. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +117 -69
  1009. package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
  1010. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js +171 -83
  1011. package/build/dist/Server/Services/NetworkDeviceOwnerRuleEngineService.js.map +1 -1
  1012. package/build/dist/Server/Services/NetworkDeviceService.js +36 -2
  1013. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  1014. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js +117 -64
  1015. package/build/dist/Server/Services/OnCallDutyPolicyLabelRuleEngineService.js.map +1 -1
  1016. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js +173 -83
  1017. package/build/dist/Server/Services/OnCallDutyPolicyOwnerRuleEngineService.js.map +1 -1
  1018. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js +116 -65
  1019. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLabelRuleEngineService.js.map +1 -1
  1020. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js +171 -83
  1021. package/build/dist/Server/Services/OnCallDutyPolicyScheduleOwnerRuleEngineService.js.map +1 -1
  1022. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js +141 -92
  1023. package/build/dist/Server/Services/PodmanHostLabelRuleEngineService.js.map +1 -1
  1024. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js +189 -101
  1025. package/build/dist/Server/Services/PodmanHostOwnerRuleEngineService.js.map +1 -1
  1026. package/build/dist/Server/Services/ProjectService.js +82 -10
  1027. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  1028. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js +141 -92
  1029. package/build/dist/Server/Services/ProxmoxClusterLabelRuleEngineService.js.map +1 -1
  1030. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js +189 -101
  1031. package/build/dist/Server/Services/ProxmoxClusterOwnerRuleEngineService.js.map +1 -1
  1032. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js +111 -63
  1033. package/build/dist/Server/Services/RumApplicationLabelRuleEngineService.js.map +1 -1
  1034. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js +161 -77
  1035. package/build/dist/Server/Services/RumApplicationOwnerRuleEngineService.js.map +1 -1
  1036. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js +117 -69
  1037. package/build/dist/Server/Services/RunbookLabelRuleEngineService.js.map +1 -1
  1038. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js +171 -83
  1039. package/build/dist/Server/Services/RunbookOwnerRuleEngineService.js.map +1 -1
  1040. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js +280 -209
  1041. package/build/dist/Server/Services/ScheduledMaintenanceLabelRuleEngineService.js.map +1 -1
  1042. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js +521 -376
  1043. package/build/dist/Server/Services/ScheduledMaintenanceOwnerRuleEngineService.js.map +1 -1
  1044. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +24 -0
  1045. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  1046. package/build/dist/Server/Services/ScheduledMaintenanceService.js +24 -8
  1047. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  1048. package/build/dist/Server/Services/{GoogleSecOpsConnectionRunService.js → SecurityEventConnectionRunService.js} +2 -2
  1049. package/build/dist/Server/Services/SecurityEventConnectionRunService.js.map +1 -0
  1050. package/build/dist/Server/Services/SecurityEventConnectionService.js +396 -0
  1051. package/build/dist/Server/Services/SecurityEventConnectionService.js.map +1 -0
  1052. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js +115 -62
  1053. package/build/dist/Server/Services/ServerlessFunctionLabelRuleEngineService.js.map +1 -1
  1054. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js +163 -77
  1055. package/build/dist/Server/Services/ServerlessFunctionOwnerRuleEngineService.js.map +1 -1
  1056. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js +141 -92
  1057. package/build/dist/Server/Services/ServiceLabelRuleEngineService.js.map +1 -1
  1058. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js +769 -18
  1059. package/build/dist/Server/Services/ServiceLevelObjectiveBurnRateRuleService.js.map +1 -1
  1060. package/build/dist/Server/Services/ServiceLevelObjectiveFeedService.js +96 -0
  1061. package/build/dist/Server/Services/ServiceLevelObjectiveFeedService.js.map +1 -0
  1062. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +614 -87
  1063. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  1064. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +844 -0
  1065. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -0
  1066. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerTeamService.js +207 -1
  1067. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerTeamService.js.map +1 -1
  1068. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerUserService.js +221 -1
  1069. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerUserService.js.map +1 -1
  1070. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +997 -50
  1071. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  1072. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js +191 -102
  1073. package/build/dist/Server/Services/ServiceOwnerRuleEngineService.js.map +1 -1
  1074. package/build/dist/Server/Services/StatusPageAnnouncementService.js +12 -0
  1075. package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
  1076. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js +118 -70
  1077. package/build/dist/Server/Services/StatusPageLabelRuleEngineService.js.map +1 -1
  1078. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js +158 -76
  1079. package/build/dist/Server/Services/StatusPageOwnerRuleEngineService.js.map +1 -1
  1080. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +19 -215
  1081. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  1082. package/build/dist/Server/Services/StatusPageSubscriberService.js +6 -0
  1083. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  1084. package/build/dist/Server/Services/TelemetryUsageBillingService.js +27 -5
  1085. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  1086. package/build/dist/Server/Services/ThreatIntelFeedService.js +1 -1
  1087. package/build/dist/Server/Services/TraceAggregationService.js +51 -25
  1088. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  1089. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js +141 -92
  1090. package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js.map +1 -1
  1091. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js +189 -101
  1092. package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js.map +1 -1
  1093. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js +117 -69
  1094. package/build/dist/Server/Services/WorkflowLabelRuleEngineService.js.map +1 -1
  1095. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js +171 -83
  1096. package/build/dist/Server/Services/WorkflowOwnerRuleEngineService.js.map +1 -1
  1097. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  1098. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  1099. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +17 -0
  1100. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  1101. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +3 -0
  1102. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  1103. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +31 -0
  1104. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  1105. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +14 -7
  1106. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  1107. package/build/dist/Server/Utils/AI/SRE/InvestigationEvidence.js +468 -0
  1108. package/build/dist/Server/Utils/AI/SRE/InvestigationEvidence.js.map +1 -0
  1109. package/build/dist/Server/Utils/AI/SRE/InvestigationReferences.js +230 -0
  1110. package/build/dist/Server/Utils/AI/SRE/InvestigationReferences.js.map +1 -0
  1111. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +24 -1
  1112. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  1113. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +21 -1
  1114. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  1115. package/build/dist/Server/Utils/AI/Toolbox/Index.js +32 -0
  1116. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  1117. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +337 -53
  1118. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  1119. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +6 -2
  1120. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -1
  1121. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +59 -0
  1122. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  1123. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +108 -15
  1124. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  1125. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +2 -2
  1126. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -1
  1127. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +115 -6
  1128. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -1
  1129. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +13 -0
  1130. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  1131. package/build/dist/Server/Utils/Database/RelationValueUtil.js +82 -0
  1132. package/build/dist/Server/Utils/Database/RelationValueUtil.js.map +1 -0
  1133. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +10 -6
  1134. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  1135. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +17 -21
  1136. package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
  1137. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +5 -3
  1138. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  1139. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +5 -4
  1140. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  1141. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +50 -10
  1142. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  1143. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +13 -1
  1144. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  1145. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js +6 -10
  1146. package/build/dist/Server/Utils/Monitor/NetworkDeviceMetricUtil.js.map +1 -1
  1147. package/build/dist/Server/Utils/Rules/MonitorRulePatternValidator.js +34 -0
  1148. package/build/dist/Server/Utils/Rules/MonitorRulePatternValidator.js.map +1 -0
  1149. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js +69 -0
  1150. package/build/dist/Server/Utils/Rules/OwnerRuleAssignment.js.map +1 -0
  1151. package/build/dist/Server/Utils/Rules/RuleRun/RuleApplication.js +25 -0
  1152. package/build/dist/Server/Utils/Rules/RuleRun/RuleApplication.js.map +1 -0
  1153. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunPermission.js +128 -0
  1154. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunPermission.js.map +1 -0
  1155. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +746 -0
  1156. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -0
  1157. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunner.js +221 -0
  1158. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunner.js.map +1 -0
  1159. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js +3 -2
  1160. package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js.map +1 -1
  1161. package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.js +752 -0
  1162. package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubClient.js.map +1 -0
  1163. package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.js +493 -0
  1164. package/build/dist/Server/Utils/SecurityEvent/Connectors/AwsSecurityHub/AwsSecurityHubConnector.js.map +1 -0
  1165. package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js +354 -0
  1166. package/build/dist/Server/Utils/SecurityEvent/Connectors/ConnectorPlatformHealth.js.map +1 -0
  1167. package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.js +405 -0
  1168. package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconClient.js.map +1 -0
  1169. package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.js +466 -0
  1170. package/build/dist/Server/Utils/SecurityEvent/Connectors/CrowdStrikeFalcon/CrowdStrikeFalconConnector.js.map +1 -0
  1171. package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.js +344 -0
  1172. package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityClient.js.map +1 -0
  1173. package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.js +511 -0
  1174. package/build/dist/Server/Utils/SecurityEvent/Connectors/ElasticSecurity/ElasticSecurityConnector.js.map +1 -0
  1175. package/build/dist/Server/Utils/SecurityEvent/{GoogleSecOps → Connectors/GoogleSecOps}/GoogleSecOpsClient.js +401 -68
  1176. package/build/dist/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsClient.js.map +1 -0
  1177. package/build/dist/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.js +1417 -0
  1178. package/build/dist/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnector.js.map +1 -0
  1179. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.js +406 -0
  1180. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrClient.js.map +1 -0
  1181. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.js +336 -0
  1182. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdr/MicrosoftDefenderXdrConnector.js.map +1 -0
  1183. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.js +474 -0
  1184. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelClient.js.map +1 -0
  1185. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.js +417 -0
  1186. package/build/dist/Server/Utils/SecurityEvent/Connectors/MicrosoftSentinel/MicrosoftSentinelConnector.js.map +1 -0
  1187. package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.js +314 -0
  1188. package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaClient.js.map +1 -0
  1189. package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.js +494 -0
  1190. package/build/dist/Server/Utils/SecurityEvent/Connectors/Okta/OktaConnector.js.map +1 -0
  1191. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js +839 -0
  1192. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller.js.map +1 -0
  1193. package/build/dist/Server/Utils/SecurityEvent/{GoogleSecOps/GoogleSecOpsRunExecutor.js → Connectors/SecurityEventConnectionRunExecutor.js} +77 -50
  1194. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionRunExecutor.js.map +1 -0
  1195. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js +214 -0
  1196. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionTester.js.map +1 -0
  1197. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js +42 -0
  1198. package/build/dist/Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry.js.map +1 -0
  1199. package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.js +494 -0
  1200. package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkClient.js.map +1 -0
  1201. package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.js +379 -0
  1202. package/build/dist/Server/Utils/SecurityEvent/Connectors/Splunk/SplunkConnector.js.map +1 -0
  1203. package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js +122 -0
  1204. package/build/dist/Server/Utils/SecurityEvent/Connectors/Types.js.map +1 -0
  1205. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +1 -1
  1206. package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js +52 -0
  1207. package/build/dist/Server/Utils/SecurityEvent/SecurityEventDedupe.js.map +1 -0
  1208. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +2 -2
  1209. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +599 -32
  1210. package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
  1211. package/build/dist/Server/Utils/Slo/SloFeedUtil.js +150 -0
  1212. package/build/dist/Server/Utils/Slo/SloFeedUtil.js.map +1 -0
  1213. package/build/dist/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.js +136 -0
  1214. package/build/dist/Server/Utils/Slo/SloLegacyMonitorLabelAdoption.js.map +1 -0
  1215. package/build/dist/Server/Utils/Slo/SloMetricUtil.js +269 -0
  1216. package/build/dist/Server/Utils/Slo/SloMetricUtil.js.map +1 -0
  1217. package/build/dist/Server/Utils/Slo/SloOwnerReferenceValidator.js +134 -0
  1218. package/build/dist/Server/Utils/Slo/SloOwnerReferenceValidator.js.map +1 -0
  1219. package/build/dist/Server/Utils/Slo/SloRecordReferenceValidator.js +137 -0
  1220. package/build/dist/Server/Utils/Slo/SloRecordReferenceValidator.js.map +1 -0
  1221. package/build/dist/Server/Utils/StartServer.js +10 -0
  1222. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  1223. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +6 -26
  1224. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -1
  1225. package/build/dist/Server/Utils/StatusPageResource.js +31 -5
  1226. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  1227. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +8 -0
  1228. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  1229. package/build/dist/Server/Utils/Telemetry/KubernetesResourceAttributes.js +80 -0
  1230. package/build/dist/Server/Utils/Telemetry/KubernetesResourceAttributes.js.map +1 -0
  1231. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +122 -67
  1232. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  1233. package/build/dist/Server/Utils/Telemetry/ResourceFacetPlanner.js +146 -0
  1234. package/build/dist/Server/Utils/Telemetry/ResourceFacetPlanner.js.map +1 -0
  1235. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +174 -248
  1236. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  1237. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +492 -0
  1238. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -0
  1239. package/build/dist/Server/Utils/Telemetry/TraceContextPropagation.js +259 -0
  1240. package/build/dist/Server/Utils/Telemetry/TraceContextPropagation.js.map +1 -0
  1241. package/build/dist/Server/Utils/Telemetry.js +11 -5
  1242. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  1243. package/build/dist/Types/AI/InvestigationEvidence.js +2 -0
  1244. package/build/dist/Types/AI/InvestigationEvidence.js.map +1 -0
  1245. package/build/dist/Types/Billing/SubscriptionStatus.js +37 -3
  1246. package/build/dist/Types/Billing/SubscriptionStatus.js.map +1 -1
  1247. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  1248. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  1249. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +5 -0
  1250. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  1251. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSloListComponent.js +2 -0
  1252. package/build/dist/Types/Dashboard/DashboardComponents/DashboardSloListComponent.js.map +1 -0
  1253. package/build/dist/Types/Dashboard/DashboardTemplates.js +275 -286
  1254. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  1255. package/build/dist/Types/Database/EnableAuditLog.js +19 -1
  1256. package/build/dist/Types/Database/EnableAuditLog.js.map +1 -1
  1257. package/build/dist/Types/Email/EmailTemplateType.js +4 -0
  1258. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  1259. package/build/dist/Types/Monitor/CriteriaFilter.js +45 -6
  1260. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  1261. package/build/dist/Types/Monitor/MonitorStep.js +2 -0
  1262. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  1263. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js +3 -1
  1264. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js.map +1 -1
  1265. package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js +3 -1
  1266. package/build/dist/Types/Monitor/MonitorStepDnssecMonitor.js.map +1 -1
  1267. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js +3 -1
  1268. package/build/dist/Types/Monitor/MonitorStepDomainMonitor.js.map +1 -1
  1269. package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js +3 -1
  1270. package/build/dist/Types/Monitor/MonitorStepExternalStatusPageMonitor.js.map +1 -1
  1271. package/build/dist/Types/Monitor/MonitorStepRetries.js +47 -0
  1272. package/build/dist/Types/Monitor/MonitorStepRetries.js.map +1 -0
  1273. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js +3 -1
  1274. package/build/dist/Types/Monitor/MonitorStepSnmpMonitor.js.map +1 -1
  1275. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticResult.js +29 -0
  1276. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticResult.js.map +1 -0
  1277. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticStatus.js +37 -0
  1278. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticStatus.js.map +1 -0
  1279. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticType.js +42 -0
  1280. package/build/dist/Types/NetworkDevice/NetworkDeviceDiagnosticType.js.map +1 -0
  1281. package/build/dist/Types/NetworkDevice/NetworkDevicePingMetricNames.js +20 -0
  1282. package/build/dist/Types/NetworkDevice/NetworkDevicePingMetricNames.js.map +1 -0
  1283. package/build/dist/Types/Permission.js +116 -2
  1284. package/build/dist/Types/Permission.js.map +1 -1
  1285. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +5 -0
  1286. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  1287. package/build/dist/Types/Rules/RuleRun.js +267 -0
  1288. package/build/dist/Types/Rules/RuleRun.js.map +1 -0
  1289. package/build/dist/Types/Rum/SessionReplay.js +95 -0
  1290. package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
  1291. package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -1
  1292. package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js +18 -0
  1293. package/build/dist/Types/SecurityEvent/Connectors/ConnectorDiagnostics.js.map +1 -0
  1294. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js +17 -0
  1295. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics.js.map +1 -0
  1296. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js +439 -0
  1297. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog.js.map +1 -0
  1298. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js +39 -0
  1299. package/build/dist/Types/SecurityEvent/Connectors/SecurityEventConnectorProvider.js.map +1 -0
  1300. package/build/dist/Types/SecurityEvent/GoogleSecOpsRegion.js +35 -0
  1301. package/build/dist/Types/SecurityEvent/GoogleSecOpsRegion.js.map +1 -0
  1302. package/build/dist/Types/ServiceLevelObjective/SloHistoryMetricName.js +26 -0
  1303. package/build/dist/Types/ServiceLevelObjective/SloHistoryMetricName.js.map +1 -0
  1304. package/build/dist/Types/ServiceLevelObjective/SloMetricType.js +45 -0
  1305. package/build/dist/Types/ServiceLevelObjective/SloMetricType.js.map +1 -0
  1306. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js +4 -0
  1307. package/build/dist/Types/StatusPage/StatusPageSubscriberNotificationEventType.js.map +1 -1
  1308. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +295 -0
  1309. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -0
  1310. package/build/dist/Types/StatusPage/SubscriberNotificationTrigger.js +13 -0
  1311. package/build/dist/Types/StatusPage/SubscriberNotificationTrigger.js.map +1 -0
  1312. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +67 -0
  1313. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -0
  1314. package/build/dist/Types/Telemetry/EntityType.js +14 -0
  1315. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  1316. package/build/dist/Types/Telemetry/ExceptionSpanScope.js +59 -0
  1317. package/build/dist/Types/Telemetry/ExceptionSpanScope.js.map +1 -0
  1318. package/build/dist/Types/Telemetry/LockedFilterDetail.js +16 -0
  1319. package/build/dist/Types/Telemetry/LockedFilterDetail.js.map +1 -0
  1320. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +16 -10
  1321. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -1
  1322. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +119 -0
  1323. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -0
  1324. package/build/dist/Types/Telemetry/ServiceType.js +7 -0
  1325. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  1326. package/build/dist/UI/Components/AlertBanner/AlertBanner.js +48 -19
  1327. package/build/dist/UI/Components/AlertBanner/AlertBanner.js.map +1 -1
  1328. package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js +10 -2
  1329. package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js.map +1 -1
  1330. package/build/dist/UI/Components/Card/Card.js +34 -17
  1331. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  1332. package/build/dist/UI/Components/Checkbox/Checkbox.js +6 -4
  1333. package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
  1334. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
  1335. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
  1336. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +1 -1
  1337. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  1338. package/build/dist/UI/Components/Detail/Detail.js +74 -15
  1339. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  1340. package/build/dist/UI/Components/Dropdown/Dropdown.js +33 -5
  1341. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  1342. package/build/dist/UI/Components/EmptyState/EmptyState.js +3 -3
  1343. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  1344. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js +47 -0
  1345. package/build/dist/UI/Components/EmptyState/EmptyStateGuideLinks.js.map +1 -0
  1346. package/build/dist/UI/Components/ErrorBoundary.js +34 -0
  1347. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  1348. package/build/dist/UI/Components/Forms/ModelForm.js +6 -5
  1349. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  1350. package/build/dist/UI/Components/LabelRule/LabelRuleTable.js +6 -2
  1351. package/build/dist/UI/Components/LabelRule/LabelRuleTable.js.map +1 -1
  1352. package/build/dist/UI/Components/LogsViewer/LogsEntityNames.js +535 -0
  1353. package/build/dist/UI/Components/LogsViewer/LogsEntityNames.js.map +1 -0
  1354. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +117 -57
  1355. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  1356. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +3 -7
  1357. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  1358. package/build/dist/UI/Components/LogsViewer/components/FacetSection.js +31 -44
  1359. package/build/dist/UI/Components/LogsViewer/components/FacetSection.js.map +1 -1
  1360. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +17 -3
  1361. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  1362. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +19 -3
  1363. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  1364. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +64 -18
  1365. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  1366. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +100 -41
  1367. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  1368. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +13 -5
  1369. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  1370. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  1371. package/build/dist/UI/Components/Markdown.tsx/InlineReferences.js +403 -0
  1372. package/build/dist/UI/Components/Markdown.tsx/InlineReferences.js.map +1 -0
  1373. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +197 -179
  1374. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  1375. package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js +30 -21
  1376. package/build/dist/UI/Components/MemberRoleAssignment/MemberRoleAssignment.js.map +1 -1
  1377. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +12 -0
  1378. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  1379. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +46 -12
  1380. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  1381. package/build/dist/UI/Components/ModelList/ModelList.js +26 -7
  1382. package/build/dist/UI/Components/ModelList/ModelList.js.map +1 -1
  1383. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +3 -1
  1384. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  1385. package/build/dist/UI/Components/Page/ModelPage.js +62 -34
  1386. package/build/dist/UI/Components/Page/ModelPage.js.map +1 -1
  1387. package/build/dist/UI/Components/RuleRun/RuleDetailFields.js +226 -0
  1388. package/build/dist/UI/Components/RuleRun/RuleDetailFields.js.map +1 -0
  1389. package/build/dist/UI/Components/RuleRun/RuleTable.js +97 -0
  1390. package/build/dist/UI/Components/RuleRun/RuleTable.js.map +1 -0
  1391. package/build/dist/UI/Components/RuleRun/RuleView.js +83 -0
  1392. package/build/dist/UI/Components/RuleRun/RuleView.js.map +1 -0
  1393. package/build/dist/UI/Components/RuleRun/RunRuleNowModal.js +74 -0
  1394. package/build/dist/UI/Components/RuleRun/RunRuleNowModal.js.map +1 -0
  1395. package/build/dist/UI/Components/RuleRun/RunRulesBulkAction.js +78 -0
  1396. package/build/dist/UI/Components/RuleRun/RunRulesBulkAction.js.map +1 -0
  1397. package/build/dist/UI/Components/Table/Table.js +3 -1
  1398. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  1399. package/build/dist/UI/Components/Table/TableRow.js +8 -1
  1400. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  1401. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +111 -0
  1402. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -0
  1403. package/build/dist/UI/Components/TelemetryViewer/ResourceFacetConfigs.js +22 -0
  1404. package/build/dist/UI/Components/TelemetryViewer/ResourceFacetConfigs.js.map +1 -0
  1405. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  1406. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  1407. package/build/dist/UI/Components/TelemetryViewer/components/FacetSearchInput.js +36 -0
  1408. package/build/dist/UI/Components/TelemetryViewer/components/FacetSearchInput.js.map +1 -0
  1409. package/build/dist/UI/Components/TelemetryViewer/components/FacetSectionHeader.js +21 -0
  1410. package/build/dist/UI/Components/TelemetryViewer/components/FacetSectionHeader.js.map +1 -0
  1411. package/build/dist/UI/Components/TelemetryViewer/components/FacetShowMoreButton.js +14 -0
  1412. package/build/dist/UI/Components/TelemetryViewer/components/FacetShowMoreButton.js.map +1 -0
  1413. package/build/dist/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.js +27 -0
  1414. package/build/dist/UI/Components/TelemetryViewer/components/HiddenFacetsFooter.js.map +1 -0
  1415. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +202 -0
  1416. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -0
  1417. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js +2 -6
  1418. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryActiveFilterChips.js.map +1 -1
  1419. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js +111 -12
  1420. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryDetailPanel.js.map +1 -1
  1421. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js +27 -40
  1422. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSection.js.map +1 -1
  1423. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +70 -10
  1424. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  1425. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +17 -3
  1426. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  1427. package/build/dist/UI/Components/TelemetryViewer/types.js +0 -4
  1428. package/build/dist/UI/Components/TelemetryViewer/types.js.map +1 -1
  1429. package/build/dist/UI/Components/TelemetryViewer/useFacetSearchExemptions.js +70 -0
  1430. package/build/dist/UI/Components/TelemetryViewer/useFacetSearchExemptions.js.map +1 -0
  1431. package/build/dist/UI/Components/TelemetryViewer/useFacetSectionSearch.js +52 -0
  1432. package/build/dist/UI/Components/TelemetryViewer/useFacetSectionSearch.js.map +1 -0
  1433. package/build/dist/UI/Utils/Clipboard.js +59 -2
  1434. package/build/dist/UI/Utils/Clipboard.js.map +1 -1
  1435. package/build/dist/UI/Utils/Navigation.js +11 -2
  1436. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  1437. package/build/dist/UI/Utils/Rules/RuleRunClient.js +98 -0
  1438. package/build/dist/UI/Utils/Rules/RuleRunClient.js.map +1 -0
  1439. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +479 -0
  1440. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -0
  1441. package/build/dist/UI/Utils/Telemetry/UseTelemetryEntityNames.js +113 -0
  1442. package/build/dist/UI/Utils/Telemetry/UseTelemetryEntityNames.js.map +1 -0
  1443. package/build/dist/Utils/AI/InvestigationReport.js +1241 -0
  1444. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -0
  1445. package/build/dist/Utils/API.js +25 -0
  1446. package/build/dist/Utils/API.js.map +1 -1
  1447. package/build/dist/Utils/Dashboard/Components/DashboardSloComponent.js +16 -2
  1448. package/build/dist/Utils/Dashboard/Components/DashboardSloComponent.js.map +1 -1
  1449. package/build/dist/Utils/Dashboard/Components/DashboardSloListComponent.js +94 -0
  1450. package/build/dist/Utils/Dashboard/Components/DashboardSloListComponent.js.map +1 -0
  1451. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  1452. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  1453. package/build/dist/Utils/Dashboard/SloWidgetSource.js +125 -0
  1454. package/build/dist/Utils/Dashboard/SloWidgetSource.js.map +1 -0
  1455. package/build/dist/Utils/Markdown/MarkdownEscape.js +49 -0
  1456. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -0
  1457. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +112 -34
  1458. package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -1
  1459. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +22 -0
  1460. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  1461. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +129 -0
  1462. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -0
  1463. package/build/dist/Utils/NetworkDiscovery/RunImportedDeviceNaming.js +255 -0
  1464. package/build/dist/Utils/NetworkDiscovery/RunImportedDeviceNaming.js.map +1 -0
  1465. package/build/dist/Utils/NetworkDiscovery/ShortDeviceNamePlanner.js +184 -0
  1466. package/build/dist/Utils/NetworkDiscovery/ShortDeviceNamePlanner.js.map +1 -0
  1467. package/build/dist/Utils/NetworkDiscovery/ShortHostnameUtil.js +79 -0
  1468. package/build/dist/Utils/NetworkDiscovery/ShortHostnameUtil.js.map +1 -0
  1469. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js +21 -12
  1470. package/build/dist/Utils/NetworkSite/CidrMatchUtil.js.map +1 -1
  1471. package/build/dist/Utils/Project/DataResidency.js +51 -0
  1472. package/build/dist/Utils/Project/DataResidency.js.map +1 -0
  1473. package/build/dist/Utils/Rules/RuleRunSummary.js +197 -0
  1474. package/build/dist/Utils/Rules/RuleRunSummary.js.map +1 -0
  1475. package/build/dist/Utils/Rum/SessionReplayHealth.js +10 -0
  1476. package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -1
  1477. package/build/dist/Utils/Rum/SessionReplayRecordingEnded.js +54 -0
  1478. package/build/dist/Utils/Rum/SessionReplayRecordingEnded.js.map +1 -0
  1479. package/build/dist/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.js +311 -0
  1480. package/build/dist/Utils/SecurityEvent/Connectors/AwsSecurityHubNormalizer.js.map +1 -0
  1481. package/build/dist/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.js +150 -0
  1482. package/build/dist/Utils/SecurityEvent/Connectors/CrowdStrikeFalconNormalizer.js.map +1 -0
  1483. package/build/dist/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.js +328 -0
  1484. package/build/dist/Utils/SecurityEvent/Connectors/ElasticSecurityNormalizer.js.map +1 -0
  1485. package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.js +405 -0
  1486. package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftDefenderXdrNormalizer.js.map +1 -0
  1487. package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.js +212 -0
  1488. package/build/dist/Utils/SecurityEvent/Connectors/MicrosoftSentinelNormalizer.js.map +1 -0
  1489. package/build/dist/Utils/SecurityEvent/Connectors/OktaNormalizer.js +351 -0
  1490. package/build/dist/Utils/SecurityEvent/Connectors/OktaNormalizer.js.map +1 -0
  1491. package/build/dist/Utils/SecurityEvent/Connectors/SplunkNormalizer.js +348 -0
  1492. package/build/dist/Utils/SecurityEvent/Connectors/SplunkNormalizer.js.map +1 -0
  1493. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +103 -10
  1494. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  1495. package/build/dist/Utils/Slo/SloAffectedResourceMarkdown.js +51 -0
  1496. package/build/dist/Utils/Slo/SloAffectedResourceMarkdown.js.map +1 -0
  1497. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +228 -0
  1498. package/build/dist/Utils/Slo/SloBurnRateTemplate.js.map +1 -0
  1499. package/build/dist/Utils/Slo/SloFeedMarkdown.js +934 -0
  1500. package/build/dist/Utils/Slo/SloFeedMarkdown.js.map +1 -0
  1501. package/build/dist/Utils/Slo/SloHealth.js +75 -9
  1502. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  1503. package/build/dist/Utils/Slo/SloListWidgetFormat.js +217 -0
  1504. package/build/dist/Utils/Slo/SloListWidgetFormat.js.map +1 -0
  1505. package/build/dist/Utils/Slo/SloMetricType.js +212 -0
  1506. package/build/dist/Utils/Slo/SloMetricType.js.map +1 -0
  1507. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +216 -0
  1508. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -0
  1509. package/build/dist/Utils/Slo/SloMonitorStatusSummary.js +129 -0
  1510. package/build/dist/Utils/Slo/SloMonitorStatusSummary.js.map +1 -0
  1511. package/build/dist/Utils/Slo/SloOverviewText.js +110 -0
  1512. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -0
  1513. package/build/dist/Utils/Slo/SloProjection.js +448 -0
  1514. package/build/dist/Utils/Slo/SloProjection.js.map +1 -0
  1515. package/build/dist/Utils/Slo/SloWidgetFormat.js +4 -2
  1516. package/build/dist/Utils/Slo/SloWidgetFormat.js.map +1 -1
  1517. package/build/dist/Utils/Telemetry/CrossSignalScope.js +16 -5
  1518. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  1519. package/build/dist/Utils/Telemetry/EntityRelationship.js +7 -0
  1520. package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
  1521. package/build/dist/Utils/Telemetry/LockedFilterSearch.js +252 -0
  1522. package/build/dist/Utils/Telemetry/LockedFilterSearch.js.map +1 -0
  1523. package/build/dist/Utils/Telemetry/NetworkHost.js +39 -0
  1524. package/build/dist/Utils/Telemetry/NetworkHost.js.map +1 -0
  1525. package/build/dist/Utils/Telemetry/OriginAllowList.js +105 -4
  1526. package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -1
  1527. package/build/dist/Utils/Traces/CriticalPath.js +9 -4
  1528. package/build/dist/Utils/Traces/CriticalPath.js.map +1 -1
  1529. package/package.json +1 -1
  1530. package/Server/API/GoogleSecOpsConnectionAPI.ts +0 -79
  1531. package/Server/Services/GoogleSecOpsConnectionService.ts +0 -101
  1532. package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +0 -774
  1533. package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +0 -423
  1534. package/Tests/App/Dashboard/GoogleSecOpsDiagnostics.test.tsx +0 -724
  1535. package/Tests/Server/API/GoogleSecOpsConnectionAPI.test.ts +0 -248
  1536. package/Tests/Server/Services/GoogleSecOpsDiagnosticsModel.test.ts +0 -153
  1537. package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.test.ts +0 -711
  1538. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsDiagnostics.test.ts +0 -838
  1539. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +0 -716
  1540. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +0 -860
  1541. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +0 -1231
  1542. package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +0 -600
  1543. package/Types/SecurityEvent/GoogleSecOpsDiagnostics.ts +0 -56
  1544. package/UI/Components/GanttChart/Bar/BarLabel.tsx +0 -17
  1545. package/UI/Components/GanttChart/Bar/Index.tsx +0 -149
  1546. package/UI/Components/GanttChart/ChartContainer.tsx +0 -50
  1547. package/UI/Components/GanttChart/Index.tsx +0 -90
  1548. package/UI/Components/GanttChart/Row/Index.tsx +0 -22
  1549. package/UI/Components/GanttChart/Row/Row.tsx +0 -231
  1550. package/UI/Components/GanttChart/Row/RowLabel.tsx +0 -30
  1551. package/UI/Components/GanttChart/Rows.tsx +0 -59
  1552. package/UI/Components/GanttChart/Timeline/Index.tsx +0 -46
  1553. package/UI/Components/GanttChart/Timeline/TimelineInterval.tsx +0 -33
  1554. package/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.tsx +0 -43
  1555. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +0 -1
  1556. package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js.map +0 -1
  1557. package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js +0 -56
  1558. package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js.map +0 -1
  1559. package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js.map +0 -1
  1560. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +0 -62
  1561. package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +0 -1
  1562. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +0 -1
  1563. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +0 -577
  1564. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +0 -1
  1565. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js.map +0 -1
  1566. package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js +0 -2
  1567. package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js.map +0 -1
  1568. package/build/dist/UI/Components/GanttChart/Bar/BarLabel.js +0 -8
  1569. package/build/dist/UI/Components/GanttChart/Bar/BarLabel.js.map +0 -1
  1570. package/build/dist/UI/Components/GanttChart/Bar/Index.js +0 -77
  1571. package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +0 -1
  1572. package/build/dist/UI/Components/GanttChart/ChartContainer.js +0 -33
  1573. package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +0 -1
  1574. package/build/dist/UI/Components/GanttChart/Index.js +0 -39
  1575. package/build/dist/UI/Components/GanttChart/Index.js.map +0 -1
  1576. package/build/dist/UI/Components/GanttChart/Row/Index.js +0 -7
  1577. package/build/dist/UI/Components/GanttChart/Row/Index.js.map +0 -1
  1578. package/build/dist/UI/Components/GanttChart/Row/Row.js +0 -106
  1579. package/build/dist/UI/Components/GanttChart/Row/Row.js.map +0 -1
  1580. package/build/dist/UI/Components/GanttChart/Row/RowLabel.js +0 -10
  1581. package/build/dist/UI/Components/GanttChart/Row/RowLabel.js.map +0 -1
  1582. package/build/dist/UI/Components/GanttChart/Rows.js +0 -19
  1583. package/build/dist/UI/Components/GanttChart/Rows.js.map +0 -1
  1584. package/build/dist/UI/Components/GanttChart/Timeline/Index.js +0 -16
  1585. package/build/dist/UI/Components/GanttChart/Timeline/Index.js.map +0 -1
  1586. package/build/dist/UI/Components/GanttChart/Timeline/TimelineInterval.js +0 -13
  1587. package/build/dist/UI/Components/GanttChart/Timeline/TimelineInterval.js.map +0 -1
  1588. package/build/dist/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.js +0 -13
  1589. package/build/dist/UI/Components/GanttChart/Timeline/TimelineIntervalMarks.js.map +0 -1
@@ -0,0 +1,4483 @@
1
+ import SecurityEventConnection from "../../../../../Models/DatabaseModels/SecurityEventConnection";
2
+ import Semaphore, {
3
+ SemaphoreLockTimeoutError,
4
+ } from "../../../../../Server/Infrastructure/Semaphore";
5
+ import OTelIngestService, {
6
+ TelemetryServiceMetadata,
7
+ } from "../../../../../Server/Services/OpenTelemetryIngestService";
8
+ import SecurityEventConnectionService from "../../../../../Server/Services/SecurityEventConnectionService";
9
+ import SecurityEventService from "../../../../../Server/Services/SecurityEventService";
10
+ import logger from "../../../../../Server/Utils/Logger";
11
+ import ConnectorErrorMessage from "../../../../../Server/Utils/SecurityEvent/ConnectorErrorMessage";
12
+ import SecurityEventConnectionPoller, {
13
+ DEFAULT_CURSOR_OVERLAP_IN_MINUTES,
14
+ DEFAULT_INITIAL_LOOKBACK_IN_MINUTES,
15
+ MAX_CHUNK_MINUTES,
16
+ MAX_DIAGNOSTIC_SAMPLES,
17
+ MAX_EVENTS_PER_RUN,
18
+ MAX_FETCH_REQUESTS,
19
+ MAX_RANGE_MS,
20
+ MIN_CHUNK_MINUTES,
21
+ POLL_REQUEST_TIMEOUT_IN_MS,
22
+ PollerOverrides,
23
+ RecordedConnectionPollFailure,
24
+ ResolvedFetchBudget,
25
+ SECURITY_EVENT_SOURCE_LOCK_NAMESPACE,
26
+ } from "../../../../../Server/Utils/SecurityEvent/Connectors/SecurityEventConnectionPoller";
27
+ import SecurityEventConnectorRegistry from "../../../../../Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry";
28
+ import {
29
+ ConnectorFetchBudget,
30
+ ConnectorFetchFailureSummary,
31
+ ConnectorFetchOptions,
32
+ ConnectorFetchResult,
33
+ ConnectorFetchWindow,
34
+ ConnectorTestOptions,
35
+ ConnectorTestResult,
36
+ SecurityConnectorSettings,
37
+ SecurityEventConnector,
38
+ attachConnectorChecks,
39
+ attachConnectorFetchSummary,
40
+ } from "../../../../../Server/Utils/SecurityEvent/Connectors/Types";
41
+ import SecurityEventDedupe from "../../../../../Server/Utils/SecurityEvent/SecurityEventDedupe";
42
+ import ThreatIntelEnricher from "../../../../../Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelEnricher";
43
+ import OneUptimeDate from "../../../../../Types/Date";
44
+ import BadDataException from "../../../../../Types/Exception/BadDataException";
45
+ import { JSONObject } from "../../../../../Types/JSON";
46
+ import ObjectID from "../../../../../Types/ObjectID";
47
+ import { SecurityConnectorCheck } from "../../../../../Types/SecurityEvent/Connectors/ConnectorDiagnostics";
48
+ import {
49
+ LEGACY_GOOGLE_SECOPS_CONNECTION_ID_ATTRIBUTE,
50
+ SECURITY_CONNECTION_ID_ATTRIBUTE,
51
+ SecurityEventConnectionRunOptions,
52
+ SecurityEventConnectionRunResult,
53
+ } from "../../../../../Types/SecurityEvent/Connectors/SecurityEventConnectionDiagnostics";
54
+ import SecurityEventConnectorProvider, {
55
+ AllSecurityEventConnectorProviders,
56
+ } from "../../../../../Types/SecurityEvent/Connectors/SecurityEventConnectorProvider";
57
+ import {
58
+ SecurityEventConnectorDefinition,
59
+ getSecurityEventConnectorDefinition,
60
+ } from "../../../../../Types/SecurityEvent/Connectors/SecurityEventConnectorCatalog";
61
+ import NormalizedSecurityEvent from "../../../../../Types/SecurityEvent/NormalizedSecurityEvent";
62
+ import OcsfSeverity from "../../../../../Types/SecurityEvent/OcsfSeverity";
63
+ import ServiceType from "../../../../../Types/Telemetry/ServiceType";
64
+ import { getJestSpyOn } from "../../../../Spy";
65
+ import {
66
+ afterEach,
67
+ beforeEach,
68
+ describe,
69
+ expect,
70
+ jest,
71
+ test,
72
+ } from "@jest/globals";
73
+
74
+ /*
75
+ * The generic poll loop behind every Security Event Connection, tested
76
+ * with a fake connector injected through PollerOverrides so nothing here
77
+ * depends on any one provider's HTTP behaviour. What is pinned is the
78
+ * part that decides whether records are lost, duplicated or silently stop
79
+ * flowing: the window arithmetic, when the cursor may move, how failures
80
+ * are booked onto the connection row, the dedupe hand-off and the
81
+ * source lock.
82
+ *
83
+ * The registry is mocked out so importing the poller does not drag every
84
+ * provider connector (and its HTTP client) into this suite.
85
+ */
86
+
87
+ jest.mock(
88
+ "../../../../../Server/Utils/SecurityEvent/Connectors/SecurityEventConnectorRegistry",
89
+ () => {
90
+ return {
91
+ __esModule: true,
92
+ default: { getConnector: jest.fn() },
93
+ };
94
+ },
95
+ );
96
+
97
+ const PROJECT_ID: ObjectID = new ObjectID(
98
+ "11111111-1111-4111-8111-111111111111",
99
+ );
100
+ const CONNECTION_ID: ObjectID = new ObjectID(
101
+ "22222222-2222-4222-8222-222222222222",
102
+ );
103
+ const SECOND_CONNECTION_ID: ObjectID = new ObjectID(
104
+ "44444444-4444-4444-8444-444444444444",
105
+ );
106
+ const PROVIDER: SecurityEventConnectorProvider =
107
+ SecurityEventConnectorProvider.OktaSystemLog;
108
+ const DEFINITION: SecurityEventConnectorDefinition =
109
+ getSecurityEventConnectorDefinition(PROVIDER)!;
110
+ const SECRET_VALUE: string = "ssws-token-9f8e7d6c5b4a";
111
+ const MINUTE_MS: number = 60 * 1000;
112
+ /*
113
+ * Okta's catalog overlap (15 minutes) is deliberately not the default, so
114
+ * the window tests below prove the poller uses the provider's own value.
115
+ */
116
+ const OVERLAP_MINUTES: number = DEFINITION.cursorOverlapInMinutes;
117
+
118
+ const SETTINGS: SecurityConnectorSettings = {
119
+ provider: PROVIDER,
120
+ config: { orgUrl: "https://acme.okta.com" },
121
+ secrets: { apiToken: SECRET_VALUE },
122
+ alertingOnly: true,
123
+ };
124
+
125
+ function makeConnection(
126
+ overrides: Partial<SecurityEventConnection> = {},
127
+ ): SecurityEventConnection {
128
+ const connection: SecurityEventConnection = new SecurityEventConnection();
129
+ connection._id = CONNECTION_ID.toString();
130
+ connection.projectId = PROJECT_ID;
131
+ connection.name = "Okta production";
132
+ connection.provider = PROVIDER;
133
+ connection.config = SETTINGS.config;
134
+ connection.secrets = JSON.stringify(SETTINGS.secrets);
135
+ connection.alertingOnly = true;
136
+ connection.pollIntervalInMinutes = 5;
137
+ connection.isEnabled = true;
138
+ Object.assign(connection, overrides);
139
+ return connection;
140
+ }
141
+
142
+ function makeServiceMetadata(): TelemetryServiceMetadata {
143
+ return {
144
+ serviceName: DEFINITION.productName,
145
+ primaryEntityId: new ObjectID("33333333-3333-4333-8333-333333333333"),
146
+ primaryEntityType: ServiceType.OpenTelemetry,
147
+ dataRententionInDays: 15,
148
+ serviceRetentionConfig: null,
149
+ serviceRetentionInDays: null,
150
+ projectRetentionConfig: null,
151
+ projectRetentionInDays: 15,
152
+ };
153
+ }
154
+
155
+ function makeEvent(
156
+ eventUid: string,
157
+ time: Date = new Date(Date.now() - 10 * MINUTE_MS),
158
+ ): NormalizedSecurityEvent {
159
+ return {
160
+ time,
161
+ eventUid,
162
+ categoryUid: 3,
163
+ categoryName: "Identity & Access Management",
164
+ classUid: 3002,
165
+ className: "Authentication",
166
+ activityName: "Logon",
167
+ severityId: 1,
168
+ severityName: OcsfSeverity.Informational,
169
+ statusName: "Success",
170
+ message: `Login ${eventUid}`,
171
+ // Deliberately wrong: the poller must overwrite these from the catalog.
172
+ vendorName: "connector-said-vendor",
173
+ productName: "connector-said-product",
174
+ ruleId: "",
175
+ ruleName: "",
176
+ mitreTactics: [],
177
+ mitreTechniques: [],
178
+ principalUser: "alice@example.com",
179
+ principalHost: "",
180
+ principalIp: "203.0.113.5",
181
+ principalProcess: "",
182
+ targetUser: "",
183
+ targetHost: "",
184
+ targetIp: "",
185
+ targetPort: 0,
186
+ targetResource: "",
187
+ observables: ["alice@example.com", "203.0.113.5"],
188
+ attributes: { "okta.eventType": "user.session.start" },
189
+ };
190
+ }
191
+
192
+ function makeFetchResult(
193
+ events: Array<NormalizedSecurityEvent>,
194
+ overrides: Partial<ConnectorFetchResult> = {},
195
+ ): ConnectorFetchResult {
196
+ return {
197
+ events,
198
+ fetchedCount: events.length,
199
+ rejectedCount: 0,
200
+ failedCount: 0,
201
+ complete: true,
202
+ requestCount: 1,
203
+ warnings: [],
204
+ samples: events.map(
205
+ (
206
+ event: NormalizedSecurityEvent,
207
+ ): { id: string; title: string; severity: string } => {
208
+ return { id: event.eventUid, title: event.message, severity: "Info" };
209
+ },
210
+ ),
211
+ ...overrides,
212
+ };
213
+ }
214
+
215
+ interface FakeConnector {
216
+ connector: SecurityEventConnector;
217
+ fetchCalls: Array<{
218
+ settings: SecurityConnectorSettings;
219
+ window: ConnectorFetchWindow;
220
+ options: ConnectorFetchOptions;
221
+ }>;
222
+ testCalls: Array<{
223
+ settings: SecurityConnectorSettings;
224
+ options: ConnectorTestOptions;
225
+ }>;
226
+ validateCalls: Array<SecurityConnectorSettings>;
227
+ }
228
+
229
+ /*
230
+ * A connector whose every entry point is scripted: `fetch` is either the
231
+ * result to resolve or the error to throw, `checks` is what testConnection
232
+ * answers, `validate` is what validateSettings throws.
233
+ */
234
+ function makeFakeConnector(script: {
235
+ fetch?: ConnectorFetchResult | Error | undefined;
236
+ checks?: Array<SecurityConnectorCheck> | undefined;
237
+ validate?: Error | undefined;
238
+ }): FakeConnector {
239
+ const fake: FakeConnector = {
240
+ connector: {
241
+ provider: PROVIDER,
242
+ validateSettings: (settings: SecurityConnectorSettings): void => {
243
+ fake.validateCalls.push(settings);
244
+ if (script.validate) {
245
+ throw script.validate;
246
+ }
247
+ },
248
+ testConnection: (
249
+ settings: SecurityConnectorSettings,
250
+ options: ConnectorTestOptions,
251
+ ): Promise<Array<SecurityConnectorCheck>> => {
252
+ fake.testCalls.push({ settings, options });
253
+ return Promise.resolve(script.checks || []);
254
+ },
255
+ fetchEvents: (
256
+ settings: SecurityConnectorSettings,
257
+ window: ConnectorFetchWindow,
258
+ options: ConnectorFetchOptions,
259
+ ): Promise<ConnectorFetchResult> => {
260
+ fake.fetchCalls.push({ settings, window, options });
261
+ if (script.fetch instanceof Error) {
262
+ return Promise.reject(script.fetch);
263
+ }
264
+ return Promise.resolve(script.fetch || makeFetchResult([]));
265
+ },
266
+ },
267
+ fetchCalls: [],
268
+ testCalls: [],
269
+ validateCalls: [],
270
+ };
271
+
272
+ return fake;
273
+ }
274
+
275
+ function overridesFor(fake: FakeConnector): PollerOverrides {
276
+ return { connector: fake.connector, settings: SETTINGS };
277
+ }
278
+
279
+ function check(
280
+ key: string,
281
+ status: SecurityConnectorCheck["status"],
282
+ message: string = `${key} ${status}`,
283
+ ): SecurityConnectorCheck {
284
+ return { key, name: `Check ${key}`, status, durationMs: 1, message };
285
+ }
286
+
287
+ type ConnectionUpdateCall = { id: ObjectID; data: JSONObject };
288
+
289
+ function updateCall(index: number = 0): ConnectionUpdateCall {
290
+ const spy: jest.Mock =
291
+ SecurityEventConnectionService.updateOneById as unknown as jest.Mock;
292
+ expect(spy.mock.calls.length).toBeGreaterThan(index);
293
+ return spy.mock.calls[index]![0] as ConnectionUpdateCall;
294
+ }
295
+
296
+ function findCheck(
297
+ result: SecurityEventConnectionRunResult,
298
+ key: string,
299
+ ): SecurityConnectorCheck {
300
+ const found: SecurityConnectorCheck | undefined = result.checks.find(
301
+ (candidate: SecurityConnectorCheck): boolean => {
302
+ return candidate.key === key;
303
+ },
304
+ );
305
+ expect({ key, found: found !== undefined }).toEqual({ key, found: true });
306
+ return found!;
307
+ }
308
+
309
+ let insertedRows: Array<JSONObject>;
310
+ let insertOptions: Array<unknown>;
311
+ let existingUids: Set<string>;
312
+ let dedupeCalls: Array<{
313
+ projectId: ObjectID;
314
+ vendorName: string;
315
+ productName: string;
316
+ ids: Array<string>;
317
+ }>;
318
+
319
+ beforeEach(() => {
320
+ insertedRows = [];
321
+ insertOptions = [];
322
+ existingUids = new Set();
323
+ dedupeCalls = [];
324
+
325
+ getJestSpyOn(Semaphore, "lock").mockResolvedValue({});
326
+ getJestSpyOn(Semaphore, "release").mockResolvedValue(undefined);
327
+ getJestSpyOn(SecurityEventDedupe, "findExistingEventUids").mockImplementation(
328
+ ((data: {
329
+ projectId: ObjectID;
330
+ vendorName: string;
331
+ productName: string;
332
+ ids: Array<string>;
333
+ }): Promise<Set<string>> => {
334
+ dedupeCalls.push(data);
335
+ return Promise.resolve(existingUids);
336
+ }) as never,
337
+ );
338
+ getJestSpyOn(OTelIngestService, "telemetryServiceFromName").mockResolvedValue(
339
+ makeServiceMetadata() as never,
340
+ );
341
+ getJestSpyOn(ThreatIntelEnricher, "enrichNormalizedEvents").mockResolvedValue(
342
+ {
343
+ eventsMatched: 0,
344
+ } as never,
345
+ );
346
+ getJestSpyOn(SecurityEventService, "insertJsonRows").mockImplementation(((
347
+ rows: Array<JSONObject>,
348
+ options: unknown,
349
+ ): Promise<void> => {
350
+ insertedRows.push(...rows);
351
+ insertOptions.push(options);
352
+ return Promise.resolve();
353
+ }) as never);
354
+ getJestSpyOn(
355
+ SecurityEventConnectionService,
356
+ "updateOneById",
357
+ ).mockResolvedValue(undefined as never);
358
+ getJestSpyOn(
359
+ SecurityEventConnectionService,
360
+ "getConnectorSettings",
361
+ ).mockResolvedValue(SETTINGS as never);
362
+ // Every run logs its warnings; failures log their errors. Keep the output quiet.
363
+ getJestSpyOn(logger, "warn").mockImplementation((() => {
364
+ return undefined;
365
+ }) as never);
366
+ getJestSpyOn(logger, "error").mockImplementation((() => {
367
+ return undefined;
368
+ }) as never);
369
+ getJestSpyOn(logger, "info").mockImplementation((() => {
370
+ return undefined;
371
+ }) as never);
372
+ });
373
+
374
+ afterEach(() => {
375
+ jest.restoreAllMocks();
376
+ (
377
+ SecurityEventConnectorRegistry.getConnector as unknown as jest.Mock
378
+ ).mockReset();
379
+ });
380
+
381
+ describe("SecurityEventConnectionPoller constants", () => {
382
+ test("the first window is a full day and the adaptive chunk runs from one minute to a day", () => {
383
+ expect(DEFAULT_INITIAL_LOOKBACK_IN_MINUTES).toBe(24 * 60);
384
+ expect(MAX_CHUNK_MINUTES).toBe(24 * 60);
385
+ expect(MIN_CHUNK_MINUTES).toBe(1);
386
+ expect(DEFAULT_CURSOR_OVERLAP_IN_MINUTES).toBe(1);
387
+ expect(MAX_RANGE_MS).toBe(7 * 24 * 60 * 60 * 1000);
388
+ expect(OVERLAP_MINUTES).not.toBe(DEFAULT_CURSOR_OVERLAP_IN_MINUTES);
389
+ });
390
+
391
+ test("every fetch is bounded in requests, records and time", () => {
392
+ expect(MAX_FETCH_REQUESTS).toBeGreaterThan(0);
393
+ expect(MAX_EVENTS_PER_RUN).toBeGreaterThan(0);
394
+ expect(POLL_REQUEST_TIMEOUT_IN_MS).toBeGreaterThan(0);
395
+ expect(MAX_DIAGNOSTIC_SAMPLES).toBeGreaterThan(0);
396
+ });
397
+ });
398
+
399
+ describe("SecurityEventConnectionPoller.getWindow", () => {
400
+ const NOW: Date = new Date("2026-09-10T12:00:00.000Z");
401
+
402
+ test("a first poll (no cursor) looks back the default 24 hours", () => {
403
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
404
+ SecurityEventConnectionPoller.getWindow(
405
+ makeConnection(),
406
+ { type: "poll" },
407
+ NOW,
408
+ );
409
+
410
+ expect(window.endTime.toISOString()).toBe(NOW.toISOString());
411
+ expect(window.startTime.toISOString()).toBe(
412
+ new Date(
413
+ NOW.getTime() - DEFAULT_INITIAL_LOOKBACK_IN_MINUTES * MINUTE_MS,
414
+ ).toISOString(),
415
+ );
416
+ expect(window.hasUsableCursor).toBe(false);
417
+ expect(window.warnings).toEqual([]);
418
+ expect(window.progressFrom?.toISOString()).toBe(
419
+ window.startTime.toISOString(),
420
+ );
421
+ expect(window.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
422
+ expect(window.overlapInMinutes).toBe(OVERLAP_MINUTES);
423
+ });
424
+
425
+ test("a cursor poll starts the provider's overlap before the cursor and ends now", () => {
426
+ const cursor: Date = new Date(NOW.getTime() - 5 * MINUTE_MS);
427
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
428
+ SecurityEventConnectionPoller.getWindow(
429
+ makeConnection({ cursor: cursor.toISOString() }),
430
+ { type: "poll" },
431
+ NOW,
432
+ );
433
+
434
+ expect(window.startTime.toISOString()).toBe(
435
+ new Date(cursor.getTime() - OVERLAP_MINUTES * MINUTE_MS).toISOString(),
436
+ );
437
+ expect(window.endTime.toISOString()).toBe(NOW.toISOString());
438
+ expect(window.hasUsableCursor).toBe(true);
439
+ expect(window.warnings).toEqual([]);
440
+ // The chunk counts only the new ground past the cursor, not the overlap.
441
+ expect(window.progressFrom?.toISOString()).toBe(cursor.toISOString());
442
+ expect(window.chunkMinutes).toBe(5);
443
+ });
444
+
445
+ test("a stale cursor is caught up in 24 hour chunks past the cursor with a warning", () => {
446
+ /*
447
+ * Review finding F1 correction: the chunk is measured from the cursor,
448
+ * so a catch-up window is a day plus the overlap long (it used to be
449
+ * exactly a day from the window start).
450
+ */
451
+ const cursor: Date = new Date(NOW.getTime() - 7 * 24 * 60 * MINUTE_MS);
452
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
453
+ SecurityEventConnectionPoller.getWindow(
454
+ makeConnection({ cursor: cursor.toISOString() }),
455
+ { type: "poll" },
456
+ NOW,
457
+ );
458
+
459
+ expect(window.hasUsableCursor).toBe(true);
460
+ expect(window.endTime.getTime() - window.startTime.getTime()).toBe(
461
+ (MAX_CHUNK_MINUTES + OVERLAP_MINUTES) * MINUTE_MS,
462
+ );
463
+ expect(window.endTime.toISOString()).toBe(
464
+ new Date(cursor.getTime() + MAX_CHUNK_MINUTES * MINUTE_MS).toISOString(),
465
+ );
466
+ expect(window.endTime.getTime()).toBeLessThan(NOW.getTime());
467
+ expect(window.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
468
+ expect(window.warnings).toEqual([
469
+ "Catching up from the saved cursor in 24 hour windows. Later records will be fetched by subsequent polls.",
470
+ ]);
471
+ });
472
+
473
+ test("a cursor exactly one chunk behind is not chunked", () => {
474
+ // Review finding F1 correction: the chunk end is cursor + 24h, so a cursor exactly a day old lands it on now.
475
+ const cursor: Date = new Date(
476
+ NOW.getTime() - MAX_CHUNK_MINUTES * MINUTE_MS,
477
+ );
478
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
479
+ SecurityEventConnectionPoller.getWindow(
480
+ makeConnection({ cursor: cursor.toISOString() }),
481
+ { type: "poll" },
482
+ NOW,
483
+ );
484
+
485
+ expect(window.endTime.toISOString()).toBe(NOW.toISOString());
486
+ expect(window.warnings).toEqual([]);
487
+ expect(window.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
488
+ });
489
+
490
+ test("the chunk comes from the previous poll's nextChunkMinutes and is measured from the cursor", () => {
491
+ const cursor: Date = new Date(NOW.getTime() - 3 * 24 * 60 * MINUTE_MS);
492
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
493
+ SecurityEventConnectionPoller.getWindow(
494
+ makeConnection({
495
+ cursor: cursor.toISOString(),
496
+ lastPollResult: { nextChunkMinutes: 90 },
497
+ }),
498
+ { type: "poll" },
499
+ NOW,
500
+ );
501
+
502
+ expect(window.startTime.toISOString()).toBe(
503
+ new Date(cursor.getTime() - OVERLAP_MINUTES * MINUTE_MS).toISOString(),
504
+ );
505
+ expect(window.endTime.toISOString()).toBe(
506
+ new Date(cursor.getTime() + 90 * MINUTE_MS).toISOString(),
507
+ );
508
+ expect(window.chunkMinutes).toBe(90);
509
+ expect(window.warnings).toEqual([
510
+ "Catching up from the saved cursor in 90 minute windows. Later records will be fetched by subsequent polls.",
511
+ ]);
512
+ });
513
+
514
+ test("a chunk no longer than the overlap still ends after the cursor, so the cursor can always move forward", () => {
515
+ /*
516
+ * Review finding F1 correction: measured from the window start, a one
517
+ * minute chunk with a 15 minute overlap would end 14 minutes BEFORE the
518
+ * cursor, and neither a complete read nor a forced advance could move
519
+ * the cursor forward again.
520
+ */
521
+ const cursor: Date = new Date(NOW.getTime() - 3 * 24 * 60 * MINUTE_MS);
522
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
523
+ SecurityEventConnectionPoller.getWindow(
524
+ makeConnection({
525
+ cursor: cursor.toISOString(),
526
+ lastPollResult: { nextChunkMinutes: MIN_CHUNK_MINUTES },
527
+ }),
528
+ { type: "poll" },
529
+ NOW,
530
+ );
531
+
532
+ expect(OVERLAP_MINUTES).toBeGreaterThan(MIN_CHUNK_MINUTES);
533
+ expect(window.endTime.getTime()).toBe(
534
+ cursor.getTime() + MIN_CHUNK_MINUTES * MINUTE_MS,
535
+ );
536
+ expect(window.endTime.getTime()).toBeGreaterThan(cursor.getTime());
537
+ expect(window.chunkMinutes).toBe(MIN_CHUNK_MINUTES);
538
+ });
539
+
540
+ test("a chunk longer than the time since the cursor ends now and records the minutes used, rounded up", () => {
541
+ const cursor: Date = new Date(NOW.getTime() - 4.5 * MINUTE_MS);
542
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
543
+ SecurityEventConnectionPoller.getWindow(
544
+ makeConnection({
545
+ cursor: cursor.toISOString(),
546
+ lastPollResult: { nextChunkMinutes: 720 },
547
+ }),
548
+ { type: "poll" },
549
+ NOW,
550
+ );
551
+
552
+ expect(window.endTime.toISOString()).toBe(NOW.toISOString());
553
+ expect(window.chunkMinutes).toBe(5);
554
+ expect(window.warnings).toEqual([]);
555
+ });
556
+
557
+ test("without a usable cursor a narrowed chunk is measured from the start of the default window", () => {
558
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
559
+ SecurityEventConnectionPoller.getWindow(
560
+ makeConnection({
561
+ cursor: "yesterday-ish",
562
+ lastPollResult: { nextChunkMinutes: 30 },
563
+ }),
564
+ { type: "poll" },
565
+ NOW,
566
+ );
567
+
568
+ const expectedStart: number =
569
+ NOW.getTime() - DEFAULT_INITIAL_LOOKBACK_IN_MINUTES * MINUTE_MS;
570
+ expect(window.hasUsableCursor).toBe(false);
571
+ expect(window.startTime.getTime()).toBe(expectedStart);
572
+ expect(window.endTime.getTime()).toBe(expectedStart + 30 * MINUTE_MS);
573
+ expect(window.chunkMinutes).toBe(30);
574
+ expect(window.warnings).toEqual([
575
+ "The saved cursor is unreadable; polling the default 24 hour window.",
576
+ "Catching up from the start of the default 24 hour window in 30 minute windows. Later records will be fetched by subsequent polls.",
577
+ ]);
578
+ });
579
+
580
+ test("an unreadable cursor falls back to the default window with a warning", () => {
581
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
582
+ SecurityEventConnectionPoller.getWindow(
583
+ makeConnection({ cursor: "yesterday-ish" }),
584
+ { type: "poll" },
585
+ NOW,
586
+ );
587
+
588
+ expect(window.hasUsableCursor).toBe(false);
589
+ expect(window.endTime.toISOString()).toBe(NOW.toISOString());
590
+ expect(window.startTime.toISOString()).toBe(
591
+ new Date(
592
+ NOW.getTime() - DEFAULT_INITIAL_LOOKBACK_IN_MINUTES * MINUTE_MS,
593
+ ).toISOString(),
594
+ );
595
+ expect(window.warnings).toHaveLength(1);
596
+ expect(window.warnings[0]).toContain("unreadable");
597
+ });
598
+
599
+ test("a future cursor falls back to the default window with a warning", () => {
600
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
601
+ SecurityEventConnectionPoller.getWindow(
602
+ makeConnection({
603
+ cursor: new Date(NOW.getTime() + 60 * MINUTE_MS).toISOString(),
604
+ }),
605
+ { type: "poll" },
606
+ NOW,
607
+ );
608
+
609
+ expect(window.hasUsableCursor).toBe(false);
610
+ expect(window.warnings).toHaveLength(1);
611
+ expect(window.warnings[0]).toContain("in the future");
612
+ });
613
+
614
+ test("a test run reads the last 24 hours and ignores the cursor", () => {
615
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
616
+ SecurityEventConnectionPoller.getWindow(
617
+ makeConnection({
618
+ cursor: new Date(NOW.getTime() - 5 * MINUTE_MS).toISOString(),
619
+ }),
620
+ { type: "test" },
621
+ NOW,
622
+ );
623
+
624
+ expect(window.hasUsableCursor).toBe(false);
625
+ expect(window.endTime.getTime() - window.startTime.getTime()).toBe(
626
+ 24 * 60 * MINUTE_MS,
627
+ );
628
+ expect(window.warnings).toEqual([]);
629
+ // Only scheduled polls carry a chunk.
630
+ expect(window.chunkMinutes).toBeUndefined();
631
+ });
632
+
633
+ test.each(["preview", "backfill"])(
634
+ "a %s run uses exactly the requested range",
635
+ (type: string) => {
636
+ const startTime: string = "2026-09-08T00:00:00.000Z";
637
+ const endTime: string = "2026-09-09T00:00:00.000Z";
638
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
639
+ SecurityEventConnectionPoller.getWindow(
640
+ makeConnection({
641
+ cursor: new Date(NOW.getTime() - 5 * MINUTE_MS).toISOString(),
642
+ }),
643
+ {
644
+ type: type as SecurityEventConnectionRunOptions["type"],
645
+ startTime,
646
+ endTime,
647
+ },
648
+ NOW,
649
+ );
650
+
651
+ expect(window.startTime.toISOString()).toBe(startTime);
652
+ expect(window.endTime.toISOString()).toBe(endTime);
653
+ expect(window.hasUsableCursor).toBe(false);
654
+ // The user's range is never cut into chunks.
655
+ expect(window.chunkMinutes).toBeUndefined();
656
+ expect(window.warnings).toEqual([]);
657
+ },
658
+ );
659
+
660
+ test("an end up to a minute in the future is tolerated for clock skew", () => {
661
+ const window: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
662
+ SecurityEventConnectionPoller.getWindow(
663
+ makeConnection(),
664
+ {
665
+ type: "preview",
666
+ startTime: new Date(NOW.getTime() - 60 * MINUTE_MS).toISOString(),
667
+ endTime: new Date(NOW.getTime() + 59 * 1000).toISOString(),
668
+ },
669
+ NOW,
670
+ );
671
+
672
+ expect(window.endTime.getTime()).toBeGreaterThan(NOW.getTime());
673
+ });
674
+
675
+ test.each<[string, Partial<SecurityEventConnectionRunOptions>]>([
676
+ ["no range at all", {}],
677
+ ["an unparseable start", { startTime: "soon", endTime: NOW.toISOString() }],
678
+ [
679
+ "an unparseable end",
680
+ {
681
+ startTime: new Date(NOW.getTime() - 60 * MINUTE_MS).toISOString(),
682
+ endTime: "later",
683
+ },
684
+ ],
685
+ [
686
+ "start equal to end",
687
+ { startTime: NOW.toISOString(), endTime: NOW.toISOString() },
688
+ ],
689
+ [
690
+ "start after end",
691
+ {
692
+ startTime: NOW.toISOString(),
693
+ endTime: new Date(NOW.getTime() - 60 * MINUTE_MS).toISOString(),
694
+ },
695
+ ],
696
+ [
697
+ "an end more than a minute in the future",
698
+ {
699
+ startTime: new Date(NOW.getTime() - 60 * MINUTE_MS).toISOString(),
700
+ endTime: new Date(NOW.getTime() + 2 * MINUTE_MS).toISOString(),
701
+ },
702
+ ],
703
+ [
704
+ "a range longer than seven days",
705
+ {
706
+ startTime: new Date(NOW.getTime() - MAX_RANGE_MS - 1000).toISOString(),
707
+ endTime: NOW.toISOString(),
708
+ },
709
+ ],
710
+ ])(
711
+ "rejects a preview/backfill range with %s",
712
+ (_label: string, range: Partial<SecurityEventConnectionRunOptions>) => {
713
+ for (const type of ["preview", "backfill"] as Array<
714
+ SecurityEventConnectionRunOptions["type"]
715
+ >) {
716
+ expect(() => {
717
+ SecurityEventConnectionPoller.getWindow(
718
+ makeConnection(),
719
+ { type, ...range },
720
+ NOW,
721
+ );
722
+ }).toThrow(BadDataException);
723
+ expect(() => {
724
+ SecurityEventConnectionPoller.getWindow(
725
+ makeConnection(),
726
+ { type, ...range },
727
+ NOW,
728
+ );
729
+ }).toThrow("Select a valid past time range of at most seven days.");
730
+ }
731
+ },
732
+ );
733
+
734
+ test.each<[string, JSONObject | null | undefined, number]>([
735
+ ["no previous poll", undefined, MAX_CHUNK_MINUTES],
736
+ ["a null result", null, MAX_CHUNK_MINUTES],
737
+ ["a result without the field", { status: "success" }, MAX_CHUNK_MINUTES],
738
+ ["the minimum", { nextChunkMinutes: MIN_CHUNK_MINUTES }, MIN_CHUNK_MINUTES],
739
+ ["a narrowed chunk", { nextChunkMinutes: 90 }, 90],
740
+ ["the maximum", { nextChunkMinutes: MAX_CHUNK_MINUTES }, MAX_CHUNK_MINUTES],
741
+ ["zero", { nextChunkMinutes: 0 }, MAX_CHUNK_MINUTES],
742
+ ["a negative value", { nextChunkMinutes: -5 }, MAX_CHUNK_MINUTES],
743
+ [
744
+ "more than a day",
745
+ { nextChunkMinutes: MAX_CHUNK_MINUTES + 1 },
746
+ MAX_CHUNK_MINUTES,
747
+ ],
748
+ ["a fraction", { nextChunkMinutes: 2.5 }, MAX_CHUNK_MINUTES],
749
+ ["a string", { nextChunkMinutes: "90" }, MAX_CHUNK_MINUTES],
750
+ ["NaN", { nextChunkMinutes: Number.NaN }, MAX_CHUNK_MINUTES],
751
+ [
752
+ "infinity",
753
+ { nextChunkMinutes: Number.POSITIVE_INFINITY },
754
+ MAX_CHUNK_MINUTES,
755
+ ],
756
+ ])(
757
+ "the chunk limit read from %s",
758
+ (
759
+ _label: string,
760
+ lastPollResult: JSONObject | null | undefined,
761
+ expected: number,
762
+ ) => {
763
+ expect(
764
+ SecurityEventConnectionPoller.getChunkLimitInMinutes(lastPollResult),
765
+ ).toBe(expected);
766
+ },
767
+ );
768
+
769
+ test("every catalog provider's own overlap is used, and an unknown provider gets the default", () => {
770
+ for (const provider of AllSecurityEventConnectorProviders) {
771
+ expect({
772
+ provider,
773
+ overlap:
774
+ SecurityEventConnectionPoller.getCursorOverlapInMinutes(provider),
775
+ }).toEqual({
776
+ provider,
777
+ overlap:
778
+ getSecurityEventConnectorDefinition(provider)!.cursorOverlapInMinutes,
779
+ });
780
+ }
781
+
782
+ expect(
783
+ SecurityEventConnectionPoller.getCursorOverlapInMinutes("not-a-provider"),
784
+ ).toBe(DEFAULT_CURSOR_OVERLAP_IN_MINUTES);
785
+ expect(
786
+ SecurityEventConnectionPoller.getCursorOverlapInMinutes(undefined),
787
+ ).toBe(DEFAULT_CURSOR_OVERLAP_IN_MINUTES);
788
+ });
789
+
790
+ test("a range of exactly seven days is accepted", () => {
791
+ expect(() => {
792
+ SecurityEventConnectionPoller.getWindow(
793
+ makeConnection(),
794
+ {
795
+ type: "backfill",
796
+ startTime: new Date(NOW.getTime() - MAX_RANGE_MS).toISOString(),
797
+ endTime: NOW.toISOString(),
798
+ },
799
+ NOW,
800
+ );
801
+ }).not.toThrow();
802
+ });
803
+ });
804
+
805
+ describe("SecurityEventConnectionPoller.executeConnection - validation", () => {
806
+ test.each<[string, (connection: SecurityEventConnection) => void]>([
807
+ [
808
+ "id",
809
+ (connection: SecurityEventConnection): void => {
810
+ delete connection._id;
811
+ },
812
+ ],
813
+ [
814
+ "projectId",
815
+ (connection: SecurityEventConnection): void => {
816
+ delete connection.projectId;
817
+ },
818
+ ],
819
+ [
820
+ "provider",
821
+ (connection: SecurityEventConnection): void => {
822
+ delete connection.provider;
823
+ },
824
+ ],
825
+ ])(
826
+ "refuses a connection missing its %s before touching the lock",
827
+ async (
828
+ _field: string,
829
+ strip: (connection: SecurityEventConnection) => void,
830
+ ) => {
831
+ const fake: FakeConnector = makeFakeConnector({});
832
+ const connection: SecurityEventConnection = makeConnection();
833
+ strip(connection);
834
+
835
+ await expect(
836
+ SecurityEventConnectionPoller.executeConnection(
837
+ connection,
838
+ { type: "poll" },
839
+ overridesFor(fake),
840
+ ),
841
+ ).rejects.toThrow("missing id, projectId or provider");
842
+ expect(Semaphore.lock).not.toHaveBeenCalled();
843
+ expect(fake.fetchCalls).toHaveLength(0);
844
+ },
845
+ );
846
+
847
+ test("a provider outside the catalog is refused and the lock is released", async () => {
848
+ const fake: FakeConnector = makeFakeConnector({});
849
+
850
+ await expect(
851
+ SecurityEventConnectionPoller.executeConnection(
852
+ makeConnection({
853
+ provider: "not-a-provider" as SecurityEventConnectorProvider,
854
+ }),
855
+ { type: "poll" },
856
+ overridesFor(fake),
857
+ ),
858
+ ).rejects.toThrow("unsupported provider");
859
+ expect(Semaphore.lock).toHaveBeenCalledTimes(1);
860
+ expect(Semaphore.release).toHaveBeenCalledTimes(1);
861
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
862
+ });
863
+ });
864
+
865
+ describe("SecurityEventConnectionPoller.executeConnection - source lock", () => {
866
+ test.each(["poll", "backfill"])(
867
+ "a %s run holds the project:provider source lock and releases it",
868
+ async (type: string) => {
869
+ const fake: FakeConnector = makeFakeConnector({});
870
+ const options: SecurityEventConnectionRunOptions =
871
+ type === "backfill"
872
+ ? {
873
+ type: "backfill",
874
+ startTime: new Date(Date.now() - 60 * MINUTE_MS).toISOString(),
875
+ endTime: new Date(Date.now() - 30 * MINUTE_MS).toISOString(),
876
+ }
877
+ : { type: "poll" };
878
+
879
+ await SecurityEventConnectionPoller.executeConnection(
880
+ makeConnection(),
881
+ options,
882
+ overridesFor(fake),
883
+ );
884
+
885
+ expect(Semaphore.lock).toHaveBeenCalledTimes(1);
886
+ expect(Semaphore.lock).toHaveBeenCalledWith({
887
+ namespace: SECURITY_EVENT_SOURCE_LOCK_NAMESPACE,
888
+ key: `${PROJECT_ID.toString()}:${PROVIDER}`,
889
+ lockTimeout: 30000,
890
+ acquireTimeout: 10000,
891
+ retryInterval: 100,
892
+ });
893
+ expect(Semaphore.release).toHaveBeenCalledTimes(1);
894
+ expect(fake.fetchCalls).toHaveLength(1);
895
+ },
896
+ );
897
+
898
+ test.each(["test", "preview"])(
899
+ "a %s run never takes the source lock, so it cannot wait behind an import",
900
+ async (type: string) => {
901
+ const fake: FakeConnector = makeFakeConnector({});
902
+ const options: SecurityEventConnectionRunOptions =
903
+ type === "preview"
904
+ ? {
905
+ type: "preview",
906
+ startTime: new Date(Date.now() - 60 * MINUTE_MS).toISOString(),
907
+ endTime: new Date(Date.now() - 30 * MINUTE_MS).toISOString(),
908
+ }
909
+ : { type: "test" };
910
+
911
+ await SecurityEventConnectionPoller.executeConnection(
912
+ makeConnection(),
913
+ options,
914
+ overridesFor(fake),
915
+ );
916
+
917
+ expect(Semaphore.lock).not.toHaveBeenCalled();
918
+ expect(Semaphore.release).not.toHaveBeenCalled();
919
+ },
920
+ );
921
+
922
+ test("a lock timeout becomes a readable 'still running' error and touches nothing", async () => {
923
+ getJestSpyOn(Semaphore, "lock").mockRejectedValue(
924
+ new SemaphoreLockTimeoutError("acquire timed out") as never,
925
+ );
926
+ const fake: FakeConnector = makeFakeConnector({});
927
+
928
+ const promise: Promise<SecurityEventConnectionRunResult> =
929
+ SecurityEventConnectionPoller.executeConnection(
930
+ makeConnection(),
931
+ { type: "poll" },
932
+ overridesFor(fake),
933
+ );
934
+
935
+ await expect(promise).rejects.toBeInstanceOf(BadDataException);
936
+ await expect(promise).rejects.toThrow(
937
+ /^Another poll or import for this source is still running/,
938
+ );
939
+ expect(fake.fetchCalls).toHaveLength(0);
940
+ expect(Semaphore.release).not.toHaveBeenCalled();
941
+ // lastError is left alone: contention is not a broken connection.
942
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
943
+ });
944
+
945
+ test("any other lock failure propagates as is", async () => {
946
+ getJestSpyOn(Semaphore, "lock").mockRejectedValue(
947
+ new Error("Redis unavailable") as never,
948
+ );
949
+ const fake: FakeConnector = makeFakeConnector({});
950
+
951
+ await expect(
952
+ SecurityEventConnectionPoller.executeConnection(
953
+ makeConnection(),
954
+ { type: "poll" },
955
+ overridesFor(fake),
956
+ ),
957
+ ).rejects.toThrow("Redis unavailable");
958
+ expect(fake.fetchCalls).toHaveLength(0);
959
+ });
960
+
961
+ test("a release failure is logged and does not fail the run", async () => {
962
+ getJestSpyOn(Semaphore, "release").mockRejectedValue(
963
+ new Error("lock already expired") as never,
964
+ );
965
+ const fake: FakeConnector = makeFakeConnector({});
966
+
967
+ const result: SecurityEventConnectionRunResult =
968
+ await SecurityEventConnectionPoller.executeConnection(
969
+ makeConnection(),
970
+ { type: "poll" },
971
+ overridesFor(fake),
972
+ );
973
+
974
+ expect(result.status).toBe("empty");
975
+ expect(logger.error).toHaveBeenCalledWith(
976
+ expect.stringContaining("could not release source lock"),
977
+ );
978
+ });
979
+
980
+ test("without overrides the connection is re-read under the lock, scoped to its project", async () => {
981
+ // The caller's snapshot says 5; the row as saved now says 15.
982
+ const reloaded: SecurityEventConnection = makeConnection({
983
+ pollIntervalInMinutes: 15,
984
+ });
985
+ getJestSpyOn(
986
+ SecurityEventConnectionService,
987
+ "findOneById",
988
+ ).mockResolvedValue(reloaded as never);
989
+ const fake: FakeConnector = makeFakeConnector({});
990
+ (
991
+ SecurityEventConnectorRegistry.getConnector as unknown as jest.Mock
992
+ ).mockReturnValue(fake.connector);
993
+
994
+ await SecurityEventConnectionPoller.executeConnection(makeConnection(), {
995
+ type: "poll",
996
+ });
997
+
998
+ expect(SecurityEventConnectionService.findOneById).toHaveBeenCalledWith(
999
+ expect.objectContaining({
1000
+ id: CONNECTION_ID,
1001
+ /*
1002
+ * Review finding F1: the adaptive chunk lives in lastPollResult, so
1003
+ * the in-lock reload must select it or every poll restarts at a day.
1004
+ */
1005
+ select: expect.objectContaining({
1006
+ secrets: true,
1007
+ cursor: true,
1008
+ lastPollResult: true,
1009
+ /*
1010
+ * The connector is told the saved interval (Google SecOps measures
1011
+ * late-created detections against it), so it must be reloaded too.
1012
+ */
1013
+ pollIntervalInMinutes: true,
1014
+ }),
1015
+ props: { isRoot: true },
1016
+ }),
1017
+ );
1018
+ expect(SecurityEventConnectorRegistry.getConnector).toHaveBeenCalledWith(
1019
+ PROVIDER,
1020
+ );
1021
+ expect(
1022
+ SecurityEventConnectionService.getConnectorSettings,
1023
+ ).toHaveBeenCalledWith(reloaded);
1024
+ expect(fake.fetchCalls).toHaveLength(1);
1025
+ // The reloaded row's interval, not the caller's stale snapshot.
1026
+ expect(fake.fetchCalls[0]!.options.pollIntervalInMinutes).toBe(15);
1027
+ });
1028
+
1029
+ test.each<[string, SecurityEventConnection | null]>([
1030
+ ["deleted", null],
1031
+ [
1032
+ "moved to another project",
1033
+ makeConnection({
1034
+ projectId: new ObjectID("55555555-5555-4555-8555-555555555555"),
1035
+ }),
1036
+ ],
1037
+ ])(
1038
+ "a connection %s while waiting for the lock is refused",
1039
+ async (_label: string, reloaded: SecurityEventConnection | null) => {
1040
+ getJestSpyOn(
1041
+ SecurityEventConnectionService,
1042
+ "findOneById",
1043
+ ).mockResolvedValue(reloaded as never);
1044
+
1045
+ await expect(
1046
+ SecurityEventConnectionPoller.executeConnection(makeConnection(), {
1047
+ type: "poll",
1048
+ }),
1049
+ ).rejects.toThrow("no longer exists in this project");
1050
+ expect(Semaphore.release).toHaveBeenCalledTimes(1);
1051
+ },
1052
+ );
1053
+ });
1054
+
1055
+ describe("SecurityEventConnectionPoller.executeConnection - poll", () => {
1056
+ test("a complete poll imports the events, advances the cursor to the window end and stamps every timestamp", async () => {
1057
+ const eventTime: Date = new Date(Date.now() - 10 * MINUTE_MS);
1058
+ const fake: FakeConnector = makeFakeConnector({
1059
+ fetch: makeFetchResult([
1060
+ makeEvent("evt-1", eventTime),
1061
+ makeEvent("evt-2", new Date(eventTime.getTime() + MINUTE_MS)),
1062
+ ]),
1063
+ });
1064
+
1065
+ const result: SecurityEventConnectionRunResult =
1066
+ await SecurityEventConnectionPoller.executeConnection(
1067
+ makeConnection(),
1068
+ { type: "poll", runId: "run-1" },
1069
+ overridesFor(fake),
1070
+ );
1071
+
1072
+ expect(result.status).toBe("success");
1073
+ expect(result.type).toBe("poll");
1074
+ expect(result.provider).toBe(PROVIDER);
1075
+ expect(result.runId).toBe("run-1");
1076
+ expect(result.complete).toBe(true);
1077
+ expect(result.fetchedCount).toBe(2);
1078
+ expect(result.ingestedCount).toBe(2);
1079
+ expect(result.duplicateCount).toBe(0);
1080
+ expect(result.requestCount).toBe(1);
1081
+ expect(result.error).toBeUndefined();
1082
+ expect(result.eventTimeStart).toBe(eventTime.toISOString());
1083
+ expect(result.eventTimeEnd).toBe(
1084
+ new Date(eventTime.getTime() + MINUTE_MS).toISOString(),
1085
+ );
1086
+ expect(
1087
+ result.checks.map((item: SecurityConnectorCheck): string => {
1088
+ return `${item.key}:${item.status}`;
1089
+ }),
1090
+ ).toEqual(["configuration:pass", "read:pass", "import:pass"]);
1091
+ expect(findCheck(result, "import").message).toBe(
1092
+ "2 imported; 0 already imported.",
1093
+ );
1094
+
1095
+ expect(SecurityEventConnectionService.updateOneById).toHaveBeenCalledTimes(
1096
+ 1,
1097
+ );
1098
+ const written: ConnectionUpdateCall = updateCall();
1099
+ expect(written.id.toString()).toBe(CONNECTION_ID.toString());
1100
+ expect(written.data["cursor"]).toBe(result.windowEnd);
1101
+ expect(written.data["lastError"]).toBeNull();
1102
+ expect(written.data["lastPolledAt"]).toEqual(new Date(result.completedAt));
1103
+ expect(written.data["lastSuccessfulPollAt"]).toEqual(
1104
+ new Date(result.completedAt),
1105
+ );
1106
+ expect(written.data["lastEventIngestedAt"]).toEqual(
1107
+ new Date(result.completedAt),
1108
+ );
1109
+ expect(written.data["lastPollResult"]).toBe(result);
1110
+ // A complete first poll read its whole day, so the next chunk stays a day.
1111
+ expect(result.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
1112
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES);
1113
+ expect(result.forcedAdvance).toBeUndefined();
1114
+ expect(result.overlapFloor).toBeUndefined();
1115
+ });
1116
+
1117
+ test("fetch is asked for the poll window with every bound and the poll timeout", async () => {
1118
+ const fake: FakeConnector = makeFakeConnector({});
1119
+ const cursor: Date = new Date(Date.now() - 5 * MINUTE_MS);
1120
+
1121
+ await SecurityEventConnectionPoller.executeConnection(
1122
+ makeConnection({ cursor: cursor.toISOString() }),
1123
+ { type: "poll" },
1124
+ overridesFor(fake),
1125
+ );
1126
+
1127
+ expect(fake.validateCalls).toEqual([SETTINGS]);
1128
+ expect(fake.fetchCalls).toHaveLength(1);
1129
+ const call: FakeConnector["fetchCalls"][number] = fake.fetchCalls[0]!;
1130
+ expect(call.settings).toBe(SETTINGS);
1131
+ expect(call.window.startTime.toISOString()).toBe(
1132
+ new Date(cursor.getTime() - OVERLAP_MINUTES * MINUTE_MS).toISOString(),
1133
+ );
1134
+ expect(call.window.endTime.getTime()).toBeGreaterThan(cursor.getTime());
1135
+ expect(call.options).toEqual({
1136
+ maxRequests: MAX_FETCH_REQUESTS,
1137
+ maxEvents: MAX_EVENTS_PER_RUN,
1138
+ requestTimeoutInMs: POLL_REQUEST_TIMEOUT_IN_MS,
1139
+ sampleLimit: MAX_DIAGNOSTIC_SAMPLES,
1140
+ purpose: "poll",
1141
+ pollIntervalInMinutes: 5,
1142
+ });
1143
+ // No connector budget, so no wall clock is imposed.
1144
+ expect(call.options).not.toHaveProperty("maxDurationMs");
1145
+ });
1146
+
1147
+ test("a poll that reads nothing is 'empty', writes nothing to storage and still advances the cursor", async () => {
1148
+ const fake: FakeConnector = makeFakeConnector({});
1149
+
1150
+ const result: SecurityEventConnectionRunResult =
1151
+ await SecurityEventConnectionPoller.executeConnection(
1152
+ makeConnection(),
1153
+ { type: "poll" },
1154
+ overridesFor(fake),
1155
+ );
1156
+
1157
+ expect(result.status).toBe("empty");
1158
+ expect(result.complete).toBe(true);
1159
+ expect(SecurityEventDedupe.findExistingEventUids).not.toHaveBeenCalled();
1160
+ expect(OTelIngestService.telemetryServiceFromName).not.toHaveBeenCalled();
1161
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
1162
+ expect(findCheck(result, "import").message).toBe(
1163
+ "0 imported; 0 already imported.",
1164
+ );
1165
+
1166
+ const written: ConnectionUpdateCall = updateCall();
1167
+ expect(written.data["cursor"]).toBe(result.windowEnd);
1168
+ expect(written.data["lastSuccessfulPollAt"]).toBeInstanceOf(Date);
1169
+ expect(written.data["lastEventIngestedAt"]).toBeUndefined();
1170
+ expect(written.data["lastError"]).toBeNull();
1171
+ });
1172
+
1173
+ test("an incomplete fetch that cannot resume holds the cursor, halves the next chunk, records the warnings and is 'partial'", async () => {
1174
+ // Review finding F1: holding alone re-read the same window forever; the next window must be narrower.
1175
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1176
+ const fake: FakeConnector = makeFakeConnector({
1177
+ fetch: makeFetchResult([makeEvent("evt-1")], {
1178
+ complete: false,
1179
+ requestCount: MAX_FETCH_REQUESTS,
1180
+ warnings: ["Stopped after 20 requests; the window was not fully read."],
1181
+ }),
1182
+ });
1183
+
1184
+ const result: SecurityEventConnectionRunResult =
1185
+ await SecurityEventConnectionPoller.executeConnection(
1186
+ makeConnection({
1187
+ cursor: cursor.toISOString(),
1188
+ lastPollResult: { nextChunkMinutes: 90 },
1189
+ }),
1190
+ { type: "poll" },
1191
+ overridesFor(fake),
1192
+ );
1193
+
1194
+ expect(result.status).toBe("partial");
1195
+ expect(result.complete).toBe(false);
1196
+ expect(result.ingestedCount).toBe(1);
1197
+ expect(result.chunkMinutes).toBe(90);
1198
+ expect(result.nextChunkMinutes).toBe(45);
1199
+ expect(result.forcedAdvance).toBeUndefined();
1200
+ expect(result.warnings).toEqual([
1201
+ "Catching up from the saved cursor in 90 minute windows. Later records will be fetched by subsequent polls.",
1202
+ "Stopped after 20 requests; the window was not fully read.",
1203
+ "This window holds more records than one poll can read; the next poll reads a 45 minute window from the same starting point.",
1204
+ ]);
1205
+ expect(logger.warn).toHaveBeenCalledWith(
1206
+ expect.stringContaining("the next poll reads a 45 minute window"),
1207
+ );
1208
+
1209
+ const written: ConnectionUpdateCall = updateCall();
1210
+ // The cursor key is absent, so the stored cursor stays where it was.
1211
+ expect("cursor" in written.data).toBe(false);
1212
+ expect(written.data["lastSuccessfulPollAt"]).toBeUndefined();
1213
+ expect(written.data["lastEventIngestedAt"]).toBeInstanceOf(Date);
1214
+ expect(written.data["lastError"]).toBe(result.warnings.join(" "));
1215
+ expect(written.data["lastPolledAt"]).toBeInstanceOf(Date);
1216
+ expect(
1217
+ (written.data["lastPollResult"] as JSONObject)["nextChunkMinutes"],
1218
+ ).toBe(45);
1219
+ });
1220
+
1221
+ test("an incomplete fetch with no warning text still explains what the next poll does", async () => {
1222
+ // Review finding F1: the old fallback promised a retry of "the same window", which is what pinned polling.
1223
+ const fake: FakeConnector = makeFakeConnector({
1224
+ fetch: makeFetchResult([], { complete: false }),
1225
+ });
1226
+
1227
+ const result: SecurityEventConnectionRunResult =
1228
+ await SecurityEventConnectionPoller.executeConnection(
1229
+ makeConnection({
1230
+ cursor: new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS).toISOString(),
1231
+ lastPollResult: { nextChunkMinutes: 10 },
1232
+ }),
1233
+ { type: "poll" },
1234
+ overridesFor(fake),
1235
+ );
1236
+
1237
+ expect(result.nextChunkMinutes).toBe(5);
1238
+ expect(updateCall().data["lastError"]).toBe(
1239
+ "Catching up from the saved cursor in 10 minute windows. Later records will be fetched by subsequent polls. This window holds more records than one poll can read; the next poll reads a 5 minute window from the same starting point.",
1240
+ );
1241
+ });
1242
+
1243
+ test("an incomplete first poll anchors the cursor one overlap into the window and halves the day", async () => {
1244
+ // Review finding F1: the anchor makes the next window start at the same instant, and the halved chunk makes it shorter.
1245
+ const fake: FakeConnector = makeFakeConnector({
1246
+ fetch: makeFetchResult([makeEvent("evt-1")], { complete: false }),
1247
+ });
1248
+
1249
+ const result: SecurityEventConnectionRunResult =
1250
+ await SecurityEventConnectionPoller.executeConnection(
1251
+ makeConnection(),
1252
+ { type: "poll" },
1253
+ overridesFor(fake),
1254
+ );
1255
+
1256
+ expect(result.status).toBe("partial");
1257
+ expect(result.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
1258
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES / 2);
1259
+ const written: ConnectionUpdateCall = updateCall();
1260
+ const anchor: string = new Date(
1261
+ new Date(result.windowStart).getTime() + OVERLAP_MINUTES * MINUTE_MS,
1262
+ ).toISOString();
1263
+ expect(written.data["cursor"]).toBe(anchor);
1264
+ expect(written.data["lastSuccessfulPollAt"]).toBeUndefined();
1265
+
1266
+ // The next poll starts where this one did and reads half a day past the anchor.
1267
+ const next: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
1268
+ SecurityEventConnectionPoller.getWindow(
1269
+ makeConnection({
1270
+ cursor: anchor,
1271
+ lastPollResult: JSON.parse(JSON.stringify(result)) as JSONObject,
1272
+ }),
1273
+ { type: "poll" },
1274
+ new Date(new Date(result.windowEnd).getTime() + 5 * MINUTE_MS),
1275
+ );
1276
+ expect(next.startTime.toISOString()).toBe(result.windowStart);
1277
+ expect(next.endTime.getTime()).toBe(
1278
+ new Date(anchor).getTime() + (MAX_CHUNK_MINUTES / 2) * MINUTE_MS,
1279
+ );
1280
+ });
1281
+
1282
+ test("a complete poll doubles the chunk it used, capped at a day", async () => {
1283
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1284
+
1285
+ for (const [used, expected] of [
1286
+ [45, 90],
1287
+ [720, MAX_CHUNK_MINUTES],
1288
+ [MAX_CHUNK_MINUTES, MAX_CHUNK_MINUTES],
1289
+ ] as Array<[number, number]>) {
1290
+ const fake: FakeConnector = makeFakeConnector({});
1291
+ const result: SecurityEventConnectionRunResult =
1292
+ await SecurityEventConnectionPoller.executeConnection(
1293
+ makeConnection({
1294
+ cursor: cursor.toISOString(),
1295
+ lastPollResult: { nextChunkMinutes: used },
1296
+ }),
1297
+ { type: "poll" },
1298
+ overridesFor(fake),
1299
+ );
1300
+
1301
+ expect({ used, chunk: result.chunkMinutes }).toEqual({
1302
+ used,
1303
+ chunk: used,
1304
+ });
1305
+ expect({ used, next: result.nextChunkMinutes }).toEqual({
1306
+ used,
1307
+ next: expected,
1308
+ });
1309
+ expect(result.windowEnd).toBe(
1310
+ new Date(cursor.getTime() + used * MINUTE_MS).toISOString(),
1311
+ );
1312
+ }
1313
+ });
1314
+
1315
+ test("a poll that stops on a bound after real progress resumes from resumeAfter and keeps the chunk", async () => {
1316
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1317
+ const resumeAfter: Date = new Date(cursor.getTime() + 20 * MINUTE_MS);
1318
+ const fake: FakeConnector = makeFakeConnector({
1319
+ fetch: makeFetchResult([makeEvent("evt-1", resumeAfter)], {
1320
+ complete: false,
1321
+ resumeAfter,
1322
+ }),
1323
+ });
1324
+
1325
+ const result: SecurityEventConnectionRunResult =
1326
+ await SecurityEventConnectionPoller.executeConnection(
1327
+ makeConnection({
1328
+ cursor: cursor.toISOString(),
1329
+ lastPollResult: { nextChunkMinutes: 60 },
1330
+ }),
1331
+ { type: "poll" },
1332
+ overridesFor(fake),
1333
+ );
1334
+
1335
+ expect(result.status).toBe("partial");
1336
+ expect(result.chunkMinutes).toBe(60);
1337
+ expect(result.nextChunkMinutes).toBe(60);
1338
+ expect(result.forcedAdvance).toBeUndefined();
1339
+ expect(result.warnings).toContain(
1340
+ `This poll stopped before the end of its window after reading records created up to ${resumeAfter.toISOString()}; the next poll resumes from there.`,
1341
+ );
1342
+ const written: ConnectionUpdateCall = updateCall();
1343
+ expect(written.data["cursor"]).toBe(resumeAfter.toISOString());
1344
+ expect(written.data["lastSuccessfulPollAt"]).toBeUndefined();
1345
+ expect(written.data["lastError"]).toContain(resumeAfter.toISOString());
1346
+ });
1347
+
1348
+ test.each<[string, (cursor: Date, windowEnd: Date) => Date]>([
1349
+ [
1350
+ "inside the overlap",
1351
+ (cursor: Date): Date => {
1352
+ return new Date(cursor.getTime() - 5 * MINUTE_MS);
1353
+ },
1354
+ ],
1355
+ [
1356
+ "exactly on the previous cursor",
1357
+ (cursor: Date): Date => {
1358
+ return cursor;
1359
+ },
1360
+ ],
1361
+ [
1362
+ "after the window end",
1363
+ (_cursor: Date, windowEnd: Date): Date => {
1364
+ return new Date(windowEnd.getTime() + MINUTE_MS);
1365
+ },
1366
+ ],
1367
+ [
1368
+ "that is not a valid date",
1369
+ (): Date => {
1370
+ return new Date("not a date");
1371
+ },
1372
+ ],
1373
+ ])(
1374
+ "a resumeAfter %s is not progress: the cursor holds and the chunk halves",
1375
+ async (_label: string, pick: (cursor: Date, windowEnd: Date) => Date) => {
1376
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1377
+ const windowEnd: Date = new Date(cursor.getTime() + 60 * MINUTE_MS);
1378
+ const fake: FakeConnector = makeFakeConnector({
1379
+ fetch: makeFetchResult([], {
1380
+ complete: false,
1381
+ resumeAfter: pick(cursor, windowEnd),
1382
+ }),
1383
+ });
1384
+
1385
+ const result: SecurityEventConnectionRunResult =
1386
+ await SecurityEventConnectionPoller.executeConnection(
1387
+ makeConnection({
1388
+ cursor: cursor.toISOString(),
1389
+ lastPollResult: { nextChunkMinutes: 60 },
1390
+ }),
1391
+ { type: "poll" },
1392
+ overridesFor(fake),
1393
+ );
1394
+
1395
+ expect(result.windowEnd).toBe(windowEnd.toISOString());
1396
+ expect(result.nextChunkMinutes).toBe(30);
1397
+ expect("cursor" in updateCall().data).toBe(false);
1398
+ },
1399
+ );
1400
+
1401
+ test("a one-minute window that still cannot be read is skipped past with a loud lastError and an overlap floor", async () => {
1402
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1403
+ const windowEnd: Date = new Date(cursor.getTime() + MINUTE_MS);
1404
+ const fake: FakeConnector = makeFakeConnector({
1405
+ fetch: makeFetchResult([makeEvent("evt-1", cursor)], {
1406
+ complete: false,
1407
+ warnings: ["Stopped after 20 requests; the window was not fully read."],
1408
+ }),
1409
+ });
1410
+
1411
+ const result: SecurityEventConnectionRunResult =
1412
+ await SecurityEventConnectionPoller.executeConnection(
1413
+ makeConnection({
1414
+ cursor: cursor.toISOString(),
1415
+ lastPollResult: { nextChunkMinutes: MIN_CHUNK_MINUTES },
1416
+ }),
1417
+ { type: "poll" },
1418
+ overridesFor(fake),
1419
+ );
1420
+
1421
+ const forcedMessage: string = `More records were created in the one minute from ${cursor.toISOString()} to ${windowEnd.toISOString()} than one poll can read. Polling moved past this minute so newer records keep arriving; use Import this time range in Diagnostics on this minute to recover what one run can read.`;
1422
+ expect(result.status).toBe("partial");
1423
+ expect(result.complete).toBe(false);
1424
+ expect(result.forcedAdvance).toBe(true);
1425
+ expect(result.chunkMinutes).toBe(MIN_CHUNK_MINUTES);
1426
+ expect(result.nextChunkMinutes).toBe(MIN_CHUNK_MINUTES);
1427
+ expect(result.overlapFloor).toBe(windowEnd.toISOString());
1428
+ expect(result.warnings[0]).toBe(forcedMessage);
1429
+
1430
+ const written: ConnectionUpdateCall = updateCall();
1431
+ expect(written.data["cursor"]).toBe(windowEnd.toISOString());
1432
+ expect(String(written.data["lastError"]).startsWith(forcedMessage)).toBe(
1433
+ true,
1434
+ );
1435
+ expect(written.data["lastSuccessfulPollAt"]).toBeUndefined();
1436
+ });
1437
+
1438
+ test("a window after a forced advance starts at the overlap floor until the overlap no longer reaches it", () => {
1439
+ const floor: Date = new Date("2026-09-10T08:00:00.000Z");
1440
+ const now: Date = new Date("2026-09-10T12:00:00.000Z");
1441
+ const windowFor: (
1442
+ cursor: Date,
1443
+ overlapFloor: string | undefined,
1444
+ ) => ReturnType<typeof SecurityEventConnectionPoller.getWindow> = (
1445
+ cursor: Date,
1446
+ overlapFloor: string | undefined,
1447
+ ): ReturnType<typeof SecurityEventConnectionPoller.getWindow> => {
1448
+ return SecurityEventConnectionPoller.getWindow(
1449
+ makeConnection({
1450
+ cursor: cursor.toISOString(),
1451
+ lastPollResult: {
1452
+ nextChunkMinutes: 2,
1453
+ ...(overlapFloor ? { overlapFloor } : {}),
1454
+ },
1455
+ }),
1456
+ { type: "poll" },
1457
+ now,
1458
+ );
1459
+ };
1460
+
1461
+ // Right after the forced advance the cursor is the floor: no overlap at all.
1462
+ const atFloor: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
1463
+ windowFor(floor, floor.toISOString());
1464
+ expect(atFloor.startTime.toISOString()).toBe(floor.toISOString());
1465
+ expect(atFloor.overlapFloor?.toISOString()).toBe(floor.toISOString());
1466
+
1467
+ // Part of the overlap past the floor is still re-read.
1468
+ const inside: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
1469
+ windowFor(new Date(floor.getTime() + 5 * MINUTE_MS), floor.toISOString());
1470
+ expect(inside.startTime.toISOString()).toBe(floor.toISOString());
1471
+ expect(inside.overlapFloor?.toISOString()).toBe(floor.toISOString());
1472
+
1473
+ // Once cursor - overlap reaches the floor it no longer clamps and is dropped.
1474
+ const reached: ReturnType<typeof SecurityEventConnectionPoller.getWindow> =
1475
+ windowFor(
1476
+ new Date(floor.getTime() + OVERLAP_MINUTES * MINUTE_MS),
1477
+ floor.toISOString(),
1478
+ );
1479
+ expect(reached.startTime.toISOString()).toBe(floor.toISOString());
1480
+ expect(reached.overlapFloor).toBeUndefined();
1481
+
1482
+ // A floor later than the cursor, or unreadable, is ignored.
1483
+ for (const bad of [
1484
+ new Date(floor.getTime() + 60 * MINUTE_MS).toISOString(),
1485
+ "not a date",
1486
+ ]) {
1487
+ const ignored: ReturnType<
1488
+ typeof SecurityEventConnectionPoller.getWindow
1489
+ > = windowFor(floor, bad);
1490
+ expect(ignored.startTime.toISOString()).toBe(
1491
+ new Date(floor.getTime() - OVERLAP_MINUTES * MINUTE_MS).toISOString(),
1492
+ );
1493
+ expect(ignored.overlapFloor).toBeUndefined();
1494
+ }
1495
+ });
1496
+
1497
+ test("a poll clamped by the overlap floor resumes past the cursor even though the window starts later, and floors the next poll at the resume point", async () => {
1498
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1499
+ const floor: Date = new Date(cursor.getTime() - 5 * MINUTE_MS);
1500
+ const resumeAfter: Date = new Date(cursor.getTime() + 2 * MINUTE_MS);
1501
+ const fake: FakeConnector = makeFakeConnector({
1502
+ fetch: makeFetchResult([], { complete: false, resumeAfter }),
1503
+ });
1504
+
1505
+ const result: SecurityEventConnectionRunResult =
1506
+ await SecurityEventConnectionPoller.executeConnection(
1507
+ makeConnection({
1508
+ cursor: cursor.toISOString(),
1509
+ lastPollResult: {
1510
+ nextChunkMinutes: 10,
1511
+ overlapFloor: floor.toISOString(),
1512
+ },
1513
+ }),
1514
+ { type: "poll" },
1515
+ overridesFor(fake),
1516
+ );
1517
+
1518
+ expect(fake.fetchCalls[0]!.window.startTime.toISOString()).toBe(
1519
+ floor.toISOString(),
1520
+ );
1521
+ /*
1522
+ * A resume floors the next poll at the resume point itself: during a
1523
+ * backlog the overlap would only re-read what this poll just read.
1524
+ */
1525
+ expect(result.overlapFloor).toBe(resumeAfter.toISOString());
1526
+ /*
1527
+ * resumeAfter is 2 minutes past the cursor but earlier than window
1528
+ * start + overlap (10 minutes past it): it is still real progress.
1529
+ */
1530
+ expect(updateCall().data["cursor"]).toBe(resumeAfter.toISOString());
1531
+ // A resumed poll keeps the chunk it was given, not the stretch it read.
1532
+ expect(result.nextChunkMinutes).toBe(10);
1533
+ });
1534
+
1535
+ test("a caught-up complete poll never shrinks the chunk it was given", async () => {
1536
+ // Given a full day but only 5 minutes of new ground up to now.
1537
+ const cursor: Date = new Date(Date.now() - 5 * MINUTE_MS);
1538
+ const fake: FakeConnector = makeFakeConnector({
1539
+ fetch: makeFetchResult([], { complete: true }),
1540
+ });
1541
+
1542
+ const result: SecurityEventConnectionRunResult =
1543
+ await SecurityEventConnectionPoller.executeConnection(
1544
+ makeConnection({
1545
+ cursor: cursor.toISOString(),
1546
+ lastPollResult: { nextChunkMinutes: 1440 },
1547
+ }),
1548
+ { type: "poll" },
1549
+ overridesFor(fake),
1550
+ );
1551
+
1552
+ expect(result.chunkMinutes).toBeLessThanOrEqual(6);
1553
+ expect(result.nextChunkMinutes).toBe(1440);
1554
+ });
1555
+
1556
+ test("a complete poll after narrowing doubles back from the stretch it read", async () => {
1557
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1558
+ const fake: FakeConnector = makeFakeConnector({
1559
+ fetch: makeFetchResult([], { complete: true }),
1560
+ });
1561
+
1562
+ const result: SecurityEventConnectionRunResult =
1563
+ await SecurityEventConnectionPoller.executeConnection(
1564
+ makeConnection({
1565
+ cursor: cursor.toISOString(),
1566
+ lastPollResult: { nextChunkMinutes: 90 },
1567
+ }),
1568
+ { type: "poll" },
1569
+ overridesFor(fake),
1570
+ );
1571
+
1572
+ expect(result.chunkMinutes).toBe(90);
1573
+ expect(result.nextChunkMinutes).toBe(180);
1574
+ });
1575
+
1576
+ test("a caught-up failed poll keeps the chunk it was given", async () => {
1577
+ const cursor: Date = new Date(Date.now() - 5 * MINUTE_MS);
1578
+ const fake: FakeConnector = makeFakeConnector({
1579
+ fetch: new Error("Okta System Log events request failed (HTTP 500)"),
1580
+ });
1581
+
1582
+ const result: SecurityEventConnectionRunResult =
1583
+ await SecurityEventConnectionPoller.executeConnection(
1584
+ makeConnection({
1585
+ cursor: cursor.toISOString(),
1586
+ lastPollResult: { nextChunkMinutes: 1440 },
1587
+ }),
1588
+ { type: "poll" },
1589
+ overridesFor(fake),
1590
+ );
1591
+
1592
+ expect(result.status).toBe("failed");
1593
+ expect(result.nextChunkMinutes).toBe(1440);
1594
+ });
1595
+
1596
+ test("a request that timed out on a wide window narrows the next one instead of retrying it forever", async () => {
1597
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1598
+ const fake: FakeConnector = makeFakeConnector({
1599
+ fetch: new Error(
1600
+ "Splunk Enterprise Security search did not complete: timeout of 60000ms exceeded",
1601
+ ),
1602
+ });
1603
+
1604
+ const result: SecurityEventConnectionRunResult =
1605
+ await SecurityEventConnectionPoller.executeConnection(
1606
+ makeConnection({
1607
+ cursor: cursor.toISOString(),
1608
+ lastPollResult: { nextChunkMinutes: 1440 },
1609
+ }),
1610
+ { type: "poll" },
1611
+ overridesFor(fake),
1612
+ );
1613
+
1614
+ expect(result.status).toBe("failed");
1615
+ expect(result.nextChunkMinutes).toBe(720);
1616
+ expect(
1617
+ result.warnings.some((warning: string): boolean => {
1618
+ return warning.includes("did not answer in time");
1619
+ }),
1620
+ ).toBe(true);
1621
+ // The cursor is held: nothing in the window was read.
1622
+ expect(updateCall().data["cursor"]).toBeUndefined();
1623
+ });
1624
+
1625
+ test("a failed fetch records a redacted error, a failure check named after the phase, and holds a usable cursor and its chunk", async () => {
1626
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1627
+ const fake: FakeConnector = makeFakeConnector({
1628
+ fetch: new Error(
1629
+ `Okta system log read failed (HTTP 401): client_secret=${SECRET_VALUE} rejected`,
1630
+ ),
1631
+ });
1632
+
1633
+ const result: SecurityEventConnectionRunResult =
1634
+ await SecurityEventConnectionPoller.executeConnection(
1635
+ makeConnection({ cursor: cursor.toISOString() }),
1636
+ { type: "poll" },
1637
+ overridesFor(fake),
1638
+ );
1639
+
1640
+ expect(result.status).toBe("failed");
1641
+ expect(result.complete).toBe(false);
1642
+ expect(result.error).toContain("Okta system log read failed (HTTP 401)");
1643
+ expect(result.error).not.toContain(SECRET_VALUE);
1644
+ expect(result.error).toContain("[REDACTED]");
1645
+
1646
+ // Review finding F1: a failure is not a volume signal, so the chunk is unchanged.
1647
+ expect(result.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
1648
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES);
1649
+ expect(result.forcedAdvance).toBeUndefined();
1650
+
1651
+ const failure: SecurityConnectorCheck = findCheck(result, "failure");
1652
+ expect(failure.status).toBe("fail");
1653
+ expect(failure.name).toBe(
1654
+ `Read ${DEFINITION.importedRecordName}s from ${DEFINITION.title}`,
1655
+ );
1656
+ expect(failure.message).toBe(result.error);
1657
+ expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
1658
+
1659
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
1660
+ const written: ConnectionUpdateCall = updateCall();
1661
+ expect("cursor" in written.data).toBe(false);
1662
+ expect(written.data["lastError"]).toBe(result.error);
1663
+ expect(written.data["lastSuccessfulPollAt"]).toBeUndefined();
1664
+ expect(written.data["lastPolledAt"]).toBeInstanceOf(Date);
1665
+ });
1666
+
1667
+ test("a failed first poll still anchors a cursor so the next poll does not restart from a day ago", async () => {
1668
+ const fake: FakeConnector = makeFakeConnector({
1669
+ fetch: new Error("Okta system log read failed (HTTP 503): unavailable"),
1670
+ });
1671
+
1672
+ const result: SecurityEventConnectionRunResult =
1673
+ await SecurityEventConnectionPoller.executeConnection(
1674
+ makeConnection(),
1675
+ { type: "poll" },
1676
+ overridesFor(fake),
1677
+ );
1678
+
1679
+ expect(result.status).toBe("failed");
1680
+ expect(updateCall().data["cursor"]).toBe(
1681
+ new Date(
1682
+ new Date(result.windowStart).getTime() + OVERLAP_MINUTES * MINUTE_MS,
1683
+ ).toISOString(),
1684
+ );
1685
+ });
1686
+
1687
+ test("a failed poll keeps a narrowed chunk and carries the overlap floor unchanged", async () => {
1688
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1689
+ const floor: Date = new Date(cursor.getTime() - MINUTE_MS);
1690
+ const fake: FakeConnector = makeFakeConnector({
1691
+ fetch: new Error("Okta system log read failed (HTTP 503): unavailable"),
1692
+ });
1693
+
1694
+ const result: SecurityEventConnectionRunResult =
1695
+ await SecurityEventConnectionPoller.executeConnection(
1696
+ makeConnection({
1697
+ cursor: cursor.toISOString(),
1698
+ lastPollResult: {
1699
+ nextChunkMinutes: 22,
1700
+ overlapFloor: floor.toISOString(),
1701
+ },
1702
+ }),
1703
+ { type: "poll" },
1704
+ overridesFor(fake),
1705
+ );
1706
+
1707
+ expect(result.status).toBe("failed");
1708
+ expect(result.chunkMinutes).toBe(22);
1709
+ expect(result.nextChunkMinutes).toBe(22);
1710
+ expect(result.overlapFloor).toBe(floor.toISOString());
1711
+ expect("cursor" in updateCall().data).toBe(false);
1712
+ });
1713
+
1714
+ test("an unreadable saved cursor is replaced by an anchored one after a failed poll", async () => {
1715
+ const fake: FakeConnector = makeFakeConnector({
1716
+ fetch: new Error("boom"),
1717
+ });
1718
+
1719
+ const result: SecurityEventConnectionRunResult =
1720
+ await SecurityEventConnectionPoller.executeConnection(
1721
+ makeConnection({ cursor: "garbage" }),
1722
+ { type: "poll" },
1723
+ overridesFor(fake),
1724
+ );
1725
+
1726
+ expect(
1727
+ result.warnings.some((warning: string): boolean => {
1728
+ return warning.includes("unreadable");
1729
+ }),
1730
+ ).toBe(true);
1731
+ expect(typeof updateCall().data["cursor"]).toBe("string");
1732
+ });
1733
+
1734
+ test("a validateSettings failure fails the run in the configuration phase without fetching", async () => {
1735
+ const fake: FakeConnector = makeFakeConnector({
1736
+ validate: new BadDataException("Okta organization URL must use https."),
1737
+ });
1738
+
1739
+ const result: SecurityEventConnectionRunResult =
1740
+ await SecurityEventConnectionPoller.executeConnection(
1741
+ makeConnection(),
1742
+ { type: "poll" },
1743
+ overridesFor(fake),
1744
+ );
1745
+
1746
+ expect(result.status).toBe("failed");
1747
+ expect(result.error).toBe("Okta organization URL must use https.");
1748
+ expect(result.checks).toHaveLength(1);
1749
+ expect(result.checks[0]!.key).toBe("failure");
1750
+ expect(result.checks[0]!.name).toBe("Validate configuration");
1751
+ expect(fake.fetchCalls).toHaveLength(0);
1752
+ expect(updateCall().data["lastError"]).toBe(
1753
+ "Okta organization URL must use https.",
1754
+ );
1755
+ });
1756
+
1757
+ test("rejected records warn but do not hold the cursor", async () => {
1758
+ const cursor: Date = new Date(Date.now() - 5 * MINUTE_MS);
1759
+ const fake: FakeConnector = makeFakeConnector({
1760
+ fetch: makeFetchResult([makeEvent("evt-1")], {
1761
+ fetchedCount: 4,
1762
+ rejectedCount: 3,
1763
+ }),
1764
+ });
1765
+
1766
+ const result: SecurityEventConnectionRunResult =
1767
+ await SecurityEventConnectionPoller.executeConnection(
1768
+ makeConnection({ cursor: cursor.toISOString() }),
1769
+ { type: "poll" },
1770
+ overridesFor(fake),
1771
+ );
1772
+
1773
+ expect(result.status).toBe("success");
1774
+ expect(result.complete).toBe(true);
1775
+ expect(result.rejectedCount).toBe(3);
1776
+ expect(result.ingestedCount).toBe(1);
1777
+ expect(result.warnings).toContain(
1778
+ `3 returned records were discarded because they do not look like ${DEFINITION.title} ${DEFINITION.importedRecordName}s.`,
1779
+ );
1780
+
1781
+ const read: SecurityConnectorCheck = findCheck(result, "read");
1782
+ expect(read.status).toBe("warn");
1783
+ expect(read.message).toBe(
1784
+ `1 ${DEFINITION.importedRecordName}s recognized; 3 rejected; 0 failed; 1 request.`,
1785
+ );
1786
+
1787
+ const written: ConnectionUpdateCall = updateCall();
1788
+ expect(written.data["cursor"]).toBe(result.windowEnd);
1789
+ expect(written.data["lastSuccessfulPollAt"]).toBeInstanceOf(Date);
1790
+ expect(written.data["lastError"]).toBeNull();
1791
+ });
1792
+
1793
+ test("normalization failures hold the cursor for retry, narrow the next window and fail the read check", async () => {
1794
+ /*
1795
+ * Review finding F1: holding alone would pin polling behind a record
1796
+ * that never normalizes; the window now narrows around it instead.
1797
+ */
1798
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1799
+ const fake: FakeConnector = makeFakeConnector({
1800
+ fetch: makeFetchResult([makeEvent("evt-1")], {
1801
+ fetchedCount: 3,
1802
+ failedCount: 2,
1803
+ requestCount: 2,
1804
+ // A complete fetch has nothing to resume from, even if a connector says so.
1805
+ resumeAfter: new Date(cursor.getTime() + 30 * MINUTE_MS),
1806
+ }),
1807
+ });
1808
+
1809
+ const result: SecurityEventConnectionRunResult =
1810
+ await SecurityEventConnectionPoller.executeConnection(
1811
+ makeConnection({
1812
+ cursor: cursor.toISOString(),
1813
+ lastPollResult: { nextChunkMinutes: 60 },
1814
+ }),
1815
+ { type: "poll" },
1816
+ overridesFor(fake),
1817
+ );
1818
+
1819
+ expect(result.status).toBe("partial");
1820
+ expect(result.complete).toBe(false);
1821
+ expect(result.failedCount).toBe(2);
1822
+ // The recognizable record is still imported; only the cursor holds.
1823
+ expect(result.ingestedCount).toBe(1);
1824
+ expect(result.nextChunkMinutes).toBe(30);
1825
+ expect(result.warnings).toContain(
1826
+ "2 records could not be normalized and were not imported.",
1827
+ );
1828
+ expect(result.warnings).toContain(
1829
+ "Some records in this window could not be normalized; the next poll reads a 30 minute window from the same starting point to retry them.",
1830
+ );
1831
+
1832
+ const read: SecurityConnectorCheck = findCheck(result, "read");
1833
+ expect(read.status).toBe("fail");
1834
+ expect(read.message).toBe(
1835
+ `1 ${DEFINITION.importedRecordName}s recognized; 0 rejected; 2 failed; 2 requests.`,
1836
+ );
1837
+
1838
+ const written: ConnectionUpdateCall = updateCall();
1839
+ expect("cursor" in written.data).toBe(false);
1840
+ expect(written.data["lastSuccessfulPollAt"]).toBeUndefined();
1841
+ expect(written.data["lastError"]).toContain("could not be normalized");
1842
+ });
1843
+
1844
+ test("a one-minute window whose records still fail to normalize is skipped past, naming the failures", async () => {
1845
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
1846
+ const windowEnd: Date = new Date(cursor.getTime() + MINUTE_MS);
1847
+ const fake: FakeConnector = makeFakeConnector({
1848
+ fetch: makeFetchResult([], { fetchedCount: 4, failedCount: 4 }),
1849
+ });
1850
+
1851
+ const result: SecurityEventConnectionRunResult =
1852
+ await SecurityEventConnectionPoller.executeConnection(
1853
+ makeConnection({
1854
+ cursor: cursor.toISOString(),
1855
+ lastPollResult: { nextChunkMinutes: MIN_CHUNK_MINUTES },
1856
+ }),
1857
+ { type: "poll" },
1858
+ overridesFor(fake),
1859
+ );
1860
+
1861
+ expect(result.forcedAdvance).toBe(true);
1862
+ expect(result.warnings[0]).toBe(
1863
+ `4 records created in the one minute from ${cursor.toISOString()} to ${windowEnd.toISOString()} could not be normalized. Polling moved past this minute so newer records keep arriving; use Import this time range in Diagnostics on this minute to retry them.`,
1864
+ );
1865
+ const written: ConnectionUpdateCall = updateCall();
1866
+ expect(written.data["cursor"]).toBe(windowEnd.toISOString());
1867
+ expect(String(written.data["lastError"])).toContain(
1868
+ "could not be normalized. Polling moved past this minute",
1869
+ );
1870
+ });
1871
+
1872
+ test("diagnostic samples are capped and warnings from the connector are kept", async () => {
1873
+ const events: Array<NormalizedSecurityEvent> = [];
1874
+ for (let index: number = 0; index < MAX_DIAGNOSTIC_SAMPLES + 5; index++) {
1875
+ events.push(makeEvent(`evt-${index}`));
1876
+ }
1877
+ const fake: FakeConnector = makeFakeConnector({
1878
+ fetch: makeFetchResult(events, {
1879
+ warnings: ["Some records were truncated by the source."],
1880
+ }),
1881
+ });
1882
+
1883
+ const result: SecurityEventConnectionRunResult =
1884
+ await SecurityEventConnectionPoller.executeConnection(
1885
+ makeConnection(),
1886
+ { type: "poll" },
1887
+ overridesFor(fake),
1888
+ );
1889
+
1890
+ expect(result.samples).toHaveLength(MAX_DIAGNOSTIC_SAMPLES);
1891
+ expect(result.warnings).toEqual([
1892
+ "Some records were truncated by the source.",
1893
+ ]);
1894
+ expect(result.ingestedCount).toBe(MAX_DIAGNOSTIC_SAMPLES + 5);
1895
+ });
1896
+ });
1897
+
1898
+ describe("SecurityEventConnectionPoller.executeConnection - ingest", () => {
1899
+ test("rows carry the catalog vendor/product, the connection attributes and the project", async () => {
1900
+ const fake: FakeConnector = makeFakeConnector({
1901
+ fetch: makeFetchResult([makeEvent("evt-1")]),
1902
+ });
1903
+
1904
+ await SecurityEventConnectionPoller.executeConnection(
1905
+ makeConnection(),
1906
+ { type: "poll" },
1907
+ overridesFor(fake),
1908
+ );
1909
+
1910
+ expect(insertedRows).toHaveLength(1);
1911
+ const row: JSONObject = insertedRows[0]!;
1912
+ expect(row["vendorName"]).toBe(DEFINITION.vendorName);
1913
+ expect(row["productName"]).toBe(DEFINITION.productName);
1914
+ expect(row["projectId"]).toBe(PROJECT_ID.toString());
1915
+ expect(row["eventUid"]).toBe("evt-1");
1916
+ expect(row["classUid"]).toBe(3002);
1917
+
1918
+ const attributes: JSONObject = row["attributes"] as JSONObject;
1919
+ expect(attributes["oneuptime.security_connection.id"]).toBe(
1920
+ CONNECTION_ID.toString(),
1921
+ );
1922
+ expect(attributes["oneuptime.security_connection.provider"]).toBe(PROVIDER);
1923
+ expect(attributes["okta.eventType"]).toBe("user.session.start");
1924
+ expect(JSON.stringify(row)).not.toContain(SECRET_VALUE);
1925
+ });
1926
+
1927
+ test("the insert asks ClickHouse for a synchronous, replicated acknowledgement", async () => {
1928
+ const fake: FakeConnector = makeFakeConnector({
1929
+ fetch: makeFetchResult([makeEvent("evt-1")]),
1930
+ });
1931
+
1932
+ await SecurityEventConnectionPoller.executeConnection(
1933
+ makeConnection(),
1934
+ { type: "poll" },
1935
+ overridesFor(fake),
1936
+ );
1937
+
1938
+ expect(SecurityEventService.insertJsonRows).toHaveBeenCalledTimes(1);
1939
+ expect(insertOptions).toEqual([
1940
+ { clickhouseSettings: { async_insert: 0, insert_distributed_sync: 1 } },
1941
+ ]);
1942
+ });
1943
+
1944
+ test("the telemetry service is resolved by product name and events are enriched before insert", async () => {
1945
+ const fake: FakeConnector = makeFakeConnector({
1946
+ fetch: makeFetchResult([makeEvent("evt-1")]),
1947
+ });
1948
+
1949
+ await SecurityEventConnectionPoller.executeConnection(
1950
+ makeConnection(),
1951
+ { type: "poll" },
1952
+ overridesFor(fake),
1953
+ );
1954
+
1955
+ expect(OTelIngestService.telemetryServiceFromName).toHaveBeenCalledWith({
1956
+ serviceName: DEFINITION.productName,
1957
+ projectId: PROJECT_ID,
1958
+ });
1959
+ expect(ThreatIntelEnricher.enrichNormalizedEvents).toHaveBeenCalledWith({
1960
+ projectId: PROJECT_ID,
1961
+ events: [expect.objectContaining({ eventUid: "evt-1" })],
1962
+ });
1963
+ });
1964
+
1965
+ test("dedupes against storage by vendor/product and within the batch", async () => {
1966
+ existingUids = new Set(["evt-stored"]);
1967
+ const fake: FakeConnector = makeFakeConnector({
1968
+ fetch: makeFetchResult([
1969
+ makeEvent("evt-stored"),
1970
+ makeEvent("evt-new"),
1971
+ makeEvent("evt-new"),
1972
+ makeEvent("evt-other"),
1973
+ ]),
1974
+ });
1975
+
1976
+ const result: SecurityEventConnectionRunResult =
1977
+ await SecurityEventConnectionPoller.executeConnection(
1978
+ makeConnection(),
1979
+ { type: "poll" },
1980
+ overridesFor(fake),
1981
+ );
1982
+
1983
+ expect(dedupeCalls).toHaveLength(1);
1984
+ expect(dedupeCalls[0]).toEqual({
1985
+ projectId: PROJECT_ID,
1986
+ vendorName: DEFINITION.vendorName,
1987
+ productName: DEFINITION.productName,
1988
+ // The in-batch duplicate is removed before the lookup.
1989
+ ids: ["evt-stored", "evt-new", "evt-other"],
1990
+ });
1991
+ expect(result.duplicateCount).toBe(2);
1992
+ expect(result.ingestedCount).toBe(2);
1993
+ expect(
1994
+ insertedRows.map((row: JSONObject): string => {
1995
+ return row["eventUid"] as string;
1996
+ }),
1997
+ ).toEqual(["evt-new", "evt-other"]);
1998
+ expect(findCheck(result, "import").message).toBe(
1999
+ "2 imported; 2 already imported.",
2000
+ );
2001
+ });
2002
+
2003
+ test("a batch that is entirely duplicates writes nothing and does not stamp lastEventIngestedAt", async () => {
2004
+ existingUids = new Set(["evt-1", "evt-2"]);
2005
+ const fake: FakeConnector = makeFakeConnector({
2006
+ fetch: makeFetchResult([makeEvent("evt-1"), makeEvent("evt-2")]),
2007
+ });
2008
+
2009
+ const result: SecurityEventConnectionRunResult =
2010
+ await SecurityEventConnectionPoller.executeConnection(
2011
+ makeConnection(),
2012
+ { type: "poll" },
2013
+ overridesFor(fake),
2014
+ );
2015
+
2016
+ expect(result.status).toBe("success");
2017
+ expect(result.ingestedCount).toBe(0);
2018
+ expect(result.duplicateCount).toBe(2);
2019
+ expect(result.eventTimeStart).toBeUndefined();
2020
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
2021
+ expect(OTelIngestService.telemetryServiceFromName).not.toHaveBeenCalled();
2022
+ const written: ConnectionUpdateCall = updateCall();
2023
+ expect(written.data["cursor"]).toBe(result.windowEnd);
2024
+ expect(written.data["lastEventIngestedAt"]).toBeUndefined();
2025
+ });
2026
+
2027
+ test("a dedupe lookup failure fails the run in the import phase and holds the cursor", async () => {
2028
+ getJestSpyOn(
2029
+ SecurityEventDedupe,
2030
+ "findExistingEventUids",
2031
+ ).mockRejectedValue(
2032
+ new Error("Duplicate lookup exceeded its time limit.") as never,
2033
+ );
2034
+ const fake: FakeConnector = makeFakeConnector({
2035
+ fetch: makeFetchResult([makeEvent("evt-1")]),
2036
+ });
2037
+
2038
+ const result: SecurityEventConnectionRunResult =
2039
+ await SecurityEventConnectionPoller.executeConnection(
2040
+ makeConnection({
2041
+ cursor: new Date(Date.now() - 5 * MINUTE_MS).toISOString(),
2042
+ }),
2043
+ { type: "poll" },
2044
+ overridesFor(fake),
2045
+ );
2046
+
2047
+ expect(result.status).toBe("failed");
2048
+ expect(findCheck(result, "failure").name).toBe("Import records");
2049
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
2050
+ expect("cursor" in updateCall().data).toBe(false);
2051
+ });
2052
+ });
2053
+
2054
+ describe("SecurityEventConnectionPoller.executeConnection - test, preview and backfill", () => {
2055
+ test("a test run validates, runs the connector's checks with the poll timeout and never fetches, imports or writes", async () => {
2056
+ const fake: FakeConnector = makeFakeConnector({
2057
+ checks: [
2058
+ check("authentication", "pass"),
2059
+ check("read-permission", "pass"),
2060
+ ],
2061
+ });
2062
+
2063
+ const result: SecurityEventConnectionRunResult =
2064
+ await SecurityEventConnectionPoller.executeConnection(
2065
+ makeConnection(),
2066
+ { type: "test" },
2067
+ overridesFor(fake),
2068
+ );
2069
+
2070
+ expect(result.status).toBe("success");
2071
+ expect(result.complete).toBe(true);
2072
+ expect(fake.testCalls).toEqual([
2073
+ {
2074
+ settings: SETTINGS,
2075
+ /*
2076
+ * A queued test only needs to know whether access works; the slow
2077
+ * availability probes belong to the synchronous Test connection.
2078
+ */
2079
+ options: {
2080
+ requestTimeoutInMs: POLL_REQUEST_TIMEOUT_IN_MS,
2081
+ skipAvailability: true,
2082
+ },
2083
+ },
2084
+ ]);
2085
+ expect(fake.fetchCalls).toHaveLength(0);
2086
+ expect(
2087
+ result.checks.map((item: SecurityConnectorCheck): string => {
2088
+ return item.key;
2089
+ }),
2090
+ ).toEqual(["configuration", "authentication", "read-permission"]);
2091
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
2092
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
2093
+ });
2094
+
2095
+ test("a test run fails when any provider check fails, naming the check", async () => {
2096
+ const fake: FakeConnector = makeFakeConnector({
2097
+ checks: [
2098
+ check("authentication", "pass"),
2099
+ check("read-permission", "fail", "HTTP 403 from the API."),
2100
+ check("detections-available", "warn"),
2101
+ ],
2102
+ });
2103
+
2104
+ const result: SecurityEventConnectionRunResult =
2105
+ await SecurityEventConnectionPoller.executeConnection(
2106
+ makeConnection(),
2107
+ { type: "test" },
2108
+ overridesFor(fake),
2109
+ );
2110
+
2111
+ expect(result.status).toBe("failed");
2112
+ expect(result.error).toBe("Check read-permission: HTTP 403 from the API.");
2113
+ // The provider's checks are kept so the reader sees which one failed.
2114
+ expect(
2115
+ result.checks.map((item: SecurityConnectorCheck): string => {
2116
+ return `${item.key}:${item.status}`;
2117
+ }),
2118
+ ).toEqual([
2119
+ "configuration:pass",
2120
+ "authentication:pass",
2121
+ "read-permission:fail",
2122
+ "detections-available:warn",
2123
+ "failure:fail",
2124
+ ]);
2125
+ expect(findCheck(result, "failure").name).toBe(
2126
+ `Check access to ${DEFINITION.title}`,
2127
+ );
2128
+ });
2129
+
2130
+ test("a preview fetches the requested range but never dedupes, inserts or updates the connection", async () => {
2131
+ const startTime: string = new Date(
2132
+ Date.now() - 120 * MINUTE_MS,
2133
+ ).toISOString();
2134
+ const endTime: string = new Date(Date.now() - 60 * MINUTE_MS).toISOString();
2135
+ const fake: FakeConnector = makeFakeConnector({
2136
+ fetch: makeFetchResult([makeEvent("evt-1"), makeEvent("evt-2")]),
2137
+ });
2138
+
2139
+ const result: SecurityEventConnectionRunResult =
2140
+ await SecurityEventConnectionPoller.executeConnection(
2141
+ makeConnection(),
2142
+ { type: "preview", startTime, endTime },
2143
+ overridesFor(fake),
2144
+ );
2145
+
2146
+ expect(result.status).toBe("success");
2147
+ expect(result.fetchedCount).toBe(2);
2148
+ expect(result.ingestedCount).toBe(0);
2149
+ expect(result.windowStart).toBe(startTime);
2150
+ expect(result.windowEnd).toBe(endTime);
2151
+ expect(fake.fetchCalls[0]!.window.startTime.toISOString()).toBe(startTime);
2152
+ expect(fake.fetchCalls[0]!.window.endTime.toISOString()).toBe(endTime);
2153
+ expect(
2154
+ result.checks.map((item: SecurityConnectorCheck): string => {
2155
+ return item.key;
2156
+ }),
2157
+ ).toEqual(["configuration", "read"]);
2158
+ expect(SecurityEventDedupe.findExistingEventUids).not.toHaveBeenCalled();
2159
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
2160
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
2161
+ // The adaptive chunk belongs to scheduled polls only.
2162
+ expect(result.chunkMinutes).toBeUndefined();
2163
+ expect(result.nextChunkMinutes).toBeUndefined();
2164
+ });
2165
+
2166
+ test("an empty preview is 'empty'", async () => {
2167
+ const fake: FakeConnector = makeFakeConnector({});
2168
+
2169
+ const result: SecurityEventConnectionRunResult =
2170
+ await SecurityEventConnectionPoller.executeConnection(
2171
+ makeConnection(),
2172
+ {
2173
+ type: "preview",
2174
+ startTime: new Date(Date.now() - 120 * MINUTE_MS).toISOString(),
2175
+ endTime: new Date(Date.now() - 60 * MINUTE_MS).toISOString(),
2176
+ },
2177
+ overridesFor(fake),
2178
+ );
2179
+
2180
+ expect(result.status).toBe("empty");
2181
+ });
2182
+
2183
+ test("a backfill imports the range and stamps only lastEventIngestedAt, leaving the cursor alone", async () => {
2184
+ const fake: FakeConnector = makeFakeConnector({
2185
+ fetch: makeFetchResult([makeEvent("evt-1")]),
2186
+ });
2187
+
2188
+ const result: SecurityEventConnectionRunResult =
2189
+ await SecurityEventConnectionPoller.executeConnection(
2190
+ makeConnection({
2191
+ cursor: new Date(Date.now() - 5 * MINUTE_MS).toISOString(),
2192
+ }),
2193
+ {
2194
+ type: "backfill",
2195
+ startTime: new Date(Date.now() - 120 * MINUTE_MS).toISOString(),
2196
+ endTime: new Date(Date.now() - 60 * MINUTE_MS).toISOString(),
2197
+ },
2198
+ overridesFor(fake),
2199
+ );
2200
+
2201
+ expect(result.status).toBe("success");
2202
+ expect(result.ingestedCount).toBe(1);
2203
+ expect(insertedRows).toHaveLength(1);
2204
+ expect(SecurityEventConnectionService.updateOneById).toHaveBeenCalledTimes(
2205
+ 1,
2206
+ );
2207
+ const written: ConnectionUpdateCall = updateCall();
2208
+ // Neither the cursor nor lastPollResult (which carries the chunk) is touched.
2209
+ expect(Object.keys(written.data)).toEqual(["lastEventIngestedAt"]);
2210
+ expect(result.chunkMinutes).toBeUndefined();
2211
+ expect(result.nextChunkMinutes).toBeUndefined();
2212
+ expect(written.data["lastEventIngestedAt"]).toEqual(
2213
+ new Date(result.completedAt),
2214
+ );
2215
+ });
2216
+
2217
+ test("a backfill that imports nothing does not touch the connection row", async () => {
2218
+ existingUids = new Set(["evt-1"]);
2219
+ const fake: FakeConnector = makeFakeConnector({
2220
+ fetch: makeFetchResult([makeEvent("evt-1")]),
2221
+ });
2222
+
2223
+ const result: SecurityEventConnectionRunResult =
2224
+ await SecurityEventConnectionPoller.executeConnection(
2225
+ makeConnection(),
2226
+ {
2227
+ type: "backfill",
2228
+ startTime: new Date(Date.now() - 120 * MINUTE_MS).toISOString(),
2229
+ endTime: new Date(Date.now() - 60 * MINUTE_MS).toISOString(),
2230
+ },
2231
+ overridesFor(fake),
2232
+ );
2233
+
2234
+ expect(result.duplicateCount).toBe(1);
2235
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
2236
+ });
2237
+ });
2238
+
2239
+ describe("SecurityEventConnectionPoller.pollConnection", () => {
2240
+ test("returns the ingested count of a poll", async () => {
2241
+ getJestSpyOn(
2242
+ SecurityEventConnectionPoller,
2243
+ "executeConnection",
2244
+ ).mockResolvedValue({
2245
+ status: "success",
2246
+ ingestedCount: 7,
2247
+ } as never);
2248
+
2249
+ await expect(
2250
+ SecurityEventConnectionPoller.pollConnection(makeConnection()),
2251
+ ).resolves.toBe(7);
2252
+ expect(
2253
+ SecurityEventConnectionPoller.executeConnection,
2254
+ ).toHaveBeenCalledWith(
2255
+ expect.any(SecurityEventConnection),
2256
+ { type: "poll" },
2257
+ undefined,
2258
+ );
2259
+ });
2260
+
2261
+ test("a failed poll (already booked on the row) surfaces as a RecordedConnectionPollFailure", async () => {
2262
+ getJestSpyOn(
2263
+ SecurityEventConnectionPoller,
2264
+ "executeConnection",
2265
+ ).mockResolvedValue({
2266
+ status: "failed",
2267
+ ingestedCount: 0,
2268
+ error: "Okta token rejected (HTTP 401).",
2269
+ } as never);
2270
+
2271
+ const promise: Promise<number> =
2272
+ SecurityEventConnectionPoller.pollConnection(makeConnection());
2273
+
2274
+ await expect(promise).rejects.toBeInstanceOf(RecordedConnectionPollFailure);
2275
+ await expect(promise).rejects.toThrow("Okta token rejected (HTTP 401).");
2276
+ });
2277
+ });
2278
+
2279
+ describe("SecurityEventConnectionPoller.pollAllDueConnections", () => {
2280
+ test("polls due and never-polled connections and skips those inside their interval", async () => {
2281
+ const now: number = Date.now();
2282
+ const notDue: SecurityEventConnection = makeConnection({
2283
+ lastPolledAt: new Date(now - 2 * MINUTE_MS),
2284
+ });
2285
+ const overdue: SecurityEventConnection = makeConnection({
2286
+ _id: SECOND_CONNECTION_ID.toString(),
2287
+ lastPolledAt: new Date(now - 10 * MINUTE_MS),
2288
+ });
2289
+ const neverPolled: SecurityEventConnection = makeConnection({
2290
+ _id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa3",
2291
+ });
2292
+ getJestSpyOn(SecurityEventConnectionService, "findBy").mockResolvedValue([
2293
+ notDue,
2294
+ overdue,
2295
+ neverPolled,
2296
+ ] as never);
2297
+ const pollSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2298
+ SecurityEventConnectionPoller,
2299
+ "pollConnection",
2300
+ ).mockResolvedValue(0 as never);
2301
+
2302
+ await SecurityEventConnectionPoller.pollAllDueConnections();
2303
+
2304
+ expect(SecurityEventConnectionService.findBy).toHaveBeenCalledWith(
2305
+ expect.objectContaining({
2306
+ query: { isEnabled: true },
2307
+ props: { isRoot: true },
2308
+ }),
2309
+ );
2310
+ expect(
2311
+ pollSpy.mock.calls.map((call: Array<unknown>): string | undefined => {
2312
+ return (call[0] as SecurityEventConnection)._id;
2313
+ }),
2314
+ ).toEqual([
2315
+ SECOND_CONNECTION_ID.toString(),
2316
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa3",
2317
+ ]);
2318
+ // Successful polls own their bookkeeping; the loop writes nothing.
2319
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
2320
+ });
2321
+
2322
+ test("a zero or missing interval is treated as at least one minute", async () => {
2323
+ const connection: SecurityEventConnection = makeConnection({
2324
+ pollIntervalInMinutes: 0,
2325
+ lastPolledAt: new Date(Date.now() - 30 * 1000),
2326
+ });
2327
+ getJestSpyOn(SecurityEventConnectionService, "findBy").mockResolvedValue([
2328
+ connection,
2329
+ ] as never);
2330
+ const pollSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2331
+ SecurityEventConnectionPoller,
2332
+ "pollConnection",
2333
+ ).mockResolvedValue(0 as never);
2334
+
2335
+ await SecurityEventConnectionPoller.pollAllDueConnections();
2336
+
2337
+ // Polled 30 seconds ago with a "0" interval read as 5 minutes: not due.
2338
+ expect(pollSpy).not.toHaveBeenCalled();
2339
+ });
2340
+
2341
+ test("an unexpected failure is stamped on the row through recordFailure, redacted, and the loop continues", async () => {
2342
+ const first: SecurityEventConnection = makeConnection();
2343
+ const second: SecurityEventConnection = makeConnection({
2344
+ _id: SECOND_CONNECTION_ID.toString(),
2345
+ });
2346
+ getJestSpyOn(SecurityEventConnectionService, "findBy").mockResolvedValue([
2347
+ first,
2348
+ second,
2349
+ ] as never);
2350
+ const recordSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2351
+ ConnectorErrorMessage,
2352
+ "recordFailure",
2353
+ );
2354
+ const pollSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2355
+ SecurityEventConnectionPoller,
2356
+ "pollConnection",
2357
+ ).mockRejectedValue(
2358
+ new Error(
2359
+ `Okta token exchange failed (HTTP 401): client_secret=${SECRET_VALUE}`,
2360
+ ) as never,
2361
+ );
2362
+
2363
+ await expect(
2364
+ SecurityEventConnectionPoller.pollAllDueConnections(),
2365
+ ).resolves.toBeUndefined();
2366
+
2367
+ expect(pollSpy).toHaveBeenCalledTimes(2);
2368
+ expect(recordSpy).toHaveBeenCalledTimes(2);
2369
+ expect(recordSpy.mock.calls[0]![0]).toEqual(
2370
+ expect.objectContaining({
2371
+ label: expect.stringContaining(CONNECTION_ID.toString()),
2372
+ }),
2373
+ );
2374
+ expect(SecurityEventConnectionService.updateOneById).toHaveBeenCalledTimes(
2375
+ 2,
2376
+ );
2377
+ const written: ConnectionUpdateCall = updateCall();
2378
+ expect(written.id.toString()).toBe(CONNECTION_ID.toString());
2379
+ expect(written.data["lastPolledAt"]).toBeInstanceOf(Date);
2380
+ expect(written.data["lastError"]).toContain("Okta token exchange failed");
2381
+ expect(written.data["lastError"]).not.toContain(SECRET_VALUE);
2382
+ expect(updateCall(1).id.toString()).toBe(SECOND_CONNECTION_ID.toString());
2383
+ });
2384
+
2385
+ test("a failure the poll already recorded on the row is not stamped a second time", async () => {
2386
+ getJestSpyOn(SecurityEventConnectionService, "findBy").mockResolvedValue([
2387
+ makeConnection(),
2388
+ makeConnection({ _id: SECOND_CONNECTION_ID.toString() }),
2389
+ ] as never);
2390
+ const pollSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2391
+ SecurityEventConnectionPoller,
2392
+ "pollConnection",
2393
+ ).mockRejectedValue(
2394
+ new RecordedConnectionPollFailure("already on the row") as never,
2395
+ );
2396
+
2397
+ await SecurityEventConnectionPoller.pollAllDueConnections();
2398
+
2399
+ expect(pollSpy).toHaveBeenCalledTimes(2);
2400
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
2401
+ });
2402
+
2403
+ test("a rejected bookkeeping write does not abandon the connections behind it", async () => {
2404
+ getJestSpyOn(SecurityEventConnectionService, "findBy").mockResolvedValue([
2405
+ makeConnection(),
2406
+ makeConnection({ _id: SECOND_CONNECTION_ID.toString() }),
2407
+ ] as never);
2408
+ const updateSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2409
+ SecurityEventConnectionService,
2410
+ "updateOneById",
2411
+ ).mockRejectedValue(new Error("deadlock detected") as never);
2412
+ const pollSpy: ReturnType<typeof getJestSpyOn> = getJestSpyOn(
2413
+ SecurityEventConnectionPoller,
2414
+ "pollConnection",
2415
+ ).mockRejectedValue(new Error("token exchange failed") as never);
2416
+
2417
+ await expect(
2418
+ SecurityEventConnectionPoller.pollAllDueConnections(),
2419
+ ).resolves.toBeUndefined();
2420
+
2421
+ expect(pollSpy).toHaveBeenCalledTimes(2);
2422
+ expect(updateSpy).toHaveBeenCalledTimes(2);
2423
+ });
2424
+
2425
+ test("a connection without an id is attempted but nothing is stamped", async () => {
2426
+ const withoutId: SecurityEventConnection = makeConnection();
2427
+ delete withoutId._id;
2428
+ getJestSpyOn(SecurityEventConnectionService, "findBy").mockResolvedValue([
2429
+ withoutId,
2430
+ ] as never);
2431
+ getJestSpyOn(
2432
+ SecurityEventConnectionPoller,
2433
+ "pollConnection",
2434
+ ).mockRejectedValue(new Error("boom") as never);
2435
+
2436
+ await SecurityEventConnectionPoller.pollAllDueConnections();
2437
+
2438
+ expect(SecurityEventConnectionService.updateOneById).not.toHaveBeenCalled();
2439
+ });
2440
+ });
2441
+
2442
+ /*
2443
+ * Review finding F1, end to end: many scheduled polls in a row against a
2444
+ * fake source that holds more records than one run can read. The fake
2445
+ * honours maxRequests and maxEvents with a fixed page size, filters on
2446
+ * creation time with an inclusive start and exclusive end, and either
2447
+ * reads in ascending creation order and reports resumeAfter (Okta, AWS,
2448
+ * Sentinel, Falcon, Elastic, Splunk) or returns newest first and cannot
2449
+ * resume. Each poll feeds the next one the cursor and lastPollResult it
2450
+ * wrote, the clock moves five minutes per poll, and storage dedupes by id,
2451
+ * exactly as the real connection row and ClickHouse would.
2452
+ */
2453
+ const SIMULATED_PAGE_SIZE: number = 25;
2454
+ const SIMULATED_RUN_CAPACITY: number = SIMULATED_PAGE_SIZE * MAX_FETCH_REQUESTS;
2455
+ const SIMULATED_POLL_INTERVAL_IN_MINUTES: number = 5;
2456
+
2457
+ interface SourceRecord {
2458
+ uid: string;
2459
+ createdAt: Date;
2460
+ }
2461
+
2462
+ interface SimulatedSource {
2463
+ records: Array<SourceRecord>;
2464
+ order: "ascending" | "newestFirst";
2465
+ failNext?: Error | undefined;
2466
+ fetchWindows: Array<ConnectorFetchWindow>;
2467
+ }
2468
+
2469
+ interface SimulationState {
2470
+ now: Date;
2471
+ cursor: string | undefined;
2472
+ lastPollResult: JSONObject | undefined;
2473
+ lastError: string | null | undefined;
2474
+ imported: Set<string>;
2475
+ insertedCount: number;
2476
+ }
2477
+
2478
+ interface SimulatedPoll {
2479
+ index: number;
2480
+ cursorBefore: string | undefined;
2481
+ result: SecurityEventConnectionRunResult;
2482
+ written: JSONObject;
2483
+ }
2484
+
2485
+ function makeSimulatedConnector(
2486
+ source: SimulatedSource,
2487
+ provider: SecurityEventConnectorProvider,
2488
+ ): SecurityEventConnector {
2489
+ return {
2490
+ provider,
2491
+ validateSettings: (): void => {
2492
+ return;
2493
+ },
2494
+ testConnection: (): Promise<Array<SecurityConnectorCheck>> => {
2495
+ return Promise.resolve([]);
2496
+ },
2497
+ fetchEvents: (
2498
+ _settings: SecurityConnectorSettings,
2499
+ window: ConnectorFetchWindow,
2500
+ options: ConnectorFetchOptions,
2501
+ ): Promise<ConnectorFetchResult> => {
2502
+ source.fetchWindows.push(window);
2503
+
2504
+ if (source.failNext) {
2505
+ const failure: Error = source.failNext;
2506
+ source.failNext = undefined;
2507
+ return Promise.reject(failure);
2508
+ }
2509
+
2510
+ const matching: Array<SourceRecord> = source.records
2511
+ .filter((record: SourceRecord): boolean => {
2512
+ return (
2513
+ record.createdAt >= window.startTime &&
2514
+ record.createdAt < window.endTime
2515
+ );
2516
+ })
2517
+ .sort((left: SourceRecord, right: SourceRecord): number => {
2518
+ return (
2519
+ left.createdAt.getTime() - right.createdAt.getTime() ||
2520
+ left.uid.localeCompare(right.uid)
2521
+ );
2522
+ });
2523
+
2524
+ if (source.order === "newestFirst") {
2525
+ matching.reverse();
2526
+ }
2527
+
2528
+ const read: Array<SourceRecord> = [];
2529
+ let requestCount: number = 0;
2530
+
2531
+ while (
2532
+ requestCount < options.maxRequests &&
2533
+ read.length < matching.length &&
2534
+ read.length < options.maxEvents
2535
+ ) {
2536
+ requestCount++;
2537
+ read.push(
2538
+ ...matching.slice(
2539
+ read.length,
2540
+ Math.min(read.length + SIMULATED_PAGE_SIZE, options.maxEvents),
2541
+ ),
2542
+ );
2543
+ }
2544
+
2545
+ const complete: boolean = read.length === matching.length;
2546
+ const last: SourceRecord | undefined = read[read.length - 1];
2547
+
2548
+ return Promise.resolve(
2549
+ makeFetchResult(
2550
+ read.map((record: SourceRecord): NormalizedSecurityEvent => {
2551
+ return makeEvent(record.uid, record.createdAt);
2552
+ }),
2553
+ {
2554
+ complete,
2555
+ requestCount: Math.max(1, requestCount),
2556
+ warnings: complete
2557
+ ? []
2558
+ : [
2559
+ `Stopped after ${requestCount} requests; the window was not fully read.`,
2560
+ ],
2561
+ samples: [],
2562
+ resumeAfter:
2563
+ !complete && source.order === "ascending" && last
2564
+ ? last.createdAt
2565
+ : undefined,
2566
+ },
2567
+ ),
2568
+ );
2569
+ },
2570
+ };
2571
+ }
2572
+
2573
+ function startSimulation(
2574
+ now: Date,
2575
+ cursor: string | undefined = undefined,
2576
+ ): SimulationState {
2577
+ const state: SimulationState = {
2578
+ now,
2579
+ cursor,
2580
+ lastPollResult: undefined,
2581
+ lastError: undefined,
2582
+ imported: new Set(),
2583
+ insertedCount: 0,
2584
+ };
2585
+
2586
+ getJestSpyOn(OneUptimeDate, "getCurrentDate").mockImplementation((): Date => {
2587
+ return new Date(state.now.getTime());
2588
+ });
2589
+ getJestSpyOn(SecurityEventDedupe, "findExistingEventUids").mockImplementation(
2590
+ ((query: { ids: Array<string> }): Promise<Set<string>> => {
2591
+ return Promise.resolve(
2592
+ new Set(
2593
+ query.ids.filter((id: string): boolean => {
2594
+ return state.imported.has(id);
2595
+ }),
2596
+ ),
2597
+ );
2598
+ }) as never,
2599
+ );
2600
+ getJestSpyOn(SecurityEventService, "insertJsonRows").mockImplementation(((
2601
+ rows: Array<JSONObject>,
2602
+ ): Promise<void> => {
2603
+ for (const row of rows) {
2604
+ state.imported.add(row["eventUid"] as string);
2605
+ }
2606
+ state.insertedCount += rows.length;
2607
+ return Promise.resolve();
2608
+ }) as never);
2609
+
2610
+ return state;
2611
+ }
2612
+
2613
+ async function runScheduledPolls(data: {
2614
+ state: SimulationState;
2615
+ source: SimulatedSource;
2616
+ provider: SecurityEventConnectorProvider;
2617
+ maxPolls: number;
2618
+ beforePoll?: ((index: number) => void) | undefined;
2619
+ until: (poll: SimulatedPoll) => boolean;
2620
+ }): Promise<Array<SimulatedPoll>> {
2621
+ const connector: SecurityEventConnector = makeSimulatedConnector(
2622
+ data.source,
2623
+ data.provider,
2624
+ );
2625
+ const updateSpy: jest.Mock =
2626
+ SecurityEventConnectionService.updateOneById as unknown as jest.Mock;
2627
+ const polls: Array<SimulatedPoll> = [];
2628
+
2629
+ for (let index: number = 0; index < data.maxPolls; index++) {
2630
+ data.beforePoll?.(index);
2631
+
2632
+ const cursorBefore: string | undefined = data.state.cursor;
2633
+ const callsBefore: number = updateSpy.mock.calls.length;
2634
+ const result: SecurityEventConnectionRunResult =
2635
+ await SecurityEventConnectionPoller.executeConnection(
2636
+ makeConnection({
2637
+ provider: data.provider,
2638
+ // exactOptionalPropertyTypes: only set what the row actually holds.
2639
+ ...(data.state.cursor !== undefined
2640
+ ? { cursor: data.state.cursor }
2641
+ : {}),
2642
+ ...(data.state.lastPollResult !== undefined
2643
+ ? { lastPollResult: data.state.lastPollResult }
2644
+ : {}),
2645
+ }),
2646
+ { type: "poll" },
2647
+ {
2648
+ connector,
2649
+ settings: { ...SETTINGS, provider: data.provider },
2650
+ },
2651
+ );
2652
+
2653
+ expect(updateSpy.mock.calls.length).toBe(callsBefore + 1);
2654
+ const written: JSONObject = (
2655
+ updateSpy.mock.calls[callsBefore]![0] as ConnectionUpdateCall
2656
+ ).data;
2657
+
2658
+ const writtenCursor: unknown = written["cursor"];
2659
+
2660
+ if (typeof writtenCursor === "string") {
2661
+ // The F1 invariant: a cursor written after a cursor is strictly later.
2662
+ if (cursorBefore !== undefined) {
2663
+ expect({
2664
+ poll: index,
2665
+ advanced: Date.parse(writtenCursor) > Date.parse(cursorBefore),
2666
+ }).toEqual({ poll: index, advanced: true });
2667
+ }
2668
+ data.state.cursor = writtenCursor;
2669
+ }
2670
+
2671
+ // A jsonb round trip, as the next reload would return it.
2672
+ data.state.lastPollResult = JSON.parse(
2673
+ JSON.stringify(written["lastPollResult"]),
2674
+ ) as JSONObject;
2675
+ data.state.lastError = written["lastError"] as string | null;
2676
+
2677
+ const poll: SimulatedPoll = { index, cursorBefore, result, written };
2678
+ polls.push(poll);
2679
+ data.state.now = new Date(
2680
+ data.state.now.getTime() + SIMULATED_POLL_INTERVAL_IN_MINUTES * MINUTE_MS,
2681
+ );
2682
+
2683
+ if (data.until(poll)) {
2684
+ break;
2685
+ }
2686
+ }
2687
+
2688
+ return polls;
2689
+ }
2690
+
2691
+ // The most scheduled polls in a row that left the cursor where it was.
2692
+ function longestCursorHold(polls: Array<SimulatedPoll>): number {
2693
+ let longest: number = 0;
2694
+ let current: number = 0;
2695
+
2696
+ for (const poll of polls) {
2697
+ current = typeof poll.written["cursor"] === "string" ? 0 : current + 1;
2698
+ longest = Math.max(longest, current);
2699
+ }
2700
+
2701
+ return longest;
2702
+ }
2703
+
2704
+ function allImported(
2705
+ state: SimulationState,
2706
+ records: Array<SourceRecord>,
2707
+ ): boolean {
2708
+ return records.every((record: SourceRecord): boolean => {
2709
+ return state.imported.has(record.uid);
2710
+ });
2711
+ }
2712
+
2713
+ function minutesAfter(time: Date, minutes: number): Date {
2714
+ return new Date(time.getTime() + minutes * MINUTE_MS);
2715
+ }
2716
+
2717
+ // log2(MAX / MIN) halvings, then the forced advance.
2718
+ const MAX_POLLS_WITHOUT_CURSOR_MOVE: number =
2719
+ Math.ceil(Math.log2(MAX_CHUNK_MINUTES / MIN_CHUNK_MINUTES)) + 1;
2720
+
2721
+ describe("SecurityEventConnectionPoller - scheduled polls against a source larger than one run (review finding F1)", () => {
2722
+ const NOW: Date = new Date("2026-09-14T12:00:00.000Z");
2723
+
2724
+ test("an ascending source that reports resumeAfter imports every record, including ones created while it catches up", async () => {
2725
+ const state: SimulationState = startSimulation(NOW);
2726
+ const source: SimulatedSource = {
2727
+ records: [],
2728
+ order: "ascending",
2729
+ fetchWindows: [],
2730
+ };
2731
+ const backlogStart: Date = minutesAfter(NOW, -20 * 60);
2732
+
2733
+ // Eight hours of backlog, five records a minute: almost five runs' worth.
2734
+ for (let index: number = 0; index < 2400; index++) {
2735
+ source.records.push({
2736
+ uid: `backlog-${index}`,
2737
+ createdAt: new Date(backlogStart.getTime() + index * 12 * 1000),
2738
+ });
2739
+ }
2740
+ expect(source.records.length).toBeGreaterThan(4 * SIMULATED_RUN_CAPACITY);
2741
+
2742
+ const polls: Array<SimulatedPoll> = await runScheduledPolls({
2743
+ state,
2744
+ source,
2745
+ provider: PROVIDER,
2746
+ maxPolls: 20,
2747
+ beforePoll: (index: number): void => {
2748
+ if (index === 0) {
2749
+ return;
2750
+ }
2751
+
2752
+ // Ten records created since the previous poll.
2753
+ for (let arrival: number = 0; arrival < 10; arrival++) {
2754
+ source.records.push({
2755
+ uid: `arrival-${index}-${arrival}`,
2756
+ createdAt: new Date(
2757
+ state.now.getTime() -
2758
+ SIMULATED_POLL_INTERVAL_IN_MINUTES * MINUTE_MS +
2759
+ arrival * 30 * 1000,
2760
+ ),
2761
+ });
2762
+ }
2763
+ },
2764
+ until: (poll: SimulatedPoll): boolean => {
2765
+ return poll.result.complete && allImported(state, source.records);
2766
+ },
2767
+ });
2768
+
2769
+ expect(polls.length).toBeLessThanOrEqual(8);
2770
+ expect(polls[polls.length - 1]!.result.status).toBe("success");
2771
+ expect(state.imported.size).toBe(source.records.length);
2772
+ // The overlap re-reads are deduplicated: every record is written once.
2773
+ expect(state.insertedCount).toBe(source.records.length);
2774
+
2775
+ // Every poll before the last resumed, so the cursor moved every time.
2776
+ for (const poll of polls.slice(0, -1)) {
2777
+ expect({
2778
+ poll: poll.index,
2779
+ status: poll.result.status,
2780
+ cursor: poll.written["cursor"],
2781
+ nextChunkMinutes: poll.result.nextChunkMinutes,
2782
+ }).toEqual({
2783
+ poll: poll.index,
2784
+ status: "partial",
2785
+ cursor: expect.any(String),
2786
+ // A resumed poll keeps the chunk it was given (a full day here).
2787
+ nextChunkMinutes: 1440,
2788
+ });
2789
+ expect(
2790
+ poll.result.warnings.some((warning: string): boolean => {
2791
+ return warning.includes("the next poll resumes from there");
2792
+ }),
2793
+ ).toBe(true);
2794
+ }
2795
+
2796
+ expect(longestCursorHold(polls)).toBe(0);
2797
+ expect(
2798
+ polls.some((poll: SimulatedPoll): boolean => {
2799
+ return poll.result.forcedAdvance === true;
2800
+ }),
2801
+ ).toBe(false);
2802
+ expect(state.lastError).toBeNull();
2803
+ });
2804
+
2805
+ test("a source that cannot resume narrows by halves around a burst, moves past it, then doubles back to a day", async () => {
2806
+ const provider: SecurityEventConnectorProvider =
2807
+ SecurityEventConnectorProvider.MicrosoftDefenderXdr;
2808
+ // The chunk arithmetic below assumes a one minute overlap.
2809
+ expect(
2810
+ getSecurityEventConnectorDefinition(provider)!.cursorOverlapInMinutes,
2811
+ ).toBe(1);
2812
+ const cursor: Date = new Date("2026-09-11T12:00:00.000Z");
2813
+ const state: SimulationState = startSimulation(NOW, cursor.toISOString());
2814
+ const source: SimulatedSource = {
2815
+ records: [
2816
+ { uid: "early", createdAt: minutesAfter(cursor, 10) },
2817
+ { uid: "late", createdAt: minutesAfter(cursor, 300) },
2818
+ ],
2819
+ order: "newestFirst",
2820
+ fetchWindows: [],
2821
+ };
2822
+
2823
+ // 600 records in ten minutes: more than one run, less than one run per five minutes.
2824
+ for (let index: number = 0; index < 600; index++) {
2825
+ source.records.push({
2826
+ uid: `burst-${index}`,
2827
+ createdAt: new Date(minutesAfter(cursor, 90).getTime() + index * 1000),
2828
+ });
2829
+ }
2830
+ expect(source.records.length).toBeGreaterThan(SIMULATED_RUN_CAPACITY);
2831
+
2832
+ const polls: Array<SimulatedPoll> = await runScheduledPolls({
2833
+ state,
2834
+ source,
2835
+ provider,
2836
+ maxPolls: 20,
2837
+ until: (): boolean => {
2838
+ return false;
2839
+ },
2840
+ });
2841
+
2842
+ expect(
2843
+ polls.map((poll: SimulatedPoll): number | undefined => {
2844
+ return poll.result.chunkMinutes;
2845
+ }),
2846
+ ).toEqual([
2847
+ 1440, 720, 360, 180, 90, 180, 90, 45, 22, 11, 5, 10, 20, 40, 80, 160, 320,
2848
+ 640, 1280, 1440,
2849
+ ]);
2850
+ expect(
2851
+ polls.map((poll: SimulatedPoll): number | undefined => {
2852
+ return poll.result.nextChunkMinutes;
2853
+ }),
2854
+ ).toEqual([
2855
+ 720, 360, 180, 90, 180, 90, 45, 22, 11, 5, 10, 20, 40, 80, 160, 320, 640,
2856
+ 1280, 1440, 1440,
2857
+ ]);
2858
+ expect(
2859
+ polls.map((poll: SimulatedPoll): string => {
2860
+ return poll.result.status;
2861
+ }),
2862
+ ).toEqual([
2863
+ "partial",
2864
+ "partial",
2865
+ "partial",
2866
+ "partial",
2867
+ "success",
2868
+ "partial",
2869
+ "partial",
2870
+ "partial",
2871
+ "partial",
2872
+ "partial",
2873
+ "success",
2874
+ "success",
2875
+ "empty",
2876
+ "empty",
2877
+ "empty",
2878
+ "success",
2879
+ "empty",
2880
+ "empty",
2881
+ "empty",
2882
+ "empty",
2883
+ ]);
2884
+ expect(polls[0]!.result.warnings).toContain(
2885
+ "This window holds more records than one poll can read; the next poll reads a 720 minute window from the same starting point.",
2886
+ );
2887
+ // The first narrowed window reads from the same start, half as far.
2888
+ expect(source.fetchWindows[1]!.startTime.toISOString()).toBe(
2889
+ source.fetchWindows[0]!.startTime.toISOString(),
2890
+ );
2891
+ expect(source.fetchWindows[1]!.endTime.toISOString()).toBe(
2892
+ minutesAfter(cursor, 720).toISOString(),
2893
+ );
2894
+
2895
+ expect(longestCursorHold(polls)).toBeLessThanOrEqual(
2896
+ MAX_POLLS_WITHOUT_CURSOR_MOVE,
2897
+ );
2898
+ expect(
2899
+ polls.some((poll: SimulatedPoll): boolean => {
2900
+ return poll.result.forcedAdvance === true;
2901
+ }),
2902
+ ).toBe(false);
2903
+ expect(allImported(state, source.records)).toBe(true);
2904
+ expect(state.insertedCount).toBe(source.records.length);
2905
+ expect(state.lastError).toBeNull();
2906
+ });
2907
+
2908
+ test.each<["ascending" | "newestFirst", number]>([
2909
+ // Identical timestamps: an ascending reader cannot resume inside the burst.
2910
+ ["ascending", 0],
2911
+ ["newestFirst", 50],
2912
+ ])(
2913
+ "a one-minute burst larger than one run (%s source, 30 minute overlap) forces exactly one advance, then newer records import",
2914
+ async (order: "ascending" | "newestFirst", spacingInMs: number) => {
2915
+ const provider: SecurityEventConnectorProvider =
2916
+ SecurityEventConnectorProvider.AwsSecurityHub;
2917
+ expect(
2918
+ getSecurityEventConnectorDefinition(provider)!.cursorOverlapInMinutes,
2919
+ ).toBe(30);
2920
+ const cursor: Date = new Date("2026-09-11T12:00:00.000Z");
2921
+ const burstMinute: Date = minutesAfter(cursor, 60);
2922
+ const state: SimulationState = startSimulation(NOW, cursor.toISOString());
2923
+ const source: SimulatedSource = {
2924
+ records: [],
2925
+ order,
2926
+ fetchWindows: [],
2927
+ };
2928
+
2929
+ for (let index: number = 0; index < 700; index++) {
2930
+ source.records.push({
2931
+ uid: `burst-${index}`,
2932
+ createdAt: new Date(burstMinute.getTime() + index * spacingInMs),
2933
+ });
2934
+ }
2935
+
2936
+ /*
2937
+ * Records created right after the burst minute sit inside the next
2938
+ * polls' 30 minute overlap: without the overlap floor every one of
2939
+ * those polls would overflow on the burst again and skip them.
2940
+ */
2941
+ const after: Array<SourceRecord> = [];
2942
+ for (let minute: number = 61; minute < 91; minute++) {
2943
+ after.push({
2944
+ uid: `after-${minute}`,
2945
+ createdAt: minutesAfter(cursor, minute),
2946
+ });
2947
+ }
2948
+ for (let minute: number = 120; minute < 170; minute++) {
2949
+ after.push({
2950
+ uid: `later-${minute}`,
2951
+ createdAt: minutesAfter(cursor, minute),
2952
+ });
2953
+ }
2954
+ source.records.push(...after);
2955
+
2956
+ const polls: Array<SimulatedPoll> = await runScheduledPolls({
2957
+ state,
2958
+ source,
2959
+ provider,
2960
+ maxPolls: 40,
2961
+ until: (poll: SimulatedPoll): boolean => {
2962
+ // A newest-first reader imports the later records early, so also wait for the cursor to pass them.
2963
+ return (
2964
+ poll.result.complete &&
2965
+ allImported(state, after) &&
2966
+ Date.parse(state.cursor || "") >=
2967
+ minutesAfter(cursor, 170).getTime()
2968
+ );
2969
+ },
2970
+ });
2971
+
2972
+ const forced: Array<SimulatedPoll> = polls.filter(
2973
+ (poll: SimulatedPoll): boolean => {
2974
+ return poll.result.forcedAdvance === true;
2975
+ },
2976
+ );
2977
+ expect(forced).toHaveLength(1);
2978
+
2979
+ const forcedPoll: SimulatedPoll = forced[0]!;
2980
+ const floor: Date = minutesAfter(burstMinute, 1);
2981
+ const forcedMessage: string = `More records were created in the one minute from ${burstMinute.toISOString()} to ${floor.toISOString()} than one poll can read. Polling moved past this minute so newer records keep arriving; use Import this time range in Diagnostics on this minute to recover what one run can read.`;
2982
+ expect(forcedPoll.cursorBefore).toBe(burstMinute.toISOString());
2983
+ expect(forcedPoll.result.chunkMinutes).toBe(MIN_CHUNK_MINUTES);
2984
+ expect(forcedPoll.written["cursor"]).toBe(floor.toISOString());
2985
+ expect(forcedPoll.result.overlapFloor).toBe(floor.toISOString());
2986
+ expect(
2987
+ String(forcedPoll.written["lastError"]).startsWith(forcedMessage),
2988
+ ).toBe(true);
2989
+
2990
+ // No window after the forced advance reaches back over the burst minute.
2991
+ for (const window of source.fetchWindows.slice(forcedPoll.index + 1)) {
2992
+ expect(window.startTime.getTime()).toBeGreaterThanOrEqual(
2993
+ floor.getTime(),
2994
+ );
2995
+ }
2996
+
2997
+ // The next poll reads its minute and the chunk starts doubling again.
2998
+ const nextPoll: SimulatedPoll = polls[forcedPoll.index + 1]!;
2999
+ expect(nextPoll.result.complete).toBe(true);
3000
+ expect(nextPoll.result.nextChunkMinutes).toBe(2 * MIN_CHUNK_MINUTES);
3001
+
3002
+ // The floor is dropped once cursor - overlap no longer reaches it.
3003
+ const lastPoll: SimulatedPoll = polls[polls.length - 1]!;
3004
+ expect(lastPoll.result.overlapFloor).toBeUndefined();
3005
+
3006
+ expect(longestCursorHold(polls)).toBeLessThanOrEqual(
3007
+ MAX_POLLS_WITHOUT_CURSOR_MOVE,
3008
+ );
3009
+ expect(allImported(state, after)).toBe(true);
3010
+ expect(lastPoll.result.complete).toBe(true);
3011
+ expect(state.lastError).toBeNull();
3012
+ },
3013
+ );
3014
+
3015
+ test("a failed poll in the middle of narrowing leaves the chunk and the cursor exactly where they were", async () => {
3016
+ const provider: SecurityEventConnectorProvider =
3017
+ SecurityEventConnectorProvider.MicrosoftDefenderXdr;
3018
+ const cursor: Date = new Date("2026-09-11T12:00:00.000Z");
3019
+ const state: SimulationState = startSimulation(NOW, cursor.toISOString());
3020
+ const source: SimulatedSource = {
3021
+ records: [],
3022
+ order: "newestFirst",
3023
+ fetchWindows: [],
3024
+ };
3025
+
3026
+ for (let index: number = 0; index < 600; index++) {
3027
+ source.records.push({
3028
+ uid: `burst-${index}`,
3029
+ createdAt: new Date(minutesAfter(cursor, 90).getTime() + index * 1000),
3030
+ });
3031
+ }
3032
+
3033
+ const polls: Array<SimulatedPoll> = await runScheduledPolls({
3034
+ state,
3035
+ source,
3036
+ provider,
3037
+ maxPolls: 8,
3038
+ beforePoll: (index: number): void => {
3039
+ if (index === 2) {
3040
+ source.failNext = new Error(
3041
+ "Microsoft Graph request failed (HTTP 503): service unavailable",
3042
+ );
3043
+ }
3044
+ },
3045
+ until: (): boolean => {
3046
+ return false;
3047
+ },
3048
+ });
3049
+
3050
+ const failed: SimulatedPoll = polls[2]!;
3051
+ expect(failed.result.status).toBe("failed");
3052
+ expect(failed.result.chunkMinutes).toBe(360);
3053
+ expect(failed.result.nextChunkMinutes).toBe(360);
3054
+ expect("cursor" in failed.written).toBe(false);
3055
+ expect(failed.written["lastError"]).toContain("HTTP 503");
3056
+
3057
+ // The same sequence as without the failure, with the failed poll repeated.
3058
+ expect(
3059
+ polls.map((poll: SimulatedPoll): number | undefined => {
3060
+ return poll.result.chunkMinutes;
3061
+ }),
3062
+ ).toEqual([1440, 720, 360, 360, 180, 90, 180, 90]);
3063
+ expect(source.fetchWindows[3]!.startTime.toISOString()).toBe(
3064
+ source.fetchWindows[2]!.startTime.toISOString(),
3065
+ );
3066
+ expect(source.fetchWindows[3]!.endTime.toISOString()).toBe(
3067
+ source.fetchWindows[2]!.endTime.toISOString(),
3068
+ );
3069
+ });
3070
+ });
3071
+
3072
+ /*
3073
+ * What a connector that reads one window in several passes needs from this
3074
+ * loop to run with the parity the retired Google SecOps poller had: its own
3075
+ * fetch budget, the operation and the poll interval in the fetch options,
3076
+ * one check per pass, a read summary that is never green for a read that
3077
+ * did not finish, its diagnostics kept on the run, and a failed run named
3078
+ * after the pass that failed.
3079
+ */
3080
+
3081
+ function historicalRun(
3082
+ type: "preview" | "backfill",
3083
+ ): SecurityEventConnectionRunOptions {
3084
+ return {
3085
+ type,
3086
+ startTime: new Date(Date.now() - 120 * MINUTE_MS).toISOString(),
3087
+ endTime: new Date(Date.now() - 60 * MINUTE_MS).toISOString(),
3088
+ };
3089
+ }
3090
+
3091
+ function runOptionsFor(
3092
+ type: SecurityEventConnectionRunOptions["type"],
3093
+ ): SecurityEventConnectionRunOptions {
3094
+ return type === "preview" || type === "backfill"
3095
+ ? historicalRun(type)
3096
+ : { type };
3097
+ }
3098
+
3099
+ function checkKeysAndStatuses(
3100
+ result: SecurityEventConnectionRunResult,
3101
+ ): Array<string> {
3102
+ return result.checks.map((item: SecurityConnectorCheck): string => {
3103
+ return `${item.key}:${item.status}`;
3104
+ });
3105
+ }
3106
+
3107
+ function withBudget(
3108
+ fake: FakeConnector,
3109
+ fetchBudget: ConnectorFetchBudget | undefined,
3110
+ ): SecurityEventConnector {
3111
+ return { ...fake.connector, fetchBudget };
3112
+ }
3113
+
3114
+ const PASS_CHECKS: Array<SecurityConnectorCheck> = [
3115
+ check(
3116
+ "rule-detections-read",
3117
+ "pass",
3118
+ "Read 12 rule detections by created time.",
3119
+ ),
3120
+ check(
3121
+ "curated-detections-read",
3122
+ "warn",
3123
+ "Curated detections are not available to this service account (HTTP 403).",
3124
+ ),
3125
+ check("alerts-view-read", "pass", "Read 3 alerts from the alerts view."),
3126
+ ];
3127
+
3128
+ const PROVIDER_DETAILS: JSONObject = {
3129
+ basis: "created-time",
3130
+ includeNonAlertingDetections: false,
3131
+ sourceCounts: { ruleDetections: 12, curatedDetections: 0, alertsView: 3 },
3132
+ creationLag: { measured: 15, lateCount: 1, maxLagMinutes: 42 },
3133
+ };
3134
+
3135
+ describe("SecurityEventConnectionPoller.resolveFetchBudget", () => {
3136
+ function resolve(
3137
+ fetchBudget: ConnectorFetchBudget | undefined,
3138
+ ): ResolvedFetchBudget {
3139
+ return SecurityEventConnectionPoller.resolveFetchBudget(
3140
+ withBudget(makeFakeConnector({}), fetchBudget),
3141
+ );
3142
+ }
3143
+
3144
+ test.each<[string, ConnectorFetchBudget | undefined]>([
3145
+ ["no budget", undefined],
3146
+ ["an empty budget", {}],
3147
+ ])(
3148
+ "a connector with %s gets the defaults and no wall clock",
3149
+ (_label: string, fetchBudget: ConnectorFetchBudget | undefined) => {
3150
+ const budget: ResolvedFetchBudget = resolve(fetchBudget);
3151
+
3152
+ expect(budget).toEqual({
3153
+ maxRequests: MAX_FETCH_REQUESTS,
3154
+ maxEvents: MAX_EVENTS_PER_RUN,
3155
+ });
3156
+ expect(budget).not.toHaveProperty("maxDurationMs");
3157
+ },
3158
+ );
3159
+
3160
+ test.each<[string, ConnectorFetchBudget, ResolvedFetchBudget]>([
3161
+ [
3162
+ "only maxRequests",
3163
+ { maxRequests: 36 },
3164
+ { maxRequests: 36, maxEvents: MAX_EVENTS_PER_RUN },
3165
+ ],
3166
+ [
3167
+ "only maxEvents",
3168
+ { maxEvents: 36000 },
3169
+ { maxRequests: MAX_FETCH_REQUESTS, maxEvents: 36000 },
3170
+ ],
3171
+ [
3172
+ "only maxDurationMs",
3173
+ { maxDurationMs: 240000 },
3174
+ {
3175
+ maxRequests: MAX_FETCH_REQUESTS,
3176
+ maxEvents: MAX_EVENTS_PER_RUN,
3177
+ maxDurationMs: 240000,
3178
+ },
3179
+ ],
3180
+ [
3181
+ "every field",
3182
+ { maxRequests: 36, maxEvents: 36000, maxDurationMs: 240000 },
3183
+ { maxRequests: 36, maxEvents: 36000, maxDurationMs: 240000 },
3184
+ ],
3185
+ [
3186
+ "values below the defaults (a budget may tighten a bound too)",
3187
+ { maxRequests: 5, maxEvents: 100, maxDurationMs: 1000 },
3188
+ { maxRequests: 5, maxEvents: 100, maxDurationMs: 1000 },
3189
+ ],
3190
+ ])(
3191
+ "overrides field by field: %s",
3192
+ (
3193
+ _label: string,
3194
+ fetchBudget: ConnectorFetchBudget,
3195
+ expected: ResolvedFetchBudget,
3196
+ ) => {
3197
+ const budget: ResolvedFetchBudget = resolve(fetchBudget);
3198
+
3199
+ expect(budget).toEqual(expected);
3200
+
3201
+ if (expected.maxDurationMs === undefined) {
3202
+ expect(budget).not.toHaveProperty("maxDurationMs");
3203
+ }
3204
+ },
3205
+ );
3206
+
3207
+ test.each([0, -0, -1, -0.5, NaN, Infinity, -Infinity, "30", null, true])(
3208
+ "an override of %j is ignored for every field",
3209
+ (value: unknown) => {
3210
+ const budget: ResolvedFetchBudget = resolve({
3211
+ maxRequests: value as never,
3212
+ maxEvents: value as never,
3213
+ maxDurationMs: value as never,
3214
+ });
3215
+
3216
+ expect(budget).toEqual({
3217
+ maxRequests: MAX_FETCH_REQUESTS,
3218
+ maxEvents: MAX_EVENTS_PER_RUN,
3219
+ });
3220
+ expect(budget).not.toHaveProperty("maxDurationMs");
3221
+ },
3222
+ );
3223
+
3224
+ test("an invalid field is ignored without discarding the valid ones beside it", () => {
3225
+ const first: ResolvedFetchBudget = resolve({
3226
+ maxRequests: 0,
3227
+ maxEvents: 36000,
3228
+ maxDurationMs: NaN,
3229
+ });
3230
+ const second: ResolvedFetchBudget = resolve({
3231
+ maxRequests: 36,
3232
+ maxEvents: -5,
3233
+ maxDurationMs: 240000,
3234
+ });
3235
+
3236
+ expect(first).toEqual({
3237
+ maxRequests: MAX_FETCH_REQUESTS,
3238
+ maxEvents: 36000,
3239
+ });
3240
+ expect(first).not.toHaveProperty("maxDurationMs");
3241
+ expect(second).toEqual({
3242
+ maxRequests: 36,
3243
+ maxEvents: MAX_EVENTS_PER_RUN,
3244
+ maxDurationMs: 240000,
3245
+ });
3246
+ });
3247
+ });
3248
+
3249
+ describe("SecurityEventConnectionPoller.executeConnection - fetch options", () => {
3250
+ test.each<["poll" | "preview" | "backfill"]>([
3251
+ ["poll"],
3252
+ ["preview"],
3253
+ ["backfill"],
3254
+ ])(
3255
+ "a %s tells the connector which operation it serves",
3256
+ async (type: "poll" | "preview" | "backfill") => {
3257
+ const fake: FakeConnector = makeFakeConnector({});
3258
+
3259
+ await SecurityEventConnectionPoller.executeConnection(
3260
+ makeConnection(),
3261
+ runOptionsFor(type),
3262
+ overridesFor(fake),
3263
+ );
3264
+
3265
+ expect(fake.fetchCalls).toHaveLength(1);
3266
+ expect(fake.fetchCalls[0]!.options.purpose).toBe(type);
3267
+ },
3268
+ );
3269
+
3270
+ test("a test run never fetches, so no connector is ever asked to fetch for a test", async () => {
3271
+ const fake: FakeConnector = makeFakeConnector({
3272
+ checks: [check("authentication", "pass")],
3273
+ });
3274
+
3275
+ await SecurityEventConnectionPoller.executeConnection(
3276
+ makeConnection(),
3277
+ { type: "test" },
3278
+ overridesFor(fake),
3279
+ );
3280
+
3281
+ expect(fake.fetchCalls).toHaveLength(0);
3282
+ });
3283
+
3284
+ test.each<[number | undefined, number]>([
3285
+ [30, 30],
3286
+ [1, 1],
3287
+ [1440, 1440],
3288
+ [0, 5],
3289
+ [undefined, 5],
3290
+ ])(
3291
+ "a connection polling every %j minutes tells the connector %j, the interval the scheduler applies",
3292
+ async (interval: number | undefined, expected: number) => {
3293
+ const fake: FakeConnector = makeFakeConnector({});
3294
+
3295
+ await SecurityEventConnectionPoller.executeConnection(
3296
+ makeConnection({ pollIntervalInMinutes: interval as number }),
3297
+ { type: "poll" },
3298
+ overridesFor(fake),
3299
+ );
3300
+
3301
+ expect(fake.fetchCalls[0]!.options.pollIntervalInMinutes).toBe(expected);
3302
+ },
3303
+ );
3304
+
3305
+ test.each<["poll" | "preview" | "backfill"]>([
3306
+ ["poll"],
3307
+ ["preview"],
3308
+ ["backfill"],
3309
+ ])(
3310
+ "a connector's fetch budget replaces the defaults for a %s",
3311
+ async (type: "poll" | "preview" | "backfill") => {
3312
+ const fake: FakeConnector = makeFakeConnector({});
3313
+
3314
+ await SecurityEventConnectionPoller.executeConnection(
3315
+ makeConnection({ pollIntervalInMinutes: 10 }),
3316
+ runOptionsFor(type),
3317
+ {
3318
+ connector: withBudget(fake, {
3319
+ maxRequests: 36,
3320
+ maxEvents: 36000,
3321
+ maxDurationMs: 240000,
3322
+ }),
3323
+ settings: SETTINGS,
3324
+ },
3325
+ );
3326
+
3327
+ expect(fake.fetchCalls[0]!.options).toEqual({
3328
+ maxRequests: 36,
3329
+ maxEvents: 36000,
3330
+ maxDurationMs: 240000,
3331
+ requestTimeoutInMs: POLL_REQUEST_TIMEOUT_IN_MS,
3332
+ sampleLimit: MAX_DIAGNOSTIC_SAMPLES,
3333
+ purpose: type,
3334
+ pollIntervalInMinutes: 10,
3335
+ });
3336
+ },
3337
+ );
3338
+
3339
+ test("an invalid budget falls back to the defaults when the fetch is made", async () => {
3340
+ const fake: FakeConnector = makeFakeConnector({});
3341
+
3342
+ await SecurityEventConnectionPoller.executeConnection(
3343
+ makeConnection(),
3344
+ { type: "poll" },
3345
+ {
3346
+ connector: withBudget(fake, {
3347
+ maxRequests: 0,
3348
+ maxEvents: 500,
3349
+ maxDurationMs: -1,
3350
+ }),
3351
+ settings: SETTINGS,
3352
+ },
3353
+ );
3354
+
3355
+ expect(fake.fetchCalls[0]!.options.maxRequests).toBe(MAX_FETCH_REQUESTS);
3356
+ expect(fake.fetchCalls[0]!.options.maxEvents).toBe(500);
3357
+ expect(fake.fetchCalls[0]!.options).not.toHaveProperty("maxDurationMs");
3358
+ });
3359
+
3360
+ test("without overrides the registry's connector brings its own budget", async () => {
3361
+ getJestSpyOn(
3362
+ SecurityEventConnectionService,
3363
+ "findOneById",
3364
+ ).mockResolvedValue(makeConnection() as never);
3365
+ const fake: FakeConnector = makeFakeConnector({});
3366
+ (
3367
+ SecurityEventConnectorRegistry.getConnector as unknown as jest.Mock
3368
+ ).mockReturnValue(withBudget(fake, { maxRequests: 36 }));
3369
+
3370
+ await SecurityEventConnectionPoller.executeConnection(makeConnection(), {
3371
+ type: "poll",
3372
+ });
3373
+
3374
+ expect(fake.fetchCalls[0]!.options.maxRequests).toBe(36);
3375
+ expect(fake.fetchCalls[0]!.options.maxEvents).toBe(MAX_EVENTS_PER_RUN);
3376
+ });
3377
+ });
3378
+
3379
+ describe("SecurityEventConnectionPoller.executeConnection - per-pass checks and the read summary", () => {
3380
+ test("a poll records the connector's pass checks verbatim, in order, between configuration and the read summary", async () => {
3381
+ const fake: FakeConnector = makeFakeConnector({
3382
+ fetch: makeFetchResult([makeEvent("evt-1")], { checks: PASS_CHECKS }),
3383
+ });
3384
+
3385
+ const result: SecurityEventConnectionRunResult =
3386
+ await SecurityEventConnectionPoller.executeConnection(
3387
+ makeConnection(),
3388
+ { type: "poll" },
3389
+ overridesFor(fake),
3390
+ );
3391
+
3392
+ expect(checkKeysAndStatuses(result)).toEqual([
3393
+ "configuration:pass",
3394
+ "rule-detections-read:pass",
3395
+ "curated-detections-read:warn",
3396
+ "alerts-view-read:pass",
3397
+ "read:pass",
3398
+ "import:pass",
3399
+ ]);
3400
+ expect(result.checks.slice(1, 4)).toEqual(PASS_CHECKS);
3401
+ // A pass that warned without leaving the window unread is not partial.
3402
+ expect(result.status).toBe("success");
3403
+ expect(
3404
+ (updateCall().data["lastPollResult"] as unknown as JSONObject)["checks"],
3405
+ ).toEqual(result.checks);
3406
+ });
3407
+
3408
+ test("a preview records the pass checks before the read summary too, with no import step", async () => {
3409
+ const fake: FakeConnector = makeFakeConnector({
3410
+ fetch: makeFetchResult([makeEvent("evt-1")], { checks: PASS_CHECKS }),
3411
+ });
3412
+
3413
+ const result: SecurityEventConnectionRunResult =
3414
+ await SecurityEventConnectionPoller.executeConnection(
3415
+ makeConnection(),
3416
+ historicalRun("preview"),
3417
+ overridesFor(fake),
3418
+ );
3419
+
3420
+ expect(checkKeysAndStatuses(result)).toEqual([
3421
+ "configuration:pass",
3422
+ "rule-detections-read:pass",
3423
+ "curated-detections-read:warn",
3424
+ "alerts-view-read:pass",
3425
+ "read:pass",
3426
+ ]);
3427
+ });
3428
+
3429
+ test("an empty pass list adds nothing", async () => {
3430
+ const fake: FakeConnector = makeFakeConnector({
3431
+ fetch: makeFetchResult([], { checks: [] }),
3432
+ });
3433
+
3434
+ const result: SecurityEventConnectionRunResult =
3435
+ await SecurityEventConnectionPoller.executeConnection(
3436
+ makeConnection(),
3437
+ { type: "poll" },
3438
+ overridesFor(fake),
3439
+ );
3440
+
3441
+ expect(checkKeysAndStatuses(result)).toEqual([
3442
+ "configuration:pass",
3443
+ "read:pass",
3444
+ "import:pass",
3445
+ ]);
3446
+ });
3447
+
3448
+ test("a read the connector could not finish is a warning that says so, never a pass", async () => {
3449
+ const fake: FakeConnector = makeFakeConnector({
3450
+ fetch: makeFetchResult([makeEvent("evt-1")], {
3451
+ complete: false,
3452
+ requestCount: MAX_FETCH_REQUESTS,
3453
+ warnings: ["Stopped by the search page budget after 20 requests."],
3454
+ checks: [
3455
+ check(
3456
+ "rule-detections-read",
3457
+ "warn",
3458
+ "Stopped by the search page budget after 20 requests.",
3459
+ ),
3460
+ ],
3461
+ }),
3462
+ });
3463
+
3464
+ const result: SecurityEventConnectionRunResult =
3465
+ await SecurityEventConnectionPoller.executeConnection(
3466
+ makeConnection({
3467
+ cursor: new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS).toISOString(),
3468
+ }),
3469
+ { type: "poll" },
3470
+ overridesFor(fake),
3471
+ );
3472
+
3473
+ expect(result.status).toBe("partial");
3474
+ expect(checkKeysAndStatuses(result)).toEqual([
3475
+ "configuration:pass",
3476
+ "rule-detections-read:warn",
3477
+ "read:warn",
3478
+ "import:pass",
3479
+ ]);
3480
+ expect(findCheck(result, "read").message).toBe(
3481
+ `1 ${DEFINITION.importedRecordName}s recognized; 0 rejected; 0 failed; ${MAX_FETCH_REQUESTS} requests. The window was not read completely; the warnings say what stopped the read.`,
3482
+ );
3483
+ });
3484
+
3485
+ test("a complete read keeps the plain summary message", async () => {
3486
+ const fake: FakeConnector = makeFakeConnector({
3487
+ fetch: makeFetchResult([makeEvent("evt-1"), makeEvent("evt-2")]),
3488
+ });
3489
+
3490
+ const result: SecurityEventConnectionRunResult =
3491
+ await SecurityEventConnectionPoller.executeConnection(
3492
+ makeConnection(),
3493
+ { type: "poll" },
3494
+ overridesFor(fake),
3495
+ );
3496
+
3497
+ const read: SecurityConnectorCheck = findCheck(result, "read");
3498
+ expect(read.status).toBe("pass");
3499
+ expect(read.message).toBe(
3500
+ `2 ${DEFINITION.importedRecordName}s recognized; 0 rejected; 0 failed; 1 request.`,
3501
+ );
3502
+ });
3503
+
3504
+ test.each<
3505
+ [
3506
+ string,
3507
+ Partial<ConnectorFetchResult>,
3508
+ SecurityConnectorCheck["status"],
3509
+ SecurityEventConnectionRunResult["status"],
3510
+ ]
3511
+ >([
3512
+ ["complete and clean", {}, "pass", "success"],
3513
+ ["incomplete", { complete: false }, "warn", "partial"],
3514
+ [
3515
+ "complete with rejected records",
3516
+ { rejectedCount: 2, fetchedCount: 3 },
3517
+ "warn",
3518
+ "success",
3519
+ ],
3520
+ [
3521
+ "incomplete with rejected records",
3522
+ { complete: false, rejectedCount: 2, fetchedCount: 3 },
3523
+ "warn",
3524
+ "partial",
3525
+ ],
3526
+ [
3527
+ "complete with normalization failures",
3528
+ { failedCount: 1, fetchedCount: 2 },
3529
+ "fail",
3530
+ "partial",
3531
+ ],
3532
+ [
3533
+ "incomplete with normalization failures",
3534
+ { complete: false, failedCount: 1, fetchedCount: 2 },
3535
+ "fail",
3536
+ "partial",
3537
+ ],
3538
+ ])(
3539
+ "a preview whose fetch is %s gets a %s read summary and a %s run",
3540
+ async (
3541
+ _label: string,
3542
+ overrides: Partial<ConnectorFetchResult>,
3543
+ readStatus: SecurityConnectorCheck["status"],
3544
+ runStatus: SecurityEventConnectionRunResult["status"],
3545
+ ) => {
3546
+ const fake: FakeConnector = makeFakeConnector({
3547
+ fetch: makeFetchResult([makeEvent("evt-1")], overrides),
3548
+ });
3549
+
3550
+ const result: SecurityEventConnectionRunResult =
3551
+ await SecurityEventConnectionPoller.executeConnection(
3552
+ makeConnection(),
3553
+ historicalRun("preview"),
3554
+ overridesFor(fake),
3555
+ );
3556
+
3557
+ const read: SecurityConnectorCheck = findCheck(result, "read");
3558
+ expect(read.status).toBe(readStatus);
3559
+ expect(result.status).toBe(runStatus);
3560
+ expect(read.message.endsWith("stopped the read.")).toBe(
3561
+ overrides.complete === false,
3562
+ );
3563
+ },
3564
+ );
3565
+ });
3566
+
3567
+ describe("SecurityEventConnectionPoller.executeConnection - provider details", () => {
3568
+ test("a poll keeps the connector's details on the run result and in the stored lastPollResult", async () => {
3569
+ const fake: FakeConnector = makeFakeConnector({
3570
+ fetch: makeFetchResult([makeEvent("evt-1")], {
3571
+ details: PROVIDER_DETAILS,
3572
+ }),
3573
+ });
3574
+
3575
+ const result: SecurityEventConnectionRunResult =
3576
+ await SecurityEventConnectionPoller.executeConnection(
3577
+ makeConnection(),
3578
+ { type: "poll" },
3579
+ overridesFor(fake),
3580
+ );
3581
+
3582
+ expect(result.providerDetails).toEqual(PROVIDER_DETAILS);
3583
+ expect(
3584
+ (updateCall().data["lastPollResult"] as unknown as JSONObject)[
3585
+ "providerDetails"
3586
+ ],
3587
+ ).toEqual(PROVIDER_DETAILS);
3588
+ });
3589
+
3590
+ test.each<["preview" | "backfill"]>([["preview"], ["backfill"]])(
3591
+ "a %s keeps them too",
3592
+ async (type: "preview" | "backfill") => {
3593
+ const fake: FakeConnector = makeFakeConnector({
3594
+ fetch: makeFetchResult([makeEvent("evt-1")], {
3595
+ details: { ...PROVIDER_DETAILS, basis: "detection-time" },
3596
+ }),
3597
+ });
3598
+
3599
+ const result: SecurityEventConnectionRunResult =
3600
+ await SecurityEventConnectionPoller.executeConnection(
3601
+ makeConnection(),
3602
+ historicalRun(type),
3603
+ overridesFor(fake),
3604
+ );
3605
+
3606
+ expect(result.providerDetails).toEqual({
3607
+ ...PROVIDER_DETAILS,
3608
+ basis: "detection-time",
3609
+ });
3610
+ },
3611
+ );
3612
+
3613
+ test("a fetch without details leaves providerDetails off the run", async () => {
3614
+ const fake: FakeConnector = makeFakeConnector({
3615
+ fetch: makeFetchResult([makeEvent("evt-1")]),
3616
+ });
3617
+
3618
+ const result: SecurityEventConnectionRunResult =
3619
+ await SecurityEventConnectionPoller.executeConnection(
3620
+ makeConnection(),
3621
+ { type: "poll" },
3622
+ overridesFor(fake),
3623
+ );
3624
+
3625
+ expect(result).not.toHaveProperty("providerDetails");
3626
+ });
3627
+
3628
+ test("details and pass checks survive a failure after the read, and the failure is named after the import", async () => {
3629
+ getJestSpyOn(
3630
+ SecurityEventDedupe,
3631
+ "findExistingEventUids",
3632
+ ).mockRejectedValue(new Error("Cluster oneuptime not found") as never);
3633
+ const fake: FakeConnector = makeFakeConnector({
3634
+ fetch: makeFetchResult([makeEvent("evt-1")], {
3635
+ details: PROVIDER_DETAILS,
3636
+ checks: PASS_CHECKS,
3637
+ }),
3638
+ });
3639
+
3640
+ const result: SecurityEventConnectionRunResult =
3641
+ await SecurityEventConnectionPoller.executeConnection(
3642
+ makeConnection(),
3643
+ { type: "poll" },
3644
+ overridesFor(fake),
3645
+ );
3646
+
3647
+ expect(result.status).toBe("failed");
3648
+ expect(result.providerDetails).toEqual(PROVIDER_DETAILS);
3649
+ expect(checkKeysAndStatuses(result)).toEqual([
3650
+ "configuration:pass",
3651
+ "rule-detections-read:pass",
3652
+ "curated-detections-read:warn",
3653
+ "alerts-view-read:pass",
3654
+ "read:pass",
3655
+ "failure:fail",
3656
+ ]);
3657
+ expect(findCheck(result, "failure").name).toBe("Import records");
3658
+ });
3659
+
3660
+ test("a fetch that throws leaves no provider details", async () => {
3661
+ const fake: FakeConnector = makeFakeConnector({
3662
+ fetch: new Error("Okta system log read failed (HTTP 503): unavailable"),
3663
+ });
3664
+
3665
+ const result: SecurityEventConnectionRunResult =
3666
+ await SecurityEventConnectionPoller.executeConnection(
3667
+ makeConnection(),
3668
+ { type: "poll" },
3669
+ overridesFor(fake),
3670
+ );
3671
+
3672
+ expect(result).not.toHaveProperty("providerDetails");
3673
+ });
3674
+ });
3675
+
3676
+ describe("SecurityEventConnectionPoller.executeConnection - a failed pass names the failure", () => {
3677
+ const READ_PHASE: string = `Read ${DEFINITION.importedRecordName}s from ${DEFINITION.title}`;
3678
+
3679
+ function failingPass(
3680
+ name: string,
3681
+ remediation?: string | undefined,
3682
+ ): SecurityConnectorCheck {
3683
+ return {
3684
+ key: "curated-detections-read",
3685
+ name,
3686
+ status: "fail",
3687
+ durationMs: 3,
3688
+ message: "HTTP 500 from the curated detections search.",
3689
+ ...(remediation ? { remediation } : {}),
3690
+ };
3691
+ }
3692
+
3693
+ test("the passes that ran are kept and the failure is named after the pass that threw, with its remediation and the redacted error", async () => {
3694
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
3695
+ const error: Error = attachConnectorChecks(
3696
+ new Error(
3697
+ `Google SecOps legacySearchCuratedDetections failed (HTTP 500): client_secret=${SECRET_VALUE}`,
3698
+ ),
3699
+ [
3700
+ PASS_CHECKS[0]!,
3701
+ failingPass(
3702
+ "Read curated rule detections by created time",
3703
+ "Google returned a server error; the next poll retries this window.",
3704
+ ),
3705
+ ],
3706
+ );
3707
+ const fake: FakeConnector = makeFakeConnector({ fetch: error });
3708
+
3709
+ const result: SecurityEventConnectionRunResult =
3710
+ await SecurityEventConnectionPoller.executeConnection(
3711
+ makeConnection({ cursor: cursor.toISOString() }),
3712
+ { type: "poll" },
3713
+ overridesFor(fake),
3714
+ );
3715
+
3716
+ expect(result.status).toBe("failed");
3717
+ expect(checkKeysAndStatuses(result)).toEqual([
3718
+ "configuration:pass",
3719
+ "rule-detections-read:pass",
3720
+ "failure:fail",
3721
+ ]);
3722
+ const failure: SecurityConnectorCheck = findCheck(result, "failure");
3723
+ expect(failure.name).toBe("Read curated rule detections by created time");
3724
+ expect(failure.remediation).toBe(
3725
+ "Google returned a server error; the next poll retries this window.",
3726
+ );
3727
+ expect(failure.message).toBe(result.error);
3728
+ expect(result.error).toContain(
3729
+ "Google SecOps legacySearchCuratedDetections failed (HTTP 500)",
3730
+ );
3731
+ expect(result.error).not.toContain(SECRET_VALUE);
3732
+ expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
3733
+
3734
+ // Still an ordinary failure for the cursor: held, chunk kept.
3735
+ const written: ConnectionUpdateCall = updateCall();
3736
+ expect("cursor" in written.data).toBe(false);
3737
+ expect(written.data["lastError"]).toBe(result.error);
3738
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES);
3739
+ });
3740
+
3741
+ test("attached checks without a failure are all kept and the phase names the failure", async () => {
3742
+ const fake: FakeConnector = makeFakeConnector({
3743
+ fetch: attachConnectorChecks(new Error("stream ended early"), [
3744
+ PASS_CHECKS[0]!,
3745
+ PASS_CHECKS[1]!,
3746
+ ]),
3747
+ });
3748
+
3749
+ const result: SecurityEventConnectionRunResult =
3750
+ await SecurityEventConnectionPoller.executeConnection(
3751
+ makeConnection(),
3752
+ { type: "poll" },
3753
+ overridesFor(fake),
3754
+ );
3755
+
3756
+ expect(checkKeysAndStatuses(result)).toEqual([
3757
+ "configuration:pass",
3758
+ "rule-detections-read:pass",
3759
+ "curated-detections-read:warn",
3760
+ "failure:fail",
3761
+ ]);
3762
+ expect(findCheck(result, "failure").name).toBe(READ_PHASE);
3763
+ expect(findCheck(result, "failure")).not.toHaveProperty("remediation");
3764
+ });
3765
+
3766
+ test("an empty attached list is the same as none", async () => {
3767
+ const fake: FakeConnector = makeFakeConnector({
3768
+ fetch: attachConnectorChecks(new Error("boom"), []),
3769
+ });
3770
+
3771
+ const result: SecurityEventConnectionRunResult =
3772
+ await SecurityEventConnectionPoller.executeConnection(
3773
+ makeConnection(),
3774
+ { type: "poll" },
3775
+ overridesFor(fake),
3776
+ );
3777
+
3778
+ expect(checkKeysAndStatuses(result)).toEqual([
3779
+ "configuration:pass",
3780
+ "failure:fail",
3781
+ ]);
3782
+ expect(findCheck(result, "failure").name).toBe(READ_PHASE);
3783
+ });
3784
+
3785
+ test("when more than one attached check failed, the last one (the pass that threw) names the failure", async () => {
3786
+ const fake: FakeConnector = makeFakeConnector({
3787
+ fetch: attachConnectorChecks(new Error("boom"), [
3788
+ failingPass("Read rule detections by created time"),
3789
+ PASS_CHECKS[2]!,
3790
+ failingPass("Read alerts view by detection time"),
3791
+ ]),
3792
+ });
3793
+
3794
+ const result: SecurityEventConnectionRunResult =
3795
+ await SecurityEventConnectionPoller.executeConnection(
3796
+ makeConnection(),
3797
+ { type: "poll" },
3798
+ overridesFor(fake),
3799
+ );
3800
+
3801
+ expect(checkKeysAndStatuses(result)).toEqual([
3802
+ "configuration:pass",
3803
+ "alerts-view-read:pass",
3804
+ "failure:fail",
3805
+ ]);
3806
+ expect(findCheck(result, "failure").name).toBe(
3807
+ "Read alerts view by detection time",
3808
+ );
3809
+ });
3810
+
3811
+ test("a failing pass without remediation adds none", async () => {
3812
+ const fake: FakeConnector = makeFakeConnector({
3813
+ fetch: attachConnectorChecks(new Error("boom"), [
3814
+ failingPass("Read rule detections by created time"),
3815
+ ]),
3816
+ });
3817
+
3818
+ const result: SecurityEventConnectionRunResult =
3819
+ await SecurityEventConnectionPoller.executeConnection(
3820
+ makeConnection(),
3821
+ { type: "poll" },
3822
+ overridesFor(fake),
3823
+ );
3824
+
3825
+ expect(findCheck(result, "failure").name).toBe(
3826
+ "Read rule detections by created time",
3827
+ );
3828
+ expect(findCheck(result, "failure")).not.toHaveProperty("remediation");
3829
+ });
3830
+
3831
+ test("entries in an attached list that are not checks are skipped", async () => {
3832
+ const fake: FakeConnector = makeFakeConnector({
3833
+ fetch: attachConnectorChecks(new Error("boom"), [
3834
+ null,
3835
+ "not a check",
3836
+ PASS_CHECKS[0]!,
3837
+ ] as never),
3838
+ });
3839
+
3840
+ const result: SecurityEventConnectionRunResult =
3841
+ await SecurityEventConnectionPoller.executeConnection(
3842
+ makeConnection(),
3843
+ { type: "poll" },
3844
+ overridesFor(fake),
3845
+ );
3846
+
3847
+ expect(checkKeysAndStatuses(result)).toEqual([
3848
+ "configuration:pass",
3849
+ "rule-detections-read:pass",
3850
+ "failure:fail",
3851
+ ]);
3852
+ });
3853
+
3854
+ test("a thrown value that is not an Error still fails the run under the phase name", async () => {
3855
+ const fake: FakeConnector = makeFakeConnector({});
3856
+ const connector: SecurityEventConnector = {
3857
+ ...fake.connector,
3858
+ fetchEvents: (): Promise<ConnectorFetchResult> => {
3859
+ return Promise.reject("socket hang up");
3860
+ },
3861
+ };
3862
+
3863
+ const result: SecurityEventConnectionRunResult =
3864
+ await SecurityEventConnectionPoller.executeConnection(
3865
+ makeConnection(),
3866
+ { type: "poll" },
3867
+ { connector, settings: SETTINGS },
3868
+ );
3869
+
3870
+ expect(result.status).toBe("failed");
3871
+ expect(result.error).toBe("socket hang up");
3872
+ expect(findCheck(result, "failure").name).toBe(READ_PHASE);
3873
+ });
3874
+
3875
+ test("a pass that timed out names the failure and, like any timeout, narrows the next window", async () => {
3876
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
3877
+ const fake: FakeConnector = makeFakeConnector({
3878
+ fetch: attachConnectorChecks(
3879
+ new Error(
3880
+ "Google SecOps legacySearchDetections timed out after 60 seconds with no response.",
3881
+ ),
3882
+ [failingPass("Read rule detections by created time")],
3883
+ ),
3884
+ });
3885
+
3886
+ const result: SecurityEventConnectionRunResult =
3887
+ await SecurityEventConnectionPoller.executeConnection(
3888
+ makeConnection({ cursor: cursor.toISOString() }),
3889
+ { type: "poll" },
3890
+ overridesFor(fake),
3891
+ );
3892
+
3893
+ expect(findCheck(result, "failure").name).toBe(
3894
+ "Read rule detections by created time",
3895
+ );
3896
+ expect(result.chunkMinutes).toBe(MAX_CHUNK_MINUTES);
3897
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES / 2);
3898
+ expect("cursor" in updateCall().data).toBe(false);
3899
+ });
3900
+ });
3901
+
3902
+ describe("SecurityEventConnectionPoller.executeConnection - a failed fetch keeps what its passes gathered", () => {
3903
+ /*
3904
+ * The retired Google SecOps poller mutated one result through every pass,
3905
+ * so its failed run still carried the passes' warnings (the curated HTTP
3906
+ * 403 downgrade among them), the request and record counts, and its
3907
+ * details. A connector hands the same over with
3908
+ * attachConnectorFetchSummary. The failure itself, lastError and the
3909
+ * cursor must not change because of it, and a connector that attaches no
3910
+ * summary must leave the failed run exactly as it was.
3911
+ */
3912
+ const CURATED_WARNING: string =
3913
+ "Curated rule detections could not be read (HTTP 403); this tenant may not have curated rule access. Rule detections and the alerts view were still read.";
3914
+ const BUDGET_WARNING: string =
3915
+ "Read rule detections by created time was stopped by the request budget after 20 requests.";
3916
+ const CATCH_UP_WARNING: string =
3917
+ "Catching up from the saved cursor in 24 hour windows. Later records will be fetched by subsequent polls.";
3918
+ const SUMMARY_DETAILS: JSONObject = {
3919
+ basis: "created-time",
3920
+ sourceCounts: { ruleDetections: 2, curatedDetections: 0, alertsView: 0 },
3921
+ includeNonAlertingDetections: false,
3922
+ };
3923
+
3924
+ function alertsViewFailure(
3925
+ summary?: ConnectorFetchFailureSummary | undefined,
3926
+ ): Error {
3927
+ const error: Error = attachConnectorChecks(
3928
+ new Error(
3929
+ `Google SecOps alerts fetch failed (HTTP 500): client_secret=${SECRET_VALUE}`,
3930
+ ),
3931
+ [
3932
+ PASS_CHECKS[0]!,
3933
+ PASS_CHECKS[1]!,
3934
+ {
3935
+ key: "alerts-view-read",
3936
+ name: "Read alerts view by detection time",
3937
+ status: "fail",
3938
+ durationMs: 2,
3939
+ message: "HTTP 500 from the alerts view.",
3940
+ },
3941
+ ],
3942
+ );
3943
+
3944
+ return summary ? attachConnectorFetchSummary(error, summary) : error;
3945
+ }
3946
+
3947
+ function lastConnectionUpdate(): ConnectionUpdateCall {
3948
+ const spy: jest.Mock =
3949
+ SecurityEventConnectionService.updateOneById as unknown as jest.Mock;
3950
+ expect(spy.mock.calls.length).toBeGreaterThan(0);
3951
+ return spy.mock.calls[
3952
+ spy.mock.calls.length - 1
3953
+ ]![0] as ConnectionUpdateCall;
3954
+ }
3955
+
3956
+ function pollWith(
3957
+ fetch: Error,
3958
+ cursor: Date,
3959
+ ): Promise<SecurityEventConnectionRunResult> {
3960
+ return SecurityEventConnectionPoller.executeConnection(
3961
+ makeConnection({ cursor: cursor.toISOString() }),
3962
+ { type: "poll" },
3963
+ overridesFor(makeFakeConnector({ fetch })),
3964
+ );
3965
+ }
3966
+
3967
+ test("the summary's warnings, counts and provider details are kept while the failure, lastError and cursor stay as they were", async () => {
3968
+ // Three days behind, so the window raises its own catch-up warning.
3969
+ const cursor: Date = new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS);
3970
+ const baseline: SecurityEventConnectionRunResult = await pollWith(
3971
+ alertsViewFailure(),
3972
+ cursor,
3973
+ );
3974
+ const baselineUpdate: ConnectionUpdateCall = lastConnectionUpdate();
3975
+
3976
+ const result: SecurityEventConnectionRunResult = await pollWith(
3977
+ alertsViewFailure({
3978
+ // A warning the window already raised, and a repeat, are written once.
3979
+ warnings: [
3980
+ CURATED_WARNING,
3981
+ CATCH_UP_WARNING,
3982
+ BUDGET_WARNING,
3983
+ CURATED_WARNING,
3984
+ ],
3985
+ requestCount: 3,
3986
+ fetchedCount: 2,
3987
+ details: SUMMARY_DETAILS,
3988
+ }),
3989
+ cursor,
3990
+ );
3991
+ const written: ConnectionUpdateCall = lastConnectionUpdate();
3992
+
3993
+ expect(baseline.warnings).toEqual([CATCH_UP_WARNING]);
3994
+ expect(result.status).toBe("failed");
3995
+ expect(result.complete).toBe(false);
3996
+ expect(result.warnings).toEqual([
3997
+ CATCH_UP_WARNING,
3998
+ CURATED_WARNING,
3999
+ BUDGET_WARNING,
4000
+ ]);
4001
+ expect(result.requestCount).toBe(3);
4002
+ expect(result.fetchedCount).toBe(2);
4003
+ expect(result.ingestedCount).toBe(0);
4004
+ expect(result.providerDetails).toEqual(SUMMARY_DETAILS);
4005
+
4006
+ // The failure is booked exactly as it is without a summary.
4007
+ expect(result.error).toBe(baseline.error);
4008
+ expect(result.error).toContain(
4009
+ "Google SecOps alerts fetch failed (HTTP 500)",
4010
+ );
4011
+ expect(checkKeysAndStatuses(result)).toEqual([
4012
+ "configuration:pass",
4013
+ "rule-detections-read:pass",
4014
+ "curated-detections-read:warn",
4015
+ "failure:fail",
4016
+ ]);
4017
+ expect(checkKeysAndStatuses(result)).toEqual(
4018
+ checkKeysAndStatuses(baseline),
4019
+ );
4020
+ expect(result.checks[result.checks.length - 1]!.key).toBe("failure");
4021
+ expect(findCheck(result, "failure")).toMatchObject({
4022
+ name: "Read alerts view by detection time",
4023
+ message: result.error,
4024
+ });
4025
+ expect(result.chunkMinutes).toBe(baseline.chunkMinutes);
4026
+ expect(result.nextChunkMinutes).toBe(baseline.nextChunkMinutes);
4027
+ expect(written.data["lastError"]).toBe(result.error);
4028
+ expect(written.data["lastError"]).toBe(baselineUpdate.data["lastError"]);
4029
+ expect("cursor" in written.data).toBe(false);
4030
+ expect("lastSuccessfulPollAt" in written.data).toBe(false);
4031
+ expect(written.data["lastPollResult"]).toMatchObject({
4032
+ status: "failed",
4033
+ requestCount: 3,
4034
+ fetchedCount: 2,
4035
+ providerDetails: SUMMARY_DETAILS,
4036
+ });
4037
+ expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
4038
+ });
4039
+
4040
+ test("a connector that attaches no summary leaves the failed run's counts, warnings and details as they were", async () => {
4041
+ const result: SecurityEventConnectionRunResult = await pollWith(
4042
+ alertsViewFailure(),
4043
+ new Date(Date.now() - 10 * MINUTE_MS),
4044
+ );
4045
+
4046
+ expect(result.status).toBe("failed");
4047
+ expect(result.requestCount).toBe(0);
4048
+ expect(result.fetchedCount).toBe(0);
4049
+ expect(result.warnings).toEqual([]);
4050
+ expect(result).not.toHaveProperty("providerDetails");
4051
+ expect(findCheck(result, "failure").name).toBe(
4052
+ "Read alerts view by detection time",
4053
+ );
4054
+ expect(lastConnectionUpdate().data["lastError"]).toBe(result.error);
4055
+ });
4056
+
4057
+ test("a malformed summary is ignored like none", async () => {
4058
+ const result: SecurityEventConnectionRunResult = await pollWith(
4059
+ alertsViewFailure({
4060
+ warnings: "not a list",
4061
+ requestCount: 3,
4062
+ fetchedCount: 2,
4063
+ details: SUMMARY_DETAILS,
4064
+ } as unknown as ConnectorFetchFailureSummary),
4065
+ new Date(Date.now() - 10 * MINUTE_MS),
4066
+ );
4067
+
4068
+ expect(result.status).toBe("failed");
4069
+ expect(result.requestCount).toBe(0);
4070
+ expect(result.fetchedCount).toBe(0);
4071
+ expect(result.warnings).toEqual([]);
4072
+ expect(result).not.toHaveProperty("providerDetails");
4073
+ });
4074
+
4075
+ test("a summary without details keeps its warnings and counts and adds no provider details", async () => {
4076
+ const result: SecurityEventConnectionRunResult = await pollWith(
4077
+ alertsViewFailure({
4078
+ warnings: [CURATED_WARNING],
4079
+ requestCount: 1,
4080
+ fetchedCount: 0,
4081
+ }),
4082
+ new Date(Date.now() - 10 * MINUTE_MS),
4083
+ );
4084
+
4085
+ expect(result).toMatchObject({
4086
+ status: "failed",
4087
+ warnings: [CURATED_WARNING],
4088
+ requestCount: 1,
4089
+ fetchedCount: 0,
4090
+ });
4091
+ expect(result).not.toHaveProperty("providerDetails");
4092
+ });
4093
+
4094
+ test.each<["preview" | "backfill"]>([["preview"], ["backfill"]])(
4095
+ "a failed %s keeps the summary too",
4096
+ async (type: "preview" | "backfill") => {
4097
+ const result: SecurityEventConnectionRunResult =
4098
+ await SecurityEventConnectionPoller.executeConnection(
4099
+ makeConnection(),
4100
+ runOptionsFor(type),
4101
+ overridesFor(
4102
+ makeFakeConnector({
4103
+ fetch: alertsViewFailure({
4104
+ warnings: [CURATED_WARNING],
4105
+ requestCount: 2,
4106
+ fetchedCount: 1,
4107
+ details: SUMMARY_DETAILS,
4108
+ }),
4109
+ }),
4110
+ ),
4111
+ );
4112
+
4113
+ expect(result).toMatchObject({
4114
+ status: "failed",
4115
+ warnings: [CURATED_WARNING],
4116
+ requestCount: 2,
4117
+ fetchedCount: 1,
4118
+ providerDetails: SUMMARY_DETAILS,
4119
+ });
4120
+ expect(findCheck(result, "failure").name).toBe(
4121
+ "Read alerts view by detection time",
4122
+ );
4123
+ },
4124
+ );
4125
+ });
4126
+
4127
+ describe("SecurityEventConnectionPoller.executeConnection - queued test with a ConnectorTestResult", () => {
4128
+ function resultConnector(
4129
+ fake: FakeConnector,
4130
+ answer: ConnectorTestResult,
4131
+ ): SecurityEventConnector {
4132
+ return {
4133
+ ...fake.connector,
4134
+ testConnection: (
4135
+ settings: SecurityConnectorSettings,
4136
+ options: ConnectorTestOptions,
4137
+ ): Promise<ConnectorTestResult> => {
4138
+ fake.testCalls.push({ settings, options });
4139
+ return Promise.resolve(answer);
4140
+ },
4141
+ };
4142
+ }
4143
+
4144
+ test("its checks are recorded, it is asked to skip availability, and counts and samples stay off the run", async () => {
4145
+ const fake: FakeConnector = makeFakeConnector({});
4146
+
4147
+ const result: SecurityEventConnectionRunResult =
4148
+ await SecurityEventConnectionPoller.executeConnection(
4149
+ makeConnection(),
4150
+ { type: "test" },
4151
+ {
4152
+ connector: resultConnector(fake, {
4153
+ checks: [
4154
+ check("authentication", "pass"),
4155
+ check("rule-detections-read", "pass"),
4156
+ ],
4157
+ counts: { alertsViewLast24h: 3 },
4158
+ samples: [{ id: "de_1", title: "Rule", severity: "High" }],
4159
+ }),
4160
+ settings: SETTINGS,
4161
+ },
4162
+ );
4163
+
4164
+ expect(result.status).toBe("success");
4165
+ expect(checkKeysAndStatuses(result)).toEqual([
4166
+ "configuration:pass",
4167
+ "authentication:pass",
4168
+ "rule-detections-read:pass",
4169
+ ]);
4170
+ expect(fake.testCalls[0]!.options).toEqual({
4171
+ requestTimeoutInMs: POLL_REQUEST_TIMEOUT_IN_MS,
4172
+ skipAvailability: true,
4173
+ });
4174
+ expect(result.samples).toEqual([]);
4175
+ expect(result).not.toHaveProperty("counts");
4176
+ expect(fake.fetchCalls).toHaveLength(0);
4177
+ });
4178
+
4179
+ test("a failing check inside it fails the run, naming that check", async () => {
4180
+ const fake: FakeConnector = makeFakeConnector({});
4181
+
4182
+ const result: SecurityEventConnectionRunResult =
4183
+ await SecurityEventConnectionPoller.executeConnection(
4184
+ makeConnection(),
4185
+ { type: "test" },
4186
+ {
4187
+ connector: resultConnector(fake, {
4188
+ checks: [
4189
+ check("authentication", "fail", "Token exchange refused."),
4190
+ ],
4191
+ }),
4192
+ settings: SETTINGS,
4193
+ },
4194
+ );
4195
+
4196
+ expect(result.status).toBe("failed");
4197
+ expect(result.error).toBe("Check authentication: Token exchange refused.");
4198
+ expect(findCheck(result, "failure").name).toBe(
4199
+ `Check access to ${DEFINITION.title}`,
4200
+ );
4201
+ });
4202
+ });
4203
+
4204
+ describe("SecurityEventConnectionPoller.executeConnection - event attribute key", () => {
4205
+ test.each<[SecurityEventConnectionRunOptions["type"]]>([
4206
+ ["poll"],
4207
+ ["preview"],
4208
+ ["backfill"],
4209
+ ["test"],
4210
+ ])(
4211
+ "a %s run leaves eventAttributeKey unset, which means the default attribute",
4212
+ async (type: SecurityEventConnectionRunOptions["type"]) => {
4213
+ const fake: FakeConnector = makeFakeConnector({
4214
+ fetch: makeFetchResult([makeEvent("evt-1")]),
4215
+ checks: [check("authentication", "pass")],
4216
+ });
4217
+
4218
+ const result: SecurityEventConnectionRunResult =
4219
+ await SecurityEventConnectionPoller.executeConnection(
4220
+ makeConnection(),
4221
+ runOptionsFor(type),
4222
+ overridesFor(fake),
4223
+ );
4224
+
4225
+ expect(result).not.toHaveProperty("eventAttributeKey");
4226
+ },
4227
+ );
4228
+
4229
+ test("a failed run leaves it unset too", async () => {
4230
+ const fake: FakeConnector = makeFakeConnector({ fetch: new Error("boom") });
4231
+
4232
+ const result: SecurityEventConnectionRunResult =
4233
+ await SecurityEventConnectionPoller.executeConnection(
4234
+ makeConnection(),
4235
+ { type: "poll" },
4236
+ overridesFor(fake),
4237
+ );
4238
+
4239
+ expect(result).not.toHaveProperty("eventAttributeKey");
4240
+ });
4241
+
4242
+ test("imported rows carry the connection id under the default attribute, never the legacy Google SecOps one", async () => {
4243
+ const fake: FakeConnector = makeFakeConnector({
4244
+ fetch: makeFetchResult([makeEvent("evt-1")]),
4245
+ });
4246
+
4247
+ await SecurityEventConnectionPoller.executeConnection(
4248
+ makeConnection(),
4249
+ { type: "poll" },
4250
+ overridesFor(fake),
4251
+ );
4252
+
4253
+ const attributes: JSONObject = insertedRows[0]!["attributes"] as JSONObject;
4254
+ expect(SECURITY_CONNECTION_ID_ATTRIBUTE).toBe(
4255
+ "oneuptime.security_connection.id",
4256
+ );
4257
+ expect(attributes[SECURITY_CONNECTION_ID_ATTRIBUTE]).toBe(
4258
+ CONNECTION_ID.toString(),
4259
+ );
4260
+ expect(attributes).not.toHaveProperty(
4261
+ LEGACY_GOOGLE_SECOPS_CONNECTION_ID_ATTRIBUTE,
4262
+ );
4263
+ });
4264
+ });
4265
+
4266
+ /*
4267
+ * Google SecOps through the shared loop with a fake connector: the catalog
4268
+ * supplies the one-minute overlap, the "detection" record name and the
4269
+ * Google / Google SecOps names that are the dedupe scope and the telemetry
4270
+ * service, so connections carried over from the retired poller recognize
4271
+ * what they already imported.
4272
+ */
4273
+ describe("SecurityEventConnectionPoller - Google SecOps connections", () => {
4274
+ const GOOGLE: SecurityEventConnectorDefinition =
4275
+ getSecurityEventConnectorDefinition(
4276
+ SecurityEventConnectorProvider.GoogleSecOps,
4277
+ )!;
4278
+ const GOOGLE_SETTINGS: SecurityConnectorSettings = {
4279
+ provider: SecurityEventConnectorProvider.GoogleSecOps,
4280
+ config: {
4281
+ region: "us",
4282
+ instanceResourceName: "projects/acme/locations/us/instances/i",
4283
+ },
4284
+ secrets: { serviceAccountJson: '{"client_email":"poller@acme"}' },
4285
+ // Detections selected under Data to import.
4286
+ alertingOnly: false,
4287
+ };
4288
+
4289
+ function googleConnection(
4290
+ overrides: Partial<SecurityEventConnection> = {},
4291
+ ): SecurityEventConnection {
4292
+ return makeConnection({
4293
+ name: "Google SecOps production",
4294
+ provider: SecurityEventConnectorProvider.GoogleSecOps,
4295
+ config: GOOGLE_SETTINGS.config,
4296
+ secrets: JSON.stringify(GOOGLE_SETTINGS.secrets),
4297
+ alertingOnly: false,
4298
+ ...overrides,
4299
+ });
4300
+ }
4301
+
4302
+ function googleOverrides(fake: FakeConnector): PollerOverrides {
4303
+ return {
4304
+ connector: withBudget(fake, {
4305
+ maxRequests: 36,
4306
+ maxEvents: 36000,
4307
+ maxDurationMs: 240000,
4308
+ }),
4309
+ settings: GOOGLE_SETTINGS,
4310
+ };
4311
+ }
4312
+
4313
+ test("a scheduled poll reads from one minute before the cursor and imports detections under Google / Google SecOps", async () => {
4314
+ const cursor: Date = new Date(Date.now() - 10 * MINUTE_MS);
4315
+ const fake: FakeConnector = makeFakeConnector({
4316
+ fetch: makeFetchResult([makeEvent("de_1"), makeEvent("de_2")], {
4317
+ checks: PASS_CHECKS,
4318
+ details: PROVIDER_DETAILS,
4319
+ }),
4320
+ });
4321
+
4322
+ const result: SecurityEventConnectionRunResult =
4323
+ await SecurityEventConnectionPoller.executeConnection(
4324
+ googleConnection({
4325
+ cursor: cursor.toISOString(),
4326
+ pollIntervalInMinutes: 5,
4327
+ }),
4328
+ { type: "poll" },
4329
+ googleOverrides(fake),
4330
+ );
4331
+
4332
+ expect(GOOGLE.cursorOverlapInMinutes).toBe(1);
4333
+ const call: FakeConnector["fetchCalls"][number] = fake.fetchCalls[0]!;
4334
+ expect(call.window.startTime.toISOString()).toBe(
4335
+ new Date(cursor.getTime() - MINUTE_MS).toISOString(),
4336
+ );
4337
+ expect(call.settings.alertingOnly).toBe(false);
4338
+ expect(call.options).toEqual({
4339
+ maxRequests: 36,
4340
+ maxEvents: 36000,
4341
+ maxDurationMs: 240000,
4342
+ requestTimeoutInMs: POLL_REQUEST_TIMEOUT_IN_MS,
4343
+ sampleLimit: MAX_DIAGNOSTIC_SAMPLES,
4344
+ purpose: "poll",
4345
+ pollIntervalInMinutes: 5,
4346
+ });
4347
+
4348
+ expect(result.status).toBe("success");
4349
+ expect(result.provider).toBe(SecurityEventConnectorProvider.GoogleSecOps);
4350
+ expect(result.providerDetails).toEqual(PROVIDER_DETAILS);
4351
+ expect(findCheck(result, "read").name).toBe(
4352
+ "Read detections from Google SecOps",
4353
+ );
4354
+ expect(dedupeCalls[0]).toEqual(
4355
+ expect.objectContaining({
4356
+ vendorName: "Google",
4357
+ productName: "Google SecOps",
4358
+ }),
4359
+ );
4360
+ expect(OTelIngestService.telemetryServiceFromName).toHaveBeenCalledWith({
4361
+ serviceName: "Google SecOps",
4362
+ projectId: PROJECT_ID,
4363
+ });
4364
+ expect(insertedRows).toHaveLength(2);
4365
+ expect(insertedRows[0]!["vendorName"]).toBe("Google");
4366
+ expect(insertedRows[0]!["productName"]).toBe("Google SecOps");
4367
+ const attributes: JSONObject = insertedRows[0]!["attributes"] as JSONObject;
4368
+ expect(attributes[SECURITY_CONNECTION_ID_ATTRIBUTE]).toBe(
4369
+ CONNECTION_ID.toString(),
4370
+ );
4371
+ expect(attributes["oneuptime.security_connection.provider"]).toBe(
4372
+ "google-secops",
4373
+ );
4374
+ expect(updateCall().data["cursor"]).toBe(result.windowEnd);
4375
+ });
4376
+
4377
+ test("a detection already imported by the retired poller counts as a duplicate, not a new row", async () => {
4378
+ existingUids = new Set(["de_1"]);
4379
+ const fake: FakeConnector = makeFakeConnector({
4380
+ fetch: makeFetchResult([makeEvent("de_1"), makeEvent("de_2")]),
4381
+ });
4382
+
4383
+ const result: SecurityEventConnectionRunResult =
4384
+ await SecurityEventConnectionPoller.executeConnection(
4385
+ googleConnection(),
4386
+ { type: "poll" },
4387
+ googleOverrides(fake),
4388
+ );
4389
+
4390
+ expect(result.duplicateCount).toBe(1);
4391
+ expect(result.ingestedCount).toBe(1);
4392
+ expect(dedupeCalls[0]!.vendorName).toBe("Google");
4393
+ expect(dedupeCalls[0]!.productName).toBe("Google SecOps");
4394
+ });
4395
+
4396
+ test("a preview tells the connector it is a preview, so it can also read by detection time", async () => {
4397
+ const fake: FakeConnector = makeFakeConnector({
4398
+ fetch: makeFetchResult([makeEvent("de_1")], {
4399
+ details: { ...PROVIDER_DETAILS, basis: "detection-time" },
4400
+ }),
4401
+ });
4402
+
4403
+ const result: SecurityEventConnectionRunResult =
4404
+ await SecurityEventConnectionPoller.executeConnection(
4405
+ googleConnection(),
4406
+ historicalRun("preview"),
4407
+ googleOverrides(fake),
4408
+ );
4409
+
4410
+ expect(fake.fetchCalls[0]!.options.purpose).toBe("preview");
4411
+ expect(result.providerDetails).toEqual({
4412
+ ...PROVIDER_DETAILS,
4413
+ basis: "detection-time",
4414
+ });
4415
+ expect(SecurityEventService.insertJsonRows).not.toHaveBeenCalled();
4416
+ });
4417
+
4418
+ test("a budget-stopped Google SecOps read is partial with a warning summary and narrows the next window", async () => {
4419
+ const fake: FakeConnector = makeFakeConnector({
4420
+ fetch: makeFetchResult([makeEvent("de_1")], {
4421
+ complete: false,
4422
+ requestCount: 16,
4423
+ warnings: [
4424
+ "The alerts view was stopped by the request budget after 16 requests.",
4425
+ ],
4426
+ checks: [
4427
+ check("rule-detections-read", "pass"),
4428
+ check(
4429
+ "alerts-view-read",
4430
+ "warn",
4431
+ "Stopped by the request budget after 16 requests.",
4432
+ ),
4433
+ ],
4434
+ }),
4435
+ });
4436
+
4437
+ const result: SecurityEventConnectionRunResult =
4438
+ await SecurityEventConnectionPoller.executeConnection(
4439
+ googleConnection({
4440
+ cursor: new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS).toISOString(),
4441
+ }),
4442
+ { type: "poll" },
4443
+ googleOverrides(fake),
4444
+ );
4445
+
4446
+ expect(result.status).toBe("partial");
4447
+ expect(checkKeysAndStatuses(result)).toEqual([
4448
+ "configuration:pass",
4449
+ "rule-detections-read:pass",
4450
+ "alerts-view-read:warn",
4451
+ "read:warn",
4452
+ "import:pass",
4453
+ ]);
4454
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES / 2);
4455
+ expect("cursor" in updateCall().data).toBe(false);
4456
+ });
4457
+
4458
+ test("a Google SecOps timeout halves the chunk like any other provider's (accepted when it moved into the framework)", async () => {
4459
+ const fake: FakeConnector = makeFakeConnector({
4460
+ fetch: new Error(
4461
+ "Google SecOps token exchange timed out after 60 seconds with no response.",
4462
+ ),
4463
+ });
4464
+
4465
+ const result: SecurityEventConnectionRunResult =
4466
+ await SecurityEventConnectionPoller.executeConnection(
4467
+ googleConnection({
4468
+ cursor: new Date(Date.now() - 3 * 24 * 60 * MINUTE_MS).toISOString(),
4469
+ }),
4470
+ { type: "poll" },
4471
+ googleOverrides(fake),
4472
+ );
4473
+
4474
+ expect(result.status).toBe("failed");
4475
+ expect(result.nextChunkMinutes).toBe(MAX_CHUNK_MINUTES / 2);
4476
+ expect(result.warnings).toContain(
4477
+ `The source did not answer in time for this window; the next poll reads a ${MAX_CHUNK_MINUTES / 2} minute window from the same starting point.`,
4478
+ );
4479
+ expect(findCheck(result, "failure").name).toBe(
4480
+ "Read detections from Google SecOps",
4481
+ );
4482
+ });
4483
+ });