@oneuptime/common 12.0.8 → 12.0.9

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 (769) hide show
  1. package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +1 -1
  2. package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +1 -1
  3. package/Models/DatabaseModels/AIConversation.ts +32 -0
  4. package/Models/DatabaseModels/AIConversationMessage.ts +41 -0
  5. package/Models/DatabaseModels/AlertEpisodeMember.ts +27 -0
  6. package/Models/DatabaseModels/IncidentEpisodeMember.ts +28 -0
  7. package/Models/DatabaseModels/Index.ts +12 -4
  8. package/Models/DatabaseModels/{TelemetryEntity.ts → InventoryItem.ts} +139 -9
  9. package/Models/DatabaseModels/InventoryItemCustomField.ts +434 -0
  10. package/Models/DatabaseModels/{TelemetryEntityRelationship.ts → InventoryItemRelationship.ts} +8 -8
  11. package/Models/DatabaseModels/NetworkDevice.ts +141 -0
  12. package/Models/DatabaseModels/NetworkDeviceLink.ts +699 -0
  13. package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +467 -0
  14. package/Models/DatabaseModels/NetworkTopologySuppression.ts +429 -0
  15. package/Models/DatabaseModels/OnCallDutyPolicyFeed.ts +9 -0
  16. package/Models/DatabaseModels/Project.ts +78 -0
  17. package/Models/DatabaseModels/UserCall.ts +42 -0
  18. package/Models/DatabaseModels/UserEmail.ts +44 -0
  19. package/Models/DatabaseModels/UserNotificationRule.ts +425 -55
  20. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +24 -2
  21. package/Models/DatabaseModels/UserPush.ts +49 -0
  22. package/Models/DatabaseModels/UserSMS.ts +43 -0
  23. package/Models/DatabaseModels/UserTelegram.ts +59 -0
  24. package/Models/DatabaseModels/UserWebhook.ts +52 -0
  25. package/Models/DatabaseModels/UserWhatsApp.ts +41 -0
  26. package/Models/DatabaseModels/WorkflowVariable.ts +12 -0
  27. package/Server/API/AIChatAPI.ts +315 -1
  28. package/Server/API/DashboardAPI.ts +217 -1
  29. package/Server/API/OnCallReadinessAPI.ts +841 -0
  30. package/Server/API/TeamComplianceAPI.ts +69 -17
  31. package/Server/API/TelemetryAPI.ts +220 -12
  32. package/Server/EnvironmentConfig.ts +52 -0
  33. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +22 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.ts +4 -4
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1786200000000-RestoreDroppedUniqueIndexes.ts +5 -5
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.ts +35 -0
  37. package/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.ts +82 -0
  38. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.ts +91 -0
  39. package/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.ts +47 -0
  40. package/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.ts +255 -0
  41. package/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.ts +107 -0
  42. package/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.ts +90 -0
  43. package/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.ts +39 -0
  44. package/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.ts +33 -0
  45. package/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.ts +59 -0
  46. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +20 -0
  47. package/Server/Infrastructure/Queue.ts +78 -13
  48. package/Server/Middleware/PublicDashboardRateLimit.ts +593 -0
  49. package/Server/Services/AIService.ts +7 -0
  50. package/Server/Services/AlertEpisodeStateTimelineService.ts +29 -0
  51. package/Server/Services/AlertSeverityService.ts +63 -0
  52. package/Server/Services/DashboardService.ts +9 -10
  53. package/Server/Services/DatabaseService.ts +32 -2
  54. package/Server/Services/IncidentEpisodeStateTimelineService.ts +29 -0
  55. package/Server/Services/IncidentSeverityService.ts +76 -0
  56. package/Server/Services/Index.ts +12 -4
  57. package/Server/Services/InventoryItemCustomFieldService.ts +9 -0
  58. package/Server/Services/{TelemetryEntityRelationshipService.ts → InventoryItemRelationshipService.ts} +4 -4
  59. package/Server/Services/{TelemetryEntityService.ts → InventoryItemService.ts} +89 -20
  60. package/Server/Services/LogAggregationService.ts +45 -8
  61. package/Server/Services/MetricAggregationService.ts +121 -0
  62. package/Server/Services/MetricService.ts +7 -7
  63. package/Server/Services/NetworkDeviceLinkRuleService.ts +10 -0
  64. package/Server/Services/NetworkDeviceLinkService.ts +84 -0
  65. package/Server/Services/NetworkDeviceService.ts +140 -0
  66. package/Server/Services/NetworkSiteService.ts +77 -25
  67. package/Server/Services/NetworkTopologySuppressionService.ts +84 -0
  68. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +41 -29
  69. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +8 -0
  70. package/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.ts +62 -13
  71. package/Server/Services/OnCallDutyPolicyScheduleService.ts +61 -1
  72. package/Server/Services/OnCallNotificationAlertingService.ts +742 -0
  73. package/Server/Services/OnCallReadinessService.ts +2803 -0
  74. package/Server/Services/OnCallSetupReminderService.ts +955 -0
  75. package/Server/Services/ProfileAggregationService.ts +123 -0
  76. package/Server/Services/StatusPageService.ts +9 -10
  77. package/Server/Services/TeamComplianceService.ts +429 -252
  78. package/Server/Services/UserCallService.ts +26 -1
  79. package/Server/Services/UserEmailService.ts +26 -1
  80. package/Server/Services/UserNotificationRuleAdminService.ts +1183 -0
  81. package/Server/Services/UserNotificationRuleService.ts +3812 -333
  82. package/Server/Services/UserOnCallLogService.ts +561 -48
  83. package/Server/Services/UserPushService.ts +29 -0
  84. package/Server/Services/UserService.ts +11 -0
  85. package/Server/Services/UserSmsService.ts +26 -1
  86. package/Server/Services/UserTelegramService.ts +24 -1
  87. package/Server/Services/UserWebhookService.ts +28 -1
  88. package/Server/Services/UserWhatsAppService.ts +24 -1
  89. package/Server/Types/Database/Permissions/BasePermission.ts +19 -0
  90. package/Server/Types/Database/Permissions/CreatePermission.ts +164 -0
  91. package/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.ts +340 -0
  92. package/Server/Types/Database/Permissions/QueryPermission.ts +48 -0
  93. package/Server/Types/Database/Permissions/TenantPermission.ts +8 -1
  94. package/Server/Types/Workflow/Components/API/Delete.ts +1 -1
  95. package/Server/Types/Workflow/Components/API/Get.ts +1 -1
  96. package/Server/Types/Workflow/Components/API/Patch.ts +1 -1
  97. package/Server/Types/Workflow/Components/API/Post.ts +1 -1
  98. package/Server/Types/Workflow/Components/API/Put.ts +1 -1
  99. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +29 -5
  100. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +18 -10
  101. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +55 -0
  102. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +3 -17
  103. package/Server/Types/Workflow/Components/Email.ts +25 -7
  104. package/Server/Types/Workflow/Components/JavaScript.ts +10 -3
  105. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +1 -1
  106. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +643 -48
  107. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +32 -3
  108. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +20 -6
  109. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +7 -0
  110. package/Server/Utils/AI/Toolbox/AIActionTools.ts +2 -2
  111. package/Server/Utils/AI/Toolbox/AIMetaTools.ts +863 -0
  112. package/Server/Utils/AI/Toolbox/AlertTools.ts +177 -15
  113. package/Server/Utils/AI/Toolbox/IncidentTools.ts +191 -10
  114. package/Server/Utils/AI/Toolbox/Index.ts +48 -0
  115. package/Server/Utils/AI/Toolbox/MonitorTools.ts +298 -11
  116. package/Server/Utils/AI/Toolbox/NoteWriteTools.ts +295 -0
  117. package/Server/Utils/AI/Toolbox/OnCallTools.ts +1246 -0
  118. package/Server/Utils/AI/Toolbox/RunbookTools.ts +424 -0
  119. package/Server/Utils/AI/Toolbox/SloTools.ts +456 -0
  120. package/Server/Utils/AI/Toolbox/StatusPageTools.ts +559 -0
  121. package/Server/Utils/AI/Toolbox/TeamTools.ts +327 -0
  122. package/Server/Utils/AI/Toolbox/TimelineTools.ts +615 -0
  123. package/Server/Utils/AI/Toolbox/WorkflowProbeTools.ts +664 -0
  124. package/Server/Utils/ClientIp.ts +221 -0
  125. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +47 -0
  126. package/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.ts +163 -0
  127. package/Server/Utils/Dashboard/PublicDashboardSloWidget.ts +147 -0
  128. package/Server/Utils/Express.ts +12 -17
  129. package/Server/Utils/LLM/LLMService.ts +85 -8
  130. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +204 -10
  131. package/Server/Utils/SSRFProtection.ts +98 -23
  132. package/Server/Utils/StartServer.ts +12 -3
  133. package/Server/Utils/Telemetry/EntityRegistry.ts +205 -18
  134. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +25 -25
  135. package/Server/Utils/Telemetry/TelemetryEntity.ts +160 -52
  136. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +7 -3
  137. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +2146 -0
  138. package/Tests/App/Dashboard/CreateWorkflowModal.test.tsx +561 -0
  139. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +2470 -0
  140. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +1897 -0
  141. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +3606 -0
  142. package/Tests/App/Dashboard/OnCallRulesDeleteGuard.test.tsx +784 -0
  143. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +1119 -0
  144. package/Tests/App/Dashboard/SloWidgetFetching.test.tsx +531 -0
  145. package/Tests/App/Dashboard/UserSettingsSetupChecklistModel.test.ts +1312 -0
  146. package/Tests/App/Dashboard/UserSettingsSetupChecklistPage.test.tsx +1390 -0
  147. package/Tests/Models/InventoryItemModel.test.ts +174 -0
  148. package/Tests/Models/InventoryItemNaming.test.ts +302 -0
  149. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +437 -0
  150. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +659 -0
  151. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +18 -0
  152. package/Tests/Server/API/DashboardPublicSloAPI.test.ts +880 -0
  153. package/Tests/Server/API/Helpers.ts +24 -15
  154. package/Tests/Server/API/OnCallReadinessAPI.test.ts +2680 -0
  155. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +915 -0
  156. package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +533 -0
  157. package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +213 -0
  158. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +432 -0
  159. package/Tests/Server/Infrastructure/Queue.test.ts +293 -0
  160. package/Tests/Server/Middleware/PublicDashboardRateLimit.test.ts +1645 -0
  161. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +2848 -0
  162. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +1393 -0
  163. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +1802 -0
  164. package/Tests/Server/Services/EpisodeStateTimelineNote.test.ts +304 -0
  165. package/Tests/Server/Services/InventoryItemDisplayName.test.ts +339 -0
  166. package/Tests/Server/Services/{TelemetryEntityManualCreate.test.ts → InventoryItemManualCreate.test.ts} +27 -27
  167. package/Tests/Server/Services/IpAllowlistSpoofing.test.ts +450 -0
  168. package/Tests/Server/Services/LogAggregationService.test.ts +235 -1
  169. package/Tests/Server/Services/MetricAggregationService.test.ts +231 -0
  170. package/Tests/Server/Services/MetricEntityMVKeyParity.test.ts +80 -29
  171. package/Tests/Server/Services/MetricServiceAggregate.test.ts +30 -30
  172. package/Tests/Server/Services/NetworkSiteService.test.ts +18 -3
  173. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +1728 -0
  174. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +2402 -0
  175. package/Tests/Server/Services/OnCallDutyPolicyExecutionLogTimelineGapFeed.test.ts +394 -0
  176. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +1744 -0
  177. package/Tests/Server/Services/OnCallReadinessService.test.ts +4295 -0
  178. package/Tests/Server/Services/OnCallSetupReminder.test.ts +1272 -0
  179. package/Tests/Server/Services/OnCallWeeklyReadinessDigest.test.ts +1021 -0
  180. package/Tests/Server/Services/ProfileAggregationService.test.ts +296 -0
  181. package/Tests/Server/Services/SeverityCreationRuleBackfill.test.ts +1536 -0
  182. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +1818 -0
  183. package/Tests/Server/Services/TeamComplianceServiceBehaviour.test.ts +1845 -0
  184. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +1394 -0
  185. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +1166 -0
  186. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +1468 -0
  187. package/Tests/Server/Services/UserOnCallLogNoNotificationRules.test.ts +1457 -0
  188. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +1546 -0
  189. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +529 -0
  190. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +1219 -0
  191. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +1089 -0
  192. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +2 -1
  193. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +190 -0
  194. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +44 -14
  195. package/Tests/Server/Types/Workflow/Components/Email.test.ts +151 -0
  196. package/Tests/Server/Types/Workflow/Components/IfElse.test.ts +98 -0
  197. package/Tests/Server/Types/Workflow/Components/JavaScript.test.ts +51 -0
  198. package/Tests/Server/Utils/AI/AIMetaTools.test.ts +586 -0
  199. package/Tests/Server/Utils/AI/AlertMonitorFilters.test.ts +582 -0
  200. package/Tests/Server/Utils/AI/ChatAgentRunner.test.ts +726 -0
  201. package/Tests/Server/Utils/AI/IncidentToolsFilters.test.ts +315 -0
  202. package/Tests/Server/Utils/AI/LLMServiceStopReason.test.ts +314 -0
  203. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +26 -3
  204. package/Tests/Server/Utils/AI/NoteWriteTools.test.ts +268 -0
  205. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +169 -0
  206. package/Tests/Server/Utils/AI/OnCallTools.test.ts +664 -0
  207. package/Tests/Server/Utils/AI/RunbookTools.test.ts +325 -0
  208. package/Tests/Server/Utils/AI/SloTools.test.ts +306 -0
  209. package/Tests/Server/Utils/AI/StatusPageTools.test.ts +391 -0
  210. package/Tests/Server/Utils/AI/TeamTools.test.ts +257 -0
  211. package/Tests/Server/Utils/AI/TimelineTools.test.ts +472 -0
  212. package/Tests/Server/Utils/AI/WorkflowProbeTools.test.ts +428 -0
  213. package/Tests/Server/Utils/ClientIp.test.ts +438 -0
  214. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +171 -0
  215. package/Tests/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.test.ts +383 -0
  216. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +23 -25
  217. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +6 -0
  218. package/Tests/Server/Utils/Monitor/Criteria/DnssecMonitorCriteria.test.ts +307 -0
  219. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +468 -0
  220. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorTelemetryDeepLinks.test.ts +460 -0
  221. package/Tests/Server/Utils/ResponseRateLimitStatusCodes.test.ts +137 -0
  222. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +40 -8
  223. package/Tests/Server/Utils/SSRFProtectionUserInfo.test.ts +351 -0
  224. package/Tests/Server/Utils/Telemetry/EntityRegistryRetirement.test.ts +531 -0
  225. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +15 -15
  226. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +356 -39
  227. package/Tests/Types/IP/IP.test.ts +263 -0
  228. package/Tests/Types/IP/IPWhitelist.test.ts +197 -0
  229. package/Tests/Types/IP/IPv6.test.ts +12 -1
  230. package/Tests/Types/Monitor/SnmpOid.test.ts +64 -0
  231. package/Tests/Types/NetworkDevice/NetworkDeviceMonitoringMethod.test.ts +110 -0
  232. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +106 -0
  233. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeDifferential.test.ts +511 -0
  234. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageEndToEnd.test.ts +489 -0
  235. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageGapTolerance.test.ts +479 -0
  236. package/Tests/Types/OnCallDutyPolicy/ScheduleCoverageState.test.ts +822 -0
  237. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +6 -6
  238. package/Tests/Types/Workflow/BaseModelComponents.test.ts +429 -0
  239. package/Tests/Types/Workflow/Components/BaseModel.test.ts +225 -0
  240. package/Tests/Types/Workflow/IntegrationCredentialMetadata.test.ts +100 -0
  241. package/Tests/Types/Workflow/TemplateSyntax.test.ts +112 -0
  242. package/Tests/Types/Workflow/Templates.test.ts +916 -101
  243. package/Tests/UI/Components/ActiveFilterChipsOpenRoute.test.tsx +82 -0
  244. package/Tests/UI/Components/ComponentsModal.test.tsx +564 -7
  245. package/Tests/UI/Components/KeyboardShortcut.test.tsx +95 -0
  246. package/Tests/UI/Components/LogDetailsPanelCrossSignal.test.tsx +448 -0
  247. package/Tests/UI/Components/LogsTableCrossLinks.test.tsx +263 -0
  248. package/Tests/UI/Components/PendingProjectInvitations.test.tsx +913 -0
  249. package/Tests/UI/Components/SimpleLogViewer.test.tsx +228 -0
  250. package/Tests/UI/Components/TableRowSelectability.test.tsx +312 -0
  251. package/Tests/UI/Components/Workflow/GraphLint.test.ts +72 -0
  252. package/Tests/UI/Components/Workflow/GraphLintSummary.test.ts +755 -0
  253. package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +242 -7
  254. package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +15 -7
  255. package/Tests/UI/Components/Workflow/ModelSchema.test.ts +242 -28
  256. package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +59 -1
  257. package/Tests/UI/Components/Workflow/StepTraceViewer.test.tsx +256 -0
  258. package/Tests/UI/Components/Workflow/UseRunWatch.test.tsx +665 -0
  259. package/Tests/UI/Components/Workflow/WorkflowIssuesModal.test.tsx +485 -0
  260. package/Tests/UI/Components/Workflow/WorkflowLogModal.test.tsx +478 -0
  261. package/Tests/UI/Components/Workflow/WorkflowStatusBar.test.tsx +379 -0
  262. package/Tests/UI/EsbuildConfig.test.ts +607 -0
  263. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  264. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +11 -4
  265. package/Tests/UI/Utils/ModelAPICreateMiscData.test.ts +94 -0
  266. package/Tests/UI/Utils/Platform.test.ts +147 -0
  267. package/Tests/UI/Utils/ProjectInvitationDisplay.test.ts +357 -0
  268. package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +198 -0
  269. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +487 -0
  270. package/Tests/Utils/Telemetry/CrossSignalScope.test.ts +698 -0
  271. package/Tests/__mocks__/bullmq.js +55 -0
  272. package/Types/AI/AIChatMessageStatus.ts +8 -1
  273. package/Types/AI/AIChatTypes.ts +12 -0
  274. package/Types/Database/AccessControl/OwnerOnlyColumn.ts +88 -0
  275. package/Types/Exception/ExceptionCode.ts +2 -0
  276. package/Types/Exception/ServiceUnavailableException.ts +8 -0
  277. package/Types/Exception/TooManyRequestsException.ts +8 -0
  278. package/Types/IP/IP.ts +93 -47
  279. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +49 -3
  280. package/Types/NetworkDevice/NetworkDeviceMonitoringMethod.ts +55 -0
  281. package/Types/OnCallDutyPolicy/Layer.ts +203 -149
  282. package/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.ts +13 -0
  283. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +155 -10
  284. package/Types/Permission.ts +193 -0
  285. package/Types/Telemetry/EntityRelationshipType.ts +1 -1
  286. package/Types/Telemetry/EntitySource.ts +1 -1
  287. package/Types/Telemetry/EntityType.ts +1 -1
  288. package/Types/Telemetry/EntityTypeGroups.ts +1 -1
  289. package/Types/Workflow/Components/BaseModel.ts +75 -29
  290. package/Types/Workflow/Components/Discord.ts +1 -0
  291. package/Types/Workflow/Components/Email.ts +12 -3
  292. package/Types/Workflow/Components/JavaScript.ts +7 -0
  293. package/Types/Workflow/Components/MicrosoftTeams.ts +3 -2
  294. package/Types/Workflow/Components/Slack.ts +1 -0
  295. package/Types/Workflow/Components/Telegram.ts +1 -0
  296. package/Types/Workflow/TemplateSyntax.ts +44 -0
  297. package/Types/Workflow/Templates.ts +2097 -45
  298. package/UI/Components/Calendar/Calendar.css +43 -0
  299. package/UI/Components/Calendar/Calendar.tsx +8 -0
  300. package/UI/Components/Card/Card.tsx +2 -2
  301. package/UI/Components/Checkbox/Checkbox.tsx +16 -0
  302. package/UI/Components/Dictionary/Dictionary.tsx +48 -9
  303. package/UI/Components/FormModal/BasicFormModal.tsx +2 -1
  304. package/UI/Components/Header/HeaderIconDropdownButton.tsx +53 -3
  305. package/UI/Components/Input/Input.tsx +1 -0
  306. package/UI/Components/KeyboardShortcut/KeyboardKey.ts +185 -0
  307. package/UI/Components/KeyboardShortcut/KeyboardShortcut.tsx +87 -0
  308. package/UI/Components/LogsViewer/LogsViewer.tsx +28 -0
  309. package/UI/Components/LogsViewer/components/ActiveFilterChips.tsx +31 -0
  310. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +18 -18
  311. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +363 -14
  312. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +11 -0
  313. package/UI/Components/LogsViewer/components/LogsTable.tsx +155 -12
  314. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +29 -0
  315. package/UI/Components/LogsViewer/types.ts +23 -0
  316. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +9 -2
  317. package/UI/Components/Navbar/NavBarMenuModal.tsx +15 -30
  318. package/UI/Components/ProjectInvitations/PendingProjectInvitations.tsx +442 -0
  319. package/UI/Components/SimpleLogViewer/SimpleLogViewer.tsx +23 -1
  320. package/UI/Components/Table/Table.tsx +49 -16
  321. package/UI/Components/Table/TableBody.tsx +53 -28
  322. package/UI/Components/Table/TableHeader.tsx +13 -0
  323. package/UI/Components/Table/TableRow.tsx +58 -26
  324. package/UI/Components/Workflow/ArgumentsForm.tsx +155 -9
  325. package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +26 -0
  326. package/UI/Components/Workflow/ComponentSettingsModal.tsx +10 -1
  327. package/UI/Components/Workflow/ComponentValuePickerModal.tsx +135 -7
  328. package/UI/Components/Workflow/ComponentsModal.tsx +116 -22
  329. package/UI/Components/Workflow/DocumentationViewer.tsx +59 -9
  330. package/UI/Components/Workflow/GraphLint.ts +42 -5
  331. package/UI/Components/Workflow/GraphLintSummary.ts +390 -0
  332. package/UI/Components/Workflow/ModelColumnEditor.tsx +154 -22
  333. package/UI/Components/Workflow/ModelSchema.ts +115 -33
  334. package/UI/Components/Workflow/RunStatusWatcher.ts +1 -1
  335. package/UI/Components/Workflow/StepTraceViewer.tsx +1 -1
  336. package/UI/Components/Workflow/UseRunWatch.ts +212 -0
  337. package/UI/Components/Workflow/VariableModal.tsx +6 -2
  338. package/UI/Components/Workflow/Workflow.tsx +49 -3
  339. package/UI/Components/Workflow/WorkflowIssuesModal.tsx +255 -0
  340. package/UI/Components/Workflow/WorkflowLogModal.tsx +128 -0
  341. package/UI/Components/Workflow/WorkflowStatusBar.tsx +224 -0
  342. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +10 -0
  343. package/UI/Utils/ModelAPI/ModelAPI.ts +7 -1
  344. package/UI/Utils/Platform.ts +149 -0
  345. package/UI/Utils/ProjectInvitationDisplay.ts +118 -0
  346. package/UI/esbuild-config.js +22 -1
  347. package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +187 -0
  348. package/Utils/Monitor/NetworkTopologyUtil.ts +888 -164
  349. package/Utils/Telemetry/CrossSignalScope.ts +502 -0
  350. package/Utils/Telemetry/EntityKey.ts +5 -5
  351. package/Utils/Telemetry/EntityRelationship.ts +1 -1
  352. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +1 -1
  353. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +1 -1
  354. package/build/dist/Models/DatabaseModels/AIConversation.js +32 -0
  355. package/build/dist/Models/DatabaseModels/AIConversation.js.map +1 -1
  356. package/build/dist/Models/DatabaseModels/AIConversationMessage.js +42 -0
  357. package/build/dist/Models/DatabaseModels/AIConversationMessage.js.map +1 -1
  358. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js +28 -0
  359. package/build/dist/Models/DatabaseModels/AlertEpisodeMember.js.map +1 -1
  360. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js +29 -0
  361. package/build/dist/Models/DatabaseModels/IncidentEpisodeMember.js.map +1 -1
  362. package/build/dist/Models/DatabaseModels/Index.js +12 -4
  363. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  364. package/build/dist/Models/DatabaseModels/{TelemetryEntity.js → InventoryItem.js} +167 -31
  365. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -0
  366. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +454 -0
  367. package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -0
  368. package/build/dist/Models/DatabaseModels/{TelemetryEntityRelationship.js → InventoryItemRelationship.js} +27 -27
  369. package/build/dist/Models/DatabaseModels/InventoryItemRelationship.js.map +1 -0
  370. package/build/dist/Models/DatabaseModels/NetworkDevice.js +141 -0
  371. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  372. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +719 -0
  373. package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -0
  374. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +475 -0
  375. package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -0
  376. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js +446 -0
  377. package/build/dist/Models/DatabaseModels/NetworkTopologySuppression.js.map +1 -0
  378. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js +9 -0
  379. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyFeed.js.map +1 -1
  380. package/build/dist/Models/DatabaseModels/Project.js +80 -0
  381. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  382. package/build/dist/Models/DatabaseModels/UserCall.js +46 -2
  383. package/build/dist/Models/DatabaseModels/UserCall.js.map +1 -1
  384. package/build/dist/Models/DatabaseModels/UserEmail.js +48 -2
  385. package/build/dist/Models/DatabaseModels/UserEmail.js.map +1 -1
  386. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +424 -55
  387. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  388. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +24 -2
  389. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  390. package/build/dist/Models/DatabaseModels/UserPush.js +51 -1
  391. package/build/dist/Models/DatabaseModels/UserPush.js.map +1 -1
  392. package/build/dist/Models/DatabaseModels/UserSMS.js +47 -2
  393. package/build/dist/Models/DatabaseModels/UserSMS.js.map +1 -1
  394. package/build/dist/Models/DatabaseModels/UserTelegram.js +65 -3
  395. package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
  396. package/build/dist/Models/DatabaseModels/UserWebhook.js +56 -2
  397. package/build/dist/Models/DatabaseModels/UserWebhook.js.map +1 -1
  398. package/build/dist/Models/DatabaseModels/UserWhatsApp.js +45 -2
  399. package/build/dist/Models/DatabaseModels/UserWhatsApp.js.map +1 -1
  400. package/build/dist/Models/DatabaseModels/WorkflowVariable.js +12 -0
  401. package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
  402. package/build/dist/Server/API/AIChatAPI.js +237 -1
  403. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  404. package/build/dist/Server/API/DashboardAPI.js +165 -13
  405. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  406. package/build/dist/Server/API/OnCallReadinessAPI.js +599 -0
  407. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -0
  408. package/build/dist/Server/API/TeamComplianceAPI.js +68 -9
  409. package/build/dist/Server/API/TeamComplianceAPI.js.map +1 -1
  410. package/build/dist/Server/API/TelemetryAPI.js +129 -18
  411. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  412. package/build/dist/Server/EnvironmentConfig.js +45 -0
  413. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  414. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +22 -0
  415. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  416. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786100000000-RestoreServiceLowerNameIndex.js +4 -4
  417. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js +18 -0
  418. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786625176831-AddMonitoringMethodToNetworkDevice.js.map +1 -0
  419. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js +39 -0
  420. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786634985763-AddNetworkDeviceLink.js.map +1 -0
  421. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js +38 -0
  422. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639512056-AddNetworkDeviceLinkRule.js.map +1 -0
  423. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js +22 -0
  424. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786639972982-AddNetworkTopologySuppression.js.map +1 -0
  425. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js +150 -0
  426. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786800000000-RenameTelemetryEntityToInventoryItem.js.map +1 -0
  427. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js +57 -0
  428. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786900000000-AddInventoryItemArchiveAndCustomFields.js.map +1 -0
  429. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js +69 -0
  430. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787000000000-AddOnCallNotificationFallbackColumns.js.map +1 -0
  431. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js +32 -0
  432. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787100000000-AddAIConversationPageContext.js.map +1 -0
  433. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js +26 -0
  434. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787200000000-AddAIChatMessageFeedback.js.map +1 -0
  435. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js +48 -0
  436. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787300000000-AddEpisodeMemberNotifyIndexes.js.map +1 -0
  437. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +20 -0
  438. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  439. package/build/dist/Server/Infrastructure/Queue.js +72 -13
  440. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  441. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js +399 -0
  442. package/build/dist/Server/Middleware/PublicDashboardRateLimit.js.map +1 -0
  443. package/build/dist/Server/Services/AIService.js +1 -0
  444. package/build/dist/Server/Services/AIService.js.map +1 -1
  445. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +24 -3
  446. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  447. package/build/dist/Server/Services/AlertSeverityService.js +54 -0
  448. package/build/dist/Server/Services/AlertSeverityService.js.map +1 -1
  449. package/build/dist/Server/Services/DashboardService.js +10 -9
  450. package/build/dist/Server/Services/DashboardService.js.map +1 -1
  451. package/build/dist/Server/Services/DatabaseService.js +24 -2
  452. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  453. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +24 -3
  454. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  455. package/build/dist/Server/Services/IncidentSeverityService.js +67 -0
  456. package/build/dist/Server/Services/IncidentSeverityService.js.map +1 -1
  457. package/build/dist/Server/Services/Index.js +12 -4
  458. package/build/dist/Server/Services/Index.js.map +1 -1
  459. package/build/dist/Server/Services/InventoryItemCustomFieldService.js +9 -0
  460. package/build/dist/Server/Services/InventoryItemCustomFieldService.js.map +1 -0
  461. package/build/dist/Server/Services/{TelemetryEntityRelationshipService.js → InventoryItemRelationshipService.js} +6 -6
  462. package/build/dist/Server/Services/InventoryItemRelationshipService.js.map +1 -0
  463. package/build/dist/Server/Services/{TelemetryEntityService.js → InventoryItemService.js} +68 -23
  464. package/build/dist/Server/Services/InventoryItemService.js.map +1 -0
  465. package/build/dist/Server/Services/LogAggregationService.js +27 -8
  466. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  467. package/build/dist/Server/Services/MetricAggregationService.js +80 -0
  468. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  469. package/build/dist/Server/Services/MetricService.js +6 -6
  470. package/build/dist/Server/Services/MetricService.js.map +1 -1
  471. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js +9 -0
  472. package/build/dist/Server/Services/NetworkDeviceLinkRuleService.js.map +1 -0
  473. package/build/dist/Server/Services/NetworkDeviceLinkService.js +71 -0
  474. package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -0
  475. package/build/dist/Server/Services/NetworkDeviceService.js +113 -0
  476. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  477. package/build/dist/Server/Services/NetworkSiteService.js +53 -13
  478. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  479. package/build/dist/Server/Services/NetworkTopologySuppressionService.js +85 -0
  480. package/build/dist/Server/Services/NetworkTopologySuppressionService.js.map +1 -0
  481. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +48 -32
  482. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  483. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +8 -0
  484. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  485. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js +51 -12
  486. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogTimelineService.js.map +1 -1
  487. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +57 -13
  488. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  489. package/build/dist/Server/Services/OnCallNotificationAlertingService.js +548 -0
  490. package/build/dist/Server/Services/OnCallNotificationAlertingService.js.map +1 -0
  491. package/build/dist/Server/Services/OnCallReadinessService.js +1961 -0
  492. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -0
  493. package/build/dist/Server/Services/OnCallSetupReminderService.js +738 -0
  494. package/build/dist/Server/Services/OnCallSetupReminderService.js.map +1 -0
  495. package/build/dist/Server/Services/ProfileAggregationService.js +68 -4
  496. package/build/dist/Server/Services/ProfileAggregationService.js.map +1 -1
  497. package/build/dist/Server/Services/StatusPageService.js +11 -10
  498. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  499. package/build/dist/Server/Services/TeamComplianceService.js +312 -160
  500. package/build/dist/Server/Services/TeamComplianceService.js.map +1 -1
  501. package/build/dist/Server/Services/UserCallService.js +24 -1
  502. package/build/dist/Server/Services/UserCallService.js.map +1 -1
  503. package/build/dist/Server/Services/UserEmailService.js +24 -1
  504. package/build/dist/Server/Services/UserEmailService.js.map +1 -1
  505. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +858 -0
  506. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -0
  507. package/build/dist/Server/Services/UserNotificationRuleService.js +2830 -175
  508. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  509. package/build/dist/Server/Services/UserOnCallLogService.js +488 -43
  510. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  511. package/build/dist/Server/Services/UserPushService.js +26 -0
  512. package/build/dist/Server/Services/UserPushService.js.map +1 -1
  513. package/build/dist/Server/Services/UserService.js +10 -0
  514. package/build/dist/Server/Services/UserService.js.map +1 -1
  515. package/build/dist/Server/Services/UserSmsService.js +24 -1
  516. package/build/dist/Server/Services/UserSmsService.js.map +1 -1
  517. package/build/dist/Server/Services/UserTelegramService.js +22 -1
  518. package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
  519. package/build/dist/Server/Services/UserWebhookService.js +25 -1
  520. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  521. package/build/dist/Server/Services/UserWhatsAppService.js +22 -1
  522. package/build/dist/Server/Services/UserWhatsAppService.js.map +1 -1
  523. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +12 -1
  524. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  525. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js +126 -0
  526. package/build/dist/Server/Types/Database/Permissions/CreatePermission.js.map +1 -1
  527. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js +254 -0
  528. package/build/dist/Server/Types/Database/Permissions/OwnerOnlyColumnPermission.js.map +1 -0
  529. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js +47 -2
  530. package/build/dist/Server/Types/Database/Permissions/QueryPermission.js.map +1 -1
  531. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +7 -0
  532. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  533. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +1 -1
  534. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  535. package/build/dist/Server/Types/Workflow/Components/API/Get.js +1 -1
  536. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  537. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +1 -1
  538. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  539. package/build/dist/Server/Types/Workflow/Components/API/Post.js +1 -1
  540. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  541. package/build/dist/Server/Types/Workflow/Components/API/Put.js +1 -1
  542. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  543. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +21 -5
  544. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  545. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +14 -10
  546. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  547. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +31 -0
  548. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -1
  549. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +3 -9
  550. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  551. package/build/dist/Server/Types/Workflow/Components/Email.js +15 -4
  552. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  553. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +7 -2
  554. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  555. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +1 -1
  556. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  557. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +514 -56
  558. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  559. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +20 -3
  560. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  561. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +19 -6
  562. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  563. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +7 -0
  564. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  565. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js +2 -2
  566. package/build/dist/Server/Utils/AI/Toolbox/AIActionTools.js.map +1 -1
  567. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js +692 -0
  568. package/build/dist/Server/Utils/AI/Toolbox/AIMetaTools.js.map +1 -0
  569. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js +148 -12
  570. package/build/dist/Server/Utils/AI/Toolbox/AlertTools.js.map +1 -1
  571. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +157 -10
  572. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  573. package/build/dist/Server/Utils/AI/Toolbox/Index.js +37 -0
  574. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  575. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js +259 -14
  576. package/build/dist/Server/Utils/AI/Toolbox/MonitorTools.js.map +1 -1
  577. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js +235 -0
  578. package/build/dist/Server/Utils/AI/Toolbox/NoteWriteTools.js.map +1 -0
  579. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +1000 -0
  580. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -0
  581. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js +356 -0
  582. package/build/dist/Server/Utils/AI/Toolbox/RunbookTools.js.map +1 -0
  583. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +394 -0
  584. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -0
  585. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js +465 -0
  586. package/build/dist/Server/Utils/AI/Toolbox/StatusPageTools.js.map +1 -0
  587. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js +280 -0
  588. package/build/dist/Server/Utils/AI/Toolbox/TeamTools.js.map +1 -0
  589. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js +527 -0
  590. package/build/dist/Server/Utils/AI/Toolbox/TimelineTools.js.map +1 -0
  591. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js +548 -0
  592. package/build/dist/Server/Utils/AI/Toolbox/WorkflowProbeTools.js.map +1 -0
  593. package/build/dist/Server/Utils/ClientIp.js +137 -0
  594. package/build/dist/Server/Utils/ClientIp.js.map +1 -0
  595. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +38 -0
  596. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  597. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js +89 -0
  598. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloHistoryPolicy.js.map +1 -0
  599. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js +77 -0
  600. package/build/dist/Server/Utils/Dashboard/PublicDashboardSloWidget.js.map +1 -0
  601. package/build/dist/Server/Utils/Express.js +12 -12
  602. package/build/dist/Server/Utils/Express.js.map +1 -1
  603. package/build/dist/Server/Utils/LLM/LLMService.js +70 -7
  604. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  605. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +121 -11
  606. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  607. package/build/dist/Server/Utils/SSRFProtection.js +82 -21
  608. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  609. package/build/dist/Server/Utils/StartServer.js +12 -4
  610. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  611. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +165 -18
  612. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  613. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +11 -11
  614. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
  615. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +122 -47
  616. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  617. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +7 -3
  618. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  619. package/build/dist/Types/AI/AIChatMessageStatus.js +8 -1
  620. package/build/dist/Types/AI/AIChatMessageStatus.js.map +1 -1
  621. package/build/dist/Types/AI/AIChatTypes.js +12 -0
  622. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  623. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js +60 -0
  624. package/build/dist/Types/Database/AccessControl/OwnerOnlyColumn.js.map +1 -0
  625. package/build/dist/Types/Exception/ExceptionCode.js +2 -0
  626. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  627. package/build/dist/Types/Exception/ServiceUnavailableException.js +8 -0
  628. package/build/dist/Types/Exception/ServiceUnavailableException.js.map +1 -0
  629. package/build/dist/Types/Exception/TooManyRequestsException.js +8 -0
  630. package/build/dist/Types/Exception/TooManyRequestsException.js.map +1 -0
  631. package/build/dist/Types/IP/IP.js +87 -43
  632. package/build/dist/Types/IP/IP.js.map +1 -1
  633. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js +50 -0
  634. package/build/dist/Types/NetworkDevice/NetworkDeviceMonitoringMethod.js.map +1 -0
  635. package/build/dist/Types/OnCallDutyPolicy/Layer.js +186 -123
  636. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  637. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js +13 -0
  638. package/build/dist/Types/OnCallDutyPolicy/OnCallDutyPolicyStatus.js.map +1 -1
  639. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +105 -11
  640. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
  641. package/build/dist/Types/Permission.js +174 -0
  642. package/build/dist/Types/Permission.js.map +1 -1
  643. package/build/dist/Types/Telemetry/EntityRelationshipType.js +1 -1
  644. package/build/dist/Types/Telemetry/EntitySource.js +1 -1
  645. package/build/dist/Types/Telemetry/EntityType.js +1 -1
  646. package/build/dist/Types/Telemetry/EntityTypeGroups.js +1 -1
  647. package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -1
  648. package/build/dist/Types/Workflow/Components/BaseModel.js +66 -28
  649. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  650. package/build/dist/Types/Workflow/Components/Discord.js +1 -0
  651. package/build/dist/Types/Workflow/Components/Discord.js.map +1 -1
  652. package/build/dist/Types/Workflow/Components/Email.js +12 -3
  653. package/build/dist/Types/Workflow/Components/Email.js.map +1 -1
  654. package/build/dist/Types/Workflow/Components/JavaScript.js +7 -0
  655. package/build/dist/Types/Workflow/Components/JavaScript.js.map +1 -1
  656. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js +3 -2
  657. package/build/dist/Types/Workflow/Components/MicrosoftTeams.js.map +1 -1
  658. package/build/dist/Types/Workflow/Components/Slack.js +1 -0
  659. package/build/dist/Types/Workflow/Components/Slack.js.map +1 -1
  660. package/build/dist/Types/Workflow/Components/Telegram.js +1 -0
  661. package/build/dist/Types/Workflow/Components/Telegram.js.map +1 -1
  662. package/build/dist/Types/Workflow/TemplateSyntax.js +12 -0
  663. package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -1
  664. package/build/dist/Types/Workflow/Templates.js +1931 -38
  665. package/build/dist/Types/Workflow/Templates.js.map +1 -1
  666. package/build/dist/UI/Components/Calendar/Calendar.js +1 -1
  667. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  668. package/build/dist/UI/Components/Checkbox/Checkbox.js +1 -1
  669. package/build/dist/UI/Components/Checkbox/Checkbox.js.map +1 -1
  670. package/build/dist/UI/Components/Dictionary/Dictionary.js +25 -11
  671. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  672. package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
  673. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +27 -4
  674. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  675. package/build/dist/UI/Components/Input/Input.js +1 -0
  676. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  677. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js +163 -0
  678. package/build/dist/UI/Components/KeyboardShortcut/KeyboardKey.js.map +1 -0
  679. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js +47 -0
  680. package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcut.js.map +1 -0
  681. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +4 -4
  682. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  683. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js +11 -1
  684. package/build/dist/UI/Components/LogsViewer/components/ActiveFilterChips.js.map +1 -1
  685. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +10 -8
  686. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -1
  687. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +227 -14
  688. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  689. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +5 -0
  690. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  691. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +69 -5
  692. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  693. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +8 -0
  694. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  695. package/build/dist/UI/Components/LogsViewer/types.js.map +1 -1
  696. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +9 -2
  697. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
  698. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +8 -21
  699. package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
  700. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js +251 -0
  701. package/build/dist/UI/Components/ProjectInvitations/PendingProjectInvitations.js.map +1 -0
  702. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js +9 -2
  703. package/build/dist/UI/Components/SimpleLogViewer/SimpleLogViewer.js.map +1 -1
  704. package/build/dist/UI/Components/Table/Table.js +27 -15
  705. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  706. package/build/dist/UI/Components/Table/TableBody.js +24 -18
  707. package/build/dist/UI/Components/Table/TableBody.js.map +1 -1
  708. package/build/dist/UI/Components/Table/TableHeader.js +9 -1
  709. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  710. package/build/dist/UI/Components/Table/TableRow.js +29 -21
  711. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  712. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +130 -12
  713. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  714. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +10 -1
  715. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
  716. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +4 -4
  717. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
  718. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js +57 -7
  719. package/build/dist/UI/Components/Workflow/ComponentValuePickerModal.js.map +1 -1
  720. package/build/dist/UI/Components/Workflow/ComponentsModal.js +53 -18
  721. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  722. package/build/dist/UI/Components/Workflow/DocumentationViewer.js +19 -6
  723. package/build/dist/UI/Components/Workflow/DocumentationViewer.js.map +1 -1
  724. package/build/dist/UI/Components/Workflow/GraphLint.js +33 -4
  725. package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -1
  726. package/build/dist/UI/Components/Workflow/GraphLintSummary.js +231 -0
  727. package/build/dist/UI/Components/Workflow/GraphLintSummary.js.map +1 -0
  728. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +116 -21
  729. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
  730. package/build/dist/UI/Components/Workflow/ModelSchema.js +72 -34
  731. package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -1
  732. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +1 -1
  733. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -1
  734. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +1 -1
  735. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -1
  736. package/build/dist/UI/Components/Workflow/UseRunWatch.js +123 -0
  737. package/build/dist/UI/Components/Workflow/UseRunWatch.js.map +1 -0
  738. package/build/dist/UI/Components/Workflow/VariableModal.js +3 -2
  739. package/build/dist/UI/Components/Workflow/VariableModal.js.map +1 -1
  740. package/build/dist/UI/Components/Workflow/Workflow.js +41 -1
  741. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  742. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js +99 -0
  743. package/build/dist/UI/Components/Workflow/WorkflowIssuesModal.js.map +1 -0
  744. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js +56 -0
  745. package/build/dist/UI/Components/Workflow/WorkflowLogModal.js.map +1 -0
  746. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js +92 -0
  747. package/build/dist/UI/Components/Workflow/WorkflowStatusBar.js.map +1 -0
  748. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +9 -0
  749. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -1
  750. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +1 -1
  751. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  752. package/build/dist/UI/Utils/Platform.js +118 -0
  753. package/build/dist/UI/Utils/Platform.js.map +1 -0
  754. package/build/dist/UI/Utils/ProjectInvitationDisplay.js +106 -0
  755. package/build/dist/UI/Utils/ProjectInvitationDisplay.js.map +1 -0
  756. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +108 -0
  757. package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -0
  758. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +642 -136
  759. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  760. package/build/dist/Utils/Telemetry/CrossSignalScope.js +328 -0
  761. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -0
  762. package/build/dist/Utils/Telemetry/EntityKey.js +5 -5
  763. package/build/dist/Utils/Telemetry/EntityRelationship.js +1 -1
  764. package/jest.config.json +1 -0
  765. package/package.json +1 -1
  766. package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +0 -1
  767. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +0 -1
  768. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +0 -1
  769. package/build/dist/Server/Services/TelemetryEntityService.js.map +0 -1
