@oneuptime/common 11.3.26 → 11.3.28

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 (241) hide show
  1. package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
  2. package/Models/DatabaseModels/IncidentMember.ts +1 -1
  3. package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
  4. package/Models/DatabaseModels/Index.ts +3 -0
  5. package/Models/DatabaseModels/MigrationFailure.ts +170 -0
  6. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
  7. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
  8. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
  9. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
  10. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
  11. package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
  12. package/Server/API/AlertAPI.ts +1 -1
  13. package/Server/API/IncidentAPI.ts +2 -2
  14. package/Server/API/IncidentEpisodeAPI.ts +1 -1
  15. package/Server/API/LlmProviderAPI.ts +1 -0
  16. package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  22. package/Server/Infrastructure/PostgresDatabase.ts +11 -0
  23. package/Server/Services/AIService.ts +74 -2
  24. package/Server/Services/AnalyticsDatabaseService.ts +10 -1
  25. package/Server/Services/DatabaseService.ts +10 -1
  26. package/Server/Services/IncidentService.ts +1 -1
  27. package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
  28. package/Server/Services/IncomingCallPolicyService.ts +42 -1
  29. package/Server/Services/Index.ts +2 -0
  30. package/Server/Services/MigrationFailureService.ts +9 -0
  31. package/Server/Services/MonitorProbeService.ts +33 -7
  32. package/Server/Services/MonitorService.ts +19 -7
  33. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  34. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
  35. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
  36. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
  37. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
  38. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
  39. package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
  40. package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
  41. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
  42. package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
  43. package/Server/Services/TeamMemberService.ts +6 -0
  44. package/Server/Services/UserNotificationRuleService.ts +13 -42
  45. package/Server/Services/UserOnCallLogService.ts +90 -0
  46. package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
  47. package/Server/Types/Database/QueryUtil.ts +57 -0
  48. package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
  49. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
  50. package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
  51. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
  52. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
  53. package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
  54. package/Server/Utils/AI/Toolbox/Index.ts +4 -1
  55. package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
  56. package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
  57. package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
  58. package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
  59. package/Server/Utils/LLM/LLMService.ts +110 -11
  60. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
  61. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
  62. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
  63. package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
  64. package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
  65. package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
  66. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
  67. package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
  68. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
  69. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
  70. package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
  71. package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
  72. package/Tests/Types/Events/Recurring.test.ts +22 -9
  73. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
  74. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
  75. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
  76. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
  77. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
  78. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
  79. package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
  80. package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
  81. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
  82. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
  83. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
  84. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
  85. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
  86. package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
  87. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
  88. package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
  89. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
  90. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
  91. package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
  92. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
  93. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
  94. package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
  95. package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
  96. package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
  97. package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
  98. package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
  99. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
  100. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
  101. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
  102. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
  103. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
  104. package/Types/Date.ts +200 -15
  105. package/Types/OnCallDutyPolicy/Layer.ts +790 -260
  106. package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
  107. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
  108. package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
  109. package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
  110. package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
  111. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
  112. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
  113. package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
  114. package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
  115. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
  116. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  118. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
  120. package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
  121. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
  122. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  123. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
  124. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  125. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
  126. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  127. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
  128. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
  129. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
  130. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  131. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
  132. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
  133. package/build/dist/Server/API/AlertAPI.js +1 -1
  134. package/build/dist/Server/API/IncidentAPI.js +2 -2
  135. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  136. package/build/dist/Server/API/LlmProviderAPI.js +1 -0
  137. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  138. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  139. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
  140. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
  142. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
  143. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
  144. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  149. package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
  150. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  151. package/build/dist/Server/Services/AIService.js +53 -2
  152. package/build/dist/Server/Services/AIService.js.map +1 -1
  153. package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
  154. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  155. package/build/dist/Server/Services/DatabaseService.js +10 -1
  156. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  157. package/build/dist/Server/Services/IncidentService.js +1 -1
  158. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
  159. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
  160. package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
  161. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  162. package/build/dist/Server/Services/Index.js +2 -0
  163. package/build/dist/Server/Services/Index.js.map +1 -1
  164. package/build/dist/Server/Services/MigrationFailureService.js +9 -0
  165. package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
  166. package/build/dist/Server/Services/MonitorProbeService.js +33 -7
  167. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  168. package/build/dist/Server/Services/MonitorService.js +12 -5
  169. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  170. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
  171. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  172. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
  173. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  174. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
  175. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  176. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
  177. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  178. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
  179. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  180. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
  181. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  182. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
  183. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  184. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
  185. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
  186. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
  187. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  188. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
  189. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  190. package/build/dist/Server/Services/TeamMemberService.js +6 -0
  191. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  192. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
  193. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  194. package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
  195. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  196. package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
  197. package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
  198. package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
  199. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  200. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
  201. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  202. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
  203. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  204. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
  205. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  206. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
  207. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
  208. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
  209. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
  210. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
  211. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  212. package/build/dist/Server/Utils/AI/Toolbox/Index.js +4 -1
  213. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  214. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
  215. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
  216. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
  217. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  218. package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
  219. package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
  220. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
  221. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
  222. package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
  223. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  224. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
  225. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  226. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
  227. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  228. package/build/dist/Types/Date.js +153 -15
  229. package/build/dist/Types/Date.js.map +1 -1
  230. package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
  231. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  232. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
  233. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
  234. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
  235. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  236. package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
  237. package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
  238. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  239. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
  240. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
  241. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import logger from "../Utils/Logger";
