@oneuptime/common 14.0.2 → 14.0.5

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 (765) hide show
  1. package/Models/DatabaseModels/Alert.ts +55 -0
  2. package/Models/DatabaseModels/AlertFeed.ts +2 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +1650 -0
  4. package/Models/DatabaseModels/DatabaseServerEndpoint.ts +540 -0
  5. package/Models/DatabaseModels/DatabaseServerFeed.ts +625 -0
  6. package/Models/DatabaseModels/DatabaseServerLabelRule.ts +514 -0
  7. package/Models/DatabaseModels/DatabaseServerOwnerRule.ts +596 -0
  8. package/Models/DatabaseModels/DatabaseServerOwnerTeam.ts +491 -0
  9. package/Models/DatabaseModels/DatabaseServerOwnerUser.ts +490 -0
  10. package/Models/DatabaseModels/Incident.ts +55 -0
  11. package/Models/DatabaseModels/IncidentAlert.ts +540 -0
  12. package/Models/DatabaseModels/IncidentFeed.ts +2 -0
  13. package/Models/DatabaseModels/Index.ts +18 -0
  14. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +26 -0
  15. package/Models/DatabaseModels/Project.ts +60 -0
  16. package/Models/DatabaseModels/RumApplication.ts +52 -1
  17. package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
  18. package/Models/DatabaseModels/TelemetryException.ts +2 -0
  19. package/Models/DatabaseModels/UserProjectSsoConsent.ts +146 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.ts +673 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.ts +51 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  25. package/Server/Middleware/MultipartFormData.ts +1 -1
  26. package/Server/Middleware/TelemetryIngest.ts +15 -8
  27. package/Server/Services/AlertReminderRuleService.ts +100 -24
  28. package/Server/Services/DatabaseServerEndpointService.ts +895 -0
  29. package/Server/Services/DatabaseServerFeedService.ts +128 -0
  30. package/Server/Services/DatabaseServerLabelRuleEngineService.ts +340 -0
  31. package/Server/Services/DatabaseServerLabelRuleService.ts +14 -0
  32. package/Server/Services/DatabaseServerOwnerRuleEngineService.ts +425 -0
  33. package/Server/Services/DatabaseServerOwnerRuleService.ts +14 -0
  34. package/Server/Services/DatabaseServerOwnerTeamService.ts +180 -0
  35. package/Server/Services/DatabaseServerOwnerUserService.ts +173 -0
  36. package/Server/Services/DatabaseServerService.ts +4546 -0
  37. package/Server/Services/DatabaseService.ts +14 -8
  38. package/Server/Services/DockerResourceService.ts +43 -2
  39. package/Server/Services/IncidentAlertService.ts +1811 -0
  40. package/Server/Services/IncidentReminderRuleService.ts +103 -23
  41. package/Server/Services/IncidentService.ts +210 -10
  42. package/Server/Services/IncidentStateTimelineService.ts +24 -0
  43. package/Server/Services/Index.ts +18 -0
  44. package/Server/Services/InventoryItemService.ts +113 -5
  45. package/Server/Services/IoTDeviceService.ts +145 -37
  46. package/Server/Services/OpenTelemetryIngestService.ts +19 -0
  47. package/Server/Services/PodmanResourceService.ts +43 -2
  48. package/Server/Services/ScheduledMaintenanceReminderRuleService.ts +107 -23
  49. package/Server/Services/ServiceService.ts +1 -1
  50. package/Server/Services/TelemetryUsageBillingService.ts +29 -2
  51. package/Server/Services/UserProjectSsoConsentService.ts +74 -0
  52. package/Server/Services/UserService.ts +14 -1
  53. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +18 -0
  54. package/Server/Types/Database/QueryHelper.ts +30 -1
  55. package/Server/Utils/AI/Toolbox/ResourceTools.ts +69 -1
  56. package/Server/Utils/Database/AffectedResourceRelations.ts +6 -0
  57. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +188 -6
  58. package/Server/Utils/Monitor/MonitorAlert.ts +28 -1
  59. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +2262 -755
  60. package/Server/Utils/Monitor/MonitorIncident.ts +28 -1
  61. package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +268 -6
  62. package/Server/Utils/Monitor/MonitorResource.ts +47 -11
  63. package/Server/Utils/Monitor/MonitorResourceContext.ts +1 -0
  64. package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +239 -21
  65. package/Server/Utils/Monitor/PerSeriesResolutionRootCause.ts +43 -0
  66. package/Server/Utils/Monitor/SeriesResourceLabels.ts +291 -0
  67. package/Server/Utils/Monitor/SeriesResourceLinker.ts +111 -12
  68. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  69. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
  70. package/Server/Utils/StartServer.ts +26 -6
  71. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  72. package/Server/Utils/Telemetry/DatabaseEndpointDiscovery.ts +688 -0
  73. package/Server/Utils/Telemetry/EntityRegistry.ts +5 -1
  74. package/Server/Utils/Telemetry/LogExceptionExtractor.ts +18 -1
  75. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +175 -12
  76. package/Server/Utils/Telemetry/ResourceFacetResolver.ts +12 -0
  77. package/Server/Utils/Telemetry/ServiceDependencyDiscovery.ts +606 -36
  78. package/Server/Utils/Telemetry/Telemetry.ts +16 -0
  79. package/Server/Utils/Telemetry/TelemetryEntity.ts +22 -22
  80. package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +16 -10
  81. package/Tests/App/Accounts/SignedInRedirect.test.tsx +241 -0
  82. package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +221 -4
  83. package/Tests/App/Dashboard/AIInsightsResourcePageContext.test.ts +42 -0
  84. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +100 -0
  85. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +86 -0
  86. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +2 -0
  87. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +19 -0
  88. package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +14 -0
  89. package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
  90. package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
  91. package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
  92. package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
  93. package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
  94. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +26 -1
  95. package/Tests/App/Dashboard/DashboardAppSsoError.test.tsx +501 -0
  96. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +64 -1
  97. package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +178 -18
  98. package/Tests/App/Dashboard/DashboardMasterPageSsoRedirect.test.tsx +444 -0
  99. package/Tests/App/Dashboard/DashboardNavBarForeignHiddenRule.test.tsx +573 -0
  100. package/Tests/App/Dashboard/DatabaseChartAxes.test.tsx +431 -0
  101. package/Tests/App/Dashboard/DatabaseContainerDetailBadge.test.tsx +249 -0
  102. package/Tests/App/Dashboard/DatabaseDashboardE2EFixes.test.ts +681 -0
  103. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +1801 -0
  104. package/Tests/App/Dashboard/DatabaseFleetSummaryRefresh.test.tsx +280 -0
  105. package/Tests/App/Dashboard/DatabaseFormHelpers.test.tsx +321 -0
  106. package/Tests/App/Dashboard/DatabaseMetricMonitorLink.test.tsx +795 -0
  107. package/Tests/App/Dashboard/DatabaseOverviewHeader.test.tsx +580 -0
  108. package/Tests/App/Dashboard/DatabaseRecommendationsEngineMetrics.test.ts +270 -0
  109. package/Tests/App/Dashboard/DatabaseServerComponents.test.tsx +1486 -0
  110. package/Tests/App/Dashboard/DatabaseServerEngineMetricsFilter.test.ts +223 -0
  111. package/Tests/App/Dashboard/DatabaseServerPresentation.test.ts +435 -0
  112. package/Tests/App/Dashboard/DatabaseServerSummary.test.ts +186 -0
  113. package/Tests/App/Dashboard/DatabaseServerTelemetryQueries.test.ts +1553 -0
  114. package/Tests/App/Dashboard/DatabaseTableTruncation.test.tsx +326 -0
  115. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +512 -0
  116. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +774 -0
  117. package/Tests/App/Dashboard/DatabaseViewLayout.test.tsx +233 -0
  118. package/Tests/App/Dashboard/DatabaseWorkloadBadge.test.tsx +972 -0
  119. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +5 -0
  120. package/Tests/App/Dashboard/DiscoveryReviewUnnamedHostHint.test.tsx +1061 -0
  121. package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
  122. package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
  123. package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
  124. package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
  125. package/Tests/App/Dashboard/EventOverlayScope.test.ts +147 -0
  126. package/Tests/App/Dashboard/EventOverlayScopeHook.test.tsx +1 -0
  127. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
  128. package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
  129. package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
  130. package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
  131. package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
  132. package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
  133. package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
  134. package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
  135. package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
  136. package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
  137. package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
  138. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
  139. package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
  140. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
  141. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
  142. package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
  143. package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
  144. package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
  145. package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
  146. package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
  147. package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
  148. package/Tests/App/Dashboard/LogsResourceFacetChips.test.tsx +1 -0
  149. package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
  150. package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
  151. package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
  152. package/Tests/App/Dashboard/MetricsViewerDrillDown.test.tsx +310 -0
  153. package/Tests/App/Dashboard/MetricsViewerEntityScopedRows.test.tsx +495 -0
  154. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +132 -1
  155. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +5 -0
  156. package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
  157. package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
  158. package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
  159. package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
  160. package/Tests/App/Dashboard/ProductOrientation.test.tsx +4 -3
  161. package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
  162. package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
  163. package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
  164. package/Tests/App/Dashboard/RecommendationsLoadedContext.test.tsx +349 -0
  165. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +62 -0
  166. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
  167. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +14 -0
  168. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
  169. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
  170. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +2 -0
  171. package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
  172. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +1085 -0
  173. package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
  174. package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
  175. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
  176. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +10 -0
  177. package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
  178. package/Tests/App/Dashboard/TracesResourceFacets.test.tsx +2 -1
  179. package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
  180. package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
  181. package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
  182. package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
  183. package/Tests/App/StatusPage/StatusPageHeaderForeignHiddenRule.test.tsx +284 -0
  184. package/Tests/ClassStrings.ts +178 -0
  185. package/Tests/Fixtures/MobilePasskeyUser.json +1 -2
  186. package/Tests/ForeignHiddenRuleGuard.ts +2152 -0
  187. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +2250 -0
  188. package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
  189. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +24 -5
  190. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
  191. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  192. package/Tests/RenderedMarkup.ts +518 -0
  193. package/Tests/ResponsiveVisibility.test.ts +490 -4
  194. package/Tests/ResponsiveVisibility.ts +505 -52
  195. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  196. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +541 -0
  197. package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
  198. package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
  199. package/Tests/Server/Infrastructure/Postgres/AddUserProjectSsoConsentMigration.test.ts +234 -0
  200. package/Tests/Server/Services/ContainerSnapshotUpsertPostgres.test.ts +319 -0
  201. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +13 -0
  202. package/Tests/Server/Services/DatabaseServerEndpointService.test.ts +2033 -0
  203. package/Tests/Server/Services/DatabaseServerFeedAndOwnerServices.test.ts +529 -0
  204. package/Tests/Server/Services/DatabaseServerLifecycle.test.ts +3302 -0
  205. package/Tests/Server/Services/DatabaseServerRegistries.test.ts +603 -0
  206. package/Tests/Server/Services/DatabaseServerRuleEngines.test.ts +754 -0
  207. package/Tests/Server/Services/DatabaseServerService.test.ts +3558 -0
  208. package/Tests/Server/Services/DatabaseServerSqlPostgres.test.ts +2045 -0
  209. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedPostgres.test.ts +362 -0
  210. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlockedReply.test.ts +210 -0
  211. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +18 -0
  212. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
  213. package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
  214. package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
  215. package/Tests/Server/Services/IncidentAlertSloLinkSurvivesEdit.test.ts +2 -0
  216. package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
  217. package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
  218. package/Tests/Server/Services/IncomingCallPolicyEscalationRuleTarget.test.ts +225 -0
  219. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +280 -2
  220. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +8 -2
  221. package/Tests/Server/Services/IoTDeviceLatestMetricsPostgres.test.ts +689 -0
  222. package/Tests/Server/Services/IoTDeviceService.test.ts +379 -0
  223. package/Tests/Server/Services/LatestMetricsValuesShape.test.ts +360 -0
  224. package/Tests/Server/Services/LlmModelPriceService.test.ts +402 -0
  225. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
  226. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +61 -9
  227. package/Tests/Server/Services/ReminderRuleLookupPostgres.test.ts +2250 -0
  228. package/Tests/Server/Services/ReminderRuleRefreshOpenSubjectsPaging.test.ts +653 -0
  229. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +68 -20
  230. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  231. package/Tests/Server/Services/TelemetryResourceFacetCounting.test.ts +9 -0
  232. package/Tests/Server/Services/UserProjectSsoConsentPostgres.test.ts +153 -0
  233. package/Tests/Server/Services/UserProjectSsoConsentService.test.ts +192 -0
  234. package/Tests/Server/Services/UserServiceFirstMasterAdminElection.test.ts +71 -0
  235. package/Tests/Server/Services/UserSignupIdTakeoverPostgres.test.ts +238 -0
  236. package/Tests/Server/Services/WorkspaceNotificationSummaryHelpers.test.ts +592 -0
  237. package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +3 -0
  238. package/Tests/Server/Types/Database/EventOverlayScopePostgres.test.ts +1 -0
  239. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +11 -0
  240. package/Tests/Server/Types/Database/Permissions/InfrastructureOwnershipScope.test.ts +8 -0
  241. package/Tests/Server/Types/Database/QueryHelperBooleanForCriteriaBackedRule.test.ts +1094 -0
  242. package/Tests/Server/Utils/AI/ResourceTools.test.ts +134 -0
  243. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +41 -0
  244. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +279 -9
  245. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaDisplayContext.test.ts +933 -0
  246. package/Tests/Server/Utils/Monitor/DatabaseMaintenanceSuppression.test.ts +582 -0
  247. package/Tests/Server/Utils/Monitor/DatabaseServerMonitorLinking.test.ts +753 -0
  248. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +1 -0
  249. package/Tests/Server/Utils/Monitor/MonitorAlertCriteriaReferences.test.ts +1 -0
  250. package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +119 -0
  251. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +60 -25
  252. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorAffectedResourceEdgeCases.test.ts +1719 -0
  253. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesAffectedResources.test.ts +1447 -0
  254. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorKubernetesRatioEmail.test.ts +352 -0
  255. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformMetricUnits.test.ts +717 -0
  256. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformRawScan.test.ts +2125 -0
  257. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorPlatformSeriesAffectedResources.test.ts +1288 -0
  258. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
  259. package/Tests/Server/Utils/Monitor/MonitorIncidentCriteriaReferences.test.ts +1 -0
  260. package/Tests/Server/Utils/Monitor/MonitorIncidentMemberRoleMembership.test.ts +1 -0
  261. package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +88 -0
  262. package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +205 -0
  263. package/Tests/Server/Utils/Monitor/MonitorResourceContext.test.ts +62 -0
  264. package/Tests/Server/Utils/Monitor/MonitorResourceDatabaseMaintenance.test.ts +803 -0
  265. package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +133 -3
  266. package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
  267. package/Tests/Server/Utils/Monitor/PerSeriesRecoveryResolution.test.ts +472 -1
  268. package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
  269. package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +236 -2
  270. package/Tests/Server/Utils/SeriesResourceLabels.test.ts +70 -0
  271. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
  272. package/Tests/Server/Utils/StartServerProtobufContentType.test.ts +54 -0
  273. package/Tests/Server/Utils/Telemetry/DatabaseEndpointDiscovery.test.ts +1895 -0
  274. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +7 -0
  275. package/Tests/Server/Utils/Telemetry/LogExceptionExtractorSeverityGate.test.ts +90 -0
  276. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +570 -0
  277. package/Tests/Server/Utils/Telemetry/ResourceFacetResolver.test.ts +97 -2
  278. package/Tests/Server/Utils/Telemetry/ServiceDependencyDiscovery.test.ts +1337 -0
  279. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +295 -0
  280. package/Tests/Types/AI/AIResourceContext.test.ts +30 -0
  281. package/Tests/Types/DatabaseServer/DatabaseContainerClassifier.test.ts +3644 -0
  282. package/Tests/Types/DatabaseServer/DatabaseContainerCommand.test.ts +340 -0
  283. package/Tests/Types/DatabaseServer/DatabaseContainerCommandCorpus.ts +826 -0
  284. package/Tests/Types/DatabaseServer/DatabaseContainerCommandPostgres.test.ts +150 -0
  285. package/Tests/Types/DatabaseServer/DatabaseEndpoint.test.ts +2657 -0
  286. package/Tests/Types/DatabaseServer/DatabaseServerDiscoverySource.test.ts +64 -0
  287. package/Tests/Types/DatabaseServer/DatabaseServerEntityKeys.test.ts +952 -0
  288. package/Tests/Types/DatabaseServer/DatabaseServerMetricCatalog.test.ts +1161 -0
  289. package/Tests/Types/DatabaseServer/DatabaseSystem.test.ts +1514 -0
  290. package/Tests/Types/DatabaseServer/DatabaseTelemetryResolver.test.ts +1558 -0
  291. package/Tests/Types/Monitor/CephAlertTemplates.test.ts +255 -6
  292. package/Tests/Types/Monitor/DatabaseAlertTemplates.test.ts +2254 -0
  293. package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +298 -0
  294. package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +271 -0
  295. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +253 -9
  296. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +13 -5
  297. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +4 -2
  298. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +111 -2
  299. package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +61 -0
  300. package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +114 -0
  301. package/Tests/Types/Monitor/Utils/KubernetesAgentEmittedMetrics.ts +181 -0
  302. package/Tests/Types/NetworkDevice/DiscoveredHostNamingStatus.test.ts +401 -0
  303. package/Tests/Types/Rum/WebVitals.test.ts +173 -0
  304. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +4 -1
  305. package/Tests/Types/Telemetry/ResourceFacetCatalog.test.ts +24 -2
  306. package/Tests/UI/Components/Card.test.tsx +93 -2
  307. package/Tests/UI/Components/FeedOptions.test.ts +6 -0
  308. package/Tests/UI/Components/FeedOptionsButton.test.tsx +8 -1
  309. package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
  310. package/Tests/UI/Components/ForeignHiddenRuleResilience.test.tsx +1147 -0
  311. package/Tests/UI/Components/HeaderRightRail.test.tsx +41 -1
  312. package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
  313. package/Tests/UI/Components/LogsEntityNames.test.ts +16 -1
  314. package/Tests/UI/Components/LogsFacetSidebarHiddenFacets.test.tsx +7 -3
  315. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
  316. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
  317. package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
  318. package/Tests/UI/Components/ResourceFacetConfigs.test.ts +2 -1
  319. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  320. package/Tests/UI/Components/SrOnlyForeignRule.test.tsx +994 -0
  321. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +34 -1
  322. package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
  323. package/Tests/UI/Components/TelemetryFacetSidebarHiddenFacets.test.tsx +4 -1
  324. package/Tests/UI/ForeignHiddenRuleGuard.test.ts +1951 -0
  325. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
  326. package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
  327. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
  328. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
  329. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +19 -1
  330. package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
  331. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
  332. package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
  333. package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
  334. package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
  335. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
  336. package/Tests/UI/Rum/ReplayTabSwitcher.test.tsx +19 -1
  337. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
  338. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
  339. package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
  340. package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
  341. package/Tests/UI/TailwindPlayCdnForeignHiddenRule.test.ts +1005 -0
  342. package/Tests/UI/ThemeGroupHoverReveal.test.ts +847 -0
  343. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +80 -0
  344. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +23 -0
  345. package/Tests/UI/Utils/ModelAPISsoRedirect.test.ts +127 -0
  346. package/Tests/UI/Utils/Navigation.test.ts +102 -0
  347. package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
  348. package/Tests/UI/Utils/Telemetry/TelemetryEntityNames.test.ts +23 -0
  349. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
  350. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  351. package/Tests/Utils/MetricUnitUtil.test.ts +227 -2
  352. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +288 -2
  353. package/Tests/Utils/Monitor/PlatformResourceIdentityAndUnits.test.ts +1559 -0
  354. package/Tests/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.test.ts +2546 -0
  355. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +315 -0
  356. package/Tests/Utils/NetworkDiscovery/DiscoveryReverseDnsChain.test.ts +336 -1
  357. package/Tests/Utils/NetworkDiscovery/NetbiosNameUtil.test.ts +244 -0
  358. package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
  359. package/Tests/Utils/ValueFormatter.test.ts +50 -0
  360. package/Tests/tsconfig.json +16 -0
  361. package/Types/AI/AIResourceContext.ts +7 -0
  362. package/Types/Dashboard/DashboardTemplates.ts +2 -1
  363. package/Types/DatabaseServer/DatabaseConnectionSpan.ts +77 -0
  364. package/Types/DatabaseServer/DatabaseContainerClassifier.ts +2068 -0
  365. package/Types/DatabaseServer/DatabaseContainerCommand.ts +801 -0
  366. package/Types/DatabaseServer/DatabaseEndpoint.ts +1943 -0
  367. package/Types/DatabaseServer/DatabaseServerDiscoverySource.ts +68 -0
  368. package/Types/DatabaseServer/DatabaseServerMetricCatalog.ts +966 -0
  369. package/Types/DatabaseServer/DatabaseSystem.ts +2124 -0
  370. package/Types/DatabaseServer/DatabaseTelemetryResolver.ts +555 -0
  371. package/Types/Email/EmailTemplateType.ts +1 -0
  372. package/Types/Incident/IncidentAlertLink.ts +38 -0
  373. package/Types/Monitor/CephAlertTemplates.ts +18 -0
  374. package/Types/Monitor/DatabaseAlertTemplates.ts +2020 -0
  375. package/Types/Monitor/KubernetesAlertTemplates.ts +64 -24
  376. package/Types/Monitor/KubernetesMetricCatalog.ts +118 -19
  377. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +14 -0
  378. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +93 -10
  379. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +109 -8
  380. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +36 -7
  381. package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +43 -2
  382. package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +41 -6
  383. package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +26 -0
  384. package/Types/NetworkDevice/DiscoveredHostNamingStatus.ts +147 -0
  385. package/Types/Permission.ts +315 -0
  386. package/Types/Probe/ProbeApiIngestResponse.ts +8 -0
  387. package/Types/Rules/RuleCriteriaFieldRegistry.ts +10 -0
  388. package/Types/Rules/RuleRun.ts +12 -0
  389. package/Types/Rum/SessionReplay.ts +25 -6
  390. package/Types/Rum/WebVitals.ts +52 -0
  391. package/Types/Telemetry/EntityType.ts +23 -0
  392. package/Types/Telemetry/ResourceFacetCatalog.ts +14 -0
  393. package/Types/Telemetry/ServiceType.ts +8 -0
  394. package/UI/Components/Banner/Banner.tsx +1 -1
  395. package/UI/Components/Card/Card.tsx +1 -1
  396. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +2 -2
  397. package/UI/Components/Charts/Line/LineChart.tsx +7 -0
  398. package/UI/Components/CommandPalette/CommandPalette.tsx +1 -1
  399. package/UI/Components/Date/StartAndEndDate.tsx +5 -5
  400. package/UI/Components/Detail/FieldLabel.tsx +8 -0
  401. package/UI/Components/Diagram/HorizontalStepChain.tsx +1 -1
  402. package/UI/Components/Feed/FeedOptionsButton.tsx +1 -1
  403. package/UI/Components/Forms/BasicForm.tsx +1 -1
  404. package/UI/Components/Header/Header.tsx +1 -1
  405. package/UI/Components/Header/HeaderIconDropdownButton.tsx +1 -1
  406. package/UI/Components/HeaderAlert/HeaderAlert.tsx +1 -1
  407. package/UI/Components/InfoCard/InfoCard.tsx +47 -1
  408. package/UI/Components/LogsViewer/components/FacetValueRow.tsx +1 -1
  409. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +1 -1
  410. package/UI/Components/Modal/ModalFooter.tsx +1 -1
  411. package/UI/Components/ModelDetail/CardModelDetail.tsx +2 -2
  412. package/UI/Components/ModelTable/BaseModelTable.tsx +99 -21
  413. package/UI/Components/ModelTable/Column.ts +5 -0
  414. package/UI/Components/Navbar/NavBar.tsx +9 -2
  415. package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -2
  416. package/UI/Components/Page/Page.tsx +1 -1
  417. package/UI/Components/Pagination/Pagination.tsx +3 -3
  418. package/UI/Components/ProgressButtons/ProgressButtonItem.tsx +1 -1
  419. package/UI/Components/SideMenu/SideMenu.tsx +4 -1
  420. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +4 -2
  421. package/UI/Components/StatusPage/ResourceList.tsx +6 -6
  422. package/UI/Components/Table/TableHeader.tsx +47 -2
  423. package/UI/Components/Table/Types/Column.ts +7 -0
  424. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +1 -1
  425. package/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.tsx +1 -1
  426. package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
  427. package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +1 -1
  428. package/UI/Components/Workflow/ComponentsModal.tsx +2 -2
  429. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  430. package/UI/Styles/Theme.css +8 -1
  431. package/UI/Utils/Navigation.ts +89 -42
  432. package/UI/Utils/PermissionGate.ts +11 -2
  433. package/UI/Utils/Telemetry/TelemetryEntityNames.ts +7 -0
  434. package/UI/Utils/TelemetryService.ts +4 -3
  435. package/Utils/Incident/IncidentFromAlerts.ts +516 -0
  436. package/Utils/LabelRuleImportExport.ts +2 -0
  437. package/Utils/MetricUnitUtil.ts +103 -11
  438. package/Utils/Monitor/MetricValueFormatter.ts +66 -13
  439. package/Utils/Monitor/PlatformMetricUnitUtil.ts +215 -0
  440. package/Utils/Monitor/PlatformResourceIdentity.ts +514 -0
  441. package/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.ts +425 -0
  442. package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +32 -0
  443. package/Utils/NetworkDiscovery/NetbiosNameUtil.ts +73 -0
  444. package/Utils/Rum/SessionTraceState.ts +212 -0
  445. package/Utils/Telemetry/DatabaseServerEntityKeys.ts +275 -0
  446. package/Utils/Telemetry/EntityKey.ts +176 -0
  447. package/Utils/ValueFormatter.ts +16 -0
  448. package/build/dist/Models/DatabaseModels/Alert.js +54 -0
  449. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  450. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
  451. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  452. package/build/dist/Models/DatabaseModels/DatabaseServer.js +1683 -0
  453. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -0
  454. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js +559 -0
  455. package/build/dist/Models/DatabaseModels/DatabaseServerEndpoint.js.map +1 -0
  456. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js +648 -0
  457. package/build/dist/Models/DatabaseModels/DatabaseServerFeed.js.map +1 -0
  458. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js +522 -0
  459. package/build/dist/Models/DatabaseModels/DatabaseServerLabelRule.js.map +1 -0
  460. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js +603 -0
  461. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerRule.js.map +1 -0
  462. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js +506 -0
  463. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerTeam.js.map +1 -0
  464. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js +505 -0
  465. package/build/dist/Models/DatabaseModels/DatabaseServerOwnerUser.js.map +1 -0
  466. package/build/dist/Models/DatabaseModels/Incident.js +54 -0
  467. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  468. package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
  469. package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
  470. package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
  471. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  472. package/build/dist/Models/DatabaseModels/Index.js +18 -0
  473. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  474. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  475. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  476. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  477. package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
  478. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  479. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
  480. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  481. package/build/dist/Models/DatabaseModels/TelemetryException.js +2 -0
  482. package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
  483. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js +162 -0
  484. package/build/dist/Models/DatabaseModels/UserProjectSsoConsent.js.map +1 -0
  485. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
  486. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
  487. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
  488. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
  489. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js +240 -0
  490. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795000000000-AddDatabaseServerTables.js.map +1 -0
  491. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js +26 -0
  492. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795100000000-AddUserProjectSsoConsent.js.map +1 -0
  493. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  494. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  495. package/build/dist/Server/Middleware/MultipartFormData.js +1 -1
  496. package/build/dist/Server/Middleware/TelemetryIngest.js +15 -8
  497. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  498. package/build/dist/Server/Services/AlertReminderRuleService.js +72 -23
  499. package/build/dist/Server/Services/AlertReminderRuleService.js.map +1 -1
  500. package/build/dist/Server/Services/DatabaseServerEndpointService.js +694 -0
  501. package/build/dist/Server/Services/DatabaseServerEndpointService.js.map +1 -0
  502. package/build/dist/Server/Services/DatabaseServerFeedService.js +110 -0
  503. package/build/dist/Server/Services/DatabaseServerFeedService.js.map +1 -0
  504. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js +272 -0
  505. package/build/dist/Server/Services/DatabaseServerLabelRuleEngineService.js.map +1 -0
  506. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js +13 -0
  507. package/build/dist/Server/Services/DatabaseServerLabelRuleService.js.map +1 -0
  508. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js +340 -0
  509. package/build/dist/Server/Services/DatabaseServerOwnerRuleEngineService.js.map +1 -0
  510. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js +13 -0
  511. package/build/dist/Server/Services/DatabaseServerOwnerRuleService.js.map +1 -0
  512. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js +160 -0
  513. package/build/dist/Server/Services/DatabaseServerOwnerTeamService.js.map +1 -0
  514. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js +153 -0
  515. package/build/dist/Server/Services/DatabaseServerOwnerUserService.js.map +1 -0
  516. package/build/dist/Server/Services/DatabaseServerService.js +3272 -0
  517. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -0
  518. package/build/dist/Server/Services/DatabaseService.js +8 -4
  519. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  520. package/build/dist/Server/Services/DockerResourceService.js +30 -3
  521. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  522. package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
  523. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
  524. package/build/dist/Server/Services/IncidentReminderRuleService.js +73 -23
  525. package/build/dist/Server/Services/IncidentReminderRuleService.js.map +1 -1
  526. package/build/dist/Server/Services/IncidentService.js +164 -12
  527. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  528. package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
  529. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  530. package/build/dist/Server/Services/Index.js +18 -0
  531. package/build/dist/Server/Services/Index.js.map +1 -1
  532. package/build/dist/Server/Services/InventoryItemService.js +88 -5
  533. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  534. package/build/dist/Server/Services/IoTDeviceService.js +111 -24
  535. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  536. package/build/dist/Server/Services/OpenTelemetryIngestService.js +24 -9
  537. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  538. package/build/dist/Server/Services/PodmanResourceService.js +30 -3
  539. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  540. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js +75 -23
  541. package/build/dist/Server/Services/ScheduledMaintenanceReminderRuleService.js.map +1 -1
  542. package/build/dist/Server/Services/ServiceService.js +1 -1
  543. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  544. package/build/dist/Server/Services/TelemetryUsageBillingService.js +23 -2
  545. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  546. package/build/dist/Server/Services/UserProjectSsoConsentService.js +79 -0
  547. package/build/dist/Server/Services/UserProjectSsoConsentService.js.map +1 -0
  548. package/build/dist/Server/Services/UserService.js +13 -1
  549. package/build/dist/Server/Services/UserService.js.map +1 -1
  550. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +18 -0
  551. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  552. package/build/dist/Server/Types/Database/QueryHelper.js +26 -1
  553. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  554. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js +46 -0
  555. package/build/dist/Server/Utils/AI/Toolbox/ResourceTools.js.map +1 -1
  556. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js +6 -0
  557. package/build/dist/Server/Utils/Database/AffectedResourceRelations.js.map +1 -1
  558. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +134 -13
  559. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  560. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -6
  561. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  562. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +1489 -524
  563. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  564. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
  565. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  566. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +196 -6
  567. package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
  568. package/build/dist/Server/Utils/Monitor/MonitorResource.js +36 -9
  569. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  570. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +1 -0
  571. package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
  572. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +175 -17
  573. package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
  574. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js +29 -0
  575. package/build/dist/Server/Utils/Monitor/PerSeriesResolutionRootCause.js.map +1 -0
  576. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +211 -0
  577. package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
  578. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +82 -10
  579. package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
  580. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  581. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  582. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
  583. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  584. package/build/dist/Server/Utils/StartServer.js +20 -4
  585. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  586. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  587. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  588. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js +442 -0
  589. package/build/dist/Server/Utils/Telemetry/DatabaseEndpointDiscovery.js.map +1 -0
  590. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -1
  591. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  592. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js +15 -1
  593. package/build/dist/Server/Utils/Telemetry/LogExceptionExtractor.js.map +1 -1
  594. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +121 -5
  595. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  596. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js +12 -0
  597. package/build/dist/Server/Utils/Telemetry/ResourceFacetResolver.js.map +1 -1
  598. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js +438 -36
  599. package/build/dist/Server/Utils/Telemetry/ServiceDependencyDiscovery.js.map +1 -1
  600. package/build/dist/Server/Utils/Telemetry/Telemetry.js +12 -0
  601. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  602. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +17 -18
  603. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  604. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +16 -10
  605. package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -1
  606. package/build/dist/Types/AI/AIResourceContext.js +7 -0
  607. package/build/dist/Types/AI/AIResourceContext.js.map +1 -1
  608. package/build/dist/Types/Dashboard/DashboardTemplates.js +2 -1
  609. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  610. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js +71 -0
  611. package/build/dist/Types/DatabaseServer/DatabaseConnectionSpan.js.map +1 -0
  612. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js +1462 -0
  613. package/build/dist/Types/DatabaseServer/DatabaseContainerClassifier.js.map +1 -0
  614. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js +691 -0
  615. package/build/dist/Types/DatabaseServer/DatabaseContainerCommand.js.map +1 -0
  616. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js +1407 -0
  617. package/build/dist/Types/DatabaseServer/DatabaseEndpoint.js.map +1 -0
  618. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js +60 -0
  619. package/build/dist/Types/DatabaseServer/DatabaseServerDiscoverySource.js.map +1 -0
  620. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js +804 -0
  621. package/build/dist/Types/DatabaseServer/DatabaseServerMetricCatalog.js.map +1 -0
  622. package/build/dist/Types/DatabaseServer/DatabaseSystem.js +1860 -0
  623. package/build/dist/Types/DatabaseServer/DatabaseSystem.js.map +1 -0
  624. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js +406 -0
  625. package/build/dist/Types/DatabaseServer/DatabaseTelemetryResolver.js.map +1 -0
  626. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  627. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  628. package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
  629. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
  630. package/build/dist/Types/Monitor/CephAlertTemplates.js +18 -0
  631. package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
  632. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js +1518 -0
  633. package/build/dist/Types/Monitor/DatabaseAlertTemplates.js.map +1 -0
  634. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +57 -24
  635. package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
  636. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +104 -18
  637. package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
  638. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +80 -3
  639. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  640. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +10 -3
  641. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  642. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +29 -0
  643. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
  644. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +21 -6
  645. package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -1
  646. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +25 -0
  647. package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
  648. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js +136 -0
  649. package/build/dist/Types/NetworkDevice/DiscoveredHostNamingStatus.js.map +1 -0
  650. package/build/dist/Types/Permission.js +276 -0
  651. package/build/dist/Types/Permission.js.map +1 -1
  652. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +10 -0
  653. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  654. package/build/dist/Types/Rules/RuleRun.js +4 -0
  655. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  656. package/build/dist/Types/Rum/WebVitals.js +25 -0
  657. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  658. package/build/dist/Types/Telemetry/EntityType.js +23 -0
  659. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  660. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js +14 -0
  661. package/build/dist/Types/Telemetry/ResourceFacetCatalog.js.map +1 -1
  662. package/build/dist/Types/Telemetry/ServiceType.js +8 -0
  663. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
  664. package/build/dist/UI/Components/Banner/Banner.js +1 -1
  665. package/build/dist/UI/Components/Banner/Banner.js.map +1 -1
  666. package/build/dist/UI/Components/Card/Card.js +1 -1
  667. package/build/dist/UI/Components/Card/Card.js.map +1 -1
  668. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  669. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  670. package/build/dist/UI/Components/Charts/Line/LineChart.js +1 -1
  671. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  672. package/build/dist/UI/Components/CommandPalette/CommandPalette.js +1 -1
  673. package/build/dist/UI/Components/CommandPalette/CommandPalette.js.map +1 -1
  674. package/build/dist/UI/Components/Date/StartAndEndDate.js +5 -5
  675. package/build/dist/UI/Components/Date/StartAndEndDate.js.map +1 -1
  676. package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
  677. package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
  678. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js +1 -1
  679. package/build/dist/UI/Components/Diagram/HorizontalStepChain.js.map +1 -1
  680. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +1 -1
  681. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -1
  682. package/build/dist/UI/Components/Forms/BasicForm.js +1 -1
  683. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  684. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  685. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  686. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js +1 -1
  687. package/build/dist/UI/Components/HeaderAlert/HeaderAlert.js.map +1 -1
  688. package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
  689. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  690. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js +1 -1
  691. package/build/dist/UI/Components/LogsViewer/components/FacetValueRow.js.map +1 -1
  692. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +1 -1
  693. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  694. package/build/dist/UI/Components/Modal/ModalFooter.js +1 -1
  695. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  696. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +2 -2
  697. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  698. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +70 -18
  699. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  700. package/build/dist/UI/Components/Navbar/NavBar.js +9 -2
  701. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  702. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -2
  703. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  704. package/build/dist/UI/Components/Page/Page.js +1 -1
  705. package/build/dist/UI/Components/Page/Page.js.map +1 -1
  706. package/build/dist/UI/Components/Pagination/Pagination.js +3 -3
  707. package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
  708. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js +1 -1
  709. package/build/dist/UI/Components/ProgressButtons/ProgressButtonItem.js.map +1 -1
  710. package/build/dist/UI/Components/SideMenu/SideMenu.js +4 -1
  711. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  712. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +3 -1
  713. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -1
  714. package/build/dist/UI/Components/StatusPage/ResourceList.js +6 -6
  715. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  716. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  717. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  718. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +1 -1
  719. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  720. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js +1 -1
  721. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetValueRow.js.map +1 -1
  722. package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
  723. package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
  724. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +1 -1
  725. package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -1
  726. package/build/dist/UI/Components/Workflow/ComponentsModal.js +2 -2
  727. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  728. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  729. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  730. package/build/dist/UI/Utils/Navigation.js +66 -33
  731. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  732. package/build/dist/UI/Utils/PermissionGate.js +4 -2
  733. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  734. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js +7 -0
  735. package/build/dist/UI/Utils/Telemetry/TelemetryEntityNames.js.map +1 -1
  736. package/build/dist/UI/Utils/TelemetryService.js +3 -2
  737. package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
  738. package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
  739. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
  740. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  741. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  742. package/build/dist/Utils/MetricUnitUtil.js +77 -13
  743. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  744. package/build/dist/Utils/Monitor/MetricValueFormatter.js +58 -13
  745. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -1
  746. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js +161 -0
  747. package/build/dist/Utils/Monitor/PlatformMetricUnitUtil.js.map +1 -0
  748. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js +323 -0
  749. package/build/dist/Utils/Monitor/PlatformResourceIdentity.js.map +1 -0
  750. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js +270 -0
  751. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostNamingDiagnosis.js.map +1 -0
  752. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +23 -0
  753. package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -1
  754. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js +61 -0
  755. package/build/dist/Utils/NetworkDiscovery/NetbiosNameUtil.js.map +1 -1
  756. package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
  757. package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
  758. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js +178 -0
  759. package/build/dist/Utils/Telemetry/DatabaseServerEntityKeys.js.map +1 -0
  760. package/build/dist/Utils/Telemetry/EntityKey.js +133 -0
  761. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  762. package/build/dist/Utils/ValueFormatter.js +15 -0
  763. package/build/dist/Utils/ValueFormatter.js.map +1 -1
  764. package/package.json +1 -1
  765. package/tsconfig.tests.json +7 -5
