@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
@@ -16,6 +16,8 @@ import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
16
16
  import OneUptimeDate from "../../Types/Date";
17
17
  import ObjectID from "../../Types/ObjectID";
18
18
  import LayerUtil from "../../Types/OnCallDutyPolicy/Layer";
19
+ import { RestrictionType } from "../../Types/OnCallDutyPolicy/RestrictionTimes";
20
+ import Recurring from "../../Types/Events/Recurring";
19
21
  import UserOverrideUtil from "../../Types/OnCallDutyPolicy/UserOverrideUtil";
20
22
  import QueryHelper from "../Types/Database/QueryHelper";
21
23
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
@@ -29,6 +31,7 @@ import NotificationSettingEventType from "../../Types/NotificationSetting/Notifi
29
31
  import BadDataException from "../../Types/Exception/BadDataException";
30
32
  import Timezone from "../../Types/Timezone";
31
33
  import logger from "../Utils/Logger";
34
+ import Semaphore from "../Infrastructure/Semaphore";
32
35
  import OnCallDutyPolicyFeedService from "./OnCallDutyPolicyFeedService";
33
36
  import { OnCallDutyPolicyFeedEventType } from "../../Models/DatabaseModels/OnCallDutyPolicyFeed";
34
37
  import { Green500 } from "../../Types/BrandColors";
@@ -111,7 +114,7 @@ export class Service extends DatabaseService {
111
114
  }
