@oneuptime/common 11.3.26 → 11.4.0

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 (298) hide show
  1. package/Models/DatabaseModels/AIRun.ts +50 -0
  2. package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
  3. package/Models/DatabaseModels/IncidentMember.ts +1 -1
  4. package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
  5. package/Models/DatabaseModels/Index.ts +3 -0
  6. package/Models/DatabaseModels/MigrationFailure.ts +170 -0
  7. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
  8. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
  9. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
  10. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
  11. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
  12. package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
  13. package/Models/DatabaseModels/Project.ts +60 -0
  14. package/Server/API/AIInvestigationAPI.ts +148 -0
  15. package/Server/API/AlertAPI.ts +1 -1
  16. package/Server/API/IncidentAPI.ts +2 -2
  17. package/Server/API/IncidentEpisodeAPI.ts +1 -1
  18. package/Server/API/LlmProviderAPI.ts +1 -0
  19. package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  28. package/Server/Infrastructure/PostgresDatabase.ts +11 -0
  29. package/Server/Services/AIService.ts +74 -2
  30. package/Server/Services/AlertService.ts +25 -0
  31. package/Server/Services/AnalyticsDatabaseService.ts +10 -1
  32. package/Server/Services/DatabaseService.ts +10 -1
  33. package/Server/Services/IncidentService.ts +26 -1
  34. package/Server/Services/IncidentStateTimelineService.ts +19 -0
  35. package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
  36. package/Server/Services/IncomingCallPolicyService.ts +42 -1
  37. package/Server/Services/Index.ts +2 -0
  38. package/Server/Services/MigrationFailureService.ts +9 -0
  39. package/Server/Services/MonitorProbeService.ts +33 -7
  40. package/Server/Services/MonitorService.ts +19 -7
  41. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  42. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
  43. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
  44. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
  45. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
  46. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
  47. package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
  48. package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
  49. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
  50. package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
  51. package/Server/Services/TeamMemberService.ts +6 -0
  52. package/Server/Services/UserNotificationRuleService.ts +13 -42
  53. package/Server/Services/UserOnCallLogService.ts +90 -0
  54. package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
  55. package/Server/Types/Database/QueryUtil.ts +57 -0
  56. package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
  57. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
  58. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
  59. package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
  60. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
  61. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
  62. package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
  63. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
  64. package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
  65. package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
  66. package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
  67. package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
  68. package/Server/Utils/AI/Toolbox/Index.ts +15 -1
  69. package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
  70. package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
  71. package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
  72. package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
  73. package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
  74. package/Server/Utils/LLM/LLMService.ts +110 -11
  75. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
  76. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
  77. package/Tests/Server/API/BaseAPI.test.ts +1 -0
  78. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
  79. package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
  80. package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
  81. package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
  82. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
  83. package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
  84. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
  85. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
  86. package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
  87. package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
  88. package/Tests/Types/Events/Recurring.test.ts +22 -9
  89. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
  90. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
  91. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
  92. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
  93. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
  94. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
  95. package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
  96. package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
  97. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
  98. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
  99. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
  100. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
  101. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
  102. package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
  103. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
  104. package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
  105. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
  106. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
  107. package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
  108. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
  109. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
  110. package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
  111. package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
  112. package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
  113. package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
  114. package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
  115. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
  116. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
  117. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
  118. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
  119. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
  120. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
  121. package/Types/Date.ts +246 -15
  122. package/Types/OnCallDutyPolicy/Layer.ts +790 -260
  123. package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
  124. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
  125. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
  126. package/UI/Components/Calendar/Calendar.tsx +8 -2
  127. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  128. package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
  129. package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
  130. package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
  131. package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
  132. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  133. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
  135. package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
  136. package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
  137. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
  138. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
  139. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  140. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  141. package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
  142. package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
  143. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
  144. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
  146. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
  148. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
  150. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
  152. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
  154. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
  155. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  156. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  157. package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
  158. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
  159. package/build/dist/Server/API/AlertAPI.js +1 -1
  160. package/build/dist/Server/API/IncidentAPI.js +2 -2
  161. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  162. package/build/dist/Server/API/LlmProviderAPI.js +1 -0
  163. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  164. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
  172. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
  173. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
  174. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
  175. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
  176. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
  177. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
  178. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  181. package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
  182. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  183. package/build/dist/Server/Services/AIService.js +53 -2
  184. package/build/dist/Server/Services/AIService.js.map +1 -1
  185. package/build/dist/Server/Services/AlertService.js +24 -0
  186. package/build/dist/Server/Services/AlertService.js.map +1 -1
  187. package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
  188. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  189. package/build/dist/Server/Services/DatabaseService.js +10 -1
  190. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  191. package/build/dist/Server/Services/IncidentService.js +25 -1
  192. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  193. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  194. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  195. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
  196. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
  197. package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
  198. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  199. package/build/dist/Server/Services/Index.js +2 -0
  200. package/build/dist/Server/Services/Index.js.map +1 -1
  201. package/build/dist/Server/Services/MigrationFailureService.js +9 -0
  202. package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
  203. package/build/dist/Server/Services/MonitorProbeService.js +33 -7
  204. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  205. package/build/dist/Server/Services/MonitorService.js +12 -5
  206. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
  208. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
  210. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  211. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
  212. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  213. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
  214. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  215. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
  216. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  217. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
  218. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  219. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
  220. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  221. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
  222. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
  223. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
  224. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  225. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
  226. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  227. package/build/dist/Server/Services/TeamMemberService.js +6 -0
  228. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  229. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
  230. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  231. package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
  232. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  233. package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
  234. package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
  235. package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
  236. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  237. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
  238. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  239. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
  240. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  241. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
  242. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  243. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
  244. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  245. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
  246. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
  247. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
  248. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
  249. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
  250. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
  251. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
  252. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
  253. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
  254. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
  255. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
  256. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
  257. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
  258. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
  259. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
  260. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/Toolbox/Index.js +10 -1
  262. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
  264. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
  265. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
  266. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
  267. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
  268. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  269. package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
  270. package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
  271. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
  272. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
  273. package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
  274. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  275. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
  276. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  277. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
  278. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  279. package/build/dist/Types/Date.js +185 -15
  280. package/build/dist/Types/Date.js.map +1 -1
  281. package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
  282. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  283. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
  284. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
  285. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
  286. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
  287. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
  288. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  289. package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
  290. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  291. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  292. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  293. package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
  294. package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
  295. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  296. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
  297. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
  298. package/package.json +1 -1