@@ -0,0 +1,1895 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
2
+ import {
3
+ CALLER_CLUSTER_ATTRIBUTE,
4
+ CALLER_NAMESPACE_ATTRIBUTE,
5
+ DATABASE_ENDPOINT_SQL_MARKER,
6
+ DATABASE_SERVER_MIN_CALLS_ENV,
7
+ DEFAULT_DATABASE_SERVER_MIN_CALLS,
8
+ DatabaseEndpointQueryWindow,
9
+ DatabaseEndpointRow,
10
+ DiscoveredDatabaseEndpoint,
11
+ NON_BLANK_TEXT_PATTERN,
12
+ ORDINAL_SUFFIX_PATTERN,
13
+ PLAIN_HOST_ADDRESS_PATTERN,
14
+ PRIVATE_IP_ADDRESS_PATTERN,
15
+ buildDatabaseEndpointSql,
16
+ databaseInstanceSql,
17
+ databaseQuerySpanSql,
18
+ firstNonEmptyAttributeSql,
19
+ getDatabaseEndpointCallerContextNeeds,
20
+ getDatabaseServerMinCalls,
21
+ getDiscoveredDatabaseEndpoints,
22
+ isDatabaseEndpointAutoCreateCandidate,
23
+ resolveDatabaseEndpointRows,
24
+ } from "../../../../Server/Utils/Telemetry/DatabaseEndpointDiscovery";
25
+ import { QUERY_SETTINGS } from "../../../../Server/Utils/Telemetry/ServiceDependencyDiscovery";
26
+ import {
27
+ DATABASE_CONNECTION_SPAN_NAMES,
28
+ isDatabaseConnectionSpanName,
29
+ } from "../../../../Types/DatabaseServer/DatabaseConnectionSpan";
30
+ import {
31
+ DatabaseCallerContext,
32
+ DatabaseEndpoint,
33
+ NETWORK_SCOPED_NAME_SUFFIXES,
34
+ canonicalizeDatabaseEndpoint,
35
+ formatDatabaseEndpoint,
36
+ readDatabaseInstanceName,
37
+ } from "../../../../Types/DatabaseServer/DatabaseEndpoint";
38
+ import {
39
+ DATABASE_ADDRESS_ATTRIBUTES,
40
+ DATABASE_PORT_ATTRIBUTES,
41
+ DATABASE_SYSTEM_ATTRIBUTES,
42
+ resolveDatabaseCallTarget,
43
+ } from "../../../../Types/DatabaseServer/DatabaseTelemetryResolver";
44
+
45
+ /*
46
+ * Client-span database discovery turns the DB CLIENT spans of the window
47
+ * into DatabaseServer rows. These tests pin the three things that decide
48
+ * whether it is right:
49
+ *
50
+ * 1. the query — CLIENT spans only, the ingest resolver's attribute
51
+ * precedence, caller context kept only where it changes the endpoint,
52
+ * bounded, and recognisable by its marker;
53
+ * 2. that the reduced caller context canonicalizes EXACTLY like the full
54
+ * one ingest used, so the endpoint a row is created under is the one
55
+ * whose key the spans carry;
56
+ * 3. row resolution and the conservative create policy.
57
+ */
58
+
59
+ const PROJECT_ID: string = "8a4c5b1e-2b3f-4c1d-9e8f-1a2b3c4d5e6f";
60
+
61
+ const WINDOW: DatabaseEndpointQueryWindow = {
62
+ projectId: PROJECT_ID,
63
+ startSql: "toDateTime64('2026-09-24 09:45:00.000000000', 9)",
64
+ endSql: "toDateTime64('2026-09-24 10:00:00.000000000', 9)",
65
+ maxRows: 321,
66
+ };
67
+
68
+ function collapse(sql: string): string {
69
+ return sql.replace(/\s+/g, " ");
70
+ }
71
+
72
+ function row(overrides: Partial<DatabaseEndpointRow>): DatabaseEndpointRow {
73
+ return {
74
+ dbSystem: "postgresql",
75
+ serverAddress: "orders.example.com",
76
+ serverPort: "",
77
+ callerNamespace: "",
78
+ callerCluster: "",
79
+ callCount: "25",
80
+ ...overrides,
81
+ };
82
+ }
83
+
84
+ function formatted(entries: Array<DiscoveredDatabaseEndpoint>): Array<string> {
85
+ return entries.map((entry: DiscoveredDatabaseEndpoint): string => {
86
+ return formatDatabaseEndpoint(entry.endpoint);
87
+ });
88
+ }
89
+
90
+ function discovered(
91
+ overrides: Partial<DiscoveredDatabaseEndpoint>,
92
+ ): DiscoveredDatabaseEndpoint {
93
+ return {
94
+ system: "postgresql",
95
+ endpoint: { host: "orders.example.com", port: 5432 },
96
+ scope: "global",
97
+ callCount: 50,
98
+ ...overrides,
99
+ };
100
+ }
101
+
102
+ describe("buildDatabaseEndpointSql", () => {
103
+ const sql: string = collapse(buildDatabaseEndpointSql(WINDOW));
104
+
105
+ test("carries a unique marker comment so the query is recognisable", () => {
106
+ expect(sql).toContain(`/* ${DATABASE_ENDPOINT_SQL_MARKER} */`);
107
+ // The dependency queries it runs next to never carry it.
108
+ expect(DATABASE_ENDPOINT_SQL_MARKER).toBe(
109
+ "oneuptime:database-endpoint-discovery",
110
+ );
111
+ });
112
+
113
+ test("reads CLIENT spans of one project inside the window", () => {
114
+ expect(sql).toContain("FROM oneuptime.SpanItemV3");
115
+ expect(sql).toContain(`WHERE projectId = '${PROJECT_ID}'`);
116
+ expect(sql).toContain(`AND startTime >= ${WINDOW.startSql}`);
117
+ expect(sql).toContain(`AND startTime < ${WINDOW.endSql}`);
118
+ expect(sql).toContain("AND kind = 'SPAN_KIND_CLIENT'");
119
+ expect(sql).not.toContain("SPAN_KIND_SERVER");
120
+ expect(sql).not.toContain("SPAN_KIND_PRODUCER");
121
+ });
122
+
123
+ test("keeps only spans that name a database system and an address", () => {
124
+ expect(sql).toContain("AND dbSystem != ''");
125
+ expect(sql).toContain("AND serverAddress != ''");
126
+ });
127
+
128
+ test("selects the system with the ingest resolver's precedence, stable name first", () => {
129
+ expect(DATABASE_SYSTEM_ATTRIBUTES).toEqual(["db.system.name", "db.system"]);
130
+ expect(sql).toContain(
131
+ "multiIf(attributes['db.system.name'] != '', attributes['db.system.name'], attributes['db.system'] != '', attributes['db.system'], '') AS dbSystem",
132
+ );
133
+ });
134
+
135
+ test("selects the address and the port in DATABASE_*_ATTRIBUTES order", () => {
136
+ expect(sql).toContain(
137
+ `${firstNonEmptyAttributeSql(DATABASE_ADDRESS_ATTRIBUTES)} AS serverAddress`,
138
+ );
139
+ expect(sql).toContain(
140
+ `${firstNonEmptyAttributeSql(DATABASE_PORT_ATTRIBUTES)} AS serverPort`,
141
+ );
142
+
143
+ const addressOrder: Array<number> = DATABASE_ADDRESS_ATTRIBUTES.map(
144
+ (key: string): number => {
145
+ return sql.indexOf(`attributes['${key}'] != ''`);
146
+ },
147
+ );
148
+ for (const index of addressOrder) {
149
+ expect(index).toBeGreaterThan(0);
150
+ }
151
+ expect(
152
+ [...addressOrder].sort((a: number, b: number): number => {
153
+ return a - b;
154
+ }),
155
+ ).toEqual(addressOrder);
156
+
157
+ const portOrder: Array<number> = DATABASE_PORT_ATTRIBUTES.map(
158
+ (key: string): number => {
159
+ return sql.indexOf(`attributes['${key}'] != ''`);
160
+ },
161
+ );
162
+ expect(
163
+ [...portOrder].sort((a: number, b: number): number => {
164
+ return a - b;
165
+ }),
166
+ ).toEqual(portOrder);
167
+ });
168
+
169
+ const PLAIN: string = `match(lower(serverAddress), '${PLAIN_HOST_ADDRESS_PATTERN}')`;
170
+ const LABELS: string =
171
+ "splitByChar('.', splitByChar(':', lower(serverAddress))[1])";
172
+
173
+ function column(alias: string): string {
174
+ const end: number = sql.indexOf(` AS ${alias}`);
175
+ const start: number = sql.lastIndexOf(" if(", end);
176
+ expect(end).toBeGreaterThan(0);
177
+ expect(start).toBeGreaterThan(0);
178
+ return sql.substring(start, end);
179
+ }
180
+
181
+ test("decides on the lowercased raw address: plain host[:port] or not", () => {
182
+ expect(PLAIN_HOST_ADDRESS_PATTERN).toBe(
183
+ "^[a-z0-9_-]+(?:[.][a-z0-9_-]+)*(?::[0-9]*)?$",
184
+ );
185
+ expect(sql).toContain(PLAIN);
186
+ });
187
+
188
+ test("keeps the caller's namespace for non-plain, single-label and StatefulSet-member addresses only", () => {
189
+ const namespace: string = column("callerNamespace");
190
+ expect(namespace).toContain(`NOT ${PLAIN}`);
191
+ expect(namespace).toContain(`OR length(${LABELS}) = 1`);
192
+ expect(namespace).toContain(
193
+ `OR (length(${LABELS}) = 2 AND match(${LABELS}[1], '${ORDINAL_SUFFIX_PATTERN}'))`,
194
+ );
195
+ expect(namespace).toContain(
196
+ `attributes['${CALLER_NAMESPACE_ATTRIBUTE}'], '' )`,
197
+ );
198
+ // The old "no dot anywhere in the raw value" test is gone.
199
+ expect(sql).not.toContain("position(serverAddress, '.') = 0");
200
+ });
201
+
202
+ test("keeps only a 'caller has a namespace' flag for plain two-label addresses", () => {
203
+ const flag: string = column("callerInKubernetes");
204
+ expect(flag).toContain(`${PLAIN} AND length(${LABELS}) = 2`);
205
+ expect(flag).toContain(
206
+ `match(attributes['${CALLER_NAMESPACE_ATTRIBUTE}'], '${NON_BLANK_TEXT_PATTERN.replace(
207
+ /\\/g,
208
+ "\\\\",
209
+ )}'), 0 )`,
210
+ );
211
+ });
212
+
213
+ test("keeps the caller's cluster for non-plain, short, Service DNS, private-zone and private / link-local IP addresses", () => {
214
+ const cluster: string = column("callerCluster");
215
+ expect(cluster).toContain(`NOT ${PLAIN}`);
216
+ expect(cluster).toContain(`OR length(${LABELS}) <= 2`);
217
+ expect(cluster).toContain(`OR has(${LABELS}, 'svc')`);
218
+ for (const suffix of NETWORK_SCOPED_NAME_SUFFIXES) {
219
+ expect(cluster).toContain(
220
+ `endsWith(splitByChar(':', lower(serverAddress))[1], '${suffix}')`,
221
+ );
222
+ }
223
+ expect(cluster).toContain("match(lower(serverAddress), '");
224
+ expect(cluster).toContain(
225
+ `attributes['${CALLER_CLUSTER_ATTRIBUTE}'], '' )`,
226
+ );
227
+ });
228
+
229
+ test("escapes the private-IP pattern for a ClickHouse string literal", () => {
230
+ // The one backslash in the pattern (the IPv6 bracket) must arrive doubled.
231
+ expect(PRIVATE_IP_ADDRESS_PATTERN).toContain("\\[");
232
+ expect(sql).toContain("|\\\\[)");
233
+ });
234
+
235
+ test("selects the SQL Server instance with readDatabaseInstanceName's precedence", () => {
236
+ expect(databaseInstanceSql()).toBe(
237
+ "multiIf(attributes['db.mssql.instance_name'] != '', attributes['db.mssql.instance_name'], position(attributes['db.namespace'], '|') > 0, splitByChar('|', attributes['db.namespace'])[1], '')",
238
+ );
239
+ expect(sql).toContain(`${databaseInstanceSql()} AS dbInstance`);
240
+ });
241
+
242
+ test("groups on exactly the endpoint and the caller context that matters", () => {
243
+ expect(sql).toContain(
244
+ "GROUP BY dbSystem, serverAddress, serverPort, dbInstance, callerNamespace, callerInKubernetes, callerCluster",
245
+ );
246
+ // Queries only - see "connection spans are not calls".
247
+ expect(sql).toContain(`countIf(${databaseQuerySpanSql()}) AS callCount`);
248
+ });
249
+
250
+ test("drops spans without a system or an address on attributeKeys before reading the map", () => {
251
+ expect(sql).toContain(
252
+ "AND hasAny(attributeKeys, ['db.system.name', 'db.system'])",
253
+ );
254
+ expect(sql).toContain(
255
+ `AND hasAny(attributeKeys, [${DATABASE_ADDRESS_ATTRIBUTES.map(
256
+ (key: string): string => {
257
+ return `'${key}'`;
258
+ },
259
+ ).join(", ")}])`,
260
+ );
261
+ // Before the map-derived filters.
262
+ expect(sql.indexOf("hasAny(attributeKeys")).toBeLessThan(
263
+ sql.indexOf("AND dbSystem != ''"),
264
+ );
265
+ });
266
+
267
+ test("never groups on anything that varies per caller instance", () => {
268
+ for (const perCaller of [
269
+ "k8s.pod.name",
270
+ "host.name",
271
+ "service.instance.id",
272
+ "container.id",
273
+ "primaryEntityId",
274
+ "traceId",
275
+ "spanId",
276
+ ]) {
277
+ expect(sql).not.toContain(perCaller);
278
+ }
279
+ });
280
+
281
+ test("is bounded: host-named rows first, then the busiest, row cap, query settings", () => {
282
+ expect(sql).toContain(
283
+ "ORDER BY (isIPv4String(splitByChar(':', lower(serverAddress))[1]) OR isIPv6String(lower(serverAddress)) OR isIPv6String(extract(lower(serverAddress), '^\\\\[([^\\\\]]*)\\\\]'))) ASC, callCount DESC LIMIT 321",
284
+ );
285
+ expect(sql.trim().endsWith(QUERY_SETTINGS)).toBe(true);
286
+ });
287
+
288
+ test("a nonsensical row cap still yields a bounded query", () => {
289
+ for (const maxRows of [0, -5, Number.NaN, Number.POSITIVE_INFINITY]) {
290
+ const capped: string = collapse(
291
+ buildDatabaseEndpointSql({ ...WINDOW, maxRows }),
292
+ );
293
+ expect(capped).toContain("LIMIT 1 ");
294
+ }
295
+ expect(
296
+ collapse(buildDatabaseEndpointSql({ ...WINDOW, maxRows: 12.9 })),
297
+ ).toContain("LIMIT 12 ");
298
+ });
299
+
300
+ test("escapes the project id", () => {
301
+ const escaped: string = buildDatabaseEndpointSql({
302
+ ...WINDOW,
303
+ projectId: "x' OR '1'='1",
304
+ });
305
+ expect(escaped).toContain("projectId = 'x\\' OR \\'1\\'=\\'1'");
306
+ });
307
+
308
+ test("never mentions the dependency queries' routing markers", () => {
309
+ // ComputeServiceDependencies tells its queries apart by these.
310
+ expect(sql).not.toContain("SELECT DISTINCT projectId");
311
+ expect(sql).not.toContain("NOT IN");
312
+ expect(sql).not.toContain("INNER JOIN");
313
+ });
314
+ });
315
+
316
+ /*
317
+ * The e2e run found rare-db.example.com created from 4 real queries: in its
318
+ * 15-minute window ClickHouse held 4 `pg.query:SELECT orders`, 4
319
+ * `pg-pool.connect` and 4 `pg.connect` CLIENT spans, every one carrying
320
+ * db.system.name + server.address, and `count()` read that as 12 calls. The
321
+ * ioredis `connect` span even carries `db.query.text = 'connect'`, so only
322
+ * the span name tells connection management from a query.
323
+ */
324
+ describe("connection spans are not calls", () => {
325
+ const sql: string = collapse(buildDatabaseEndpointSql(WINDOW));
326
+
327
+ test("the call count counts queries only, never connection spans", () => {
328
+ expect(sql).toContain(`countIf(${databaseQuerySpanSql()}) AS callCount`);
329
+ expect(sql).not.toContain("count() AS callCount");
330
+ });
331
+
332
+ test("connection spans still reach the query, so a row owning the endpoint is sighted", () => {
333
+ // No WHERE predicate on the name: only the count ignores them.
334
+ const where: string = sql.substring(sql.indexOf(" WHERE "));
335
+ expect(where).not.toContain("pg.connect");
336
+ });
337
+
338
+ test("the predicate compares the trimmed, lowercased name with every listed name", () => {
339
+ const predicate: string = databaseQuerySpanSql();
340
+ expect(predicate.startsWith("NOT has([")).toBe(true);
341
+ expect(predicate.endsWith("], lower(trimBoth(ifNull(name, ''))))")).toBe(
342
+ true,
343
+ );
344
+ for (const name of DATABASE_CONNECTION_SPAN_NAMES) {
345
+ expect(predicate).toContain(`'${name.toLowerCase()}'`);
346
+ }
347
+ expect(databaseQuerySpanSql("s.name")).toContain("ifNull(s.name, '')");
348
+ });
349
+
350
+ test.each<[string, boolean]>([
351
+ // node-postgres, as the e2e app emitted them.
352
+ ["pg.connect", true],
353
+ ["pg-pool.connect", true],
354
+ ["pg.query:SELECT orders", false],
355
+ // ioredis: `connect` is management; the handshake commands are commands.
356
+ ["connect", true],
357
+ ["info", false],
358
+ ["client", false],
359
+ ["auth", false],
360
+ ["ping", false],
361
+ // psycopg2 (Python), as the e2e billing-api emitted it.
362
+ ["SELECT", false],
363
+ // node-redis v4 / v5.
364
+ ["redis-connect", true],
365
+ ["redis-GET", false],
366
+ // node-oracledb: connect-time round trips are management; execute is a query.
367
+ ["oracledb.getConnection", true],
368
+ ["oracledb.Pool.getConnection", true],
369
+ ["oracledb.AuthMessage", true],
370
+ ["oracledb.Connection.execute:SELECT orders", false],
371
+ // Case and padding do not matter; a query on a table called connect is a query.
372
+ [" PG-POOL.CONNECT ", true],
373
+ ["SELECT connect", false],
374
+ ["", false],
375
+ ])("%s → connection span: %s", (name: string, expected: boolean) => {
376
+ expect(isDatabaseConnectionSpanName(name)).toBe(expected);
377
+ });
378
+
379
+ test("a span without a name is a query", () => {
380
+ expect(isDatabaseConnectionSpanName(undefined)).toBe(false);
381
+ expect(isDatabaseConnectionSpanName(null)).toBe(false);
382
+ });
383
+
384
+ test("the list is one spelling per name", () => {
385
+ const lowered: Array<string> = DATABASE_CONNECTION_SPAN_NAMES.map(
386
+ (name: string): string => {
387
+ return name.toLowerCase();
388
+ },
389
+ );
390
+ expect(new Set<string>(lowered).size).toBe(lowered.length);
391
+ });
392
+
393
+ test("the real rare-db window: 4 queries and their connects do not reach 10 calls", () => {
394
+ const window: Array<string> = [];
395
+ for (let run: number = 0; run < 4; run++) {
396
+ window.push("pg-pool.connect", "pg.connect", "pg.query:SELECT orders");
397
+ }
398
+ const queries: number = window.filter((name: string): boolean => {
399
+ return !isDatabaseConnectionSpanName(name);
400
+ }).length;
401
+
402
+ expect(window).toHaveLength(12);
403
+ expect(queries).toBe(4);
404
+ expect(
405
+ isDatabaseEndpointAutoCreateCandidate({
406
+ discovered: discovered({
407
+ endpoint: { host: "rare-db.example.com", port: 5432 },
408
+ callCount: queries,
409
+ }),
410
+ minCalls: DEFAULT_DATABASE_SERVER_MIN_CALLS,
411
+ }),
412
+ ).toBe(false);
413
+ });
414
+ });
415
+
416
+ describe("firstNonEmptyAttributeSql", () => {
417
+ test("falls through empty attributes to ''", () => {
418
+ expect(firstNonEmptyAttributeSql(["a", "b"])).toBe(
419
+ "multiIf(attributes['a'] != '', attributes['a'], attributes['b'] != '', attributes['b'], '')",
420
+ );
421
+ });
422
+
423
+ test("escapes attribute names", () => {
424
+ expect(firstNonEmptyAttributeSql(["it's"])).toBe(
425
+ "multiIf(attributes['it\\'s'] != '', attributes['it\\'s'], '')",
426
+ );
427
+ });
428
+ });
429
+
430
+ describe("PRIVATE_IP_ADDRESS_PATTERN (the regex the query runs)", () => {
431
+ const pattern: RegExp = new RegExp(PRIVATE_IP_ADDRESS_PATTERN);
432
+
433
+ const PRIVATE: Array<string> = [
434
+ "10.0.0.5",
435
+ "10.0.0.5:5432",
436
+ "010.000.000.005",
437
+ "172.16.0.1",
438
+ "172.31.255.255",
439
+ "192.168.1.20",
440
+ "100.64.0.1",
441
+ "100.127.0.1",
442
+ "169.254.1.10",
443
+ "169.254.169.254:5432",
444
+ "fd00::1",
445
+ "[fd12:3456::1]:5432",
446
+ "fc00::1",
447
+ "fe80::1",
448
+ "[fe80::1%eth0]:5432",
449
+ "febf::1",
450
+ "::ffff:10.0.0.5",
451
+ "0:0:0:0:0:ffff:a00:5",
452
+ "postgresql://user:pw@10.0.0.5:5432/db",
453
+ "jdbc:postgresql://192.168.0.9/app",
454
+ "mongodb://[fd00::5]:27017",
455
+ "tcp:10.1.2.3,1433",
456
+ ];
457
+
458
+ for (const address of PRIVATE) {
459
+ test(`matches private or link-local address ${address}`, () => {
460
+ expect(pattern.test(address.toLowerCase())).toBe(true);
461
+ });
462
+ }
463
+
464
+ const PUBLIC: Array<string> = [
465
+ "8.8.8.8",
466
+ "11.0.0.1",
467
+ "110.0.0.1",
468
+ "172.15.0.1",
469
+ "172.32.0.1",
470
+ "192.169.1.1",
471
+ "100.63.0.1",
472
+ "100.128.0.1",
473
+ "169.253.1.1",
474
+ "db.example.com",
475
+ "orders.cjd8.eu-west-1.rds.amazonaws.com:5432",
476
+ "2001:4860:4860::8888",
477
+ "fec0::1",
478
+ "db-10.example.com",
479
+ ];
480
+
481
+ for (const address of PUBLIC) {
482
+ test(`does not match public address ${address}`, () => {
483
+ expect(pattern.test(address.toLowerCase())).toBe(false);
484
+ });
485
+ }
486
+ });
487
+
488
+ describe("the reduced caller context canonicalizes exactly like the full one", () => {
489
+ /*
490
+ * Ingest resolves each span with the caller's FULL resource context; the
491
+ * query keeps only the parts getDatabaseEndpointCallerContextNeeds says
492
+ * can matter. If the two ever disagree the cron would create or match a
493
+ * row under an endpoint no span is keyed by — so prove they agree.
494
+ */
495
+ const ADDRESSES: Array<string> = [
496
+ "postgres",
497
+ "postgres:5433",
498
+ "POSTGRES",
499
+ "orders-db",
500
+ "orders.shop",
501
+ "Orders.Shop:6543",
502
+ "postgres.data",
503
+ "mongo-0.mongo-headless",
504
+ "redis-node-2.redis-headless:6379",
505
+ "postgres-2.data",
506
+ "db.internal",
507
+ "db.local",
508
+ "orders.shop.svc",
509
+ "orders.shop.svc.cluster.local",
510
+ "orders.shop.svc.cluster.local.",
511
+ "pg-0.pg-headless.shop.svc.cluster.local:5432",
512
+ "orders.shop.svc.corp.internal",
513
+ "ORDERS.SHOP.SVC",
514
+ "db.cluster.local",
515
+ "ip-10-0-0-5.ec2.internal",
516
+ "db-1.c.my-project.internal",
517
+ "nas.home.arpa",
518
+ "build-7.localdomain",
519
+ "host.minikube.internal",
520
+ "10.0.0.5",
521
+ "10.0.0.5:6543",
522
+ "010.000.000.005",
523
+ "172.20.1.1",
524
+ "192.168.4.4",
525
+ "100.100.1.1",
526
+ "169.254.1.10",
527
+ "8.8.8.8",
528
+ "34.120.1.9:5432",
529
+ "fd00::1",
530
+ "[fd00::1]:5432",
531
+ "FD12:3456::7",
532
+ "fe80::1%eth0",
533
+ "::ffff:10.0.0.5",
534
+ "::ffff:8.8.8.8",
535
+ "2001:db8::1",
536
+ "orders.cjd8.eu-west-1.rds.amazonaws.com",
537
+ "orders.cjd8.eu-west-1.rds.amazonaws.com:5432",
538
+ "postgresql://app:secret@orders.shop.svc:5432/orders",
539
+ "postgresql://app@postgres/orders",
540
+ "jdbc:mysql://10.2.3.4:3306/app",
541
+ "mongodb://mongo-0.mongo.shop.svc.cluster.local:27017,mongo-1:27017",
542
+ "tcp:10.9.9.9,1433",
543
+ "tcp:postgres",
544
+ "sqlserver\\INSTANCE",
545
+ "sql1.corp.example.com\\INST01",
546
+ // The dotless-predicate finding's four probes.
547
+ "postgres.",
548
+ "postgresql://svc.user@postgres:5432/db",
549
+ "mongo-a:27017,mongo-b.example.com:27017",
550
+ "jdbc:sqlserver://mssql:1433;databaseName=app.db",
551
+ // More values whose raw text has a dot the parsed host does not.
552
+ "postgres.:5432",
553
+ "postgres@db.example.com",
554
+ "postgres?sslmode=require.x",
555
+ " postgres ",
556
+ "postgres.data.",
557
+ "mongo-b.example.com:27017,mongo-a:27017",
558
+ "localhost",
559
+ "127.0.0.1:5432",
560
+ "host.docker.internal",
561
+ "[REDACTED]",
562
+ "***.***.***.***",
563
+ ];
564
+
565
+ const CALLERS: Array<DatabaseCallerContext> = [
566
+ {
567
+ kubernetesNamespace: "shop",
568
+ kubernetesClusterName: "prod-eu",
569
+ isEphemeral: true,
570
+ },
571
+ {
572
+ kubernetesNamespace: "billing",
573
+ kubernetesClusterName: "arn:aws:eks:eu-west-1:1234:cluster/Prod",
574
+ isEphemeral: true,
575
+ },
576
+ { kubernetesNamespace: "shop", isEphemeral: true },
577
+ { kubernetesNamespace: "data", isEphemeral: true },
578
+ { kubernetesClusterName: "prod-eu", isEphemeral: true },
579
+ { kubernetesNamespace: "Not A Namespace", isEphemeral: true },
580
+ {
581
+ kubernetesNamespace: " ",
582
+ kubernetesClusterName: " ",
583
+ isEphemeral: true,
584
+ },
585
+ { kubernetesNamespace: " ", isEphemeral: true },
586
+ { isEphemeral: false, hostName: "vm-1" },
587
+ ];
588
+
589
+ function nonBlank(value: string | null | undefined): boolean {
590
+ return typeof value === "string" && value.trim().length > 0;
591
+ }
592
+
593
+ for (const system of ["postgresql", "mysql", "mongodb", "redis", "mssql"]) {
594
+ for (const address of ADDRESSES) {
595
+ test(`${system} ${address}`, () => {
596
+ for (const caller of CALLERS) {
597
+ const needs: {
598
+ namespace: boolean;
599
+ kubernetes: boolean;
600
+ cluster: boolean;
601
+ } = getDatabaseEndpointCallerContextNeeds(address);
602
+
603
+ const full: DatabaseEndpoint | null = canonicalizeDatabaseEndpoint({
604
+ system,
605
+ address,
606
+ caller,
607
+ purpose: "client-call",
608
+ });
609
+
610
+ const reduced: DatabaseEndpoint | null = canonicalizeDatabaseEndpoint(
611
+ {
612
+ system,
613
+ address,
614
+ caller: {
615
+ kubernetesNamespace: needs.namespace
616
+ ? caller.kubernetesNamespace
617
+ : null,
618
+ kubernetesClusterName: needs.cluster
619
+ ? caller.kubernetesClusterName
620
+ : null,
621
+ runsInKubernetes:
622
+ needs.kubernetes && nonBlank(caller.kubernetesNamespace),
623
+ isEphemeral: true,
624
+ },
625
+ purpose: "client-call",
626
+ },
627
+ );
628
+
629
+ expect(reduced ? formatDatabaseEndpoint(reduced) : null).toBe(
630
+ full ? formatDatabaseEndpoint(full) : null,
631
+ );
632
+
633
+ // And the cron's row resolution lands on the ingest resolver's endpoint.
634
+ const ingest: { endpoint: DatabaseEndpoint } | null =
635
+ resolveDatabaseCallTarget({
636
+ getAttribute: (key: string): unknown => {
637
+ return (
638
+ {
639
+ "db.system.name": system,
640
+ "server.address": address,
641
+ } as Record<string, unknown>
642
+ )[key];
643
+ },
644
+ caller,
645
+ });
646
+ const cron: Array<DiscoveredDatabaseEndpoint> =
647
+ resolveDatabaseEndpointRows([
648
+ {
649
+ dbSystem: system,
650
+ serverAddress: address,
651
+ serverPort: "",
652
+ dbInstance: "",
653
+ callerNamespace: needs.namespace
654
+ ? caller.kubernetesNamespace || ""
655
+ : "",
656
+ callerInKubernetes:
657
+ needs.kubernetes && nonBlank(caller.kubernetesNamespace)
658
+ ? 1
659
+ : 0,
660
+ callerCluster: needs.cluster
661
+ ? caller.kubernetesClusterName || ""
662
+ : "",
663
+ callCount: 1,
664
+ },
665
+ ]);
666
+
667
+ expect(formatted(cron)).toEqual(
668
+ ingest ? [formatDatabaseEndpoint(ingest.endpoint)] : [],
669
+ );
670
+ }
671
+ });
672
+ }
673
+ }
674
+
675
+ test("the four probes of the dotless-predicate finding keep the context the host needs", () => {
676
+ const caller: DatabaseCallerContext = {
677
+ kubernetesNamespace: "prod",
678
+ kubernetesClusterName: "c1",
679
+ isEphemeral: true,
680
+ };
681
+ for (const [address, expected] of [
682
+ ["postgres.", "postgres.prod.svc.cluster.local:5432@c1"],
683
+ [
684
+ "postgresql://svc.user@postgres:5432/db",
685
+ "postgres.prod.svc.cluster.local:5432@c1",
686
+ ],
687
+ [
688
+ "mongo-a:27017,mongo-b.example.com:27017",
689
+ "mongo-a.prod.svc.cluster.local:27017@c1",
690
+ ],
691
+ [
692
+ "jdbc:sqlserver://mssql:1433;databaseName=app.db",
693
+ "mssql.prod.svc.cluster.local:1433@c1",
694
+ ],
695
+ ] as Array<[string, string]>) {
696
+ const needs: {
697
+ namespace: boolean;
698
+ kubernetes: boolean;
699
+ cluster: boolean;
700
+ } = getDatabaseEndpointCallerContextNeeds(address);
701
+ expect(needs.namespace).toBe(true);
702
+ expect(needs.cluster).toBe(true);
703
+ const system: string = address.includes("mongo")
704
+ ? "mongodb"
705
+ : address.includes("sqlserver")
706
+ ? "mssql"
707
+ : "postgresql";
708
+ expect(
709
+ formatted(
710
+ resolveDatabaseEndpointRows([
711
+ {
712
+ dbSystem: system,
713
+ serverAddress: address,
714
+ callerNamespace: caller.kubernetesNamespace || "",
715
+ callerCluster: caller.kubernetesClusterName || "",
716
+ callCount: 1,
717
+ },
718
+ ]),
719
+ ),
720
+ ).toEqual([expected]);
721
+ }
722
+ });
723
+ });
724
+
725
+ describe("grouping is bounded by construction", () => {
726
+ interface SimulatedSpan {
727
+ attributes: Record<string, string>;
728
+ }
729
+
730
+ // What the query's SELECT would produce for one span (JS twin of the SQL).
731
+ function groupKeyOf(span: SimulatedSpan): string {
732
+ const first: (keys: ReadonlyArray<string>) => string = (
733
+ keys: ReadonlyArray<string>,
734
+ ): string => {
735
+ for (const key of keys) {
736
+ const value: string | undefined = span.attributes[key];
737
+ if (value !== undefined && value !== "") {
738
+ return value;
739
+ }
740
+ }
741
+ return "";
742
+ };
743
+ const address: string = first(DATABASE_ADDRESS_ATTRIBUTES);
744
+ const needs: { namespace: boolean; kubernetes: boolean; cluster: boolean } =
745
+ getDatabaseEndpointCallerContextNeeds(address);
746
+ const namespace: string = span.attributes[CALLER_NAMESPACE_ATTRIBUTE] || "";
747
+ return [
748
+ first(DATABASE_SYSTEM_ATTRIBUTES),
749
+ address,
750
+ first(DATABASE_PORT_ATTRIBUTES),
751
+ needs.namespace ? namespace : "",
752
+ needs.kubernetes && namespace.trim() ? "1" : "0",
753
+ needs.cluster ? span.attributes[CALLER_CLUSTER_ATTRIBUTE] || "" : "",
754
+ ].join("|");
755
+ }
756
+
757
+ function callers(address: string, count: number): Array<SimulatedSpan> {
758
+ const spans: Array<SimulatedSpan> = [];
759
+ for (let index: number = 0; index < count; index++) {
760
+ spans.push({
761
+ attributes: {
762
+ "db.system.name": "postgresql",
763
+ "server.address": address,
764
+ "server.port": "5432",
765
+ [CALLER_NAMESPACE_ATTRIBUTE]: `ns-${index % 40}`,
766
+ [CALLER_CLUSTER_ATTRIBUTE]: `cluster-${index % 7}`,
767
+ "resource.k8s.pod.name": `api-${index}`,
768
+ "resource.host.name": `node-${index}`,
769
+ },
770
+ });
771
+ }
772
+ return spans;
773
+ }
774
+
775
+ test("1000 callers of one public endpoint collapse to ONE group", () => {
776
+ const keys: Set<string> = new Set<string>(
777
+ callers("orders.cjd8.eu-west-1.rds.amazonaws.com", 1000).map(groupKeyOf),
778
+ );
779
+ expect(keys.size).toBe(1);
780
+ });
781
+
782
+ test("1000 callers of one Service FQDN are one group per cluster, never per namespace or pod", () => {
783
+ const keys: Set<string> = new Set<string>(
784
+ callers("orders.shop.svc.cluster.local", 1000).map(groupKeyOf),
785
+ );
786
+ expect(keys.size).toBe(7);
787
+ });
788
+
789
+ test("1000 callers of one <service>.<namespace> short name are one group per cluster too", () => {
790
+ const keys: Set<string> = new Set<string>(
791
+ callers("postgres.data", 1000).map(groupKeyOf),
792
+ );
793
+ expect(keys.size).toBe(7);
794
+ });
795
+
796
+ test("1000 callers of one private IP are one group per cluster", () => {
797
+ const keys: Set<string> = new Set<string>(
798
+ callers("10.0.0.5", 1000).map(groupKeyOf),
799
+ );
800
+ expect(keys.size).toBe(7);
801
+ });
802
+
803
+ test("a single-label Service name keeps namespace and cluster — they name different databases", () => {
804
+ const keys: Set<string> = new Set<string>(
805
+ callers("postgres", 1000).map(groupKeyOf),
806
+ );
807
+ // 40 namespaces x 7 clusters, bounded by the caller estate, not its pods.
808
+ expect(keys.size).toBe(280);
809
+ });
810
+ });
811
+
812
+ describe("getDatabaseEndpointCallerContextNeeds", () => {
813
+ const CASES: Array<{
814
+ address: string;
815
+ namespace: boolean;
816
+ kubernetes: boolean;
817
+ cluster: boolean;
818
+ }> = [
819
+ { address: "postgres", namespace: true, kubernetes: false, cluster: true },
820
+ {
821
+ address: "postgres:5432",
822
+ namespace: true,
823
+ kubernetes: false,
824
+ cluster: true,
825
+ },
826
+ {
827
+ address: "postgres.data",
828
+ namespace: false,
829
+ kubernetes: true,
830
+ cluster: true,
831
+ },
832
+ {
833
+ address: "Postgres.Data:6543",
834
+ namespace: false,
835
+ kubernetes: true,
836
+ cluster: true,
837
+ },
838
+ {
839
+ address: "mongo-0.mongo-headless",
840
+ namespace: true,
841
+ kubernetes: true,
842
+ cluster: true,
843
+ },
844
+ {
845
+ address: "orders.shop.svc",
846
+ namespace: false,
847
+ kubernetes: false,
848
+ cluster: true,
849
+ },
850
+ {
851
+ address: "orders.shop.svc.cluster.local",
852
+ namespace: false,
853
+ kubernetes: false,
854
+ cluster: true,
855
+ },
856
+ {
857
+ address: "ORDERS.SHOP.SVC.CLUSTER.LOCAL",
858
+ namespace: false,
859
+ kubernetes: false,
860
+ cluster: true,
861
+ },
862
+ {
863
+ address: "db.cluster.local",
864
+ namespace: false,
865
+ kubernetes: false,
866
+ cluster: true,
867
+ },
868
+ {
869
+ address: "ip-10-0-0-5.ec2.internal",
870
+ namespace: false,
871
+ kubernetes: false,
872
+ cluster: true,
873
+ },
874
+ {
875
+ address: "nas.home.arpa",
876
+ namespace: false,
877
+ kubernetes: false,
878
+ cluster: true,
879
+ },
880
+ { address: "10.0.0.5", namespace: false, kubernetes: false, cluster: true },
881
+ {
882
+ address: "169.254.1.1",
883
+ namespace: false,
884
+ kubernetes: false,
885
+ cluster: true,
886
+ },
887
+ { address: "fd00::1", namespace: true, kubernetes: false, cluster: true },
888
+ {
889
+ address: "db.example.com",
890
+ namespace: false,
891
+ kubernetes: false,
892
+ cluster: false,
893
+ },
894
+ {
895
+ address: "db.internalapi.com",
896
+ namespace: false,
897
+ kubernetes: false,
898
+ cluster: false,
899
+ },
900
+ { address: "8.8.8.8", namespace: false, kubernetes: false, cluster: false },
901
+ // Not a plain host[:port]: everything is kept.
902
+ { address: "postgres.", namespace: true, kubernetes: false, cluster: true },
903
+ {
904
+ address: "postgresql://svc.user@postgres:5432/db",
905
+ namespace: true,
906
+ kubernetes: false,
907
+ cluster: true,
908
+ },
909
+ {
910
+ address: "a.example.com:5432,b.example.com:5432",
911
+ namespace: true,
912
+ kubernetes: false,
913
+ cluster: true,
914
+ },
915
+ {
916
+ address: "sql1.corp\\INST01",
917
+ namespace: true,
918
+ kubernetes: false,
919
+ cluster: true,
920
+ },
921
+ {
922
+ address: " db.example.com",
923
+ namespace: true,
924
+ kubernetes: false,
925
+ cluster: true,
926
+ },
927
+ { address: "", namespace: true, kubernetes: false, cluster: true },
928
+ ];
929
+
930
+ for (const entry of CASES) {
931
+ test(`"${entry.address}" → namespace ${entry.namespace}, kubernetes ${entry.kubernetes}, cluster ${entry.cluster}`, () => {
932
+ expect(getDatabaseEndpointCallerContextNeeds(entry.address)).toEqual({
933
+ namespace: entry.namespace,
934
+ kubernetes: entry.kubernetes,
935
+ cluster: entry.cluster,
936
+ });
937
+ });
938
+ }
939
+
940
+ test("lowercases ASCII only, as ClickHouse lower() does", () => {
941
+ // U+212A KELVIN SIGN lowercases to "k" in JavaScript but not in SQL.
942
+ expect(getDatabaseEndpointCallerContextNeeds("Kafka.data")).toEqual({
943
+ namespace: true,
944
+ kubernetes: false,
945
+ cluster: true,
946
+ });
947
+ });
948
+
949
+ test("tolerates a non-string address", () => {
950
+ expect(
951
+ getDatabaseEndpointCallerContextNeeds(undefined as unknown as string),
952
+ ).toEqual({ namespace: true, kubernetes: false, cluster: true });
953
+ });
954
+ });
955
+
956
+ describe("resolveDatabaseEndpointRows", () => {
957
+ test("canonicalizes the system and applies the engine's default port", () => {
958
+ const result: Array<DiscoveredDatabaseEndpoint> =
959
+ resolveDatabaseEndpointRows([
960
+ row({ dbSystem: "postgres", serverAddress: "Orders.Example.COM." }),
961
+ ]);
962
+
963
+ expect(result).toEqual([
964
+ {
965
+ system: "postgresql",
966
+ endpoint: { host: "orders.example.com", port: 5432 },
967
+ scope: "global",
968
+ callCount: 25,
969
+ },
970
+ ]);
971
+ });
972
+
973
+ test("an explicit port wins over the default, as a string or a number", () => {
974
+ expect(
975
+ formatted(
976
+ resolveDatabaseEndpointRows([
977
+ row({ serverAddress: "a.example.com", serverPort: "6432" }),
978
+ row({ serverAddress: "b.example.com", serverPort: 7432 }),
979
+ ]),
980
+ ).sort(),
981
+ ).toEqual(["a.example.com:6432", "b.example.com:7432"]);
982
+ });
983
+
984
+ test("a dotless Service name from a known namespace and cluster becomes a qualified FQDN", () => {
985
+ const result: Array<DiscoveredDatabaseEndpoint> =
986
+ resolveDatabaseEndpointRows([
987
+ row({
988
+ serverAddress: "orders-db",
989
+ callerNamespace: "shop",
990
+ callerCluster: "prod-eu",
991
+ }),
992
+ ]);
993
+
994
+ expect(formatted(result)).toEqual([
995
+ "orders-db.shop.svc.cluster.local:5432@prod-eu",
996
+ ]);
997
+ expect(result[0]!.scope).toBe("global");
998
+ });
999
+
1000
+ test("the same Service name without a cluster is LOCAL scope", () => {
1001
+ const result: Array<DiscoveredDatabaseEndpoint> =
1002
+ resolveDatabaseEndpointRows([
1003
+ row({ serverAddress: "orders-db", callerNamespace: "shop" }),
1004
+ ]);
1005
+
1006
+ expect(formatted(result)).toEqual([
1007
+ "orders-db.shop.svc.cluster.local:5432",
1008
+ ]);
1009
+ expect(result[0]!.scope).toBe("local");
1010
+ });
1011
+
1012
+ test("a bare single-label name with no namespace is LOCAL scope", () => {
1013
+ const result: Array<DiscoveredDatabaseEndpoint> =
1014
+ resolveDatabaseEndpointRows([row({ serverAddress: "postgres" })]);
1015
+
1016
+ expect(formatted(result)).toEqual(["postgres:5432"]);
1017
+ expect(result[0]!.scope).toBe("local");
1018
+ });
1019
+
1020
+ test("a private IP is qualified with the caller's cluster, else LOCAL", () => {
1021
+ const result: Array<DiscoveredDatabaseEndpoint> =
1022
+ resolveDatabaseEndpointRows([
1023
+ row({ serverAddress: "10.0.0.5", callerCluster: "prod-eu" }),
1024
+ row({ serverAddress: "10.0.0.6" }),
1025
+ ]);
1026
+
1027
+ const byEndpoint: Map<string, DiscoveredDatabaseEndpoint> = new Map(
1028
+ result.map((entry: DiscoveredDatabaseEndpoint) => {
1029
+ return [formatDatabaseEndpoint(entry.endpoint), entry];
1030
+ }),
1031
+ );
1032
+ expect(byEndpoint.get("10.0.0.5:5432@prod-eu")?.scope).toBe("global");
1033
+ expect(byEndpoint.get("10.0.0.6:5432")?.scope).toBe("local");
1034
+ });
1035
+
1036
+ test("drops loopback, host-relative, scrubbed and unparseable addresses", () => {
1037
+ expect(
1038
+ resolveDatabaseEndpointRows([
1039
+ row({ serverAddress: "localhost" }),
1040
+ row({ serverAddress: "127.0.0.1:5432" }),
1041
+ row({ serverAddress: "::1" }),
1042
+ row({ serverAddress: "host.docker.internal" }),
1043
+ row({ serverAddress: "[REDACTED]" }),
1044
+ row({ serverAddress: "***.***.***.***" }),
1045
+ row({ serverAddress: "/var/run/postgresql" }),
1046
+ row({ serverAddress: " " }),
1047
+ row({ serverAddress: "" }),
1048
+ row({ serverAddress: undefined }),
1049
+ ]),
1050
+ ).toEqual([]);
1051
+ });
1052
+
1053
+ test("drops rows without a system", () => {
1054
+ expect(
1055
+ resolveDatabaseEndpointRows([
1056
+ row({ dbSystem: "" }),
1057
+ row({ dbSystem: " " }),
1058
+ row({ dbSystem: undefined }),
1059
+ ]),
1060
+ ).toEqual([]);
1061
+ });
1062
+
1063
+ test("keeps an unknown engine (it is still keyed) under its lowercased name", () => {
1064
+ const result: Array<DiscoveredDatabaseEndpoint> =
1065
+ resolveDatabaseEndpointRows([
1066
+ row({
1067
+ dbSystem: "AcmeDB",
1068
+ serverAddress: "acme.example.com",
1069
+ serverPort: "9000",
1070
+ }),
1071
+ ]);
1072
+ expect(result).toHaveLength(1);
1073
+ expect(result[0]!.system).toBe("acmedb");
1074
+ expect(formatted(result)).toEqual(["acme.example.com:9000"]);
1075
+ });
1076
+
1077
+ test("rows that land on one endpoint merge into one entry with their calls summed", () => {
1078
+ const result: Array<DiscoveredDatabaseEndpoint> =
1079
+ resolveDatabaseEndpointRows([
1080
+ row({ serverAddress: "orders.example.com", callCount: "10" }),
1081
+ row({
1082
+ serverAddress: "ORDERS.example.com:5432",
1083
+ dbSystem: "postgres",
1084
+ callCount: 5,
1085
+ }),
1086
+ row({
1087
+ serverAddress: "postgresql://app:pw@orders.example.com/orders",
1088
+ callCount: "7",
1089
+ }),
1090
+ ]);
1091
+
1092
+ expect(result).toHaveLength(1);
1093
+ expect(formatted(result)).toEqual(["orders.example.com:5432"]);
1094
+ expect(result[0]!.callCount).toBe(22);
1095
+ expect(result[0]!.system).toBe("postgresql");
1096
+ });
1097
+
1098
+ test("one caller namespace per row never splits an FQDN endpoint", () => {
1099
+ const result: Array<DiscoveredDatabaseEndpoint> =
1100
+ resolveDatabaseEndpointRows([
1101
+ row({
1102
+ serverAddress: "orders.shop.svc",
1103
+ callerCluster: "prod-eu",
1104
+ callCount: 3,
1105
+ }),
1106
+ row({
1107
+ serverAddress: "orders.shop.svc.cluster.local",
1108
+ callerCluster: "prod-eu",
1109
+ callCount: 4,
1110
+ }),
1111
+ ]);
1112
+
1113
+ expect(formatted(result)).toEqual([
1114
+ "orders.shop.svc.cluster.local:5432@prod-eu",
1115
+ ]);
1116
+ expect(result[0]!.callCount).toBe(7);
1117
+ });
1118
+
1119
+ test("when engines disagree on one endpoint, an auto-creatable engine wins, then the busiest", () => {
1120
+ const result: Array<DiscoveredDatabaseEndpoint> =
1121
+ resolveDatabaseEndpointRows([
1122
+ row({
1123
+ dbSystem: "other_sql",
1124
+ serverAddress: "crdb.example.com:26257",
1125
+ callCount: 500,
1126
+ }),
1127
+ row({
1128
+ dbSystem: "postgresql",
1129
+ serverAddress: "crdb.example.com:26257",
1130
+ callCount: 20,
1131
+ }),
1132
+ row({
1133
+ dbSystem: "cockroachdb",
1134
+ serverAddress: "crdb.example.com:26257",
1135
+ callCount: 30,
1136
+ }),
1137
+ ]);
1138
+
1139
+ expect(result).toHaveLength(1);
1140
+ expect(result[0]!.system).toBe("cockroachdb");
1141
+ expect(result[0]!.callCount).toBe(550);
1142
+ });
1143
+
1144
+ /*
1145
+ * Regression: a pure majority recorded a MariaDB server as MySQL whenever
1146
+ * the MySQL drivers outnumbered the MariaDB connector — the one client
1147
+ * that can tell. Engines of one family are one database seen by
1148
+ * different clients: the fork wins, whatever the counts.
1149
+ */
1150
+ test("engines of one family: the fork a client named wins over a busier family value", () => {
1151
+ const result: Array<DiscoveredDatabaseEndpoint> =
1152
+ resolveDatabaseEndpointRows([
1153
+ row({
1154
+ dbSystem: "mysql",
1155
+ serverAddress: "orders.example.com",
1156
+ callCount: 900,
1157
+ }),
1158
+ row({
1159
+ dbSystem: "mariadb",
1160
+ serverAddress: "orders.example.com",
1161
+ callCount: 3,
1162
+ }),
1163
+ ]);
1164
+
1165
+ expect(result).toHaveLength(1);
1166
+ expect(result[0]!.system).toBe("mariadb");
1167
+ expect(result[0]!.callCount).toBe(903);
1168
+ expect(formatted(result)).toEqual(["orders.example.com:3306"]);
1169
+ });
1170
+
1171
+ test("the fork wins in any row order, and when it is the busier one", () => {
1172
+ const rows: Array<DatabaseEndpointRow> = [
1173
+ row({ dbSystem: "valkey", serverAddress: "cache.example.com" }),
1174
+ row({ dbSystem: "redis", serverAddress: "cache.example.com" }),
1175
+ ];
1176
+ expect(resolveDatabaseEndpointRows(rows)[0]!.system).toBe("valkey");
1177
+ expect(resolveDatabaseEndpointRows([...rows].reverse())[0]!.system).toBe(
1178
+ "valkey",
1179
+ );
1180
+ expect(
1181
+ resolveDatabaseEndpointRows([
1182
+ row({
1183
+ dbSystem: "valkey",
1184
+ serverAddress: "cache.example.com",
1185
+ callCount: 90,
1186
+ }),
1187
+ row({
1188
+ dbSystem: "redis",
1189
+ serverAddress: "cache.example.com",
1190
+ callCount: 10,
1191
+ }),
1192
+ ])[0]!.system,
1193
+ ).toBe("valkey");
1194
+ });
1195
+
1196
+ test("aliases of the family engine fold into the family with the fork", () => {
1197
+ const result: Array<DiscoveredDatabaseEndpoint> =
1198
+ resolveDatabaseEndpointRows([
1199
+ row({
1200
+ dbSystem: "postgres",
1201
+ serverAddress: "crdb.example.com:26257",
1202
+ callCount: 40,
1203
+ }),
1204
+ row({
1205
+ dbSystem: "postgresql",
1206
+ serverAddress: "crdb.example.com:26257",
1207
+ callCount: 40,
1208
+ }),
1209
+ row({
1210
+ dbSystem: "cockroachdb",
1211
+ serverAddress: "crdb.example.com:26257",
1212
+ callCount: 1,
1213
+ }),
1214
+ ]);
1215
+ expect(result[0]!.system).toBe("cockroachdb");
1216
+ expect(result[0]!.callCount).toBe(81);
1217
+ });
1218
+
1219
+ test("two forks of one family: the busier fork, then the name — never order-dependent", () => {
1220
+ const rows: Array<DatabaseEndpointRow> = [
1221
+ row({
1222
+ dbSystem: "mysql",
1223
+ serverAddress: "x.example.com",
1224
+ callCount: 500,
1225
+ }),
1226
+ row({ dbSystem: "tidb", serverAddress: "x.example.com", callCount: 7 }),
1227
+ row({
1228
+ dbSystem: "mariadb",
1229
+ serverAddress: "x.example.com",
1230
+ callCount: 9,
1231
+ }),
1232
+ ];
1233
+ expect(resolveDatabaseEndpointRows(rows)[0]!.system).toBe("mariadb");
1234
+ expect(resolveDatabaseEndpointRows([...rows].reverse())[0]!.system).toBe(
1235
+ "mariadb",
1236
+ );
1237
+
1238
+ const tied: Array<DatabaseEndpointRow> = [
1239
+ row({ dbSystem: "tidb", serverAddress: "x.example.com", callCount: 9 }),
1240
+ row({
1241
+ dbSystem: "mariadb",
1242
+ serverAddress: "x.example.com",
1243
+ callCount: 9,
1244
+ }),
1245
+ ];
1246
+ expect(resolveDatabaseEndpointRows(tied)[0]!.system).toBe("mariadb");
1247
+ expect(resolveDatabaseEndpointRows([...tied].reverse())[0]!.system).toBe(
1248
+ "mariadb",
1249
+ );
1250
+ });
1251
+
1252
+ test("between families, a family's calls count together", () => {
1253
+ // postgresql + cockroachdb (50) outvote mysql (45), though each alone would not.
1254
+ const result: Array<DiscoveredDatabaseEndpoint> =
1255
+ resolveDatabaseEndpointRows([
1256
+ row({
1257
+ dbSystem: "mysql",
1258
+ serverAddress: "x.example.com:1",
1259
+ callCount: 45,
1260
+ }),
1261
+ row({
1262
+ dbSystem: "postgresql",
1263
+ serverAddress: "x.example.com:1",
1264
+ callCount: 25,
1265
+ }),
1266
+ row({
1267
+ dbSystem: "cockroachdb",
1268
+ serverAddress: "x.example.com:1",
1269
+ callCount: 25,
1270
+ }),
1271
+ ]);
1272
+ expect(result[0]!.system).toBe("cockroachdb");
1273
+ expect(result[0]!.callCount).toBe(95);
1274
+ });
1275
+
1276
+ test("an unknown engine never joins a known family", () => {
1277
+ const result: Array<DiscoveredDatabaseEndpoint> =
1278
+ resolveDatabaseEndpointRows([
1279
+ row({
1280
+ dbSystem: "mysql_fork_x",
1281
+ serverAddress: "x.example.com:3306",
1282
+ callCount: 1000,
1283
+ }),
1284
+ row({
1285
+ dbSystem: "mysql",
1286
+ serverAddress: "x.example.com:3306",
1287
+ callCount: 1,
1288
+ }),
1289
+ ]);
1290
+ // The known, auto-creatable engine still wins over the unknown one.
1291
+ expect(result[0]!.system).toBe("mysql");
1292
+ });
1293
+
1294
+ test("an engine tie is broken by name, so the answer is stable", () => {
1295
+ const first: Array<DiscoveredDatabaseEndpoint> =
1296
+ resolveDatabaseEndpointRows([
1297
+ row({ dbSystem: "postgresql", serverAddress: "x.example.com:1" }),
1298
+ row({ dbSystem: "mysql", serverAddress: "x.example.com:1" }),
1299
+ ]);
1300
+ const second: Array<DiscoveredDatabaseEndpoint> =
1301
+ resolveDatabaseEndpointRows([
1302
+ row({ dbSystem: "mysql", serverAddress: "x.example.com:1" }),
1303
+ row({ dbSystem: "postgresql", serverAddress: "x.example.com:1" }),
1304
+ ]);
1305
+ expect(first[0]!.system).toBe("mysql");
1306
+ expect(second[0]!.system).toBe("mysql");
1307
+ });
1308
+
1309
+ test("returns the busiest endpoints first, then by endpoint", () => {
1310
+ const result: Array<DiscoveredDatabaseEndpoint> =
1311
+ resolveDatabaseEndpointRows([
1312
+ row({ serverAddress: "b.example.com", callCount: "5" }),
1313
+ row({ serverAddress: "c.example.com", callCount: "90" }),
1314
+ row({ serverAddress: "a.example.com", callCount: "5" }),
1315
+ ]);
1316
+ expect(formatted(result)).toEqual([
1317
+ "c.example.com:5432",
1318
+ "a.example.com:5432",
1319
+ "b.example.com:5432",
1320
+ ]);
1321
+ });
1322
+
1323
+ test("malformed call counts count as zero, never NaN", () => {
1324
+ const result: Array<DiscoveredDatabaseEndpoint> =
1325
+ resolveDatabaseEndpointRows([
1326
+ row({ callCount: "not-a-number" }),
1327
+ row({ callCount: -4 }),
1328
+ row({ callCount: undefined }),
1329
+ ]);
1330
+ expect(result).toHaveLength(1);
1331
+ expect(result[0]!.callCount).toBe(0);
1332
+ });
1333
+
1334
+ test("tolerates junk input", () => {
1335
+ expect(
1336
+ resolveDatabaseEndpointRows(
1337
+ undefined as unknown as Array<DatabaseEndpointRow>,
1338
+ ),
1339
+ ).toEqual([]);
1340
+ expect(
1341
+ resolveDatabaseEndpointRows([
1342
+ null as unknown as DatabaseEndpointRow,
1343
+ "row" as unknown as DatabaseEndpointRow,
1344
+ ]),
1345
+ ).toEqual([]);
1346
+ });
1347
+ });
1348
+
1349
+ describe("isDatabaseEndpointAutoCreateCandidate", () => {
1350
+ test("a busy global endpoint of a known engine, named by host, is a candidate", () => {
1351
+ expect(
1352
+ isDatabaseEndpointAutoCreateCandidate({
1353
+ discovered: discovered({}),
1354
+ minCalls: 10,
1355
+ }),
1356
+ ).toBe(true);
1357
+ });
1358
+
1359
+ test("the call threshold is inclusive", () => {
1360
+ expect(
1361
+ isDatabaseEndpointAutoCreateCandidate({
1362
+ discovered: discovered({ callCount: 10 }),
1363
+ minCalls: 10,
1364
+ }),
1365
+ ).toBe(true);
1366
+ expect(
1367
+ isDatabaseEndpointAutoCreateCandidate({
1368
+ discovered: discovered({ callCount: 9 }),
1369
+ minCalls: 10,
1370
+ }),
1371
+ ).toBe(false);
1372
+ });
1373
+
1374
+ test("a LOCAL endpoint never creates", () => {
1375
+ expect(
1376
+ isDatabaseEndpointAutoCreateCandidate({
1377
+ discovered: discovered({
1378
+ endpoint: { host: "postgres", port: 5432 },
1379
+ scope: "local",
1380
+ callCount: 100000,
1381
+ }),
1382
+ minCalls: 10,
1383
+ }),
1384
+ ).toBe(false);
1385
+ });
1386
+
1387
+ test("an IP literal never creates, even when it is global", () => {
1388
+ for (const endpoint of [
1389
+ { host: "34.120.1.9", port: 5432 },
1390
+ { host: "10.0.0.5", port: 5432, kubernetesClusterName: "prod-eu" },
1391
+ { host: "2001:db8::1", port: 5432 },
1392
+ ]) {
1393
+ expect(
1394
+ isDatabaseEndpointAutoCreateCandidate({
1395
+ discovered: discovered({ endpoint, scope: "global" }),
1396
+ minCalls: 10,
1397
+ }),
1398
+ ).toBe(false);
1399
+ }
1400
+ });
1401
+
1402
+ test("an unknown engine never creates", () => {
1403
+ expect(
1404
+ isDatabaseEndpointAutoCreateCandidate({
1405
+ discovered: discovered({ system: "acmedb" }),
1406
+ minCalls: 10,
1407
+ }),
1408
+ ).toBe(false);
1409
+ });
1410
+
1411
+ test("cloud-API and in-process engines never create", () => {
1412
+ for (const system of ["aws.dynamodb", "gcp.spanner", "sqlite"]) {
1413
+ expect(
1414
+ isDatabaseEndpointAutoCreateCandidate({
1415
+ discovered: discovered({
1416
+ system,
1417
+ endpoint: { host: "dynamodb.us-east-1.amazonaws.com", port: 443 },
1418
+ }),
1419
+ minCalls: 10,
1420
+ }),
1421
+ ).toBe(false);
1422
+ }
1423
+ });
1424
+
1425
+ test("a cluster-qualified Service FQDN is a candidate", () => {
1426
+ expect(
1427
+ isDatabaseEndpointAutoCreateCandidate({
1428
+ discovered: discovered({
1429
+ endpoint: {
1430
+ host: "orders.shop.svc.cluster.local",
1431
+ port: 5432,
1432
+ kubernetesClusterName: "prod-eu",
1433
+ },
1434
+ }),
1435
+ minCalls: 10,
1436
+ }),
1437
+ ).toBe(true);
1438
+ });
1439
+ });
1440
+
1441
+ describe("getDatabaseServerMinCalls", () => {
1442
+ let saved: string | undefined;
1443
+
1444
+ beforeEach(() => {
1445
+ saved = process.env[DATABASE_SERVER_MIN_CALLS_ENV];
1446
+ });
1447
+
1448
+ afterEach(() => {
1449
+ if (saved === undefined) {
1450
+ delete process.env[DATABASE_SERVER_MIN_CALLS_ENV];
1451
+ } else {
1452
+ process.env[DATABASE_SERVER_MIN_CALLS_ENV] = saved;
1453
+ }
1454
+ });
1455
+
1456
+ test("defaults to 10", () => {
1457
+ delete process.env[DATABASE_SERVER_MIN_CALLS_ENV];
1458
+ expect(DEFAULT_DATABASE_SERVER_MIN_CALLS).toBe(10);
1459
+ expect(getDatabaseServerMinCalls()).toBe(10);
1460
+ });
1461
+
1462
+ test("reads the environment on every call", () => {
1463
+ process.env[DATABASE_SERVER_MIN_CALLS_ENV] = " 25 ";
1464
+ expect(getDatabaseServerMinCalls()).toBe(25);
1465
+ process.env[DATABASE_SERVER_MIN_CALLS_ENV] = "3";
1466
+ expect(getDatabaseServerMinCalls()).toBe(3);
1467
+ });
1468
+
1469
+ test("is at least 1", () => {
1470
+ process.env[DATABASE_SERVER_MIN_CALLS_ENV] = "0";
1471
+ expect(getDatabaseServerMinCalls()).toBe(1);
1472
+ process.env[DATABASE_SERVER_MIN_CALLS_ENV] = "-7";
1473
+ expect(getDatabaseServerMinCalls()).toBe(1);
1474
+ });
1475
+
1476
+ test("falls back to the default for anything unparseable", () => {
1477
+ for (const value of ["", " ", "ten", "2.5", "Infinity"]) {
1478
+ process.env[DATABASE_SERVER_MIN_CALLS_ENV] = value;
1479
+ expect(getDatabaseServerMinCalls()).toBe(10);
1480
+ }
1481
+ });
1482
+ });
1483
+
1484
+ /*
1485
+ * ---- Audit regressions (lane B1) ------------------------------------------
1486
+ */
1487
+
1488
+ describe("resolveDatabaseEndpointRows — two-label Kubernetes names", () => {
1489
+ test("the finding's probe: postgres.data from two clusters is two cluster-qualified endpoints, not one global row", () => {
1490
+ const result: Array<DiscoveredDatabaseEndpoint> =
1491
+ resolveDatabaseEndpointRows([
1492
+ row({
1493
+ serverAddress: "postgres.data",
1494
+ callerInKubernetes: 1,
1495
+ callerCluster: "staging",
1496
+ callCount: "40",
1497
+ }),
1498
+ row({
1499
+ serverAddress: "postgres.data",
1500
+ callerInKubernetes: 1,
1501
+ callerCluster: "prod",
1502
+ callCount: "40",
1503
+ }),
1504
+ ]);
1505
+
1506
+ expect(formatted(result).sort()).toEqual([
1507
+ "postgres.data.svc.cluster.local:5432@prod",
1508
+ "postgres.data.svc.cluster.local:5432@staging",
1509
+ ]);
1510
+ for (const entry of result) {
1511
+ expect(entry.callCount).toBe(40);
1512
+ expect(entry.scope).toBe("global");
1513
+ }
1514
+ });
1515
+
1516
+ test("callers with a namespace but no cluster give a LOCAL endpoint that can never create a row", () => {
1517
+ const result: Array<DiscoveredDatabaseEndpoint> =
1518
+ resolveDatabaseEndpointRows([
1519
+ row({
1520
+ serverAddress: "postgres.data",
1521
+ callerInKubernetes: "1",
1522
+ callCount: "500",
1523
+ }),
1524
+ ]);
1525
+ expect(formatted(result)).toEqual(["postgres.data.svc.cluster.local:5432"]);
1526
+ expect(result[0]!.scope).toBe("local");
1527
+ expect(
1528
+ isDatabaseEndpointAutoCreateCandidate({
1529
+ discovered: result[0]!,
1530
+ minCalls: 10,
1531
+ }),
1532
+ ).toBe(false);
1533
+ });
1534
+
1535
+ test("callers outside Kubernetes keep the domain reading", () => {
1536
+ const result: Array<DiscoveredDatabaseEndpoint> =
1537
+ resolveDatabaseEndpointRows([
1538
+ row({ serverAddress: "postgres.data", callerInKubernetes: 0 }),
1539
+ ]);
1540
+ expect(formatted(result)).toEqual(["postgres.data:5432"]);
1541
+ expect(result[0]!.scope).toBe("global");
1542
+ });
1543
+
1544
+ test("the flag is read leniently (ClickHouse may send it as a number, string or boolean)", () => {
1545
+ for (const flag of [1, "1", true, "true"]) {
1546
+ expect(
1547
+ formatted(
1548
+ resolveDatabaseEndpointRows([
1549
+ row({
1550
+ serverAddress: "postgres.data",
1551
+ callerInKubernetes: flag,
1552
+ callerCluster: "prod",
1553
+ }),
1554
+ ]),
1555
+ ),
1556
+ ).toEqual(["postgres.data.svc.cluster.local:5432@prod"]);
1557
+ }
1558
+ for (const flag of [0, "0", false, "", undefined]) {
1559
+ expect(
1560
+ formatted(
1561
+ resolveDatabaseEndpointRows([
1562
+ row({ serverAddress: "postgres.data", callerInKubernetes: flag }),
1563
+ ]),
1564
+ ),
1565
+ ).toEqual(["postgres.data:5432"]);
1566
+ }
1567
+ });
1568
+
1569
+ test("a StatefulSet member short name uses the namespace the query kept", () => {
1570
+ expect(
1571
+ formatted(
1572
+ resolveDatabaseEndpointRows([
1573
+ row({
1574
+ dbSystem: "mongodb",
1575
+ serverAddress: "mongo-0.mongo-headless",
1576
+ callerNamespace: "data",
1577
+ callerInKubernetes: 1,
1578
+ callerCluster: "prod",
1579
+ }),
1580
+ ]),
1581
+ ),
1582
+ ).toEqual(["mongo-0.mongo-headless.data.svc.cluster.local:27017@prod"]);
1583
+ });
1584
+ });
1585
+
1586
+ describe("resolveDatabaseEndpointRows — SQL Server named instances", () => {
1587
+ test("the finding's probe: two instances on one host are two endpoints (from the address)", () => {
1588
+ const result: Array<DiscoveredDatabaseEndpoint> =
1589
+ resolveDatabaseEndpointRows([
1590
+ row({
1591
+ dbSystem: "mssql",
1592
+ serverAddress: "sql1.corp.example.com\\INST01",
1593
+ }),
1594
+ row({
1595
+ dbSystem: "mssql",
1596
+ serverAddress: "sql1.corp.example.com\\INST02",
1597
+ }),
1598
+ ]);
1599
+ expect(formatted(result).sort()).toEqual([
1600
+ "sql1.corp.example.com\\inst01",
1601
+ "sql1.corp.example.com\\inst02",
1602
+ ]);
1603
+ });
1604
+
1605
+ /*
1606
+ * The query's dbInstance column (databaseInstanceSql) holds what
1607
+ * readDatabaseInstanceName reads from the span, and the row reaches the
1608
+ * ingest resolver as `db.mssql.instance_name`. Whatever the resolver does
1609
+ * with an instance attribute, the cron therefore does the same with the
1610
+ * grouped row: the pairs below must always resolve alike.
1611
+ */
1612
+ const INSTANCE_SPANS: Array<{
1613
+ span: Record<string, unknown>;
1614
+ dbInstance: string;
1615
+ }> = [
1616
+ { span: { "db.mssql.instance_name": "INST01" }, dbInstance: "INST01" },
1617
+ { span: { "db.namespace": "INST02|orders" }, dbInstance: "INST02" },
1618
+ {
1619
+ span: {
1620
+ "db.mssql.instance_name": "INST03",
1621
+ "db.namespace": "INST04|orders",
1622
+ },
1623
+ dbInstance: "INST03",
1624
+ },
1625
+ {
1626
+ span: { "db.mssql.instance_name": "", "db.namespace": "INST05|x" },
1627
+ dbInstance: "INST05",
1628
+ },
1629
+ { span: { "db.namespace": "orders" }, dbInstance: "" },
1630
+ {
1631
+ span: { "db.mssql.instance_name": "MSSQLSERVER" },
1632
+ dbInstance: "MSSQLSERVER",
1633
+ },
1634
+ {
1635
+ span: { "server.port": "1434", "db.mssql.instance_name": "INST01" },
1636
+ dbInstance: "INST01",
1637
+ },
1638
+ ];
1639
+
1640
+ for (const entry of INSTANCE_SPANS) {
1641
+ test(`an instance column resolves like the span ${JSON.stringify(entry.span)}`, () => {
1642
+ for (const system of ["microsoft.sql_server", "mssql", "postgresql"]) {
1643
+ const attributes: Record<string, unknown> = {
1644
+ "db.system.name": system,
1645
+ "server.address": "sql1.corp.example.com",
1646
+ ...entry.span,
1647
+ };
1648
+ const ingest: { endpoint: DatabaseEndpoint } | null =
1649
+ resolveDatabaseCallTarget({
1650
+ getAttribute: (key: string): unknown => {
1651
+ return attributes[key];
1652
+ },
1653
+ caller: { isEphemeral: false, hostName: "vm" },
1654
+ });
1655
+
1656
+ expect(
1657
+ formatted(
1658
+ resolveDatabaseEndpointRows([
1659
+ row({
1660
+ dbSystem: system,
1661
+ serverAddress: "sql1.corp.example.com",
1662
+ serverPort: (entry.span["server.port"] as string) || "",
1663
+ dbInstance: entry.dbInstance,
1664
+ }),
1665
+ ]),
1666
+ ),
1667
+ ).toEqual(ingest ? [formatDatabaseEndpoint(ingest.endpoint)] : []);
1668
+ }
1669
+ });
1670
+ }
1671
+
1672
+ test("the instance column's SQL and readDatabaseInstanceName pick the same value", () => {
1673
+ for (const entry of INSTANCE_SPANS) {
1674
+ const expected: string | null = readDatabaseInstanceName({
1675
+ system: "mssql",
1676
+ getAttribute: (key: string): unknown => {
1677
+ return entry.span[key];
1678
+ },
1679
+ });
1680
+ expect(expected ?? "").toBe(entry.dbInstance);
1681
+ }
1682
+ });
1683
+
1684
+ test("an instance column is ignored for every other engine", () => {
1685
+ expect(
1686
+ formatted(
1687
+ resolveDatabaseEndpointRows([
1688
+ row({ serverAddress: "db.example.com", dbInstance: "INST01" }),
1689
+ ]),
1690
+ ),
1691
+ ).toEqual(["db.example.com:5432"]);
1692
+ });
1693
+ });
1694
+
1695
+ describe("resolveDatabaseEndpointRows — managed cluster members are one database", () => {
1696
+ const MEMBERS: Array<string> = [
1697
+ "c0-shard-00-00.abcd.mongodb.net",
1698
+ "c0-shard-00-01.abcd.mongodb.net",
1699
+ "c0-shard-00-02.abcd.mongodb.net",
1700
+ ];
1701
+
1702
+ test("the finding's probe: three Atlas members are ONE entry, not three create candidates", () => {
1703
+ const result: Array<DiscoveredDatabaseEndpoint> =
1704
+ resolveDatabaseEndpointRows(
1705
+ MEMBERS.map((serverAddress: string, index: number) => {
1706
+ return row({
1707
+ dbSystem: "mongodb",
1708
+ serverAddress: serverAddress,
1709
+ callCount: String(10 + index),
1710
+ });
1711
+ }),
1712
+ );
1713
+
1714
+ expect(result).toHaveLength(1);
1715
+ // Recorded under the busiest member, the others its siblings.
1716
+ expect(formatDatabaseEndpoint(result[0]!.endpoint)).toBe(
1717
+ "c0-shard-00-02.abcd.mongodb.net:27017",
1718
+ );
1719
+ expect(
1720
+ (result[0]!.siblings || []).map((endpoint: DatabaseEndpoint) => {
1721
+ return formatDatabaseEndpoint(endpoint);
1722
+ }),
1723
+ ).toEqual([
1724
+ "c0-shard-00-00.abcd.mongodb.net:27017",
1725
+ "c0-shard-00-01.abcd.mongodb.net:27017",
1726
+ ]);
1727
+ expect(result[0]!.callCount).toBe(33);
1728
+ expect(result[0]!.displayName).toBe("MongoDB c0.abcd.mongodb.net:27017");
1729
+ expect(result[0]!.system).toBe("mongodb");
1730
+ expect(getDiscoveredDatabaseEndpoints(result[0]!)).toHaveLength(3);
1731
+ });
1732
+
1733
+ test("a quiet cluster still counts its members' calls together towards the minimum", () => {
1734
+ const result: Array<DiscoveredDatabaseEndpoint> =
1735
+ resolveDatabaseEndpointRows(
1736
+ MEMBERS.map((serverAddress: string) => {
1737
+ return row({
1738
+ dbSystem: "mongodb",
1739
+ serverAddress: serverAddress,
1740
+ callCount: "4",
1741
+ });
1742
+ }),
1743
+ );
1744
+ expect(result[0]!.callCount).toBe(12);
1745
+ expect(
1746
+ isDatabaseEndpointAutoCreateCandidate({
1747
+ discovered: result[0]!,
1748
+ minCalls: 10,
1749
+ }),
1750
+ ).toBe(true);
1751
+ });
1752
+
1753
+ test("a client using the cluster's own name joins its members, and that name is preferred", () => {
1754
+ const result: Array<DiscoveredDatabaseEndpoint> =
1755
+ resolveDatabaseEndpointRows([
1756
+ row({
1757
+ dbSystem: "mongodb",
1758
+ serverAddress: "c0-shard-00-01.abcd.mongodb.net",
1759
+ callCount: "90",
1760
+ }),
1761
+ row({
1762
+ dbSystem: "mongodb",
1763
+ serverAddress: "c0.abcd.mongodb.net",
1764
+ callCount: "5",
1765
+ }),
1766
+ ]);
1767
+ expect(result).toHaveLength(1);
1768
+ expect(formatDatabaseEndpoint(result[0]!.endpoint)).toBe(
1769
+ "c0.abcd.mongodb.net:27017",
1770
+ );
1771
+ expect(result[0]!.callCount).toBe(95);
1772
+ });
1773
+
1774
+ test("a single member seen alone is still named after its cluster, with no siblings", () => {
1775
+ const result: Array<DiscoveredDatabaseEndpoint> =
1776
+ resolveDatabaseEndpointRows([
1777
+ row({ dbSystem: "mongodb", serverAddress: MEMBERS[1]! }),
1778
+ ]);
1779
+ expect(result).toHaveLength(1);
1780
+ expect(result[0]!.siblings).toBeUndefined();
1781
+ expect(result[0]!.displayName).toBe("MongoDB c0.abcd.mongodb.net:27017");
1782
+ });
1783
+
1784
+ test("different clusters, and a cluster on another port, stay apart", () => {
1785
+ const result: Array<DiscoveredDatabaseEndpoint> =
1786
+ resolveDatabaseEndpointRows([
1787
+ row({ dbSystem: "mongodb", serverAddress: MEMBERS[0]! }),
1788
+ row({
1789
+ dbSystem: "mongodb",
1790
+ serverAddress: "c1-shard-00-00.abcd.mongodb.net",
1791
+ }),
1792
+ row({
1793
+ dbSystem: "mongodb",
1794
+ serverAddress: "c0-shard-00-01.abcd.mongodb.net:27018",
1795
+ }),
1796
+ ]);
1797
+ expect(result).toHaveLength(3);
1798
+ });
1799
+
1800
+ test("ordinary endpoints carry no siblings and no display name", () => {
1801
+ const result: Array<DiscoveredDatabaseEndpoint> =
1802
+ resolveDatabaseEndpointRows([row({})]);
1803
+ expect(result[0]).toEqual({
1804
+ system: "postgresql",
1805
+ endpoint: { host: "orders.example.com", port: 5432 },
1806
+ scope: "global",
1807
+ callCount: 25,
1808
+ });
1809
+ });
1810
+ });
1811
+
1812
+ describe("isDatabaseEndpointAutoCreateCandidate — per-network names", () => {
1813
+ test("never creates for a link-local address, qualified or not", () => {
1814
+ for (const endpoint of [
1815
+ { host: "169.254.1.10", port: 5432, kubernetesClusterName: "prod" },
1816
+ { host: "fe80::1", port: 5432 },
1817
+ ]) {
1818
+ const [resolved]: Array<DiscoveredDatabaseEndpoint> =
1819
+ resolveDatabaseEndpointRows([
1820
+ row({
1821
+ serverAddress:
1822
+ endpoint.host === "fe80::1" ? "fe80::1%eth0" : endpoint.host,
1823
+ callerCluster: endpoint.kubernetesClusterName || "",
1824
+ }),
1825
+ ]);
1826
+ expect(resolved!.scope).toBe("local");
1827
+ expect(
1828
+ isDatabaseEndpointAutoCreateCandidate({
1829
+ discovered: resolved!,
1830
+ minCalls: 1,
1831
+ }),
1832
+ ).toBe(false);
1833
+ }
1834
+ });
1835
+
1836
+ test("never creates for a private-zone name without a cluster, but does with one", () => {
1837
+ const [vm]: Array<DiscoveredDatabaseEndpoint> = resolveDatabaseEndpointRows(
1838
+ [row({ serverAddress: "ip-10-0-0-5.ec2.internal", callCount: "900" })],
1839
+ );
1840
+ expect(vm!.scope).toBe("local");
1841
+ expect(
1842
+ isDatabaseEndpointAutoCreateCandidate({ discovered: vm!, minCalls: 10 }),
1843
+ ).toBe(false);
1844
+
1845
+ const [podCaller]: Array<DiscoveredDatabaseEndpoint> =
1846
+ resolveDatabaseEndpointRows([
1847
+ row({
1848
+ serverAddress: "ip-10-0-0-5.ec2.internal",
1849
+ callerCluster: "prod",
1850
+ callCount: "900",
1851
+ }),
1852
+ ]);
1853
+ expect(
1854
+ isDatabaseEndpointAutoCreateCandidate({
1855
+ discovered: podCaller!,
1856
+ minCalls: 10,
1857
+ }),
1858
+ ).toBe(true);
1859
+ });
1860
+
1861
+ test("dev-tool gateway names never resolve at all", () => {
1862
+ expect(
1863
+ resolveDatabaseEndpointRows([
1864
+ row({ serverAddress: "host.minikube.internal", callCount: "900" }),
1865
+ row({ serverAddress: "host.k3d.internal:5432", callCount: "900" }),
1866
+ ]),
1867
+ ).toEqual([]);
1868
+ });
1869
+
1870
+ test("a named SQL Server instance on an IP never creates; on a host name it may", () => {
1871
+ const [ip]: Array<DiscoveredDatabaseEndpoint> = resolveDatabaseEndpointRows(
1872
+ [
1873
+ row({
1874
+ dbSystem: "mssql",
1875
+ serverAddress: "10.0.0.5\\INST01",
1876
+ callerCluster: "prod",
1877
+ }),
1878
+ ],
1879
+ );
1880
+ expect(
1881
+ isDatabaseEndpointAutoCreateCandidate({ discovered: ip!, minCalls: 1 }),
1882
+ ).toBe(false);
1883
+
1884
+ const [host]: Array<DiscoveredDatabaseEndpoint> =
1885
+ resolveDatabaseEndpointRows([
1886
+ row({
1887
+ dbSystem: "mssql",
1888
+ serverAddress: "sql1.corp.example.com\\INST01",
1889
+ }),
1890
+ ]);
1891
+ expect(
1892
+ isDatabaseEndpointAutoCreateCandidate({ discovered: host!, minCalls: 1 }),
1893
+ ).toBe(true);
1894
+ });
1895
+ });