2
2
  import DatabaseDataSourceOptions from "./Postgres/DataSourceOptions";
3
+ import { recordSchemaMigrationFailureBestEffort } from "../Utils/Database/MigrationFailureLog";
3
4
  import Sleep from "../../Types/Sleep";
4
5
  import { DataSource, DataSourceOptions, QueryRunner } from "typeorm";
5
6
  import { createDatabase, dropDatabase } from "typeorm-extension";
@@ -95,6 +96,16 @@ export default class Database {
95
96
  } catch (err) {
96
97
  logger.error("Postgres Database Connection Failed");
97
98
  logger.error(err);
99
+
100
+ /*
101
+ * When this process runs schema migrations on boot (migrationsRun=true),
102
+ * connect() also fails if a migration threw. Record which migration
103
+ * failed and why — on a throwaway connection, since the DataSource above
104
+ * is unusable — so the admin health page can explain the pending schema.
105
+ * Best-effort and self-contained: it never throws and never masks `err`.
106
+ */
107
+ await recordSchemaMigrationFailureBestEffort(dataSourceOptions, err);
108
+
98
109
  throw err;
99
110
  }
100
111
  }
@@ -11,7 +11,10 @@ import LLMService, {
11
11
  LLMMessage,
12
12
  LLMToolCall,
13
13
  LLMToolDefinition,
14
+ LLMUsage,
14
15
  } from "../Utils/LLM/LLMService";
16
+ import LlmType from "../../Types/LLM/LlmType";
17
+ import { Span, trace } from "@opentelemetry/api";
15
18
  import LlmProvider from "../../Models/DatabaseModels/LlmProvider";
16
19
  import LlmLog from "../../Models/DatabaseModels/LlmLog";
17
20
  import LlmLogStatus from "../../Types/LlmLogStatus";
@@ -129,9 +132,17 @@ export class Service extends BaseService {
129
132
  logEntry.aiRunId = request.aiRunId;
130
133
  }
131
134
 
132
- // Check if billing should apply
135
+ /*
136
+ * Check if billing should apply. Only bill for the global (OneUptime-hosted)
137
+ * provider, and only when it actually has a per-token cost. A free global
138
+ * provider (costPerMillionTokensInUSDCents = 0, the default) consumes no
139
+ * balance, so it must not require or block on one either — otherwise a $0
140
+ * provider would still fail with "Insufficient AI balance".
141
+ */
133
142
  const shouldBill: boolean =
