@oneuptime/common 12.0.1 → 12.0.3

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 (385) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  2. package/Models/DatabaseModels/Host.ts +12 -4
  3. package/Models/DatabaseModels/Index.ts +2 -0
  4. package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
  5. package/Models/DatabaseModels/KubernetesResource.ts +8 -7
  6. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  7. package/Models/DatabaseModels/PodmanResource.ts +6 -6
  8. package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
  9. package/Models/DatabaseModels/StatusPage.ts +95 -0
  10. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  11. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  12. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  13. package/Server/API/BaseAPI.ts +13 -2
  14. package/Server/API/DashboardAPI.ts +39 -0
  15. package/Server/API/TeamMemberAPI.ts +85 -0
  16. package/Server/EnvironmentConfig.ts +31 -1
  17. package/Server/Infrastructure/GlobalCache.ts +115 -0
  18. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  25. package/Server/Middleware/ProjectAuthorization.ts +66 -4
  26. package/Server/Services/AlertStateTimelineService.ts +20 -0
  27. package/Server/Services/BillingService.ts +600 -51
  28. package/Server/Services/CephClusterService.ts +27 -45
  29. package/Server/Services/CephResourceService.ts +43 -4
  30. package/Server/Services/CloudResourceService.ts +24 -42
  31. package/Server/Services/DatabaseService.ts +198 -6
  32. package/Server/Services/DockerHostService.ts +20 -38
  33. package/Server/Services/DockerResourceService.ts +62 -8
  34. package/Server/Services/DockerSwarmClusterService.ts +28 -46
  35. package/Server/Services/DockerSwarmResourceService.ts +53 -4
  36. package/Server/Services/HostService.ts +47 -57
  37. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  38. package/Server/Services/Index.ts +2 -0
  39. package/Server/Services/IoTDeviceService.ts +1 -17
  40. package/Server/Services/IoTFleetService.ts +20 -38
  41. package/Server/Services/KubernetesClusterService.ts +18 -36
  42. package/Server/Services/KubernetesContainerService.ts +50 -9
  43. package/Server/Services/KubernetesResourceService.ts +53 -7
  44. package/Server/Services/LabelService.ts +153 -1
  45. package/Server/Services/MetricTypeService.ts +84 -0
  46. package/Server/Services/MonitorService.ts +146 -14
  47. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  48. package/Server/Services/MonitorTemplateService.ts +65 -0
  49. package/Server/Services/PodmanHostService.ts +20 -38
  50. package/Server/Services/PodmanResourceService.ts +62 -8
  51. package/Server/Services/ProjectService.ts +165 -35
  52. package/Server/Services/ProxmoxClusterService.ts +24 -42
  53. package/Server/Services/ProxmoxResourceService.ts +43 -4
  54. package/Server/Services/RumApplicationService.ts +20 -38
  55. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  56. package/Server/Services/ServerlessFunctionService.ts +25 -43
  57. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
  58. package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
  59. package/Server/Services/ServiceService.ts +299 -73
  60. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  61. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  62. package/Server/Services/StatusPageService.ts +95 -36
  63. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
  64. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  65. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  66. package/Server/Services/TelemetryEntityService.ts +1 -0
  67. package/Server/Types/Database/JSONColumnQuery.ts +621 -0
  68. package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
  69. package/Server/Types/Database/QueryHelper.ts +17 -32
  70. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  71. package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
  72. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  73. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  74. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  75. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  76. package/Server/Utils/SingleFlight.ts +83 -0
  77. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  78. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  79. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  80. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  81. package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
  82. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  83. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  84. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  85. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  86. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
  87. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  88. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
  89. package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
  90. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  91. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
  92. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
  93. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
  94. package/Tests/Server/Services/BillingService.test.ts +78 -3
  95. package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
  96. package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
  97. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  98. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  99. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
  100. package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
  101. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
  102. package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
  103. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
  104. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
  105. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  106. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  107. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  108. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
  109. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
  110. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
  111. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
  112. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
  113. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  114. package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
  115. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  116. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  117. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  118. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
  119. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
  120. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
  121. package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
  122. package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
  123. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  124. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  125. package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
  126. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  127. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  128. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  129. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  130. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  131. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  132. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  133. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  134. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  135. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  136. package/Tests/Types/Decimal.test.ts +37 -0
  137. package/Tests/Types/Events/Recurring.test.ts +157 -0
  138. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  139. package/Tests/Types/Port.test.ts +38 -0
  140. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  141. package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
  142. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
  143. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  144. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  145. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
  146. package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
  147. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  148. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  149. package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
  150. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
  151. package/Types/CustomField/CustomFieldDefinition.ts +24 -0
  152. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  153. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  154. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  155. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  156. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  157. package/Types/Database/DatabaseProperty.ts +26 -0
  158. package/Types/Database/NumericColumnValue.ts +128 -0
  159. package/Types/Date.ts +112 -0
  160. package/Types/Decimal.ts +16 -4
  161. package/Types/Events/Recurring.ts +99 -1
  162. package/Types/Permission.ts +48 -0
  163. package/Types/Port.ts +16 -4
  164. package/Types/StatusPage/StatusPageReport.ts +11 -0
  165. package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
  166. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  167. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  168. package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
  169. package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
  170. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
  171. package/UI/Utils/TeamMembersByUser.ts +302 -0
  172. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  173. package/Utils/Dashboard/Components/Index.ts +7 -0
  174. package/Utils/StatusPage/ReportPeriod.ts +236 -0
  175. package/Utils/Telemetry/HostIpAddresses.ts +98 -0
  176. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  177. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  178. package/build/dist/Models/DatabaseModels/Host.js +12 -4
  179. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  180. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  181. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  182. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
  183. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
  184. package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
  185. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  186. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  187. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  188. package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
  189. package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
  190. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
  191. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  192. package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
  193. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  194. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  195. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  196. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  197. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  198. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  199. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  200. package/build/dist/Server/API/BaseAPI.js +11 -1
  201. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  202. package/build/dist/Server/API/DashboardAPI.js +39 -0
  203. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  204. package/build/dist/Server/API/TeamMemberAPI.js +57 -2
  205. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  206. package/build/dist/Server/EnvironmentConfig.js +27 -1
  207. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  208. package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
  209. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  210. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  211. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  212. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
  213. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
  214. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
  215. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
  216. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
  217. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
  218. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
  219. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
  220. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  221. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  222. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  223. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  224. package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
  225. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  226. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  227. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  228. package/build/dist/Server/Services/BillingService.js +466 -36
  229. package/build/dist/Server/Services/BillingService.js.map +1 -1
  230. package/build/dist/Server/Services/CephClusterService.js +26 -39
  231. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  232. package/build/dist/Server/Services/CephResourceService.js +28 -3
  233. package/build/dist/Server/Services/CephResourceService.js.map +1 -1
  234. package/build/dist/Server/Services/CloudResourceService.js +23 -36
  235. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  236. package/build/dist/Server/Services/DatabaseService.js +155 -2
  237. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  238. package/build/dist/Server/Services/DockerHostService.js +19 -32
  239. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  240. package/build/dist/Server/Services/DockerResourceService.js +34 -4
  241. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  242. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
  243. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  244. package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
  245. package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
  246. package/build/dist/Server/Services/HostService.js +58 -64
  247. package/build/dist/Server/Services/HostService.js.map +1 -1
  248. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  249. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  250. package/build/dist/Server/Services/Index.js +2 -0
  251. package/build/dist/Server/Services/Index.js.map +1 -1
  252. package/build/dist/Server/Services/IoTDeviceService.js +1 -8
  253. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  254. package/build/dist/Server/Services/IoTFleetService.js +19 -32
  255. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  256. package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
  257. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  258. package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
  259. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
  260. package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
  261. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  262. package/build/dist/Server/Services/LabelService.js +127 -0
  263. package/build/dist/Server/Services/LabelService.js.map +1 -1
  264. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  265. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  266. package/build/dist/Server/Services/MonitorService.js +128 -9
  267. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  268. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  269. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  270. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  271. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  272. package/build/dist/Server/Services/PodmanHostService.js +19 -32
  273. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  274. package/build/dist/Server/Services/PodmanResourceService.js +34 -4
  275. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  276. package/build/dist/Server/Services/ProjectService.js +135 -21
  277. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  278. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
  279. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  280. package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
  281. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  282. package/build/dist/Server/Services/RumApplicationService.js +19 -32
  283. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  284. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  285. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  286. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -37
  287. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  288. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
  289. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
  290. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
  291. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  292. package/build/dist/Server/Services/ServiceService.js +236 -61
  293. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  294. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  295. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  296. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  297. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  298. package/build/dist/Server/Services/StatusPageService.js +69 -46
  299. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  300. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  301. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  302. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  303. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  304. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  305. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  306. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  307. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  308. package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
  309. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
  310. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
  311. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
  312. package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
  313. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  314. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  315. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  316. package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
  317. package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
  318. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  319. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  320. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  321. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  322. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  323. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  324. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  325. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  326. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  327. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  328. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  329. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  330. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  331. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  332. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  333. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  334. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  335. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  336. package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
  337. package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
  338. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  339. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  340. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  341. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  342. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  343. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  344. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  345. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  346. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  347. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  348. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  349. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  350. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  351. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  352. package/build/dist/Types/Date.js +68 -0
  353. package/build/dist/Types/Date.js.map +1 -1
  354. package/build/dist/Types/Decimal.js +14 -3
  355. package/build/dist/Types/Decimal.js.map +1 -1
  356. package/build/dist/Types/Events/Recurring.js +63 -0
  357. package/build/dist/Types/Events/Recurring.js.map +1 -1
  358. package/build/dist/Types/Permission.js +42 -0
  359. package/build/dist/Types/Permission.js.map +1 -1
  360. package/build/dist/Types/Port.js +15 -5
  361. package/build/dist/Types/Port.js.map +1 -1
  362. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
  363. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
  364. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  365. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  366. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  367. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  368. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  369. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
  370. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  371. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
  372. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
  373. package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
  374. package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
  375. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  376. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  377. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  378. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  379. package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
  380. package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
  381. package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
  382. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
  383. package/jest.config.json +2 -0
  384. package/package.json +1 -1
  385. package/tsconfig.json +12 -1
