@memberjunction/server 6.2.0-edge.2 → 6.2.0-edge.4

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 (435) hide show
  1. package/README.md +30 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +25 -0
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -0
  5. package/dist/agentSessions/BridgeHostInstance.js +30 -0
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -0
  7. package/dist/agentSessions/HostInstance.d.ts +75 -11
  8. package/dist/agentSessions/HostInstance.d.ts.map +1 -1
  9. package/dist/agentSessions/HostInstance.js +99 -28
  10. package/dist/agentSessions/HostInstance.js.map +1 -1
  11. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  12. package/dist/agentSessions/ReturningVisitorRecap.js +7 -3
  13. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  14. package/dist/agentSessions/SessionJanitor.d.ts +173 -25
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +452 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +43 -2
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +99 -8
  21. package/dist/agentSessions/SessionManager.js.map +1 -1
  22. package/dist/agentSessions/SessionRunIDVerifier.d.ts +63 -0
  23. package/dist/agentSessions/SessionRunIDVerifier.d.ts.map +1 -0
  24. package/dist/agentSessions/SessionRunIDVerifier.js +129 -0
  25. package/dist/agentSessions/SessionRunIDVerifier.js.map +1 -0
  26. package/dist/agentSessions/index.d.ts +2 -0
  27. package/dist/agentSessions/index.d.ts.map +1 -1
  28. package/dist/agentSessions/index.js +10 -3
  29. package/dist/agentSessions/index.js.map +1 -1
  30. package/dist/auth/actionAuthorization.d.ts +40 -0
  31. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  32. package/dist/auth/actionAuthorization.js +94 -0
  33. package/dist/auth/actionAuthorization.js.map +1 -0
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +3 -10
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  39. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  40. package/dist/auth/newUsers.d.ts +10 -0
  41. package/dist/auth/newUsers.d.ts.map +1 -1
  42. package/dist/auth/newUsers.js +80 -11
  43. package/dist/auth/newUsers.js.map +1 -1
  44. package/dist/config.d.ts +4567 -71
  45. package/dist/config.d.ts.map +1 -1
  46. package/dist/config.js +246 -3
  47. package/dist/config.js.map +1 -1
  48. package/dist/context.d.ts +8 -0
  49. package/dist/context.d.ts.map +1 -1
  50. package/dist/context.js +25 -34
  51. package/dist/context.js.map +1 -1
  52. package/dist/generated/graphql-schemas/__mj.d.ts +1073 -10
  53. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  54. package/dist/generated/graphql-schemas/__mj.js +9188 -3649
  55. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  56. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  57. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  58. package/dist/generic/ResolverBase.d.ts +62 -6
  59. package/dist/generic/ResolverBase.d.ts.map +1 -1
  60. package/dist/generic/ResolverBase.js +179 -35
  61. package/dist/generic/ResolverBase.js.map +1 -1
  62. package/dist/generic/RunViewResolver.d.ts +4 -0
  63. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  64. package/dist/generic/RunViewResolver.js +37 -1
  65. package/dist/generic/RunViewResolver.js.map +1 -1
  66. package/dist/index.d.ts +7 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +214 -159
  69. package/dist/index.js.map +1 -1
  70. package/dist/isolatedProvider.d.ts +20 -0
  71. package/dist/isolatedProvider.d.ts.map +1 -0
  72. package/dist/isolatedProvider.js +22 -0
  73. package/dist/isolatedProvider.js.map +1 -0
  74. package/dist/postgresqlCompat.d.ts +8 -0
  75. package/dist/postgresqlCompat.d.ts.map +1 -0
  76. package/dist/postgresqlCompat.js +19 -0
  77. package/dist/postgresqlCompat.js.map +1 -0
  78. package/dist/postgresqlPoolSettings.d.ts +108 -0
  79. package/dist/postgresqlPoolSettings.d.ts.map +1 -0
  80. package/dist/postgresqlPoolSettings.js +134 -0
  81. package/dist/postgresqlPoolSettings.js.map +1 -0
  82. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  83. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  84. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  85. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  86. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  87. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  88. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  89. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  90. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  91. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  92. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  93. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  94. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  95. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  96. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +138 -0
  97. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  98. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  99. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  100. package/dist/realtimeSessions/RealtimeVerifyRouter.js +98 -0
  101. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  102. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  103. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  104. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  105. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  106. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  107. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  108. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  109. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  110. package/dist/realtimeSessions/index.d.ts +17 -0
  111. package/dist/realtimeSessions/index.d.ts.map +1 -0
  112. package/dist/realtimeSessions/index.js +17 -0
  113. package/dist/realtimeSessions/index.js.map +1 -0
  114. package/dist/realtimeSessions/sessionAccess.d.ts +86 -0
  115. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  116. package/dist/realtimeSessions/sessionAccess.js +90 -0
  117. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  118. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  119. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  120. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  121. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  122. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  123. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  124. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  125. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  126. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  127. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  128. package/dist/realtimeSessions/verificationCore.js +356 -0
  129. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  130. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  131. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  132. package/dist/realtimeSessions/verificationPages.js +110 -0
  133. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  134. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  135. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  136. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  137. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  138. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  139. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  140. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  141. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  142. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  143. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  144. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  145. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  146. package/dist/resolvers/ActionResolver.d.ts +31 -5
  147. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  148. package/dist/resolvers/ActionResolver.js +75 -14
  149. package/dist/resolvers/ActionResolver.js.map +1 -1
  150. package/dist/resolvers/AdhocQueryResolver.d.ts +35 -17
  151. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  152. package/dist/resolvers/AdhocQueryResolver.js +92 -166
  153. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  154. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  155. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  156. package/dist/resolvers/CacheStatsResolver.js +113 -2
  157. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  158. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  159. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  160. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  161. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  162. package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
  163. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  164. package/dist/resolvers/EntityRecordNameResolver.js +9 -47
  165. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  166. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +12 -1
  167. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  168. package/dist/resolvers/FetchEntityVectorsResolver.js +83 -2
  169. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  170. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  171. package/dist/resolvers/GetDataResolver.js +9 -0
  172. package/dist/resolvers/GetDataResolver.js.map +1 -1
  173. package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
  174. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
  175. package/dist/resolvers/HumanHandoffResolver.js +330 -0
  176. package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
  177. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  178. package/dist/resolvers/MCPResolver.js +9 -4
  179. package/dist/resolvers/MCPResolver.js.map +1 -1
  180. package/dist/resolvers/MeetingResolver.d.ts +69 -0
  181. package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
  182. package/dist/resolvers/MeetingResolver.js +381 -0
  183. package/dist/resolvers/MeetingResolver.js.map +1 -0
  184. package/dist/resolvers/RealtimeBridgeResolver.d.ts +88 -3
  185. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  186. package/dist/resolvers/RealtimeBridgeResolver.js +352 -12
  187. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  188. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +156 -108
  189. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  190. package/dist/resolvers/RealtimeClientSessionResolver.js +395 -293
  191. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  192. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  193. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  194. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  195. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  196. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  197. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  198. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  199. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  200. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  201. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  202. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  204. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  205. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  206. package/dist/resolvers/RunAIAgentResolver.js +68 -11
  207. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  208. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunTemplateResolver.js +12 -11
  210. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  211. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  212. package/dist/resolvers/SyncDataResolver.js +6 -2
  213. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  214. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  215. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  216. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  217. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  218. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  219. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  220. package/dist/resolvers/UserAvatarResolver.js +124 -0
  221. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  222. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  223. package/dist/resolvers/UserResolver.js +2 -17
  224. package/dist/resolvers/UserResolver.js.map +1 -1
  225. package/dist/resolvers/adhocTimeout.d.ts +7 -0
  226. package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
  227. package/dist/resolvers/adhocTimeout.js +12 -0
  228. package/dist/resolvers/adhocTimeout.js.map +1 -0
  229. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  230. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  231. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  232. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  233. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  234. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  235. package/dist/resolvers/avatarInputValidation.js +97 -0
  236. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  237. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  238. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  239. package/dist/resolvers/currentUserRoles.js +30 -0
  240. package/dist/resolvers/currentUserRoles.js.map +1 -1
  241. package/dist/resolvers/meetings/index.d.ts +5 -0
  242. package/dist/resolvers/meetings/index.d.ts.map +1 -0
  243. package/dist/resolvers/meetings/index.js +5 -0
  244. package/dist/resolvers/meetings/index.js.map +1 -0
  245. package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
  246. package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
  247. package/dist/resolvers/meetings/meetingHelpers.js +212 -0
  248. package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
  249. package/dist/resolvers/meetings/meetingMutations.d.ts +18 -0
  250. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
  251. package/dist/resolvers/meetings/meetingMutations.js +439 -0
  252. package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
  253. package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
  254. package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
  255. package/dist/resolvers/meetings/meetingQueries.js +113 -0
  256. package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
  257. package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
  258. package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
  259. package/dist/resolvers/meetings/meetingTypes.js +345 -0
  260. package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
  261. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  262. package/dist/rest/ViewOperationsHandler.js +8 -5
  263. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  264. package/dist/services/TaskGraphActionRunner.d.ts +8 -0
  265. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  266. package/dist/services/TaskGraphActionRunner.js +27 -1
  267. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  268. package/dist/services/TaskGraphContinuationDeliverer.d.ts +4 -3
  269. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  270. package/dist/services/TaskGraphContinuationDeliverer.js +13 -7
  271. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  272. package/dist/services/WorkQueueHostService.d.ts +36 -0
  273. package/dist/services/WorkQueueHostService.d.ts.map +1 -0
  274. package/dist/services/WorkQueueHostService.js +97 -0
  275. package/dist/services/WorkQueueHostService.js.map +1 -0
  276. package/dist/services/workQueueConfig.d.ts +103 -0
  277. package/dist/services/workQueueConfig.d.ts.map +1 -0
  278. package/dist/services/workQueueConfig.js +48 -0
  279. package/dist/services/workQueueConfig.js.map +1 -0
  280. package/dist/sharedCache.d.ts +105 -0
  281. package/dist/sharedCache.d.ts.map +1 -0
  282. package/dist/sharedCache.js +276 -0
  283. package/dist/sharedCache.js.map +1 -0
  284. package/dist/types.d.ts +11 -0
  285. package/dist/types.d.ts.map +1 -1
  286. package/dist/types.js.map +1 -1
  287. package/package.json +97 -94
  288. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  289. package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
  290. package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
  291. package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
  292. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  293. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  294. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  295. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
  296. package/src/__tests__/FetchEntityVectorsResolver.test.ts +285 -0
  297. package/src/__tests__/HostInstance.test.ts +84 -0
  298. package/src/__tests__/HumanHandoffResolver.test.ts +250 -0
  299. package/src/__tests__/MeetingResolver.test.ts +448 -0
  300. package/src/__tests__/RealtimeBridgeResolver.test.ts +651 -4
  301. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  302. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  303. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  304. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  305. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  306. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
  307. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  308. package/src/__tests__/ResolverBase.clientOldValues.test.ts +6 -3
  309. package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
  310. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
  311. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  312. package/src/__tests__/RunAIAgentResolver.branch.test.ts +169 -0
  313. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +4 -4
  314. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  315. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  316. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  317. package/src/__tests__/SessionJanitor.test.ts +845 -18
  318. package/src/__tests__/SessionManager.test.ts +213 -17
  319. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  320. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  321. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +41 -0
  322. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +50 -3
  323. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  324. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  325. package/src/__tests__/WorkQueueHostService.test.ts +133 -0
  326. package/src/__tests__/actionAuthorization.test.ts +130 -0
  327. package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
  328. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  329. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  330. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  331. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  332. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  333. package/src/__tests__/newUsers.template.test.ts +151 -0
  334. package/src/__tests__/newUsers.test.ts +74 -5
  335. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  336. package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
  337. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  338. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  339. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  340. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  341. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  342. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  343. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  344. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  345. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  346. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  347. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  348. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  349. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  350. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  351. package/src/__tests__/serverExtensionReservedRoots.test.ts +2 -0
  352. package/src/__tests__/sharedCache.test.ts +310 -0
  353. package/src/__tests__/translateBracketsToPG.test.ts +20 -0
  354. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  355. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  356. package/src/agentSessions/HostInstance.ts +124 -28
  357. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  358. package/src/agentSessions/SessionJanitor.ts +543 -57
  359. package/src/agentSessions/SessionManager.ts +125 -25
  360. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  361. package/src/agentSessions/index.ts +10 -3
  362. package/src/auth/actionAuthorization.ts +116 -0
  363. package/src/auth/index.ts +3 -8
  364. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  365. package/src/auth/newUsers.ts +99 -11
  366. package/src/config.ts +255 -3
  367. package/src/context.ts +26 -34
  368. package/src/generated/graphql-schemas/__mj.ts +22701 -18838
  369. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  370. package/src/generic/ResolverBase.ts +210 -36
  371. package/src/generic/RunViewResolver.ts +37 -2
  372. package/src/index.ts +236 -170
  373. package/src/isolatedProvider.ts +22 -0
  374. package/src/postgresqlCompat.ts +21 -0
  375. package/src/postgresqlPoolSettings.ts +213 -0
  376. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  377. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  378. package/src/realtimeSessions/README.md +179 -0
  379. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  380. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +167 -0
  381. package/src/realtimeSessions/RealtimeVerifyRouter.ts +108 -0
  382. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  383. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  384. package/src/realtimeSessions/index.ts +16 -0
  385. package/src/realtimeSessions/sessionAccess.ts +141 -0
  386. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  387. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  388. package/src/realtimeSessions/verificationCore.ts +502 -0
  389. package/src/realtimeSessions/verificationPages.ts +149 -0
  390. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  391. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  392. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  393. package/src/resolvers/ActionResolver.ts +84 -21
  394. package/src/resolvers/AdhocQueryResolver.ts +105 -187
  395. package/src/resolvers/CacheStatsResolver.ts +89 -2
  396. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  397. package/src/resolvers/EntityRecordNameResolver.ts +9 -51
  398. package/src/resolvers/FetchEntityVectorsResolver.ts +97 -2
  399. package/src/resolvers/GetDataResolver.ts +10 -0
  400. package/src/resolvers/HumanHandoffResolver.ts +312 -0
  401. package/src/resolvers/MCPResolver.ts +8 -5
  402. package/src/resolvers/MeetingResolver.ts +438 -0
  403. package/src/resolvers/RealtimeBridgeResolver.ts +385 -19
  404. package/src/resolvers/RealtimeClientSessionResolver.ts +503 -344
  405. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  406. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  407. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  408. package/src/resolvers/RunAIAgentResolver.ts +78 -11
  409. package/src/resolvers/RunTemplateResolver.ts +13 -12
  410. package/src/resolvers/SyncDataResolver.ts +6 -2
  411. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  412. package/src/resolvers/UserAvatarResolver.ts +111 -0
  413. package/src/resolvers/UserResolver.ts +2 -16
  414. package/src/resolvers/adhocTimeout.ts +12 -0
  415. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  416. package/src/resolvers/avatarInputValidation.ts +123 -0
  417. package/src/resolvers/currentUserRoles.ts +32 -1
  418. package/src/resolvers/meetings/index.ts +4 -0
  419. package/src/resolvers/meetings/meetingHelpers.ts +307 -0
  420. package/src/resolvers/meetings/meetingMutations.ts +556 -0
  421. package/src/resolvers/meetings/meetingQueries.ts +172 -0
  422. package/src/resolvers/meetings/meetingTypes.ts +238 -0
  423. package/src/rest/ViewOperationsHandler.ts +8 -5
  424. package/src/services/TaskGraphActionRunner.ts +29 -1
  425. package/src/services/TaskGraphContinuationDeliverer.ts +13 -7
  426. package/src/services/WorkQueueHostService.ts +129 -0
  427. package/src/services/workQueueConfig.ts +52 -0
  428. package/src/sharedCache.ts +302 -0
  429. package/src/types.ts +12 -0
  430. package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
  431. package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
  432. package/dist/resolvers/adhoc-query-helpers.js +0 -50
  433. package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
  434. package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
  435. package/src/resolvers/adhoc-query-helpers.ts +0 -67
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ dotenv.config({ quiet: true });
4
4
 
