@oneuptime/common 12.0.5 → 12.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/Models/DatabaseModels/AIRun.ts +33 -0
  2. package/Models/DatabaseModels/DataSource.ts +744 -0
  3. package/Models/DatabaseModels/Index.ts +3 -0
  4. package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
  5. package/Models/DatabaseModels/User.ts +2 -0
  6. package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
  8. package/Models/DatabaseModels/UserSession.ts +2 -0
  9. package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
  10. package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
  11. package/Server/API/AIInsightAPI.ts +47 -4
  12. package/Server/API/AIInvestigationAPI.ts +25 -0
  13. package/Server/API/DashboardAPI.ts +176 -492
  14. package/Server/API/DataSourceAPI.ts +268 -0
  15. package/Server/API/StatusPageAPI.ts +11 -1
  16. package/Server/API/UserAPI.ts +119 -0
  17. package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  22. package/Server/Infrastructure/Queue.ts +26 -0
  23. package/Server/Infrastructure/QueueWorker.ts +17 -0
  24. package/Server/Middleware/TelemetryIngest.ts +8 -3
  25. package/Server/Services/AIInsightService.ts +132 -12
  26. package/Server/Services/AIRunService.ts +34 -0
  27. package/Server/Services/AIService.ts +17 -2
  28. package/Server/Services/AlertService.ts +38 -32
  29. package/Server/Services/AnalyticsDatabaseService.ts +8 -4
  30. package/Server/Services/DataSourceService.ts +200 -0
  31. package/Server/Services/HostService.ts +92 -0
  32. package/Server/Services/IncidentService.ts +45 -0
  33. package/Server/Services/Index.ts +2 -0
  34. package/Server/Services/MonitorService.ts +49 -0
  35. package/Server/Services/MonitorStatusService.ts +166 -0
  36. package/Server/Services/OpenTelemetryIngestService.ts +255 -184
  37. package/Server/Services/RumSessionPinService.ts +7 -21
  38. package/Server/Services/StatusPageSubscriberService.ts +13 -0
  39. package/Server/Services/UserService.ts +280 -9
  40. package/Server/Static/Vendor/README.md +62 -0
  41. package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
  42. package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
  43. package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
  44. package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
  45. package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
  46. package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
  47. package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
  48. package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
  49. package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
  50. package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
  51. package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
  52. package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
  53. package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
  54. package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
  55. package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
  56. package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
  57. package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
  58. package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
  59. package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
  60. package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
  61. package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
  62. package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
  63. package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
  64. package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
  65. package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
  66. package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
  67. package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
  68. package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
  69. package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
  70. package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
  71. package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
  72. package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
  73. package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
  74. package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
  75. package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
  76. package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
  77. package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
  78. package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
  79. package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
  80. package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
  81. package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
  82. package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
  83. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
  84. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
  85. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
  86. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
  87. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
  88. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
  89. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
  90. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
  91. package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
  92. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
  93. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
  94. package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
  95. package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
  96. package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
  97. package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
  98. package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
  99. package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
  100. package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
  101. package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
  102. package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
  103. package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
  104. package/Server/Utils/DataSource/EgressGuard.ts +446 -0
  105. package/Server/Utils/DataSource/HttpFetch.ts +221 -0
  106. package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
  107. package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
  108. package/Server/Utils/DataSource/Types.ts +93 -0
  109. package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
  110. package/Server/Utils/InProcessMemo.ts +141 -0
  111. package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
  112. package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
  113. package/Server/Utils/Monitor/MonitorResource.ts +14 -0
  114. package/Server/Utils/Response.ts +12 -1
  115. package/Server/Utils/SSRFProtection.ts +237 -0
  116. package/Server/Utils/StartServer.ts +8 -0
  117. package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
  118. package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
  119. package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
  120. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
  121. package/Server/Utils/Telemetry/Telemetry.ts +112 -1
  122. package/Server/Utils/Telemetry.ts +104 -3
  123. package/Server/Utils/VendorAssets.ts +165 -0
  124. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
  125. package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
  126. package/Server/Views/Partials/Head.ejs +21 -7
  127. package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
  128. package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
  129. package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
  130. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
  131. package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
  132. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
  133. package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
  134. package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
  135. package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
  136. package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
  137. package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
  138. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
  139. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
  140. package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
  141. package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
  142. package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
  143. package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
  144. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
  145. package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
  146. package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
  147. package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
  148. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
  149. package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
  150. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
  151. package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
  152. package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
  153. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
  154. package/Tests/Server/Services/AIInsightService.test.ts +266 -11
  155. package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
  156. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
  157. package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
  158. package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
  159. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
  160. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
  161. package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
  162. package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
  163. package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
  164. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
  165. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
  166. package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
  167. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
  168. package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
  169. package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
  170. package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
  171. package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
  172. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
  173. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
  174. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
  175. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
  176. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
  177. package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
  178. package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
  179. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
  180. package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
  181. package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
  182. package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
  183. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
  184. package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
  185. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
  186. package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
  187. package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
  188. package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
  189. package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
  190. package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
  191. package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
  192. package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
  193. package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
  194. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
  195. package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
  196. package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
  197. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
  198. package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
  199. package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
  200. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
  201. package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
  202. package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
  203. package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
  205. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
  206. package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
  207. package/Tests/Server/Utils/Telemetry.test.ts +87 -0
  208. package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
  209. package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
  210. package/Tests/Server/Views/OfflineRender.test.ts +297 -0
  211. package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
  212. package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
  213. package/Tests/Types/Password.test.ts +363 -0
  214. package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
  215. package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
  216. package/Tests/UI/Components/Modal.test.tsx +281 -1
  217. package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
  218. package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
  219. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
  220. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  221. package/Tests/Utils/Analytics.test.ts +23 -0
  222. package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
  223. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
  224. package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
  225. package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
  226. package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
  227. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
  228. package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
  229. package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
  230. package/Types/AI/AIInsightEvidence.ts +6 -0
  231. package/Types/Analytics/RevenueEvent.ts +31 -0
  232. package/Types/Dashboard/DashboardComponentType.ts +31 -0
  233. package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
  234. package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
  235. package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
  236. package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
  237. package/Types/DataSource/DataSourceLimits.ts +62 -0
  238. package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
  239. package/Types/DataSource/DataSourceTableResult.ts +38 -0
  240. package/Types/DataSource/DataSourceType.ts +213 -0
  241. package/Types/Date.ts +43 -3
  242. package/Types/Password.ts +64 -0
  243. package/Types/Permission.ts +46 -0
  244. package/Types/UserAuthenticationStatus.ts +34 -0
  245. package/UI/Components/Button/Button.tsx +1 -1
  246. package/UI/Components/ErrorBoundary.tsx +459 -108
  247. package/UI/Components/Icon/Icon.tsx +10 -9
  248. package/UI/Components/Modal/ConfirmModal.tsx +6 -1
  249. package/UI/Components/Modal/Modal.tsx +267 -35
  250. package/UI/Components/Modal/ModalFooter.tsx +6 -1
  251. package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
  252. package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
  253. package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
  254. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
  255. package/UI/Components/StatusPage/ResourceList.tsx +570 -0
  256. package/UI/Styles/Theme.css +18 -0
  257. package/UI/Utils/API/RequestOptions.ts +7 -0
  258. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
  259. package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
  260. package/UI/Utils/TestDataSource.ts +61 -0
  261. package/UI/esbuild-config.js +45 -0
  262. package/Utils/Analytics.ts +15 -0
  263. package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
  264. package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
  265. package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
  266. package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
  267. package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
  268. package/Utils/Dashboard/Components/Index.ts +28 -0
  269. package/Utils/DataSource/DataSourceQueryText.ts +94 -0
  270. package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
  271. package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
  272. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
  273. package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
  274. package/Utils/StatusPage/ResourceExplorer.ts +853 -0
  275. package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
  276. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
  278. package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
  279. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  280. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
  282. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/User.js +2 -0
  284. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
  286. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
  288. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
  290. package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
  292. package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
  294. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  295. package/build/dist/Server/API/AIInsightAPI.js +34 -4
  296. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  297. package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
  298. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  299. package/build/dist/Server/API/DashboardAPI.js +119 -433
  300. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  301. package/build/dist/Server/API/DataSourceAPI.js +173 -0
  302. package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
  303. package/build/dist/Server/API/StatusPageAPI.js +11 -1
  304. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  305. package/build/dist/Server/API/UserAPI.js +94 -2
  306. package/build/dist/Server/API/UserAPI.js.map +1 -1
  307. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
  308. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  309. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
  310. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
  311. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
  312. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
  313. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
  314. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
  315. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  316. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  317. package/build/dist/Server/Infrastructure/Queue.js +27 -0
  318. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  319. package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
  320. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  321. package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
  322. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  323. package/build/dist/Server/Services/AIInsightService.js +110 -10
  324. package/build/dist/Server/Services/AIInsightService.js.map +1 -1
  325. package/build/dist/Server/Services/AIRunService.js +34 -0
  326. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  327. package/build/dist/Server/Services/AIService.js +16 -2
  328. package/build/dist/Server/Services/AIService.js.map +1 -1
  329. package/build/dist/Server/Services/AlertService.js +28 -35
  330. package/build/dist/Server/Services/AlertService.js.map +1 -1
  331. package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
  332. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  333. package/build/dist/Server/Services/DataSourceService.js +159 -0
  334. package/build/dist/Server/Services/DataSourceService.js.map +1 -0
  335. package/build/dist/Server/Services/HostService.js +74 -0
  336. package/build/dist/Server/Services/HostService.js.map +1 -1
  337. package/build/dist/Server/Services/IncidentService.js +35 -24
  338. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  339. package/build/dist/Server/Services/Index.js +2 -0
  340. package/build/dist/Server/Services/Index.js.map +1 -1
  341. package/build/dist/Server/Services/MonitorService.js +45 -0
  342. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  343. package/build/dist/Server/Services/MonitorStatusService.js +129 -0
  344. package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
  345. package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
  346. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  347. package/build/dist/Server/Services/RumSessionPinService.js +7 -15
  348. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  349. package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
  350. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  351. package/build/dist/Server/Services/UserService.js +241 -10
  352. package/build/dist/Server/Services/UserService.js.map +1 -1
  353. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
  354. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
  356. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
  358. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
  360. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  361. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
  362. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  363. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
  364. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  365. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
  366. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
  368. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  369. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
  372. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  373. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
  374. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  375. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
  376. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
  377. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
  378. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
  379. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
  380. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
  381. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
  382. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
  383. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
  384. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
  385. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
  386. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
  387. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
  388. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
  389. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
  390. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
  391. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
  392. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
  393. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
  394. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
  395. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
  396. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
  397. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
  398. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
  399. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
  400. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
  401. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
  402. package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
  403. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
  404. package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
  405. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
  406. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
  407. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
  408. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
  409. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
  410. package/build/dist/Server/Utils/DataSource/Types.js +2 -0
  411. package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
  412. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
  413. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
  414. package/build/dist/Server/Utils/InProcessMemo.js +113 -0
  415. package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
  416. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
  417. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
  418. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
  419. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  420. package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
  421. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  422. package/build/dist/Server/Utils/Response.js +9 -1
  423. package/build/dist/Server/Utils/Response.js.map +1 -1
  424. package/build/dist/Server/Utils/SSRFProtection.js +192 -0
  425. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
  426. package/build/dist/Server/Utils/StartServer.js +7 -0
  427. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  428. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
  429. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
  430. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
  431. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  432. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
  433. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
  434. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
  435. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
  436. package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
  437. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  438. package/build/dist/Server/Utils/Telemetry.js +93 -6
  439. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  440. package/build/dist/Server/Utils/VendorAssets.js +121 -0
  441. package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
  442. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
  443. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  444. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
  445. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  446. package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
  447. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
  448. package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
  449. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  450. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
  451. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
  452. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
  453. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
  454. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
  455. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
  456. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
  457. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
  458. package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
  459. package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
  460. package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
  461. package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
  462. package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
  463. package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
  464. package/build/dist/Types/DataSource/DataSourceType.js +183 -0
  465. package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
  466. package/build/dist/Types/Date.js +38 -3
  467. package/build/dist/Types/Date.js.map +1 -1
  468. package/build/dist/Types/Password.js +56 -0
  469. package/build/dist/Types/Password.js.map +1 -0
  470. package/build/dist/Types/Permission.js +42 -0
  471. package/build/dist/Types/Permission.js.map +1 -1
  472. package/build/dist/Types/UserAuthenticationStatus.js +2 -0
  473. package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
  474. package/build/dist/UI/Components/ErrorBoundary.js +388 -72
  475. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  476. package/build/dist/UI/Components/Icon/Icon.js +10 -4
  477. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  478. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  479. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  480. package/build/dist/UI/Components/Modal/Modal.js +178 -19
  481. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  482. package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
  483. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  484. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
  485. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  486. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
  487. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  488. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
  489. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
  490. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
  491. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
  492. package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
  493. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
  494. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
  495. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  496. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
  497. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  498. package/build/dist/UI/Utils/TestDataSource.js +39 -0
  499. package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
  500. package/build/dist/Utils/Analytics.js +4 -0
  501. package/build/dist/Utils/Analytics.js.map +1 -1
  502. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
  503. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
  504. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
  505. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
  506. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
  507. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
  508. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
  509. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
  510. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
  511. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
  512. package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
  513. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  514. package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
  515. package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
  516. package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
  517. package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
  518. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
  519. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
  520. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
  521. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
  522. package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
  523. package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
  524. package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
  525. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
  526. package/jest.config.json +1 -0
  527. package/package.json +5 -1
  528. package/tsconfig.json +12 -10