@@ -0,0 +1,157 @@
1
+ /**
2
+ * REPRO: restricted daily rotation resolved during the post-window gap of the
3
+ * current rotation period attributes the NEXT shift to the WRONG (previous)
4
+ * user, and stays off-by-one forever after.
5
+ *
6
+ * Config: daily rotation x1, users [A,B], daily restriction 09:00-17:00.
7
+ * Layer start Mon 2025-01-06 00:00 UTC, handoff = +1 day.
8
+ * Full expansion (calendar/preview) : Jan6=A, Jan7=B, Jan8=A ...
9
+ * Windowed resolution asked at Jan6 20:52 (after 17:00 close) : Jan7=A (WRONG).
10
+ */
11
+ import LayerUtil, { LayerProps } from "../../../Types/OnCallDutyPolicy/Layer";
12
+ import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
13
+ import RestrictionTimes, {
14
+ RestrictionType,
15
+ } from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
16
+ import Recurring from "../../../Types/Events/Recurring";
17
+ import OneUptimeDate from "../../../Types/Date";
18
+ import User from "../../../Models/DatabaseModels/User";
19
+ import EventInterval from "../../../Types/Events/EventInterval";
20
+ import PositiveNumber from "../../../Types/PositiveNumber";
21
+
22
+ function user(id: string): User {
23
+ return {
24
+ id: {
25
+ toString: (): string => {
26
+ return id;
27
+ },
28
+ } as any,
29
+ } as User;
30
+ }
31
+
32
+ function dailyRestriction(sh: number, eh: number): RestrictionTimes {
33
+ const r: RestrictionTimes = new RestrictionTimes();
34
+ r.restictionType = RestrictionType.Daily;
35
+ r.dayRestrictionTimes = {
36
+ startTime: OneUptimeDate.getDateWithCustomTime({
37
+ hours: sh,
38
+ minutes: 0,
39
+ seconds: 0,
40
+ }),
41
+ endTime: OneUptimeDate.getDateWithCustomTime({
42
+ hours: eh,
43
+ minutes: 0,
44
+ seconds: 0,
45
+ }),
46
+ };
47
+ return r;
48
+ }
49
+
50
+ function makeLayer(): LayerProps {
51
+ const rot: Recurring = new Recurring();
52
+ rot.intervalType = EventInterval.Day;
53
+ rot.intervalCount = new PositiveNumber(1);
54
+ const start: Date = OneUptimeDate.fromString("2025-01-06T00:00:00.000Z");
55
+ return {
56
+ users: [user("A"), user("B")],
57
+ startDateTimeOfLayer: start,
58
+ restrictionTimes: dailyRestriction(9, 17),
59
+ handOffTime: Recurring.getNextDateInterval(start, rot),
60
+ rotation: rot,
61
+ timezone: undefined,
62
+ };
63
+ }
64
+
65
+ function fmt(e: CalendarEvent): string {
66
+ return `${e.title} [${e.start.toISOString()} -> ${e.end.toISOString()}]`;
67
+ }
68
+
69
+ describe("Restricted daily rotation: post-window gap query is off-by-one", () => {
70
+ test("full expansion says Jan7=B but a Jan6-evening query says Jan7=A", () => {
71
+ const layer: LayerProps = makeLayer();
72
+ const util: LayerUtil = new LayerUtil();
73
+
74
+ // FULL expansion (ground truth = what the dashboard calendar shows).
75
+ const full: Array<CalendarEvent> = util.getEvents({
76
+ ...layer,
77
+ calendarStartDate: layer.startDateTimeOfLayer,
78
+ calendarEndDate: OneUptimeDate.fromString("2025-01-10T00:00:00.000Z"),
79
+ });
80
+
81
+ // Identify who covers the Jan 7 09:00-17:00 window in the full expansion.
82
+ const jan7noon: Date = OneUptimeDate.fromString("2025-01-07T12:00:00.000Z");
83
+ const jan7Cover: CalendarEvent | undefined = full.find(
84
+ (e: CalendarEvent) => {
85
+ return (
86
+ OneUptimeDate.isOnOrAfter(jan7noon, e.start) &&
87
+ OneUptimeDate.isBefore(jan7noon, e.end)
88
+ );
89
+ },
90
+ );
91
+
92
+ // WINDOWED resolution asked during the Jan 6 evening gap (after 17:00).
93
+ const askAt: Date = OneUptimeDate.fromString("2025-01-06T20:52:48.000Z");
94
+ const windowed: Array<CalendarEvent> = util.getEvents(
95
+ {
96
+ ...layer,
97
+ calendarStartDate: askAt,
98
+ calendarEndDate: OneUptimeDate.addRemoveDays(askAt, 5),
99
+ },
100
+ { getNumberOfEvents: 1 },
101
+ );
102
+ const windowedNext: CalendarEvent | undefined = windowed[0];
103
+
104
+ // Also the LIVE multi-layer path used by paging (single layer).
105
+ const live: Array<CalendarEvent> = util.getMultiLayerEvents(
106
+ {
107
+ layers: [layer],
108
+ calendarStartDate: askAt,
109
+ calendarEndDate: OneUptimeDate.addRemoveDays(askAt, 5),
110
+ },
111
+ { getNumberOfEvents: 1 },
112
+ );
113
+
114
+ // eslint-disable-next-line no-console
115
+ console.log("FULL:", full.map(fmt));
116
+ // eslint-disable-next-line no-console
117
+ console.log("Jan7 covered by (full):", jan7Cover && fmt(jan7Cover));
118
+ // eslint-disable-next-line no-console
119
+ console.log(
120
+ "WINDOWED next @Jan6 20:52:",
121
+ windowedNext && fmt(windowedNext),
122
+ );
123
+ // eslint-disable-next-line no-console
124
+ console.log("LIVE next @Jan6 20:52:", live[0] && fmt(live[0]));
125
+
126
+ // Ground truth: Jan 7 belongs to B.
127
+ expect(jan7Cover?.title).toBe("B");
128
+
129
+ // The windowed "next on-call" is the Jan 7 09:00-17:00 window ...
130
+ expect(
131
+ OneUptimeDate.getDateAsLocalFormattedString(windowedNext!.start),
132
+ ).toContain("2025");
133
+ expect(windowedNext!.start.getUTCHours()).toBe(9);
134
+ expect(windowedNext!.start.getUTCDate()).toBe(7);
135
+ // ... and MUST be B to match the calendar. It is A -> BUG.
136
+ expect(windowedNext?.title).toBe("B");
137
+ expect(live[0]?.title).toBe("B");
138
+ });
139
+
140
+ test("querying INSIDE Jan7 window correctly returns B (proves inconsistency)", () => {
141
+ const layer: LayerProps = makeLayer();
142
+ const util: LayerUtil = new LayerUtil();
143
+ const askInside: Date = OneUptimeDate.fromString(
144
+ "2025-01-07T10:00:00.000Z",
145
+ );
146
+ const windowed: Array<CalendarEvent> = util.getEvents(
147
+ {
148
+ ...layer,
149
+ calendarStartDate: askInside,
150
+ calendarEndDate: OneUptimeDate.addRemoveDays(askInside, 5),
151
+ },
152
+ { getNumberOfEvents: 1 },
153
+ );
154
+ // Asked inside the window, the same day resolves to B (correct).
155
+ expect(windowed[0]?.title).toBe("B");
156
+ });
157
+ });
@@ -0,0 +1,414 @@
1
+ import LayerUtil, { LayerProps } from "../../../Types/OnCallDutyPolicy/Layer";
2
+ import CalendarEvent from "../../../Types/Calendar/CalendarEvent";
3
+ import RestrictionTimes, {
4
+ RestrictionType,
5
+ WeeklyResctriction,
6
+ } from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
7
+ import Recurring from "../../../Types/Events/Recurring";
8
+ import OneUptimeDate from "../../../Types/Date";
9
+ import User from "../../../Models/DatabaseModels/User";
10
+ import EventInterval from "../../../Types/Events/EventInterval";
11
+ import DayOfWeek from "../../../Types/Day/DayOfWeek";
12
+
13
+ /*
14
+ * These tests lock down the on-call rotation/restriction correctness fixes with
15
+ * EXACT assertions (dates + user identities), covering the cases the previous
16
+ * suite missed: intervalCount >= 2, long-lived rotations, wrap-around weekly
17
+ * restrictions, overnight daily restrictions, and multi-layer "next" roster.
18
+ */
19
+
20
+ function user(id: string): User {
21
+ return {
22
+ id: {
23
+ toString: () => {
24
+ return id;
25
+ },
26
+ } as any,
27
+ } as User;
28
+ }
29
+
30
+ function rotation(
31
+ intervalType: EventInterval,
32
+ intervalCount: number,
33
+ ): Recurring {
34
+ return Recurring.fromJSON({
35
+ _type: "Recurring",
36
+ value: {
37
+ intervalType: intervalType,
38
+ intervalCount: { _type: "PositiveNumber", value: intervalCount },
39
+ },
40
+ } as any);
41
+ }
42
+
43
+ function noRestriction(): RestrictionTimes {
44
+ const r: RestrictionTimes = new RestrictionTimes();
45
+ r.restictionType = RestrictionType.None;
46
+ r.dayRestrictionTimes = null;
47
+ return r;
48
+ }
49
+
50
+ // Returns the Monday 00:00 (local) of the week containing `base`.
51
+ function mondayOf(base: Date): Date {
52
+ const startOfWeek: Date = OneUptimeDate.getStartOfTheWeek(base); // Sunday
53
+ return OneUptimeDate.addRemoveDays(startOfWeek, 1); // Monday
54
+ }
55
+
56
+ // Is time T covered by any event? (start <= T < end)
57
+ function coveringEvent(
58
+ events: Array<CalendarEvent>,
59
+ t: Date,
60
+ ): CalendarEvent | null {
61
+ for (const e of events) {
62
+ if (
63
+ OneUptimeDate.isOnOrAfter(t, e.start) &&
64
+ OneUptimeDate.isBefore(t, e.end)
65
+ ) {
66
+ return e;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+
72
+ describe("LayerUtil HIGH-1: intervalCount >= 2 rotation stays on boundaries", () => {
73
+ test("every-2-days rotation keeps handoffs on even-day boundaries from a mid-interval preview", () => {
74
+ const util: LayerUtil = new LayerUtil();
75
+ // Fixed Jan 2026 (no DST transitions) local midnight anchor.
76
+ const layerStart: Date = OneUptimeDate.getStartOfDay(
77
+ new Date(2026, 0, 1, 0, 0, 0),
78
+ );
79
+
80
+ // Preview window starts 2.5 days in (mid second interval [day2,day4]).
81
+ const calStart: Date = OneUptimeDate.addRemoveHours(
82
+ OneUptimeDate.addRemoveDays(layerStart, 2),
83
+ 12,
84
+ );
85
+ const calEnd: Date = OneUptimeDate.addRemoveDays(layerStart, 12);
86
+
87
+ const layer: LayerProps = {
88
+ users: [user("A"), user("B")],
89
+ startDateTimeOfLayer: layerStart,
90
+ handOffTime: layerStart,
91
+ restrictionTimes: noRestriction(),
92
+ rotation: rotation(EventInterval.Day, 2),
93
+ };
94
+
95
+ const events: Array<CalendarEvent> = util.getEvents({
96
+ ...layer,
97
+ calendarStartDate: calStart,
98
+ calendarEndDate: calEnd,
99
+ });
100
+
101
+ const dayOf: (d: Date) => number = (d: Date): number => {
102
+ return Math.round(
103
+ OneUptimeDate.getSecondsBetweenTwoDates(layerStart, d) / 86400,
104
+ );
105
+ };
106
+
107
+ // First event must END at day 4 (the true next boundary), not day 5.
108
+ expect(dayOf(events[0]!.end)).toBe(4);
109
+ // The current user at day 2.5 is B (interval [day2,day4]).
110
+ expect(events[0]!.title).toBe("B");
111
+
112
+ // Every event boundary is an even number of days from the layer start.
113
+ for (const e of events) {
114
+ expect(dayOf(e.end) % 2).toBe(0);
115
+ }
116
+
117
+ // Users alternate B, A, B, A ...
118
+ expect(
119
+ events.slice(0, 4).map((e: CalendarEvent) => {
120
+ return e.title;
121
+ }),
122
+ ).toEqual(["B", "A", "B", "A"]);
123
+ });
124
+
125
+ test("every-3-hours rotation lands on 3-hour boundaries", () => {
126
+ const util: LayerUtil = new LayerUtil();
127
+ const layerStart: Date = OneUptimeDate.getStartOfDay(
128
+ new Date(2026, 0, 1, 0, 0, 0),
129
+ );
130
+ // Preview from 10:30 (inside interval [9:00,12:00]).
131
+ const calStart: Date = OneUptimeDate.addRemoveHours(
132
+ OneUptimeDate.addRemoveMinutes(layerStart, 30),
133
+ 10,
134
+ );
135
+ const calEnd: Date = OneUptimeDate.addRemoveHours(layerStart, 24);
136
+
137
+ const events: Array<CalendarEvent> = util.getEvents({
138
+ users: [user("A"), user("B"), user("C")],
139
+ startDateTimeOfLayer: layerStart,
140
+ handOffTime: layerStart,
141
+ restrictionTimes: noRestriction(),
142
+ rotation: rotation(EventInterval.Hour, 3),
143
+ calendarStartDate: calStart,
144
+ calendarEndDate: calEnd,
145
+ });
146
+
147
+ const hourOf: (d: Date) => number = (d: Date): number => {
148
+ return Math.round(
149
+ OneUptimeDate.getSecondsBetweenTwoDates(layerStart, d) / 3600,
150
+ );
151
+ };
152
+
153
+ // First handoff must be at hour 12, not 14.
154
+ expect(hourOf(events[0]!.end)).toBe(12);
155
+ // Every boundary is a multiple of 3 hours.
156
+ for (const e of events) {
157
+ expect(hourOf(e.end) % 3).toBe(0);
158
+ }
159
+ });
160
+
161
+ test("intervalCount = 1 daily rotation is unchanged (regression guard)", () => {
162
+ const util: LayerUtil = new LayerUtil();
163
+ const layerStart: Date = OneUptimeDate.getStartOfDay(
164
+ new Date(2026, 0, 1, 0, 0, 0),
165
+ );
166
+ const calStart: Date = OneUptimeDate.addRemoveHours(
167
+ OneUptimeDate.addRemoveDays(layerStart, 3),
168
+ 6,
169
+ );
170
+ const calEnd: Date = OneUptimeDate.addRemoveDays(layerStart, 8);
171
+
172
+ const events: Array<CalendarEvent> = util.getEvents({
173
+ users: [user("A"), user("B")],
174
+ startDateTimeOfLayer: layerStart,
175
+ handOffTime: layerStart,
176
+ restrictionTimes: noRestriction(),
177
+ rotation: rotation(EventInterval.Day, 1),
178
+ calendarStartDate: calStart,
179
+ calendarEndDate: calEnd,
180
+ });
181
+
182
+ const dayOf: (d: Date) => number = (d: Date): number => {
183
+ return Math.round(
184
+ OneUptimeDate.getSecondsBetweenTwoDates(layerStart, d) / 86400,
185
+ );
186
+ };
187
+ // First handoff after day 3.25 is day 4.
188
+ expect(dayOf(events[0]!.end)).toBe(4);
189
+ // day 3 (index 3) -> user B (0-indexed: day0=A,1=B,2=A,3=B).
190
+ expect(events[0]!.title).toBe("B");
191
+ });
192
+ });
193
+
194
+ describe("LayerUtil M-7 defense: invalid rotation interval does not hang or crash", () => {
195
+ test("intervalCount = 0 is clamped to a single unit (no infinite loop)", () => {
196
+ const util: LayerUtil = new LayerUtil();
197
+ const layerStart: Date = OneUptimeDate.getStartOfDay(
198
+ new Date(2026, 0, 1, 0, 0, 0),
199
+ );
200
+ const calEnd: Date = OneUptimeDate.addRemoveDays(layerStart, 4);
201
+
202
+ const events: Array<CalendarEvent> = util.getEvents({
203
+ users: [user("A"), user("B")],
204
+ startDateTimeOfLayer: layerStart,
205
+ handOffTime: layerStart,
206
+ restrictionTimes: noRestriction(),
207
+ rotation: rotation(EventInterval.Day, 0), // invalid -> clamped to 1
208
+ calendarStartDate: layerStart,
209
+ calendarEndDate: calEnd,
210
+ });
211
+
212
+ // Should terminate and produce daily events (treated as intervalCount 1).
213
+ expect(events.length).toBeGreaterThan(0);
214
+ const dayOf: (d: Date) => number = (d: Date): number => {
215
+ return Math.round(
216
+ OneUptimeDate.getSecondsBetweenTwoDates(layerStart, d) / 86400,
217
+ );
218
+ };
219
+ expect(dayOf(events[0]!.end)).toBe(1);
220
+ });
221
+ });
222
+
223
+ describe("LayerUtil M-1: long-lived rotation resolves the correct current user (no 10000 cap)", () => {
224
+ test("hourly rotation 12000 hours after start resolves the analytically-correct user", () => {
225
+ const util: LayerUtil = new LayerUtil();
226
+ const layerStart: Date = OneUptimeDate.getStartOfDay(
227
+ new Date(2024, 0, 1, 0, 0, 0),
228
+ );
229
+ // 12000 hours later (well past the old 10000-iteration cap).
230
+ const calStart: Date = OneUptimeDate.addRemoveHours(layerStart, 12000);
231
+ const calEnd: Date = OneUptimeDate.addRemoveHours(calStart, 5);
232
+
233
+ const events: Array<CalendarEvent> = util.getEvents({
234
+ users: [user("A"), user("B"), user("C")],
235
+ startDateTimeOfLayer: layerStart,
236
+ handOffTime: layerStart,
237
+ restrictionTimes: noRestriction(),
238
+ rotation: rotation(EventInterval.Hour, 1),
239
+ calendarStartDate: calStart,
240
+ calendarEndDate: calEnd,
241
+ });
242
+
243
+ /*
244
+ * 12000 boundaries elapsed -> index 12000 % 3 = 0 -> user A.
245
+ * The old capped simulation returned 10000 % 3 = 1 -> user B (wrong).
246
+ */
247
+ expect(events[0]!.title).toBe("A");
248
+ });
249
+ });
250
+
251
+ describe("LayerUtil HIGH-2: weekly wrap-around restriction has no phantom nightly windows", () => {
252
+ test("Fri 22:00 -> Mon 06:00 covers weekend only, not Tue/Wed/Thu nights", () => {
253
+ const util: LayerUtil = new LayerUtil();
254
+ const monday: Date = mondayOf(new Date(2026, 1, 15)); // February, no DST
255
+ const calEnd: Date = OneUptimeDate.addRemoveDays(monday, 7);
256
+
257
+ const friday22: Date = OneUptimeDate.addRemoveHours(
258
+ OneUptimeDate.addRemoveDays(monday, 4),
259
+ 22,
260
+ );
261
+ const monday06: Date = OneUptimeDate.addRemoveHours(monday, 6);
262
+
263
+ const weekly: WeeklyResctriction = {
264
+ startDay: DayOfWeek.Friday,
265
+ endDay: DayOfWeek.Monday,
266
+ startTime: friday22,
267
+ endTime: monday06,
268
+ };
269
+ const restrictionTimes: RestrictionTimes = new RestrictionTimes();
270
+ restrictionTimes.restictionType = RestrictionType.Weekly;
271
+ restrictionTimes.weeklyRestrictionTimes = [weekly];
272
+
273
+ const events: Array<CalendarEvent> = util.getEvents({
274
+ users: [user("u1")],
275
+ startDateTimeOfLayer: monday,
276
+ handOffTime: OneUptimeDate.addRemoveWeeks(monday, 1),
277
+ restrictionTimes: restrictionTimes,
278
+ rotation: rotation(EventInterval.Week, 1),
279
+ calendarStartDate: monday,
280
+ calendarEndDate: calEnd,
281
+ });
282
+
283
+ // Weekend nights ARE covered.
284
+ const sat02: Date = OneUptimeDate.addRemoveHours(
285
+ OneUptimeDate.addRemoveDays(monday, 5),
286
+ 2,
287
+ );
288
+ expect(coveringEvent(events, sat02)).not.toBeNull();
289
+
290
+ // The Monday-morning tail (00:00-06:00) of the prior weekend IS covered.
291
+ const mon03: Date = OneUptimeDate.addRemoveHours(monday, 3);
292
+ expect(coveringEvent(events, mon03)).not.toBeNull();
293
+
294
+ // Mid-week nights are NOT covered (these were the phantom windows).
295
+ const tue02: Date = OneUptimeDate.addRemoveHours(
296
+ OneUptimeDate.addRemoveDays(monday, 1),
297
+ 2,
298
+ );
299
+ const wed02: Date = OneUptimeDate.addRemoveHours(
300
+ OneUptimeDate.addRemoveDays(monday, 2),
301
+ 2,
302
+ );
303
+ const thu02: Date = OneUptimeDate.addRemoveHours(
304
+ OneUptimeDate.addRemoveDays(monday, 3),
305
+ 2,
306
+ );
307
+ expect(coveringEvent(events, tue02)).toBeNull();
308
+ expect(coveringEvent(events, wed02)).toBeNull();
309
+ expect(coveringEvent(events, thu02)).toBeNull();
310
+ });
311
+ });
312
+
313
+ describe("LayerUtil HIGH-3: overnight daily restriction covers the first-day morning", () => {
314
+ test("22:00 -> 06:00 with midnight daily rotation has no nightly coverage gap", () => {
315
+ const util: LayerUtil = new LayerUtil();
316
+ const monday: Date = mondayOf(new Date(2026, 1, 15));
317
+ const calEnd: Date = OneUptimeDate.addRemoveDays(monday, 3);
318
+
319
+ const restrictionTimes: RestrictionTimes = new RestrictionTimes();
320
+ restrictionTimes.restictionType = RestrictionType.Daily;
321
+ restrictionTimes.dayRestrictionTimes = {
322
+ startTime: OneUptimeDate.getDateWithCustomTime({
323
+ hours: 22,
324
+ minutes: 0,
325
+ seconds: 0,
326
+ }),
327
+ endTime: OneUptimeDate.getDateWithCustomTime({
328
+ hours: 6,
329
+ minutes: 0,
330
+ seconds: 0,
331
+ }),
332
+ };
333
+
334
+ const events: Array<CalendarEvent> = util.getEvents({
335
+ users: [user("A"), user("B"), user("C")],
336
+ startDateTimeOfLayer: monday,
337
+ handOffTime: OneUptimeDate.addRemoveDays(monday, 1),
338
+ restrictionTimes: restrictionTimes,
339
+ rotation: rotation(EventInterval.Day, 1),
340
+ calendarStartDate: monday,
341
+ calendarEndDate: calEnd,
342
+ });
343
+
344
+ // Tue 03:00 must be covered (day-2 user B, via the tail of Mon 22:00 window).
345
+ const tue03: Date = OneUptimeDate.addRemoveHours(
346
+ OneUptimeDate.addRemoveDays(monday, 1),
347
+ 3,
348
+ );
349
+ const cover: CalendarEvent | null = coveringEvent(events, tue03);
350
+ expect(cover).not.toBeNull();
351
+ expect(cover!.title).toBe("B");
352
+ });
353
+ });
354
+
355
+ describe("LayerUtil M-2: multi-layer 'next' roster keeps a fallback layer's post-block coverage", () => {
356
+ test("restricted primary + unrestricted fallback yields fallback as the next event", () => {
357
+ const util: LayerUtil = new LayerUtil();
358
+ const monday: Date = mondayOf(new Date(2026, 1, 15));
359
+ const calStart: Date = monday;
360
+ const calEnd: Date = OneUptimeDate.addRemoveDays(monday, 14);
361
+
362
+ // Primary: user A, weekly, active only Mon 00:00 -> Wed 00:00.
363
+ const primaryRestriction: RestrictionTimes = new RestrictionTimes();
364
+ primaryRestriction.restictionType = RestrictionType.Weekly;
365
+ primaryRestriction.weeklyRestrictionTimes = [
366
+ {
367
+ startDay: DayOfWeek.Monday,
368
+ endDay: DayOfWeek.Wednesday,
369
+ startTime: monday, // Monday 00:00
370
+ endTime: OneUptimeDate.addRemoveDays(monday, 2), // Wednesday 00:00
371
+ },
372
+ ];
373
+
374
+ const primary: LayerProps = {
375
+ users: [user("A")],
376
+ startDateTimeOfLayer: monday,
377
+ handOffTime: OneUptimeDate.addRemoveWeeks(monday, 1),
378
+ restrictionTimes: primaryRestriction,
379
+ rotation: rotation(EventInterval.Week, 1),
380
+ };
381
+
382
+ // Fallback: user B, daily, unrestricted.
383
+ const fallback: LayerProps = {
384
+ users: [user("B")],
385
+ startDateTimeOfLayer: monday,
386
+ handOffTime: OneUptimeDate.addRemoveDays(monday, 1),
387
+ restrictionTimes: noRestriction(),
388
+ rotation: rotation(EventInterval.Day, 1),
389
+ };
390
+
391
+ const events: Array<CalendarEvent> = util.getMultiLayerEvents(
392
+ {
393
+ layers: [primary, fallback],
394
+ calendarStartDate: calStart,
395
+ calendarEndDate: calEnd,
396
+ },
397
+ { getNumberOfEvents: 2 },
398
+ );
399
+
400
+ expect(events.length).toBe(2);
401
+ // Current: A (Mon 00:00 -> Wed 00:00).
402
+ expect(events[0]!.title).toBe("A");
403
+ // Next: fallback B filling the Wed gap — NOT A's next week.
404
+ expect(events[1]!.title).toBe("B");
405
+ /*
406
+ * And B's segment starts at the Wed 00:00 gap (end of A's block). The
407
+ * overlap-removal separates adjacent events by 1 second, so allow a small
408
+ * tolerance — the point is it starts at the gap, not A's next week.
409
+ */
410
+ const wed00: number = OneUptimeDate.addRemoveDays(monday, 2).getTime();
411
+ expect(events[1]!.start.getTime()).toBeGreaterThanOrEqual(wed00);
412
+ expect(events[1]!.start.getTime()).toBeLessThan(wed00 + 5000);
413
+ });
414
+ });