5
5
  import { expressMiddleware } from '@as-integrations/express5';
6
6
  import { mergeSchemas } from '@graphql-tools/schema';
7
- import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ResolveStartupMode } from '@memberjunction/core';
7
+ import { Metadata, DatabasePlatform, SetProvider, StartupManager as StartupManagerImport, BaseEntity, BaseEntityEvent, RunView, DatabaseProviderBase, ProviderBase, ResolveStartupMode } from '@memberjunction/core';
8
8
  import { UserCache, resolveDbPlatformFromEnv } from '@memberjunction/generic-database-provider';
9
9
  import { MJGlobal, MJEventType, UUIDsEqual, ShutdownRegistry } from '@memberjunction/global';
10
10
  import { setupSQLServerClient, SQLServerDataProvider, SQLServerProviderConfigData } from '@memberjunction/sqlserver-dataprovider';
@@ -19,9 +19,10 @@ import { default as fg } from 'fast-glob';
19
19
  import { useServer } from 'graphql-ws/lib/use/ws';
20
20
  import { createServer } from 'node:http';
21
21
  import { fileURLToPath } from 'node:url';
22
- import { readFileSync, writeFileSync } from 'node:fs';
22
+ import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
23
+ import { tmpdir } from 'node:os';
23
24
  import { Session as InspectorSession } from 'node:inspector';