@@ -0,0 +1,1546 @@
1
+ import UserCall from "../../../../../Models/DatabaseModels/UserCall";
2
+ import UserEmail from "../../../../../Models/DatabaseModels/UserEmail";
3
+ import UserNotificationRule from "../../../../../Models/DatabaseModels/UserNotificationRule";
4
+ import UserPush from "../../../../../Models/DatabaseModels/UserPush";
5
+ import UserSMS from "../../../../../Models/DatabaseModels/UserSMS";
6
+ import UserTelegram from "../../../../../Models/DatabaseModels/UserTelegram";
7
+ import UserWebhook from "../../../../../Models/DatabaseModels/UserWebhook";
8
+ import UserWhatsApp from "../../../../../Models/DatabaseModels/UserWhatsApp";
9
+ import BaseModel from "../../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
10
+ import DatabaseRequestType from "../../../../../Server/Types/BaseDatabase/DatabaseRequestType";
11
+ import BasePermission from "../../../../../Server/Types/Database/Permissions/BasePermission";
12
+ import ColumnPermissions from "../../../../../Server/Types/Database/Permissions/ColumnPermission";
13
+ import CreatePermission from "../../../../../Server/Types/Database/Permissions/CreatePermission";
14
+ import TenantPermission from "../../../../../Server/Types/Database/Permissions/TenantPermission";
15
+ import Query from "../../../../../Server/Types/Database/Query";
16
+ import Select from "../../../../../Server/Types/Database/Select";
17
+ import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
18
+ import DatabaseCommonInteractionPropsUtil, {
19
+ PermissionType,
20
+ } from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
21
+ import { ColumnAccessControl } from "../../../../../Types/BaseDatabase/AccessControl";
22
+ import BadDataException from "../../../../../Types/Exception/BadDataException";
23
+ import NotAuthorizedException from "../../../../../Types/Exception/NotAuthorizedException";
24
+ import ObjectID from "../../../../../Types/ObjectID";
25
+ import UserType from "../../../../../Types/UserType";
26
+ import Permission, {
27
+ PermissionHelper,
28
+ PermissionProps,
29
+ UserPermission,
30
+ } from "../../../../../Types/Permission";
31
+ import { describe, expect, test } from "@jest/globals";
32
+
33
+ /*
34
+ * WHAT THIS FILE GUARDS
35
+ *
36
+ * An administrator has to be able to answer "why did this member's page go
37
+ * unanswered", and until Phase 3 nobody but the member could see the rows that
38
+ * decide it: a member with no matching rule was silently unpageable, and the
39
+ * only person able to notice was the person who was not being notified.
40
+ *
41
+ * PHASE 3 SHIPPED THAT CAPABILITY ON THE RULE TABLE AND ONLY THERE. The seven
42
+ * notification-method models - UserEmail, UserSMS, UserCall, UserPush,
43
+ * UserWhatsApp, UserTelegram, UserWebhook - stay CurrentUser-only on every
44
+ * verb, so an administrator reading one of THEM is refused exactly like any
45
+ * other member. That split is not a leftover; it is the design, and it is the
46
+ * design because the alternative was tried and did not hold.
47
+ *
48
+ * WHY THE METHOD TABLES COULD NOT BE WIDENED. Widening a read on a model that
49
+ * scopes rows by owner is a privilege change rather than a convenience change,
50
+ * and the mechanism is indirect enough to be worth stating:
51
+ * TenantPermission.isAccessGrantedOnlyByCurrentUser intersects what the caller
52
+ * holds with what the model's list accepts, and while the only survivor is the
53
+ * auto-granted Permission.CurrentUser, addCurrentUserScopeToQuery rewrites the
54
+ * query to `userId = me` and rejects outright any attempt to name somebody
55
+ * else. That single stamp is the ONLY thing making the columns underneath
56
+ * owner-only - `read: [Permission.CurrentUser]` on a COLUMN never meant "on my
57
+ * own row", because CurrentUser is auto-granted to every authenticated caller
58
+ * and ColumnPermissions intersects by NAME without ever seeing the query. Add
59
+ * one administrator permission to a method model's table list and the stamp
60
+ * stops being applied for whoever holds it, and the webhook URL and its signing
61
+ * secret, the push device token, and the Telegram chat id and verification code
62
+ * become readable on every member's row in the project. The column-level guard
63
+ * written to contain that was then walked past by nested relation selects, by
64
+ * `query` filters it never inspected, and by sort columns appended to the
65
+ * select after it had run. The widening was reverted rather than patched a
66
+ * fourth time.
67
+ *
68
+ * WHAT AN ADMINISTRATOR USES INSTEAD. OnCallReadinessService answers the
69
+ * diagnosis question as root and returns ReadinessMethod
70
+ * { methodId, methodType, maskedIdentifier, isVerified } - masked server-side
71
+ * by the one code path that holds the raw value. methodId is a foreign key
72
+ * rather than a secret, and it is what lets an administrator POINT A RULE AT a
73
+ * method without the method's row ever being read.
74
+ *
75
+ * So every assertion here is about the exact shape of what did and did not
76
+ * lift:
77
+ *
78
+ * - the rule table lifts for an administrator and not for a plain member, on
79
+ * all four verbs;
80
+ * - the seven method models lift for NOBODY on ANY verb, and the read case is
81
+ * asserted in both directions because it is the one that shipped widened
82
+ * once already;
83
+ * - the rule's method FK id columns are admin-readable while the method
84
+ * RELATION columns are not, which is what keeps "re-point this rule"
85
+ * possible without making "read what it points at" possible;
86
+ * - the ownership and tenant COLUMNS stay frozen, so the capability cannot be
87
+ * turned into "re-point this rule at a different owner", which is the same
88
+ * hijack wearing a different verb;
89
+ * - the two granular permissions are opt-in - held by nobody until an
90
+ * administrator grants them, and never auto-granted the way CurrentUser is;
91
+ * - the roles are listed ALONGSIDE the granular permissions on the rule
92
+ * table, so the feature is not dead on arrival for a project whose teams
93
+ * predate them;
94
+ * - the create-side ownership gate added in Phase 2.5 still refuses a plain
95
+ * member who names another user.
96
+ *
97
+ * The tests are written against the permission layer directly rather than
98
+ * through the API, because the API is not the boundary: POST/PATCH
99
+ * /api/user-notification-rule stays reachable with any member session, and the
100
+ * admin UI is a convenience over these checks rather than a gate in front of
101
+ * them.
102
+ */
103
+
104
+ const PROJECT_ID: ObjectID = new ObjectID(
105
+ "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
106
+ );
107
+ const CALLER_ID: ObjectID = new ObjectID(
108
+ "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
109
+ );
110
+ const OTHER_USER_ID: ObjectID = new ObjectID(
111
+ "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
112
+ );
113
+
114
+ type ModelConstructor = { new (): BaseModel };
115
+
116
+ /*
117
+ * The permissions that lift the read scope, and the permissions that lift the
118
+ * write scope, ON THE RULE TABLE. Spelled out here rather than imported from
119
+ * the model, so that a change to the model's private constant has to be
120
+ * reflected deliberately in a test rather than being absorbed silently by a
121
+ * shared reference.
122
+ *
123
+ * They appear in the method-model groups too, but only ever inside a
124
+ * `not.toContain` or a refusal - there is no verb on a notification method that
125
+ * any of them may perform on somebody else's row.
126
+ */
127
+ const ADMIN_READ_GRANTS: Array<Permission> = [
128
+ Permission.ProjectOwner,
129
+ Permission.ProjectAdmin,
130
+ Permission.ReadProjectUserNotificationRule,
131
+ ];
132
+
133
+ const ADMIN_WRITE_GRANTS: Array<Permission> = [
134
+ Permission.ProjectOwner,
135
+ Permission.ProjectAdmin,
136
+ Permission.EditProjectUserNotificationRule,
137
+ ];
138
+
139
+ /*
140
+ * The two permissions this phase introduces. Kept separate from the lists above
141
+ * because several assertions are about the permissions themselves - their
142
+ * catalogue entry, their opt-in-ness - rather than about any model.
143
+ */
144
+ const NEW_GRANULAR_PERMISSIONS: Array<Permission> = [
145
+ Permission.ReadProjectUserNotificationRule,
146
+ Permission.EditProjectUserNotificationRule,
147
+ ];
148
+
149
+ /*
150
+ * The seven notification methods a rule can point at. Every method assertion
151
+ * runs against all seven rather than against a representative one: they are
152
+ * seven copies of one decorator shape, and a widening applied to one of them is
153
+ * exactly the kind of thing that survives review.
154
+ */
155
+ const METHOD_MODELS: Array<[string, ModelConstructor]> = [
156
+ ["UserEmail", UserEmail],
157
+ ["UserSMS", UserSMS],
158
+ ["UserCall", UserCall],
159
+ ["UserPush", UserPush],
160
+ ["UserWhatsApp", UserWhatsApp],
161
+ ["UserTelegram", UserTelegram],
162
+ ["UserWebhook", UserWebhook],
163
+ ];
164
+
165
+ const ROW_SCOPED_REQUEST_TYPES: Array<DatabaseRequestType> = [
166
+ DatabaseRequestType.Read,
167
+ DatabaseRequestType.Update,
168
+ DatabaseRequestType.Delete,
169
+ ];
170
+
171
+ /*
172
+ * A plain project member: authenticated, holding nothing but the permissions
173
+ * every logged-in caller is auto-granted. This is the shape an escalation
174
+ * attempt arrives in, and also the ordinary shape of a first-party dashboard
175
+ * request.
176
+ *
177
+ * Rebuilt on every call rather than shared, because
178
+ * DatabaseCommonInteractionPropsUtil.getUserPermissions MUTATES the props it is
179
+ * handed - it pushes Public and CurrentUser into globalPermissions in place - so
180
+ * a shared object would carry state between tests.
181
+ */
182
+ function memberProps(): DatabaseCommonInteractionProps {
183
+ return {
184
+ userId: CALLER_ID,
185
+ tenantId: PROJECT_ID,
186
+ userType: UserType.User,
187
+ };
188
+ }
189
+
190
+ /*
191
+ * A caller holding specific permissions FOR THIS TENANT. Note that they also
192
+ * receive the auto-granted CurrentUser on top, exactly as a real session would -
193
+ * an administrator is still a user with notification rules and notification
194
+ * methods of their own, and several of the behaviours below only make sense
195
+ * because both are true at once.
196
+ */
197
+ function tenantProps(
198
+ ...permissions: Array<Permission>
199
+ ): DatabaseCommonInteractionProps {
200
+ return {
201
+ userId: CALLER_ID,
202
+ tenantId: PROJECT_ID,
203
+ userType: UserType.User,
204
+ userTenantAccessPermission: {
205
+ [PROJECT_ID.toString()]: {
206
+ projectId: PROJECT_ID,
207
+ permissions: permissions.map((permission: Permission) => {
208
+ return {
209
+ permission: permission,
210
+ labelIds: [],
211
+ isBlockPermission: false,
212
+ _type: "UserPermission" as const,
213
+ };
214
+ }),
215
+ _type: "UserTenantAccessPermission",
216
+ },
217
+ },
218
+ };
219
+ }
220
+
221
+ function makeRow(
222
+ modelType: ModelConstructor,
223
+ ownerId?: ObjectID | undefined,
224
+ ): BaseModel {
225
+ const row: BaseModel = new modelType();
226
+ row.setColumnValue("projectId", PROJECT_ID);
227
+
228
+ if (ownerId) {
229
+ row.setColumnValue("userId", ownerId);
230
+ }
231
+
232
+ return row;
233
+ }
234
+
235
+ function scopedUserIdOf(query: Query<BaseModel>): string | undefined {
236
+ return (query as Record<string, unknown>)["userId"]?.toString();
237
+ }
238
+
239
+ /*
240
+ * The same ownership predicate, read off a query that has been through
241
+ * BasePermission.checkPermissions rather than the tenant scope alone.
242
+ *
243
+ * The two are not interchangeable and the difference is not cosmetic.
244
+ * checkPermissions finishes by running QueryUtil.serializeQuery, which rewrites
245
+ * an ObjectID-valued predicate into QueryHelper.equalTo() - a TypeORM Raw()
246
+ * FindOperator whose uuid does NOT live on `.value` (that slot holds the
247
+ * SQL-generator function) but in `.objectLiteralParameters` under a per-call
248
+ * random key. So the id is the sole VALUE in that bag rather than a value at a
249
+ * known key, and `.toString()` on the operator yields "[object Object]" - which
250
+ * would make a naive assertion pass or fail for reasons unrelated to ownership.
251
+ *
252
+ * Accepts the unserialized form too, so a caller does not have to know which
253
+ * stage produced the query it is holding.
254
+ */
255
+ function ownerIdOf(query: Query<BaseModel>): string | undefined {
256
+ const predicate: unknown = (query as Record<string, unknown>)["userId"];
257
+
258
+ if (predicate === undefined || predicate === null) {
259
+ return undefined;
260
+ }
261
+
262
+ if (predicate instanceof ObjectID || typeof predicate === "string") {
263
+ return predicate.toString();
264
+ }
265
+
266
+ const rawOperator: {
267
+ type?: string | undefined;
268
+ objectLiteralParameters?: Record<string, unknown> | undefined;
269
+ } = predicate as {
270
+ type?: string | undefined;
271
+ objectLiteralParameters?: Record<string, unknown> | undefined;
272
+ };
273
+
274
+ if (rawOperator.type !== "raw") {
275
+ return undefined;
276
+ }
277
+
278
+ const boundValues: Array<unknown> = Object.values(
279
+ rawOperator.objectLiteralParameters || {},
280
+ );
281
+
282
+ if (boundValues.length !== 1) {
283
+ return undefined;
284
+ }
285
+
286
+ return String(boundValues[0]);
287
+ }
288
+
289
+ describe("the rule table - an administrator's row scope lifts, a member's does not", () => {
290
+ test.each(ADMIN_READ_GRANTS)(
291
+ "%s reads another member's rules without being forced onto their own",
292
+ async (grant: Permission): Promise<void> => {
293
+ /*
294
+ * Two halves of the same claim, and both are needed. An unscoped query
295
+ * proves the ownership predicate is no longer stamped on; a query that
296
+ * NAMES the other user proves addCurrentUserScopeToQuery is no longer
297
+ * rejecting the attempt outright. Before this phase the second half threw
298
+ * NotAuthorizedException, which is why an administrator could not even
299
+ * ask the question.
300
+ */
301
+ const unscoped: Query<BaseModel> =
302
+ await TenantPermission.addTenantScopeToQuery(
303
+ UserNotificationRule,
304
+ {},
305
+ null,
306
+ tenantProps(grant),
307
+ DatabaseRequestType.Read,
308
+ );
309
+
310
+ expect(scopedUserIdOf(unscoped)).toBeUndefined();
311
+ expect(
312
+ (unscoped as Record<string, unknown>)["projectId"]?.toString(),
313
+ ).toBe(PROJECT_ID.toString());
314
+
315
+ const targeted: Query<BaseModel> =
316
+ await TenantPermission.addTenantScopeToQuery(
317
+ UserNotificationRule,
318
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
319
+ null,
320
+ tenantProps(grant),
321
+ DatabaseRequestType.Read,
322
+ );
323
+
324
+ expect(scopedUserIdOf(targeted)).toBe(OTHER_USER_ID.toString());
325
+ },
326
+ );
327
+
328
+ test.each(ADMIN_WRITE_GRANTS)(
329
+ "%s may update and delete another member's rules",
330
+ async (grant: Permission): Promise<void> => {
331
+ /*
332
+ * Repair, not just diagnosis. Update is what re-points a rule at a method
333
+ * the member actually verified; delete is the supported way to move a
334
+ * rule between owners, since the ownership column itself is frozen.
335
+ */
336
+ for (const requestType of [
337
+ DatabaseRequestType.Update,
338
+ DatabaseRequestType.Delete,
339
+ ]) {
340
+ const query: Query<BaseModel> =
341
+ await TenantPermission.addTenantScopeToQuery(
342
+ UserNotificationRule,
343
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
344
+ null,
345
+ tenantProps(grant),
346
+ requestType,
347
+ );
348
+
349
+ expect(scopedUserIdOf(query)).toBe(OTHER_USER_ID.toString());
350
+ }
351
+ },
352
+ );
353
+
354
+ test("a plain member is still forced onto their own rules on every verb", async () => {
355
+ for (const requestType of [
356
+ ...ROW_SCOPED_REQUEST_TYPES,
357
+ DatabaseRequestType.Create,
358
+ ]) {
359
+ const query: Query<BaseModel> =
360
+ await TenantPermission.addTenantScopeToQuery(
361
+ UserNotificationRule,
362
+ {},
363
+ null,
364
+ memberProps(),
365
+ requestType,
366
+ );
367
+
368
+ expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
369
+ }
370
+ });
371
+
372
+ test("a plain member naming another user is rejected, not quietly redirected", async () => {
373
+ /*
374
+ * The distinction matters more than it looks. Service hooks run before the
375
+ * final permission check, so silently rewriting the target would let a hook
376
+ * inspect one row while the database operation mutated a different one.
377
+ * Widening the table list must not have turned this refusal into a rewrite.
378
+ */
379
+ for (const requestType of ROW_SCOPED_REQUEST_TYPES) {
380
+ await expect(
381
+ TenantPermission.addTenantScopeToQuery(
382
+ UserNotificationRule,
383
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
384
+ null,
385
+ memberProps(),
386
+ requestType,
387
+ ),
388
+ ).rejects.toThrow(NotAuthorizedException);
389
+ }
390
+ });
391
+
392
+ test("the lift is granted per operation, not per model", async () => {
393
+ /*
394
+ * ReadProjectUserNotificationRule appears only in the read lists and
395
+ * EditProjectUserNotificationRule only in the write lists, so each holder is
396
+ * still force-scoped on the verbs the other one covers. This is worth
397
+ * pinning because the natural refactor - one ADMIN_PERMISSIONS constant
398
+ * spread everywhere - would silently turn a read grant into a write grant.
399
+ */
400
+ const readOnly: Query<BaseModel> =
401
+ await TenantPermission.addTenantScopeToQuery(
402
+ UserNotificationRule,
403
+ {},
404
+ null,
405
+ tenantProps(Permission.ReadProjectUserNotificationRule),
406
+ DatabaseRequestType.Update,
407
+ );
408
+
409
+ expect(scopedUserIdOf(readOnly)).toBe(CALLER_ID.toString());
410
+
411
+ const writeOnly: Query<BaseModel> =
412
+ await TenantPermission.addTenantScopeToQuery(
413
+ UserNotificationRule,
414
+ {},
415
+ null,
416
+ tenantProps(Permission.EditProjectUserNotificationRule),
417
+ DatabaseRequestType.Read,
418
+ );
419
+
420
+ expect(scopedUserIdOf(writeOnly)).toBe(CALLER_ID.toString());
421
+ });
422
+
423
+ test("an administrator of a DIFFERENT project gets no lift here", async () => {
424
+ /*
425
+ * userTenantAccessPermission is keyed by project, and getUserPermissions
426
+ * only reads the bucket matching props.tenantId. A ProjectAdmin of some
427
+ * other project therefore arrives holding nothing but CurrentUser and is
428
+ * scoped like any member. This is the tenant half of the isolation the
429
+ * service-layer membership check completes on the create path.
430
+ */
431
+ const foreignProjectId: ObjectID = ObjectID.generate();
432
+
433
+ const query: Query<BaseModel> =
434
+ await TenantPermission.addTenantScopeToQuery(
435
+ UserNotificationRule,
436
+ {},
437
+ null,
438
+ {
439
+ userId: CALLER_ID,
440
+ tenantId: PROJECT_ID,
441
+ userType: UserType.User,
442
+ userTenantAccessPermission: {
443
+ [foreignProjectId.toString()]: {
444
+ projectId: foreignProjectId,
445
+ permissions: [
446
+ {
447
+ permission: Permission.ProjectAdmin,
448
+ labelIds: [],
449
+ isBlockPermission: false,
450
+ _type: "UserPermission",
451
+ },
452
+ ],
453
+ _type: "UserTenantAccessPermission",
454
+ },
455
+ },
456
+ },
457
+ DatabaseRequestType.Read,
458
+ );
459
+
460
+ expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
461
+ });
462
+
463
+ test("the rule table's lists carry the roles and the granular permission and nothing else", () => {
464
+ const rule: UserNotificationRule = new UserNotificationRule();
465
+
466
+ expect(rule.getReadPermissions()).toEqual([
467
+ Permission.CurrentUser,
468
+ ...ADMIN_READ_GRANTS,
469
+ ]);
470
+
471
+ for (const list of [
472
+ rule.getCreatePermissions(),
473
+ rule.getUpdatePermissions(),
474
+ rule.getDeletePermissions(),
475
+ ]) {
476
+ expect(list).toEqual([Permission.CurrentUser, ...ADMIN_WRITE_GRANTS]);
477
+ }
478
+ });
479
+ });
480
+
481
+ describe("the seven methods - an administrator may not look either", () => {
482
+ /*
483
+ * THE CORE DECISION OF THIS PHASE AS IT FINALLY SHIPPED, AND THE ONE MOST
484
+ * LIKELY TO BE "TIDIED UP" LATER, because seven models that differ from their
485
+ * sibling in all four lists look like an oversight rather than a decision.
486
+ *
487
+ * The read half of these lists was widened once and then reverted. The reason
488
+ * it could not stay is that the table-level scope is not one gate among
489
+ * several here - it is the ONLY gate. Every column on these models declares
490
+ * `read: [Permission.CurrentUser]`, and that list is intersected by NAME
491
+ * against a permission set that always contains the auto-granted CurrentUser,
492
+ * so it admits every authenticated caller on its own. What made it mean
493
+ * "owner only" was TenantPermission stamping `userId = me` onto the query,
494
+ * which happens exactly while this table list holds nothing but CurrentUser.
495
+ * Widening the table read removed the stamp and with it the only row scope in
496
+ * the path.
497
+ *
498
+ * Write was never widened and never could be, for a second and independent
499
+ * reason. A rule is a pair - the ownership column decides whose pages select
500
+ * the row, the method relation decides where those pages are delivered - so an
501
+ * administrator who could ADD a method to somebody else's account would hold a
502
+ * one-step paging hijack: register a phone or webhook you control on the
503
+ * victim, point their rules at it, and their pages arrive on your device.
504
+ *
505
+ * Neither restriction costs the administrator the diagnosis. A member with no
506
+ * methods needs to verify a device THEY own, which nobody else can do for
507
+ * them, and "does this colleague have a verified method" is answered by
508
+ * OnCallReadinessService's masked payload without this table being read at
509
+ * all.
510
+ */
511
+
512
+ test.each(METHOD_MODELS)(
513
+ "an administrator naming another member's %s row for read is refused",
514
+ async (_name: string, modelType: ModelConstructor): Promise<void> => {
515
+ /*
516
+ * The inversion of the assertion this group used to carry, and the reason
517
+ * the group exists at all. The table list is CurrentUser-only, so an
518
+ * administrator's intersection contains nothing but the auto-granted
519
+ * permission; addCurrentUserScopeToQuery therefore refuses the named
520
+ * target outright rather than quietly redirecting it, exactly as it does
521
+ * for a plain member.
522
+ *
523
+ * Both the read grants AND the write grants are exercised, because the
524
+ * claim is that NO permission a project can hand out reaches these rows -
525
+ * not that one particular list was left alone.
526
+ */
527
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
528
+ await expect(
529
+ TenantPermission.addTenantScopeToQuery(
530
+ modelType,
531
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
532
+ null,
533
+ tenantProps(grant),
534
+ DatabaseRequestType.Read,
535
+ ),
536
+ ).rejects.toThrow(NotAuthorizedException);
537
+ }
538
+ },
539
+ );
540
+
541
+ test.each(METHOD_MODELS)(
542
+ "an administrator's unscoped %s read comes back pinned to their own rows",
543
+ async (_name: string, modelType: ModelConstructor): Promise<void> => {
544
+ /*
545
+ * The shape a leak would actually arrive through, and the reason refusing
546
+ * a NAMED target is not sufficient on its own. An admin listing page asks
547
+ * for a project's methods rather than one member's, so it issues no userId
548
+ * at all - and a query with no userId cannot be refused for naming the
549
+ * wrong one. What protects it is the stamp: the ownership predicate is
550
+ * written onto the query, and the listing comes back containing the
551
+ * administrator's own rows and nobody else's.
552
+ */
553
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
554
+ const query: Query<BaseModel> =
555
+ await TenantPermission.addTenantScopeToQuery(
556
+ modelType,
557
+ {},
558
+ null,
559
+ tenantProps(grant),
560
+ DatabaseRequestType.Read,
561
+ );
562
+
563
+ expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
564
+ }
565
+ },
566
+ );
567
+
568
+ test.each(METHOD_MODELS)(
569
+ "an administrator reading their OWN %s row still succeeds end to end",
570
+ async (_name: string, modelType: ModelConstructor): Promise<void> => {
571
+ /*
572
+ * The constraint every version of this design has had to respect, and the
573
+ * one a heavy-handed fix breaks. An administrator is a member too, and
574
+ * /api/user-webhook and its siblings are where they manage their own
575
+ * methods; a change that withheld these rows whenever the caller happens
576
+ * to hold an administrator permission would satisfy every refusal above
577
+ * and break the administrator's own notification settings page.
578
+ *
579
+ * Driven through BasePermission.checkPermissions rather than the tenant
580
+ * scope alone so that the table-level check, the query check and the
581
+ * select check all run - the owner's read has to survive the whole path,
582
+ * not just the first stage of it.
583
+ */
584
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
585
+ await expect(
586
+ BasePermission.checkPermissions(
587
+ modelType,
588
+ { userId: CALLER_ID } as Query<BaseModel>,
589
+ { userId: true } as Select<BaseModel>,
590
+ tenantProps(grant),
591
+ DatabaseRequestType.Read,
592
+ ),
593
+ ).resolves.toBeDefined();
594
+ }
595
+ },
596
+ );
597
+
598
+ test.each(METHOD_MODELS)(
599
+ "an administrator is still force-scoped when updating or deleting %s",
600
+ async (_name: string, modelType: ModelConstructor): Promise<void> => {
601
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
602
+ for (const requestType of [
603
+ DatabaseRequestType.Update,
604
+ DatabaseRequestType.Delete,
605
+ ]) {
606
+ const query: Query<BaseModel> =
607
+ await TenantPermission.addTenantScopeToQuery(
608
+ modelType,
609
+ {},
610
+ null,
611
+ tenantProps(grant),
612
+ requestType,
613
+ );
614
+
615
+ expect(scopedUserIdOf(query)).toBe(CALLER_ID.toString());
616
+ }
617
+ }
618
+ },
619
+ );
620
+
621
+ test.each(METHOD_MODELS)(
622
+ "an administrator naming another member's %s row for update or delete is refused",
623
+ async (_name: string, modelType: ModelConstructor): Promise<void> => {
624
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
625
+ for (const requestType of [
626
+ DatabaseRequestType.Update,
627
+ DatabaseRequestType.Delete,
628
+ ]) {
629
+ await expect(
630
+ TenantPermission.addTenantScopeToQuery(
631
+ modelType,
632
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
633
+ null,
634
+ tenantProps(grant),
635
+ requestType,
636
+ ),
637
+ ).rejects.toThrow(NotAuthorizedException);
638
+ }
639
+ }
640
+ },
641
+ );
642
+
643
+ test.each(METHOD_MODELS)(
644
+ "an administrator cannot create a %s row on another member's behalf",
645
+ (_name: string, modelType: ModelConstructor): void => {
646
+ /*
647
+ * The create list is [CurrentUser], so an administrator arrives at
648
+ * CreatePermission's ownership gate looking exactly like a member: the
649
+ * intersection contains nothing but the auto-granted permission, the gate
650
+ * does not defer, and a row naming somebody else is refused.
651
+ */
652
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
653
+ expect(() => {
654
+ return CreatePermission.checkCreatePermissions(
655
+ modelType,
656
+ makeRow(modelType, OTHER_USER_ID) as never,
657
+ tenantProps(grant),
658
+ );
659
+ }).toThrow(NotAuthorizedException);
660
+ }
661
+ },
662
+ );
663
+
664
+ test.each(METHOD_MODELS)(
665
+ "%s's own notification methods are still fully theirs to manage",
666
+ (_name: string, modelType: ModelConstructor): void => {
667
+ /*
668
+ * The other side of every refusal above: withholding these rows from
669
+ * administrators must not have withheld them from the owner, who is the
670
+ * only person able to verify a device in the first place.
671
+ */
672
+ expect(() => {
673
+ return CreatePermission.checkCreatePermissions(
674
+ modelType,
675
+ makeRow(modelType, CALLER_ID) as never,
676
+ memberProps(),
677
+ );
678
+ }).not.toThrow();
679
+ },
680
+ );
681
+
682
+ test.each(METHOD_MODELS)(
683
+ "%s's table lists name CurrentUser and nothing else, on all four verbs",
684
+ (_name: string, modelType: ModelConstructor): void => {
685
+ /*
686
+ * Asserted literally rather than with toContain. The failure mode this
687
+ * guards is an entry ARRIVING, not one going missing, so a containment
688
+ * check would pass straight through the very change it exists to catch.
689
+ *
690
+ * READ IS IN THIS LIST DELIBERATELY. It is the one that shipped widened
691
+ * and was reverted, so it is the one a later commit is most likely to
692
+ * re-add while "restoring the admin feature" - and re-adding it silently
693
+ * removes the row scope that every column on the model depends on.
694
+ */
695
+ const model: BaseModel = new modelType();
696
+
697
+ for (const list of [
698
+ model.getReadPermissions(),
699
+ model.getCreatePermissions(),
700
+ model.getUpdatePermissions(),
701
+ model.getDeletePermissions(),
702
+ ]) {
703
+ expect(list).toEqual([Permission.CurrentUser]);
704
+ }
705
+ },
706
+ );
707
+ });
708
+
709
+ describe("R2 - the ownership and tenant columns stay frozen after creation", () => {
710
+ /*
711
+ * The original plan said to mirror the widened lists onto the per-column
712
+ * blocks. Applied literally to userId that would let an administrator
713
+ * RE-POINT an existing rule at a different owner: take a rule that already
714
+ * points at your own verified phone, move its userId to a colleague, and the
715
+ * colleague's pages ring your handset. Nothing distinguishes that from a
716
+ * legitimate edit at the row level. It is the hole the create-side ownership
717
+ * gate closes, reopened through a different verb.
718
+ *
719
+ * Both spellings have to be pinned. `user` and `userId` are two decorated
720
+ * members over one physical join column - an update expressed as
721
+ * `user: { _id }` resolves to the same write - so closing one and leaving the
722
+ * other open closes nothing at all.
723
+ */
724
+
725
+ const FROZEN_COLUMNS: Array<string> = [
726
+ "userId",
727
+ "user",
728
+ "projectId",
729
+ "project",
730
+ ];
731
+
732
+ test("the rule model's ownership and tenant columns are writable by nobody", () => {
733
+ const rule: UserNotificationRule = new UserNotificationRule();
734
+
735
+ for (const columnName of FROZEN_COLUMNS) {
736
+ const accessControl: ColumnAccessControl | null =
737
+ rule.getColumnAccessControlFor(columnName);
738
+
739
+ expect(accessControl).not.toBeNull();
740
+ expect(accessControl?.update).toEqual([]);
741
+ }
742
+ });
743
+
744
+ test.each(METHOD_MODELS)(
745
+ "%s's ownership and tenant columns are writable by nobody",
746
+ (_name: string, modelType: ModelConstructor): void => {
747
+ const model: BaseModel = new modelType();
748
+
749
+ for (const columnName of FROZEN_COLUMNS) {
750
+ const accessControl: ColumnAccessControl | null =
751
+ model.getColumnAccessControlFor(columnName);
752
+
753
+ expect(accessControl).not.toBeNull();
754
+ expect(accessControl?.update).toEqual([]);
755
+ }
756
+ },
757
+ );
758
+
759
+ test("no administrator permission has leaked into a frozen column's update list", () => {
760
+ /*
761
+ * The empty-array assertions above already cover this, but they read as
762
+ * "this happens to be empty" rather than "these permissions specifically
763
+ * must never appear". Stating the second form separately means a future
764
+ * mirroring of the widened lists fails against a test that names the reason.
765
+ */
766
+ const models: Array<BaseModel> = [
767
+ new UserNotificationRule(),
768
+ ...METHOD_MODELS.map(([, modelType]: [string, ModelConstructor]) => {
769
+ return new modelType();
770
+ }),
771
+ ];
772
+
773
+ for (const model of models) {
774
+ for (const columnName of FROZEN_COLUMNS) {
775
+ const updateList: Array<Permission> =
776
+ model.getColumnAccessControlFor(columnName)?.update || [];
777
+
778
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
779
+ expect(updateList).not.toContain(grant);
780
+ }
781
+ }
782
+ }
783
+ });
784
+
785
+ test("an administrator sending a new owner in an update body is refused by the column gate", () => {
786
+ /*
787
+ * The behavioural teeth of the assertions above. A frozen column is not a
788
+ * documentation exercise: ColumnPermissions rejects the write because the
789
+ * update list is empty and therefore intersects nothing, no matter what the
790
+ * caller holds.
791
+ */
792
+ const rule: UserNotificationRule = new UserNotificationRule();
793
+ rule.userId = OTHER_USER_ID;
794
+
795
+ expect(() => {
796
+ return ColumnPermissions.checkDataColumnPermissions(
797
+ UserNotificationRule,
798
+ rule,
799
+ tenantProps(Permission.EditProjectUserNotificationRule),
800
+ DatabaseRequestType.Update,
801
+ );
802
+ }).toThrow(BadDataException);
803
+ });
804
+
805
+ test("an administrator may still rewrite the columns a repair actually needs", () => {
806
+ /*
807
+ * The capability has to survive the freeze or the phase delivers nothing.
808
+ * Re-pointing the method FK is the repair; the delay and the opt-out flag
809
+ * are the two preference columns worth correcting. All three carry no
810
+ * redirect risk on their own, because the service layer separately asserts
811
+ * that a method FK being written belongs to the same user as the rule's
812
+ * persisted owner.
813
+ */
814
+ const rule: UserNotificationRule = new UserNotificationRule();
815
+ rule.userEmailId = ObjectID.generate();
816
+ rule.notifyAfterMinutes = 5;
817
+ rule.isOptOut = false;
818
+
819
+ expect(() => {
820
+ return ColumnPermissions.checkDataColumnPermissions(
821
+ UserNotificationRule,
822
+ rule,
823
+ tenantProps(Permission.EditProjectUserNotificationRule),
824
+ DatabaseRequestType.Update,
825
+ );
826
+ }).not.toThrow();
827
+ });
828
+
829
+ test("the rule's address columns stay create-only even for an administrator", () => {
830
+ /*
831
+ * ruleType and the two severity relations are the cell in the grid a rule
832
+ * occupies. Moving one reads as an edit and behaves as a delete plus a
833
+ * create, so it stays closed to everybody - an administrator repairing a
834
+ * member's setup adds and removes rules rather than sliding one sideways.
835
+ */
836
+ const rule: UserNotificationRule = new UserNotificationRule();
837
+
838
+ for (const columnName of [
839
+ "ruleType",
840
+ "incidentSeverityId",
841
+ "alertSeverityId",
842
+ ]) {
843
+ expect(rule.getColumnAccessControlFor(columnName)?.update).toEqual([]);
844
+ }
845
+ });
846
+ });
847
+
848
+ describe("R4 - the new permissions are opt-in, catalogued, and never auto-granted", () => {
849
+ test.each(NEW_GRANULAR_PERMISSIONS)(
850
+ "%s has a catalogue entry, so getTitle does not throw at runtime",
851
+ (permission: Permission): void => {
852
+ /*
853
+ * A granular permission needs BOTH the enum member and a PermissionProps
854
+ * entry. Without the entry, PermissionHelper.getTitle throws
855
+ * BadDataException - and it is called from TablePermission's failure path,
856
+ * so the missing entry would surface as an exception raised while
857
+ * composing the message for a DIFFERENT exception.
858
+ */
859
+ expect(() => {
860
+ return PermissionHelper.getTitle(permission);
861
+ }).not.toThrow();
862
+
863
+ expect(PermissionHelper.getTitle(permission).length).toBeGreaterThan(0);
864
+ expect(
865
+ PermissionHelper.getDescription(permission).length,
866
+ ).toBeGreaterThan(0);
867
+ },
868
+ );
869
+
870
+ test.each(NEW_GRANULAR_PERMISSIONS)(
871
+ "%s is assignable to a tenant but is not a role permission",
872
+ (permission: Permission): void => {
873
+ /*
874
+ * isRolePermission true would file it under getRolePermissionProps(), the
875
+ * list every default project role is built from - so the ability to read
876
+ * and rewrite a colleague's paging configuration would arrive silently
877
+ * with an existing role instead of being handed out on purpose. It also
878
+ * breaks the roles/granular partition asserted in Permission.test.ts.
879
+ */
880
+ const props: PermissionProps | undefined =
881
+ PermissionHelper.getAllPermissionProps().find(
882
+ (item: PermissionProps) => {
883
+ return item.permission === permission;
884
+ },
885
+ );
886
+
887
+ expect(props).toBeDefined();
888
+ expect(props?.isAssignableToTenant).toBe(true);
889
+ expect(props?.isRolePermission).toBe(false);
890
+
891
+ expect(
892
+ PermissionHelper.getRolePermissionProps().map(
893
+ (item: PermissionProps) => {
894
+ return item.permission;
895
+ },
896
+ ),
897
+ ).not.toContain(permission);
898
+
899
+ expect(
900
+ PermissionHelper.getTenantPermissionProps().map(
901
+ (item: PermissionProps) => {
902
+ return item.permission;
903
+ },
904
+ ),
905
+ ).toContain(permission);
906
+ },
907
+ );
908
+
909
+ test.each(NEW_GRANULAR_PERMISSIONS)(
910
+ "%s is not handed to a member who was never granted it",
911
+ (permission: Permission): void => {
912
+ /*
913
+ * Public and CurrentUser are pushed onto every authenticated caller by
914
+ * getUserPermissions itself. Anything that joined them would be held by
915
+ * the entire project the moment it shipped.
916
+ */
917
+ const resolved: Array<Permission> =
918
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
919
+ memberProps(),
920
+ PermissionType.Allow,
921
+ ).map((item: UserPermission) => {
922
+ return item.permission;
923
+ });
924
+
925
+ expect(resolved).toContain(Permission.CurrentUser);
926
+ expect(resolved).not.toContain(permission);
927
+ },
928
+ );
929
+
930
+ test.each(NEW_GRANULAR_PERMISSIONS)(
931
+ "%s counts as a real grant rather than an auto-granted one",
932
+ (permission: Permission): void => {
933
+ /*
934
+ * This is the behavioural statement of "not in
935
+ * AUTO_GRANTED_TENANT_PERMISSIONS", which is module-private to
936
+ * TenantPermission. That list is what the intersection subtracts before
937
+ * deciding whether the caller is here purely as some logged-in user; a new
938
+ * permission added to it would appear to lift the scope while actually
939
+ * lifting it for everybody. Asserting the consequence rather than the
940
+ * constant also survives the constant being renamed.
941
+ */
942
+ expect(
943
+ TenantPermission.isAccessGrantedOnlyByCurrentUser(
944
+ UserNotificationRule,
945
+ tenantProps(permission),
946
+ permission === Permission.ReadProjectUserNotificationRule
947
+ ? DatabaseRequestType.Read
948
+ : DatabaseRequestType.Update,
949
+ ),
950
+ ).toBe(false);
951
+ },
952
+ );
953
+
954
+ test("a caller holding neither is still treated as here-by-CurrentUser-alone", () => {
955
+ // The control for the assertion above - without it, `false` proves nothing.
956
+ expect(
957
+ TenantPermission.isAccessGrantedOnlyByCurrentUser(
958
+ UserNotificationRule,
959
+ memberProps(),
960
+ DatabaseRequestType.Read,
961
+ ),
962
+ ).toBe(true);
963
+ });
964
+
965
+ test.each(NEW_GRANULAR_PERMISSIONS)(
966
+ "%s does not lift the scope on a notification method, however real a grant it is",
967
+ (permission: Permission): void => {
968
+ /*
969
+ * The same helper, pointed at the models the permission deliberately does
970
+ * NOT reach. isAccessGrantedOnlyByCurrentUser answers per model, and here
971
+ * it answers true for every verb because the permission appears in none of
972
+ * these lists - which is what makes the "real grant" result above a
973
+ * statement about the rule table rather than about the permission being
974
+ * powerful everywhere it is held.
975
+ */
976
+ for (const [, modelType] of METHOD_MODELS) {
977
+ for (const requestType of ROW_SCOPED_REQUEST_TYPES) {
978
+ expect(
979
+ TenantPermission.isAccessGrantedOnlyByCurrentUser(
980
+ modelType,
981
+ tenantProps(permission),
982
+ requestType,
983
+ ),
984
+ ).toBe(true);
985
+ }
986
+ }
987
+ },
988
+ );
989
+ });
990
+
991
+ describe("R5 - a project whose teams predate the granular permissions still gets the feature", () => {
992
+ /*
993
+ * Existing project teams are seeded with ROLES only. A brand-new granular
994
+ * permission is therefore held by literally nobody on the day it ships, so a
995
+ * list containing only ReadProjectUserNotificationRule would leave this
996
+ * capability dead on arrival for every project that already exists. The roles
997
+ * are listed ALONGSIDE the granular permission on the rule table for that
998
+ * reason, and this group asserts the consequence rather than trusting the
999
+ * list.
1000
+ */
1001
+
1002
+ test.each([Permission.ProjectOwner, Permission.ProjectAdmin])(
1003
+ "%s alone - with no granular grant at all - can read another member's rules",
1004
+ async (role: Permission): Promise<void> => {
1005
+ const query: Query<BaseModel> =
1006
+ await TenantPermission.addTenantScopeToQuery(
1007
+ UserNotificationRule,
1008
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
1009
+ null,
1010
+ tenantProps(role),
1011
+ DatabaseRequestType.Read,
1012
+ );
1013
+
1014
+ expect(scopedUserIdOf(query)).toBe(OTHER_USER_ID.toString());
1015
+ },
1016
+ );
1017
+
1018
+ test.each([Permission.ProjectOwner, Permission.ProjectAdmin])(
1019
+ "%s alone can repair another member's rules",
1020
+ async (role: Permission): Promise<void> => {
1021
+ for (const requestType of [
1022
+ DatabaseRequestType.Update,
1023
+ DatabaseRequestType.Delete,
1024
+ ]) {
1025
+ const query: Query<BaseModel> =
1026
+ await TenantPermission.addTenantScopeToQuery(
1027
+ UserNotificationRule,
1028
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
1029
+ null,
1030
+ tenantProps(role),
1031
+ requestType,
1032
+ );
1033
+
1034
+ expect(scopedUserIdOf(query)).toBe(OTHER_USER_ID.toString());
1035
+ }
1036
+ },
1037
+ );
1038
+
1039
+ test.each(METHOD_MODELS)(
1040
+ "%s is unreadable by a role holder too, so the reach stops at the rule table",
1041
+ async (_name: string, modelType: ModelConstructor): Promise<void> => {
1042
+ /*
1043
+ * The boundary of the previous two assertions. ProjectOwner is the most
1044
+ * powerful role a project has, and it still cannot reach a colleague's
1045
+ * notification method - so "an administrator can see a member's paging
1046
+ * configuration" means the RULES and their method FK ids, and stops
1047
+ * exactly there. Asserted per role rather than only for the granular
1048
+ * permission because a role is what an existing project actually holds.
1049
+ */
1050
+ for (const role of [Permission.ProjectOwner, Permission.ProjectAdmin]) {
1051
+ await expect(
1052
+ TenantPermission.addTenantScopeToQuery(
1053
+ modelType,
1054
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
1055
+ null,
1056
+ tenantProps(role),
1057
+ DatabaseRequestType.Read,
1058
+ ),
1059
+ ).rejects.toThrow(NotAuthorizedException);
1060
+ }
1061
+ },
1062
+ );
1063
+
1064
+ test("both roles sit beside the granular permission in every rule-table list", () => {
1065
+ const rule: UserNotificationRule = new UserNotificationRule();
1066
+
1067
+ expect(rule.getReadPermissions()).toContain(Permission.ProjectOwner);
1068
+ expect(rule.getReadPermissions()).toContain(Permission.ProjectAdmin);
1069
+ expect(rule.getReadPermissions()).toContain(
1070
+ Permission.ReadProjectUserNotificationRule,
1071
+ );
1072
+
1073
+ for (const list of [
1074
+ rule.getCreatePermissions(),
1075
+ rule.getUpdatePermissions(),
1076
+ rule.getDeletePermissions(),
1077
+ ]) {
1078
+ expect(list).toContain(Permission.ProjectOwner);
1079
+ expect(list).toContain(Permission.ProjectAdmin);
1080
+ expect(list).toContain(Permission.EditProjectUserNotificationRule);
1081
+ }
1082
+ });
1083
+
1084
+ test.each(METHOD_MODELS)(
1085
+ "neither role nor granular permission appears anywhere in %s's lists",
1086
+ (_name: string, modelType: ModelConstructor): void => {
1087
+ /*
1088
+ * The complement of the assertion above, stated as a prohibition so the
1089
+ * reason is legible at the point of failure. The literal-equality test in
1090
+ * the methods group already fails if any of these arrive; this one fails
1091
+ * with a message that names WHICH permission arrived, which is the
1092
+ * difference between a five-second diagnosis and a five-minute one.
1093
+ */
1094
+ const model: BaseModel = new modelType();
1095
+
1096
+ for (const list of [
1097
+ model.getReadPermissions(),
1098
+ model.getCreatePermissions(),
1099
+ model.getUpdatePermissions(),
1100
+ model.getDeletePermissions(),
1101
+ ]) {
1102
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
1103
+ expect(list).not.toContain(grant);
1104
+ }
1105
+ }
1106
+ },
1107
+ );
1108
+ });
1109
+
1110
+ describe("the create-side ownership gate survived the widening", () => {
1111
+ /*
1112
+ * Phase 2.5 closed a hole in which create checked column NAMES but never the
1113
+ * ownership VALUE, so a caller could write a row owned by somebody else. That
1114
+ * gate is permission-AWARE: CurrentUser alone means "you may create rows for
1115
+ * yourself and nobody else", while a real role permission in the create list
1116
+ * makes it defer.
1117
+ *
1118
+ * Widening the rule table's create list is exactly the change that could have
1119
+ * weakened it, because a member's intersection now has more candidates to
1120
+ * survive. It does not - the member holds none of the added permissions - but
1121
+ * that is a fact about the caller, not about the list, so it needs asserting.
1122
+ */
1123
+
1124
+ test("a plain member still cannot create a rule owned by another user", () => {
1125
+ expect(() => {
1126
+ return CreatePermission.checkCreatePermissions(
1127
+ UserNotificationRule,
1128
+ makeRow(UserNotificationRule, OTHER_USER_ID) as never,
1129
+ memberProps(),
1130
+ );
1131
+ }).toThrow(NotAuthorizedException);
1132
+ });
1133
+
1134
+ test("the refusal still names the problem", () => {
1135
+ expect(() => {
1136
+ return CreatePermission.checkCreatePermissions(
1137
+ UserNotificationRule,
1138
+ makeRow(UserNotificationRule, OTHER_USER_ID) as never,
1139
+ memberProps(),
1140
+ );
1141
+ }).toThrow(/another user's/i);
1142
+ });
1143
+
1144
+ test("a member creating a rule for themselves is untouched", () => {
1145
+ expect(() => {
1146
+ return CreatePermission.checkCreatePermissions(
1147
+ UserNotificationRule,
1148
+ makeRow(UserNotificationRule, CALLER_ID) as never,
1149
+ memberProps(),
1150
+ );
1151
+ }).not.toThrow();
1152
+ });
1153
+
1154
+ test("a member omitting the owner is stamped rather than refused", () => {
1155
+ const row: BaseModel = makeRow(UserNotificationRule, undefined);
1156
+
1157
+ CreatePermission.checkCreatePermissions(
1158
+ UserNotificationRule,
1159
+ row as never,
1160
+ memberProps(),
1161
+ );
1162
+
1163
+ expect(row.getColumnValue("userId")?.toString()).toBe(CALLER_ID.toString());
1164
+ });
1165
+
1166
+ test("a member cannot plant an opt-out row to silence somebody else", () => {
1167
+ /*
1168
+ * The denial-of-paging direction of the same hole. An opt-out row is how a
1169
+ * user says "do not page me here"; planted on somebody else it suppresses
1170
+ * their pages AND suppresses the fallback that would otherwise rescue them,
1171
+ * because it is a genuine matching row.
1172
+ */
1173
+ const optOut: UserNotificationRule = new UserNotificationRule();
1174
+ optOut.projectId = PROJECT_ID;
1175
+ optOut.userId = OTHER_USER_ID;
1176
+ optOut.isOptOut = true;
1177
+
1178
+ expect(() => {
1179
+ return CreatePermission.checkCreatePermissions(
1180
+ UserNotificationRule,
1181
+ optOut,
1182
+ memberProps(),
1183
+ );
1184
+ }).toThrow(NotAuthorizedException);
1185
+ });
1186
+
1187
+ test.each(ADMIN_WRITE_GRANTS)(
1188
+ "%s reaches the service layer rather than being refused by the framework gate",
1189
+ (grant: Permission): void => {
1190
+ /*
1191
+ * The second branch of the create gate, and the one this phase walks
1192
+ * through: a caller holding a real role permission in the create list is
1193
+ * not stamped, so provisioning a rule for a member who has none is
1194
+ * possible at all.
1195
+ *
1196
+ * THE FRAMEWORK GATE IS NOT THE WHOLE CHECK. It deliberately says nothing
1197
+ * about WHICH user may be named, which is why UserNotificationRuleService
1198
+ * separately verifies that the supplied userId is a member of
1199
+ * props.tenantId before the row is written - without that, this branch
1200
+ * hands any administrator the ability to write rules for a user id
1201
+ * belonging to another project entirely. Do not read a passing test here
1202
+ * as "on-behalf-of creation is safe"; read it as "the framework defers,
1203
+ * and the service is where the answer lives".
1204
+ */
1205
+ expect(() => {
1206
+ return CreatePermission.checkCreatePermissions(
1207
+ UserNotificationRule,
1208
+ makeRow(UserNotificationRule, OTHER_USER_ID) as never,
1209
+ tenantProps(grant),
1210
+ );
1211
+ }).not.toThrow();
1212
+ },
1213
+ );
1214
+
1215
+ test("a read-only grant does not become a create grant by sitting on the same model", () => {
1216
+ /*
1217
+ * ReadProjectUserNotificationRule appears in no create list anywhere, so its
1218
+ * holder's intersection is the auto-granted CurrentUser alone and the gate
1219
+ * enforces ownership on them exactly as on a plain member. Worth its own
1220
+ * test because the two granular permissions are granted and revoked
1221
+ * together in practice, which makes it easy to assume they behave alike.
1222
+ */
1223
+ expect(() => {
1224
+ return CreatePermission.checkCreatePermissions(
1225
+ UserNotificationRule,
1226
+ makeRow(UserNotificationRule, OTHER_USER_ID) as never,
1227
+ tenantProps(Permission.ReadProjectUserNotificationRule),
1228
+ );
1229
+ }).toThrow(NotAuthorizedException);
1230
+ });
1231
+ });
1232
+
1233
+ describe("the credentials behind a notification method, guarded by the table scope alone", () => {
1234
+ /*
1235
+ * REPLACES the group that used to guard these five columns with a per-column
1236
+ * decorator. That decorator - @OwnerOnlyColumn - is gone, and so is the
1237
+ * widening that made it necessary. The invariant it protected did not go
1238
+ * anywhere: an administrator learns THAT a member has a webhook, never where
1239
+ * it points.
1240
+ *
1241
+ * What enforces it now is the table scope itself, which is why this group is
1242
+ * a quarter of the size of the one it replaces. There is no longer a
1243
+ * column-level decision to verify per column and per query shape, because
1244
+ * there is no query on these models that reaches another member's ROW - so
1245
+ * every column on that row, credential or label, is out of reach for the same
1246
+ * single reason. The five columns are still enumerated rather than reduced to
1247
+ * one representative, because they are what a reader comes here looking for
1248
+ * and because "the row is unreachable" is a claim worth checking against the
1249
+ * things that would actually matter if it were false.
1250
+ *
1251
+ * The columns: the webhook URL and its signing secret (both bearer
1252
+ * credentials - anyone holding them can impersonate OneUptime to the member's
1253
+ * endpoint), the push device token, the Telegram chat id an unverified bot
1254
+ * message can be addressed to, and the verification code that turns
1255
+ * possession of that chat id into a verified method.
1256
+ */
1257
+
1258
+ /*
1259
+ * Ordered name-then-column so the two %s placeholders in a test title consume
1260
+ * the two strings; a model constructor in a substituted position prints its
1261
+ * entire transpiled class body as the test name.
1262
+ */
1263
+ const CREDENTIAL_COLUMNS: Array<[string, string, ModelConstructor]> = [
1264
+ ["UserWebhook", "webhookUrl", UserWebhook],
1265
+ ["UserWebhook", "secret", UserWebhook],
1266
+ ["UserPush", "deviceToken", UserPush],
1267
+ ["UserTelegram", "telegramChatId", UserTelegram],
1268
+ ["UserTelegram", "verificationCode", UserTelegram],
1269
+ ];
1270
+
1271
+ test.each(CREDENTIAL_COLUMNS)(
1272
+ "an administrator selecting %s.%s on another member's row never reaches the row",
1273
+ async (
1274
+ _name: string,
1275
+ column: string,
1276
+ modelType: ModelConstructor,
1277
+ ): Promise<void> => {
1278
+ /*
1279
+ * Driven through BasePermission.checkPermissions - the entry point
1280
+ * ReadPermission.checkReadPermission and therefore every findBy goes
1281
+ * through - rather than through the tenant scope alone, so the refusal
1282
+ * asserted is the one a real request receives.
1283
+ */
1284
+ for (const grant of [...ADMIN_READ_GRANTS, ...ADMIN_WRITE_GRANTS]) {
1285
+ await expect(
1286
+ BasePermission.checkPermissions(
1287
+ modelType,
1288
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
1289
+ { [column]: true } as Select<BaseModel>,
1290
+ tenantProps(grant),
1291
+ DatabaseRequestType.Read,
1292
+ ),
1293
+ ).rejects.toThrow(NotAuthorizedException);
1294
+ }
1295
+ },
1296
+ );
1297
+
1298
+ test.each(CREDENTIAL_COLUMNS)(
1299
+ "an unscoped listing that selects %s.%s comes back holding only the caller's own rows",
1300
+ async (
1301
+ _name: string,
1302
+ column: string,
1303
+ modelType: ModelConstructor,
1304
+ ): Promise<void> => {
1305
+ /*
1306
+ * The shape a leak actually arrives through, and the one the refusal
1307
+ * above cannot cover: an admin listing page fetches a project's methods
1308
+ * rather than one member's, so it names no user and cannot be refused for
1309
+ * naming the wrong one. It is allowed - and comes back stamped with the
1310
+ * caller's own id, which is what makes "allowed" harmless.
1311
+ */
1312
+ const { query } = await BasePermission.checkPermissions(
1313
+ modelType,
1314
+ {} as Query<BaseModel>,
1315
+ { [column]: true } as Select<BaseModel>,
1316
+ tenantProps(Permission.ProjectAdmin),
1317
+ DatabaseRequestType.Read,
1318
+ );
1319
+
1320
+ expect(ownerIdOf(query)).toBe(CALLER_ID.toString());
1321
+ },
1322
+ );
1323
+
1324
+ test.each(CREDENTIAL_COLUMNS)(
1325
+ "%s.%s is still delivered to its owner, so nothing was fixed by emptying a list",
1326
+ async (
1327
+ _name: string,
1328
+ column: string,
1329
+ modelType: ModelConstructor,
1330
+ ): Promise<void> => {
1331
+ /*
1332
+ * The owner reads their own credential constantly - the settings page
1333
+ * renders it, verification compares it, delivery uses it. A fix that
1334
+ * wrote `read: []` on these columns would satisfy every refusal above and
1335
+ * break notification setup for every user in every project, so the
1336
+ * companion assertion is not optional.
1337
+ *
1338
+ * Issued as the UNSCOPED query the settings page actually sends, and by a
1339
+ * plain member, because that is the overwhelming majority of traffic to
1340
+ * these endpoints.
1341
+ */
1342
+ await expect(
1343
+ BasePermission.checkPermissions(
1344
+ modelType,
1345
+ {} as Query<BaseModel>,
1346
+ { [column]: true } as Select<BaseModel>,
1347
+ memberProps(),
1348
+ DatabaseRequestType.Read,
1349
+ ),
1350
+ ).resolves.toBeDefined();
1351
+ },
1352
+ );
1353
+ });
1354
+
1355
+ describe("the rule relation - an administrator reads the FK, never the method behind it", () => {
1356
+ /*
1357
+ * THE ONE PATH THAT DOES NOT PASS THROUGH THE METHOD TABLE'S SCOPE, and
1358
+ * therefore the only place the guarantee above has to be re-established
1359
+ * rather than inherited.
1360
+ *
1361
+ * A read of UserNotificationRule is deliberately NOT row-scoped for an
1362
+ * administrator - that is the whole feature. A nested relation select issued
1363
+ * against that read (`select: { userEmail: { email: true } }`) resolves the
1364
+ * method row through the rule's join, so the only scope anywhere in the path
1365
+ * is the one the RULE table decided. Two checks stand between the request and
1366
+ * the row, and it is worth knowing exactly what each of them looks at:
1367
+ *
1368
+ * - SelectPermission.checkSelectPermission validates the OUTER key
1369
+ * (`userEmail`) against UserNotificationRule's column list, by name.
1370
+ * - QueryPermission.checkRelationQueryPermission validates the INNER key
1371
+ * (`email`) against UserEmail's column list - but only after consulting
1372
+ * that column's `canReadOnRelationQuery` flag, and a column carrying
1373
+ * `true` is waved through with no permission check at all. The related
1374
+ * model's TABLE list is never consulted by either check.
1375
+ *
1376
+ * Neither check can see the query, so neither can tell whose row this is.
1377
+ * That leaves the outer column list as the only place the decision can be
1378
+ * made, and it is why the seven method RELATION columns on
1379
+ * UserNotificationRule are `read: [Permission.CurrentUser]` while the seven
1380
+ * FK ID columns beside them are admin-readable. The pairing is the design in
1381
+ * one line: an administrator may learn WHICH method a rule points at, and may
1382
+ * re-point it at a different one, without ever learning what the method IS.
1383
+ * The id is a foreign key rather than a secret; the identifier behind it is
1384
+ * the secret, and OnCallReadinessService is where an administrator gets a
1385
+ * masked version of it.
1386
+ *
1387
+ * Asserted through the real permission entry point rather than by reading the
1388
+ * decorators, because the decorator is the mechanism and the refusal is the
1389
+ * guarantee - and this is precisely the path on which a plausible-looking
1390
+ * decorator has already failed to produce the refusal once.
1391
+ */
1392
+
1393
+ const METHOD_FK_COLUMNS: Array<string> = [
1394
+ "userEmailId",
1395
+ "userSmsId",
1396
+ "userCallId",
1397
+ "userPushId",
1398
+ "userWhatsAppId",
1399
+ "userTelegramId",
1400
+ "userWebhookId",
1401
+ ];
1402
+
1403
+ /*
1404
+ * Relation column, the identifier column selected through it, and the model
1405
+ * it resolves to. One entry per method, each naming the field that IS the
1406
+ * credential or the contact point for that channel.
1407
+ */
1408
+ const RELATION_SELECT_SHAPES: Array<[string, string]> = [
1409
+ ["userEmail", "email"],
1410
+ ["userSms", "phone"],
1411
+ ["userCall", "phone"],
1412
+ ["userPush", "deviceToken"],
1413
+ ["userWhatsApp", "phone"],
1414
+ ["userTelegram", "telegramChatId"],
1415
+ ["userWebhook", "webhookUrl"],
1416
+ ];
1417
+
1418
+ test.each(ADMIN_READ_GRANTS)(
1419
+ "%s reads another member's rule together with every method FK id",
1420
+ async (grant: Permission): Promise<void> => {
1421
+ /*
1422
+ * The capability half, and it has to pass or the refusal below has taken
1423
+ * the feature with it. Selecting all seven ids at once rather than one
1424
+ * representative, because the admin surface renders the whole row and a
1425
+ * single narrowed column would break the page while six tests still
1426
+ * passed.
1427
+ */
1428
+ const { query } = await BasePermission.checkPermissions(
1429
+ UserNotificationRule,
1430
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
1431
+ {
1432
+ ...Object.fromEntries(
1433
+ METHOD_FK_COLUMNS.map((column: string) => {
1434
+ return [column, true];
1435
+ }),
1436
+ ),
1437
+ userId: true,
1438
+ ruleType: true,
1439
+ } as Select<BaseModel>,
1440
+ tenantProps(grant),
1441
+ DatabaseRequestType.Read,
1442
+ );
1443
+
1444
+ expect(ownerIdOf(query)).toBe(OTHER_USER_ID.toString());
1445
+ },
1446
+ );
1447
+
1448
+ test("an administrator selecting a method's fields through the relation is refused", async () => {
1449
+ /*
1450
+ * The guarantee, run across every method and every permission that opens
1451
+ * the rule table, and collected rather than asserted one at a time so a
1452
+ * single failure names every combination that leaks instead of stopping at
1453
+ * the first.
1454
+ *
1455
+ * Deliberately agnostic about HOW the framework refuses: throwing is what
1456
+ * the select gate does today, but stripping the relation from the select
1457
+ * would be equally sound. What must never happen is the nested selection
1458
+ * surviving into the read the database is asked to perform, because at that
1459
+ * point the join has already been written against a query scoped to
1460
+ * somebody else.
1461
+ */
1462
+ const leaked: Array<string> = [];
1463
+
1464
+ for (const [relation, column] of RELATION_SELECT_SHAPES) {
1465
+ for (const grant of ADMIN_READ_GRANTS) {
1466
+ const select: Select<BaseModel> = {
1467
+ [relation]: { [column]: true },
1468
+ } as Select<BaseModel>;
1469
+
1470
+ let refused: boolean = false;
1471
+
1472
+ try {
1473
+ await BasePermission.checkPermissions(
1474
+ UserNotificationRule,
1475
+ { userId: OTHER_USER_ID } as Query<BaseModel>,
1476
+ select,
1477
+ tenantProps(grant),
1478
+ DatabaseRequestType.Read,
1479
+ );
1480
+ } catch {
1481
+ refused = true;
1482
+ }
1483
+
1484
+ if (
1485
+ !refused &&
1486
+ (select as Record<string, unknown>)[relation] !== undefined
1487
+ ) {
1488
+ leaked.push(`${relation}.${column} to ${grant}`);
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ expect(leaked).toEqual([]);
1494
+ });
1495
+
1496
+ test("a member selecting a method's fields through the relation on their OWN rules still works", async () => {
1497
+ /*
1498
+ * The constraint the refusal has to respect, and the reason it cannot be
1499
+ * implemented by narrowing the related column or by clearing
1500
+ * canReadOnRelationQuery on it. Every notification-rule table in the
1501
+ * dashboard renders "which address does this rule page" for its owner, and
1502
+ * that render is exactly this select issued by a plain member whose query
1503
+ * carries no userId until TenantPermission stamps one on.
1504
+ *
1505
+ * A fix that inspects the caller's raw query rather than the scoped one
1506
+ * sees no userId here, concludes the read is not owner-scoped, and breaks
1507
+ * notification setup for every user in every project.
1508
+ */
1509
+ for (const [relation, column] of RELATION_SELECT_SHAPES) {
1510
+ await expect(
1511
+ BasePermission.checkPermissions(
1512
+ UserNotificationRule,
1513
+ {} as Query<BaseModel>,
1514
+ { [relation]: { [column]: true } } as Select<BaseModel>,
1515
+ memberProps(),
1516
+ DatabaseRequestType.Read,
1517
+ ),
1518
+ ).resolves.toBeDefined();
1519
+ }
1520
+ });
1521
+
1522
+ test("the FK id columns are admin-readable and the relation columns beside them are not", () => {
1523
+ /*
1524
+ * The decorator-level statement of the pairing the two behavioural tests
1525
+ * above assert from either side. Kept because the behaviour and the
1526
+ * declaration can drift in one direction silently: a relation column
1527
+ * widened to match its FK sibling "for consistency" is a one-line change
1528
+ * that reads as tidying, and this fails on the commit that makes it rather
1529
+ * than on the request that exploits it.
1530
+ */
1531
+ const rule: UserNotificationRule = new UserNotificationRule();
1532
+
1533
+ for (const column of METHOD_FK_COLUMNS) {
1534
+ expect(rule.getColumnAccessControlFor(column)?.read).toEqual([
1535
+ Permission.CurrentUser,
1536
+ ...ADMIN_READ_GRANTS,
1537
+ ]);
1538
+ }
1539
+
1540
+ for (const [relation] of RELATION_SELECT_SHAPES) {
1541
+ expect(rule.getColumnAccessControlFor(relation)?.read).toEqual([
1542
+ Permission.CurrentUser,
1543
+ ]);
1544
+ }
1545
+ });
1546
+ });