@@ -1,6 +1,7 @@
1
1
  import InsightStore, {
2
2
  DISMISSED_COOLDOWN_DAYS,
3
3
  MAX_NEW_INSIGHTS_PER_PROJECT_PER_SCAN,
4
+ RESOLVED_COOLDOWN_HOURS,
4
5
  UpsertCandidatesResult,
5
6
  } from "../../../../../Server/Utils/AI/SRE/Insights/InsightStore";
6
7
  import { InsightCandidate } from "../../../../../Server/Utils/AI/SRE/Insights/Types";
@@ -46,12 +47,14 @@ function fakeExisting(overrides?: {
46
47
  status?: AIInsightStatus;
47
48
  occurrenceCount?: number;
48
49
  humanVerdictAt?: Date | undefined;
50
+ lastSeenAt?: Date | undefined;
49
51
  }): AIInsight {
50
52
  return {
51
53
  id: ObjectID.generate(),
52
54
  status: overrides?.status ?? AIInsightStatus.Detected,
53
55
  occurrenceCount: overrides?.occurrenceCount ?? 2,
54
56
  humanVerdictAt: overrides?.humanVerdictAt,
57
+ lastSeenAt: overrides?.lastSeenAt,
55
58
  } as unknown as AIInsight;
56
59
  }