24
- import { sep } from 'node:path';
25
+ import { join, sep } from 'node:path';
25
26
  import 'reflect-metadata';
26
27
  import { ReplaySubject } from 'rxjs';
27
28
  import { BuildSchemaOptions, buildSchemaSync, getMetadataStorage, GraphQLTimestamp, PubSubEngine } from 'type-graphql';
@@ -31,8 +32,10 @@ import { WebSocketServer } from 'ws';
31
32
  import { RealtimeProxyServer } from './realtimeProxy/RealtimeProxyServer.js';
32
33
  import buildApolloServer from './apolloServer/index.js';
33
34
  import { configInfo, configFilePath, dbDatabase, dbHost, dbPort, dbUsername, graphqlPort, graphqlRootPath, mj_core_schema, websiteRunFromPackage, RESTApiOptions } from './config.js';
35
+ import { TranslateBracketsToPG } from './postgresqlCompat.js';
36
+ import { BuildPostgreSQLConnectionConfig, DescribeReadOnlyLoginOverreach, PostgreSQLReadOnlyPool, ResolvePostgreSQLEndpoint, ResolvePostgreSQLReadOnlyCredentials, ToPGPoolConfig } from './postgresqlPoolSettings.js';
34
37
  import { default as jwt } from 'jsonwebtoken';
35
- import { contextFunction, CreateUnifiedAuthMiddleware, getUserPayload } from './context.js';
38
+ import { contextFunction, CreateReadOnlyProvider, CreateUnifiedAuthMiddleware, getUserPayload } from './context.js';
36
39
  import { UserPayload } from './types.js';
37
40
  import { requireSystemUserDirective, publicDirective } from './directives/index.js';
38
41
  import { variablesLoggingMiddleware } from './logging/variablesLoggingMiddleware.js';
@@ -48,6 +51,14 @@ import { createRealtimeSdpBrokerRouter } from './rest/RealtimeSdpBrokerHandler.j
48
51
  import { REALTIME_SDP_EXCHANGE_PATH } from '@memberjunction/ai';
49
52
  import { createMagicLinkHandler, createMagicLinkJwksRouter, registerMagicLinkAuthProvider, MAGIC_LINK_MOUNT_PATH } from './auth/magicLink/index.js';
50
53
  import { createWidgetHandler, WIDGET_MOUNT_PATH } from './realtimeWidget/index.js';
54
+ import {
55
+ CreateRealtimeVerifyRouter,
56
+ REALTIME_VERIFY_MOUNT_PATH,
57
+ RealtimeSessionEventService,
58
+ RealtimeSessionVerificationService,
59
+ ParseReplicatedRealtimeSessionEvent,
60
+ type RealtimeSessionEventTopicPayload,
61
+ } from './realtimeSessions/index.js';
51
62
  import { resolve } from 'node:path';
52
63
  import { DataSourceInfo, raiseEvent } from './types.js';
53
64
 
