@oneuptime/common 14.0.10 → 14.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (837) hide show
  1. package/Models/DatabaseModels/AIConversation.ts +127 -1
  2. package/Models/DatabaseModels/CephCluster.ts +12 -0
  3. package/Models/DatabaseModels/CloudResource.ts +12 -0
  4. package/Models/DatabaseModels/DatabaseServer.ts +12 -0
  5. package/Models/DatabaseModels/DockerHost.ts +12 -0
  6. package/Models/DatabaseModels/DockerSwarmCluster.ts +12 -0
  7. package/Models/DatabaseModels/GlobalOidc.ts +0 -4
  8. package/Models/DatabaseModels/GlobalOidcProject.ts +0 -4
  9. package/Models/DatabaseModels/GlobalSso.ts +0 -4
  10. package/Models/DatabaseModels/GlobalSsoProject.ts +0 -4
  11. package/Models/DatabaseModels/Host.ts +12 -0
  12. package/Models/DatabaseModels/IncidentForm.ts +594 -0
  13. package/Models/DatabaseModels/IncidentFormSubmission.ts +283 -0
  14. package/Models/DatabaseModels/IncidentTemplate.ts +59 -0
  15. package/Models/DatabaseModels/Index.ts +14 -0
  16. package/Models/DatabaseModels/IoTFleet.ts +12 -0
  17. package/Models/DatabaseModels/KubernetesCluster.ts +12 -0
  18. package/Models/DatabaseModels/MessageQueue.ts +1102 -0
  19. package/Models/DatabaseModels/MessageQueueLabelRule.ts +553 -0
  20. package/Models/DatabaseModels/MessageQueueOwnerRule.ts +633 -0
  21. package/Models/DatabaseModels/MessageQueueOwnerTeam.ts +491 -0
  22. package/Models/DatabaseModels/MessageQueueOwnerUser.ts +490 -0
  23. package/Models/DatabaseModels/PodmanHost.ts +12 -0
  24. package/Models/DatabaseModels/Project.ts +59 -0
  25. package/Models/DatabaseModels/ProjectOidc.ts +0 -4
  26. package/Models/DatabaseModels/ProjectSso.ts +0 -4
  27. package/Models/DatabaseModels/ProxmoxCluster.ts +12 -0
  28. package/Models/DatabaseModels/RumApplication.ts +12 -0
  29. package/Models/DatabaseModels/ServerlessFunction.ts +12 -0
  30. package/Models/DatabaseModels/Service.ts +12 -0
  31. package/Models/DatabaseModels/StatusPageOidc.ts +0 -4
  32. package/Models/DatabaseModels/StatusPageSso.ts +0 -4
  33. package/Models/DatabaseModels/TeamComplianceSetting.ts +47 -2
  34. package/Models/DatabaseModels/VMwareVCenter.ts +12 -0
  35. package/Server/API/AIChatAPI.ts +13 -114
  36. package/Server/API/AIInvestigationAPI.ts +3 -3
  37. package/Server/API/AIInvestigationConversationAPI.ts +266 -0
  38. package/Server/API/IncidentFormAPI.ts +376 -0
  39. package/Server/API/ProjectOIDC.ts +16 -25
  40. package/Server/API/ProjectSSO.ts +16 -24
  41. package/Server/API/StatusPageAPI.ts +42 -77
  42. package/Server/Enterprise/EnterpriseEdition.ts +93 -28
  43. package/Server/Enterprise/EnterpriseFeature.ts +25 -3
  44. package/Server/Enterprise/EnterpriseLicenseSnapshot.ts +1 -1
  45. package/Server/Enterprise/EnterpriseServerModule.ts +4 -1
  46. package/Server/EnvironmentConfig.ts +1 -1
  47. package/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.ts +129 -0
  48. package/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.ts +52 -0
  49. package/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.ts +75 -0
  50. package/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.ts +477 -0
  51. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  52. package/Server/Middleware/IncidentFormRateLimit.ts +847 -0
  53. package/Server/Middleware/UserAuthorization.ts +16 -38
  54. package/Server/Services/AIService.ts +10 -13
  55. package/Server/Services/IncidentCustomFieldService.ts +112 -2
  56. package/Server/Services/IncidentEpisodeMemberService.ts +24 -4
  57. package/Server/Services/IncidentEpisodeService.ts +10 -1
  58. package/Server/Services/IncidentFormService.ts +1566 -0
  59. package/Server/Services/IncidentFormSubmissionService.ts +97 -0
  60. package/Server/Services/IncidentGroupingEngineService.ts +45 -8
  61. package/Server/Services/IncidentService.ts +123 -5
  62. package/Server/Services/IncidentTemplateService.ts +22 -0
  63. package/Server/Services/Index.ts +14 -0
  64. package/Server/Services/MessageQueueLabelRuleEngineService.ts +321 -0
  65. package/Server/Services/MessageQueueLabelRuleService.ts +14 -0
  66. package/Server/Services/MessageQueueOwnerRuleEngineService.ts +412 -0
  67. package/Server/Services/MessageQueueOwnerRuleService.ts +14 -0
  68. package/Server/Services/MessageQueueOwnerTeamService.ts +62 -0
  69. package/Server/Services/MessageQueueOwnerUserService.ts +62 -0
  70. package/Server/Services/MessageQueueService.ts +1463 -0
  71. package/Server/Services/OpenTelemetryIngestService.ts +37 -4
  72. package/Server/Services/ProjectService.ts +0 -59
  73. package/Server/Services/StatusPagePrivateUserService.ts +1 -4
  74. package/Server/Services/StatusPageService.ts +1 -51
  75. package/Server/Services/TeamComplianceSettingService.ts +163 -42
  76. package/Server/Services/UserNotificationRuleService.ts +8 -1
  77. package/Server/Services/WorkspaceNotificationSummaryService.ts +12 -3
  78. package/Server/Types/Database/Permissions/CreatePermission.ts +17 -3
  79. package/Server/Types/Database/Permissions/EditionPermission.ts +140 -38
  80. package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +16 -0
  81. package/Server/Types/Database/Permissions/TablePermission.ts +3 -3
  82. package/Server/Types/Database/Permissions/UpdatePermission.ts +20 -6
  83. package/Server/Utils/AI/Chat/AgentContextCompactor.ts +200 -0
  84. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +307 -48
  85. package/Server/Utils/AI/Chat/ChatRunCancellation.ts +156 -0
  86. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +78 -18
  87. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  88. package/Server/Utils/AI/Chat/ToolOutputPager.ts +322 -0
  89. package/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.ts +88 -30
  90. package/Server/Utils/AI/ClusterAccess/KubectlJobRunner.ts +13 -1
  91. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +0 -32
  92. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +27 -25
  93. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +7 -1
  94. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +34 -14
  95. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +69 -24
  96. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +9 -0
  97. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +53 -10
  98. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +27 -10
  99. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +27 -10
  100. package/Server/Utils/AI/SRE/InvestigationQueue.ts +16 -19
  101. package/Server/Utils/AI/SRE/InvestigationThread.ts +646 -0
  102. package/Server/Utils/AI/SRE/InvestigationThreadService.ts +715 -0
  103. package/Server/Utils/AI/Toolbox/Serializer.ts +10 -3
  104. package/Server/Utils/EditionEnforcement.ts +19 -202
  105. package/Server/Utils/LLM/LLMService.ts +259 -63
  106. package/Server/Utils/RelaxedScimTeamLocksReport.ts +245 -0
  107. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +25 -0
  108. package/Server/Utils/SameOriginRequest.ts +203 -0
  109. package/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.ts +2 -0
  110. package/Server/Utils/Telemetry/EntityRegistry.ts +4 -1
  111. package/Server/Utils/Telemetry/MessageQueueDiscovery.ts +1402 -0
  112. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +4 -1
  113. package/Server/Utils/VendorAssets.ts +3 -3
  114. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +3 -1
  115. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +3 -1
  116. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +10 -1
  117. package/Tests/App/Accounts/IncidentFormPage.test.tsx +2036 -0
  118. package/Tests/App/Accounts/IncidentFormServerMessages.test.ts +119 -0
  119. package/Tests/App/Accounts/IncidentFormUtils.test.ts +1157 -0
  120. package/Tests/App/Accounts/LoginPasskey.test.tsx +6 -4
  121. package/Tests/App/Accounts/LoginSso.test.tsx +724 -0
  122. package/Tests/App/AdminDashboard/AdminAuthenticationRequireSso.test.tsx +338 -0
  123. package/Tests/App/AdminDashboard/AdminAuthenticationSsoCardLocales.test.tsx +331 -0
  124. package/Tests/App/AdminDashboard/AdminEnterprisePluginPage.test.tsx +47 -33
  125. package/Tests/App/AdminDashboard/GlobalSsoOidcPages.test.tsx +1021 -0
  126. package/Tests/App/Dashboard/AffectedResourcesDisplay.test.tsx +876 -78
  127. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +20 -3
  128. package/Tests/App/Dashboard/CephProxmoxVMwareDocumentationCards.test.tsx +359 -0
  129. package/Tests/App/Dashboard/CephSetupGuide.test.ts +961 -0
  130. package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +381 -95
  131. package/Tests/App/Dashboard/CloudSetupGuide.test.ts +1463 -0
  132. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +174 -12
  133. package/Tests/App/Dashboard/ContainerSetupGuideCards.test.tsx +345 -0
  134. package/Tests/App/Dashboard/CoreListEmptyStates.test.tsx +501 -0
  135. package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +0 -1
  136. package/Tests/App/Dashboard/DatabaseDocumentationCard.test.tsx +400 -0
  137. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +5 -0
  138. package/Tests/App/Dashboard/DatabaseSetupGuide.test.ts +1869 -0
  139. package/Tests/App/Dashboard/DatabaseTelemetryScope.test.ts +44 -0
  140. package/Tests/App/Dashboard/DatabaseTelemetryTabs.test.tsx +50 -9
  141. package/Tests/App/Dashboard/DockerSetupGuide.test.ts +899 -0
  142. package/Tests/App/Dashboard/DockerSwarmSetupGuide.test.ts +964 -0
  143. package/Tests/App/Dashboard/EnterpriseFeatureUpgrade.test.tsx +141 -7
  144. package/Tests/App/Dashboard/EnterprisePluginPage.test.tsx +52 -39
  145. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +7 -1
  146. package/Tests/App/Dashboard/GettingStartedTaskRoutes.test.tsx +331 -0
  147. package/Tests/App/Dashboard/HomeOverviewStatsFirstRun.test.tsx +925 -0
  148. package/Tests/App/Dashboard/HostDocumentationCard.test.tsx +225 -0
  149. package/Tests/App/Dashboard/HostSetupGuide.test.ts +1183 -0
  150. package/Tests/App/Dashboard/IdentityEnterpriseShells.test.tsx +69 -45
  151. package/Tests/App/Dashboard/IncidentCreateTemplateCustomFieldSettings.test.tsx +745 -0
  152. package/Tests/App/Dashboard/IncidentCreateTemplateOwners.test.tsx +414 -0
  153. package/Tests/App/Dashboard/IncidentCustomFieldCreateSettingsForm.test.ts +1042 -0
  154. package/Tests/App/Dashboard/IncidentCustomFieldSettingsCard.test.tsx +1816 -0
  155. package/Tests/App/Dashboard/IncidentFormPlan.test.ts +89 -0
  156. package/Tests/App/Dashboard/IncidentFormShareLinkCard.test.tsx +846 -0
  157. package/Tests/App/Dashboard/IncidentFormsPages.test.tsx +1324 -0
  158. package/Tests/App/Dashboard/IncidentTemplateCustomFieldSettings.test.tsx +630 -0
  159. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +13 -0
  160. package/Tests/App/Dashboard/IncomingEmailMonitorLinkVerifyHint.test.tsx +104 -0
  161. package/Tests/App/Dashboard/InvestigationConversation.test.tsx +1107 -0
  162. package/Tests/App/Dashboard/InvestigationConversationData.test.ts +546 -0
  163. package/Tests/App/Dashboard/InvestigationEvidenceList.test.tsx +170 -5
  164. package/Tests/App/Dashboard/InvestigationNotStarted.test.tsx +207 -2
  165. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +729 -34
  166. package/Tests/App/Dashboard/InvestigationPanelStatus.test.tsx +9 -21
  167. package/Tests/App/Dashboard/InvestigationReportView.test.tsx +274 -37
  168. package/Tests/App/Dashboard/InvestigationStatusBadge.test.tsx +130 -0
  169. package/Tests/App/Dashboard/IoTDocumentationCard.test.tsx +218 -0
  170. package/Tests/App/Dashboard/IoTSetupGuide.test.ts +1124 -0
  171. package/Tests/App/Dashboard/KubernetesClusterAiAccessSettings.test.ts +20 -6
  172. package/Tests/App/Dashboard/KubernetesClusterAiDocumentationMarkdown.test.ts +107 -83
  173. package/Tests/App/Dashboard/KubernetesSetupGuide.test.ts +787 -0
  174. package/Tests/App/Dashboard/MessageQueueBreadcrumbs.test.tsx +228 -0
  175. package/Tests/App/Dashboard/MessageQueueBrokerHealth.test.tsx +1538 -0
  176. package/Tests/App/Dashboard/MessageQueueBrokerHealthMetricChart.test.tsx +969 -0
  177. package/Tests/App/Dashboard/MessageQueueBrokerHealthReads.test.ts +889 -0
  178. package/Tests/App/Dashboard/MessageQueueBrokerMetricsReach.test.ts +236 -0
  179. package/Tests/App/Dashboard/MessageQueueDocumentationCard.test.tsx +479 -0
  180. package/Tests/App/Dashboard/MessageQueueListPages.test.tsx +1232 -0
  181. package/Tests/App/Dashboard/MessageQueueMetricDescriptions.test.ts +244 -0
  182. package/Tests/App/Dashboard/MessageQueueMonitorLink.test.ts +952 -0
  183. package/Tests/App/Dashboard/MessageQueueMonitorLinkMonitorCreate.test.tsx +859 -0
  184. package/Tests/App/Dashboard/MessageQueueOverview.test.tsx +1773 -0
  185. package/Tests/App/Dashboard/MessageQueueOverviewPresentation.test.ts +196 -0
  186. package/Tests/App/Dashboard/MessageQueuePresentation.test.ts +799 -0
  187. package/Tests/App/Dashboard/MessageQueueProducersConsumers.test.tsx +680 -0
  188. package/Tests/App/Dashboard/MessageQueueRoutes.test.tsx +383 -0
  189. package/Tests/App/Dashboard/MessageQueueRulePages.test.tsx +278 -0
  190. package/Tests/App/Dashboard/MessageQueueSetupGuide.test.ts +2778 -0
  191. package/Tests/App/Dashboard/MessageQueueSideMenus.test.tsx +273 -0
  192. package/Tests/App/Dashboard/MessageQueueSpanPopulations.test.ts +930 -0
  193. package/Tests/App/Dashboard/MessageQueueSpanStore.ts +351 -0
  194. package/Tests/App/Dashboard/MessageQueueTelemetryScope.test.ts +375 -0
  195. package/Tests/App/Dashboard/MessageQueueTelemetryScopeTabs.test.tsx +668 -0
  196. package/Tests/App/Dashboard/MessageQueueViewPages.test.tsx +685 -0
  197. package/Tests/App/Dashboard/MonitorAndHomeNotOperationalMenus.test.tsx +342 -0
  198. package/Tests/App/Dashboard/MonitorCreateFirstRunDefaults.test.tsx +623 -0
  199. package/Tests/App/Dashboard/MonitorCreateFromMonitorBackedDevice.test.tsx +442 -0
  200. package/Tests/App/Dashboard/MonitorLogsPageViewSummary.test.tsx +814 -0
  201. package/Tests/App/Dashboard/MonitorSetupCardBodies.test.tsx +246 -0
  202. package/Tests/App/Dashboard/NavigationSearchKeywords.test.tsx +8 -0
  203. package/Tests/App/Dashboard/OnboardingSsoEmptyState.test.tsx +27 -12
  204. package/Tests/App/Dashboard/PlanGatedPage.test.tsx +292 -0
  205. package/Tests/App/Dashboard/PodmanSetupGuide.test.ts +948 -0
  206. package/Tests/App/Dashboard/ProductOrientation.test.tsx +5 -4
  207. package/Tests/App/Dashboard/ProductSettingsSectionCollapsed.test.tsx +266 -0
  208. package/Tests/App/Dashboard/ProxmoxSetupGuide.test.ts +1318 -0
  209. package/Tests/App/Dashboard/ResourceAiAgentAttentionServerParity.test.ts +555 -0
  210. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +298 -9
  211. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +453 -0
  212. package/Tests/App/Dashboard/ResourceArchiveSettings.test.tsx +16 -0
  213. package/Tests/App/Dashboard/ResourceConnectionGuideCard.test.tsx +444 -0
  214. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsInfra.test.tsx +798 -0
  215. package/Tests/App/Dashboard/ResourceConnectionGuideOverviewsPlatforms.test.tsx +717 -0
  216. package/Tests/App/Dashboard/ResourceConnectionGuides.test.ts +981 -0
  217. package/Tests/App/Dashboard/ResourceConnectionState.test.ts +132 -0
  218. package/Tests/App/Dashboard/RumDocumentationCard.test.tsx +293 -0
  219. package/Tests/App/Dashboard/RumSessionReplayRetentionSettings.test.tsx +467 -0
  220. package/Tests/App/Dashboard/RumSetupGuide.test.ts +758 -0
  221. package/Tests/App/Dashboard/ScheduledMaintenanceOverviewPage.test.tsx +25 -0
  222. package/Tests/App/Dashboard/ServerlessDocumentationCard.test.tsx +254 -0
  223. package/Tests/App/Dashboard/ServerlessSetupGuide.test.ts +1074 -0
  224. package/Tests/App/Dashboard/SetupGuide.test.ts +246 -0
  225. package/Tests/App/Dashboard/SetupGuideCard.test.tsx +756 -0
  226. package/Tests/App/Dashboard/SloListSideMenu.test.tsx +120 -0
  227. package/Tests/App/Dashboard/SsoPages.test.tsx +833 -0
  228. package/Tests/App/Dashboard/TelemetryDocumentation.test.tsx +253 -0
  229. package/Tests/App/Dashboard/TelemetryRetentionShells.test.tsx +730 -0
  230. package/Tests/App/Dashboard/VMwareSetupGuide.test.ts +1033 -0
  231. package/Tests/App/StatusPage/StatusPageSsoEmptyState.test.tsx +87 -7
  232. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +28 -9
  233. package/Tests/Models/DatabaseModels/IncidentFormModel.test.ts +1250 -0
  234. package/Tests/Models/DatabaseModels/MessageQueueModels.test.ts +1983 -0
  235. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  236. package/Tests/Models/DatabaseModels/TeamComplianceSettingModel.test.ts +190 -14
  237. package/Tests/Models/IncidentTemplateCustomFieldSettingsColumn.test.ts +291 -0
  238. package/Tests/Server/API/AIInvestigationConversationAPI.test.ts +412 -0
  239. package/Tests/Server/API/GlobalConfigLicense.test.ts +38 -9
  240. package/Tests/Server/API/IncidentFormAPI.test.ts +942 -0
  241. package/Tests/Server/API/IncidentFormPublicRoutes.test.ts +1653 -0
  242. package/Tests/Server/API/KubernetesClusterAiAccessAPI.test.ts +2 -1
  243. package/Tests/Server/API/{SsoProviderListingsEdition.test.ts → SsoProviderListings.test.ts} +164 -110
  244. package/Tests/Server/Enterprise/EnterpriseEdition.test.ts +76 -54
  245. package/Tests/Server/Enterprise/EnterpriseFeatureActive.test.ts +125 -42
  246. package/Tests/Server/Enterprise/EnterpriseLicenseSnapshot.test.ts +36 -10
  247. package/Tests/Server/Enterprise/EnterpriseModelFeatureMap.test.ts +51 -9
  248. package/Tests/Server/Enterprise/FakeEnterpriseModule.ts +13 -8
  249. package/Tests/Server/Enterprise/SsoInCommunityEdition.test.ts +540 -0
  250. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  251. package/Tests/Server/Infrastructure/Postgres/AddIncidentFormsMigration.test.ts +438 -0
  252. package/Tests/Server/Infrastructure/Postgres/AddTeamComplianceRuleNotificationChannelsMigration.test.ts +255 -0
  253. package/Tests/Server/Infrastructure/Postgres/MessageQueueTablesMigration.test.ts +637 -0
  254. package/Tests/Server/Middleware/IncidentFormRateLimit.test.ts +1630 -0
  255. package/Tests/Server/Middleware/UserAuthorization.test.ts +16 -17
  256. package/Tests/Server/Middleware/UserAuthorizationSsoEnforcement.test.ts +544 -0
  257. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +37 -8
  258. package/Tests/Server/Services/IncidentCreatedFeedTitle.test.ts +212 -0
  259. package/Tests/Server/Services/IncidentCustomFieldDeleteFormQuestions.test.ts +297 -0
  260. package/Tests/Server/Services/IncidentFormOwnersCreatedNotification.test.ts +497 -0
  261. package/Tests/Server/Services/IncidentFormPostgres.test.ts +958 -0
  262. package/Tests/Server/Services/IncidentFormPublicForm.test.ts +1260 -0
  263. package/Tests/Server/Services/IncidentFormPublicSubmission.test.ts +2520 -0
  264. package/Tests/Server/Services/IncidentFormRegistration.test.ts +159 -0
  265. package/Tests/Server/Services/IncidentFormService.test.ts +663 -0
  266. package/Tests/Server/Services/IncidentFormSubmissionService.test.ts +430 -0
  267. package/Tests/Server/Services/IncidentFormSubmissionTemplateBranch.test.ts +473 -0
  268. package/Tests/Server/Services/IncidentGroupingTemplateSubstitution.test.ts +81 -0
  269. package/Tests/Server/Services/IncidentTemplateCustomFieldSettings.test.ts +262 -0
  270. package/Tests/Server/Services/IncidentTemplateOwnersOnDeclare.test.ts +566 -0
  271. package/Tests/Server/Services/IncidentTitleMarkdownSinks.test.ts +640 -0
  272. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +3 -3
  273. package/Tests/Server/Services/MessageQueueOwnerServices.test.ts +440 -0
  274. package/Tests/Server/Services/MessageQueueRegistries.test.ts +577 -0
  275. package/Tests/Server/Services/MessageQueueRuleEngines.test.ts +900 -0
  276. package/Tests/Server/Services/MessageQueueService.test.ts +2210 -0
  277. package/Tests/Server/Services/MessageQueueSqlPostgres.test.ts +1271 -0
  278. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +28 -1
  279. package/Tests/Server/Services/OpenTelemetryRetentionOverrideEdition.test.ts +474 -0
  280. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +21 -0
  281. package/Tests/Server/Services/OwnerRowUniqueness.test.ts +3 -0
  282. package/Tests/Server/Services/ProjectServiceAuditLogLicense.test.ts +0 -1
  283. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +56 -6
  284. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -1
  285. package/Tests/Server/Services/SsoRequirementReads.test.ts +454 -0
  286. package/Tests/Server/Services/SsoRequirementWrites.test.ts +529 -0
  287. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +3 -3
  288. package/Tests/Server/Services/StatusPagePrivateUserInvitationPolicy.test.ts +107 -89
  289. package/Tests/Server/Services/TeamComplianceSettingPostgres.test.ts +1068 -176
  290. package/Tests/Server/Services/TeamComplianceSettingService.test.ts +1962 -200
  291. package/Tests/Server/Types/Database/Permissions/EditionPermission.test.ts +654 -297
  292. package/Tests/Server/Types/Database/Permissions/RetentionOverrideColumns.test.ts +784 -0
  293. package/Tests/Server/Types/Database/Permissions/SsoScalePlanGate.test.ts +418 -0
  294. package/Tests/Server/Utils/AI/AIAlertGating.test.ts +0 -1
  295. package/Tests/Server/Utils/AI/AIInvestigationEngineRunLimits.test.ts +136 -0
  296. package/Tests/Server/Utils/AI/AIInvestigationQueue.test.ts +15 -14
  297. package/Tests/Server/Utils/AI/AgentContextCompactor.test.ts +351 -0
  298. package/Tests/Server/Utils/AI/ChatAgentRunnerSharedThread.test.ts +842 -0
  299. package/Tests/Server/Utils/AI/ChatRunCancellation.test.ts +195 -0
  300. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +10 -57
  301. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +20 -24
  302. package/Tests/Server/Utils/AI/Insights/InvestigationQueueInsightSubject.test.ts +7 -30
  303. package/Tests/Server/Utils/AI/InvestigationClusterAccessWiring.test.ts +225 -29
  304. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +266 -25
  305. package/Tests/Server/Utils/AI/InvestigationThread.test.ts +580 -0
  306. package/Tests/Server/Utils/AI/InvestigationThreadService.test.ts +801 -0
  307. package/Tests/Server/Utils/AI/KubectlInvestigationToolkit.test.ts +525 -43
  308. package/Tests/Server/Utils/AI/KubectlJobRunner.test.ts +4 -2
  309. package/Tests/Server/Utils/AI/LLMServiceUnreachableProvider.test.ts +435 -0
  310. package/Tests/Server/Utils/AI/ObservabilityAssistantLimits.test.ts +462 -0
  311. package/Tests/Server/Utils/AI/RemediationExecutionRunner.test.ts +14 -25
  312. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +2 -0
  313. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +297 -15
  314. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +10 -6
  315. package/Tests/Server/Utils/AI/ToolOutputPager.test.ts +382 -0
  316. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +23 -4
  317. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +25 -6
  318. package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +14 -3
  319. package/Tests/Server/Utils/EditionEnforcement.test.ts +107 -147
  320. package/Tests/Server/Utils/{CommunityEditionSsoReport.test.ts → RelaxedScimTeamLocksReport.test.ts} +212 -208
  321. package/Tests/Server/Utils/SameOriginRequest.test.ts +372 -0
  322. package/Tests/Server/Utils/Telemetry/EntityRegistry.test.ts +20 -0
  323. package/Tests/Server/Utils/Telemetry/MessageQueueDiscovery.test.ts +2590 -0
  324. package/Tests/Server/Utils/Telemetry/MessageQueueSpanQueryTwin.ts +246 -0
  325. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +23 -0
  326. package/Tests/Types/AI/AIAgentRunLimits.test.ts +255 -0
  327. package/Tests/Types/Code/JsonSyntax.test.ts +1300 -0
  328. package/Tests/Types/CustomField/CustomFieldCreateSettings.test.ts +1113 -0
  329. package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +91 -0
  330. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +30 -0
  331. package/Tests/Types/Incident/IncidentFormIpAllowlist.test.ts +227 -0
  332. package/Tests/Types/Incident/IncidentFormPublic.test.ts +1916 -0
  333. package/Tests/Types/MessageQueue/MessageQueueEntityKey.test.ts +916 -0
  334. package/Tests/Types/MessageQueue/MessageQueueIdentity.test.ts +567 -0
  335. package/Tests/Types/MessageQueue/MessageQueueManualIdentity.test.ts +686 -0
  336. package/Tests/Types/MessageQueue/MessageQueueMetricCatalog.test.ts +1726 -0
  337. package/Tests/Types/MessageQueue/MessageQueueMetricResolution.test.ts +1758 -0
  338. package/Tests/Types/MessageQueue/MessagingInstrumentationFixtures.test.ts +4686 -0
  339. package/Tests/Types/MessageQueue/MessagingSystem.test.ts +840 -0
  340. package/Tests/Types/MessageQueue/MessagingTelemetryFixtures.ts +3287 -0
  341. package/Tests/Types/MessageQueue/MessagingTelemetryResolver.test.ts +2966 -0
  342. package/Tests/Types/Monitor/MessageQueueAlertTemplates.test.ts +5401 -0
  343. package/Tests/Types/Monitor/MetricEngineFold.ts +154 -0
  344. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +5 -3
  345. package/Tests/Types/Team/ComplianceRule.test.ts +588 -37
  346. package/Tests/Types/Team/TeamComplianceStatus.test.ts +64 -11
  347. package/Tests/Types/XML.test.ts +51 -0
  348. package/Tests/UI/Components/CodeEditor/CodeEditor.test.tsx +2362 -0
  349. package/Tests/UI/Components/CodeEditor/CodeEditorCommands.test.ts +810 -0
  350. package/Tests/UI/Components/CodeEditor/CodeEditorHighlight.test.ts +338 -0
  351. package/Tests/UI/Components/CodeEditor/CodeEditorLanguages.test.ts +222 -0
  352. package/Tests/UI/Components/CodeEditor/CodeEditorStyles.test.ts +807 -0
  353. package/Tests/UI/Components/CodeEditor/NoMonaco.test.ts +245 -0
  354. package/Tests/UI/Components/CodeEditor/YamlEditor.test.tsx +729 -0
  355. package/Tests/UI/Components/CopyTextButton.test.tsx +76 -0
  356. package/Tests/UI/Components/EditionLabelLapseCopy.ts +51 -14
  357. package/Tests/UI/Components/EditionLabelLicenseStatus.test.tsx +131 -23
  358. package/Tests/UI/Components/ErrorMessageAction.test.tsx +142 -0
  359. package/Tests/UI/Components/FacetSidebarInScope.test.tsx +459 -0
  360. package/Tests/UI/Components/FacetValuesInScope.test.ts +270 -0
  361. package/Tests/UI/Components/Forms/FormFieldCodeEditors.test.tsx +294 -0
  362. package/Tests/UI/Components/Forms/FormFieldMarkdown.test.tsx +126 -0
  363. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +22 -75
  364. package/Tests/UI/Components/Forms/ModelFormOnBeforeCreateMiscData.test.tsx +254 -0
  365. package/Tests/UI/Components/Icon.test.tsx +32 -0
  366. package/Tests/UI/Components/Input.test.tsx +31 -0
  367. package/Tests/UI/Components/LockedFilterChipScope.test.tsx +501 -0
  368. package/Tests/UI/Components/MarkdownConverters.test.ts +253 -1
  369. package/Tests/UI/Components/MarkdownEditor.test.tsx +2785 -2
  370. package/Tests/UI/Components/MarkdownEditorHistory.test.ts +243 -0
  371. package/Tests/UI/Components/MarkdownListEditing.test.ts +1118 -0
  372. package/Tests/UI/Components/MarkdownPaste.test.ts +1180 -0
  373. package/Tests/UI/Components/MarkdownViewerCopy.test.tsx +466 -0
  374. package/Tests/UI/Components/MarkdownVisualEditing.test.ts +1089 -0
  375. package/Tests/UI/Components/ModelTable/BaseModelTableEmptyStateAction.test.tsx +623 -0
  376. package/Tests/UI/Components/ResetObjectID.test.tsx +282 -0
  377. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  378. package/Tests/UI/Components/Table/EmptyTableMessage.test.ts +439 -0
  379. package/Tests/UI/Components/Table/TableEmptyState.test.tsx +352 -0
  380. package/Tests/UI/Components/TableRowMobileTitles.test.tsx +78 -0
  381. package/Tests/UI/Components/TextArea.test.tsx +19 -0
  382. package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +12 -29
  383. package/Tests/UI/Components/fixtures/MarkdownPasteFixtures.ts +460 -0
  384. package/Tests/UI/EsbuildConfig.test.ts +115 -39
  385. package/Tests/UI/EsbuildEnterpriseAlias.test.ts +4 -4
  386. package/Tests/UI/Monitor/MonitorLogSummaryRender.test.tsx +377 -0
  387. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +81 -1
  388. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +17 -0
  389. package/Tests/Utils/LabelRuleImportExport.test.ts +5 -0
  390. package/Tests/Utils/Markdown/DashboardMarkdownRenderer.ts +81 -0
  391. package/Tests/Utils/Markdown/UntrustedMarkdown.test.ts +1339 -0
  392. package/Tests/Utils/Monitor/MonitorLogSummaryUtil.test.ts +862 -0
  393. package/Types/AI/AIAgentRunLimits.ts +146 -0
  394. package/Types/Code/CodeType.ts +2 -0
  395. package/Types/Code/JsonSyntax.ts +923 -0
  396. package/Types/CustomField/CustomFieldCreateSettings.ts +549 -0
  397. package/Types/CustomField/CustomFieldMappingCatalog.ts +77 -0
  398. package/Types/CustomField/CustomFieldValueValidator.ts +10 -7
  399. package/Types/EnterpriseLicense/EnterpriseLicensePeriods.ts +1 -1
  400. package/Types/Icon/IconProp.ts +2 -0
  401. package/Types/Incident/IncidentFormIpAllowlist.ts +206 -0
  402. package/Types/Incident/IncidentFormPublic.ts +1145 -0
  403. package/Types/Kubernetes/KubernetesClusterAiAccess.ts +10 -3
  404. package/Types/MessageQueue/MessageQueueIdentity.ts +256 -0
  405. package/Types/MessageQueue/MessageQueueManualIdentity.ts +325 -0
  406. package/Types/MessageQueue/MessageQueueMetricCatalog.ts +1357 -0
  407. package/Types/MessageQueue/MessagingSystem.ts +518 -0
  408. package/Types/MessageQueue/MessagingTelemetryResolver.ts +2311 -0
  409. package/Types/Monitor/MessageQueueAlertTemplates.ts +2128 -0
  410. package/Types/Permission.ts +310 -0
  411. package/Types/ResourceAiAgent/ResourceAiAccess.ts +10 -4
  412. package/Types/Rules/RuleCriteriaFieldRegistry.ts +12 -0
  413. package/Types/Rules/RuleRun.ts +4 -0
  414. package/Types/Team/ComplianceNotificationChannel.ts +4 -3
  415. package/Types/Team/ComplianceRule.ts +71 -18
  416. package/Types/Team/ComplianceRuleType.ts +2 -2
  417. package/Types/Team/TeamComplianceStatus.ts +6 -2
  418. package/Types/Telemetry/EntityType.ts +16 -0
  419. package/Types/Telemetry/LockedFilterDetail.ts +22 -0
  420. package/Types/XML.ts +26 -0
  421. package/UI/Components/CodeEditor/CodeEditor.css +379 -0
  422. package/UI/Components/CodeEditor/CodeEditor.tsx +1128 -294
  423. package/UI/Components/CodeEditor/CodeEditorCommands.ts +774 -0
  424. package/UI/Components/CodeEditor/CodeEditorHighlight.ts +106 -0
  425. package/UI/Components/CodeEditor/CodeEditorLanguages.ts +164 -0
  426. package/UI/Components/CodeEditor/YamlEditor.tsx +28 -326
  427. package/UI/Components/CopyTextButton/CopyTextButton.tsx +5 -1
  428. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +6 -25
  429. package/UI/Components/EditionLabel/EditionLabel.tsx +14 -13
  430. package/UI/Components/ErrorMessage/ErrorMessage.tsx +11 -1
  431. package/UI/Components/Forms/Fields/FormField.tsx +8 -0
  432. package/UI/Components/Forms/Types/Field.ts +8 -0
  433. package/UI/Components/Icon/Icon.tsx +18 -0
  434. package/UI/Components/Input/Input.tsx +11 -4
  435. package/UI/Components/LogsViewer/LogsViewer.tsx +4 -0
  436. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +21 -3
  437. package/UI/Components/Markdown.tsx/MarkdownConverters.ts +0 -0
  438. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +877 -79
  439. package/UI/Components/Markdown.tsx/MarkdownEditorHistory.ts +284 -0
  440. package/UI/Components/Markdown.tsx/MarkdownListEditing.ts +1362 -0
  441. package/UI/Components/Markdown.tsx/MarkdownPaste.ts +1326 -0
  442. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +106 -10
  443. package/UI/Components/Markdown.tsx/MarkdownVisualEditing.ts +1032 -0
  444. package/UI/Components/ModelTable/BaseModelTable.tsx +85 -7
  445. package/UI/Components/ResetObjectID/ResetObjectID.tsx +35 -9
  446. package/UI/Components/Table/EmptyTableMessage.ts +81 -0
  447. package/UI/Components/Table/Table.tsx +50 -21
  448. package/UI/Components/Table/TableRow.tsx +9 -1
  449. package/UI/Components/TelemetryViewer/FacetVisibility.ts +97 -2
  450. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +13 -0
  451. package/UI/Components/TelemetryViewer/components/LockedFilterChip.tsx +214 -7
  452. package/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.tsx +21 -3
  453. package/UI/Components/TextArea/TextArea.tsx +9 -2
  454. package/UI/Components/Workflow/ModelFieldPicker.tsx +10 -12
  455. package/UI/Styles/Theme.css +8 -0
  456. package/UI/esbuild-config.js +2 -32
  457. package/Utils/LabelRuleImportExport.ts +2 -0
  458. package/Utils/Markdown/UntrustedMarkdown.ts +1513 -0
  459. package/Utils/Monitor/IncomingEmailMonitorRequestUtil.ts +41 -0
  460. package/Utils/Monitor/MonitorLogSummaryUtil.ts +212 -0
  461. package/Utils/Telemetry/EntityKey.ts +48 -0
  462. package/build/dist/Models/DatabaseModels/AIConversation.js +125 -1
  463. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  464. package/build/dist/Models/DatabaseModels/CephCluster.js +12 -0
  465. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  466. package/build/dist/Models/DatabaseModels/CloudResource.js +12 -0
  467. package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
  468. package/build/dist/Models/DatabaseModels/DatabaseServer.js +12 -0
  469. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  470. package/build/dist/Models/DatabaseModels/DockerHost.js +12 -0
  471. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  472. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +12 -0
  473. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  474. package/build/dist/Models/DatabaseModels/GlobalOidc.js +0 -4
  475. package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
  476. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js +0 -4
  477. package/build/dist/Models/DatabaseModels/GlobalOidcProject.js.map +1 -1
  478. package/build/dist/Models/DatabaseModels/GlobalSso.js +0 -4
  479. package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
  480. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js +0 -4
  481. package/build/dist/Models/DatabaseModels/GlobalSsoProject.js.map +1 -1
  482. package/build/dist/Models/DatabaseModels/Host.js +12 -0
  483. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  484. package/build/dist/Models/DatabaseModels/IncidentForm.js +605 -0
  485. package/build/dist/Models/DatabaseModels/IncidentForm.js.map +1 -0
  486. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js +299 -0
  487. package/build/dist/Models/DatabaseModels/IncidentFormSubmission.js.map +1 -0
  488. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +60 -0
  489. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  490. package/build/dist/Models/DatabaseModels/Index.js +14 -0
  491. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  492. package/build/dist/Models/DatabaseModels/IoTFleet.js +12 -0
  493. package/build/dist/Models/DatabaseModels/IoTFleet.js.map +1 -1
  494. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +12 -0
  495. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  496. package/build/dist/Models/DatabaseModels/MessageQueue.js +1106 -0
  497. package/build/dist/Models/DatabaseModels/MessageQueue.js.map +1 -0
  498. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js +562 -0
  499. package/build/dist/Models/DatabaseModels/MessageQueueLabelRule.js.map +1 -0
  500. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js +643 -0
  501. package/build/dist/Models/DatabaseModels/MessageQueueOwnerRule.js.map +1 -0
  502. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js +506 -0
  503. package/build/dist/Models/DatabaseModels/MessageQueueOwnerTeam.js.map +1 -0
  504. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js +505 -0
  505. package/build/dist/Models/DatabaseModels/MessageQueueOwnerUser.js.map +1 -0
  506. package/build/dist/Models/DatabaseModels/PodmanHost.js +12 -0
  507. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  508. package/build/dist/Models/DatabaseModels/Project.js +61 -0
  509. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  510. package/build/dist/Models/DatabaseModels/ProjectOidc.js +0 -4
  511. package/build/dist/Models/DatabaseModels/ProjectOidc.js.map +1 -1
  512. package/build/dist/Models/DatabaseModels/ProjectSso.js +0 -4
  513. package/build/dist/Models/DatabaseModels/ProjectSso.js.map +1 -1
  514. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +12 -0
  515. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  516. package/build/dist/Models/DatabaseModels/RumApplication.js +12 -0
  517. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  518. package/build/dist/Models/DatabaseModels/ServerlessFunction.js +12 -0
  519. package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
  520. package/build/dist/Models/DatabaseModels/Service.js +12 -0
  521. package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
  522. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +0 -4
  523. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  524. package/build/dist/Models/DatabaseModels/StatusPageSso.js +0 -4
  525. package/build/dist/Models/DatabaseModels/StatusPageSso.js.map +1 -1
  526. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js +49 -3
  527. package/build/dist/Models/DatabaseModels/TeamComplianceSetting.js.map +1 -1
  528. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +12 -0
  529. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  530. package/build/dist/Server/API/AIChatAPI.js +9 -103
  531. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  532. package/build/dist/Server/API/AIInvestigationAPI.js +3 -3
  533. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  534. package/build/dist/Server/API/AIInvestigationConversationAPI.js +162 -0
  535. package/build/dist/Server/API/AIInvestigationConversationAPI.js.map +1 -0
  536. package/build/dist/Server/API/IncidentFormAPI.js +245 -0
  537. package/build/dist/Server/API/IncidentFormAPI.js.map +1 -0
  538. package/build/dist/Server/API/ProjectOIDC.js +16 -24
  539. package/build/dist/Server/API/ProjectOIDC.js.map +1 -1
  540. package/build/dist/Server/API/ProjectSSO.js +16 -24
  541. package/build/dist/Server/API/ProjectSSO.js.map +1 -1
  542. package/build/dist/Server/API/StatusPageAPI.js +42 -75
  543. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  544. package/build/dist/Server/Enterprise/EnterpriseEdition.js +79 -25
  545. package/build/dist/Server/Enterprise/EnterpriseEdition.js.map +1 -1
  546. package/build/dist/Server/Enterprise/EnterpriseFeature.js +24 -3
  547. package/build/dist/Server/Enterprise/EnterpriseFeature.js.map +1 -1
  548. package/build/dist/Server/Enterprise/EnterpriseLicenseSnapshot.js +1 -1
  549. package/build/dist/Server/Enterprise/EnterpriseServerModule.js.map +1 -1
  550. package/build/dist/Server/EnvironmentConfig.js +1 -1
  551. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js +63 -0
  552. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796400000000-AddIncidentForms.js.map +1 -0
  553. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js +41 -0
  554. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796500000000-AddTeamComplianceRuleNotificationChannels.js.map +1 -0
  555. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js +40 -0
  556. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796600000000-AddAIInvestigationConversationAndTimeLimits.js.map +1 -0
  557. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js +183 -0
  558. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796700000000-AddMessageQueueTables.js.map +1 -0
  559. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  560. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  561. package/build/dist/Server/Middleware/IncidentFormRateLimit.js +559 -0
  562. package/build/dist/Server/Middleware/IncidentFormRateLimit.js.map +1 -0
  563. package/build/dist/Server/Middleware/UserAuthorization.js +11 -30
  564. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  565. package/build/dist/Server/Services/AIService.js +10 -13
  566. package/build/dist/Server/Services/AIService.js.map +1 -1
  567. package/build/dist/Server/Services/IncidentCustomFieldService.js +83 -0
  568. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  569. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +8 -4
  570. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  571. package/build/dist/Server/Services/IncidentEpisodeService.js +10 -1
  572. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  573. package/build/dist/Server/Services/IncidentFormService.js +1202 -0
  574. package/build/dist/Server/Services/IncidentFormService.js.map +1 -0
  575. package/build/dist/Server/Services/IncidentFormSubmissionService.js +91 -0
  576. package/build/dist/Server/Services/IncidentFormSubmissionService.js.map +1 -0
  577. package/build/dist/Server/Services/IncidentGroupingEngineService.js +20 -8
  578. package/build/dist/Server/Services/IncidentGroupingEngineService.js.map +1 -1
  579. package/build/dist/Server/Services/IncidentService.js +106 -4
  580. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  581. package/build/dist/Server/Services/IncidentTemplateService.js +18 -0
  582. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  583. package/build/dist/Server/Services/Index.js +14 -0
  584. package/build/dist/Server/Services/Index.js.map +1 -1
  585. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js +262 -0
  586. package/build/dist/Server/Services/MessageQueueLabelRuleEngineService.js.map +1 -0
  587. package/build/dist/Server/Services/MessageQueueLabelRuleService.js +13 -0
  588. package/build/dist/Server/Services/MessageQueueLabelRuleService.js.map +1 -0
  589. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js +334 -0
  590. package/build/dist/Server/Services/MessageQueueOwnerRuleEngineService.js.map +1 -0
  591. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js +13 -0
  592. package/build/dist/Server/Services/MessageQueueOwnerRuleService.js.map +1 -0
  593. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js +62 -0
  594. package/build/dist/Server/Services/MessageQueueOwnerTeamService.js.map +1 -0
  595. package/build/dist/Server/Services/MessageQueueOwnerUserService.js +62 -0
  596. package/build/dist/Server/Services/MessageQueueOwnerUserService.js.map +1 -0
  597. package/build/dist/Server/Services/MessageQueueService.js +1140 -0
  598. package/build/dist/Server/Services/MessageQueueService.js.map +1 -0
  599. package/build/dist/Server/Services/OpenTelemetryIngestService.js +25 -4
  600. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  601. package/build/dist/Server/Services/ProjectService.js +0 -54
  602. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  603. package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -4
  604. package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
  605. package/build/dist/Server/Services/StatusPageService.js +0 -46
  606. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  607. package/build/dist/Server/Services/TeamComplianceSettingService.js +120 -31
  608. package/build/dist/Server/Services/TeamComplianceSettingService.js.map +1 -1
  609. package/build/dist/Server/Services/UserNotificationRuleService.js +8 -1
  610. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  611. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +12 -3
  612. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  613. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +6 -3
  614. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  615. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js +109 -37
  616. package/build/dist/Server/Types/Database/Permissions/EditionPermission.js.map +1 -1
  617. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +16 -0
  618. package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
  619. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +3 -3
  620. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js +9 -6
  621. package/build/dist/Server/Types/Database/Permissions/UpdatePermission.js.map +1 -1
  622. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js +128 -0
  623. package/build/dist/Server/Utils/AI/Chat/AgentContextCompactor.js.map +1 -0
  624. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +192 -50
  625. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  626. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js +133 -0
  627. package/build/dist/Server/Utils/AI/Chat/ChatRunCancellation.js.map +1 -0
  628. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +53 -13
  629. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  630. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  631. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js +206 -0
  632. package/build/dist/Server/Utils/AI/Chat/ToolOutputPager.js.map +1 -0
  633. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js +53 -26
  634. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlInvestigationToolkit.js.map +1 -1
  635. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js +4 -2
  636. package/build/dist/Server/Utils/AI/ClusterAccess/KubectlJobRunner.js.map +1 -1
  637. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +1 -29
  638. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -1
  639. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +24 -24
  640. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  641. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +7 -2
  642. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  643. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +29 -14
  644. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  645. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +38 -14
  646. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -1
  647. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +2 -0
  648. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -1
  649. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +40 -10
  650. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  651. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +19 -7
  652. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  653. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +19 -7
  654. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  655. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js +15 -18
  656. package/build/dist/Server/Utils/AI/SRE/InvestigationQueue.js.map +1 -1
  657. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js +412 -0
  658. package/build/dist/Server/Utils/AI/SRE/InvestigationThread.js.map +1 -0
  659. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js +495 -0
  660. package/build/dist/Server/Utils/AI/SRE/InvestigationThreadService.js.map +1 -0
  661. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +10 -3
  662. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  663. package/build/dist/Server/Utils/EditionEnforcement.js +20 -158
  664. package/build/dist/Server/Utils/EditionEnforcement.js.map +1 -1
  665. package/build/dist/Server/Utils/LLM/LLMService.js +193 -54
  666. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  667. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js +160 -0
  668. package/build/dist/Server/Utils/RelaxedScimTeamLocksReport.js.map +1 -0
  669. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +25 -0
  670. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  671. package/build/dist/Server/Utils/SameOriginRequest.js +151 -0
  672. package/build/dist/Server/Utils/SameOriginRequest.js.map +1 -0
  673. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js +2 -0
  674. package/build/dist/Server/Utils/TeamMember/ProjectLeaveResourceCleanup.js.map +1 -1
  675. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +4 -1
  676. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  677. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js +1015 -0
  678. package/build/dist/Server/Utils/Telemetry/MessageQueueDiscovery.js.map +1 -0
  679. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +4 -1
  680. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -1
  681. package/build/dist/Server/Utils/VendorAssets.js +3 -3
  682. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +3 -1
  683. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  684. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +3 -1
  685. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  686. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -1
  687. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  688. package/build/dist/Types/AI/AIAgentRunLimits.js +91 -0
  689. package/build/dist/Types/AI/AIAgentRunLimits.js.map +1 -0
  690. package/build/dist/Types/Code/CodeType.js +2 -0
  691. package/build/dist/Types/Code/CodeType.js.map +1 -1
  692. package/build/dist/Types/Code/JsonSyntax.js +604 -0
  693. package/build/dist/Types/Code/JsonSyntax.js.map +1 -0
  694. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js +310 -0
  695. package/build/dist/Types/CustomField/CustomFieldCreateSettings.js.map +1 -0
  696. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +38 -0
  697. package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -1
  698. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +10 -7
  699. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -1
  700. package/build/dist/Types/EnterpriseLicense/EnterpriseLicensePeriods.js +1 -1
  701. package/build/dist/Types/Icon/IconProp.js +2 -0
  702. package/build/dist/Types/Icon/IconProp.js.map +1 -1
  703. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js +119 -0
  704. package/build/dist/Types/Incident/IncidentFormIpAllowlist.js.map +1 -0
  705. package/build/dist/Types/Incident/IncidentFormPublic.js +678 -0
  706. package/build/dist/Types/Incident/IncidentFormPublic.js.map +1 -0
  707. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js +10 -3
  708. package/build/dist/Types/Kubernetes/KubernetesClusterAiAccess.js.map +1 -1
  709. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js +184 -0
  710. package/build/dist/Types/MessageQueue/MessageQueueIdentity.js.map +1 -0
  711. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js +190 -0
  712. package/build/dist/Types/MessageQueue/MessageQueueManualIdentity.js.map +1 -0
  713. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js +1057 -0
  714. package/build/dist/Types/MessageQueue/MessageQueueMetricCatalog.js.map +1 -0
  715. package/build/dist/Types/MessageQueue/MessagingSystem.js +394 -0
  716. package/build/dist/Types/MessageQueue/MessagingSystem.js.map +1 -0
  717. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js +1768 -0
  718. package/build/dist/Types/MessageQueue/MessagingTelemetryResolver.js.map +1 -0
  719. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js +1207 -0
  720. package/build/dist/Types/Monitor/MessageQueueAlertTemplates.js.map +1 -0
  721. package/build/dist/Types/Permission.js +274 -0
  722. package/build/dist/Types/Permission.js.map +1 -1
  723. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +10 -4
  724. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -1
  725. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +12 -0
  726. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  727. package/build/dist/Types/Rules/RuleRun.js +4 -0
  728. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  729. package/build/dist/Types/Team/ComplianceNotificationChannel.js +4 -3
  730. package/build/dist/Types/Team/ComplianceNotificationChannel.js.map +1 -1
  731. package/build/dist/Types/Team/ComplianceRule.js +48 -14
  732. package/build/dist/Types/Team/ComplianceRule.js.map +1 -1
  733. package/build/dist/Types/Team/ComplianceRuleType.js +2 -2
  734. package/build/dist/Types/Telemetry/EntityType.js +16 -0
  735. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  736. package/build/dist/Types/XML.js +23 -0
  737. package/build/dist/Types/XML.js.map +1 -1
  738. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +641 -266
  739. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  740. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js +464 -0
  741. package/build/dist/UI/Components/CodeEditor/CodeEditorCommands.js.map +1 -0
  742. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js +69 -0
  743. package/build/dist/UI/Components/CodeEditor/CodeEditorHighlight.js.map +1 -0
  744. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js +108 -0
  745. package/build/dist/UI/Components/CodeEditor/CodeEditorLanguages.js.map +1 -0
  746. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +14 -186
  747. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -1
  748. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js +5 -1
  749. package/build/dist/UI/Components/CopyTextButton/CopyTextButton.js.map +1 -1
  750. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +5 -14
  751. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -1
  752. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +14 -13
  753. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  754. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js +1 -1
  755. package/build/dist/UI/Components/ErrorMessage/ErrorMessage.js.map +1 -1
  756. package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -3
  757. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  758. package/build/dist/UI/Components/Icon/Icon.js +8 -0
  759. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  760. package/build/dist/UI/Components/Input/Input.js +11 -3
  761. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  762. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +2 -1
  763. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  764. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +14 -4
  765. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  766. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js +0 -0
  767. package/build/dist/UI/Components/Markdown.tsx/MarkdownConverters.js.map +1 -1
  768. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +710 -65
  769. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  770. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js +238 -0
  771. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditorHistory.js.map +1 -0
  772. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js +1028 -0
  773. package/build/dist/UI/Components/Markdown.tsx/MarkdownListEditing.js.map +1 -0
  774. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js +1088 -0
  775. package/build/dist/UI/Components/Markdown.tsx/MarkdownPaste.js.map +1 -0
  776. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +70 -10
  777. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  778. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js +887 -0
  779. package/build/dist/UI/Components/Markdown.tsx/MarkdownVisualEditing.js.map +1 -0
  780. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +57 -6
  781. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  782. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js +7 -5
  783. package/build/dist/UI/Components/ResetObjectID/ResetObjectID.js.map +1 -1
  784. package/build/dist/UI/Components/Table/EmptyTableMessage.js +51 -0
  785. package/build/dist/UI/Components/Table/EmptyTableMessage.js.map +1 -0
  786. package/build/dist/UI/Components/Table/Table.js +30 -11
  787. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  788. package/build/dist/UI/Components/Table/TableRow.js +9 -2
  789. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  790. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js +63 -1
  791. package/build/dist/UI/Components/TelemetryViewer/FacetVisibility.js.map +1 -1
  792. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +4 -2
  793. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  794. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js +91 -10
  795. package/build/dist/UI/Components/TelemetryViewer/components/LockedFilterChip.js.map +1 -1
  796. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js +14 -4
  797. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryFacetSidebar.js.map +1 -1
  798. package/build/dist/UI/Components/TextArea/TextArea.js +9 -3
  799. package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
  800. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js +1 -2
  801. package/build/dist/UI/Components/Workflow/ModelFieldPicker.js.map +1 -1
  802. package/build/dist/Utils/LabelRuleImportExport.js +2 -0
  803. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  804. package/build/dist/Utils/Markdown/UntrustedMarkdown.js +988 -0
  805. package/build/dist/Utils/Markdown/UntrustedMarkdown.js.map +1 -0
  806. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js +34 -0
  807. package/build/dist/Utils/Monitor/IncomingEmailMonitorRequestUtil.js.map +1 -0
  808. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js +148 -0
  809. package/build/dist/Utils/Monitor/MonitorLogSummaryUtil.js.map +1 -0
  810. package/build/dist/Utils/Telemetry/EntityKey.js +36 -0
  811. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  812. package/package.json +1 -3
  813. package/Server/Utils/CommunityEditionSsoReport.ts +0 -361
  814. package/Tests/App/Accounts/LoginSsoEdition.test.tsx +0 -616
  815. package/Tests/App/AdminDashboard/AdminAuthenticationSsoEdition.test.tsx +0 -217
  816. package/Tests/App/AdminDashboard/GlobalSSOEnterpriseShells.test.tsx +0 -271
  817. package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +0 -215
  818. package/Tests/App/Dashboard/TelemetryResourceRetentionSettings.test.tsx +0 -784
  819. package/Tests/Server/Middleware/UserAuthorizationEditionEnforcement.test.ts +0 -518
  820. package/Tests/Server/Services/SsoRequirementReadMasking.test.ts +0 -419
  821. package/Tests/Server/Services/SsoRequirementWriteGuard.test.ts +0 -769
  822. package/Tests/UI/Components/CodeEditor.test.tsx +0 -638
  823. package/Tests/UI/Components/MonacoLoader.test.ts +0 -67
  824. package/Tests/UI/Components/MonacoRuntime.test.ts +0 -121
  825. package/Tests/UI/Components/Telemetry/TelemetryRetentionConfigForm.test.tsx +0 -329
  826. package/Tests/UI/Components/YamlEditor.test.tsx +0 -847
  827. package/UI/Components/CodeEditor/MonacoLoader.ts +0 -36
  828. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -319
  829. package/UI/Components/Telemetry/TelemetryRetentionConfigSummary.tsx +0 -223
  830. package/build/dist/Server/Utils/CommunityEditionSsoReport.js +0 -223
  831. package/build/dist/Server/Utils/CommunityEditionSsoReport.js.map +0 -1
  832. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +0 -34
  833. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +0 -1
  834. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js +0 -171
  835. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +0 -1
  836. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js +0 -119
  837. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigSummary.js.map +0 -1