57
60
 
@@ -166,7 +169,7 @@ describe("InsightStore.upsertCandidates — dedupe matrix", () => {
166
169
  [AIInsightStatus.ActionRequired],
167
170
  [AIInsightStatus.FixOpened],
168
171
  ])(
169
- "existing %s insight → REFRESH: lastSeenAt, occurrenceCount+1, evidence/detail/severity updated — and status is NEVER touched",
172
+ "existing %s insight → REFRESH: lastSeenAt, occurrenceCount+1, title/evidence/detail/severity updated — and status is NEVER touched",
170
173
  async (status: AIInsightStatus) => {
171
174
  const existing: AIInsight = fakeExisting({
172
175
  status,
@@ -179,7 +182,17 @@ describe("InsightStore.upsertCandidates — dedupe matrix", () => {
179
182
  const result: UpsertCandidatesResult =
180
183
  await InsightStore.upsertCandidates({
181
184
  projectId,
182
- candidates: [makeCandidate({ severity: AIInsightSeverity.High })],
185
+ candidates: [
186
+ makeCandidate({
187
+ severity: AIInsightSeverity.High,
188
+ /*
189
+ * Detectors put live numbers in the title (a spike multiplier,
190
+ * a latency factor). A title frozen at creation would show
191
+ * "5.2x" next to a High severity and a 12x detail body.
192
+ */
193
+ title: "New exception: NullPointerException in checkout (12.0x)",
194
+ }),
195
+ ],
183
196
  now,
184
197
  });
185
198
 
@@ -195,6 +208,7 @@ describe("InsightStore.upsertCandidates — dedupe matrix", () => {
195
208
  lastSeenAt: now,
196
209
  occurrenceCount: 5,
197
210
  severity: AIInsightSeverity.High,
211
+ title: "New exception: NullPointerException in checkout (12.0x)",
198
212
  detailMarkdown: "**3 occurrences** in the last 24 hours.",
199
213
  evidence: { exception: { recentOccurrenceCount: 3 } },
200
214
  }),
@@ -317,8 +331,65 @@ describe("InsightStore.upsertCandidates — dedupe matrix", () => {
317
331
  expect(result.suppressed).toBe(1);
318
332
  });
319
333
 
320
- test("existing Resolved insight → CREATE new: a resolved issue that reappears is a regression", async () => {
321
- mockFindOneBy(fakeExisting({ status: AIInsightStatus.Resolved }));
334
+ /*
335
+ * Resolve has to stick. Detectors emit on a standing condition, not on an
336
+ * event — NewException re-emits an unchanged candidate on all ~96 ticks of
337
+ * its 24h window — so without a cooldown a resolved insight was re-filed
338
+ * byte-identical 15 minutes later, with its own triage run, every time.
339
+ */
340
+ test("existing Resolved insight within the cooldown → suppress, not a byte-identical re-file", async () => {
341
+ mockFindOneBy(
342
+ fakeExisting({
343
+ status: AIInsightStatus.Resolved,
344
+ humanVerdictAt: new Date(now.getTime() - 15 * 60 * 1000),
345
+ }),
346
+ );
347
+ const create: jest.SpyInstance = mockCreate();
348
+ const update: jest.SpyInstance = mockUpdateOneById();
349
+
350
+ const result: UpsertCandidatesResult = await InsightStore.upsertCandidates({
351
+ projectId,
352
+ candidates: [makeCandidate()],
353
+ now,
354
+ });
355
+
356
+ expect(create).not.toHaveBeenCalled();
357
+ expect(update).not.toHaveBeenCalled();
358
+ expect(result.suppressed).toBe(1);
359
+ expect(result.created).toHaveLength(0);
360
+ expect(result.refreshed).toBe(0);
361
+ });
362
+
363
+ test("resolve cooldown boundary: exactly RESOLVED_COOLDOWN_HOURS old still suppresses (inclusive)", async () => {
364
+ mockFindOneBy(
365
+ fakeExisting({
366
+ status: AIInsightStatus.Resolved,
367
+ humanVerdictAt: new Date(
368
+ now.getTime() - RESOLVED_COOLDOWN_HOURS * 60 * 60 * 1000,
369
+ ),
370
+ }),
371
+ );
372
+ const create: jest.SpyInstance = mockCreate();
373
+
374
+ const result: UpsertCandidatesResult = await InsightStore.upsertCandidates({
375
+ projectId,
376
+ candidates: [makeCandidate()],
377
+ now,
378
+ });
379
+
380
+ expect(create).not.toHaveBeenCalled();
381
+ expect(result.suppressed).toBe(1);
382
+ });
383
+
384
+ test("resolved past the cooldown → CREATE new: a resolved issue that comes back IS a regression", async () => {
385
+ mockFindOneBy(
386
+ fakeExisting({
387
+ status: AIInsightStatus.Resolved,
388
+ humanVerdictAt: new Date(
389
+ now.getTime() - (RESOLVED_COOLDOWN_HOURS * 60 * 60 * 1000 + 1),
390
+ ),
391
+ }),
392
+ );
322
393
  const create: jest.SpyInstance = mockCreate();
323
394
  const update: jest.SpyInstance = mockUpdateOneById();
324
395
 
@@ -333,6 +404,52 @@ describe("InsightStore.upsertCandidates — dedupe matrix", () => {
333
404
  expect(result.created).toHaveLength(1);
334
405
  expect(result.refreshed).toBe(0);
335
406
  });
407
+
408
+ /*
409
+ * resolveInsight leaves an existing verdict alone, so a Confirm-then-Resolve
410
+ * leaves humanVerdictAt pinned to the CONFIRM. lastSeenAt is the honest
411
+ * fallback: refreshes stop the instant a row goes terminal, so it is the
412
+ * last tick before the human closed it — never later than the close.
413
+ */
414
+ test("no verdict timestamp: the cooldown falls back to lastSeenAt", async () => {
415
+ mockFindOneBy(
416
+ fakeExisting({
417
+ status: AIInsightStatus.Resolved,
418
+ humanVerdictAt: undefined,
419
+ lastSeenAt: new Date(now.getTime() - 60 * 60 * 1000),
420
+ }),
421
+ );
422
+ const create: jest.SpyInstance = mockCreate();
423
+
424
+ const result: UpsertCandidatesResult = await InsightStore.upsertCandidates({
425
+ projectId,
426
+ candidates: [makeCandidate()],
427
+ now,
428
+ });
429
+
430
+ expect(create).not.toHaveBeenCalled();
431
+ expect(result.suppressed).toBe(1);
432
+ });
433
+
434
+ test("an undateable terminal row suppresses — never re-file what we cannot date", async () => {
435
+ mockFindOneBy(
436
+ fakeExisting({
437
+ status: AIInsightStatus.Resolved,
438
+ humanVerdictAt: undefined,
439
+ lastSeenAt: undefined,
440
+ }),
441
+ );
442
+ const create: jest.SpyInstance = mockCreate();
443
+
444
+ const result: UpsertCandidatesResult = await InsightStore.upsertCandidates({
445
+ projectId,
446
+ candidates: [makeCandidate()],
447
+ now,
448
+ });
449
+
450
+ expect(create).not.toHaveBeenCalled();
451
+ expect(result.suppressed).toBe(1);
452
+ });
336
453
  });
337
454
 
338
455
  describe("InsightStore.upsertCandidates — per-scan new-insight cap", () => {
@@ -1,11 +1,13 @@
1
1
  import { afterEach, describe, expect, test } from "@jest/globals";
2
2
  import NewExceptionDetector, {
3
3
  NEW_EXCEPTION_CANDIDATE_LIMIT,
4
+ NEW_EXCEPTION_GROUP_SCAN_LIMIT,
4
5
  NEW_EXCEPTION_HIGH_SEVERITY_OCCURRENCE_COUNT,
5
6
  NEW_EXCEPTION_MIN_OCCURRENCE_COUNT,
6
7
  NEW_EXCEPTION_TITLE_MESSAGE_MAX_LENGTH,
7
8
  NewExceptionDecision,
8
9
  } from "../../../../../Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector";
10
+ import { buildExceptionFailureModeKey } from "../../../../../Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity";
9
11
  import { InsightCandidate } from "../../../../../Server/Utils/AI/SRE/Insights/Types";
10
12
  import TelemetryExceptionService from "../../../../../Server/Services/TelemetryExceptionService";
11
13
  import ServiceService from "../../../../../Server/Services/ServiceService";
@@ -13,15 +15,20 @@ import TelemetryException from "../../../../../Models/DatabaseModels/TelemetryEx
13
15
  import Service from "../../../../../Models/DatabaseModels/Service";
14
16
  import AIInsightSeverity from "../../../../../Types/AI/AIInsightSeverity";
15
17
  import AIInsightType from "../../../../../Types/AI/AIInsightType";
18
+ import SortOrder from "../../../../../Types/BaseDatabase/SortOrder";
16
19
  import ObjectID from "../../../../../Types/ObjectID";
17
20
 
18
21
  /*
19
22
  * Invariant under test: the NewException detector deterministically turns
20
23
  * "an exception group born in the last 24h that is already recurring" into
21
- * exactly one insight candidate per groupqualifying at >= 3 lifetime
22
- * occurrences, escalating to High at >= 50, fingerprinted on the group id
23
- * and reads Postgres as root with an explicit projectId (never user
24
- * ACL). No LLM is ever involved.
24
+ * exactly one insight candidate per FAILURE MODE (entity, exception type,
25
+ * normalized message), NOT per exception group, because group identity at
26
+ * ingest includes the stack trace and one throw site reached from several
27
+ * call paths is several groups. Occurrences are summed across the member
28
+ * groups, the candidate qualifies at >= 3 and escalates to High at >= 50,
29
+ * the fingerprint is the failure-mode key (never the group id, which would
30
+ * file one insight per call path), and Postgres is read as root with an
31
+ * explicit projectId (never user ACL). No LLM is ever involved.
25
32
  */
26
33
 
27
34
  const projectId: ObjectID = ObjectID.generate();
@@ -96,10 +103,43 @@ describe("NewExceptionDetector.evaluateNewException (pure decision matrix)", ()
96
103
  });
97
104
 
98
105
  describe("NewExceptionDetector.buildExceptionLabel (pure)", () => {
99
- test("prefers the exception type when present", () => {
106
+ test("carries the type AND the message — the type alone collides", () => {
100
107
  expect(
101
108
  NewExceptionDetector.buildExceptionLabel("TypeError", "some message"),
102
- ).toBe("TypeError");
109
+ ).toBe("TypeError: some message");
110
+ });
111
+
112
+ /*
113
+ * The reported symptom: instrumentation that types every failure by HTTP
114
+ * status turns a whole service's unrelated exceptions into one string.
115
+ */
116
+ test("a low-signal type is disambiguated by the message", () => {
117
+ expect(
118
+ NewExceptionDetector.buildExceptionLabel(
119
+ "401",
120
+ "Authenticated user or a valid API key is needed to read record of Monitor.",
121
+ ),
122
+ ).not.toBe(
123
+ NewExceptionDetector.buildExceptionLabel(
124
+ "401",
125
+ "Authenticated user or a valid API key is needed to read record of Incident.",
126
+ ),
127
+ );
128
+ });
129
+
130
+ test("does not repeat a type the runtime already prefixed onto the message", () => {
131
+ expect(
132
+ NewExceptionDetector.buildExceptionLabel("Error", "Error: boom"),
133
+ ).toBe("Error: boom");
134
+ expect(
135
+ NewExceptionDetector.buildExceptionLabel("TypeError", "typeerror: boom"),
136
+ ).toBe("typeerror: boom");
137
+ });
138
+
139
+ test("falls back to the type alone when there is no message", () => {
140
+ expect(NewExceptionDetector.buildExceptionLabel("TypeError", "")).toBe(
141
+ "TypeError",
142
+ );
103
143
  });
104
144
 
105
145
  test("falls back to the message when the type is empty/whitespace", () => {
@@ -151,6 +191,94 @@ describe("NewExceptionDetector.buildFingerprint (pure)", () => {
151
191
  });
152
192
  });
153
193
 
194
+ /*
195
+ * The failure-mode key IS the insight's identity. Two properties matter and
196
+ * both are load-bearing: it must ignore the stack trace (so one throw site
197
+ * reached from many call paths is one insight), and it must be stable across
198
+ * ticks and releases (a key that drifts files a duplicate on every drift).
199
+ */
200
+ describe("buildExceptionFailureModeKey (pure identity)", () => {
201
+ test("same entity + type + message → same key, whatever the stack trace", () => {
202
+ const a: string = buildExceptionFailureModeKey({
203
+ primaryEntityId: serviceId,
204
+ exceptionType: "401",
205
+ message: "Unauthorized",
206
+ });
207
+ const b: string = buildExceptionFailureModeKey({
208
+ primaryEntityId: serviceId,
209
+ exceptionType: "401",
210
+ message: "Unauthorized",
211
+ });
212
+ expect(a).toBe(b);
213
+ });
214
+
215
+ test("interpolated ids do not split one failure mode", () => {
216
+ expect(
217
+ buildExceptionFailureModeKey({
218
+ primaryEntityId: serviceId,
219
+ exceptionType: "BadDataException",
220
+ message:
221
+ "invalid input syntax for type uuid: 550e8400-e29b-41d4-a716-446655440000",
222
+ }),
223
+ ).toBe(
224
+ buildExceptionFailureModeKey({
225
+ primaryEntityId: serviceId,
226
+ exceptionType: "BadDataException",
227
+ message:
228
+ "invalid input syntax for type uuid: 6ba7b810-9dad-11d1-80b4-00c04fd430c8",
229
+ }),
230
+ );
231
+ });
232
+
233
+ test("a different message, type or entity is a different failure mode", () => {
234
+ const base: string = buildExceptionFailureModeKey({
235
+ primaryEntityId: serviceId,
236
+ exceptionType: "401",
237
+ message: "read record of Monitor",
238
+ });
239
+
240
+ expect(
241
+ buildExceptionFailureModeKey({
242
+ primaryEntityId: serviceId,
243
+ exceptionType: "401",
244
+ message: "read record of Incident",
245
+ }),
246
+ ).not.toBe(base);
247
+
248
+ expect(
249
+ buildExceptionFailureModeKey({
250
+ primaryEntityId: serviceId,
251
+ exceptionType: "403",
252
+ message: "read record of Monitor",
253
+ }),
254
+ ).not.toBe(base);
255
+
256
+ expect(
257
+ buildExceptionFailureModeKey({
258
+ primaryEntityId: ObjectID.generate(),
259
+ exceptionType: "401",
260
+ message: "read record of Monitor",
261
+ }),
262
+ ).not.toBe(base);
263
+ });
264
+
265
+ test("an unattributed entity still yields a stable key", () => {
266
+ expect(
267
+ buildExceptionFailureModeKey({
268
+ primaryEntityId: undefined,
269
+ exceptionType: "Error",
270
+ message: "boom",
271
+ }),
272
+ ).toBe(
273
+ buildExceptionFailureModeKey({
274
+ primaryEntityId: undefined,
275
+ exceptionType: "Error",
276
+ message: "boom",
277
+ }),
278
+ );
279
+ });
280
+ });
281
+
154
282
  describe("NewExceptionDetector.detect (IO wiring)", () => {
155
283
  afterEach(() => {
156
284
  jest.restoreAllMocks();
@@ -181,18 +309,35 @@ describe("NewExceptionDetector.detect (IO wiring)", () => {
181
309
  firstSeenAt: expect.anything(),
182
310
  occuranceCount: expect.anything(),
183
311
  }),
184
- limit: NEW_EXCEPTION_CANDIDATE_LIMIT,
312
+ limit: NEW_EXCEPTION_GROUP_SCAN_LIMIT,
185
313
  props: expect.objectContaining({ isRoot: true }),
186
314
  }),
187
315
  );
188
316
 
317
+ /*
318
+ * The ORDER BY carries a tie-break. occuranceCount climbs with every
319
+ * ingested event, so the long tail sits densely tied right at the LIMIT
320
+ * and an untie-broken sort hands back a different arbitrary subset every
321
+ * tick.
322
+ */
323
+ expect(findBySpy.mock.calls[0]![0]!.sort).toEqual({
324
+ occuranceCount: SortOrder.Descending,
325
+ firstSeenAt: SortOrder.Ascending,
326
+ });
327
+
189
328
  expect(candidates).toHaveLength(1);
190
329
  const candidate: InsightCandidate = candidates[0]!;
191
330
  expect(candidate.insightType).toBe(AIInsightType.NewException);
192
331
  expect(candidate.fingerprint).toBe(
193
- `new-exception:${exceptionId.toString()}`,
332
+ `new-exception:${buildExceptionFailureModeKey({
333
+ primaryEntityId: serviceId,
334
+ exceptionType: "TypeError",
335
+ message: "Cannot read properties of undefined (reading 'user')",
336
+ })}`,
337
+ );
338
+ expect(candidate.title).toBe(
339
+ "New exception: TypeError: Cannot read properties of undefined (reading 'user') in web-api",
194
340
  );
195
- expect(candidate.title).toBe("New exception: TypeError in web-api");
196
341
  expect(candidate.severity).toBe(AIInsightSeverity.High);
197
342
  expect(candidate.serviceName).toBe("web-api");
198
343
  expect(candidate.telemetryServiceId).toBe(serviceId);
@@ -201,11 +346,215 @@ describe("NewExceptionDetector.detect (IO wiring)", () => {
201
346
  expect.objectContaining({
202
347
  exceptionType: "TypeError",
203
348
  totalOccurrenceCount: 57,
349
+ distinctExceptionGroupCount: 1,
204
350
  firstSeenAt: "2026-07-14T02:00:00.000Z",
205
351
  }),
206
352
  );
207
353
  expect(candidate.detailMarkdown).toContain("Occurrences so far: 57");
208
354
  expect(candidate.detailMarkdown).toContain("web-api");
355
+ // A single group says nothing about stack variants.
356
+ expect(candidate.detailMarkdown).not.toContain("distinct stack traces");
357
+ });
358
+
359
+ /*
360
+ * THE REGRESSION THIS DETECTOR EXISTS TO PREVENT. The ingest fingerprint
361
+ * hashes the normalized stack trace, so one throw site reached from three
362
+ * call paths is three TelemetryException groups. Filing one insight per
363
+ * group is what produced a wall of identical "New exception: 401 in api"
364
+ * rows.
365
+ */
366
+ test("stack-trace variants of one failure mode collapse into ONE candidate", async () => {
367
+ const variantIds: Array<ObjectID> = [
368
+ ObjectID.generate(),
369
+ ObjectID.generate(),
370
+ ObjectID.generate(),
371
+ ];
372
+
373
+ jest.spyOn(TelemetryExceptionService, "findBy").mockResolvedValue([
374
+ fakeException({
375
+ id: variantIds[0]!,
376
+ exceptionType: "401",
377
+ message: "Unauthorized",
378
+ occuranceCount: 12,
379
+ firstSeenAt: new Date("2026-07-14T06:00:00.000Z"),
380
+ }),
381
+ fakeException({
382
+ id: variantIds[1]!,
383
+ exceptionType: "401",
384
+ message: "Unauthorized",
385
+ occuranceCount: 30,
386
+ firstSeenAt: new Date("2026-07-14T04:00:00.000Z"),
387
+ }),
388
+ fakeException({
389
+ id: variantIds[2]!,
390
+ exceptionType: "401",
391
+ message: "Unauthorized",
392
+ occuranceCount: 9,
393
+ firstSeenAt: new Date("2026-07-14T02:00:00.000Z"),
394
+ }),
395
+ ] as unknown as Array<TelemetryException>);
396
+ jest
397
+ .spyOn(ServiceService, "findOneById")
398
+ .mockResolvedValue({ name: "api" } as unknown as Service);
399
+
400
+ const detector: NewExceptionDetector = new NewExceptionDetector();
401
+ const candidates: Array<InsightCandidate> = await detector.detect({
402
+ projectId,
403
+ now,
404
+ });
405
+
406
+ expect(candidates).toHaveLength(1);
407
+ const candidate: InsightCandidate = candidates[0]!;
408
+
409
+ // Counts are summed, so severity reflects the whole failure mode.
410
+ expect(candidate.evidence.exception?.totalOccurrenceCount).toBe(51);
411
+ expect(candidate.evidence.exception?.distinctExceptionGroupCount).toBe(3);
412
+ expect(candidate.severity).toBe(AIInsightSeverity.High);
413
+ // The earliest member dates the finding.
414
+ expect(candidate.evidence.exception?.firstSeenAt).toBe(
415
+ "2026-07-14T02:00:00.000Z",
416
+ );
417
+ // The loudest member is what the insight links to.
418
+ expect(candidate.telemetryExceptionId).toBe(variantIds[1]!);
419
+ expect(candidate.detailMarkdown).toContain(
420
+ "Raised from 3 distinct stack traces",
421
+ );
422
+ });
423
+
424
+ test("the fingerprint does not move when the loudest member changes", async () => {
425
+ const quiet: ObjectID = ObjectID.generate();
426
+ const loud: ObjectID = ObjectID.generate();
427
+
428
+ jest
429
+ .spyOn(ServiceService, "findOneById")
430
+ .mockResolvedValue({ name: "api" } as unknown as Service);
431
+
432
+ type FingerprintForOrderFunction = (
433
+ order: Array<[ObjectID, number]>,
434
+ ) => Promise<string>;
435
+
436
+ const fingerprintForOrder: FingerprintForOrderFunction = async (
437
+ order: Array<[ObjectID, number]>,
438
+ ): Promise<string> => {
439
+ jest.spyOn(TelemetryExceptionService, "findBy").mockResolvedValue(
440
+ order.map(([id, count]: [ObjectID, number]) => {
441
+ return fakeException({
442
+ id: id,
443
+ exceptionType: "401",
444
+ message: "Unauthorized",
445
+ occuranceCount: count,
446
+ });
447
+ }) as unknown as Array<TelemetryException>,
448
+ );
449
+
450
+ const detector: NewExceptionDetector = new NewExceptionDetector();
451
+ const candidates: Array<InsightCandidate> = await detector.detect({
452
+ projectId,
453
+ now,
454
+ });
455
+ return candidates[0]!.fingerprint;
456
+ };
457
+
458
+ const before: string = await fingerprintForOrder([
459
+ [quiet, 40],
460
+ [loud, 5],
461
+ ]);
462
+ const after: string = await fingerprintForOrder([
463
+ [loud, 900],
464
+ [quiet, 40],
465
+ ]);
466
+
467
+ expect(before).toBe(after);
468
+ });
469
+
470
+ test("distinct failure modes stay distinct, and the loudest is first", async () => {
471
+ jest.spyOn(TelemetryExceptionService, "findBy").mockResolvedValue([
472
+ fakeException({
473
+ id: ObjectID.generate(),
474
+ exceptionType: "401",
475
+ message: "read record of Monitor",
476
+ occuranceCount: 8,
477
+ }),
478
+ fakeException({
479
+ id: ObjectID.generate(),
480
+ exceptionType: "401",
481
+ message: "read record of Incident",
482
+ occuranceCount: 60,
483
+ }),
484
+ ] as unknown as Array<TelemetryException>);
485
+ jest
486
+ .spyOn(ServiceService, "findOneById")
487
+ .mockResolvedValue({ name: "api" } as unknown as Service);
488
+
489
+ const detector: NewExceptionDetector = new NewExceptionDetector();
490
+ const candidates: Array<InsightCandidate> = await detector.detect({
491
+ projectId,
492
+ now,
493
+ });
494
+
495
+ expect(candidates).toHaveLength(2);
496
+ expect(candidates[0]!.fingerprint).not.toBe(candidates[1]!.fingerprint);
497
+ expect(candidates[0]!.title).not.toBe(candidates[1]!.title);
498
+ expect(candidates[0]!.evidence.exception?.totalOccurrenceCount).toBe(60);
499
+ });
500
+
501
+ test("caps emitted candidates at the candidate limit, not the scan limit", async () => {
502
+ const groups: Array<TelemetryException> = [];
503
+ for (let i: number = 0; i < NEW_EXCEPTION_GROUP_SCAN_LIMIT; i++) {
504
+ groups.push(
505
+ fakeException({
506
+ id: ObjectID.generate(),
507
+ exceptionType: "Error",
508
+ message: `distinct failure ${i}`,
509
+ occuranceCount: 5,
510
+ }),
511
+ );
512
+ }
513
+
514
+ jest
515
+ .spyOn(TelemetryExceptionService, "findBy")
516
+ .mockResolvedValue(groups as unknown as Array<TelemetryException>);
517
+ jest.spyOn(ServiceService, "findOneById").mockResolvedValue(null);
518
+
519
+ const detector: NewExceptionDetector = new NewExceptionDetector();
520
+ const candidates: Array<InsightCandidate> = await detector.detect({
521
+ projectId,
522
+ now,
523
+ });
524
+
525
+ expect(candidates).toHaveLength(NEW_EXCEPTION_CANDIDATE_LIMIT);
526
+ });
527
+
528
+ test("resolves each service name once, however many groups share it", async () => {
529
+ jest.spyOn(TelemetryExceptionService, "findBy").mockResolvedValue([
530
+ fakeException({
531
+ id: ObjectID.generate(),
532
+ message: "first failure",
533
+ occuranceCount: 10,
534
+ }),
535
+ fakeException({
536
+ id: ObjectID.generate(),
537
+ message: "second failure",
538
+ occuranceCount: 9,
539
+ }),
540
+ fakeException({
541
+ id: ObjectID.generate(),
542
+ message: "third failure",
543
+ occuranceCount: 8,
544
+ }),
545
+ ] as unknown as Array<TelemetryException>);
546
+ const findOneByIdSpy: jest.SpyInstance = jest
547
+ .spyOn(ServiceService, "findOneById")
548
+ .mockResolvedValue({ name: "web-api" } as unknown as Service);
549
+
550
+ const detector: NewExceptionDetector = new NewExceptionDetector();
551
+ const candidates: Array<InsightCandidate> = await detector.detect({
552
+ projectId,
553
+ now,
554
+ });
555
+
556
+ expect(candidates).toHaveLength(3);
557
+ expect(findOneByIdSpy).toHaveBeenCalledTimes(1);
209
558
  });
210
559
 
211
560
  test("unresolvable service (host/cluster entity) ships without a service name", async () => {
@@ -223,7 +572,9 @@ describe("NewExceptionDetector.detect (IO wiring)", () => {
223
572
  });
224
573
 
225
574
  expect(candidates).toHaveLength(1);
226
- expect(candidates[0]!.title).toBe("New exception: TypeError");
575
+ expect(candidates[0]!.title).toBe(
576
+ "New exception: TypeError: Cannot read properties of undefined (reading 'user')",
577
+ );
227
578
  expect(candidates[0]!.serviceName).toBeUndefined();
228
579
  expect(candidates[0]!.telemetryServiceId).toBeUndefined();
229
580
  expect(candidates[0]!.severity).toBe(AIInsightSeverity.Medium);