112
115
  async sendNotificationToUserOnScheduleHandoff(data) {
113
116
  // Before we send any notification, we need to check if this schedule is attached to any on-call policy.
114
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
117
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
115
118
  const escalationRulesAttachedToSchedule = await OnCallDutyPolicyEscalationRuleScheduleService.findBy({
116
119
  query: {
117
120
  onCallDutyPolicyScheduleId: data.scheduleId,
@@ -275,14 +278,24 @@ export class Service extends DatabaseService {
275
278
  },
276
279
  });
277
280
  }
278
- if ((_m = newInformation.currentUserIdOnRoster) === null || _m === void 0 ? void 0 : _m.toString()) {
281
+ /*
282
+ * Only notify "you are now on-call" when the current user ACTUALLY
283
+ * changed — not merely when the handoff time changed. The enclosing
284
+ * guard also fires on a rosterHandoffAt change, which happens every
285
+ * rotation period; without this user-changed check a continuing (e.g.
286
+ * single-user) roster was re-sent the full email + SMS + phone call +
287
+ * push + WhatsApp bundle every period. Mirrors the removal branch above.
288
+ */
289
+ if (((_m = previousInformation.currentUserIdOnRoster) === null || _m === void 0 ? void 0 : _m.toString()) !==
290
+ ((_o = newInformation.currentUserIdOnRoster) === null || _o === void 0 ? void 0 : _o.toString()) &&
291
+ ((_p = newInformation.currentUserIdOnRoster) === null || _p === void 0 ? void 0 : _p.toString())) {
279
292
  // send email to the new current user.
280
293
  const sendEmailToUserId = newInformation.currentUserIdOnRoster;
281
294
  const userTimezone = await UserService.getTimezoneForUser(sendEmailToUserId);
282
295
  const vars = {
283
296
  onCallPolicyName: onCallPolicy.name || "No name provided",
284
297
  escalationRuleName: onCallDutyPolicyEscalationRule.name || "No name provided",
285
- escalationRuleOrder: ((_o = onCallDutyPolicyEscalationRule.order) === null || _o === void 0 ? void 0 : _o.toString()) || "-",
298
+ escalationRuleOrder: ((_q = onCallDutyPolicyEscalationRule.order) === null || _q === void 0 ? void 0 : _q.toString()) || "-",
286
299
  reason: "You are now on-call for the policy " +
287
300
  onCallPolicy.name +
288
301
  " because your on-call roster on schedule " +
@@ -371,7 +384,7 @@ export class Service extends DatabaseService {
371
384
  feedInfoInMarkdown: `📞 **${await UserService.getUserMarkdownString({
372
385
  userId: sendEmailToUserId,
373
386
  projectId: projectId,
374
- })}** is currently on call for [On-Call Policy ${(_p = escalationRule.onCallDutyPolicy) === null || _p === void 0 ? void 0 : _p.name}](${(await OnCallDutyPolicyService.getOnCallDutyPolicyLinkInDashboard(projectId, onCallDutyPolicyId)).toString()}) escalation rule **${(_q = escalationRule.onCallDutyPolicyEscalationRule) === null || _q === void 0 ? void 0 : _q.name}** with order **${(_r = escalationRule.onCallDutyPolicyEscalationRule) === null || _r === void 0 ? void 0 : _r.order}** because of schedule **${onCallSchedule.name}** and your on-call roster starts at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
387
+ })}** is currently on call for [On-Call Policy ${(_r = escalationRule.onCallDutyPolicy) === null || _r === void 0 ? void 0 : _r.name}](${(await OnCallDutyPolicyService.getOnCallDutyPolicyLinkInDashboard(projectId, onCallDutyPolicyId)).toString()}) escalation rule **${(_s = escalationRule.onCallDutyPolicyEscalationRule) === null || _s === void 0 ? void 0 : _s.name}** with order **${(_t = escalationRule.onCallDutyPolicyEscalationRule) === null || _t === void 0 ? void 0 : _t.order}** because of schedule **${onCallSchedule.name}** and your on-call roster starts at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
375
388
  date: newInformation.rosterStartAt,
376
389
  timezones: userTimezone ? [userTimezone] : [Timezone.GMT],
377
390
  })}** and ends at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
@@ -387,20 +400,31 @@ export class Service extends DatabaseService {
387
400
  }
388
401
  }
389
402
  // send an email to the next user.
390
- if (((_s = previousInformation.nextUserIdOnRoster) === null || _s === void 0 ? void 0 : _s.toString()) !==
391
- ((_t = newInformation.nextUserIdOnRoster) === null || _t === void 0 ? void 0 : _t.toString()) ||
392
- ((_u = previousInformation.nextHandOffTimeAt) === null || _u === void 0 ? void 0 : _u.toString()) !==
393
- ((_v = newInformation.nextHandOffTimeAt) === null || _v === void 0 ? void 0 : _v.toString()) ||
394
- ((_w = previousInformation.nextRosterStartAt) === null || _w === void 0 ? void 0 : _w.toString()) !==
395
- ((_x = newInformation.nextRosterStartAt) === null || _x === void 0 ? void 0 : _x.toString())) {
396
- if ((_y = newInformation.nextUserIdOnRoster) === null || _y === void 0 ? void 0 : _y.toString()) {
403
+ if (((_u = previousInformation.nextUserIdOnRoster) === null || _u === void 0 ? void 0 : _u.toString()) !==
404
+ ((_v = newInformation.nextUserIdOnRoster) === null || _v === void 0 ? void 0 : _v.toString()) ||
405
+ ((_w = previousInformation.nextHandOffTimeAt) === null || _w === void 0 ? void 0 : _w.toString()) !==
406
+ ((_x = newInformation.nextHandOffTimeAt) === null || _x === void 0 ? void 0 : _x.toString()) ||
407
+ ((_y = previousInformation.nextRosterStartAt) === null || _y === void 0 ? void 0 : _y.toString()) !==
408
+ ((_z = newInformation.nextRosterStartAt) === null || _z === void 0 ? void 0 : _z.toString())) {
409
+ /*
410
+ * Only notify "you are next on-call" when the next user ACTUALLY
411
+ * changed — not merely when the next handoff/start time advanced. The
412
+ * enclosing guard also fires when nextHandOffTimeAt / nextRosterStartAt
413
+ * change, which happens every rotation period; without this
414
+ * user-changed check a continuing (e.g. single-user) roster re-sent the
415
+ * full email + SMS + phone call + push + WhatsApp bundle every period.
416
+ * Mirrors the current-user "now on-call" branch above.
417
+ */
418
+ if (((_0 = previousInformation.nextUserIdOnRoster) === null || _0 === void 0 ? void 0 : _0.toString()) !==
419
+ ((_1 = newInformation.nextUserIdOnRoster) === null || _1 === void 0 ? void 0 : _1.toString()) &&
420
+ ((_2 = newInformation.nextUserIdOnRoster) === null || _2 === void 0 ? void 0 : _2.toString())) {
397
421
  // send email to the next user.
398
422
  const sendEmailToUserId = newInformation.nextUserIdOnRoster;
399
423
  const userTimezone = await UserService.getTimezoneForUser(sendEmailToUserId);
400
424
  const vars = {
401
425
  onCallPolicyName: onCallPolicy.name || "No name provided",
402
426
  escalationRuleName: onCallDutyPolicyEscalationRule.name || "No name provided",
403
- escalationRuleOrder: ((_z = onCallDutyPolicyEscalationRule.order) === null || _z === void 0 ? void 0 : _z.toString()) || "-",
427
+ escalationRuleOrder: ((_3 = onCallDutyPolicyEscalationRule.order) === null || _3 === void 0 ? void 0 : _3.toString()) || "-",
404
428
  reason: "You are next on-call for the policy " +
405
429
  onCallPolicy.name +
406
430
  " because your on-call roster on schedule " +
@@ -469,7 +493,7 @@ export class Service extends DatabaseService {
469
493
  feedInfoInMarkdown: `➡️ **${await UserService.getUserMarkdownString({
470
494
  userId: sendEmailToUserId,
471
495
  projectId: projectId,
472
- })}** is next on call for [On-Call Policy ${(_0 = escalationRule.onCallDutyPolicy) === null || _0 === void 0 ? void 0 : _0.name}](${(await OnCallDutyPolicyService.getOnCallDutyPolicyLinkInDashboard(projectId, onCallDutyPolicyId)).toString()}) escalation rule **${(_1 = escalationRule.onCallDutyPolicyEscalationRule) === null || _1 === void 0 ? void 0 : _1.name}** with order **${(_2 = escalationRule.onCallDutyPolicyEscalationRule) === null || _2 === void 0 ? void 0 : _2.order}**. The on-call roster on schedule **${onCallSchedule.name}** will start when the next handoff happens which is at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
496
+ })}** is next on call for [On-Call Policy ${(_4 = escalationRule.onCallDutyPolicy) === null || _4 === void 0 ? void 0 : _4.name}](${(await OnCallDutyPolicyService.getOnCallDutyPolicyLinkInDashboard(projectId, onCallDutyPolicyId)).toString()}) escalation rule **${(_5 = escalationRule.onCallDutyPolicyEscalationRule) === null || _5 === void 0 ? void 0 : _5.name}** with order **${(_6 = escalationRule.onCallDutyPolicyEscalationRule) === null || _6 === void 0 ? void 0 : _6.order}**. The on-call roster on schedule **${onCallSchedule.name}** will start when the next handoff happens which is at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
473
497
  date: newInformation.nextRosterStartAt,
474
498
  timezones: userTimezone ? [userTimezone] : [Timezone.GMT],
475
499
  })}** and will end at **${OneUptimeDate.getDateAsFormattedStringInMultipleTimezones({
@@ -487,6 +511,50 @@ export class Service extends DatabaseService {
487
511
  }
488
512
  }
489
513
  async refreshCurrentUserIdAndHandoffTimeInSchedule(scheduleId) {
514
+ /*
515
+ * Serialize per-schedule refreshes across processes (audit L2). This method
516
+ * reads the persisted roster, diffs it, SENDS the handoff notification
517
+ * bundle + feed items, and only THEN persists — notifying before persisting
518
+ * is deliberate (audit F11). Without a lock the override-triggered refresh
519
+ * (API process) and the EVERY_MINUTE RefreshHandoffTime cron (Workers
520
+ * process) can both read the same stale roster and both send the full page
521
+ * before either persists, double-paging the incoming user. A blocking
522
+ * per-schedule mutex makes the second caller wait, then read the
523
+ * already-persisted new roster and diff to "no change" -> no duplicate page.
524
+ * Best-effort: if the lock cannot be acquired (Redis unavailable, or the
525
+ * acquire window is exceeded) we proceed unlocked — no worse than before.
526
+ */
527
+ let mutex = null;
528
+ try {
529
+ mutex = await Semaphore.lock({
530
+ key: scheduleId.toString(),
531
+ namespace: "OnCallDutyPolicyScheduleService.refreshRoster",
532
+ lockTimeout: 60000,
533
+ acquireTimeout: 15000,
534
+ retryInterval: 200,
535
+ acquireAttemptsLimit: 100,
536
+ });
537
+ }
538
+ catch (err) {
539
+ logger.debug("Proceeding with roster refresh without a per-schedule lock (could not acquire) for scheduleId: " +
540
+ scheduleId.toString());
541
+ logger.debug(err);
542
+ }
543
+ try {
544
+ return await this.refreshCurrentUserIdAndHandoffTimeInScheduleInternal(scheduleId);
545
+ }
546
+ finally {
547
+ if (mutex) {
548
+ try {
549
+ await Semaphore.release(mutex);
550
+ }
551
+ catch (err) {
552
+ logger.error(err);
553
+ }
554
+ }
555
+ }
556
+ }
557
+ async refreshCurrentUserIdAndHandoffTimeInScheduleInternal(scheduleId) {
490
558
  logger.debug("refreshCurrentUserIdAndHandoffTimeInSchedule called with scheduleId: " +
491
559
  scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
492
560
  // get previoius result.
@@ -525,10 +593,51 @@ export class Service extends DatabaseService {
525
593
  });
526
594
  logger.debug("Fetching new schedule information for scheduleId: " +
527
595
  scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
528
- const newInformation = await this.getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId);
596
+ /*
597
+ * When this schedule is attached to exactly one on-call policy, resolve the
598
+ * roster in that policy's context so its policy-scoped overrides are
599
+ * reflected in the persisted roster, handoff notifications and time logs —
600
+ * matching the live paging path, which already passes the policy id (audit
601
+ * F10). Attached to zero or multiple policies, the roster stays
602
+ * policy-agnostic (global overrides only), because a single schedule-level
603
+ * roster cannot represent divergent per-policy overrides.
604
+ */
605
+ const singleAttachedPolicyId = await this.getSingleAttachedPolicyId(scheduleId);
606
+ const newInformation = await this.getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId, {
607
+ onCallDutyPolicyId: singleAttachedPolicyId,
608
+ });
529
609
  logger.debug(newInformation, {
530
610
  onCallDutyPolicyScheduleId: scheduleId.toString(),
531
611
  });
612
+ logger.debug("Updating schedule with new information for scheduleId: " +
613
+ scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
614
+ logger.debug("Sending notifications for schedule handoff for scheduleId: " +
615
+ scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
616
+ /*
617
+ * Send the handoff notification BEFORE persisting the new roster. The
618
+ * decision to notify is a diff of the persisted-previous vs new roster; if
619
+ * we persisted first and the notification then threw (a transient DB error
620
+ * in the notification-settings / timezone lookup, or a partial send), the
621
+ * row would already show the new user, so the next tick's diff would show
622
+ * "no change" and the newly-on-call user would NEVER be told they are on
623
+ * call — the handoff page would be silently and permanently lost (audit
624
+ * F11). By notifying first, a failed send leaves the roster un-advanced, so
625
+ * the schedule is re-selected next tick and the notification is retried
626
+ * (at worst a duplicate send, which is far better than a missed page for an
627
+ * on-call system).
628
+ */
629
+ await this.sendNotificationToUserOnScheduleHandoff({
630
+ scheduleId: scheduleId,
631
+ previousInformation: previousInformation,
632
+ newInformation: {
633
+ currentUserIdOnRoster: newInformation.currentUserId,
634
+ rosterHandoffAt: newInformation.handOffTimeAt,
635
+ nextUserIdOnRoster: newInformation.nextUserId,
636
+ nextHandOffTimeAt: newInformation.nextHandOffTimeAt,
637
+ rosterStartAt: newInformation.rosterStartAt,
638
+ nextRosterStartAt: newInformation.nextRosterStartAt,
639
+ },
640
+ });
532
641
  logger.debug("Updating schedule with new information for scheduleId: " +
533
642
  scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
534
643
  await this.updateOneById({
@@ -546,26 +655,11 @@ export class Service extends DatabaseService {
546
655
  ignoreHooks: true,
547
656
  },
548
657
  });
549
- logger.debug("Sending notifications for schedule handoff for scheduleId: " +
550
- scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
551
- // send notification to the users.
552
- await this.sendNotificationToUserOnScheduleHandoff({
553
- scheduleId: scheduleId,
554
- previousInformation: previousInformation,
555
- newInformation: {
556
- currentUserIdOnRoster: newInformation.currentUserId,
557
- rosterHandoffAt: newInformation.handOffTimeAt,
558
- nextUserIdOnRoster: newInformation.nextUserId,
559
- nextHandOffTimeAt: newInformation.nextHandOffTimeAt,
560
- rosterStartAt: newInformation.rosterStartAt,
561
- nextRosterStartAt: newInformation.nextRosterStartAt,
562
- },
563
- });
564
658
  logger.debug("Returning new schedule information for scheduleId: " +
565
659
  scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
566
660
  return newInformation;
567
661
  }
568
- async getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId) {
662
+ async getCurrrentUserIdAndHandoffTimeInSchedule(scheduleId, options) {
569
663
  logger.debug("getCurrrentUserIdAndHandoffTimeInSchedule called with scheduleId: " +
570
664
  scheduleId.toString(), { onCallDutyPolicyScheduleId: scheduleId.toString() });
571
665
  const resultReturn = {
@@ -582,6 +676,7 @@ export class Service extends DatabaseService {
582
676
  const events = await this.getEventByIndexInSchedule({
583
677
  scheduleId: scheduleId,
584
678
  getNumberOfEvents: 2,
679
+ onCallDutyPolicyId: options === null || options === void 0 ? void 0 : options.onCallDutyPolicyId,
585
680
  });
586
681
  logger.debug("Events fetched: " + JSON.stringify(events), {
587
682
  onCallDutyPolicyScheduleId: scheduleId.toString(),
@@ -620,10 +715,28 @@ export class Service extends DatabaseService {
620
715
  // get start time
621
716
  const startTime = currentEvent === null || currentEvent === void 0 ? void 0 : currentEvent.start; // this is user id in string.
622
717
  if (startTime) {
623
- logger.debug("Current rosterStartAt: " + startTime.toISOString(), {
718
+ logger.debug("Current rosterStartAt (clamped): " + startTime.toISOString(), {
624
719
  onCallDutyPolicyScheduleId: scheduleId.toString(),
625
720
  });
626
- resultReturn.rosterStartAt = startTime;
721
+ /*
722
+ * currentEvent.start is clamped to the resolution window start (now).
723
+ * Recover the true start of the current coverage window so the
724
+ * persisted/displayed "on call since" reflects when the shift actually
725
+ * began, not ~now (audit F12). Best-effort with a fallback to the
726
+ * clamped value.
727
+ */
728
+ if (resultReturn.currentUserId) {
729
+ resultReturn.rosterStartAt = await this.getTrueRosterStartAt({
730
+ scheduleId: scheduleId,
731
+ currentUserId: resultReturn.currentUserId,
732
+ now: OneUptimeDate.getCurrentDate(),
733
+ onCallDutyPolicyId: options === null || options === void 0 ? void 0 : options.onCallDutyPolicyId,
734
+ fallbackStart: startTime,
735
+ });
736
+ }
737
+ else {
738
+ resultReturn.rosterStartAt = startTime;
739
+ }
627
740
  }
628
741
  }
629
742
  // do the same for next event.
@@ -660,9 +773,85 @@ export class Service extends DatabaseService {
660
773
  });
661
774
  return resultReturn;
662
775
  }
776
+ /*
777
+ * Returns the on-call policy this schedule is attached to IFF it is attached
778
+ * to exactly one; otherwise undefined (audit F10). A lazy require avoids a
779
+ * circular import — OnCallDutyPolicyEscalationRuleScheduleService imports this
780
+ * service.
781
+ */
782
+ async getSingleAttachedPolicyId(scheduleId) {
783
+ var _a;
784
+ try {
785
+ const escalationRuleScheduleService = require("./OnCallDutyPolicyEscalationRuleScheduleService").default;
786
+ const links = await escalationRuleScheduleService.findBy({
787
+ query: { onCallDutyPolicyScheduleId: scheduleId },
788
+ select: { onCallDutyPolicyId: true },
789
+ limit: LIMIT_PER_PROJECT,
790
+ skip: 0,
791
+ props: { isRoot: true },
792
+ });
793
+ const distinctPolicyIds = new Set();
794
+ for (const link of links) {
795
+ const policyId = (_a = link.onCallDutyPolicyId) === null || _a === void 0 ? void 0 : _a.toString();
796
+ if (policyId) {
797
+ distinctPolicyIds.add(policyId);
798
+ }
799
+ }
800
+ if (distinctPolicyIds.size === 1) {
801
+ return new ObjectID(Array.from(distinctPolicyIds)[0]);
802
+ }
803
+ }
804
+ catch (err) {
805
+ logger.error(err);
806
+ }
807
+ return undefined;
808
+ }
809
+ /**
810
+ * Re-resolve and persist the roster for every schedule in the project whose
811
+ * layers include `userId`. Called when a user override is created / updated /
812
+ * deleted so the persisted roster, handoff notifications and on-call time logs
813
+ * reflect the override mid-period instead of waiting for the next natural
814
+ * handoff or the per-minute cron (which does not re-select an established
815
+ * roster) — audit F4. Best-effort and idempotent: refreshing an unaffected
816
+ * schedule simply recomputes the same roster; per-schedule errors are logged
817
+ * and never abort the rest.
818
+ */
819
+ async refreshRostersForUserInProject(data) {
820
+ var _a;
821
+ const layerUsers = await OnCallDutyPolicyScheduleLayerUserService.findBy({
822
+ query: {
823
+ projectId: data.projectId,
824
+ userId: data.userId,
825
+ },
826
+ select: {
827
+ onCallDutyPolicyScheduleId: true,
828
+ },
829
+ limit: LIMIT_PER_PROJECT,
830
+ skip: 0,
831
+ props: {
832
+ isRoot: true,
833
+ },
834
+ });
835
+ const scheduleIds = new Set();
836
+ for (const layerUser of layerUsers) {
837
+ const scheduleId = (_a = layerUser.onCallDutyPolicyScheduleId) === null || _a === void 0 ? void 0 : _a.toString();
838
+ if (scheduleId) {
839
+ scheduleIds.add(scheduleId);
840
+ }
841
+ }
842
+ for (const scheduleId of scheduleIds) {
843
+ try {
844
+ await this.refreshCurrentUserIdAndHandoffTimeInSchedule(new ObjectID(scheduleId));
845
+ }
846
+ catch (err) {
847
+ logger.error(`Error refreshing roster for schedule ${scheduleId} after a user override change.`);
848
+ logger.error(err);
849
+ }
850
+ }
851
+ }
663
852
  async getScheduleLayerProps(data) {
664
853
  // get schedule layers.
665
- var _a, _b;
854
+ var _a, _b, _c;
666
855
  const scheduleId = data.scheduleId;
667
856
  const layers = await OnCallDutyPolicyScheduleLayerService.findBy({
668
857
  query: {
@@ -706,6 +895,21 @@ export class Service extends DatabaseService {
706
895
  isRoot: true,
707
896
  },
708
897
  });
898
+ /*
899
+ * The schedule's timezone (if set) is applied to every layer so restriction
900
+ * wall-clock times resolve in that zone. When null (existing schedules), the
901
+ * layer engine falls back to server-local time — unchanged legacy behavior.
902
+ */
903
+ const schedule = await this.findOneById({
904
+ id: scheduleId,
905
+ select: {
906
+ timezone: true,
907
+ },
908
+ props: {
909
+ isRoot: true,
910
+ },
911
+ });
912
+ const scheduleTimezone = ((_a = schedule === null || schedule === void 0 ? void 0 : schedule.timezone) === null || _a === void 0 ? void 0 : _a.toString()) || undefined;
709
913
  const layerProps = [];
710
914
  const scheduleUserIds = [];
711
915
  const seenUserIds = new Set();
@@ -723,7 +927,7 @@ export class Service extends DatabaseService {
723
927
  return Boolean(user);
724
928
  });
725
929
  for (const user of usersForLayer) {
726
- const idStr = ((_a = user.id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
930
+ const idStr = ((_b = user.id) === null || _b === void 0 ? void 0 : _b.toString()) || "";
727
931
  if (idStr && !seenUserIds.has(idStr)) {
728
932
  seenUserIds.add(idStr);
729
933
  scheduleUserIds.push(user.id);
@@ -735,9 +939,10 @@ export class Service extends DatabaseService {
735
939
  restrictionTimes: layer.restrictionTimes,
736
940
  rotation: layer.rotation,
737
941
  handOffTime: layer.handOffTime,
942
+ timezone: scheduleTimezone,
738
943
  });
739
944
  }
740
- const projectId = ((_b = layers[0]) === null || _b === void 0 ? void 0 : _b.projectId) || null;
945
+ const projectId = ((_c = layers[0]) === null || _c === void 0 ? void 0 : _c.projectId) || null;
741
946
  return { layerProps, projectId, scheduleUserIds };
742
947
  }
743
948
  async fetchOverridesForSchedule(data) {
@@ -796,6 +1001,166 @@ export class Service extends DatabaseService {
796
1001
  };
797
1002
  });
798
1003
  }
1004
+ /*
1005
+ * Compute how far out to resolve calendar events. Returns at least 1 year
1006
+ * from `from`, but extends far enough to contain (getNumberOfEvents + 1)
1007
+ * rotation periods for the slowest layer, so multi-year rotations still yield
1008
+ * a current and next event.
1009
+ */
1010
+ computeResolutionWindowEnd(layerProps, from, getNumberOfEvents) {
1011
+ const periodsNeeded = Math.max(2, getNumberOfEvents + 1);
1012
+ let windowEnd = from;
1013
+ let anyLayerRestricted = false;
1014
+ for (const layer of layerProps) {
1015
+ if (this.isLayerRestricted(layer)) {
1016
+ anyLayerRestricted = true;
1017
+ }
1018
+ if (!layer.rotation) {
1019
+ continue;
1020
+ }
1021
+ let recurring;
1022
+ try {
1023
+ recurring =
1024
+ layer.rotation instanceof Recurring
1025
+ ? layer.rotation
1026
+ : Recurring.fromJSON(layer.rotation);
1027
+ }
1028
+ catch (_a) {
1029
+ continue;
1030
+ }
1031
+ let candidate = from;
1032
+ for (let i = 0; i < periodsNeeded; i++) {
1033
+ candidate = Recurring.getNextDateInterval(candidate, recurring);
1034
+ }
1035
+ if (OneUptimeDate.isAfter(candidate, windowEnd)) {
1036
+ windowEnd = candidate;
1037
+ }
1038
+ }
1039
+ /*
1040
+ * Floor the window. Restricted layers can have long coverage gaps (a
1041
+ * weekend-only or business-hours restriction), so the next COVERED event may
1042
+ * be far past the naive (getNumberOfEvents + 1)-period horizon — keep a
1043
+ * generous 1-year floor for them so a current/next user is still found.
1044
+ * UNRESTRICTED layers produce exactly one event per rotation period, so the
1045
+ * per-layer candidate above already spans the events we need; flooring those
1046
+ * at a full year forced a fast (hourly/daily) rotation to materialize
1047
+ * thousands of events on every resolution, and getMultiLayerEvents then ran
1048
+ * an O(n^2) overlap merge over them — a real worker stall (audit H2). For
1049
+ * unrestricted layers a small 1-day floor is ample margin.
1050
+ */
1051
+ const floor = anyLayerRestricted
1052
+ ? OneUptimeDate.addRemoveYears(from, 1)
1053
+ : OneUptimeDate.addRemoveDays(from, 1);
1054
+ if (OneUptimeDate.isBefore(windowEnd, floor)) {
1055
+ windowEnd = floor;
1056
+ }
1057
+ return windowEnd;
1058
+ }
1059
+ /*
1060
+ * True when the layer carries an active (non-None) restriction. Handles both a
1061
+ * hydrated RestrictionTimes instance (exposes `restictionType`) and the raw
1062
+ * serialized JSON form ({ _type, value: { restictionType } }) so it is safe
1063
+ * regardless of where the LayerProps came from.
1064
+ */
1065
+ isLayerRestricted(layer) {
1066
+ var _a;
1067
+ const rt = layer.restrictionTimes;
1068
+ if (!rt) {
1069
+ return false;
1070
+ }
1071
+ const anyRt = rt;
1072
+ const type = anyRt.restictionType || ((_a = anyRt.value) === null || _a === void 0 ? void 0 : _a.restictionType);
1073
+ return Boolean(type) && type !== RestrictionType.None;
1074
+ }
1075
+ /*
1076
+ * Mirror of computeResolutionWindowEnd, stepping BACKWARDS: the earliest of
1077
+ * `from` minus `periodsBack` rotation periods across the layers (at least a
1078
+ * day). Used to recover the true, un-clamped start of the current coverage
1079
+ * window for rosterStartAt (audit F12).
1080
+ */
1081
+ computeResolutionWindowStart(layerProps, from, periodsBack) {
1082
+ let windowStart = OneUptimeDate.addRemoveDays(from, -1);
1083
+ for (const layer of layerProps) {
1084
+ if (!layer.rotation) {
1085
+ continue;
1086
+ }
1087
+ let recurring;
1088
+ try {
1089
+ recurring =
1090
+ layer.rotation instanceof Recurring
1091
+ ? layer.rotation
1092
+ : Recurring.fromJSON(layer.rotation);
1093
+ }
1094
+ catch (_a) {
1095
+ continue;
1096
+ }
1097
+ let candidate = from;
1098
+ for (let i = 0; i < periodsBack; i++) {
1099
+ candidate = Recurring.getNextDateInterval(candidate, recurring, true);
1100
+ }
1101
+ if (OneUptimeDate.isBefore(candidate, windowStart)) {
1102
+ windowStart = candidate;
1103
+ }
1104
+ }
1105
+ return windowStart;
1106
+ }
1107
+ /*
1108
+ * Recover the TRUE start of the current on-call coverage window for
1109
+ * `currentUserId`. getEventByIndexInSchedule always expands from `now`, and
1110
+ * LayerUtil clamps the first event's start to the window start, so the current
1111
+ * event's start reads ~now rather than when the shift actually began. We
1112
+ * re-expand over a bounded lookback window and return the start of the segment
1113
+ * that covers `now` for the current user. Best-effort: any failure (or no
1114
+ * covering segment found) falls back to the clamped value, so the who-is-paged
1115
+ * path is never affected (audit F12).
1116
+ */
1117
+ async getTrueRosterStartAt(data) {
1118
+ var _a;
1119
+ try {
1120
+ const { layerProps, projectId, scheduleUserIds } = await this.getScheduleLayerProps({ scheduleId: data.scheduleId });
1121
+ if (layerProps.length === 0) {
1122
+ return data.fallbackStart;
1123
+ }
1124
+ const windowStart = this.computeResolutionWindowStart(layerProps, data.now, 2);
1125
+ const windowEnd = OneUptimeDate.addRemoveSeconds(data.now, 1);
1126
+ let events = this.layerUtil.getMultiLayerEvents({
1127
+ layers: layerProps,
1128
+ calendarStartDate: windowStart,
1129
+ calendarEndDate: windowEnd,
1130
+ });
1131
+ if (projectId && events.length > 0) {
1132
+ const overrides = await this.fetchOverridesForSchedule({
1133
+ projectId,
1134
+ scheduleUserIds,
1135
+ windowStart,
1136
+ windowEnd,
1137
+ currentOnCallDutyPolicyId: data.onCallDutyPolicyId,
1138
+ });
1139
+ if (overrides.length > 0) {
1140
+ events = UserOverrideUtil.applyOverridesToEvents({
1141
+ events,
1142
+ overrides,
1143
+ currentOnCallDutyPolicyId: (_a = data.onCallDutyPolicyId) === null || _a === void 0 ? void 0 : _a.toString(),
1144
+ });
1145
+ }
1146
+ }
1147
+ const currentUserIdStr = data.currentUserId.toString();
1148
+ for (const event of events) {
1149
+ if (!event) {
1150
+ continue;
1151
+ }
1152
+ if (event.title === currentUserIdStr &&
1153
+ OneUptimeDate.isOnOrBefore(event.start, data.now) &&
1154
+ OneUptimeDate.isOnOrAfter(event.end, data.now)) {
1155
+ return event.start;
1156
+ }
1157
+ }
1158
+ }
1159
+ catch (err) {
1160
+ logger.error(err);
1161
+ }
1162
+ return data.fallbackStart;
1163
+ }
799
1164
  async getEventByIndexInSchedule(data) {
800
1165
  var _a;
801
1166
  logger.debug("getEventByIndexInSchedule called with data: " + JSON.stringify(data), {
@@ -817,7 +1182,15 @@ export class Service extends DatabaseService {
817
1182
  logger.debug("Current start time: " + currentStartTime.toISOString(), {
818
1183
  onCallDutyPolicyScheduleId: data.scheduleId.toString(),
819
1184
  });
820
- const currentEndTime = OneUptimeDate.addRemoveYears(currentStartTime, 1);
1185
+ /*
1186
+ * Size the resolution window from the layers' rotations rather than a fixed
1187
+ * 1 year. A rotation period longer than a year (e.g. a 3-year rotation)
1188
+ * would otherwise clamp the current event's end to now+1yr and report a
1189
+ * bogus handoff time with no "next" user. The window is at least 1 year and
1190
+ * large enough to contain the requested number of events for the slowest
1191
+ * layer.
1192
+ */
1193
+ const currentEndTime = this.computeResolutionWindowEnd(layerProps, currentStartTime, data.getNumberOfEvents);
821
1194
  logger.debug("Current end time: " + currentEndTime.toISOString(), {
822
1195
  onCallDutyPolicyScheduleId: data.scheduleId.toString(),
823
1196
  });