134
- IsBillingEnabled && (llmProvider.isGlobalLlm || false);
143
+ IsBillingEnabled &&
144
+ (llmProvider.isGlobalLlm || false) &&
145
+ (llmProvider.costPerMillionTokensInUSDCents || 0) > 0;
135
146
 
136
147
  // Check balance if billing enabled and using global provider
137
148
  if (shouldBill) {
@@ -234,6 +245,18 @@ export class Service extends BaseService {
234
245
  }
235
246
  }
236
247
 
248
+ /*
249
+ * Emit gen_ai.* semantic-convention attributes on the active span so
250
+ * OneUptime's own AI usage is a first-class LLM span in OneUptime's own
251
+ * telemetry (dogfooding — LlmSpanUtil detects these). Never fails the call.
252
+ */
253
+ this.setGenAiSpanAttributes({
254
+ llmType: llmProvider.llmType,
255
+ modelName: llmConfig.modelName,
256
+ usage: response.usage,
257
+ costInUSDCents: logEntry.costInUSDCents,
258
+ });
259
+
237
260
  // Save log entry
238
261
  const savedLog: LlmLog = await LlmLogService.create({
239
262
  data: logEntry,
@@ -261,6 +284,55 @@ export class Service extends BaseService {
261
284
  throw error;
262
285
  }
263
286
  }
287
+
288
+ /*
289
+ * Set gen_ai.* attributes (OpenTelemetry GenAI semantic conventions) on the
290
+ * currently-active span. The @CaptureSpan()-wrapped caller owns that span, so
291
+ * LlmSpanUtil recognizes these calls as first-class LLM spans.
292
+ */
293
+ private setGenAiSpanAttributes(data: {
294
+ llmType: LlmType;
295
+ modelName?: string | undefined;
296
+ usage?: LLMUsage | undefined;
297
+ costInUSDCents?: number | undefined;
298
+ }): void {
299
+ try {
300
+ const span: Span | undefined = trace.getActiveSpan();
301
+ if (!span) {
302
+ return;
303
+ }
304
+
305
+ span.setAttribute("gen_ai.system", data.llmType.toString());
306
+ span.setAttribute("gen_ai.provider.name", data.llmType.toString());
307
+ span.setAttribute("gen_ai.operation.name", "chat");
308
+
309
+ if (data.modelName) {
310
+ span.setAttribute("gen_ai.request.model", data.modelName);
311
+ span.setAttribute("gen_ai.response.model", data.modelName);
312
+ }
313
+
314
+ if (data.usage) {
315
+ span.setAttribute(
316
+ "gen_ai.usage.input_tokens",
317
+ data.usage.promptTokens || 0,
318
+ );
319
+ span.setAttribute(
320
+ "gen_ai.usage.output_tokens",
321
+ data.usage.completionTokens || 0,
322
+ );
323
+ span.setAttribute(
324
+ "gen_ai.usage.total_tokens",
325
+ data.usage.totalTokens || 0,
326
+ );
327
+ }
328
+
329
+ if (data.costInUSDCents) {
330
+ span.setAttribute("gen_ai.usage.cost_usd", data.costInUSDCents / 100);
331
+ }
332
+ } catch {
333
+ // Telemetry must never fail the LLM call.
334
+ }
335
+ }
264
336
  }
265
337
 
266
338
  export default new Service();
@@ -1899,7 +1899,16 @@ export default class AnalyticsDatabaseService<
1899
1899
  return data;
1900
1900
  }
1901
1901
 