@@ -0,0 +1,179 @@
1
+ import {
2
+ truncateLongText,
3
+ truncateShortText,
4
+ truncateToLength,
5
+ } from "../../../../Server/Utils/Database/TruncateColumnValue";
6
+ import ColumnLength from "../../../../Types/Database/ColumnLength";
7
+ import { describe, expect, test } from "@jest/globals";
8
+
9
+ /*
10
+ * The clamp that keeps a single oversized value from aborting a 500-row
11
+ * bulk INSERT (issue #3006 follow-up). Everything here is about the
12
+ * boundary: clip one character too eagerly and legitimate values get
13
+ * silently corrupted; clip one too late and Postgres still raises 22001
14
+ * and drops the whole chunk.
15
+ */
16
+
17
+ describe("truncateToLength", () => {
18
+ test("returns a short value untouched, by identity", () => {
19
+ const value: string = "nginx:1.27";
20
+ expect(truncateToLength(value, 100)).toBe(value);
21
+ });
22
+
23
+ test("returns a value of exactly maxLength untouched", () => {
24
+ /*
25
+ * The off-by-one that matters most: varchar(100) accepts exactly
26
+ * 100 characters, so clipping at 100 would corrupt a legal value.
27
+ */
28
+ const value: string = "a".repeat(100);
29
+ const result: string = truncateToLength(value, 100);
30
+ expect(result).toBe(value);
31
+ expect(result).toHaveLength(100);
32
+ });
33
+
34
+ test("clips a value one character too long down to exactly maxLength", () => {
35
+ const value: string = "a".repeat(101);
36
+ const result: string = truncateToLength(value, 100);
37
+ expect(result).toHaveLength(100);
38
+ expect(value.startsWith(result)).toBe(true);
39
+ });
40
+
41
+ test("clips a wildly oversized value down to exactly maxLength", () => {
42
+ const result: string = truncateToLength("b".repeat(5000), 100);
43
+ expect(result).toHaveLength(100);
44
+ expect(result).toBe("b".repeat(100));
45
+ });
46
+
47
+ test("the clipped value is always a prefix of the original", () => {
48
+ const value: string =
49
+ "ghcr.io/some-really-long-organization-name/some-really-long-repository-name:v1.2.3-rc.4-with-a-long-build-suffix";
50
+ const result: string = truncateToLength(value, 40);
51
+ expect(value.startsWith(result)).toBe(true);
52
+ expect(result).toHaveLength(40);
53
+ });
54
+
55
+ test("passes null through as null", () => {
56
+ expect(truncateToLength(null, 100)).toBeNull();
57
+ });
58
+
59
+ test("normalizes undefined to null, so it binds as SQL NULL", () => {
60
+ expect(truncateToLength(undefined, 100)).toBeNull();
61
+ });
62
+
63
+ test("keeps an empty string as a string, never as null", () => {
64
+ /*
65
+ * KubernetesContainer.podNamespaceKey is NOT NULL with a "" default
66
+ * for cluster-scoped pods — turning "" into NULL would break the
67
+ * insert and the unique index that covers it.
68
+ */
69
+ const result: string = truncateToLength("", 100);
70
+ expect(result).toBe("");
71
+ expect(result).not.toBeNull();
72
+ });
73
+
74
+ test("handles a maxLength of zero without throwing", () => {
75
+ expect(truncateToLength("anything", 0)).toBe("");
76
+ });
77
+
78
+ describe("multi-byte characters", () => {
79
+ /*
80
+ * Postgres counts varchar(n) in characters; JS counts string length
81
+ * in UTF-16 code units. Astral-plane characters (emoji, rare CJK)
82
+ * are two code units each, so clipping by code units is always
83
+ * conservative — but the cut must not land inside a surrogate pair.
84
+ */
85
+ test("never emits more characters than the column allows", () => {
86
+ const value: string = "😀".repeat(200); // 400 code units, 200 chars
87
+ const result: string = truncateToLength(value, 100);
88
+
89
+ expect(result.length).toBeLessThanOrEqual(100);
90
+ expect(Array.from(result).length).toBeLessThanOrEqual(100);
91
+ });
92
+
93
+ test("does not split a surrogate pair when the cut lands inside one", () => {
94
+ /*
95
+ * 99 ASCII + one 2-code-unit emoji = 101 code units; cut at 100
96
+ * would otherwise land between the emoji's two halves.
97
+ */
98
+ const value: string = `${"a".repeat(99)}😀`;
99
+ const result: string = truncateToLength(value, 100);
100
+
101
+ expect(result).toBe("a".repeat(99));
102
+ expect(result).not.toContain("\ud83d");
103
+ // A lone surrogate survives a round trip through UTF-8 as U+FFFD.
104
+ expect(Buffer.from(result, "utf8").toString("utf8")).toBe(result);
105
+ });
106
+
107
+ test("keeps a whole surrogate pair when it fits exactly", () => {
108
+ const value: string = `${"a".repeat(98)}😀b`; // 101 code units
109
+ const result: string = truncateToLength(value, 100);
110
+
111
+ expect(result).toBe(`${"a".repeat(98)}😀`);
112
+ expect(result).toHaveLength(100);
113
+ expect(Buffer.from(result, "utf8").toString("utf8")).toBe(result);
114
+ });
115
+
116
+ test("leaves a multi-byte value that fits completely alone", () => {
117
+ const value: string = "café-über-日本語";
118
+ expect(truncateToLength(value, 100)).toBe(value);
119
+ });
120
+ });
121
+ });
122
+
123
+ describe("truncateShortText", () => {
124
+ test("clamps at ColumnLength.ShortText", () => {
125
+ expect(truncateShortText("a".repeat(500))).toHaveLength(
126
+ ColumnLength.ShortText,
127
+ );
128
+ });
129
+
130
+ test("ColumnLength.ShortText is still 100, the varchar width it mirrors", () => {
131
+ expect(ColumnLength.ShortText).toBe(100);
132
+ });
133
+
134
+ test("agrees with truncateToLength at the same bound", () => {
135
+ const value: string = "z".repeat(250);
136
+ expect(truncateShortText(value)).toBe(
137
+ truncateToLength(value, ColumnLength.ShortText),
138
+ );
139
+ });
140
+
141
+ test("passes null and short values straight through", () => {
142
+ expect(truncateShortText(null)).toBeNull();
143
+ expect(truncateShortText("osd.3")).toBe("osd.3");
144
+ });
145
+ });
146
+
147
+ describe("truncateLongText", () => {
148
+ test("clamps at ColumnLength.LongText", () => {
149
+ expect(truncateLongText("a".repeat(5000))).toHaveLength(
150
+ ColumnLength.LongText,
151
+ );
152
+ });
153
+
154
+ test("ColumnLength.LongText is still 500, the varchar width it mirrors", () => {
155
+ expect(ColumnLength.LongText).toBe(500);
156
+ });
157
+
158
+ test("accepts a 253-character Kubernetes DNS-subdomain name untouched", () => {
159
+ /*
160
+ * The whole point of widening the name columns: the longest name
161
+ * Kubernetes itself will ever produce has to pass through unclipped.
162
+ */
163
+ const podName: string = "p".repeat(253);
164
+ expect(truncateLongText(podName)).toBe(podName);
165
+ });
166
+
167
+ test("is strictly more permissive than truncateShortText", () => {
168
+ const value: string = "c".repeat(400);
169
+ expect(truncateLongText(value)).toBe(value);
170
+ expect(truncateShortText(value)).toHaveLength(ColumnLength.ShortText);
171
+ });
172
+
173
+ test("passes null and short values straight through", () => {
174
+ expect(truncateLongText(null)).toBeNull();
175
+ expect(truncateLongText("ghcr.io/acme/api:1.2.3")).toBe(
176
+ "ghcr.io/acme/api:1.2.3",
177
+ );
178
+ });
179
+ });
@@ -0,0 +1,302 @@
1
+ import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
2
+ import TelemetryEntityService from "../../../Server/Services/TelemetryEntityService";
3
+ import { reconcileByNaturalKey } from "../../../Server/Utils/Telemetry/EntityRegistry";
4
+ import TelemetryEntity from "../../../Models/DatabaseModels/TelemetryEntity";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import {
7
+ afterEach,
8
+ beforeEach,
9
+ describe,
10
+ expect,
11
+ jest,
12
+ test,
13
+ } from "@jest/globals";
14
+
15
+ /*
16
+ * The entity registry had the same defect as the Service heartbeat, one layer
17
+ * down and harder to see.
18
+ *
19
+ * Its fence keys on (project + the whole set of promoted entity keys), which
20
+ * in practice is unique per POD — a pod's own key is in the set. But the
21
+ * writes it gates are per ROW: the single TelemetryEntity row for a Kubernetes
22
+ * cluster receives one UPDATE per pod in that cluster per window, the
23
+ * namespace row one per pod in the namespace. When throttle granularity is
24
+ * finer than write granularity, the throttle does not bound the writes at all,
25
+ * and the mismatch factor is the pod count.
26
+ *
27
+ * So there are now two fences: the cheap set-level one, and a per-row one
28
+ * claimed atomically before the lookup — which also removes the redundant
29
+ * SELECT, not just the redundant UPDATE. The bump itself is the SKIP LOCKED
30
+ * write, so shared rows (a cluster, a namespace) cannot convoy the way Service
31
+ * did.
32
+ *
33
+ * Everything external is mocked — no Postgres, no Redis.
34
+ */
35
+
36
+ const PROJECT_ID: ObjectID = ObjectID.generate();
37
+ const ROW_ID: ObjectID = ObjectID.generate();
38
+
39
+ type BumpCall = { id: ObjectID; data: Record<string, unknown> };
40
+
41
+ let cache: Map<string, string>;
42
+ let deletedKeys: Array<string>;
43
+ let bumps: Array<BumpCall>;
44
+ let reads: number;
45
+ let existing: TelemetryEntity | null;
46
+ let bumpLands: boolean;
47
+
48
+ function existingRow(): TelemetryEntity {
49
+ const row: TelemetryEntity = new TelemetryEntity();
50
+ row.id = ROW_ID;
51
+ row._id = ROW_ID.toString();
52
+ return row;
53
+ }
54
+
55
+ function reconcile(rowFenceId: string): Promise<void> {
56
+ return reconcileByNaturalKey<TelemetryEntity>({
57
+ service: TelemetryEntityService,
58
+ query: { projectId: PROJECT_ID, entityKey: "k8s-cluster/prod" },
59
+ lastSeenAt: new Date(),
60
+ describe: "entity k8s-cluster/prod",
61
+ rowFenceId: rowFenceId,
62
+ buildModel: (): TelemetryEntity => {
63
+ return existingRow();
64
+ },
65
+ });
66
+ }
67
+
68
+ beforeEach(() => {
69
+ cache = new Map<string, string>();
70
+ deletedKeys = [];
71
+ bumps = [];
72
+ reads = 0;
73
+ existing = existingRow();
74
+ bumpLands = true;
75
+
76
+ jest
77
+ .spyOn(GlobalCache, "setStringIfNotExists")
78
+ .mockImplementation(async (namespace: string, key: string) => {
79
+ const full: string = `${namespace}:${key}`;
80
+ if (cache.has(full)) {
81
+ return false;
82
+ }
83
+ cache.set(full, "1");
84
+ return true;
85
+ });
86
+
87
+ jest
88
+ .spyOn(GlobalCache, "deleteKey")
89
+ .mockImplementation(async (namespace: string, key: string) => {
90
+ const full: string = `${namespace}:${key}`;
91
+ deletedKeys.push(full);
92
+ cache.delete(full);
93
+ });
94
+
95
+ jest
96
+ .spyOn(TelemetryEntityService, "findOneBy")
97
+ .mockImplementation(async () => {
98
+ reads++;
99
+ return existing;
100
+ });
101
+
102
+ jest
103
+ .spyOn(TelemetryEntityService, "updateColumnsByIdIfUnlockedWithoutHooks")
104
+ .mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
105
+ bumps.push({
106
+ id: input.id,
107
+ data: { ...(input.data as Record<string, unknown>) },
108
+ });
109
+ return bumpLands;
110
+ });
111
+
112
+ jest
113
+ .spyOn(TelemetryEntityService, "create")
114
+ .mockImplementation(async (input: { data: TelemetryEntity }) => {
115
+ return input.data;
116
+ });
117
+ });
118
+
119
+ afterEach(() => {
120
+ jest.restoreAllMocks();
121
+ });
122
+
123
+ describe("reconcileByNaturalKey — the per-row fence", () => {
124
+ test("bumps the row on first contact", async () => {
125
+ await reconcile("project:k8s-cluster:prod");
126
+
127
+ expect(bumps).toHaveLength(1);
128
+ expect(bumps[0]!.id.toString()).toBe(ROW_ID.toString());
129
+ expect(bumps[0]!.data["lastSeenAt"]).toBeInstanceOf(Date);
130
+ });
131
+
132
+ /*
133
+ * The defect, reproduced: many pods, one shared row. Before the per-row
134
+ * fence, each pod's distinct set-level fence let it straight through to the
135
+ * same cluster row.
136
+ */
137
+ test("many callers for the same row produce one bump per window", async () => {
138
+ for (let pod: number = 0; pod < 50; pod++) {
139
+ await reconcile("project:k8s-cluster:prod");
140
+ }
141
+
142
+ expect(bumps).toHaveLength(1);
143
+ });
144
+
145
+ /*
146
+ * Claimed BEFORE the lookup. The redundant SELECTs were as much of the pool
147
+ * pressure as the redundant UPDATEs.
148
+ */
149
+ test("a fenced caller does not even read the row", async () => {
150
+ await reconcile("project:k8s-cluster:prod");
151
+ const readsAfterFirst: number = reads;
152
+
153
+ await reconcile("project:k8s-cluster:prod");
154
+
155
+ expect(reads).toBe(readsAfterFirst);
156
+ });
157
+
158
+ test("different rows are fenced independently", async () => {
159
+ await reconcile("project:k8s-cluster:prod");
160
+ await reconcile("project:k8s-namespace:default");
161
+
162
+ expect(bumps).toHaveLength(2);
163
+ });
164
+
165
+ test("claims the fence atomically, never read-then-write", async () => {
166
+ jest.spyOn(GlobalCache, "getString");
167
+ jest.spyOn(GlobalCache, "setString");
168
+
169
+ await reconcile("project:k8s-cluster:prod");
170
+
171
+ expect(GlobalCache.setStringIfNotExists).toHaveBeenCalled();
172
+ expect(GlobalCache.getString).not.toHaveBeenCalled();
173
+ expect(GlobalCache.setString).not.toHaveBeenCalled();
174
+ });
175
+
176
+ /*
177
+ * The fence id concatenates entity keys and is unbounded in length. Hashing
178
+ * keeps the Redis key fixed-width — a pod reporting hundreds of entities
179
+ * would otherwise produce a multi-kilobyte key.
180
+ */
181
+ test("hashes the fence id to a fixed-width key", async () => {
182
+ const longId: string = `project:${"k".repeat(5000)}`;
183
+
184
+ await reconcile(longId);
185
+
186
+ const key: string = (GlobalCache.setStringIfNotExists as jest.Mock).mock
187
+ .calls[0]![1] as string;
188
+
189
+ expect(key.length).toBeLessThan(100);
190
+ expect(key).not.toContain("kkkk");
191
+ });
192
+
193
+ test("a cache outage fails open so liveness still advances", async () => {
194
+ jest
195
+ .spyOn(GlobalCache, "setStringIfNotExists")
196
+ .mockRejectedValue(new Error("redis down"));
197
+
198
+ await reconcile("project:k8s-cluster:prod");
199
+
200
+ expect(bumps).toHaveLength(1);
201
+ });
202
+ });
203
+
204
+ describe("reconcileByNaturalKey — contention and failure re-open the window", () => {
205
+ test("uses the non-blocking write primitive", async () => {
206
+ await reconcile("project:k8s-cluster:prod");
207
+
208
+ expect(
209
+ TelemetryEntityService.updateColumnsByIdIfUnlockedWithoutHooks,
210
+ ).toHaveBeenCalled();
211
+ });
212
+
213
+ /*
214
+ * A skipped bump means the row was locked, so nothing was written — but the
215
+ * fence is already claimed and would suppress the next attempt for the whole
216
+ * window. Release it so the next batch retries; each attempt is
217
+ * non-blocking, so this cannot become a retry storm.
218
+ */
219
+ test("a skipped bump releases the row fence", async () => {
220
+ bumpLands = false;
221
+
222
+ await reconcile("project:k8s-cluster:prod");
223
+
224
+ expect(deletedKeys).toHaveLength(1);
225
+ });
226
+
227
+ test("the released fence actually lets the next attempt through", async () => {
228
+ bumpLands = false;
229
+ await reconcile("project:k8s-cluster:prod");
230
+
231
+ bumpLands = true;
232
+ await reconcile("project:k8s-cluster:prod");
233
+
234
+ expect(bumps).toHaveLength(2);
235
+ });
236
+
237
+ /*
238
+ * An over-budget skip writes nothing, so holding the window would suppress
239
+ * the gate's own re-evaluation for a decision that had no effect.
240
+ */
241
+ test("an over-budget create releases the row fence", async () => {
242
+ existing = null;
243
+
244
+ await reconcileByNaturalKey<TelemetryEntity>({
245
+ service: TelemetryEntityService,
246
+ query: { projectId: PROJECT_ID, entityKey: "k8s-cluster/prod" },
247
+ lastSeenAt: new Date(),
248
+ describe: "entity k8s-cluster/prod",
249
+ rowFenceId: "project:k8s-cluster:prod",
250
+ buildModel: (): TelemetryEntity => {
251
+ return existingRow();
252
+ },
253
+ beforeCreate: async (): Promise<boolean> => {
254
+ return false;
255
+ },
256
+ });
257
+
258
+ expect(deletedKeys).toHaveLength(1);
259
+ });
260
+
261
+ test("an invalid create releases the row fence so the warning keeps surfacing", async () => {
262
+ existing = null;
263
+
264
+ jest
265
+ .spyOn(TelemetryEntityService, "create")
266
+ .mockRejectedValue(new Error("null value in column violates not-null"));
267
+
268
+ await reconcile("project:k8s-cluster:prod");
269
+
270
+ expect(deletedKeys).toHaveLength(1);
271
+ });
272
+
273
+ test("a lost create race bumps the winner instead of failing", async () => {
274
+ existing = null;
275
+
276
+ jest
277
+ .spyOn(TelemetryEntityService, "create")
278
+ .mockRejectedValue(new Error("duplicate key value violates unique"));
279
+
280
+ jest
281
+ .spyOn(TelemetryEntityService, "findOneBy")
282
+ .mockImplementationOnce(async () => {
283
+ reads++;
284
+ return null;
285
+ })
286
+ .mockImplementation(async () => {
287
+ reads++;
288
+ return existingRow();
289
+ });
290
+
291
+ await reconcile("project:k8s-cluster:prod");
292
+
293
+ expect(bumps).toHaveLength(1);
294
+ expect(deletedKeys).toHaveLength(0);
295
+ });
296
+
297
+ test("a successful bump leaves the fence claimed", async () => {
298
+ await reconcile("project:k8s-cluster:prod");
299
+
300
+ expect(deletedKeys).toHaveLength(0);
301
+ });
302
+ });