@@ -58,21 +69,28 @@ import { LocalCacheManager, StartupManager, TelemetryManager, TelemetryLevel, Lo
58
69
  import { getSystemUser, validateAuthProvidersRegistered } from './auth/index.js';
59
70
  import { createAuthProviderCatalogRouter, AUTH_CATALOG_MOUNT_PATH } from './auth/AuthProviderCatalogRouter.js';
60
71
  import { GetAPIKeyEngine } from '@memberjunction/api-keys';
61
- import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
62
- import { GenericDatabaseProvider } from '@memberjunction/generic-database-provider';
72
+ import { ActionEngineServer } from '@memberjunction/actions';
73
+ import { CreateAdhocSQLAuthorizer } from './resolvers/AdhocQueryResolver.js';
74
+ import { CacheManagerConfigFromSettings, CreateSharedCacheFromEnvironment, StartEngineSweeper, StartMetadataSweep, StartUserCacheChecks, WarmupLeaseMsFromSettings, WirePushStatusFanOut, WireSharedCacheEvents } from './sharedCache.js';
63
75
  import { PubSubManager } from './generic/PubSubManager.js';
64
76
  import { ReconcileOrphanedConversationDetails } from './generic/OrphanedConversationDetailReconciler.js';
65
77
  import {
66
- PUSH_STATUS_UPDATES_TOPIC,
67
- SetPushStatusPublishHook,
68
- ParseReplicatedStatusUpdate,
69
- } from './generic/PushStatusResolver.js';
78
+ HANDOFF_OFFER_FANOUT_CHANNEL,
79
+ HANDOFF_OFFER_TOPIC,
80
+ SetHandoffOfferPublishHook,
81
+ ParseReplicatedHandoffOfferUpdate,
82
+ } from './resolvers/HumanHandoffResolver.js';
83
+ import { RoomHandoffEngine } from '@memberjunction/livekit-room-server';
84
+ // Type-only: the provider is built in ./sharedCache.ts. Still needed here because the handoff-offer
85
+ // fan-out below stays in this file — see the note at its call site.
86
+ import type { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
70
87
  import { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
71
88
  import { PublishIntegrationProgress } from './resolvers/IntegrationProgressResolver.js';
72
89
  import { RegisterRSUProgressBridge } from './integration/RSUProgressBridge.js';
73
90
  import { ClientToolRequestManager, AgentRunWatchdog } from '@memberjunction/ai-agents';
74
- import { SessionJanitor } from './agentSessions/index.js';
91
+ import { BindBridgeEngineHostInstance, SessionJanitor, SetHostInstancePort, StartBridgeOrphanReconciliation } from './agentSessions/index.js';
75
92
  import { StartTaskGraphDispatcher } from './services/StartTaskGraphDispatcher.js';
93
+ import { MJServerWorkQueueProviderSource, StartWorkQueueHost } from './services/WorkQueueHostService.js';
76
94
  import { GetAttachmentService } from '@memberjunction/aiengine';
77
95
  import { MJStorageBlobStore } from './services/MJStorageBlobStore.js';
78
96
  import { CACHE_INVALIDATION_TOPIC, MayBroadcastRecordData, ConfigureRecordDataBroadcast } from './generic/CacheInvalidationResolver.js';
@@ -122,6 +140,7 @@ export * from 'type-graphql';
122
140
  // later importers, which makes schema build fail on ViewResult.RowCount.
123
141
  export { Int, Float, ID } from 'type-graphql';
124
142
  export { NewUserBase } from './auth/newUsers.js';
143
+ export { CreateIsolatedProvider } from './isolatedProvider.js';
125
144
  export { configInfo, DEFAULT_SERVER_CONFIG } from './config.js';
126
145
  export { ServerExtensionLoader, BaseServerExtension, DefaultServerExtensionServiceRegistry } from '@memberjunction/server-extensions-core';
127
146
  export type {
@@ -186,6 +205,8 @@ export * from './resolvers/GenerateSeedTaxonomyResolver.js';
186
205
  export * from './resolvers/PipelineProgressResolver.js';
187
206
  export * from './resolvers/IntegrationProgressResolver.js';
188
207
  export * from './resolvers/IdentityClaimRedemptionResolver.js';
208
+ export * from './resolvers/UserAvatarResolver.js';
209
+ export * from './resolvers/avatarInputValidation.js';
189
210
  export * from './resolvers/ClientToolRequestResolver.js';
190
211
  export * from './resolvers/AutotagPipelineResolver.js';
191
212
  export * from './resolvers/TagGovernanceResolver.js';
@@ -243,6 +264,10 @@ export * from './resolvers/CurrentUserContextResolver.js';
243
264
  export * from './resolvers/RSUResolver.js';
244
265
  export * from './resolvers/AgentSessionResolver.js';
245
266
  export * from './resolvers/RealtimeClientSessionResolver.js';
267
+ export * from './resolvers/RealtimeSessionEventsResolver.js';
268
+ export * from './resolvers/RealtimeSessionVerificationResolver.js';
269
+ export * from './realtimeSessions/index.js';
270
+ export * from './resolvers/MeetingResolver.js';
246
271
  export * from './resolvers/RemoteBrowserActionResolver.js';
247
272
  export * from './agentSessions/index.js';
248
273
  export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
@@ -296,7 +321,8 @@ function resolveServerVersion(): string | undefined {
296
321
  const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
297
322
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string };
298
323
  return pkg.version;
299
- } catch {
324
+ } catch (err) {
325
+ LogError('Failed to resolve server version from package.json', undefined, err);
300
326
  return undefined;
301
327
  }
302
328
  }
@@ -304,55 +330,80 @@ function resolveServerVersion(): string | undefined {
304
330
  /** How often to re-check for conversation details left behind by finished runs. */
305
331
  const ORPHAN_DETAIL_SWEEP_INTERVAL_MS = 5 * 60_000;
306
332
 
307
- /** Redis channel carrying replicated push-status updates between server instances. */
308
- const PUSH_STATUS_FANOUT_CHANNEL = 'push-status-updates';
309
-
310
333
  /**
311
- * Replicate push-status updates across server instances over Redis (MJ #4222).
312
- *
313
- * Outbound: every locally-published update is forwarded on a shared channel. Inbound: a message
314
- * from another instance is republished onto THIS instance's local topic, where the normal
315
- * subscription filter decides who receives it — so the identity gate (`ownerUserId` vs. the
316
- * connection's authenticated user) still applies to a replicated message exactly as it does to a
317
- * local one. The replica has no say in who sees what.
334
+ * Replicate handoff-offer updates across server instances over Redis.
318
335
  *
319
- * Republishing goes straight to `PubSubManager`, never back through `publishStatusUpdate`, so an
320
- * inbound message cannot be re-broadcast and loop. `SourceServerId` guards the remaining case: a
321
- * publisher also receives its own message from Redis.
336
+ * Outbound: every locally-published offer change is forwarded on a shared Redis channel.
337
+ * Inbound: a message from another instance is republished onto THIS instance's local GraphQL
338
+ * topic (where the subscription filter scopes to the target user) and delivered to RoomHandoffEngine
339
+ * so the call-hosting instance can transition its local flow (e.g. wait for the user to join the room).
322
340
  */
323
- async function wirePushStatusFanOut(redisProvider: RedisLocalStorageProvider, startupLog: StartupLogger): Promise<void> {
341
+ async function wireHandoffOfferFanOut(redisProvider: RedisLocalStorageProvider, startupLog: StartupLogger): Promise<void> {
324
342
  try {
325
- await redisProvider.SubscribeToChannel(PUSH_STATUS_FANOUT_CHANNEL, (raw: string) => {
343
+ await redisProvider.SubscribeToChannel(HANDOFF_OFFER_FANOUT_CHANNEL, (raw: string) => {
326
344
  try {
327
- const payload = ParseReplicatedStatusUpdate(raw, MJGlobal.Instance.ProcessUUID);
345
+ const payload = ParseReplicatedHandoffOfferUpdate(raw, MJGlobal.Instance.ProcessUUID);
328
346
  if (!payload) {
329
347
  return;
330
348
  }
331
- // Rebuilt as a plain record: the topic's publish signature takes an index-signature type,
332
- // and listing the fields keeps the wire shape explicit at the one place it crosses hosts.
333
- PubSubManager.Instance.Publish(PUSH_STATUS_UPDATES_TOPIC, {
334
- sessionId: payload.sessionId,
335
- ownerUserId: payload.ownerUserId,
336
- message: payload.message,
337
- SourceServerId: payload.SourceServerId,
349
+ PubSubManager.Instance.Publish(HANDOFF_OFFER_TOPIC, {
350
+ UserID: payload.UserID,
351
+ Kind: payload.Kind,
352
+ Offer: payload.Offer,
338
353
  });
339
- } catch {
354
+ RoomHandoffEngine.Instance.OnRemoteOfferChange(payload);
355
+ } catch (err) {
340
356
  // A malformed message on a shared channel must not take down the subscriber.
357
+ LogError('Error processing handoff-offer fan-out message', undefined, err);
341
358
  }
342
359
  });
343
360
 
344
- SetPushStatusPublishHook((payload) => {
345
- redisProvider.PublishMessage(PUSH_STATUS_FANOUT_CHANNEL, JSON.stringify(payload));
361
+ SetHandoffOfferPublishHook((payload) => {
362
+ redisProvider.PublishMessage(HANDOFF_OFFER_FANOUT_CHANNEL, JSON.stringify(payload));
346
363
  });
347
364
 
348
- // Printed unconditionally, not verbose-gated. "Is fan-out actually on?" is the first question
349
- // anyone debugging a hung conversation behind a load balancer asks, and a silent default left
350
- // no way to answer it.
351
- console.log('[MJAPI] Push-status updates: cross-instance fan-out enabled via Redis');
365
+ console.log('[MJAPI] Handoff-offer updates: cross-instance fan-out enabled via Redis');
366
+ startupLog.LogIf('verbose', 'Handoff-offer updates: cross-instance fan-out enabled via Redis');
367
+ } catch (err) {
368
+ console.warn(`Handoff-offer fan-out unavailable: ${err instanceof Error ? err.message : String(err)}`);
369
+ }
370
+ }
371
+
372
+ /** Redis channel carrying replicated realtime session events between server instances. */
373
+ const REALTIME_SESSION_EVENTS_FANOUT_CHANNEL = 'realtime-session-events';
374
+
375
+ /**
376
+ * Replicate realtime session events across server instances over Redis.
377
+ *
378
+ * Behind a load balancer the browser's WebSocket (the `RealtimeSessionEvents` subscription) lives on one
379
+ * replica while the request that raises the event — typically the public verify-link redemption — can be
380
+ * handled by another. The session-events topic is an in-process PubSub, so without this an
381
+ * `identity.verified` published on replica B never reaches the subscriber on replica A.
382
+ *
383
+ * Inbound messages are republished onto THIS instance's local topic only (never back through the
384
+ * replication hook, so there is no loop) and are still judged by the subscription filter against the
385
+ * connection's identity — a replica has no say in who sees what. The durable
386
+ * `RealtimeSessionVerificationStatus` query remains the backstop if Redis is down.
387
+ */
388
+ async function wireRealtimeSessionEventFanOut(redisProvider: RedisLocalStorageProvider): Promise<void> {
389
+ try {
390
+ await redisProvider.SubscribeToChannel(REALTIME_SESSION_EVENTS_FANOUT_CHANNEL, (raw: string) => {
391
+ try {
392
+ const payload = ParseReplicatedRealtimeSessionEvent(raw, MJGlobal.Instance.ProcessUUID);
393
+ if (payload) {
394
+ RealtimeSessionEventService.Instance.PublishReplicated(payload);
395
+ }
396
+ } catch (err) {
397
+ LogError(`Realtime session event fan-out: dropping a malformed replicated event: ${(err as Error).message}`);
398
+ }
399
+ });
400
+ RealtimeSessionEventService.Instance.SetReplicationHook((payload: RealtimeSessionEventTopicPayload) => {
401
+ redisProvider.PublishMessage(REALTIME_SESSION_EVENTS_FANOUT_CHANNEL, JSON.stringify(payload));
402
+ });
403
+ console.log('[MJAPI] Realtime session events: cross-instance fan-out enabled via Redis');
352
404
  } catch (err) {
353
- // Single-instance delivery still works, and the durable tail query covers the rest. Degraded,
354
- // not broken — so this must not stop the server from starting.
355
- console.warn(`Push-status fan-out unavailable: ${(err as Error).message}`);
405
+ // Single-instance delivery still works and the status query covers the rest: degraded, not broken.
406
+ console.warn(`Realtime session event fan-out unavailable: ${(err as Error).message}`);
356
407
  }
357
408
  }
358
409
 
@@ -369,6 +420,12 @@ GetAttachmentService().BlobStore = new MJStorageBlobStore();
369
420
 
370
421
  export const Serve = async (resolverPaths: Array<string>, app: Application = CreateApp(), options?: MJServerOptions): Promise<void> => {
371
422
  const t0 = performance.now();
423
+ // Name this MJAPI instance by its port before anything stamps a session row with its identity, so the janitor's
424
+ // startup recovery closes only the sessions an earlier boot of this instance left, never those of another MJAPI on
425
+ // the same host (#5309).
426
+ SetHostInstancePort(graphqlPort);
427
+ // The realtime bridge engine stamps its bridge rows with the same identity and reconciles orphans by it (#5310).
428
+ BindBridgeEngineHostInstance();
372
429
  // Level-gated startup logger. Resolves verbosity from telemetry.level (single
373
430
  // operator knob). At `standard` (default), per-phase timings are collapsed into
374
431
  // the one-line summary; at `verbose`+ each phase prints inline as before.
@@ -397,30 +454,37 @@ const setupComplete$ = new ReplaySubject(1);
397
454
  const dbType = GetDbType();
398
455
  const dataSources: DataSourceInfo[] = [];
399
456
 
457
+ // Shared cache first: the database provider loads metadata and startup engines through
458
+ // it, so the first server to start fills it and later servers read it instead of each querying
459
+ // the database and broadcasting what they loaded. Subscribed before engines load so they hear
460
+ // other servers while they start.
461
+ const sharedCache = CreateSharedCacheFromEnvironment(configInfo.cacheSettings);
462
+ if (sharedCache) {
463
+ await WireSharedCacheEvents(sharedCache, () => (Metadata.Provider instanceof ProviderBase ? Metadata.Provider : undefined)); // global-provider-ok: bootstrap
464
+ // The cross-instance fan-outs ride this connection. Push-status moved into sharedCache.ts
465
+ // with the rest of the Redis wiring; the handoff and realtime-session ones stay here deliberately —
466
+ // they reach into RoomHandoffEngine and RealtimeSessionEventService, and the cache module should not
467
+ // acquire a dependency on either.
468
+ await WirePushStatusFanOut(sharedCache);
469
+ await wireHandoffOfferFanOut(sharedCache, startupLog);
470
+ await wireRealtimeSessionEventFanOut(sharedCache);
471
+ startupLog.LogIf('verbose', `Redis cache provider connected: ${process.env.REDIS_URL}`);
472
+ }
473
+ const cacheManagerConfig = CacheManagerConfigFromSettings(configInfo.cacheSettings);
474
+ const warmupLeaseMs = WarmupLeaseMsFromSettings(configInfo.cacheSettings);
475
+
400
476
  if (dbType === 'postgresql') {
401
477
  // ─── PostgreSQL Path ───────────────────────────────────────────
402
478
  startupLog.BeginPhase('Connecting to database');
403
479
  startupLog.LogIf('verbose', 'Database type: PostgreSQL');
404
480
  const pg = await import('pg');
405
- const { PostgreSQLDataProvider, PostgreSQLProviderConfigData } = await import('@memberjunction/postgresql-dataprovider');
481
+ const { PostgreSQLDataProvider, PostgreSQLProviderConfigData, MJPostgresTypes } = await import('@memberjunction/postgresql-dataprovider');
406
482
 
407
- const pgHost = process.env.PG_HOST || process.env.DB_HOST || 'localhost';
408
- const pgPort = parseInt(process.env.PG_PORT || process.env.DB_PORT || '5432', 10);
409
- const pgUser = process.env.PG_USERNAME || process.env.DB_USERNAME || 'postgres';
410
- const pgPass = process.env.PG_PASSWORD || process.env.DB_PASSWORD || '';
411
- const pgDatabase = process.env.PG_DATABASE || process.env.DB_DATABASE || '';
412
-
413
- const pgPool = new pg.default.Pool({
414
- host: pgHost,
415
- port: pgPort,
416
- user: pgUser,
417
- password: pgPass,
418
- database: pgDatabase,
419
- max: configInfo.databaseSettings.connectionPool?.max ?? 50,
420
- min: configInfo.databaseSettings.connectionPool?.min ?? 5,
421
- idleTimeoutMillis: configInfo.databaseSettings.connectionPool?.idleTimeoutMillis ?? 30000,
422
- connectionTimeoutMillis: configInfo.databaseSettings.connectionPool?.acquireTimeoutMillis ?? 30000,
423
- });
483
+ const pgEndpoint = ResolvePostgreSQLEndpoint();
484
+ const { Host: pgHost, Port: pgPort, User: pgUser, Database: pgDatabase } = pgEndpoint;
485
+ // Every API pool carries the statement and idle-in-transaction timeouts from connection #1.
486
+ const pgConnectionConfig = BuildPostgreSQLConnectionConfig(pgEndpoint, configInfo.databaseSettings, 'api');
487
+ const pgPool = new pg.default.Pool(ToPGPoolConfig(pgConnectionConfig));
424
488
 
425
489
  // Verify connection
426
490
  const testClient = await pgPool.connect();
@@ -428,6 +492,28 @@ const setupComplete$ = new ReplaySubject(1);
428
492
  testClient.release();
429
493
  startupLog.LogIf('verbose', `PostgreSQL pool connected to ${pgHost}:${pgPort}/${pgDatabase}`);
430
494
 
495
+ // A read-only pool opened with the read-only login, as SQL Server has. Read-only per-request
496
+ // providers (TestQuerySQL and other caller-supplied SQL) share it instead of the primary pool.
497
+ const pgReadOnlyCredentials = ResolvePostgreSQLReadOnlyCredentials(configInfo);
498
+ if (pgReadOnlyCredentials) {
499
+ // A pool a provider runs on must carry the provider's type parsers, so BIGINT and NUMERIC
500
+ // come back as numbers here as they do on the provider's own pool.
501
+ const readOnlyPgPool = new pg.default.Pool({
502
+ ...ToPGPoolConfig(BuildPostgreSQLConnectionConfig({ ...pgEndpoint, ...pgReadOnlyCredentials }, configInfo.databaseSettings, 'read-only')),
503
+ types: MJPostgresTypes,
504
+ });
505
+ const readOnlyTestClient = await readOnlyPgPool.connect();
506
+ try {
507
+ for (const warning of await DescribeReadOnlyLoginOverreach(readOnlyTestClient, mj_core_schema)) {
508
+ LogStatus(`WARNING: ${warning}`);
509
+ }
510
+ } finally {
511
+ readOnlyTestClient.release();
512
+ }
513
+ PostgreSQLReadOnlyPool.Instance.Pool = readOnlyPgPool;
514
+ startupLog.LogIf('verbose', 'Read-only PostgreSQL pool has been initialized.');
515
+ }
516
+
431
517
  // Create a DataSourceInfo with a MSSQL-compatible wrapper around pg.Pool
432
518
  // This allows existing code (types, util, context) to work without changes
433
519
  const mssqlCompatPool = createMSSQLCompatPool(pgPool);
@@ -441,20 +527,12 @@ const setupComplete$ = new ReplaySubject(1);
441
527
  }));
442
528
 
443
529
  // Set up the PostgreSQL provider
444
- const pgConnectionConfig = {
445
- Host: pgHost,
446
- Port: pgPort,
447
- Database: pgDatabase,
448
- User: pgUser,
449
- Password: pgPass,
450
- MaxConnections: configInfo.databaseSettings.connectionPool?.max ?? 50,
451
- MinConnections: configInfo.databaseSettings.connectionPool?.min ?? 5,
452
- };
453
530
  const pgConfigData = new PostgreSQLProviderConfigData(
454
531
  pgConnectionConfig,
455
532
  mj_core_schema,
456
533
  cacheRefreshInterval / 1000, // convert ms to seconds
457
534
  );
535
+ pgConfigData.LocalStorageProvider = sharedCache ?? undefined;
458
536
  const provider = new PostgreSQLDataProvider();
459
537
  await provider.Config(pgConfigData);
460
538
  SetProvider(provider);
@@ -466,7 +544,7 @@ const setupComplete$ = new ReplaySubject(1);
466
544
  const sysUser = UserCache.Instance.GetSystemUser();
467
545
  const backupSysUser = UserCache.Instance.Users.find(u => u.IsActive && u.Type === 'Owner');
468
546
  const pgStartupMode = ResolveStartupMode({ configValue: configInfo.startup?.mode, defaultMode: 'full' });
469
- await StartupManagerImport.Instance.Startup(false, sysUser || backupSysUser, provider, { mode: pgStartupMode.mode });
547
+ await StartupManagerImport.Instance.Startup(false, sysUser || backupSysUser, provider, { mode: pgStartupMode.mode, cacheManagerConfig, warmupLeaseMs });
470
548
 
471
549
  // Both provider sources have now had their turn — config/env at module load, metadata via
472
550
  // AuthProviderEngine's startup hook — so "no providers at all" is finally a meaningful check.
@@ -483,7 +561,7 @@ const setupComplete$ = new ReplaySubject(1);
483
561
  if (poolAny._pgPool) {
484
562
  const thePgPool = poolAny._pgPool as import('pg').Pool;
485
563
  // Translate SQL Server bracket syntax to PostgreSQL double-quote syntax
486
- const pgQuery = translateBracketsToPG(query);
564
+ const pgQuery = TranslateBracketsToPG(query);
487
565
  const result = await thePgPool.query(pgQuery);
488
566
  return result.rows;
489
567
  }
@@ -502,21 +580,21 @@ const setupComplete$ = new ReplaySubject(1);
502
580
  const pgCodegenPass = process.env.CODEGEN_DB_PASSWORD;
503
581
  if (pgCodegenUser && pgCodegenPass) {
504
582
  try {
505
- const codegenPgPool = new pg.default.Pool({
506
- host: pgHost,
507
- port: pgPort,
508
- user: pgCodegenUser,
509
- password: pgCodegenPass,
510
- database: pgDatabase,
511
- max: 10,
512
- });
583
+ // CodeGen and DDL work runs long statements across every entity, so this pool gets the
584
+ // long CodeGen timeout rather than the API one, as the SQL Server CodeGen pool does.
585
+ const codegenPgConnectionConfig = BuildPostgreSQLConnectionConfig(
586
+ { ...pgEndpoint, User: pgCodegenUser, Password: pgCodegenPass },
587
+ configInfo.databaseSettings,
588
+ 'codegen',
589
+ );
590
+ const codegenPgPool = new pg.default.Pool(ToPGPoolConfig(codegenPgConnectionConfig));
513
591
  const codegenTestClient = await codegenPgPool.connect();
514
592
  await codegenTestClient.query('SELECT 1');
515
593
  codegenTestClient.release();
516
594
 
517
595
  const { RuntimeSchemaManager } = await import('@memberjunction/schema-engine');
518
596
  const codegenPgConfigData = new PostgreSQLProviderConfigData(
519
- { Host: pgHost, Port: pgPort, Database: pgDatabase, User: pgCodegenUser, Password: pgCodegenPass },
597
+ codegenPgConnectionConfig,
520
598
  mj_core_schema,
521
599
  cacheRefreshInterval / 1000, // ms → seconds
522
600
  );
@@ -624,10 +702,11 @@ const setupComplete$ = new ReplaySubject(1);
624
702
 
625
703
  // cacheRefreshInterval is configured in ms; checkRefreshIntervalSeconds declares seconds — see providerConfigUnits.ts
626
704
  const config = new SQLServerProviderConfigData(pool, mj_core_schema, MetadataCacheRefreshIntervalSeconds(cacheRefreshInterval));
705
+ config.LocalStorageProvider = sharedCache ?? undefined;
627
706
  // MJAPI is a long-running server, so entry-point default is 'full' engine pre-warm;
628
707
  // MJ_STARTUP_MODE / mj.config.cjs startup.mode can override per the shared precedence chain
629
708
  const startupMode = ResolveStartupMode({ configValue: configInfo.startup?.mode, defaultMode: 'full' });
630
- await setupSQLServerClient(config, { mode: startupMode.mode });
709
+ await setupSQLServerClient(config, { mode: startupMode.mode, cacheManagerConfig, warmupLeaseMs });
631
710
 
632
711
  // See the note on the PostgreSQL path above: this is the first point at which both the
633
712
  // config/env providers and the metadata catalog have been registered.
@@ -752,83 +831,31 @@ const setupComplete$ = new ReplaySubject(1);
752
831
  startupLog.LogIf('verbose', 'Server telemetry disabled');
753
832
  }
754
833
 
755
- // Optionally inject Redis as the shared storage provider for cross-server cache invalidation
756
- if (process.env.REDIS_URL) {
757
- const redisProvider = new RedisLocalStorageProvider({
758
- url: process.env.REDIS_URL,
759
- keyPrefix: process.env.REDIS_KEY_PREFIX || 'mj',
760
- enablePubSub: true,
761
- enableLogging: configInfo.cacheSettings?.verboseLogging ?? false,
762
- });
763
- (Metadata.Provider as GenericDatabaseProvider).SetLocalStorageProvider(redisProvider); // global-provider-ok: bootstrap (Redis cache wiring)
764
- await redisProvider.StartListening();
765
-
766
- // Connect Redis pub/sub events to LocalCacheManager callback dispatch
767
- // so cross-server cache invalidation messages are routed to registered callbacks
768
- redisProvider.OnCacheChanged((event) => {
769
- const sourceShort = event.SourceServerId ? event.SourceServerId.substring(0, 8) : 'unknown';
770
- console.log(`[MJAPI] Redis pub/sub → DispatchCacheChange: ${event.Action} for "${event.CacheKey}" from server ${sourceShort}`);
771
- LocalCacheManager.Instance.DispatchCacheChange(event);
772
-
773
- // Also broadcast to connected browser clients via GraphQL subscription
774
- // Extract entity name from the cache key (format: EntityName|Filter|OrderBy|...)
775
- const entityName = event.CacheKey ? event.CacheKey.split('|')[0] : '';
776
- if (entityName) {
777
- PubSubManager.Instance.Publish(CACHE_INVALIDATION_TOPIC, {
778
- entityName,
779
- primaryKeyValues: null, // entity-level invalidation
780
- action: event.Action || 'save',
781
- sourceServerId: event.SourceServerId || 'unknown',
782
- timestamp: new Date(),
783
- });
784
- }
785
- });
786
-
787
- // Fan push-status updates across server instances (MJ #4222).
788
- //
789
- // Behind a load balancer the browser's WebSocket lives on one replica while the mutation that
790
- // drives the agent can be handled by another. The push topic is an in-process PubSub, so a
791
- // completion published on replica B never reaches a subscriber on replica A — the browser waits
792
- // forever for an event that was delivered to nobody. Replicating progress and completion over
793
- // Redis closes that, and the durable tail query remains the backstop if Redis is down.
794
- await wirePushStatusFanOut(redisProvider, startupLog);
795
-
796
- startupLog.LogIf('verbose', `Redis cache provider connected: ${process.env.REDIS_URL}`);
797
- }
798
-
799
- // If Redis is available, swap LocalCacheManager's storage provider to Redis.
800
- // LocalCacheManager may have already been initialized (with in-memory provider)
801
- // during engine loading. SetStorageProvider migrates cached data to Redis.
802
- if (process.env.REDIS_URL) {
803
- await LocalCacheManager.Instance.SetStorageProvider(Metadata.Provider.LocalStorageProvider); // global-provider-ok: bootstrap
804
- startupLog.LogIf('verbose', 'LocalCacheManager: storage provider swapped to Redis');
805
- }
806
- // Ensure LocalCacheManager is initialized (no-op if already done during engine loading)
807
- if (!LocalCacheManager.Instance.IsInitialized) {
808
- // Build cache config from mj.config.cjs cacheSettings
809
- const cs = configInfo.cacheSettings;
810
- const cacheConfig = {
811
- maxSizeBytes: (cs.maxMemoryMB ?? 150) * 1024 * 1024,
812
- maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
813
- defaultTTLMs: (cs.defaultTTLSeconds ?? 0) * 1000,
814
- evictionSweepIntervalMs: (cs.evictionSweepIntervalSeconds ?? 300) * 1000,
815
- verboseLogging: cs.verboseLogging ?? false,
816
- };
817
- await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheConfig); // global-provider-ok: bootstrap
818
- if (startupLog.IsAtLeast('verbose')) {
819
- // eslint-disable-next-line no-console
820
- console.log('LocalCacheManager initialized with cache config:', JSON.stringify({
821
- maxMemoryMB: cs.maxMemoryMB ?? 150,
822
- maxPercentOfCachePerEntity: cs.maxPercentOfCachePerEntity ?? 50,
823
- evictionSweepIntervalSeconds: cs.evictionSweepIntervalSeconds ?? 300,
824
- }));
825
- }
834
+ // The database provider already initialized LocalCacheManager from inside its own Config(), with
835
+ // no settings — this call is what makes `cacheSettings` take effect (Initialize merges a config
836
+ // handed to it later). Unconditional on purpose: the guard that used to sit here
837
+ // skipped exactly the case that needed it.
838
+ await LocalCacheManager.Instance.Initialize(Metadata.Provider.LocalStorageProvider, cacheManagerConfig); // global-provider-ok: bootstrap
839
+ if (sharedCache) {
840
+ await LocalCacheManager.Instance.SetStorageProvider(sharedCache);
826
841
  }
842
+ const engineSweepMs = StartEngineSweeper(configInfo.cacheSettings);
843
+ startupLog.LogIf('verbose', engineSweepMs > 0 ? `Engine/database sweep every ${engineSweepMs / 1000}s` : 'Engine/database sweep disabled');
844
+ const userCacheCheckMs = StartUserCacheChecks(configInfo.cacheSettings);
845
+ startupLog.LogIf('verbose', userCacheCheckMs > 0 ? `User cache staleness check every ${userCacheCheckMs / 1000}s` : 'User cache staleness check disabled');
846
+ // Costs nothing unless a metadata entity declares TrustServerCacheCompletely = false.
847
+ const metadataSweepMs = StartMetadataSweep(configInfo.cacheSettings, () => (Metadata.Provider instanceof ProviderBase ? Metadata.Provider : undefined)); // global-provider-ok: bootstrap (this process's one provider)
848
+ startupLog.LogIf('verbose', metadataSweepMs > 0 ? `Metadata/database sweep every ${metadataSweepMs / 1000}s (only for entities declaring drift)` : 'Metadata/database sweep disabled');
827
849
 
828
850
  // Initialize APIKeyEngine singleton — reads apiKeyGeneration from mj.config.cjs automatically
829
851
  // This must happen before any request handler calls GetAPIKeyEngine()
830
852
  GetAPIKeyEngine();
831
853
 
854
+ // Actions that run caller-supplied SQL (Run Ad-hoc Query) run only on the read-only login, with
855
+ // ExecuteAdhocQuery's table check and timeout limit.
856
+ ActionEngineServer.Instance.SetReadOnlyProviderFactory(() => CreateReadOnlyProvider(dataSources));
857
+ ActionEngineServer.Instance.SetAdhocSQLAuthorizer(CreateAdhocSQLAuthorizer(configInfo.databaseSettings.requestTimeout));
858
+
832
859
  setupComplete$.next(true);
833
860
  raiseEvent('setupComplete', dataSources, null, this);
834
861
 
@@ -1076,7 +1103,7 @@ const setupComplete$ = new ReplaySubject(1);
1076
1103
  const profile = await new Promise<unknown>((res, rej) =>
1077
1104
  profSession!.post('Profiler.stop', (e, r) => (e ? rej(e) : res((r as { profile: unknown }).profile)))
1078
1105
  );
1079
- const outPath = `/tmp/mjapi_schema_build_${process.pid}.cpuprofile`;
1106
+ const outPath = join(mkdtempSync(join(tmpdir(), 'mjapi-schema-')), `mjapi_schema_build_${process.pid}.cpuprofile`);
1080
1107
  writeFileSync(outPath, JSON.stringify(profile));
1081
1108
  profSession.disconnect();
1082
1109
  // eslint-disable-next-line no-console
@@ -1359,6 +1386,19 @@ const setupComplete$ = new ReplaySubject(1);
1359
1386
  startupLog.LogIf('verbose', `[Widget] Public routes registered at ${WIDGET_MOUNT_PATH}/session and ${WIDGET_MOUNT_PATH}/session/refresh`);
1360
1387
  }
1361
1388
 
1389
+ // ─── Realtime identity verification: PUBLIC verify-link routes (before auth mw) ───
1390
+ // The person opening the emailed link holds no MJ token — the single-use token is the capability.
1391
+ // GET only renders a confirm button; the POST redeems (mail scanners GET every link). Mounted only
1392
+ // when verification is enabled AND able to send mail AND able to protect its codes (see
1393
+ // realtime.identityVerification config: communicationProvider + hmacSecret).
1394
+ if (configInfo.realtime?.identityVerification?.enabled && !RealtimeSessionVerificationService.Instance.IsEnabled) {
1395
+ console.warn('[RealtimeVerify] realtime.identityVerification.enabled is true but verification is unavailable: it needs a communicationProvider and an hmacSecret of at least 16 characters.');
1396
+ }
1397
+ if (RealtimeSessionVerificationService.Instance.IsEnabled) {
1398
+ app.use(REALTIME_VERIFY_MOUNT_PATH, cors<cors.CorsRequest>(), CreateRealtimeVerifyRouter());
1399
+ startupLog.LogIf('verbose', `[RealtimeVerify] Public routes registered at ${REALTIME_VERIFY_MOUNT_PATH}/:token (GET) and ${REALTIME_VERIFY_MOUNT_PATH} (POST)`);
1400
+ }
1401
+
1362
1402
  // ─── Server extensions loader & shared service registry ───────────────────
1363
1403
  const extensionLoader = new ServerExtensionLoader();
1364
1404
  _currentExtensionLoader = extensionLoader;
@@ -1367,13 +1407,22 @@ const setupComplete$ = new ReplaySubject(1);
1367
1407
  // Backwards-compatibility shim: synthesize ServerExtensionConfig entries from legacy configInfo.telephony
1368
1408
  const telephonyExtensionConfigs: ServerExtensionConfig[] = [];
1369
1409
  if (configInfo.telephony?.enabled) {
1410
+ // Settings every carrier shares (inbound run-as user, call cap, outbound policy). Each carrier's own block is
1411
+ // spread AFTER them, so a carrier can override one explicitly.
1412
+ const sharedTelephonySettings: Record<string, unknown> = {
1413
+ inboundRunAsUserEmail: configInfo.telephony.inboundRunAsUserEmail,
1414
+ maxCallSeconds: configInfo.telephony.maxCallSeconds,
1415
+ maxConcurrentCalls: configInfo.telephony.maxConcurrentCalls,
1416
+ transferTargets: configInfo.telephony.transferTargets,
1417
+ outbound: configInfo.telephony.outbound,
1418
+ };
1370
1419
  if (configInfo.telephony.twilio) {
1371
1420
  telephonyExtensionConfigs.push({
1372
1421
  Enabled: true,
1373
1422
  DriverClass: 'TwilioTelephonyExtension',
1374
1423
  RootPath: '/telephony/twilio',
1375
1424
  Phase: 'pre-auth',
1376
- Settings: configInfo.telephony.twilio as unknown as Record<string, unknown>,
1425
+ Settings: { ...sharedTelephonySettings, ...configInfo.telephony.twilio },
1377
1426
  });
1378
1427
  }
1379
1428
  if (configInfo.telephony.vonage) {
@@ -1382,7 +1431,7 @@ const setupComplete$ = new ReplaySubject(1);
1382
1431
  DriverClass: 'VonageTelephonyExtension',
1383
1432
  RootPath: '/telephony/vonage',
1384
1433
  Phase: 'pre-auth',
1385
- Settings: configInfo.telephony.vonage as unknown as Record<string, unknown>,
1434
+ Settings: { ...sharedTelephonySettings, ...configInfo.telephony.vonage },
1386
1435
  });
1387
1436
  }
1388
1437
  if (configInfo.telephony.ringcentral) {
@@ -1391,7 +1440,16 @@ const setupComplete$ = new ReplaySubject(1);
1391
1440
  DriverClass: 'RingCentralTelephonyExtension',
1392
1441
  RootPath: '/telephony/ringcentral',
1393
1442
  Phase: 'pre-auth',
1394
- Settings: configInfo.telephony.ringcentral as unknown as Record<string, unknown>,
1443
+ Settings: { ...sharedTelephonySettings, ...configInfo.telephony.ringcentral },
1444
+ });
1445
+ }
1446
+ if (configInfo.telephony.livekitSip) {
1447
+ telephonyExtensionConfigs.push({
1448
+ Enabled: true,
1449
+ DriverClass: 'LiveKitSipExtension',
1450
+ RootPath: '/telephony/livekit-sip',
1451
+ Phase: 'pre-auth',
1452
+ Settings: { ...sharedTelephonySettings, ...configInfo.telephony.livekitSip },
1395
1453
  });
1396
1454
  }
1397
1455
  if (configInfo.telephony.teams?.enabled) {
@@ -1684,12 +1742,16 @@ const setupComplete$ = new ReplaySubject(1);
1684
1742
  });
1685
1743
  }
1686
1744
 
1687
- // Launch the AI Agent Session janitor: run own-host orphan recovery once at boot, then keep a
1688
- // periodic staleness sweep running. Self-registers with ShutdownRegistry, so its timer is cleared
1689
- // by the gracefulShutdown drain below (no explicit Stop() wiring needed here).
1745
+ // Launch the AI Agent Session janitor: run own-host orphan recovery once at boot, then a periodic tick
1746
+ // that closes stale and over-deadline sessions and rebuilds lost realtime recordings from their
1747
+ // crash-recovery shards (storage reads and writes). Self-registers with ShutdownRegistry, so its timer
1748
+ // is cleared by the gracefulShutdown drain below (no explicit Stop() wiring needed here).
1690
1749
  if (resumeUser && Metadata.Provider instanceof DatabaseProviderBase) { // global-provider-ok: server startup recovery — boot-time session janitor uses the server's own provider
1691
1750
  SessionJanitor.Instance.Start(Metadata.Provider, resumeUser) // global-provider-ok: server-owned background reconciler runs under the server's provider + system user
1692
1751
  .catch(err => console.warn(`[SessionJanitor] Startup failed: ${err}`));
1752
+ // The bridge engine's orphan reconcile likewise starts only now that this process holds its port, so it can't
1753
+ // take the live bridges of another MJAPI still serving that port for its own orphans (#5310).
1754
+ StartBridgeOrphanReconciliation(Metadata.Provider, resumeUser); // global-provider-ok: server-owned background reconciler runs under the server's provider + system user
1693
1755
  }
1694
1756
 
1695
1757
  // Launch the durable task-graph dispatcher: claim Pending tasks, execute them with a fresh
@@ -1723,6 +1785,18 @@ const setupComplete$ = new ReplaySubject(1);
1723
1785
  .catch(err => console.warn(`[TaskGraphDispatcher] Startup failed: ${err}`));
1724
1786
  }
1725
1787
 
1788
+ // Start the durable work-queue host where enabled. It plans which subscriptions this instance runs,
1789
+ // re-plans on a timer, and self-registers with ShutdownRegistry, so gracefulShutdown's awaited
1790
+ // ShutdownAll() drains it (up to 2 × shutdownDrainMs) before the HTTP server closes.
1791
+ // Not awaited: a slow engine load must not delay readiness, and a failure never stops the API.
1792
+ const workQueueProvider = Metadata.Provider; // global-provider-ok: server startup — the work-queue host runs on the server's own provider
1793
+ if (configInfo.workQueue?.enabled && workQueueProvider instanceof DatabaseProviderBase) {
1794
+ const workQueuePool = dataSources[0]?.dataSource;
1795
+ const providerSource = new MJServerWorkQueueProviderSource(workQueuePool instanceof sql.ConnectionPool ? workQueuePool : null, workQueueProvider);
1796
+ StartWorkQueueHost(configInfo.workQueue, workQueueProvider, providerSource)
1797
+ .catch(error => console.error('❌ Failed to start the work queue host:', error));
1798
+ }
1799
+
1726
1800
 
1727
1801
  // Set up graceful shutdown handlers
1728
1802
  const gracefulShutdown = async (signal: string) => {
@@ -2206,11 +2280,3 @@ function createMSSQLCompatPool(pgPool: import('pg').Pool): sql.ConnectionPool {
2206
2280
  return wrapper as unknown as sql.ConnectionPool;
2207
2281
  }
2208
2282
 
2209
- /**
2210
- * Translates SQL Server bracket-quoted identifiers to PostgreSQL double-quoted identifiers.
2211
- * Converts [schema].[table] to "schema"."table" and handles common T-SQL patterns.
2212
- */
2213
- function translateBracketsToPG(sql: string): string {
2214
- // Replace [identifier] with "identifier"
2215
- return sql.replace(/\[([^\]]+)\]/g, '"$1"');
2216
- }