@@ -0,0 +1,2966 @@
1
+ import {
2
+ AZURE_MESSAGING_PROVIDER_NAMESPACES,
3
+ AZURE_RESOURCE_PROVIDER_ATTRIBUTES,
4
+ hasMessagingTrigger,
5
+ isTemporaryMessagingDestination,
6
+ MESSAGE_QUEUE_DESTINATION_MAX_LENGTH,
7
+ MESSAGING_ADDRESS_ATTRIBUTES,
8
+ MESSAGING_BROKER_SCOPE_ADDRESS_ATTRIBUTES,
9
+ MESSAGING_CONSUMER_GROUP_ATTRIBUTES,
10
+ MESSAGING_DESTINATION_ATTRIBUTES,
11
+ MESSAGING_DIRECTION_ATTRIBUTES,
12
+ MESSAGING_PROTOCOL_NAME_ATTRIBUTE,
13
+ MESSAGING_RESOLVER_INPUT_ATTRIBUTES,
14
+ MESSAGING_SYSTEM_ATTRIBUTE,
15
+ MESSAGING_TEMPORARY_FLAG_ATTRIBUTES,
16
+ MESSAGING_TRIGGER_ATTRIBUTES,
17
+ MessagingAddressAttribute,
18
+ RABBITMQ_ROUTING_KEY_ATTRIBUTES,
19
+ ResolvedMessagingDestination,
20
+ resolveMessagingSpan,
21
+ RPC_SYSTEM_ATTRIBUTES,
22
+ SNS_TOPIC_ARN_ATTRIBUTES,
23
+ SQS_QUEUE_URL_ATTRIBUTES,
24
+ } from "../../../Types/MessageQueue/MessagingTelemetryResolver";
25
+ import {
26
+ MESSAGING_SYSTEMS,
27
+ MessagingSystemDescriptor,
28
+ } from "../../../Types/MessageQueue/MessagingSystem";
29
+ import { SpanKind } from "../../../Models/AnalyticsModels/Span";
30
+ import {
31
+ CLIENT,
32
+ CONSUMER,
33
+ destinationOf,
34
+ FixtureAttributes,
35
+ INTERNAL,
36
+ NUMERIC_SPAN_KIND_CASES,
37
+ NumericSpanKindCase,
38
+ PRODUCER,
39
+ SERVER,
40
+ SPAN_FIXTURES,
41
+ SpanFixture,
42
+ toStoredColumns,
43
+ toStoredKind,
44
+ } from "./MessagingTelemetryFixtures";
45
+ import { describe, expect, test } from "@jest/globals";
46
+
47
+ function resolveSpan(
48
+ attributes: FixtureAttributes,
49
+ kind: string | null = null,
50
+ ): ResolvedMessagingDestination | null {
51
+ return resolveMessagingSpan({
52
+ getAttribute: (key: string): unknown => {
53
+ return attributes[key];
54
+ },
55
+ kind,
56
+ });
57
+ }
58
+
59
+ // Resolve, recording every attribute key the resolver reads.
60
+ function resolveRecordingReads(
61
+ attributes: FixtureAttributes,
62
+ kind: string | null,
63
+ reads: Set<string>,
64
+ ): ResolvedMessagingDestination | null {
65
+ return resolveMessagingSpan({
66
+ getAttribute: (key: string): unknown => {
67
+ reads.add(key);
68
+ return attributes[key];
69
+ },
70
+ kind,
71
+ });
72
+ }
73
+
74
+ function destinationFor(
75
+ system: string,
76
+ destination: string,
77
+ extra: FixtureAttributes = {},
78
+ ): string | null {
79
+ const resolved: ResolvedMessagingDestination | null = resolveSpan(
80
+ {
81
+ "messaging.system": system,
82
+ "messaging.destination.name": destination,
83
+ ...extra,
84
+ },
85
+ PRODUCER,
86
+ );
87
+ return resolved ? resolved.destination : null;
88
+ }
89
+
90
+ describe("span kinds are the stored SpanKind strings", () => {
91
+ test("the literals the resolver compares with are the Span model's enum values", () => {
92
+ expect(PRODUCER).toBe(SpanKind.Producer);
93
+ expect(CONSUMER).toBe(SpanKind.Consumer);
94
+ expect(CLIENT).toBe(SpanKind.Client);
95
+ expect(INTERNAL).toBe(SpanKind.Internal);
96
+ expect(SERVER).toBe(SpanKind.Server);
97
+ });
98
+ });
99
+
100
+ describe("attribute precedence lists", () => {
101
+ test("triggers", () => {
102
+ expect(MESSAGING_TRIGGER_ATTRIBUTES).toEqual([
103
+ "messaging.system",
104
+ "messaging.destination.name",
105
+ "messaging.destination",
106
+ "message_bus.destination",
107
+ "az.namespace",
108
+ "azure.resource_provider.namespace",
109
+ "aws.sqs.queue.url",
110
+ "aws.queue_url",
111
+ "aws.sns.topic.arn",
112
+ ]);
113
+ });
114
+
115
+ test("destination: template, name, the 1.17-1.20 source keys, pre-1.17, Azure legacy", () => {
116
+ expect(MESSAGING_DESTINATION_ATTRIBUTES).toEqual([
117
+ "messaging.destination.template",
118
+ "messaging.destination.name",
119
+ "messaging.source.template",
120
+ "messaging.source.name",
121
+ "messaging.destination",
122
+ "message_bus.destination",
123
+ ]);
124
+ });
125
+
126
+ test("temporary flags never include the untrustworthy pre-1.17 key", () => {
127
+ expect(MESSAGING_TEMPORARY_FLAG_ATTRIBUTES).toEqual([
128
+ "messaging.destination.temporary",
129
+ "messaging.destination.anonymous",
130
+ "messaging.source.temporary",
131
+ "messaging.source.anonymous",
132
+ ]);
133
+ expect(MESSAGING_RESOLVER_INPUT_ATTRIBUTES).not.toContain(
134
+ "messaging.temp_destination",
135
+ );
136
+ });
137
+
138
+ test("address: current, pre-1.21, 1.22+ peer, sockets, Azure legacy, messaging.url", () => {
139
+ expect(MESSAGING_ADDRESS_ATTRIBUTES).toEqual([
140
+ { address: "server.address", port: "server.port" },
141
+ { address: "net.peer.name", port: "net.peer.port" },
142
+ { address: "network.peer.address", port: "network.peer.port" },
143
+ { address: "net.sock.peer.addr", port: null },
144
+ { address: "server.socket.address", port: null },
145
+ { address: "peer.address", port: null },
146
+ { address: "messaging.url", port: null },
147
+ ]);
148
+ expect(MESSAGING_BROKER_SCOPE_ADDRESS_ATTRIBUTES).toEqual([
149
+ "server.address",
150
+ "net.peer.name",
151
+ "peer.address",
152
+ "network.peer.address",
153
+ ]);
154
+ });
155
+
156
+ test("direction, consumer group and system-specific keys", () => {
157
+ expect(MESSAGING_DIRECTION_ATTRIBUTES).toEqual([
158
+ "messaging.operation.type",
159
+ "messaging.operation",
160
+ "messaging.operation.name",
161
+ "rpc.method",
162
+ ]);
163
+ expect(MESSAGING_CONSUMER_GROUP_ATTRIBUTES).toEqual([
164
+ "messaging.consumer.group.name",
165
+ "messaging.destination.subscription.name",
166
+ "messaging.kafka.consumer.group",
167
+ "messaging.kafka.consumer_group",
168
+ "messaging.rocketmq.client_group",
169
+ "messaging.eventhubs.consumer.group",
170
+ "messaging.servicebus.destination.subscription_name",
171
+ // azure-core-metrics-opentelemetry's name for subscriptionName.
172
+ "messaging.servicebus.subscription_name",
173
+ ]);
174
+ expect(RABBITMQ_ROUTING_KEY_ATTRIBUTES).toEqual([
175
+ "messaging.rabbitmq.destination.routing_key",
176
+ "messaging.rabbitmq.routing_key",
177
+ ]);
178
+ expect(MESSAGING_PROTOCOL_NAME_ATTRIBUTE).toBe("network.protocol.name");
179
+ expect(SQS_QUEUE_URL_ATTRIBUTES).toEqual([
180
+ "aws.sqs.queue.url",
181
+ "aws.queue_url",
182
+ ]);
183
+ expect(SNS_TOPIC_ARN_ATTRIBUTES).toEqual(["aws.sns.topic.arn"]);
184
+ expect(AZURE_RESOURCE_PROVIDER_ATTRIBUTES).toEqual([
185
+ "az.namespace",
186
+ "azure.resource_provider.namespace",
187
+ ]);
188
+ expect(RPC_SYSTEM_ATTRIBUTES).toEqual(["rpc.system", "rpc.system.name"]);
189
+ expect(MESSAGING_SYSTEM_ATTRIBUTE).toBe("messaging.system");
190
+ });
191
+ });
192
+
193
+ describe("MESSAGING_RESOLVER_INPUT_ATTRIBUTES", () => {
194
+ test("has no duplicates and no blank keys", () => {
195
+ expect(new Set(MESSAGING_RESOLVER_INPUT_ATTRIBUTES).size).toBe(
196
+ MESSAGING_RESOLVER_INPUT_ATTRIBUTES.length,
197
+ );
198
+ for (const key of MESSAGING_RESOLVER_INPUT_ATTRIBUTES) {
199
+ expect(key).toBe(key.trim());
200
+ expect(key.length).toBeGreaterThan(0);
201
+ }
202
+ });
203
+
204
+ test("includes every precedence list", () => {
205
+ const addressKeys: Array<string> = [];
206
+ for (const attribute of MESSAGING_ADDRESS_ATTRIBUTES) {
207
+ addressKeys.push(attribute.address);
208
+ if (attribute.port) {
209
+ addressKeys.push(attribute.port);
210
+ }
211
+ }
212
+ for (const key of [
213
+ ...MESSAGING_TRIGGER_ATTRIBUTES,
214
+ ...MESSAGING_DESTINATION_ATTRIBUTES,
215
+ ...MESSAGING_TEMPORARY_FLAG_ATTRIBUTES,
216
+ ...addressKeys,
217
+ ...MESSAGING_BROKER_SCOPE_ADDRESS_ATTRIBUTES,
218
+ ...MESSAGING_DIRECTION_ATTRIBUTES,
219
+ ...MESSAGING_CONSUMER_GROUP_ATTRIBUTES,
220
+ ...RABBITMQ_ROUTING_KEY_ATTRIBUTES,
221
+ ...SQS_QUEUE_URL_ATTRIBUTES,
222
+ ...SNS_TOPIC_ARN_ATTRIBUTES,
223
+ ...AZURE_RESOURCE_PROVIDER_ATTRIBUTES,
224
+ ...RPC_SYSTEM_ATTRIBUTES,
225
+ MESSAGING_PROTOCOL_NAME_ATTRIBUTE,
226
+ "rpc.service",
227
+ "component",
228
+ ]) {
229
+ expect(MESSAGING_RESOLVER_INPUT_ATTRIBUTES).toContain(key);
230
+ }
231
+ });
232
+
233
+ test("never names a per-message or per-instance value (the cron groups by every key)", () => {
234
+ for (const forbidden of [
235
+ "messaging.message.id",
236
+ "messaging.message_id",
237
+ "messaging.message.conversation_id",
238
+ "messaging.conversation_id",
239
+ "messaging.message.body.size",
240
+ "messaging.message.envelope.size",
241
+ "messaging.batch.message_count",
242
+ "messaging.kafka.message.offset",
243
+ "messaging.kafka.offset",
244
+ "messaging.kafka.partition",
245
+ "messaging.kafka.destination.partition",
246
+ "messaging.destination.partition.id",
247
+ "messaging.consumer.id",
248
+ "messaging.consumer_id",
249
+ "messaging.client.id",
250
+ "messaging.client_id",
251
+ "messaging.kafka.client_id",
252
+ "messaging.rocketmq.message.id",
253
+ "messaging.rocketmq.queue_id",
254
+ "messaging.rocketmq.queue_offset",
255
+ "messaging.gcp_pubsub.message.id",
256
+ "messaging.gcp_pubsub.message.ack_id",
257
+ "messaging.servicebus.message.delivery_count",
258
+ "messaging.servicebus.message.enqueued_time",
259
+ "url.full",
260
+ "http.url",
261
+ "gcp.resource.name",
262
+ "service.instance.id",
263
+ "k8s.pod.name",
264
+ "host.name",
265
+ "container.id",
266
+ ]) {
267
+ expect(MESSAGING_RESOLVER_INPUT_ATTRIBUTES).not.toContain(forbidden);
268
+ }
269
+ for (const key of MESSAGING_RESOLVER_INPUT_ATTRIBUTES) {
270
+ // Span attributes only: resource and scope keys never identify a queue.
271
+ expect(key.startsWith("resource.")).toBe(false);
272
+ expect(key.startsWith("scope.")).toBe(false);
273
+ }
274
+ });
275
+
276
+ test("the resolver never reads a key outside the list, over every fixture and kind", () => {
277
+ const outside: Set<string> = new Set<string>();
278
+ const allowed: Set<string> = new Set<string>(
279
+ MESSAGING_RESOLVER_INPUT_ATTRIBUTES,
280
+ );
281
+ for (const fixture of SPAN_FIXTURES) {
282
+ for (const kind of [
283
+ fixture.kind,
284
+ PRODUCER,
285
+ CONSUMER,
286
+ CLIENT,
287
+ INTERNAL,
288
+ null,
289
+ ]) {
290
+ const reads: Set<string> = new Set<string>();
291
+ resolveRecordingReads(fixture.attributes, kind, reads);
292
+ for (const key of reads) {
293
+ if (!allowed.has(key)) {
294
+ outside.add(`${fixture.name}: ${key}`);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ expect([...outside]).toEqual([]);
300
+ });
301
+
302
+ test("every listed key is read by some fixture (the list has no stale keys)", () => {
303
+ const read: Set<string> = new Set<string>();
304
+ for (const fixture of SPAN_FIXTURES) {
305
+ resolveRecordingReads(fixture.attributes, fixture.kind, read);
306
+ }
307
+ /*
308
+ * The full-attribute probe: every key present, so each precedence list
309
+ * is walked to its end for at least one system.
310
+ */
311
+ const everything: FixtureAttributes = {};
312
+ for (const key of MESSAGING_RESOLVER_INPUT_ATTRIBUTES) {
313
+ everything[key] = "";
314
+ }
315
+ for (const system of ["aws_sqs", "aws.sns", "rabbitmq", "servicebus"]) {
316
+ resolveRecordingReads(
317
+ {
318
+ ...everything,
319
+ "messaging.system": system,
320
+ "rpc.system": "aws-api",
321
+ "rpc.service": "SQS",
322
+ component: "servicebus",
323
+ },
324
+ CLIENT,
325
+ read,
326
+ );
327
+ }
328
+ expect(
329
+ MESSAGING_RESOLVER_INPUT_ATTRIBUTES.filter((key: string): boolean => {
330
+ return !read.has(key);
331
+ }),
332
+ ).toEqual([]);
333
+ });
334
+ });
335
+
336
+ describe("resolveMessagingSpan — real instrumentations across semconv generations", () => {
337
+ test("the corpus is large and its names are unique", () => {
338
+ expect(SPAN_FIXTURES.length).toBeGreaterThanOrEqual(100);
339
+ expect(
340
+ new Set(
341
+ SPAN_FIXTURES.map((fixture: SpanFixture): string => {
342
+ return fixture.name;
343
+ }),
344
+ ).size,
345
+ ).toBe(SPAN_FIXTURES.length);
346
+ });
347
+
348
+ test.each(
349
+ SPAN_FIXTURES.map((fixture: SpanFixture): [string, SpanFixture] => {
350
+ return [fixture.name, fixture];
351
+ }),
352
+ )("%s", (_name: string, fixture: SpanFixture) => {
353
+ expect(resolveSpan(fixture.attributes, fixture.kind)).toEqual(
354
+ fixture.expected,
355
+ );
356
+ });
357
+
358
+ test.each(
359
+ SPAN_FIXTURES.map((fixture: SpanFixture): [string, SpanFixture] => {
360
+ return [fixture.name, fixture];
361
+ }),
362
+ )(
363
+ "%s — resolves identically read back from ClickHouse (the discovery cron's view)",
364
+ (_name: string, fixture: SpanFixture) => {
365
+ /*
366
+ * The cron selects exactly MESSAGING_RESOLVER_INPUT_ATTRIBUTES from the
367
+ * stored span — every value a string, a missing key '' — and hands
368
+ * the row to the same resolver: it must land on the same destination
369
+ * the ingest-time stamp used.
370
+ */
371
+ const row: Record<string, string> = toStoredColumns(
372
+ fixture.attributes,
373
+ MESSAGING_RESOLVER_INPUT_ATTRIBUTES,
374
+ );
375
+ expect(
376
+ resolveMessagingSpan({
377
+ getAttribute: (key: string): unknown => {
378
+ return row[key];
379
+ },
380
+ kind: fixture.kind,
381
+ }),
382
+ ).toEqual(resolveSpan(fixture.attributes, fixture.kind));
383
+ },
384
+ );
385
+ });
386
+
387
+ describe("span kind", () => {
388
+ const KAFKA_SEND: FixtureAttributes = {
389
+ "messaging.system": "kafka",
390
+ "messaging.destination.name": "orders",
391
+ };
392
+
393
+ test.each([
394
+ [SERVER],
395
+ ["SPAN_KIND_SERVER"],
396
+ ["server"],
397
+ ["Server"],
398
+ [" SERVER "],
399
+ ])("a SERVER span (%j) never resolves", (kind: string) => {
400
+ expect(resolveSpan(KAFKA_SEND, kind)).toBeNull();
401
+ });
402
+
403
+ test("OTLP's integer 2 is SERVER too", () => {
404
+ expect(
405
+ resolveMessagingSpan({
406
+ getAttribute: (key: string): unknown => {
407
+ return KAFKA_SEND[key];
408
+ },
409
+ kind: 2 as unknown as string,
410
+ }),
411
+ ).toBeNull();
412
+ });
413
+
414
+ test.each([
415
+ [PRODUCER, "publish"],
416
+ ["PRODUCER", "publish"],
417
+ ["producer", "publish"],
418
+ [CONSUMER, "consume"],
419
+ ["Consumer", "consume"],
420
+ [CLIENT, "unknown"],
421
+ [INTERNAL, "unknown"],
422
+ ["SPAN_KIND_UNSPECIFIED", "unknown"],
423
+ ["", "unknown"],
424
+ ["nonsense", "unknown"],
425
+ ])(
426
+ "every other kind (%j) resolves, and only decides the direction: %s",
427
+ (kind: string, direction: string) => {
428
+ expect(resolveSpan(KAFKA_SEND, kind)?.direction).toBe(direction);
429
+ },
430
+ );
431
+
432
+ test.each([
433
+ [1, "unknown"],
434
+ [3, "unknown"],
435
+ [4, "publish"],
436
+ [5, "consume"],
437
+ [0, "unknown"],
438
+ [9, "unknown"],
439
+ ])("OTLP integer kind %d → %s", (kind: number, direction: string) => {
440
+ expect(
441
+ resolveMessagingSpan({
442
+ getAttribute: (key: string): unknown => {
443
+ return KAFKA_SEND[key];
444
+ },
445
+ kind: kind as unknown as string,
446
+ })?.direction,
447
+ ).toBe(direction);
448
+ });
449
+
450
+ test("no kind at all resolves", () => {
451
+ expect(
452
+ resolveMessagingSpan({
453
+ getAttribute: (key: string): unknown => {
454
+ return KAFKA_SEND[key];
455
+ },
456
+ }),
457
+ ).toEqual(destinationOf({ system: "kafka", destination: "orders" }));
458
+ });
459
+
460
+ /*
461
+ * Spans on which the kind changes the answer: a plain Kafka span's
462
+ * direction; a RabbitMQ joined name, which a consumer splits to its queue
463
+ * and anything else to its exchange; and confluent-kafka's legacy
464
+ * "receive" on its producer spans, which only a PRODUCER kind outranks.
465
+ */
466
+ const KIND_SENSITIVE_SPANS: ReadonlyArray<FixtureAttributes> = [
467
+ KAFKA_SEND,
468
+ {
469
+ "messaging.system": "rabbitmq",
470
+ "messaging.destination.name": "direct_logs:warning",
471
+ "messaging.rabbitmq.destination.routing_key": "warning",
472
+ },
473
+ {
474
+ "messaging.system": "kafka",
475
+ "messaging.destination.name": "orders",
476
+ "messaging.operation": "receive",
477
+ },
478
+ ];
479
+
480
+ // Any value as the kind: a remapper's configuration is any JSON.
481
+ function resolveKindSensitiveSpans(
482
+ kind: unknown,
483
+ ): Array<ResolvedMessagingDestination | null> {
484
+ return KIND_SENSITIVE_SPANS.map(
485
+ (attributes: FixtureAttributes): ResolvedMessagingDestination | null => {
486
+ return resolveMessagingSpan({
487
+ getAttribute: (key: string): unknown => {
488
+ return attributes[key];
489
+ },
490
+ kind: kind as string | number | null | undefined,
491
+ });
492
+ },
493
+ );
494
+ }
495
+
496
+ test("those spans tell SERVER, PRODUCER, CONSUMER and every other kind apart", () => {
497
+ const answers: (kind: string | null) => string = (
498
+ kind: string | null,
499
+ ): string => {
500
+ return JSON.stringify(resolveKindSensitiveSpans(kind));
501
+ };
502
+ expect(
503
+ new Set<string>([SERVER, PRODUCER, CONSUMER, INTERNAL].map(answers)).size,
504
+ ).toBe(4);
505
+ // The resolver reads INTERNAL, CLIENT and no kind alike.
506
+ expect(answers(CLIENT)).toBe(answers(INTERNAL));
507
+ expect(answers(null)).toBe(answers(INTERNAL));
508
+ expect(resolveKindSensitiveSpans(SERVER)).toEqual([null, null, null]);
509
+ });
510
+
511
+ test("the numeric kind cases are unique, and name every OTLP kind as a number and as its digits", () => {
512
+ const seen: Set<string> = new Set<string>();
513
+ for (const { kind } of NUMERIC_SPAN_KIND_CASES) {
514
+ const token: string = `${typeof kind}:${String(kind)}`;
515
+ expect(seen.has(token)).toBe(false);
516
+ seen.add(token);
517
+ }
518
+ for (let number: number = 1; number <= 5; number++) {
519
+ expect(seen.has(`number:${number}`)).toBe(true);
520
+ expect(seen.has(`string:${number}`)).toBe(true);
521
+ }
522
+ });
523
+
524
+ test.each(
525
+ NUMERIC_SPAN_KIND_CASES.map(
526
+ (entry: NumericSpanKindCase): [number | string, string | null] => {
527
+ return [entry.kind, entry.reads];
528
+ },
529
+ ),
530
+ )(
531
+ "a Span Kind Remapper's %j (a number, or a trimmed run of digits read as Number() reads it) resolves as %s does",
532
+ (kind: number | string, reads: string | null) => {
533
+ expect(resolveKindSensitiveSpans(kind)).toEqual(
534
+ resolveKindSensitiveSpans(reads),
535
+ );
536
+ },
537
+ );
538
+
539
+ test('"2", " 2 " and "02" are SERVER exactly like 2: no queue', () => {
540
+ for (const kind of [2, "2", " 2 ", "02", "0000002"]) {
541
+ expect({ kind, resolved: resolveKindSensitiveSpans(kind) }).toEqual({
542
+ kind,
543
+ resolved: [null, null, null],
544
+ });
545
+ }
546
+ });
547
+
548
+ test("a kind resolves alike as the value ingest hands over and as the text ClickHouse keeps for it", () => {
549
+ const values: Array<unknown> = [
550
+ ...NUMERIC_SPAN_KIND_CASES.map((entry: NumericSpanKindCase): unknown => {
551
+ return entry.kind;
552
+ }),
553
+ SERVER,
554
+ PRODUCER,
555
+ CONSUMER,
556
+ CLIENT,
557
+ INTERNAL,
558
+ "SPAN_KIND_UNSPECIFIED",
559
+ "server",
560
+ " Producer ",
561
+ "",
562
+ " ",
563
+ null,
564
+ undefined,
565
+ // Numbers JSON cannot write are stored NULL.
566
+ Number.NaN,
567
+ Number.POSITIVE_INFINITY,
568
+ Number.NEGATIVE_INFINITY,
569
+ -0,
570
+ 0.5,
571
+ 4.000000000000001,
572
+ Number.MIN_VALUE,
573
+ Number.MAX_SAFE_INTEGER,
574
+ 2 ** 60,
575
+ true,
576
+ false,
577
+ [2],
578
+ ["SPAN_KIND_SERVER"],
579
+ {},
580
+ { kind: 2 },
581
+ // Whitespace trim() removes, around digits.
582
+ `2${String.fromCharCode(0xa0)}`,
583
+ `${String.fromCharCode(0xfeff)}4${String.fromCharCode(0x3000)}`,
584
+ "\t5\n",
585
+ // Digits of other scripts, and other numerals: names, and no kind's.
586
+ String.fromCharCode(0x662),
587
+ String.fromCharCode(0xff12),
588
+ "0x2",
589
+ "0b10",
590
+ "2n",
591
+ "2 2",
592
+ "Infinity",
593
+ "NaN",
594
+ `${"0".repeat(400)}2`,
595
+ "9".repeat(400),
596
+ ];
597
+
598
+ for (const value of values) {
599
+ expect({
600
+ value,
601
+ resolved: resolveKindSensitiveSpans(value),
602
+ }).toEqual({
603
+ value,
604
+ resolved: resolveKindSensitiveSpans(toStoredKind(value)),
605
+ });
606
+ }
607
+ });
608
+ });
609
+
610
+ describe("system precedence", () => {
611
+ test("messaging.system beats every other signal", () => {
612
+ expect(
613
+ resolveSpan(
614
+ {
615
+ "messaging.system": "rabbitmq",
616
+ "az.namespace": "Microsoft.ServiceBus",
617
+ "rpc.system": "aws-api",
618
+ "rpc.service": "SQS",
619
+ "aws.sqs.queue.url": "https://sqs.us-east-1.amazonaws.com/1/q",
620
+ "messaging.destination.name": "orders",
621
+ },
622
+ PRODUCER,
623
+ )?.system,
624
+ ).toBe("rabbitmq");
625
+ });
626
+
627
+ test.each([
628
+ ["Microsoft.ServiceBus", "servicebus"],
629
+ ["microsoft.servicebus", "servicebus"],
630
+ ["MICROSOFT.SERVICEBUS", "servicebus"],
631
+ ["Microsoft.EventHub", "eventhubs"],
632
+ ["microsoft.eventhub", "eventhubs"],
633
+ ])("az.namespace %s means %s", (namespace: string, system: string) => {
634
+ expect(
635
+ resolveSpan(
636
+ {
637
+ "az.namespace": namespace,
638
+ "messaging.destination.name": "orders",
639
+ },
640
+ PRODUCER,
641
+ )?.system,
642
+ ).toBe(system);
643
+ expect(
644
+ resolveSpan(
645
+ {
646
+ "azure.resource_provider.namespace": namespace,
647
+ "messaging.destination.name": "orders",
648
+ },
649
+ PRODUCER,
650
+ )?.system,
651
+ ).toBe(system);
652
+ });
653
+
654
+ test("the Azure provider namespace beats the legacy component", () => {
655
+ expect(
656
+ resolveSpan(
657
+ {
658
+ "az.namespace": "Microsoft.EventHub",
659
+ component: "servicebus",
660
+ "message_bus.destination": "orders",
661
+ },
662
+ PRODUCER,
663
+ )?.system,
664
+ ).toBe("eventhubs");
665
+ });
666
+
667
+ test.each([
668
+ ["servicebus", "message_bus.destination"],
669
+ ["ServiceBus", "peer.address"],
670
+ ["eventhubs", "message_bus.destination"],
671
+ ["EVENTHUBS", "peer.address"],
672
+ ])(
673
+ "component %s counts beside %s",
674
+ (component: string, companion: string) => {
675
+ const attributes: FixtureAttributes = {
676
+ component,
677
+ "messaging.destination.name": "orders",
678
+ [companion]:
679
+ companion === "peer.address"
680
+ ? "sb://ns1.servicebus.windows.net/"
681
+ : "orders",
682
+ };
683
+ expect(resolveSpan(attributes, PRODUCER)?.system).toBe(
684
+ component.toLowerCase(),
685
+ );
686
+ },
687
+ );
688
+
689
+ test("a component that is not an Azure messaging service is no evidence", () => {
690
+ expect(
691
+ resolveSpan(
692
+ {
693
+ component: "http",
694
+ "message_bus.destination": "orders",
695
+ "peer.address": "api.example.com",
696
+ },
697
+ PRODUCER,
698
+ ),
699
+ ).toBeNull();
700
+ });
701
+
702
+ test.each([
703
+ ["aws-api", "SQS", "aws_sqs"],
704
+ ["aws-api", "AmazonSQS", "aws_sqs"],
705
+ ["AWS-API", "sqs", "aws_sqs"],
706
+ ["aws-api", "Sqs", "aws_sqs"],
707
+ ["aws-api", "SNS", "aws.sns"],
708
+ ["aws-api", "AmazonSNS", "aws.sns"],
709
+ ["aws-api", "Sns", "aws.sns"],
710
+ ])(
711
+ "rpc.system %s + rpc.service %s → %s",
712
+ (rpcSystem: string, rpcService: string, system: string) => {
713
+ for (const systemKey of ["rpc.system", "rpc.system.name"]) {
714
+ expect(
715
+ resolveSpan(
716
+ {
717
+ [systemKey]: rpcSystem,
718
+ "rpc.service": rpcService,
719
+ "messaging.destination.name": "orders",
720
+ },
721
+ PRODUCER,
722
+ )?.system,
723
+ ).toBe(system);
724
+ }
725
+ },
726
+ );
727
+
728
+ test("an AWS RPC span for another service is not messaging", () => {
729
+ expect(
730
+ resolveSpan(
731
+ {
732
+ "rpc.system": "aws-api",
733
+ "rpc.service": "DynamoDB",
734
+ "messaging.destination.name": "orders",
735
+ },
736
+ CLIENT,
737
+ ),
738
+ ).toBeNull();
739
+ expect(
740
+ resolveSpan(
741
+ {
742
+ "rpc.system": "grpc",
743
+ "rpc.service": "SQS",
744
+ "messaging.destination.name": "orders",
745
+ },
746
+ CLIENT,
747
+ ),
748
+ ).toBeNull();
749
+ });
750
+
751
+ test("the SQS queue URL and SNS topic ARN keys name their systems", () => {
752
+ expect(
753
+ resolveSpan({ "aws.sqs.queue.url": "https://sqs.x/1/q" }, CLIENT),
754
+ ).toEqual(destinationOf({ system: "aws_sqs", destination: "q" }));
755
+ expect(
756
+ resolveSpan({ "aws.queue_url": "https://sqs.x/1/q2" }, CLIENT)?.system,
757
+ ).toBe("aws_sqs");
758
+ expect(
759
+ resolveSpan({ "aws.sns.topic.arn": "arn:aws:sns:us-east-1:1:t" }, CLIENT)
760
+ ?.system,
761
+ ).toBe("aws.sns");
762
+ });
763
+
764
+ test("an excluded messaging.system stops, whatever else the span says", () => {
765
+ expect(
766
+ resolveSpan(
767
+ {
768
+ "messaging.system": "spring_integration",
769
+ "az.namespace": "Microsoft.ServiceBus",
770
+ "messaging.destination.name": "orders",
771
+ },
772
+ PRODUCER,
773
+ ),
774
+ ).toBeNull();
775
+ });
776
+
777
+ test("a malformed messaging.system with no other evidence is no queue", () => {
778
+ expect(
779
+ resolveSpan(
780
+ {
781
+ "messaging.system": "Some Broker!",
782
+ "messaging.destination.name": "orders",
783
+ },
784
+ PRODUCER,
785
+ ),
786
+ ).toBeNull();
787
+ });
788
+
789
+ test("Celery (destination but no system) is not detected", () => {
790
+ expect(
791
+ resolveSpan(
792
+ {
793
+ "messaging.destination": "celery",
794
+ "celery.action": "run",
795
+ "celery.task_name": "tasks.add",
796
+ },
797
+ CONSUMER,
798
+ ),
799
+ ).toBeNull();
800
+ });
801
+ });
802
+
803
+ describe("destination precedence", () => {
804
+ const ALL: FixtureAttributes = {
805
+ "messaging.system": "kafka",
806
+ "messaging.destination.template": "t-template",
807
+ "messaging.destination.name": "t-name",
808
+ "messaging.source.template": "t-source-template",
809
+ "messaging.source.name": "t-source",
810
+ "messaging.destination": "t-legacy",
811
+ "message_bus.destination": "t-azure",
812
+ };
813
+
814
+ test("template > name > source.template > source.name > messaging.destination > message_bus.destination", () => {
815
+ const attributes: FixtureAttributes = { ...ALL };
816
+ const order: Array<string> = [];
817
+ for (const key of MESSAGING_DESTINATION_ATTRIBUTES) {
818
+ const resolved: ResolvedMessagingDestination | null = resolveSpan(
819
+ attributes,
820
+ PRODUCER,
821
+ );
822
+ order.push(resolved ? resolved.destination : "(none)");
823
+ delete attributes[key];
824
+ }
825
+ expect(order).toEqual([
826
+ "t-template",
827
+ "t-name",
828
+ "t-source-template",
829
+ "t-source",
830
+ "t-legacy",
831
+ "t-azure",
832
+ ]);
833
+ expect(resolveSpan(attributes, PRODUCER)).toBeNull();
834
+ });
835
+
836
+ test("empty and whitespace-only values are absent: the next key is used", () => {
837
+ expect(
838
+ destinationFor("kafka", "", { "messaging.destination": "orders" }),
839
+ ).toBe("orders");
840
+ expect(
841
+ destinationFor("kafka", " ", { "messaging.destination": "orders" }),
842
+ ).toBe("orders");
843
+ });
844
+
845
+ test("a placeholder in the first present key means no queue — never 'try the next key'", () => {
846
+ expect(
847
+ destinationFor("kafka", "unknown", { "messaging.destination": "orders" }),
848
+ ).toBeNull();
849
+ expect(
850
+ destinationFor("jms", "(temporary)", {
851
+ "messaging.destination": "orders",
852
+ }),
853
+ ).toBeNull();
854
+ });
855
+
856
+ test("values are trimmed and keep their casing", () => {
857
+ expect(destinationFor("kafka", " Orders.Created ")).toBe(
858
+ "Orders.Created",
859
+ );
860
+ });
861
+
862
+ test("a number or boolean is read as the text ClickHouse stores", () => {
863
+ expect(destinationFor("kafka", 42 as unknown as string)).toBe("42");
864
+ expect(destinationFor("kafka", true as unknown as string)).toBe("true");
865
+ });
866
+
867
+ test("a destination naming several at once (a JSON list) is no queue", () => {
868
+ expect(destinationFor("kafka", '["orders", "payments"]')).toBeNull();
869
+ expect(
870
+ destinationFor("kafka", ["orders", "payments"] as unknown as string),
871
+ ).toBeNull();
872
+ });
873
+
874
+ test("objects are not destinations; the next key is used", () => {
875
+ expect(
876
+ destinationFor("kafka", { name: "x" } as unknown as string, {
877
+ "messaging.destination": "orders",
878
+ }),
879
+ ).toBe("orders");
880
+ });
881
+
882
+ test("span names are never parsed", () => {
883
+ expect(
884
+ resolveSpan(
885
+ { "messaging.system": "kafka", name: "orders publish" },
886
+ PRODUCER,
887
+ ),
888
+ ).toBeNull();
889
+ });
890
+ });
891
+
892
+ describe("per-system destination normalization", () => {
893
+ test.each([
894
+ ["https://sqs.us-east-1.amazonaws.com/123456789012/orders", "orders"],
895
+ ["https://sqs.us-east-1.amazonaws.com/123456789012/orders/", "orders"],
896
+ ["https://sqs.us-east-1.amazonaws.com/123456789012/orders?x=1", "orders"],
897
+ ["https://us-east-1.queue.amazonaws.com/123456789012/legacy", "legacy"],
898
+ ["http://localhost:4566/000000000000/local-queue", "local-queue"],
899
+ ["http://localhost:9324/queue/elasticmq-queue", "elasticmq-queue"],
900
+ ["arn:aws:sqs:us-east-2:123456789012:my-queue", "my-queue"],
901
+ ["arn:aws-cn:sqs:cn-north-1:123456789012:cn-queue", "cn-queue"],
902
+ ["orders.fifo", "orders.fifo"],
903
+ ["orders", "orders"],
904
+ ])("SQS %s → %s", (raw: string, name: string) => {
905
+ expect(destinationFor("aws_sqs", raw)).toBe(name);
906
+ });
907
+
908
+ test("an SQS URL with no queue in its path, or a truncated ARN, is no queue", () => {
909
+ expect(
910
+ destinationFor("aws_sqs", "https://sqs.us-east-1.amazonaws.com"),
911
+ ).toBeNull();
912
+ expect(
913
+ destinationFor("aws_sqs", "https://sqs.us-east-1.amazonaws.com/"),
914
+ ).toBeNull();
915
+ // SQS names cannot contain ":": an ARN cut short names nothing.
916
+ for (const truncated of [
917
+ "arn:aws:sqs:us-east-1",
918
+ "arn:aws:sqs:us-east-1:123456789012",
919
+ "arn:aws:sqs:us-east-1:123456789012:",
920
+ "ARN:aws:sqs",
921
+ "arn:",
922
+ ]) {
923
+ expect({
924
+ truncated,
925
+ queue: destinationFor("aws_sqs", truncated),
926
+ }).toEqual({ truncated, queue: null });
927
+ }
928
+ });
929
+
930
+ test("an SNS ARN cut short of its topic is no topic either", () => {
931
+ for (const truncated of [
932
+ "arn:aws:sns:us-east-1",
933
+ "arn:aws:sns:us-east-1:123456789012",
934
+ "arn:aws:sns:us-east-1:123456789012:",
935
+ "arn:",
936
+ ]) {
937
+ expect({
938
+ truncated,
939
+ topic: destinationFor("aws.sns", truncated),
940
+ }).toEqual({ truncated, topic: null });
941
+ }
942
+ // The full ARN still names its topic.
943
+ expect(
944
+ destinationFor("aws.sns", "arn:aws:sns:us-east-1:123456789012:orders"),
945
+ ).toBe("orders");
946
+ });
947
+
948
+ test.each([
949
+ ["arn:aws:sns:us-east-1:123456789012:order-events", "order-events"],
950
+ ["arn:aws:sns:us-east-1:123456789012:orders.fifo", "orders.fifo"],
951
+ // A subscription ARN names its topic.
952
+ [
953
+ "arn:aws:sns:us-east-1:123456789012:order-events:6b0e71bd-7e97-4d97-80ce-4a0994e55286",
954
+ "order-events",
955
+ ],
956
+ ["order-events", "order-events"],
957
+ ])("SNS %s → %s", (raw: string, name: string) => {
958
+ expect(destinationFor("aws.sns", raw)).toBe(name);
959
+ });
960
+
961
+ test.each([
962
+ ["arn:aws:sns:us-east-1:123456789012:endpoint/GCM/app/5f2b7c1e"],
963
+ ["arn:aws:sns:us-east-1:123456789012:app/APNS/my-app"],
964
+ /*
965
+ * The same, cut after the ARN's last ":" (Go otelaws'
966
+ * extractDestinationName, JS aws-sdk's messaging.destination): an SNS
967
+ * topic name never contains "/".
968
+ */
969
+ ["endpoint/GCM/billing-app/5e3e9847-3183-3f18-a7e8-671c3a57d4b3"],
970
+ ["endpoint/APNS/my-app/0f1e2d3c"],
971
+ ["app/APNS/my-app"],
972
+ ])(
973
+ "an SNS platform endpoint or application (%s) is no queue",
974
+ (raw: string) => {
975
+ expect(destinationFor("aws.sns", raw)).toBeNull();
976
+ },
977
+ );
978
+
979
+ test("only SNS reads a '/' as a push endpoint: other systems keep the name", () => {
980
+ expect(destinationFor("kafka", "endpoint/GCM/app/x")).toBe(
981
+ "endpoint/GCM/app/x",
982
+ );
983
+ expect(destinationFor("servicebus", "apps/orders")).toBe("apps/orders");
984
+ });
985
+
986
+ test.each([
987
+ ["projects/acme/topics/orders", "orders"],
988
+ ["projects/acme/subscriptions/billing-sub", "billing-sub"],
989
+ ["//pubsub.googleapis.com/projects/acme/topics/orders", "orders"],
990
+ ["orders", "orders"],
991
+ // Not a Pub/Sub resource name: kept.
992
+ ["projects/acme/datasets/x", "projects/acme/datasets/x"],
993
+ ])("Pub/Sub %s → %s", (raw: string, name: string) => {
994
+ expect(destinationFor("gcp_pubsub", raw)).toBe(name);
995
+ });
996
+
997
+ test.each([
998
+ ["orders", "orders", null, false],
999
+ ["order-events/Subscriptions/billing", "order-events", "billing", false],
1000
+ ["order-events/subscriptions/billing", "order-events", "billing", false],
1001
+ ["ORDER-EVENTS/SUBSCRIPTIONS/Billing", "ORDER-EVENTS", "Billing", false],
1002
+ ["orders/$DeadLetterQueue", "orders", null, true],
1003
+ ["orders/$deadletterqueue", "orders", null, true],
1004
+ ["orders/$Transfer/$DeadLetterQueue", "orders", null, true],
1005
+ ["orders/$transfer/$deadletterqueue", "orders", null, true],
1006
+ [
1007
+ "order-events/Subscriptions/billing/$DeadLetterQueue",
1008
+ "order-events",
1009
+ "billing",
1010
+ true,
1011
+ ],
1012
+ // Service Bus queue names may themselves contain "/".
1013
+ ["apps/orders", "apps/orders", null, false],
1014
+ ["apps/orders/$DeadLetterQueue", "apps/orders", null, true],
1015
+ ])(
1016
+ "Service Bus %s → %s (subscription %s, dead letter %s)",
1017
+ (
1018
+ raw: string,
1019
+ destination: string,
1020
+ consumerGroup: string | null,
1021
+ isDeadLetter: boolean,
1022
+ ) => {
1023
+ const resolved: ResolvedMessagingDestination | null = resolveSpan(
1024
+ {
1025
+ "messaging.system": "servicebus",
1026
+ "messaging.destination.name": raw,
1027
+ },
1028
+ CONSUMER,
1029
+ );
1030
+ expect(resolved).toMatchObject({
1031
+ destination,
1032
+ consumerGroup,
1033
+ isDeadLetter,
1034
+ });
1035
+ },
1036
+ );
1037
+
1038
+ test.each([
1039
+ ["telemetry", "telemetry", null],
1040
+ ["telemetry/ConsumerGroups/$Default", "telemetry", "$Default"],
1041
+ [
1042
+ "telemetry/consumergroups/analytics/Partitions/3",
1043
+ "telemetry",
1044
+ "analytics",
1045
+ ],
1046
+ ])(
1047
+ "Event Hubs %s → %s (consumer group %s)",
1048
+ (raw: string, destination: string, consumerGroup: string | null) => {
1049
+ expect(
1050
+ resolveSpan(
1051
+ {
1052
+ "messaging.system": "eventhubs",
1053
+ "messaging.destination.name": raw,
1054
+ },
1055
+ CONSUMER,
1056
+ ),
1057
+ ).toMatchObject({ destination, consumerGroup, isDeadLetter: false });
1058
+ },
1059
+ );
1060
+
1061
+ test("an entity path with nothing before its marker is no queue", () => {
1062
+ expect(destinationFor("servicebus", "/$DeadLetterQueue")).toBeNull();
1063
+ expect(destinationFor("servicebus", "/Subscriptions/billing")).toBeNull();
1064
+ });
1065
+
1066
+ test.each([
1067
+ [
1068
+ "persistent://public/default/orders-partition-0",
1069
+ "persistent://public/default/orders",
1070
+ ],
1071
+ [
1072
+ "persistent://public/default/orders-partition-12",
1073
+ "persistent://public/default/orders",
1074
+ ],
1075
+ ["persistent://acme/ns/orders", "persistent://acme/ns/orders"],
1076
+ ["orders", "persistent://public/default/orders"],
1077
+ ["orders-partition-3", "persistent://public/default/orders"],
1078
+ ["acme/ns/orders", "persistent://acme/ns/orders"],
1079
+ // Not tenant/namespace/topic: left as given.
1080
+ ["acme/orders", "acme/orders"],
1081
+ ["a/b/c/d", "a/b/c/d"],
1082
+ // Only a trailing partition suffix folds.
1083
+ [
1084
+ "persistent://p/d/orders-partition-x",
1085
+ "persistent://p/d/orders-partition-x",
1086
+ ],
1087
+ ])("Pulsar %s → %s", (raw: string, name: string) => {
1088
+ expect(destinationFor("pulsar", raw)).toBe(name);
1089
+ });
1090
+
1091
+ test.each([
1092
+ ["activemq", "queue://orders", "orders"],
1093
+ ["activemq", "topic://prices", "prices"],
1094
+ ["activemq", "QUEUE://Orders", "Orders"],
1095
+ ["jms", "queue://orders", "orders"],
1096
+ ["jms", "orders", "orders"],
1097
+ // Only ActiveMQ / JMS strip the prefix.
1098
+ ["kafka", "queue://orders", "queue://orders"],
1099
+ ])("%s %s → %s", (system: string, raw: string, name: string) => {
1100
+ expect(destinationFor(system, raw)).toBe(name);
1101
+ });
1102
+
1103
+ test.each([
1104
+ // A PRODUCER span: [destination, routing key, queue]
1105
+ ["direct_logs:warning", "warning", "direct_logs"],
1106
+ /*
1107
+ * A producer names no queue: semconv joins three parts on a consumer
1108
+ * only, so a producer's three are one exchange name even when its
1109
+ * routing key happens to be the middle one.
1110
+ */
1111
+ ["direct_logs:warning:my_queue", "warning", "direct_logs:warning:my_queue"],
1112
+ /*
1113
+ * Without its routing key, a producer span's name is ONE exchange name:
1114
+ * semconv requires the routing-key attribute whenever the key is
1115
+ * non-empty, so a joined {exchange}:{routing key} never comes without
1116
+ * it, and it joins three parts on a consumer only. MassTransit names
1117
+ * exchanges `Namespace:Type` — a generic message type's, a Fault<T>'s
1118
+ * among them, with two colons (RabbitMqMessageNameFormatter) — and
1119
+ * publishes to them with an empty routing key.
1120
+ */
1121
+ ["direct_logs:warning", null, "direct_logs:warning"],
1122
+ ["MyApp.Contracts:OrderSubmitted", null, "MyApp.Contracts:OrderSubmitted"],
1123
+ ["direct_logs:warning:my_queue", null, "direct_logs:warning:my_queue"],
1124
+ [
1125
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1126
+ null,
1127
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1128
+ ],
1129
+ [
1130
+ "Sample.Contracts:EntityChanged--Sample.Domain:Customer--",
1131
+ null,
1132
+ "Sample.Contracts:EntityChanged--Sample.Domain:Customer--",
1133
+ ],
1134
+ ["amq.default:orders", "orders", "orders"],
1135
+ ["<default>:orders", null, "orders"],
1136
+ [":orders", null, "orders"],
1137
+ ["amq.default", "orders", "orders"],
1138
+ ["<default>", "orders", "orders"],
1139
+ ["AMQ.DEFAULT", "orders", "orders"],
1140
+ ["amq.default", null, null],
1141
+ ["<default>", null, null],
1142
+ // An empty queue part falls back to the two-part rule.
1143
+ ["ex:rk:", "rk", "ex"],
1144
+ ["ex:", "rk", "ex"],
1145
+ // The routing key contradicts the split: the whole name stands.
1146
+ ["Contracts:OrderSubmitted", "order.submitted", "Contracts:OrderSubmitted"],
1147
+ ["a:b:c", "x", "a:b:c"],
1148
+ // Four parts: not the semconv shape.
1149
+ ["a:b:c:d", null, "a:b:c:d"],
1150
+ // aio-pika's comma form.
1151
+ ["orders,order.created", null, "orders"],
1152
+ [",task_queue", null, "task_queue"],
1153
+ ["a,b,c", null, "a,b,c"],
1154
+ // Built-in exchanges are real destinations.
1155
+ ["amq.topic", "sensors.#", "amq.topic"],
1156
+ // An empty part names nothing: the default exchange's marker, its key.
1157
+ ["amq.default:", "orders", "orders"],
1158
+ [":", "orders", "orders"],
1159
+ ["amq.default:", null, null],
1160
+ ])(
1161
+ "RabbitMQ %s (routing key %s) → %s",
1162
+ (raw: string, routingKey: string | null, queue: string | null) => {
1163
+ const extra: FixtureAttributes =
1164
+ routingKey === null
1165
+ ? {}
1166
+ : { "messaging.rabbitmq.destination.routing_key": routingKey };
1167
+ expect(destinationFor("rabbitmq", raw, extra)).toBe(queue);
1168
+ },
1169
+ );
1170
+
1171
+ /*
1172
+ * What a consumer's span says about its name: written to the conventions
1173
+ * that join names (the Java agent's opt-in mode: 1.26+ operation keys), to
1174
+ * RabbitMQ.Client for .NET's (1.26+ keys, network.protocol.name "amqp",
1175
+ * the exchange alone), or to the older ones (the exchange alone, legacy
1176
+ * messaging.operation).
1177
+ */
1178
+ const JOINING: FixtureAttributes = { "messaging.operation.type": "process" };
1179
+ const DOTNET_CLIENT: FixtureAttributes = {
1180
+ "messaging.operation.type": "process",
1181
+ "network.protocol.name": "amqp",
1182
+ };
1183
+ const OLDER: FixtureAttributes = { "messaging.operation": "process" };
1184
+
1185
+ test.each([
1186
+ /*
1187
+ * A CONSUMER span: [destination, routing key, conventions, queue]
1188
+ * Semconv 1.30: the queue after the exchange...
1189
+ */
1190
+ ["direct_logs:warning", "warning", JOINING, "warning"],
1191
+ ["logs:logs-audit", null, JOINING, "logs-audit"],
1192
+ // ...or after the routing key of a default-exchange delivery.
1193
+ ["invoices:invoices-audit", "invoices", JOINING, "invoices-audit"],
1194
+ ["direct_logs:warning:my_queue", "warning", JOINING, "my_queue"],
1195
+ ["direct_logs:warning:my_queue", null, JOINING, "my_queue"],
1196
+ /*
1197
+ * An exchange named with a colon, its empty routing key left out: the
1198
+ * Java agent's opt-in consumer of a MassTransit exchange.
1199
+ */
1200
+ ["MyApp.Contracts:OrderSubmitted:billing", null, JOINING, "billing"],
1201
+ /*
1202
+ * Three parts, no routing key, and not the joining conventions: one
1203
+ * exchange name, as the .NET client and the Java agent's default mode
1204
+ * name a MassTransit Fault<T> exchange.
1205
+ */
1206
+ [
1207
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1208
+ null,
1209
+ DOTNET_CLIENT,
1210
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1211
+ ],
1212
+ [
1213
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1214
+ null,
1215
+ OLDER,
1216
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1217
+ ],
1218
+ [
1219
+ "direct_logs:warning:my_queue",
1220
+ null,
1221
+ OLDER,
1222
+ "direct_logs:warning:my_queue",
1223
+ ],
1224
+ ["direct_logs:warning:my_queue", null, {}, "direct_logs:warning:my_queue"],
1225
+ // A routing key where the joined form cannot put it: one name.
1226
+ [
1227
+ "MyApp.Contracts:OrderSubmitted",
1228
+ "order.submitted",
1229
+ JOINING,
1230
+ "MyApp.Contracts:OrderSubmitted",
1231
+ ],
1232
+ // A routing key where the joined form puts it: split, whoever wrote it.
1233
+ ["direct_logs:warning", "warning", OLDER, "warning"],
1234
+ // No routing key and no joining conventions: one exchange name.
1235
+ [
1236
+ "MyApp.Contracts:OrderSubmitted",
1237
+ null,
1238
+ DOTNET_CLIENT,
1239
+ "MyApp.Contracts:OrderSubmitted",
1240
+ ],
1241
+ [
1242
+ "MyApp.Contracts:OrderSubmitted",
1243
+ null,
1244
+ OLDER,
1245
+ "MyApp.Contracts:OrderSubmitted",
1246
+ ],
1247
+ ["logs:logs-audit", null, {}, "logs:logs-audit"],
1248
+ // A blank operation type is none.
1249
+ [
1250
+ "logs:logs-audit",
1251
+ null,
1252
+ { "messaging.operation.type": " " },
1253
+ "logs:logs-audit",
1254
+ ],
1255
+ // Any other protocol name leaves the joining conventions in place.
1256
+ [
1257
+ "logs:logs-audit",
1258
+ null,
1259
+ { ...JOINING, "network.protocol.name": "AMQPS" },
1260
+ "logs-audit",
1261
+ ],
1262
+ [
1263
+ "logs:logs-audit",
1264
+ null,
1265
+ { ...JOINING, "network.protocol.name": "AMQP" },
1266
+ "logs:logs-audit",
1267
+ ],
1268
+ // The default exchange first always splits.
1269
+ ["amq.default:orders", null, OLDER, "orders"],
1270
+ ["<default>:orders", null, DOTNET_CLIENT, "orders"],
1271
+ // aio-pika's comma form and a bare name are unchanged on a consumer.
1272
+ ["orders,order.created", null, OLDER, "orders"],
1273
+ ["orders", "orders", JOINING, "orders"],
1274
+ ])(
1275
+ "RabbitMQ consumer %s (routing key %s, %j) → %s",
1276
+ (
1277
+ raw: string,
1278
+ routingKey: string | null,
1279
+ conventions: FixtureAttributes,
1280
+ queue: string | null,
1281
+ ) => {
1282
+ const resolved: ResolvedMessagingDestination | null = resolveSpan(
1283
+ {
1284
+ "messaging.system": "rabbitmq",
1285
+ "messaging.destination.name": raw,
1286
+ ...(routingKey === null
1287
+ ? {}
1288
+ : { "messaging.rabbitmq.destination.routing_key": routingKey }),
1289
+ ...conventions,
1290
+ },
1291
+ CONSUMER,
1292
+ );
1293
+ expect(resolved?.destination ?? null).toBe(queue);
1294
+ if (resolved) {
1295
+ expect(resolved.direction).toBe("consume");
1296
+ }
1297
+ },
1298
+ );
1299
+
1300
+ test("a settle span is on the consuming side: it names the queue too", () => {
1301
+ expect(
1302
+ resolveSpan(
1303
+ {
1304
+ "messaging.system": "rabbitmq",
1305
+ "messaging.destination.name": "logs:logs-audit",
1306
+ "messaging.operation.type": "settle",
1307
+ "messaging.operation.name": "ack",
1308
+ },
1309
+ CLIENT,
1310
+ ),
1311
+ ).toEqual(
1312
+ destinationOf({
1313
+ system: "rabbitmq",
1314
+ destination: "logs-audit",
1315
+ direction: "settle",
1316
+ }),
1317
+ );
1318
+ });
1319
+
1320
+ test.each([
1321
+ // A PRODUCER span written to the joining conventions.
1322
+ ["orders.topic:orders.eu.created", "orders.eu.created", "orders.topic"],
1323
+ /*
1324
+ * A joined producer name always carries its routing key: without one,
1325
+ * the colon is the exchange's own (the Java agent publishing to a
1326
+ * MassTransit exchange).
1327
+ */
1328
+ ["MyApp.Contracts:OrderSubmitted", null, "MyApp.Contracts:OrderSubmitted"],
1329
+ [
1330
+ "orders.topic:orders.eu.created",
1331
+ "other.key",
1332
+ "orders.topic:orders.eu.created",
1333
+ ],
1334
+ // The routing key as the first part is no producer's joined name.
1335
+ ["invoices:x", "invoices", "invoices:x"],
1336
+ /*
1337
+ * Three parts on a producer are one exchange name, whatever the
1338
+ * conventions and wherever the routing key is: semconv joins three on
1339
+ * a consumer only.
1340
+ */
1341
+ [
1342
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1343
+ null,
1344
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1345
+ ],
1346
+ ["direct_logs:warning:my_queue", "warning", "direct_logs:warning:my_queue"],
1347
+ // An empty third part leaves the two-part rule: the exchange.
1348
+ ["orders.topic:orders.eu.created:", "orders.eu.created", "orders.topic"],
1349
+ ])(
1350
+ "RabbitMQ joined producer %s (routing key %s) → %s",
1351
+ (raw: string, routingKey: string | null, exchange: string) => {
1352
+ expect(
1353
+ destinationFor("rabbitmq", raw, {
1354
+ "messaging.operation.type": "send",
1355
+ ...(routingKey === null
1356
+ ? {}
1357
+ : { "messaging.rabbitmq.destination.routing_key": routingKey }),
1358
+ }),
1359
+ ).toBe(exchange);
1360
+ },
1361
+ );
1362
+
1363
+ test("MassTransit: each message type's exchange keys its own queue, never the namespace", () => {
1364
+ const destinations: Array<string | null> = [
1365
+ "MyApp.Contracts:OrderSubmitted",
1366
+ "MyApp.Contracts:OrderCancelled",
1367
+ ].map((exchange: string): string | null => {
1368
+ return destinationFor("rabbitmq", exchange, {
1369
+ "messaging.operation": "send",
1370
+ });
1371
+ });
1372
+ expect(destinations).toEqual([
1373
+ "MyApp.Contracts:OrderSubmitted",
1374
+ "MyApp.Contracts:OrderCancelled",
1375
+ ]);
1376
+ // A blank routing key (RabbitMQ.Client v7 always tags it) is none.
1377
+ expect(
1378
+ destinationFor("rabbitmq", "MyApp.Contracts:OrderSubmitted", {
1379
+ "messaging.rabbitmq.destination.routing_key": "",
1380
+ }),
1381
+ ).toBe("MyApp.Contracts:OrderSubmitted");
1382
+ });
1383
+
1384
+ /*
1385
+ * RabbitMqMessageNameFormatter writes a generic type's arguments after
1386
+ * "--" in their own `Namespace:Type`, so the exchange of Fault<T> — which
1387
+ * MassTransit publishes on every consumer fault, by default — and of the
1388
+ * job service's JobStarted<T> / JobCompleted<T> holds two colons. Each is
1389
+ * one exchange: its last segment names no queue, and cutting it there
1390
+ * would merge every message type sharing that segment.
1391
+ */
1392
+ test("MassTransit: a generic message type's exchange, a Fault<T>'s among them, is kept whole by every instrumentation", () => {
1393
+ const exchanges: Array<string> = [
1394
+ "MassTransit:Fault--Billing.Contracts:SubmitOrder--",
1395
+ "MassTransit:Fault--Sales.Contracts:SubmitOrder--",
1396
+ "MassTransit:Fault--MyApp.Jobs:ConvertVideo--",
1397
+ "MassTransit.Contracts.JobService:JobStarted--MyApp.Jobs:ConvertVideo--",
1398
+ "MassTransit.Contracts.JobService:JobCompleted--MyApp.Jobs:ConvertVideo--",
1399
+ ];
1400
+ const shapes: Array<[string, FixtureAttributes, string]> = [
1401
+ // MassTransit's own send span (StartSendActivity): no empty key tag.
1402
+ ["MassTransit send", { "messaging.operation": "send" }, PRODUCER],
1403
+ [
1404
+ "RabbitMQ.Client v7 publish",
1405
+ {
1406
+ "messaging.operation.type": "send",
1407
+ "messaging.operation.name": "publish",
1408
+ "network.protocol.name": "amqp",
1409
+ "messaging.rabbitmq.destination.routing_key": "",
1410
+ },
1411
+ PRODUCER,
1412
+ ],
1413
+ [
1414
+ "RabbitMQ.Client v7 deliver",
1415
+ {
1416
+ "messaging.operation.type": "process",
1417
+ "messaging.operation.name": "deliver",
1418
+ "network.protocol.name": "amqp",
1419
+ "messaging.rabbitmq.destination.routing_key": "",
1420
+ },
1421
+ CONSUMER,
1422
+ ],
1423
+ [
1424
+ "Java agent default-mode process",
1425
+ { "messaging.operation": "process" },
1426
+ CONSUMER,
1427
+ ],
1428
+ // The Java agent's opt-in publish: the exchange alone, the key empty.
1429
+ [
1430
+ "Java agent opt-in publish",
1431
+ {
1432
+ "messaging.operation.type": "send",
1433
+ "messaging.operation.name": "publish",
1434
+ },
1435
+ PRODUCER,
1436
+ ],
1437
+ ];
1438
+ for (const [shape, conventions, kind] of shapes) {
1439
+ const destinations: Array<string | null> = exchanges.map(
1440
+ (exchange: string): string | null => {
1441
+ return (
1442
+ resolveSpan(
1443
+ {
1444
+ "messaging.system": "rabbitmq",
1445
+ "messaging.destination.name": exchange,
1446
+ ...conventions,
1447
+ },
1448
+ kind,
1449
+ )?.destination ?? null
1450
+ );
1451
+ },
1452
+ );
1453
+ expect({ shape, destinations }).toEqual({
1454
+ shape,
1455
+ destinations: exchanges,
1456
+ });
1457
+ }
1458
+ });
1459
+
1460
+ test("a three-part name on a span with no direction is one name", () => {
1461
+ expect(
1462
+ resolveSpan({
1463
+ "messaging.system": "rabbitmq",
1464
+ "messaging.destination.name":
1465
+ "MassTransit:Fault--Sample.Contracts:SubmitOrder--",
1466
+ })?.destination,
1467
+ ).toBe("MassTransit:Fault--Sample.Contracts:SubmitOrder--");
1468
+ expect(
1469
+ resolveSpan({
1470
+ "messaging.system": "rabbitmq",
1471
+ "messaging.destination.name": "direct_logs:warning:my_queue",
1472
+ })?.destination,
1473
+ ).toBe("direct_logs:warning:my_queue");
1474
+ // Its routing key as the middle part does not make it a consumer's.
1475
+ expect(
1476
+ resolveSpan({
1477
+ "messaging.system": "rabbitmq",
1478
+ "messaging.destination.name": "direct_logs:warning:my_queue",
1479
+ "messaging.rabbitmq.destination.routing_key": "warning",
1480
+ })?.destination,
1481
+ ).toBe("direct_logs:warning:my_queue");
1482
+ });
1483
+
1484
+ test("the pre-1.17 routing key key is read too", () => {
1485
+ expect(
1486
+ destinationFor("rabbitmq", "<default>", {
1487
+ "messaging.rabbitmq.routing_key": "orders",
1488
+ }),
1489
+ ).toBe("orders");
1490
+ });
1491
+
1492
+ test("with no destination at all, a RabbitMQ span names the routing key (the default exchange)", () => {
1493
+ expect(
1494
+ resolveSpan(
1495
+ {
1496
+ "messaging.system": "rabbitmq",
1497
+ "messaging.rabbitmq.destination.routing_key": "orders",
1498
+ },
1499
+ PRODUCER,
1500
+ )?.destination,
1501
+ ).toBe("orders");
1502
+ });
1503
+
1504
+ test("a bare routing key is a queue name: its colons are never split", () => {
1505
+ expect(
1506
+ resolveSpan(
1507
+ {
1508
+ "messaging.system": "rabbitmq",
1509
+ "messaging.rabbitmq.destination.routing_key": "tenant:orders",
1510
+ },
1511
+ PRODUCER,
1512
+ )?.destination,
1513
+ ).toBe("tenant:orders");
1514
+ });
1515
+
1516
+ test("other systems never fall back to a routing key", () => {
1517
+ expect(
1518
+ resolveSpan(
1519
+ {
1520
+ "messaging.system": "kafka",
1521
+ "messaging.rabbitmq.destination.routing_key": "orders",
1522
+ },
1523
+ PRODUCER,
1524
+ ),
1525
+ ).toBeNull();
1526
+ });
1527
+
1528
+ test.each([
1529
+ ["activemq", "ActiveMQ.DLQ", true],
1530
+ ["activemq", "activemq.dlq", true],
1531
+ ["jms", "DLQ.orders", true],
1532
+ ["activemq", "orders", false],
1533
+ ["rocketmq", "%DLQ%billing", true],
1534
+ ["rocketmq", "%dlq%billing", true],
1535
+ ["rocketmq", "orders", false],
1536
+ // A DLQ-looking name elsewhere is just a name.
1537
+ ["kafka", "DLQ.orders", false],
1538
+ ["aws_sqs", "orders-dlq", false],
1539
+ ])(
1540
+ "%s %s is a dead-letter queue: %s",
1541
+ (system: string, raw: string, isDeadLetter: boolean) => {
1542
+ expect(
1543
+ resolveSpan(
1544
+ {
1545
+ "messaging.system": system,
1546
+ "messaging.destination.name": raw,
1547
+ },
1548
+ CONSUMER,
1549
+ )?.isDeadLetter,
1550
+ ).toBe(isDeadLetter);
1551
+ },
1552
+ );
1553
+ });
1554
+
1555
+ describe("temporary, anonymous and placeholder destinations", () => {
1556
+ test.each([
1557
+ ["messaging.destination.temporary", true],
1558
+ ["messaging.destination.temporary", "true"],
1559
+ ["messaging.destination.temporary", "TRUE"],
1560
+ ["messaging.destination.temporary", " True "],
1561
+ ["messaging.destination.anonymous", true],
1562
+ ["messaging.destination.anonymous", "true"],
1563
+ ["messaging.source.temporary", true],
1564
+ ["messaging.source.anonymous", "true"],
1565
+ ])("%s = %j marks no queue", (key: string, value: boolean | string) => {
1566
+ expect(destinationFor("kafka", "orders", { [key]: value })).toBeNull();
1567
+ });
1568
+
1569
+ test.each([
1570
+ ["messaging.destination.temporary", false],
1571
+ ["messaging.destination.temporary", "false"],
1572
+ ["messaging.destination.anonymous", ""],
1573
+ ["messaging.destination.anonymous", "yes"],
1574
+ ["messaging.destination.temporary", 1],
1575
+ // Pika and aio-pika set it on every publish: never trusted.
1576
+ ["messaging.temp_destination", true],
1577
+ ])("%s = %j is not a temporary mark", (key: string, value: unknown) => {
1578
+ expect(destinationFor("kafka", "orders", { [key]: value })).toBe("orders");
1579
+ });
1580
+
1581
+ test.each([
1582
+ ["kafka", "(temporary)", true],
1583
+ ["kafka", "(Anonymous)", true],
1584
+ ["rabbitmq", "<generated>", true],
1585
+ ["kafka", "<default>", true],
1586
+ ["kafka", "unknown", true],
1587
+ ["kafka", "UNKNOWN", true],
1588
+ ["aws_sqs", "aws:sqs", true],
1589
+ ["aws.sns", "phone_number", true],
1590
+ ["aws.sns", "phone_number:**", true],
1591
+ ["aws.sns", "PHONE_NUMBER:**", true],
1592
+ ["aws.sns", "+15555550100", true],
1593
+ ["aws.sns", "+44 20 7946 0958", true],
1594
+ ["servicebus", "-NamespaceOnlyMetric-", true],
1595
+ ["kafka", "5f2b7c1e-8d3a-4b6f-9c0d-1e2f3a4b5c6d", true],
1596
+ ["kafka", "5F2B7C1E-8D3A-4B6F-9C0D-1E2F3A4B5C6D", true],
1597
+ ["jms", "temp-queue://ID:host-1-2-3:1:1", true],
1598
+ ["activemq", "temp-topic://ID:host-1-2-3:1:1", true],
1599
+ ["rabbitmq", "amq.gen-JzTY20BRgKO-HjmUJj0wLg", true],
1600
+ ["rabbitmq", "spring.gen-u8IWOpPoQf2a8uJ4mZr1QQ", true],
1601
+ ["rabbitmq", "amq.rabbitmq.reply-to", true],
1602
+ ["rabbitmq", "amq.rabbitmq.reply-to.g1h2AA5yZXBseS10bw==", true],
1603
+ ["rabbitmq", "orders.anonymous.X8o1yfRBQXSp5Bdd09ls7w", true],
1604
+ ["jms", "$TMP$.EMS.1", true],
1605
+ ["activemq", "$tmp$.x", true],
1606
+ ["jms", "ActiveMQ.Advisory.Connection", true],
1607
+ ["activemq", "ActiveMQ.Advisory.Producer.Queue.orders", true],
1608
+ ["nats", "_INBOX", true],
1609
+ ["nats", "_INBOX.abc123", true],
1610
+ ["nats", "_INBOX.", true],
1611
+ ["nats", "$JS.ACK", true],
1612
+ ["nats", "$JS.ACK.ORDERS.billing.1.2.3.4.0", true],
1613
+ ["pulsar", "persistent://public/default/__change_events", true],
1614
+ ["pulsar", "__change_events", true],
1615
+ ["pulsar", "__transaction_log_1", true],
1616
+ ["pulsar", "persistent://pulsar/system/__transaction_buffer", true],
1617
+ // Real names.
1618
+ ["kafka", "orders", false],
1619
+ ["kafka", "unknown-orders", false],
1620
+ ["rabbitmq", "amq.topic", false],
1621
+ ["rabbitmq", "orders.anonymous.short", false],
1622
+ ["rabbitmq", "amq.gen", false],
1623
+ ["nats", "_INBOXES", false],
1624
+ ["nats", "orders._INBOX", false],
1625
+ ["pulsar", "persistent://public/default/orders", false],
1626
+ ["pulsar", "persistent://public/default/my__topic", false],
1627
+ ["aws.sns", "order-events", false],
1628
+ ["aws.sns", "+orders", false],
1629
+ ["activemq", "ActiveMQ.DLQ", false],
1630
+ ["servicebus", "orders", false],
1631
+ // Rules are per system: another system's generated name is a name.
1632
+ ["kafka", "amq.gen-JzTY20BRgKO-HjmUJj0wLg", false],
1633
+ ["kafka", "_INBOX.abc", false],
1634
+ ["kafka", "ActiveMQ.Advisory.Connection", false],
1635
+ ["rabbitmq", "$TMP$.x", false],
1636
+ ["kafka", "__change_events", false],
1637
+ ["kafka", "+15555550100", false],
1638
+ /*
1639
+ * The SMS placeholders are SNS's, and exact: real destinations of any
1640
+ * system may start with "phone_number" (an SNS topic name cannot hold
1641
+ * the ":" of the censored placeholder).
1642
+ */
1643
+ ["aws.sns", "phone_numbers", false],
1644
+ ["aws.sns", "phone_number_verified", false],
1645
+ ["kafka", "phone_number", false],
1646
+ ["kafka", "phone_numbers", false],
1647
+ ["kafka", "phone_number_verified", false],
1648
+ ["rabbitmq", "phone_number.changed", false],
1649
+ ["rabbitmq", "phone_number_verification", false],
1650
+ ["aws_sqs", "phone_number_updates", false],
1651
+ // What a scrubber leaves of a WHOLE value names nothing, for any system.
1652
+ ["kafka", "[REDACTED]", true],
1653
+ ["aws_sqs", "[redacted]", true],
1654
+ ["rabbitmq", "[HASHED:ab12cd34]", true],
1655
+ ["servicebus", "[hashed:0F1E2D3C]", true],
1656
+ ["kafka", "***", true],
1657
+ ["jms", "*", true],
1658
+ // A value scrubbed only in part still names its queue.
1659
+ ["kafka", "orders-[REDACTED]", false],
1660
+ ["kafka", "tenant-[HASHED:ab12cd34]-events", false],
1661
+ ["kafka", "orders-***", false],
1662
+ ["kafka", "[HASHED:not-hex]", false],
1663
+ ])(
1664
+ "isTemporaryMessagingDestination(%s, %s) = %s",
1665
+ (system: string, destination: string, temporary: boolean) => {
1666
+ expect(isTemporaryMessagingDestination(system, destination)).toBe(
1667
+ temporary,
1668
+ );
1669
+ },
1670
+ );
1671
+
1672
+ test("system aliases apply their system's rules", () => {
1673
+ expect(
1674
+ isTemporaryMessagingDestination("artemis", "ActiveMQ.Advisory.Topic"),
1675
+ ).toBe(true);
1676
+ expect(isTemporaryMessagingDestination("jetstream", "_INBOX.x")).toBe(true);
1677
+ });
1678
+
1679
+ test("a blank or non-string name is never a queue", () => {
1680
+ expect(isTemporaryMessagingDestination("kafka", "")).toBe(true);
1681
+ expect(isTemporaryMessagingDestination("kafka", " ")).toBe(true);
1682
+ expect(
1683
+ isTemporaryMessagingDestination("kafka", null as unknown as string),
1684
+ ).toBe(true);
1685
+ expect(
1686
+ isTemporaryMessagingDestination("kafka", 42 as unknown as string),
1687
+ ).toBe(true);
1688
+ });
1689
+
1690
+ test("an unknown or empty system gets only the general rules", () => {
1691
+ expect(isTemporaryMessagingDestination("ibmmq", "unknown")).toBe(true);
1692
+ expect(isTemporaryMessagingDestination("ibmmq", "DEV.QUEUE.1")).toBe(false);
1693
+ expect(isTemporaryMessagingDestination("", "(temporary)")).toBe(true);
1694
+ expect(isTemporaryMessagingDestination("", "orders")).toBe(false);
1695
+ });
1696
+
1697
+ test("real destinations named phone_number… resolve on every system but SNS's placeholders", () => {
1698
+ expect(
1699
+ resolveSpan(
1700
+ {
1701
+ "messaging.system": "kafka",
1702
+ "messaging.destination.name": "phone_numbers",
1703
+ },
1704
+ PRODUCER,
1705
+ ),
1706
+ ).toEqual(
1707
+ destinationOf({
1708
+ system: "kafka",
1709
+ destination: "phone_numbers",
1710
+ direction: "publish",
1711
+ }),
1712
+ );
1713
+ expect(destinationFor("kafka", "phone_number_verified")).toBe(
1714
+ "phone_number_verified",
1715
+ );
1716
+ expect(destinationFor("aws_sqs", "phone_number_updates")).toBe(
1717
+ "phone_number_updates",
1718
+ );
1719
+ expect(
1720
+ destinationFor("rabbitmq", "amq.default", {
1721
+ "messaging.rabbitmq.destination.routing_key":
1722
+ "phone_number_verification",
1723
+ }),
1724
+ ).toBe("phone_number_verification");
1725
+ expect(destinationFor("aws.sns", "phone_numbers")).toBe("phone_numbers");
1726
+ // Python botocore's SMS publish: destination "phone_number:**".
1727
+ expect(destinationFor("aws.sns", "phone_number:**")).toBeNull();
1728
+ expect(
1729
+ resolveSpan(
1730
+ {
1731
+ "rpc.system": "aws-api",
1732
+ "rpc.service": "SNS",
1733
+ "messaging.destination": "phone_number:**",
1734
+ "messaging.destination.name": "phone_number:**",
1735
+ },
1736
+ PRODUCER,
1737
+ ),
1738
+ ).toBeNull();
1739
+ });
1740
+
1741
+ test("a scrubbed destination is no queue on any system, whatever its normalizer does", () => {
1742
+ for (const system of [
1743
+ "kafka",
1744
+ "rabbitmq",
1745
+ "aws_sqs",
1746
+ "aws.sns",
1747
+ "gcp_pubsub",
1748
+ "servicebus",
1749
+ "pulsar",
1750
+ "activemq",
1751
+ "ibmmq",
1752
+ ]) {
1753
+ for (const scrubbed of ["[REDACTED]", "[HASHED:5e3e9847]", "***"]) {
1754
+ expect({
1755
+ system,
1756
+ scrubbed,
1757
+ queue: destinationFor(system, scrubbed),
1758
+ }).toEqual({
1759
+ system,
1760
+ scrubbed,
1761
+ queue: null,
1762
+ });
1763
+ }
1764
+ }
1765
+ // Pulsar would otherwise expand it into persistent://public/default/….
1766
+ expect(destinationFor("pulsar", "[REDACTED]")).toBeNull();
1767
+ // Scrubbed in part, the rest still names the queue.
1768
+ expect(destinationFor("kafka", "user-[REDACTED]-events")).toBe(
1769
+ "user-[REDACTED]-events",
1770
+ );
1771
+ });
1772
+ });
1773
+
1774
+ /*
1775
+ * MassTransit's per-process endpoints, named the way MassTransit names them:
1776
+ * DefaultEndpointNameFormatter.GetTemporaryQueueName writes
1777
+ * `{machine}_{process}_{tag}_{id}`, the machine and process names stripped
1778
+ * to ASCII letters and digits (the machine cut to 14 characters when the
1779
+ * whole would pass 72), and InstanceEndpointDefinition `Instance_{id}`; the
1780
+ * {id} is a NewId in lowercase z-base-32 (these are ZBase32Formatter.Format
1781
+ * of NewIds' formatter arrays).
1782
+ */
1783
+ describe("MassTransit's per-process endpoints", () => {
1784
+ // The pod ordersapi-7d9f8c6b5-xk2lq running OrdersApi: its bus endpoint.
1785
+ const BUS_ENDPOINT: string =
1786
+ "ordersapi7d9f8c6b5xk2lq_OrdersApi_bus_kd4oyqbeynuojexybdxt7414fx";
1787
+ // The next pod of the same deployment: a new name.
1788
+ const NEXT_POD_BUS_ENDPOINT: string =
1789
+ "ordersapi7d9f8c6b5mnp4r_OrdersApi_bus_kawyb4raybmxtdeybdxt7hihry";
1790
+ const INSTANCE_ENDPOINT: string = "Instance_axhob3yrynqhyacybdxt6jeg8m";
1791
+
1792
+ test.each([
1793
+ ["rabbitmq", BUS_ENDPOINT, true],
1794
+ ["rabbitmq", NEXT_POD_BUS_ENDPOINT, true],
1795
+ // TemporaryEndpointDefinition's default tag, on a Windows host.
1796
+ [
1797
+ "rabbitmq",
1798
+ "BILLINGWORKER02_dotnet_endpoint_7d4yysgeydaos4hybdxt76a6rx",
1799
+ true,
1800
+ ],
1801
+ // ConnectResponseEndpoint.
1802
+ [
1803
+ "rabbitmq",
1804
+ "ordersapi7d9f8c6b5xk2lq_OrdersApi_response_3rfybnfaybu7a1yybdxt6yhynm",
1805
+ true,
1806
+ ],
1807
+ // A SignalR hub's endpoint: its tag holds an underscore.
1808
+ [
1809
+ "rabbitmq",
1810
+ "webfrontend6c8d9_WebFrontend_signalr_ChatHub_6x7ry6deyyr7c3hybdxt6n9b88",
1811
+ true,
1812
+ ],
1813
+ // ip-10-0-12-34.eu-west-1.compute.internal, cut to 14 characters.
1814
+ [
1815
+ "rabbitmq",
1816
+ "ip10012034euwe_ContosoBillingWorker_bus_68pobtdryda85beybdxt68ffbx",
1817
+ true,
1818
+ ],
1819
+ // A service's instance endpoint, under each endpoint name formatter.
1820
+ ["rabbitmq", INSTANCE_ENDPOINT, true],
1821
+ ["rabbitmq", "instance_axhob3yrynqhyacybdxt6jeg8m", true],
1822
+ ["rabbitmq", "instance-axhob3yrynqhyacybdxt6jeg8m", true],
1823
+ // Every broker MassTransit runs on, by any alias, in any case.
1824
+ ["activemq", BUS_ENDPOINT, true],
1825
+ ["artemis", BUS_ENDPOINT, true],
1826
+ ["jms", BUS_ENDPOINT, true],
1827
+ ["servicebus", BUS_ENDPOINT, true],
1828
+ ["servicebus", BUS_ENDPOINT.toUpperCase(), true],
1829
+ ["azure_servicebus", INSTANCE_ENDPOINT, true],
1830
+ ["aws_sqs", BUS_ENDPOINT, true],
1831
+ ["amazonsqs", NEXT_POD_BUS_ENDPOINT, true],
1832
+ // Real names, however close.
1833
+ ["rabbitmq", "event_bus_orders", false],
1834
+ ["rabbitmq", "service_bus_topic", false],
1835
+ ["rabbitmq", "orders_endpoint_v2", false],
1836
+ ["rabbitmq", "submit-order", false],
1837
+ ["rabbitmq", "instance_settings", false],
1838
+ ["rabbitmq", "orders_kd4oyqbeynuojexybdxt7414fx", false],
1839
+ // An id is 26 z-base-32 characters (no l, v, 0 or 2): no more, no fewer.
1840
+ ["rabbitmq", BUS_ENDPOINT.slice(0, -1), false],
1841
+ ["rabbitmq", `${BUS_ENDPOINT}y`, false],
1842
+ ["rabbitmq", `${BUS_ENDPOINT.slice(0, -1)}l`, false],
1843
+ ["rabbitmq", `${BUS_ENDPOINT.slice(0, -1)}0`, false],
1844
+ // MassTransit strips the machine name to letters and digits.
1845
+ [
1846
+ "rabbitmq",
1847
+ "ordersapi-7d9f8c6b5-xk2lq_OrdersApi_bus_kd4oyqbeynuojexybdxt7414fx",
1848
+ false,
1849
+ ],
1850
+ // Only on the brokers MassTransit runs on.
1851
+ ["kafka", BUS_ENDPOINT, false],
1852
+ ["eventhubs", BUS_ENDPOINT, false],
1853
+ ["gcp_pubsub", BUS_ENDPOINT, false],
1854
+ ["kafka", INSTANCE_ENDPOINT, false],
1855
+ ])(
1856
+ "isTemporaryMessagingDestination(%s, %s) = %s",
1857
+ (system: string, destination: string, temporary: boolean) => {
1858
+ expect(isTemporaryMessagingDestination(system, destination)).toBe(
1859
+ temporary,
1860
+ );
1861
+ },
1862
+ );
1863
+
1864
+ test("however a span names one, it is no queue; the service's own endpoints are", () => {
1865
+ // RabbitMQ.Client v7: a responder answering on the requester's bus endpoint.
1866
+ expect(
1867
+ resolveSpan(
1868
+ {
1869
+ "messaging.system": "rabbitmq",
1870
+ "network.protocol.name": "amqp",
1871
+ "messaging.operation.type": "send",
1872
+ "messaging.operation.name": "publish",
1873
+ "messaging.destination.name": BUS_ENDPOINT,
1874
+ "messaging.rabbitmq.destination.routing_key": "",
1875
+ },
1876
+ PRODUCER,
1877
+ ),
1878
+ ).toBeNull();
1879
+ /*
1880
+ * MassTransit's own send span folds only "_bus_", "_endpoint_",
1881
+ * "_signalr_" and "Instance_" names: a response endpoint's is whole.
1882
+ */
1883
+ expect(
1884
+ resolveSpan(
1885
+ {
1886
+ "messaging.system": "rabbitmq",
1887
+ "messaging.operation": "send",
1888
+ "messaging.destination.name":
1889
+ "ordersapi7d9f8c6b5xk2lq_OrdersApi_response_3rfybnfaybu7a1yybdxt6yhynm",
1890
+ },
1891
+ PRODUCER,
1892
+ ),
1893
+ ).toBeNull();
1894
+ expect(
1895
+ resolveSpan(
1896
+ {
1897
+ "messaging.system": "servicebus",
1898
+ "messaging.destination.name": BUS_ENDPOINT,
1899
+ "server.address": "shop-prod.servicebus.windows.net",
1900
+ },
1901
+ CONSUMER,
1902
+ ),
1903
+ ).toBeNull();
1904
+ expect(
1905
+ resolveSpan(
1906
+ {
1907
+ "messaging.system": "aws_sqs",
1908
+ "aws.sqs.queue.url": `https://sqs.eu-west-1.amazonaws.com/123456789012/${NEXT_POD_BUS_ENDPOINT}`,
1909
+ },
1910
+ CONSUMER,
1911
+ ),
1912
+ ).toBeNull();
1913
+ expect(
1914
+ resolveSpan(
1915
+ {
1916
+ "messaging.system": "rabbitmq",
1917
+ "messaging.operation": "process",
1918
+ "messaging.destination.name": "submit-order",
1919
+ },
1920
+ CONSUMER,
1921
+ )?.destination,
1922
+ ).toBe("submit-order");
1923
+ });
1924
+
1925
+ test("a name longer than a queue name may be is never tested, and never a queue", () => {
1926
+ const long: string = `a_b_${"x_".repeat(20000)}kd4oyqbeynuojexybdxt7414fx`;
1927
+ expect(isTemporaryMessagingDestination("rabbitmq", long)).toBe(false);
1928
+ expect(destinationFor("rabbitmq", long)).toBeNull();
1929
+ });
1930
+ });
1931
+
1932
+ describe("control characters", () => {
1933
+ test.each([
1934
+ ["NUL", "orders\u0000x"],
1935
+ ["tab", "orders\tx"],
1936
+ ["newline", "orders\nx"],
1937
+ ["escape", "orders\u001bx"],
1938
+ ["unit separator", "orders\u001fx"],
1939
+ ["DEL", "orders\u007fx"],
1940
+ ])(
1941
+ "a destination holding %s is no queue (Postgres cannot store U+0000, and none belongs in a name)",
1942
+ (_name: string, destination: string) => {
1943
+ expect(destinationFor("kafka", destination)).toBeNull();
1944
+ expect(destinationFor("rabbitmq", destination)).toBeNull();
1945
+ },
1946
+ );
1947
+
1948
+ test("surrounding whitespace is trimmed, never rejected; other characters are kept", () => {
1949
+ expect(destinationFor("kafka", "\torders\n")).toBe("orders");
1950
+ expect(destinationFor("kafka", "orders\u0085x")).toBe("orders\u0085x");
1951
+ expect(destinationFor("kafka", "orders é ü")).toBe("orders é ü");
1952
+ });
1953
+
1954
+ test("a display-only value holding one is dropped, not the queue", () => {
1955
+ const resolved: ResolvedMessagingDestination | null = resolveSpan(
1956
+ {
1957
+ "messaging.system": "kafka",
1958
+ "messaging.destination.name": "orders",
1959
+ "server.address": "kafka-1\u0000evil",
1960
+ "messaging.kafka.consumer.group": "billing\u0000x",
1961
+ },
1962
+ CONSUMER,
1963
+ );
1964
+ expect(resolved).toEqual(
1965
+ destinationOf({
1966
+ system: "kafka",
1967
+ destination: "orders",
1968
+ direction: "consume",
1969
+ }),
1970
+ );
1971
+ });
1972
+ });
1973
+
1974
+ describe("blank values", () => {
1975
+ test("the allocation-free trigger check treats exactly what String.prototype.trim removes as blank", () => {
1976
+ const disagreements: Array<string> = [];
1977
+ for (let code: number = 0; code <= 0xffff; code++) {
1978
+ const character: string = String.fromCharCode(code);
1979
+ const blank: boolean = character.trim() === "";
1980
+ const trigger: boolean = hasMessagingTrigger({
1981
+ "messaging.system": `${character}${character}`,
1982
+ });
1983
+ if (trigger === blank) {
1984
+ disagreements.push(code.toString(16));
1985
+ }
1986
+ }
1987
+ expect(disagreements).toEqual([]);
1988
+ });
1989
+ });
1990
+
1991
+ describe("UUID templating and length", () => {
1992
+ test("each UUID becomes {uuid}, so a per-request queue keys one queue", () => {
1993
+ expect(
1994
+ destinationFor("rabbitmq", "reply-5f2b7c1e-8d3a-4b6f-9c0d-1e2f3a4b5c6d"),
1995
+ ).toBe("reply-{uuid}");
1996
+ expect(
1997
+ destinationFor(
1998
+ "kafka",
1999
+ "tenant-5F2B7C1E-8D3A-4B6F-9C0D-1E2F3A4B5C6D.orders.0f1e2d3c-4b5a-6978-8a9b-0c1d2e3f4a5b",
2000
+ ),
2001
+ ).toBe("tenant-{uuid}.orders.{uuid}");
2002
+ // Different UUIDs land on the same destination.
2003
+ expect(
2004
+ destinationFor("kafka", "jobs-11111111-2222-3333-4444-555555555555"),
2005
+ ).toBe(
2006
+ destinationFor("kafka", "jobs-66666666-7777-8888-9999-000000000000"),
2007
+ );
2008
+ });
2009
+
2010
+ test("a bare UUID is generated, not templated", () => {
2011
+ expect(
2012
+ destinationFor("kafka", "5f2b7c1e-8d3a-4b6f-9c0d-1e2f3a4b5c6d"),
2013
+ ).toBeNull();
2014
+ });
2015
+
2016
+ test("something UUID-like but not canonical is kept", () => {
2017
+ expect(destinationFor("kafka", "5f2b7c1e8d3a4b6f9c0d1e2f3a4b5c6d")).toBe(
2018
+ "5f2b7c1e8d3a4b6f9c0d1e2f3a4b5c6d",
2019
+ );
2020
+ expect(destinationFor("kafka", "orders-2026-09-30")).toBe(
2021
+ "orders-2026-09-30",
2022
+ );
2023
+ });
2024
+
2025
+ test("a destination may be 255 characters, not 256", () => {
2026
+ expect(MESSAGE_QUEUE_DESTINATION_MAX_LENGTH).toBe(255);
2027
+ expect(destinationFor("kafka", "a".repeat(255))).toBe("a".repeat(255));
2028
+ expect(destinationFor("kafka", "a".repeat(256))).toBeNull();
2029
+ });
2030
+
2031
+ test("the length is checked after normalization and templating", () => {
2032
+ // 36-character UUIDs shrink to 6 characters each.
2033
+ const long: string = `${"q".repeat(200)}${"-5f2b7c1e-8d3a-4b6f-9c0d-1e2f3a4b5c6d".repeat(3)}`;
2034
+ expect(long.length).toBeGreaterThan(255);
2035
+ expect(destinationFor("kafka", long)).toBe(
2036
+ `${"q".repeat(200)}-{uuid}-{uuid}-{uuid}`,
2037
+ );
2038
+ // An SQS URL longer than 255 whose queue name is short is fine.
2039
+ expect(
2040
+ destinationFor(
2041
+ "aws_sqs",
2042
+ `https://sqs.us-east-1.amazonaws.com/${"1".repeat(300)}/orders`,
2043
+ ),
2044
+ ).toBe("orders");
2045
+ });
2046
+ });
2047
+
2048
+ describe("broker scope (Azure namespaces only)", () => {
2049
+ function scopeOf(
2050
+ system: string,
2051
+ address: FixtureAttributes,
2052
+ ): string | undefined {
2053
+ return resolveSpan(
2054
+ {
2055
+ "messaging.system": system,
2056
+ "messaging.destination.name": "orders",
2057
+ ...address,
2058
+ },
2059
+ PRODUCER,
2060
+ )?.brokerScope;
2061
+ }
2062
+
2063
+ test.each([
2064
+ ["server.address", "orders-prod.servicebus.windows.net", "orders-prod"],
2065
+ ["server.address", "ORDERS-PROD.ServiceBus.Windows.Net", "orders-prod"],
2066
+ ["server.address", "orders-prod.servicebus.windows.net.", "orders-prod"],
2067
+ ["net.peer.name", "orders-prod.servicebus.windows.net", "orders-prod"],
2068
+ ["peer.address", "sb://orders-prod.servicebus.windows.net/", "orders-prod"],
2069
+ [
2070
+ "peer.address",
2071
+ "amqps://orders-prod.servicebus.windows.net:5671",
2072
+ "orders-prod",
2073
+ ],
2074
+ [
2075
+ "network.peer.address",
2076
+ "orders-prod.servicebus.windows.net",
2077
+ "orders-prod",
2078
+ ],
2079
+ ["server.address", "gov.servicebus.usgovcloudapi.net", "gov"],
2080
+ ["server.address", "cn.servicebus.chinacloudapi.cn", "cn"],
2081
+ ["server.address", "de.servicebus.cloudapi.de", "de"],
2082
+ ["server.address", "localhost", ""],
2083
+ ["server.address", "10.0.0.5", ""],
2084
+ ["server.address", "servicebus.windows.net", ""],
2085
+ ["server.address", "orders-prod.servicebus.windows.net.evil.com", ""],
2086
+ ["server.address", "orders.custom-domain.com", ""],
2087
+ ])(
2088
+ "a Service Bus span with %s = %s is scoped %j",
2089
+ (key: string, value: string, scope: string) => {
2090
+ expect(scopeOf("servicebus", { [key]: value })).toBe(scope);
2091
+ expect(scopeOf("eventhubs", { [key]: value })).toBe(scope);
2092
+ },
2093
+ );
2094
+
2095
+ test("the first key naming a namespace wins, in precedence order", () => {
2096
+ expect(
2097
+ scopeOf("servicebus", {
2098
+ "server.address": "10.0.0.5",
2099
+ "net.peer.name": "second.servicebus.windows.net",
2100
+ "peer.address": "sb://third.servicebus.windows.net/",
2101
+ }),
2102
+ ).toBe("second");
2103
+ expect(
2104
+ scopeOf("servicebus", {
2105
+ "server.address": "first.servicebus.windows.net",
2106
+ "net.peer.name": "second.servicebus.windows.net",
2107
+ }),
2108
+ ).toBe("first");
2109
+ });
2110
+
2111
+ test("no address at all leaves the scope empty", () => {
2112
+ expect(scopeOf("servicebus", {})).toBe("");
2113
+ });
2114
+
2115
+ test("every other system has an empty scope, even on a namespace host", () => {
2116
+ for (const system of ["kafka", "eventgrid", "rabbitmq", "aws_sqs"]) {
2117
+ expect(
2118
+ scopeOf(system, {
2119
+ "server.address": "orders-prod.servicebus.windows.net",
2120
+ }),
2121
+ ).toBe("");
2122
+ }
2123
+ });
2124
+ });
2125
+
2126
+ describe("broker address (display only)", () => {
2127
+ function addressOf(attributes: FixtureAttributes): string | null | undefined {
2128
+ return resolveSpan(
2129
+ {
2130
+ "messaging.system": "kafka",
2131
+ "messaging.destination.name": "orders",
2132
+ ...attributes,
2133
+ },
2134
+ PRODUCER,
2135
+ )?.brokerAddress;
2136
+ }
2137
+
2138
+ test.each([
2139
+ [{ "server.address": "kafka-1" }, "kafka-1"],
2140
+ [{ "server.address": "kafka-1", "server.port": 9092 }, "kafka-1:9092"],
2141
+ [{ "server.address": "kafka-1", "server.port": "9092" }, "kafka-1:9092"],
2142
+ [{ "server.address": "kafka-1", "server.port": "09092" }, "kafka-1:9092"],
2143
+ [{ "server.address": "kafka-1:9093", "server.port": 9092 }, "kafka-1:9093"],
2144
+ [{ "server.address": "kafka-1", "server.port": "abc" }, "kafka-1"],
2145
+ [{ "server.address": "kafka-1", "server.port": 70000 }, "kafka-1"],
2146
+ [{ "server.address": "kafka-1", "server.port": 0 }, "kafka-1"],
2147
+ [{ "server.address": "::1", "server.port": 9092 }, "[::1]:9092"],
2148
+ [{ "server.address": "[::1]:9092" }, "[::1]:9092"],
2149
+ [
2150
+ { "server.address": "[2001:db8::1]", "server.port": 9092 },
2151
+ "[2001:db8::1]:9092",
2152
+ ],
2153
+ [{ "server.address": "2001:db8::1" }, "2001:db8::1"],
2154
+ [{ "server.address": "kafka-1:9092,kafka-2:9092" }, "kafka-1:9092"],
2155
+ [{ "server.address": '["kafka-1:9092", "kafka-2:9092"]' }, "kafka-1:9092"],
2156
+ [{ "server.address": "['kafka-1:9092', 'kafka-2:9092']" }, "kafka-1:9092"],
2157
+ [{ "server.address": ["kafka-1:9092", "kafka-2:9092"] }, "kafka-1:9092"],
2158
+ [{ "server.address": "[]" }, null],
2159
+ [{ "server.address": " " }, null],
2160
+ [{ "net.peer.name": "kafka-1", "net.peer.port": 9092 }, "kafka-1:9092"],
2161
+ [
2162
+ { "network.peer.address": "10.0.0.7", "network.peer.port": 9092 },
2163
+ "10.0.0.7:9092",
2164
+ ],
2165
+ [{ "net.sock.peer.addr": "10.0.0.8" }, "10.0.0.8"],
2166
+ [{ "server.socket.address": "10.0.0.9" }, "10.0.0.9"],
2167
+ [
2168
+ { "peer.address": "sb://ns.servicebus.windows.net/" },
2169
+ "ns.servicebus.windows.net",
2170
+ ],
2171
+ [
2172
+ { "messaging.url": "amqp://guest:secret@rabbit.prod:5672/vhost" },
2173
+ "rabbit.prod:5672",
2174
+ ],
2175
+ [{ "messaging.url": "amqps://user:p%40ss@rabbit.prod" }, "rabbit.prod"],
2176
+ [{ "messaging.url": "user:secret@rabbit.prod:5672" }, "rabbit.prod:5672"],
2177
+ [{ "messaging.url": "rabbit.prod:5672/vhost" }, "rabbit.prod:5672"],
2178
+ [
2179
+ { "messaging.url": "https://sqs.us-east-1.amazonaws.com/1/q" },
2180
+ "sqs.us-east-1.amazonaws.com",
2181
+ ],
2182
+ [
2183
+ { "messaging.url": "https://host.example:8443/path?q=1#f" },
2184
+ "host.example:8443",
2185
+ ],
2186
+ ])("%j → %j", (attributes: FixtureAttributes, address: string | null) => {
2187
+ expect(addressOf(attributes)).toBe(address);
2188
+ });
2189
+
2190
+ test("server.address wins over every other key; the port pairs with its own address key", () => {
2191
+ expect(
2192
+ addressOf({
2193
+ "server.address": "a",
2194
+ "net.peer.name": "b",
2195
+ "net.peer.port": 1,
2196
+ "network.peer.address": "c",
2197
+ "messaging.url": "amqp://d",
2198
+ }),
2199
+ ).toBe("a");
2200
+ expect(
2201
+ addressOf({
2202
+ "net.peer.name": "b",
2203
+ "server.port": 9092,
2204
+ }),
2205
+ ).toBe("b");
2206
+ });
2207
+
2208
+ test("an address is clamped to 255 characters", () => {
2209
+ const address: string | null | undefined = addressOf({
2210
+ "server.address": `${"h".repeat(300)}.example`,
2211
+ });
2212
+ expect(address?.length).toBe(255);
2213
+ });
2214
+
2215
+ test("no address key leaves it null", () => {
2216
+ expect(addressOf({})).toBeNull();
2217
+ });
2218
+ });
2219
+
2220
+ describe("direction precedence", () => {
2221
+ function directionOf(
2222
+ attributes: FixtureAttributes,
2223
+ kind: string | null,
2224
+ system: string = "kafka",
2225
+ ): string | undefined {
2226
+ return resolveSpan(
2227
+ {
2228
+ "messaging.system": system,
2229
+ "messaging.destination.name": "orders",
2230
+ ...attributes,
2231
+ },
2232
+ kind,
2233
+ )?.direction;
2234
+ }
2235
+
2236
+ test.each([
2237
+ ["send", "publish"],
2238
+ ["publish", "publish"],
2239
+ ["create", "publish"],
2240
+ ["receive", "consume"],
2241
+ ["process", "consume"],
2242
+ ["deliver", "consume"],
2243
+ ["settle", "settle"],
2244
+ ["SEND", "publish"],
2245
+ [" Process ", "consume"],
2246
+ ])("messaging.operation.type %j → %s", (value: string, direction: string) => {
2247
+ expect(directionOf({ "messaging.operation.type": value }, CLIENT)).toBe(
2248
+ direction,
2249
+ );
2250
+ expect(directionOf({ "messaging.operation": value }, CLIENT)).toBe(
2251
+ direction,
2252
+ );
2253
+ });
2254
+
2255
+ test("messaging.operation.type beats the span kind", () => {
2256
+ expect(
2257
+ directionOf({ "messaging.operation.type": "settle" }, CONSUMER),
2258
+ ).toBe("settle");
2259
+ expect(
2260
+ directionOf({ "messaging.operation.type": "receive" }, PRODUCER),
2261
+ ).toBe("consume");
2262
+ });
2263
+
2264
+ test("the span kind beats the legacy messaging.operation (confluent-kafka's 'receive' on producers)", () => {
2265
+ expect(directionOf({ "messaging.operation": "receive" }, PRODUCER)).toBe(
2266
+ "publish",
2267
+ );
2268
+ expect(directionOf({ "messaging.operation": "publish" }, CONSUMER)).toBe(
2269
+ "consume",
2270
+ );
2271
+ });
2272
+
2273
+ test("the legacy operation beats the operation name", () => {
2274
+ expect(
2275
+ directionOf(
2276
+ {
2277
+ "messaging.operation": "settle",
2278
+ "messaging.operation.name": "send",
2279
+ },
2280
+ CLIENT,
2281
+ ),
2282
+ ).toBe("settle");
2283
+ });
2284
+
2285
+ test.each([
2286
+ // The Google Cloud Pub/Sub clients' ack RPC spans (OpenTelemetryPubsubTracer).
2287
+ ["ack", "settle"],
2288
+ ["modack", "settle"],
2289
+ ["nack", "settle"],
2290
+ ["ACK", "settle"],
2291
+ ["subscribe", "consume"],
2292
+ ["poll", "consume"],
2293
+ ["schedule", "publish"],
2294
+ ])(
2295
+ "an operation NAME in the legacy messaging.operation key (%s) → %s",
2296
+ (operation: string, direction: string) => {
2297
+ expect(directionOf({ "messaging.operation": operation }, CLIENT)).toBe(
2298
+ direction,
2299
+ );
2300
+ },
2301
+ );
2302
+
2303
+ test("the legacy key's operation name still yields to the span kind and the operation type", () => {
2304
+ expect(directionOf({ "messaging.operation": "ack" }, PRODUCER)).toBe(
2305
+ "publish",
2306
+ );
2307
+ expect(
2308
+ directionOf(
2309
+ {
2310
+ "messaging.operation": "ack",
2311
+ "messaging.operation.type": "receive",
2312
+ },
2313
+ CLIENT,
2314
+ ),
2315
+ ).toBe("consume");
2316
+ // ...and beats messaging.operation.name, as the legacy type does.
2317
+ expect(
2318
+ directionOf(
2319
+ {
2320
+ "messaging.operation": "ack",
2321
+ "messaging.operation.name": "send",
2322
+ },
2323
+ CLIENT,
2324
+ ),
2325
+ ).toBe("settle");
2326
+ });
2327
+
2328
+ test("a Java Pub/Sub ack span settles its subscription, which is its consumer group", () => {
2329
+ expect(
2330
+ resolveSpan(
2331
+ {
2332
+ "messaging.system": "gcp_pubsub",
2333
+ "messaging.destination.name": "orders-billing",
2334
+ "gcp.project_id": "shop-prod",
2335
+ "code.function": "sendAckOperations",
2336
+ "messaging.operation": "ack",
2337
+ "messaging.batch.message_count": 3,
2338
+ },
2339
+ CLIENT,
2340
+ ),
2341
+ ).toEqual(
2342
+ destinationOf({
2343
+ system: "gcp_pubsub",
2344
+ destination: "orders-billing",
2345
+ direction: "settle",
2346
+ consumerGroup: "orders-billing",
2347
+ }),
2348
+ );
2349
+ });
2350
+
2351
+ test.each([
2352
+ ["send", "publish"],
2353
+ ["publish", "publish"],
2354
+ ["schedule", "publish"],
2355
+ ["publish_input", "publish"],
2356
+ ["publish_batch_input", "publish"],
2357
+ ["create", "publish"],
2358
+ ["event", "publish"],
2359
+ ["poll", "consume"],
2360
+ ["receive", "consume"],
2361
+ ["fetch", "consume"],
2362
+ ["fetch (empty)", "consume"],
2363
+ ["peek", "consume"],
2364
+ ["receive_deferred", "consume"],
2365
+ ["subscribe", "consume"],
2366
+ ["deliver", "consume"],
2367
+ ["consume", "consume"],
2368
+ ["process", "consume"],
2369
+ ["ack", "settle"],
2370
+ ["nack", "settle"],
2371
+ ["modack", "settle"],
2372
+ ["reject", "settle"],
2373
+ ["complete", "settle"],
2374
+ ["abandon", "settle"],
2375
+ ["defer", "settle"],
2376
+ ["dead_letter", "settle"],
2377
+ ["delete", "settle"],
2378
+ ["commit", "settle"],
2379
+ ["checkpoint", "settle"],
2380
+ ["term", "settle"],
2381
+ ["nak", "settle"],
2382
+ ["in-progress", "settle"],
2383
+ ["Checkpoint", "settle"],
2384
+ ["renew_message_lock", "unknown"],
2385
+ ["get_partition_properties", "unknown"],
2386
+ ])("messaging.operation.name %j → %s", (value: string, direction: string) => {
2387
+ expect(directionOf({ "messaging.operation.name": value }, CLIENT)).toBe(
2388
+ direction,
2389
+ );
2390
+ });
2391
+
2392
+ test("the operation name beats the AWS rpc.method", () => {
2393
+ expect(
2394
+ directionOf(
2395
+ {
2396
+ "messaging.operation.name": "receive",
2397
+ "rpc.method": "SendMessage",
2398
+ },
2399
+ CLIENT,
2400
+ "aws_sqs",
2401
+ ),
2402
+ ).toBe("consume");
2403
+ });
2404
+
2405
+ test.each([
2406
+ ["SendMessage", "publish"],
2407
+ ["SendMessageBatch", "publish"],
2408
+ ["Publish", "publish"],
2409
+ ["PublishBatch", "publish"],
2410
+ ["ReceiveMessage", "consume"],
2411
+ ["DeleteMessage", "settle"],
2412
+ ["DeleteMessageBatch", "settle"],
2413
+ ["ChangeMessageVisibility", "settle"],
2414
+ ["ChangeMessageVisibilityBatch", "settle"],
2415
+ ["SQS/SendMessage", "publish"],
2416
+ ["SNS/Publish", "publish"],
2417
+ ["sqs/receivemessage", "consume"],
2418
+ ["GetQueueAttributes", "unknown"],
2419
+ ["ListQueues", "unknown"],
2420
+ ])("AWS rpc.method %s → %s", (method: string, direction: string) => {
2421
+ expect(directionOf({ "rpc.method": method }, CLIENT, "aws_sqs")).toBe(
2422
+ direction,
2423
+ );
2424
+ expect(directionOf({ "rpc.method": method }, CLIENT, "aws.sns")).toBe(
2425
+ direction,
2426
+ );
2427
+ });
2428
+
2429
+ test("rpc.method means nothing outside SQS and SNS", () => {
2430
+ expect(directionOf({ "rpc.method": "SendMessage" }, CLIENT)).toBe(
2431
+ "unknown",
2432
+ );
2433
+ });
2434
+
2435
+ test("an unknown value falls through to the next piece of evidence", () => {
2436
+ expect(
2437
+ directionOf(
2438
+ {
2439
+ "messaging.operation.type": "custom",
2440
+ "messaging.operation": "whatever",
2441
+ "messaging.operation.name": "ack",
2442
+ },
2443
+ CLIENT,
2444
+ ),
2445
+ ).toBe("settle");
2446
+ expect(
2447
+ directionOf({ "messaging.operation.type": "custom" }, CONSUMER),
2448
+ ).toBe("consume");
2449
+ });
2450
+
2451
+ test("a CLIENT or INTERNAL span with no evidence is unknown", () => {
2452
+ expect(directionOf({}, CLIENT)).toBe("unknown");
2453
+ expect(directionOf({}, INTERNAL)).toBe("unknown");
2454
+ expect(directionOf({}, null)).toBe("unknown");
2455
+ });
2456
+ });
2457
+
2458
+ describe("consumer group precedence (display only)", () => {
2459
+ function groupOf(
2460
+ attributes: FixtureAttributes,
2461
+ system: string = "kafka",
2462
+ kind: string = CONSUMER,
2463
+ destination: string = "orders",
2464
+ ): string | null | undefined {
2465
+ return resolveSpan(
2466
+ {
2467
+ "messaging.system": system,
2468
+ "messaging.destination.name": destination,
2469
+ ...attributes,
2470
+ },
2471
+ kind,
2472
+ )?.consumerGroup;
2473
+ }
2474
+
2475
+ test("each key in order: 1.27+, Kafka 1.17-1.26, Kafka ≤1.16, RocketMQ, Event Hubs, Service Bus, the Azure Java SDK's metrics", () => {
2476
+ const attributes: FixtureAttributes = {};
2477
+ MESSAGING_CONSUMER_GROUP_ATTRIBUTES.forEach(
2478
+ (key: string, index: number): void => {
2479
+ attributes[key] = `g${index}`;
2480
+ },
2481
+ );
2482
+ const seen: Array<string | null | undefined> = [];
2483
+ for (const key of MESSAGING_CONSUMER_GROUP_ATTRIBUTES) {
2484
+ seen.push(groupOf(attributes));
2485
+ delete attributes[key];
2486
+ }
2487
+ expect(seen).toEqual(["g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7"]);
2488
+ expect(groupOf(attributes)).toBeNull();
2489
+ });
2490
+
2491
+ test("an attribute beats the Service Bus entity path", () => {
2492
+ expect(
2493
+ groupOf(
2494
+ { "messaging.destination.subscription.name": "from-attribute" },
2495
+ "servicebus",
2496
+ CONSUMER,
2497
+ "order-events/Subscriptions/from-path",
2498
+ ),
2499
+ ).toBe("from-attribute");
2500
+ expect(
2501
+ groupOf(
2502
+ {},
2503
+ "servicebus",
2504
+ CONSUMER,
2505
+ "order-events/Subscriptions/from-path",
2506
+ ),
2507
+ ).toBe("from-path");
2508
+ });
2509
+
2510
+ test("a Pub/Sub consumer's destination is its subscription; a publisher has none", () => {
2511
+ expect(groupOf({}, "gcp_pubsub", CONSUMER, "billing-sub")).toBe(
2512
+ "billing-sub",
2513
+ );
2514
+ expect(
2515
+ groupOf(
2516
+ { "messaging.operation.name": "modack" },
2517
+ "gcp_pubsub",
2518
+ CLIENT,
2519
+ "billing-sub",
2520
+ ),
2521
+ ).toBe("billing-sub");
2522
+ expect(groupOf({}, "gcp_pubsub", PRODUCER, "orders")).toBeNull();
2523
+ expect(groupOf({}, "gcp_pubsub", CLIENT, "orders")).toBeNull();
2524
+ });
2525
+
2526
+ test("the per-instance consumer id is never read", () => {
2527
+ expect(
2528
+ groupOf({
2529
+ "messaging.consumer_id": "billing - consumer-1",
2530
+ "messaging.consumer.id": "billing - consumer-1",
2531
+ }),
2532
+ ).toBeNull();
2533
+ });
2534
+
2535
+ test("a consumer group is trimmed and clamped to 255 characters", () => {
2536
+ expect(groupOf({ "messaging.consumer.group.name": " billing " })).toBe(
2537
+ "billing",
2538
+ );
2539
+ expect(
2540
+ groupOf({ "messaging.consumer.group.name": "g".repeat(300) })?.length,
2541
+ ).toBe(255);
2542
+ });
2543
+ });
2544
+
2545
+ describe("hasMessagingTrigger", () => {
2546
+ const AZURE_PROVIDER_KEYS: ReadonlyArray<string> = [
2547
+ "az.namespace",
2548
+ "azure.resource_provider.namespace",
2549
+ ];
2550
+
2551
+ test.each(
2552
+ MESSAGING_TRIGGER_ATTRIBUTES.map((key: string): [string, string] => {
2553
+ return [
2554
+ key,
2555
+ AZURE_PROVIDER_KEYS.includes(key) ? "Microsoft.ServiceBus" : "x",
2556
+ ];
2557
+ }),
2558
+ )("%s alone (%j) makes a candidate", (key: string, value: string) => {
2559
+ expect(hasMessagingTrigger({ [key]: value })).toBe(true);
2560
+ });
2561
+
2562
+ test.each(
2563
+ AZURE_PROVIDER_KEYS.flatMap(
2564
+ (key: string): Array<[string, unknown, boolean]> => {
2565
+ return [
2566
+ [key, "Microsoft.ServiceBus", true],
2567
+ [key, "Microsoft.EventHub", true],
2568
+ [key, " microsoft.eventhub ", true],
2569
+ [key, "MICROSOFT.SERVICEBUS", true],
2570
+ [
2571
+ key,
2572
+ `${" ".repeat(200)}Microsoft.ServiceBus${"\t".repeat(50)}`,
2573
+ true,
2574
+ ],
2575
+ // Every other Azure SDK client stamps its own provider: no candidate.
2576
+ [key, "Microsoft.Storage", false],
2577
+ [key, "Microsoft.DocumentDB", false],
2578
+ [key, "Microsoft.KeyVault", false],
2579
+ [key, "Microsoft.EventGrid", false],
2580
+ [key, "Microsoft.ServiceBus.Extra", false],
2581
+ [key, "x", false],
2582
+ [key, "", false],
2583
+ [key, 42, false],
2584
+ [key, true, false],
2585
+ [key, ["Microsoft.ServiceBus"], false],
2586
+ [key, "m".repeat(100_000), false],
2587
+ ];
2588
+ },
2589
+ ),
2590
+ )(
2591
+ "the Azure provider key %s with %j is a trigger: %s",
2592
+ (key: string, value: unknown, expected: boolean) => {
2593
+ expect(hasMessagingTrigger({ [key]: value })).toBe(expected);
2594
+ },
2595
+ );
2596
+
2597
+ test("AZURE_MESSAGING_PROVIDER_NAMESPACES: Service Bus's and Event Hubs' namespaces, lowercase and sorted, each resolving to its system", () => {
2598
+ expect(AZURE_MESSAGING_PROVIDER_NAMESPACES).toEqual([
2599
+ "microsoft.eventhub",
2600
+ "microsoft.servicebus",
2601
+ ]);
2602
+ const systems: Record<string, string> = {
2603
+ "microsoft.eventhub": "eventhubs",
2604
+ "microsoft.servicebus": "servicebus",
2605
+ };
2606
+
2607
+ for (const namespace of AZURE_MESSAGING_PROVIDER_NAMESPACES) {
2608
+ expect(namespace).toBe(namespace.trim().toLowerCase());
2609
+ for (const key of AZURE_PROVIDER_KEYS) {
2610
+ for (const spelling of [
2611
+ namespace,
2612
+ namespace.toUpperCase(),
2613
+ ` ${namespace}\t`,
2614
+ ]) {
2615
+ expect(hasMessagingTrigger({ [key]: spelling })).toBe(true);
2616
+ expect(
2617
+ resolveSpan(
2618
+ { [key]: spelling, "messaging.destination.name": "orders" },
2619
+ PRODUCER,
2620
+ )?.system,
2621
+ ).toBe(systems[namespace]);
2622
+ }
2623
+ }
2624
+ }
2625
+ });
2626
+
2627
+ test("an Azure provider value is a trigger exactly when its trimmed, lowercased text is in AZURE_MESSAGING_PROVIDER_NAMESPACES", () => {
2628
+ const padding: string = `${String.fromCharCode(0xa0)}${String.fromCharCode(
2629
+ 0xfeff,
2630
+ )}${String.fromCharCode(0x3000)}`;
2631
+ const names: Array<string> = [
2632
+ // Every name the messaging catalog knows, aliases included.
2633
+ ...MESSAGING_SYSTEMS.flatMap(
2634
+ (descriptor: MessagingSystemDescriptor): Array<string> => {
2635
+ return [descriptor.system, ...descriptor.aliases];
2636
+ },
2637
+ ),
2638
+ "Microsoft.ServiceBus",
2639
+ "Microsoft.EventHub",
2640
+ // Other Azure SDK clients, and near misses.
2641
+ "Microsoft.Storage",
2642
+ "Microsoft.KeyVault",
2643
+ "Microsoft.DocumentDB",
2644
+ "Microsoft.EventGrid",
2645
+ "Microsoft.EventHubs",
2646
+ "Microsoft.ServiceBus/namespaces",
2647
+ "Microsoft.Service Bus",
2648
+ "xMicrosoft.EventHub",
2649
+ "Microsoft",
2650
+ "",
2651
+ ];
2652
+ const probes: Array<string> = names.flatMap(
2653
+ (name: string): Array<string> => {
2654
+ return [name, name.toUpperCase(), `${padding}${name}${padding}`];
2655
+ },
2656
+ );
2657
+
2658
+ let admitted: number = 0;
2659
+ for (const key of AZURE_PROVIDER_KEYS) {
2660
+ for (const probe of probes) {
2661
+ const listed: boolean = AZURE_MESSAGING_PROVIDER_NAMESPACES.includes(
2662
+ probe.trim().toLowerCase(),
2663
+ );
2664
+ admitted += listed ? 1 : 0;
2665
+ expect({
2666
+ key,
2667
+ probe,
2668
+ trigger: hasMessagingTrigger({ [key]: probe }),
2669
+ }).toEqual({ key, probe, trigger: listed });
2670
+ }
2671
+ }
2672
+ // The probe reaches both namespaces through both keys: not vacuous.
2673
+ expect(admitted).toBeGreaterThanOrEqual(2 * AZURE_PROVIDER_KEYS.length * 3);
2674
+ });
2675
+
2676
+ test("an Azure SDK span of a non-messaging client never resolves, and the gate refuses it", () => {
2677
+ for (const provider of [
2678
+ "Microsoft.Storage",
2679
+ "Microsoft.DocumentDB",
2680
+ "Microsoft.KeyVault",
2681
+ ]) {
2682
+ const attributes: Record<string, unknown> = {
2683
+ "az.namespace": provider,
2684
+ "azure.resource_provider.namespace": provider,
2685
+ "server.address": "account.blob.core.windows.net",
2686
+ "http.request.method": "PUT",
2687
+ };
2688
+ expect({ provider, trigger: hasMessagingTrigger(attributes) }).toEqual({
2689
+ provider,
2690
+ trigger: false,
2691
+ });
2692
+ expect({
2693
+ provider,
2694
+ resolved: resolveSpan(attributes, "SPAN_KIND_CLIENT"),
2695
+ }).toEqual({ provider, resolved: null });
2696
+ }
2697
+ });
2698
+
2699
+ test("no trigger key, no candidate", () => {
2700
+ expect(
2701
+ hasMessagingTrigger({
2702
+ "http.request.method": "GET",
2703
+ "server.address": "api.example.com",
2704
+ "messaging.operation": "publish",
2705
+ "messaging.destination.template": "orders.{id}",
2706
+ "rpc.system": "aws-api",
2707
+ "resource.messaging.system": "kafka",
2708
+ }),
2709
+ ).toBe(false);
2710
+ expect(hasMessagingTrigger({})).toBe(false);
2711
+ });
2712
+
2713
+ test.each([
2714
+ ["", false],
2715
+ [" ", false],
2716
+ ["\t\n", false],
2717
+ ["kafka", true],
2718
+ [" kafka ", true],
2719
+ [0, true],
2720
+ [42, true],
2721
+ [Number.NaN, false],
2722
+ [Number.POSITIVE_INFINITY, false],
2723
+ [true, true],
2724
+ [false, true],
2725
+ [[], true],
2726
+ [["kafka"], true],
2727
+ [null, false],
2728
+ [undefined, false],
2729
+ [{ nested: "kafka" }, false],
2730
+ ])(
2731
+ "a messaging.system value of %j is a trigger: %s",
2732
+ (value: unknown, expected: boolean) => {
2733
+ expect(hasMessagingTrigger({ "messaging.system": value })).toBe(expected);
2734
+ },
2735
+ );
2736
+
2737
+ test.each([[null], [undefined], ["messaging.system"], [42], [true]])(
2738
+ "a non-object (%j) is never a candidate",
2739
+ (value: unknown) => {
2740
+ expect(hasMessagingTrigger(value as Record<string, unknown> | null)).toBe(
2741
+ false,
2742
+ );
2743
+ },
2744
+ );
2745
+
2746
+ test("only own keys count", () => {
2747
+ const inherited: Record<string, unknown> = Object.create({
2748
+ "messaging.system": "kafka",
2749
+ }) as Record<string, unknown>;
2750
+ expect(hasMessagingTrigger(inherited)).toBe(false);
2751
+ });
2752
+
2753
+ test("it agrees with the resolver's own gate on every fixture", () => {
2754
+ for (const fixture of SPAN_FIXTURES) {
2755
+ if (!hasMessagingTrigger(fixture.attributes)) {
2756
+ expect({
2757
+ name: fixture.name,
2758
+ resolved: resolveSpan(fixture.attributes, fixture.kind),
2759
+ }).toEqual({ name: fixture.name, resolved: null });
2760
+ }
2761
+ }
2762
+ });
2763
+
2764
+ test("it never enumerates the map, touches only trigger keys, and stops at the first hit", () => {
2765
+ const ownKeysCalls: Array<string> = [];
2766
+ const touched: Array<string> = [];
2767
+ const target: Record<string, unknown> = {
2768
+ "messaging.destination.name": "orders",
2769
+ "messaging.system": "kafka",
2770
+ "http.request.method": "GET",
2771
+ };
2772
+ const proxy: Record<string, unknown> = new Proxy(target, {
2773
+ ownKeys: (object: Record<string, unknown>): Array<string | symbol> => {
2774
+ ownKeysCalls.push("ownKeys");
2775
+ return Reflect.ownKeys(object);
2776
+ },
2777
+ get: (object: Record<string, unknown>, key: string | symbol): unknown => {
2778
+ touched.push(String(key));
2779
+ return Reflect.get(object, key);
2780
+ },
2781
+ getOwnPropertyDescriptor: (
2782
+ object: Record<string, unknown>,
2783
+ key: string | symbol,
2784
+ ): PropertyDescriptor | undefined => {
2785
+ touched.push(String(key));
2786
+ return Reflect.getOwnPropertyDescriptor(object, key);
2787
+ },
2788
+ has: (object: Record<string, unknown>, key: string | symbol): boolean => {
2789
+ touched.push(String(key));
2790
+ return Reflect.has(object, key);
2791
+ },
2792
+ });
2793
+
2794
+ expect(hasMessagingTrigger(proxy)).toBe(true);
2795
+ expect(ownKeysCalls).toEqual([]);
2796
+ for (const key of touched) {
2797
+ expect(MESSAGING_TRIGGER_ATTRIBUTES).toContain(key);
2798
+ }
2799
+ // messaging.system is checked first, and it is a hit.
2800
+ expect(new Set(touched)).toEqual(new Set<string>(["messaging.system"]));
2801
+ });
2802
+
2803
+ test("a row with thousands of unrelated keys is still a handful of lookups", () => {
2804
+ const row: Record<string, unknown> = {};
2805
+ for (let index: number = 0; index < 5000; index++) {
2806
+ row[`attribute.${index}`] = `value-${index}`;
2807
+ }
2808
+ let lookups: number = 0;
2809
+ const proxy: Record<string, unknown> = new Proxy(row, {
2810
+ getOwnPropertyDescriptor: (
2811
+ object: Record<string, unknown>,
2812
+ key: string | symbol,
2813
+ ): PropertyDescriptor | undefined => {
2814
+ lookups++;
2815
+ return Reflect.getOwnPropertyDescriptor(object, key);
2816
+ },
2817
+ });
2818
+ expect(hasMessagingTrigger(proxy)).toBe(false);
2819
+ expect(lookups).toBe(MESSAGING_TRIGGER_ATTRIBUTES.length);
2820
+ });
2821
+ });
2822
+
2823
+ describe("the resolver reads cheaply and never throws", () => {
2824
+ test("a span with no trigger is rejected after reading only the trigger keys", () => {
2825
+ const reads: Set<string> = new Set<string>();
2826
+ expect(
2827
+ resolveRecordingReads(
2828
+ {
2829
+ "http.request.method": "GET",
2830
+ "server.address": "api.example.com",
2831
+ "rpc.system": "aws-api",
2832
+ "rpc.service": "SQS",
2833
+ },
2834
+ CLIENT,
2835
+ reads,
2836
+ ),
2837
+ ).toBeNull();
2838
+ expect([...reads]).toEqual([...MESSAGING_TRIGGER_ATTRIBUTES]);
2839
+ });
2840
+
2841
+ test("a SERVER span is rejected before any attribute is read", () => {
2842
+ const reads: Set<string> = new Set<string>();
2843
+ expect(
2844
+ resolveRecordingReads(
2845
+ { "messaging.system": "kafka", "messaging.destination.name": "x" },
2846
+ SERVER,
2847
+ reads,
2848
+ ),
2849
+ ).toBeNull();
2850
+ expect(reads.size).toBe(0);
2851
+ });
2852
+
2853
+ test("bad input is null, never an exception", () => {
2854
+ expect(
2855
+ resolveMessagingSpan(
2856
+ null as unknown as { getAttribute: (key: string) => unknown },
2857
+ ),
2858
+ ).toBeNull();
2859
+ expect(
2860
+ resolveMessagingSpan({
2861
+ getAttribute: "nope" as unknown as (key: string) => unknown,
2862
+ }),
2863
+ ).toBeNull();
2864
+ });
2865
+
2866
+ test("any mix of attribute values resolves or is null, without throwing", () => {
2867
+ // A deterministic pseudo-random walk over every input key.
2868
+ let seed: number = 20260930;
2869
+ const next: () => number = (): number => {
2870
+ seed = (seed * 1103515245 + 12345) % 2147483648;
2871
+ return seed / 2147483648;
2872
+ };
2873
+ const values: Array<unknown> = [
2874
+ "",
2875
+ " ",
2876
+ "kafka",
2877
+ "servicebus",
2878
+ "aws_sqs",
2879
+ "rabbitmq",
2880
+ "<default>",
2881
+ "amq.default",
2882
+ "orders",
2883
+ "a:b:c",
2884
+ ",x",
2885
+ "[",
2886
+ "[]",
2887
+ '["a"]',
2888
+ "https://",
2889
+ "https://x/1/q",
2890
+ "arn:",
2891
+ "arn:aws:sns:::",
2892
+ "sb://",
2893
+ "@",
2894
+ ":",
2895
+ "::",
2896
+ "[::",
2897
+ "true",
2898
+ "Microsoft.ServiceBus",
2899
+ "aws-api",
2900
+ "SQS",
2901
+ 0,
2902
+ -1,
2903
+ Number.NaN,
2904
+ true,
2905
+ false,
2906
+ null,
2907
+ undefined,
2908
+ [],
2909
+ ["a", 1],
2910
+ {},
2911
+ Symbol("s"),
2912
+ (): string => {
2913
+ return "fn";
2914
+ },
2915
+ ];
2916
+ const kinds: Array<string | null> = [
2917
+ PRODUCER,
2918
+ CONSUMER,
2919
+ CLIENT,
2920
+ INTERNAL,
2921
+ null,
2922
+ ];
2923
+ for (let round: number = 0; round < 2000; round++) {
2924
+ const attributes: FixtureAttributes = {};
2925
+ for (const key of MESSAGING_RESOLVER_INPUT_ATTRIBUTES) {
2926
+ if (next() < 0.3) {
2927
+ attributes[key] = values[Math.floor(next() * values.length)];
2928
+ }
2929
+ }
2930
+ const kind: string | null =
2931
+ kinds[Math.floor(next() * kinds.length)] ?? null;
2932
+ let threw: boolean = false;
2933
+ let resolved: ResolvedMessagingDestination | null = null;
2934
+ try {
2935
+ resolved = resolveSpan(attributes, kind);
2936
+ } catch {
2937
+ threw = true;
2938
+ }
2939
+ expect({ round, threw }).toEqual({ round, threw: false });
2940
+ if (resolved !== null) {
2941
+ const destination: ResolvedMessagingDestination = resolved;
2942
+ expect(destination.destination.length).toBeGreaterThan(0);
2943
+ expect(destination.destination.length).toBeLessThanOrEqual(
2944
+ MESSAGE_QUEUE_DESTINATION_MAX_LENGTH,
2945
+ );
2946
+ expect(destination.destination).toBe(destination.destination.trim());
2947
+ expect(["publish", "consume", "settle", "unknown"]).toContain(
2948
+ destination.direction,
2949
+ );
2950
+ }
2951
+ }
2952
+ });
2953
+ });
2954
+
2955
+ describe("MESSAGING_ADDRESS_ATTRIBUTES shape", () => {
2956
+ test("every port key pairs with its own address generation", () => {
2957
+ for (const attribute of MESSAGING_ADDRESS_ATTRIBUTES) {
2958
+ const pair: MessagingAddressAttribute = attribute;
2959
+ if (pair.port) {
2960
+ expect(pair.port.split(".").slice(0, -1).join(".")).toBe(
2961
+ pair.address.split(".").slice(0, -1).join("."),
2962
+ );
2963
+ }
2964
+ }
2965
+ });
2966
+ });