1902
- protected async getException(error: Exception): Promise<void> {
1902
+ /*
1903
+ * Rethrow hook for the catch blocks below. MUST stay synchronous and
1904
+ * `never`-returning: the call sites use `throw this.getException(error)`,
1905
+ * so if this were `async` it would return a Promise that `throw` then
1906
+ * throws verbatim (never awaited) — the real exception is lost, callers up
1907
+ * the stack catch a bare Promise (surfacing as "[object Promise]"), and the
1908
+ * un-awaited rejection becomes an unhandled rejection. Throwing directly
1909
+ * propagates the original exception on the synchronous throw path.
1910
+ */
1911
+ protected getException(error: Exception): never {
1903
1912
  throw error;
1904
1913
  }
1905
1914
 
@@ -443,7 +443,16 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
443
443
  return Promise.resolve(error);
444
444
  }
445
445
 
446
- protected async getException(error: Exception): Promise<void> {
446
+ /*
447
+ * Rethrow hook for the catch blocks below. MUST stay synchronous and
448
+ * `never`-returning: the call sites use `throw this.getException(error)`,
449
+ * so if this were `async` it would return a Promise that `throw` then
450
+ * throws verbatim (never awaited) — the real exception is lost, callers up
451
+ * the stack catch a bare Promise (surfacing as "[object Promise]"), and the
452
+ * un-awaited rejection becomes an unhandled rejection. Throwing directly
453
+ * propagates the original exception on the synchronous throw path.
454
+ */
455
+ protected getException(error: Exception): never {
447
456
  throw error;
448
457
  }
449
458
 
@@ -3479,7 +3479,7 @@ ${incidentSeverity.name}
3479
3479
  const response: LLMCompletionResponse = await LLMService.getCompletion({
3480
3480
  llmProviderConfig: llmConfig,
3481
3481
  messages: aiContext.messages,
3482
- temperature: 0.7,
3482
+ temperature: 0.2,
3483
3483
  });
3484
3484
 
3485
3485
  return response.content;
@@ -135,6 +135,79 @@ export class Service extends DatabaseService<IncomingCallPolicyEscalationRule> {
135
135
  protected override async onBeforeUpdate(
136
136
  updateBy: UpdateBy<IncomingCallPolicyEscalationRule>,
137
137
  ): Promise<OnUpdate<IncomingCallPolicyEscalationRule>> {
138
+ /*
139
+ * Enforce user/schedule mutual exclusivity on update (parity with onBeforeCreate).
140
+ * Only runs when the update actually touches one of the routing-target fields, so
141
+ * internal updates (status/order via isRoot) are unaffected.
142
+ */
143
+ const data: UpdateBy<IncomingCallPolicyEscalationRule>["data"] =
144
+ updateBy.data;
145
+ const isTouchingTarget: boolean =
146
+ data.userId !== undefined ||
147
+ data.onCallDutyPolicyScheduleId !== undefined;
148
+
149
+ if (isTouchingTarget && updateBy.query._id) {
150
+ const settingUser: boolean = Boolean(data.userId);
151
+ const settingSchedule: boolean = Boolean(data.onCallDutyPolicyScheduleId);
152
+
153
+ if (settingUser && settingSchedule) {
154
+ throw new BadDataException(
155
+ "Only one of User or On-Call Schedule can be specified, not both",
156
+ );
157
+ }
158
+
159
+ // Setting one target clears the other so a rule can never hold both.
160
+ const nullableData: {
161
+ userId?: ObjectID | null;
162
+ onCallDutyPolicyScheduleId?: ObjectID | null;
163
+ } = data as {
164
+ userId?: ObjectID | null;
165
+ onCallDutyPolicyScheduleId?: ObjectID | null;
166
+ };
167
+ if (settingUser) {
168
+ nullableData.onCallDutyPolicyScheduleId = null;
169
+ }
170
+ if (settingSchedule) {
171
+ nullableData.userId = null;
172
+ }
173
+
174
+ const existing: IncomingCallPolicyEscalationRule | null =
175
+ await this.findOneBy({
176
+ query: {
177
+ _id: updateBy.query._id!,
178
+ },
179
+ select: {
180
+ userId: true,
181
+ onCallDutyPolicyScheduleId: true,
182
+ },
183
+ props: {
184
+ isRoot: true,
185
+ },
186
+ });
187
+
188
+ /*
189
+ * Whether each target will be present AFTER this update, accounting for
190
+ * fields left untouched (keep existing) and the opposite-field clearing
191
+ * done above.
192
+ */
193
+ const willHaveUser: boolean = settingUser
194
+ ? true
195
+ : data.userId === undefined
196
+ ? Boolean(existing?.userId)
197
+ : false;
198
+ const willHaveSchedule: boolean = settingSchedule
199
+ ? true
200
+ : data.onCallDutyPolicyScheduleId === undefined
201
+ ? Boolean(existing?.onCallDutyPolicyScheduleId)
202
+ : false;
203
+
204
+ if (!willHaveUser && !willHaveSchedule) {
205
+ throw new BadDataException(
206
+ "Either a User or an On-Call Schedule must be specified for the escalation rule",
207
+ );
208
+ }
209
+ }
210
+
138
211
  if (updateBy.data.order && !updateBy.props.isRoot && updateBy.query._id) {
139
212
  const resource: IncomingCallPolicyEscalationRule | null =
140
213
  await this.findOneBy({
@@ -194,8 +267,8 @@ export class Service extends DatabaseService<IncomingCallPolicyEscalationRule> {
194
267
  if (newOrder > currentOrder) {
195
268
  // moving down.
196
269
  for (const resource of resources) {
197
- if (resource.order! <= newOrder) {
198
- // increment order.
270
+ if (resource.order! > currentOrder && resource.order! <= newOrder) {
271
+ // decrement order to fill the gap left by the moved rule.
199
272
  await this.updateOneBy({
200
273
  query: {
201
274
  _id: resource._id!,
@@ -2,7 +2,10 @@ import DatabaseService from "./DatabaseService";
2
2
  import IncomingCallPolicy from "../../Models/DatabaseModels/IncomingCallPolicy";
3
3
  import IncomingCallPolicyLabelRuleEngineService from "./IncomingCallPolicyLabelRuleEngineService";
4
4
  import IncomingCallPolicyOwnerRuleEngineService from "./IncomingCallPolicyOwnerRuleEngineService";
5
- import { OnCreate } from "../Types/Database/Hooks";
5
+ import { OnCreate, OnDelete } from "../Types/Database/Hooks";
6
+ import DeleteBy from "../Types/Database/DeleteBy";
7
+ import LIMIT_MAX from "../../Types/Database/LimitMax";
8
+ import releaseIncomingCallPhoneNumber from "../Utils/IncomingCallPhoneNumber";
6
9
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
7
10
  import logger, { LogAttributes } from "../Utils/Logger";
8
11
 
@@ -11,6 +14,44 @@ export class Service extends DatabaseService<IncomingCallPolicy> {
11
14
  super(IncomingCallPolicy);
12
15
  }
13
16
 
17
+ @CaptureSpan()
18
+ protected override async onBeforeDelete(
19
+ deleteBy: DeleteBy<IncomingCallPolicy>,
20
+ ): Promise<OnDelete<IncomingCallPolicy>> {
21
+ /*
22
+ * Release any provisioned numbers before the policy rows are removed so we
23
+ * don't leave paid, orphaned numbers on the provider that can never be
24
+ * released again (the policy — and its SID — would be gone).
25
+ */
26
+ const policies: Array<IncomingCallPolicy> = await this.findBy({
27
+ query: deleteBy.query,
28
+ select: {
29
+ _id: true,
30
+ callProviderPhoneNumberId: true,
31
+ projectCallSMSConfigId: true,
32
+ },
33
+ limit: LIMIT_MAX,
34
+ skip: 0,
35
+ props: {
36
+ isRoot: true,
37
+ },
38
+ });
39
+
40
+ for (const policy of policies) {
41
+ if (policy.callProviderPhoneNumberId && policy.projectCallSMSConfigId) {
42
+ await releaseIncomingCallPhoneNumber({
43
+ projectCallSMSConfigId: policy.projectCallSMSConfigId,
44
+ callProviderPhoneNumberId: policy.callProviderPhoneNumberId,
45
+ });
46
+ }
47
+ }
48
+
49
+ return {
50
+ deleteBy,
51
+ carryForward: null,
52
+ };
53
+ }
54
+
14
55
  @CaptureSpan()
15
56
  protected override async onCreateSuccess(
16
57
  _onCreate: OnCreate<IncomingCallPolicy>,
@@ -12,6 +12,7 @@ import BillingService from "./BillingService";
12
12
  import CallLogService from "./CallLogService";
13
13
  import CallService from "./CallService";
14
14
  import DataMigrationService from "./DataMigrationService";
15
+ import MigrationFailureService from "./MigrationFailureService";
15
16
  import DomainService from "./DomainService";
16
17
  import EmailLogService from "./EmailLogService";
17
18
  import EmailVerificationTokenService from "./EmailVerificationTokenService";
@@ -273,6 +274,7 @@ const services: Array<BaseService> = [
273
274
  CallService,
274
275
 
275
276
  DataMigrationService,
277
+ MigrationFailureService,
276
278
  DomainService,
277
279
 
278
280
  EmailLogService,
@@ -0,0 +1,9 @@
1
+ import DatabaseService from "./DatabaseService";
2
+ import Model from "../../Models/DatabaseModels/MigrationFailure";
3
+
4
+ export class Service extends DatabaseService<Model> {
5
+ public constructor() {
6
+ super(Model);
7
+ }
8
+ }
9
+ export default new Service();
@@ -310,8 +310,14 @@ export class Service extends DatabaseService<MonitorProbe> {
310
310
  _onCreate: OnCreate<MonitorProbe>,
311
311
  createdItem: MonitorProbe,
312
312
  ): Promise<MonitorProbe> {
313
- if (createdItem.probeId) {
314
- await MonitorService.refreshProbeStatus(createdItem.probeId);
313
+ /*
314
+ * Refresh only the monitor this probe was just added to. Do NOT call
315
+ * refreshProbeStatus(probeId) here: a global/shared probe can be attached
316
+ * to thousands of monitors, and refreshing every one of them on a single
317
+ * create storms the database and can 500 the request.
318
+ */
319
+ if (createdItem.monitorId) {
320
+ await this.refreshMonitorStatusSafely(createdItem.monitorId);
315
321
  }
316
322
 
317
323
  return Promise.resolve(createdItem);
@@ -321,7 +327,7 @@ export class Service extends DatabaseService<MonitorProbe> {
321
327
  onUpdate: OnUpdate<MonitorProbe>,
322
328
  updatedItemIds: ObjectID[],
323
329
  ): Promise<OnUpdate<MonitorProbe>> {
324
- // if isEnabled is updated, refresh the probe status
330
+ // if isEnabled is updated, refresh the status of the affected monitors.
325
331
  if (onUpdate.updateBy.data.isEnabled !== undefined) {
326
332
  const monitorProbes: Array<MonitorProbe> = await this.findBy({
327
333
  query: {
@@ -329,8 +335,6 @@ export class Service extends DatabaseService<MonitorProbe> {
329
335
  },
330
336
  select: {
331
337
  monitorId: true,
332
- probeId: true,
333
- nextPingAt: true,
334
338
  },
335
339
  limit: LIMIT_PER_PROJECT,
336
340
  skip: 0,
@@ -340,16 +344,38 @@ export class Service extends DatabaseService<MonitorProbe> {
340
344
  });
341
345
 
342
346
  for (const monitorProbe of monitorProbes) {
343
- if (!monitorProbe.probeId) {
347
+ if (!monitorProbe.monitorId) {
344
348
  continue;
345
349
  }
346
350
 
347
- await MonitorService.refreshProbeStatus(monitorProbe.probeId);
351
+ /*
352
+ * Refresh only the monitor whose probe row changed. Do NOT call
353
+ * refreshProbeStatus(probeId) here: a global/shared probe can be
354
+ * attached to thousands of monitors, and refreshing all of them on a
355
+ * single enable/disable toggle storms the database and 500s the
356
+ * request (the row itself is already saved by this point).
357
+ */
358
+ await this.refreshMonitorStatusSafely(monitorProbe.monitorId);
348
359
  }
349
360
  }
350
361
 
351
362
  return onUpdate;
352
363
  }
364
+
365
+ /*
366
+ * Refresh a single monitor's aggregate probe status without letting a
367
+ * failure bubble up. These refreshes run in post-commit hooks
368
+ * (onCreateSuccess / onUpdateSuccess) after the MonitorProbe row is already
369
+ * persisted, so a status-refresh error must not turn a successful save into
370
+ * a 500 for the user — log it instead.
371
+ */
372
+ private async refreshMonitorStatusSafely(monitorId: ObjectID): Promise<void> {
373
+ try {
374
+ await MonitorService.refreshMonitorProbeStatus(monitorId);
375
+ } catch (err) {
376
+ logger.error(err);
377
+ }
378
+ }
353
379
  }
354
380
 
355
381
  export default new Service();
@@ -1620,14 +1620,26 @@ ${createdItem.description?.trim() || "No description provided."}
1620
1620
 
1621
1621
  /*
1622
1622
  * Each monitor appears at most once for a given probeId (composite
1623
- * unique on MonitorProbe), so concurrent refreshes operate on
1624
- * disjoint rows and are safe to run in parallel.
1623
+ * unique on MonitorProbe), so concurrent refreshes operate on disjoint
1624
+ * rows and are safe to run in parallel. A global/shared probe can be
1625
+ * attached to thousands of monitors, though, so refresh in bounded
1626
+ * batches instead of firing every refresh at once — an unbounded
1627
+ * Promise.all here would exhaust the database connection pool.
1625
1628
  */
1626
- await Promise.all(
1627
- monitorProbes.map((monitorProbe: MonitorProbe) => {
1628
- return this.refreshMonitorProbeStatus(monitorProbe.monitorId!);
1629
- }),
1630
- );
1629
+ const refreshConcurrency: number = 50;
1630
+
1631
+ for (let i: number = 0; i < monitorProbes.length; i += refreshConcurrency) {
1632
+ const batch: Array<MonitorProbe> = monitorProbes.slice(
1633
+ i,
1634
+ i + refreshConcurrency,
1635
+ );
1636
+
1637
+ await Promise.all(
1638
+ batch.map((monitorProbe: MonitorProbe) => {
1639
+ return this.refreshMonitorProbeStatus(monitorProbe.monitorId!);
1640
+ }),
1641
+ );
1642
+ }
1631
1643
  }
1632
1644
 
1633
1645
  @CaptureSpan()
@@ -22,6 +22,9 @@ import PushNotificationMessage from "../../Types/PushNotification/PushNotificati
22
22
  import PushNotificationUtil from "../Utils/PushNotificationUtil";
23
23
  import { createWhatsAppMessageFromTemplate } from "../Utils/WhatsAppTemplateUtil";
24
24
  import { WhatsAppMessagePayload } from "../../Types/WhatsApp/WhatsAppMessage";
25
+ import OnCallDutyPolicyTimeLogService from "./OnCallDutyPolicyTimeLogService";
26
+ import OneUptimeDate from "../../Types/Date";
27
+ import logger, { LogAttributes } from "../Utils/Logger";
25
28
 
26
29
  export class Service extends DatabaseService<Model> {
27
30
  public constructor() {
@@ -86,6 +89,39 @@ export class Service extends DatabaseService<Model> {
86
89
  return createdItem;
87
90
  }
88
91
 
92
+ /*
93
+ * Open the roster user's on-call time log for this schedule/rule/policy.
94
+ * Mirrors onDeleteSuccess's endTimeLogForUser (detaching a schedule closes
95
+ * the log, so attaching must open it). Without this, attaching a schedule
96
+ * whose roster user is ALREADY established never opened a log — the roster
97
+ * refresh only opens one when the current user CHANGES — so the whole stint
98
+ * went unrecorded in on-call reporting and the later detach's
99
+ * endTimeLogForUser was a no-op (audit M1). startTimeLogForUser is idempotent
100
+ * (open-log dedup), so a later natural handoff re-opening the same log is a
101
+ * no-op.
102
+ */
103
+ if (
104
+ createdModel.onCallDutyPolicy?.id &&
105
+ createdModel.onCallDutyPolicyEscalationRule?.id &&
106
+ createdModel.projectId &&
107
+ createdModel.onCallDutyPolicyScheduleId
108
+ ) {
109
+ OnCallDutyPolicyTimeLogService.startTimeLogForUser({
110
+ projectId: createdModel.projectId,
111
+ onCallDutyPolicyId: createdModel.onCallDutyPolicy.id,
112
+ onCallDutyPolicyEscalationRuleId:
113
+ createdModel.onCallDutyPolicyEscalationRule.id,
114
+ userId: userOnSchedule,
115
+ onCallDutyPolicyScheduleId: createdModel.onCallDutyPolicyScheduleId,
116
+ startsAt: OneUptimeDate.getCurrentDate(),
117
+ }).catch((error: Error) => {
118
+ logger.error(error, {
119
+ projectId: createdModel.projectId?.toString(),
120
+ userId: userOnSchedule?.toString(),
121
+ } as LogAttributes);
122
+ });
123
+ }
124
+
89
125
  const scheduleName: string =
90
126
  createdModel.onCallDutyPolicySchedule?.name || "No name provided";
91
127
 
@@ -289,6 +325,35 @@ export class Service extends DatabaseService<Model> {
289
325
  continue;
290
326
  }
291
327
 
328
+ /*
329
+ * Close the roster user's open on-call time log for this schedule/rule/
330
+ * policy. Roster handoffs open a time log keyed by schedule + rule + policy
331
+ * (OnCallDutyPolicyScheduleService.startTimeLogForUser); the user-link and
332
+ * team-link delete handlers close theirs, but detaching a SCHEDULE from the
333
+ * rule left the log open forever, so the roster user kept showing as
334
+ * on-call in reporting (audit F18).
335
+ */
336
+ if (
337
+ deletedItem.onCallDutyPolicy?.id &&
338
+ deletedItem.onCallDutyPolicyEscalationRule?.id &&
339
+ deletedItem.projectId
340
+ ) {
341
+ OnCallDutyPolicyTimeLogService.endTimeLogForUser({
342
+ projectId: deletedItem.projectId,
343
+ onCallDutyPolicyId: deletedItem.onCallDutyPolicy.id,
344
+ onCallDutyPolicyEscalationRuleId:
345
+ deletedItem.onCallDutyPolicyEscalationRule.id,
346
+ userId: userOnSchedule,
347
+ onCallDutyPolicyScheduleId: deletedItem.onCallDutyPolicyScheduleId!,
348
+ endsAt: OneUptimeDate.getCurrentDate(),
349
+ }).catch((error: Error) => {
350
+ logger.error(error, {
351
+ projectId: deletedItem.projectId?.toString(),
352
+ userId: userOnSchedule?.toString(),
353
+ } as LogAttributes);
354
+ });
355
+ }
356
+
292
357
  const sendEmailToUserId: ObjectID | undefined | null = userOnSchedule;
293
358
 
294
359
  if (!sendEmailToUserId) {