@memberjunction/server 6.2.0-edge.3 → 6.2.0-edge.5

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 (543) hide show
  1. package/README.md +20 -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 +236 -27
  15. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  16. package/dist/agentSessions/SessionJanitor.js +682 -54
  17. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  18. package/dist/agentSessions/SessionManager.d.ts +48 -3
  19. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  20. package/dist/agentSessions/SessionManager.js +112 -9
  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/apiRoot.d.ts +32 -0
  31. package/dist/apiRoot.d.ts.map +1 -0
  32. package/dist/apiRoot.js +67 -0
  33. package/dist/apiRoot.js.map +1 -0
  34. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  35. package/dist/auth/AuthProviderCatalogRouter.js +25 -0
  36. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  37. package/dist/auth/LoginScreenEngine.d.ts +22 -0
  38. package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
  39. package/dist/auth/LoginScreenEngine.js +57 -0
  40. package/dist/auth/LoginScreenEngine.js.map +1 -0
  41. package/dist/auth/actionAuthorization.d.ts +40 -0
  42. package/dist/auth/actionAuthorization.d.ts.map +1 -0
  43. package/dist/auth/actionAuthorization.js +94 -0
  44. package/dist/auth/actionAuthorization.js.map +1 -0
  45. package/dist/auth/index.d.ts +1 -0
  46. package/dist/auth/index.d.ts.map +1 -1
  47. package/dist/auth/index.js +4 -10
  48. package/dist/auth/index.js.map +1 -1
  49. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  50. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  51. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  52. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  53. package/dist/auth/magicLink/MagicLinkService.js +6 -3
  54. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  55. package/dist/auth/newUsers.d.ts +10 -0
  56. package/dist/auth/newUsers.d.ts.map +1 -1
  57. package/dist/auth/newUsers.js +80 -11
  58. package/dist/auth/newUsers.js.map +1 -1
  59. package/dist/config.d.ts +2514 -22
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +166 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/context.d.ts +8 -0
  64. package/dist/context.d.ts.map +1 -1
  65. package/dist/context.js +14 -11
  66. package/dist/context.js.map +1 -1
  67. package/dist/coreRoutes.d.ts +29 -0
  68. package/dist/coreRoutes.d.ts.map +1 -0
  69. package/dist/coreRoutes.js +142 -0
  70. package/dist/coreRoutes.js.map +1 -0
  71. package/dist/generated/graphql-schemas/__mj.d.ts +504 -3
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +2456 -7
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +43 -8
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +59 -19
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.js +3 -3
  80. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  81. package/dist/generic/PushStatusResolver.js +1 -1
  82. package/dist/generic/PushStatusResolver.js.map +1 -1
  83. package/dist/generic/ResolverBase.d.ts +123 -48
  84. package/dist/generic/ResolverBase.d.ts.map +1 -1
  85. package/dist/generic/ResolverBase.js +293 -305
  86. package/dist/generic/ResolverBase.js.map +1 -1
  87. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  88. package/dist/generic/RunViewResolver.js +16 -0
  89. package/dist/generic/RunViewResolver.js.map +1 -1
  90. package/dist/globalCors.d.ts +33 -0
  91. package/dist/globalCors.d.ts.map +1 -0
  92. package/dist/globalCors.js +58 -0
  93. package/dist/globalCors.js.map +1 -0
  94. package/dist/index.d.ts +17 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +219 -278
  97. package/dist/index.js.map +1 -1
  98. package/dist/isolatedProvider.d.ts +20 -0
  99. package/dist/isolatedProvider.d.ts.map +1 -0
  100. package/dist/isolatedProvider.js +22 -0
  101. package/dist/isolatedProvider.js.map +1 -0
  102. package/dist/multiTenancy/index.d.ts.map +1 -1
  103. package/dist/multiTenancy/index.js +22 -2
  104. package/dist/multiTenancy/index.js.map +1 -1
  105. package/dist/realtimeProxy/RealtimeProxyServer.d.ts +44 -4
  106. package/dist/realtimeProxy/RealtimeProxyServer.d.ts.map +1 -1
  107. package/dist/realtimeProxy/RealtimeProxyServer.js +121 -16
  108. package/dist/realtimeProxy/RealtimeProxyServer.js.map +1 -1
  109. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts +171 -0
  110. package/dist/realtimeProxy/RealtimeRelayTunnel.d.ts.map +1 -0
  111. package/dist/realtimeProxy/RealtimeRelayTunnel.js +532 -0
  112. package/dist/realtimeProxy/RealtimeRelayTunnel.js.map +1 -0
  113. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts +148 -0
  114. package/dist/realtimeSessions/RealtimeSessionEventService.d.ts.map +1 -0
  115. package/dist/realtimeSessions/RealtimeSessionEventService.js +191 -0
  116. package/dist/realtimeSessions/RealtimeSessionEventService.js.map +1 -0
  117. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts +66 -0
  118. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -0
  119. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +139 -0
  120. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -0
  121. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts +38 -0
  122. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -0
  123. package/dist/realtimeSessions/RealtimeVerifyRouter.js +99 -0
  124. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -0
  125. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  126. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  127. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  128. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  129. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts +61 -0
  130. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -0
  131. package/dist/realtimeSessions/SessionVerificationPortsBase.js +149 -0
  132. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -0
  133. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts +57 -0
  134. package/dist/realtimeSessions/SlidingWindowRateLimiter.d.ts.map +1 -0
  135. package/dist/realtimeSessions/SlidingWindowRateLimiter.js +69 -0
  136. package/dist/realtimeSessions/SlidingWindowRateLimiter.js.map +1 -0
  137. package/dist/realtimeSessions/index.d.ts +19 -0
  138. package/dist/realtimeSessions/index.d.ts.map +1 -0
  139. package/dist/realtimeSessions/index.js +19 -0
  140. package/dist/realtimeSessions/index.js.map +1 -0
  141. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +30 -0
  142. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  143. package/dist/realtimeSessions/ownedRealtimeSession.js +47 -0
  144. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  145. package/dist/realtimeSessions/sessionAccess.d.ts +92 -0
  146. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -0
  147. package/dist/realtimeSessions/sessionAccess.js +94 -0
  148. package/dist/realtimeSessions/sessionAccess.js.map +1 -0
  149. package/dist/realtimeSessions/sessionConfigStamps.d.ts +70 -0
  150. package/dist/realtimeSessions/sessionConfigStamps.d.ts.map +1 -0
  151. package/dist/realtimeSessions/sessionConfigStamps.js +93 -0
  152. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -0
  153. package/dist/realtimeSessions/subscriptionAuthorization.d.ts +65 -0
  154. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -0
  155. package/dist/realtimeSessions/subscriptionAuthorization.js +71 -0
  156. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -0
  157. package/dist/realtimeSessions/verificationCore.d.ts +243 -0
  158. package/dist/realtimeSessions/verificationCore.d.ts.map +1 -0
  159. package/dist/realtimeSessions/verificationCore.js +356 -0
  160. package/dist/realtimeSessions/verificationCore.js.map +1 -0
  161. package/dist/realtimeSessions/verificationPages.d.ts +66 -0
  162. package/dist/realtimeSessions/verificationPages.d.ts.map +1 -0
  163. package/dist/realtimeSessions/verificationPages.js +110 -0
  164. package/dist/realtimeSessions/verificationPages.js.map +1 -0
  165. package/dist/realtimeSessions/verificationPolicy.d.ts +147 -0
  166. package/dist/realtimeSessions/verificationPolicy.d.ts.map +1 -0
  167. package/dist/realtimeSessions/verificationPolicy.js +249 -0
  168. package/dist/realtimeSessions/verificationPolicy.js.map +1 -0
  169. package/dist/realtimeSessions/verificationWorkflow.d.ts +263 -0
  170. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -0
  171. package/dist/realtimeSessions/verificationWorkflow.js +435 -0
  172. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -0
  173. package/dist/realtimeWidget/widgetGuestElevation.d.ts +20 -2
  174. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  175. package/dist/realtimeWidget/widgetGuestElevation.js +31 -2
  176. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  177. package/dist/resolvers/ActionResolver.d.ts +31 -5
  178. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  179. package/dist/resolvers/ActionResolver.js +75 -14
  180. package/dist/resolvers/ActionResolver.js.map +1 -1
  181. package/dist/resolvers/AdhocQueryResolver.d.ts +14 -1
  182. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  183. package/dist/resolvers/AdhocQueryResolver.js +21 -5
  184. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  185. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  186. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  187. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  188. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  189. package/dist/resolvers/CacheStatsResolver.d.ts +27 -0
  190. package/dist/resolvers/CacheStatsResolver.d.ts.map +1 -1
  191. package/dist/resolvers/CacheStatsResolver.js +113 -2
  192. package/dist/resolvers/CacheStatsResolver.js.map +1 -1
  193. package/dist/resolvers/ClientToolRequestResolver.d.ts +4 -2
  194. package/dist/resolvers/ClientToolRequestResolver.d.ts.map +1 -1
  195. package/dist/resolvers/ClientToolRequestResolver.js +28 -10
  196. package/dist/resolvers/ClientToolRequestResolver.js.map +1 -1
  197. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  198. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  199. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  200. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  201. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  202. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  203. package/dist/resolvers/DatasetResolver.js +57 -8
  204. package/dist/resolvers/DatasetResolver.js.map +1 -1
  205. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  206. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  207. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  208. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  209. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +27 -1
  210. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  211. package/dist/resolvers/FetchEntityVectorsResolver.js +148 -2
  212. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  213. package/dist/resolvers/FileResolver.d.ts +82 -11
  214. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  215. package/dist/resolvers/FileResolver.js +279 -101
  216. package/dist/resolvers/FileResolver.js.map +1 -1
  217. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  218. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  219. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  220. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  221. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  222. package/dist/resolvers/GetDataResolver.js +6 -3
  223. package/dist/resolvers/GetDataResolver.js.map +1 -1
  224. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -1
  225. package/dist/resolvers/HumanHandoffResolver.js +7 -1
  226. package/dist/resolvers/HumanHandoffResolver.js.map +1 -1
  227. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  228. package/dist/resolvers/IntegrationDiscoveryResolver.js +4 -2
  229. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  230. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  231. package/dist/resolvers/MCPResolver.js +9 -4
  232. package/dist/resolvers/MCPResolver.js.map +1 -1
  233. package/dist/resolvers/QueryResolver.d.ts +2 -0
  234. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  235. package/dist/resolvers/QueryResolver.js +13 -0
  236. package/dist/resolvers/QueryResolver.js.map +1 -1
  237. package/dist/resolvers/RealtimeBridgeResolver.d.ts +54 -2
  238. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  239. package/dist/resolvers/RealtimeBridgeResolver.js +155 -10
  240. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  241. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +157 -33
  242. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  243. package/dist/resolvers/RealtimeClientSessionResolver.js +404 -116
  244. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  245. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts +61 -0
  246. package/dist/resolvers/RealtimeSessionEventsResolver.d.ts.map +1 -0
  247. package/dist/resolvers/RealtimeSessionEventsResolver.js +130 -0
  248. package/dist/resolvers/RealtimeSessionEventsResolver.js.map +1 -0
  249. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts +85 -0
  250. package/dist/resolvers/RealtimeSessionVerificationResolver.d.ts.map +1 -0
  251. package/dist/resolvers/RealtimeSessionVerificationResolver.js +188 -0
  252. package/dist/resolvers/RealtimeSessionVerificationResolver.js.map +1 -0
  253. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +2 -0
  254. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  255. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -1
  256. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  257. package/dist/resolvers/RunAIAgentResolver.d.ts +10 -0
  258. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  259. package/dist/resolvers/RunAIAgentResolver.js +100 -14
  260. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  261. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  262. package/dist/resolvers/RunTemplateResolver.js +12 -11
  263. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  264. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  265. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  266. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  267. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  268. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  269. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  270. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  271. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  272. package/dist/resolvers/SyncDataResolver.js +6 -2
  273. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  274. package/dist/resolvers/SyncRolesUsersResolver.d.ts +2 -0
  275. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  276. package/dist/resolvers/SyncRolesUsersResolver.js +23 -6
  277. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  278. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  279. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  280. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  281. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  282. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  283. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  284. package/dist/resolvers/TestQuerySQLResolver.js +65 -13
  285. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  286. package/dist/resolvers/TransactionGroupResolver.d.ts +19 -2
  287. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  288. package/dist/resolvers/TransactionGroupResolver.js +102 -24
  289. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  290. package/dist/resolvers/UserAvatarResolver.d.ts +16 -0
  291. package/dist/resolvers/UserAvatarResolver.d.ts.map +1 -0
  292. package/dist/resolvers/UserAvatarResolver.js +124 -0
  293. package/dist/resolvers/UserAvatarResolver.js.map +1 -0
  294. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  295. package/dist/resolvers/UserResolver.js +2 -17
  296. package/dist/resolvers/UserResolver.js.map +1 -1
  297. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  298. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  299. package/dist/resolvers/agent-run-data-guard.js +72 -0
  300. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  301. package/dist/resolvers/agentVisionConsentAudit.d.ts +37 -0
  302. package/dist/resolvers/agentVisionConsentAudit.d.ts.map +1 -0
  303. package/dist/resolvers/agentVisionConsentAudit.js +63 -0
  304. package/dist/resolvers/agentVisionConsentAudit.js.map +1 -0
  305. package/dist/resolvers/avatarInputValidation.d.ts +40 -0
  306. package/dist/resolvers/avatarInputValidation.d.ts.map +1 -0
  307. package/dist/resolvers/avatarInputValidation.js +97 -0
  308. package/dist/resolvers/avatarInputValidation.js.map +1 -0
  309. package/dist/resolvers/currentUserRoles.d.ts +29 -0
  310. package/dist/resolvers/currentUserRoles.d.ts.map +1 -1
  311. package/dist/resolvers/currentUserRoles.js +30 -0
  312. package/dist/resolvers/currentUserRoles.js.map +1 -1
  313. package/dist/resolvers/meetings/meetingMutations.d.ts +6 -0
  314. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  315. package/dist/resolvers/meetings/meetingMutations.js +21 -0
  316. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  317. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  318. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  319. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  320. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  321. package/dist/rest/ViewOperationsHandler.d.ts +3 -1
  322. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  323. package/dist/rest/ViewOperationsHandler.js +21 -12
  324. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  325. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  326. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  327. package/dist/rest/setupRESTEndpoints.js +6 -0
  328. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  329. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  330. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  331. package/dist/serverExtensionReservedRoots.js +14 -17
  332. package/dist/serverExtensionReservedRoots.js.map +1 -1
  333. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  334. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  335. package/dist/services/MJStorageBlobStore.js +25 -19
  336. package/dist/services/MJStorageBlobStore.js.map +1 -1
  337. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  338. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  339. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  340. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  341. package/dist/services/TaskGraphActionRunner.js +27 -5
  342. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  343. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  344. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  345. package/dist/services/TaskGraphAgentRunner.js +157 -9
  346. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  347. package/dist/services/TaskGraphContinuationDeliverer.d.ts +57 -4
  348. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  349. package/dist/services/TaskGraphContinuationDeliverer.js +607 -18
  350. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  351. package/dist/sharedCache.d.ts +105 -0
  352. package/dist/sharedCache.d.ts.map +1 -0
  353. package/dist/sharedCache.js +279 -0
  354. package/dist/sharedCache.js.map +1 -0
  355. package/dist/types.d.ts +6 -0
  356. package/dist/types.d.ts.map +1 -1
  357. package/dist/types.js.map +1 -1
  358. package/package.json +99 -96
  359. package/src/__tests__/ActionResolver.authorization.test.ts +353 -0
  360. package/src/__tests__/AdhocQueryResolver.sqlAuthorizer.test.ts +104 -0
  361. package/src/__tests__/BridgeHostInstance.test.ts +232 -0
  362. package/src/__tests__/ClientToolRequestResolver.test.ts +116 -0
  363. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  364. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  365. package/src/__tests__/FetchEntityVectorsResolver.test.ts +433 -0
  366. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  367. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  368. package/src/__tests__/HostInstance.test.ts +84 -0
  369. package/src/__tests__/HumanHandoffResolver.test.ts +17 -0
  370. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  371. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  372. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  373. package/src/__tests__/MeetingResolver.test.ts +75 -1
  374. package/src/__tests__/RealtimeBridgeResolver.test.ts +415 -6
  375. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +383 -0
  376. package/src/__tests__/RealtimeClientSessionResolver.test.ts +682 -58
  377. package/src/__tests__/RealtimeProxyServer.test.ts +16 -1
  378. package/src/__tests__/RealtimeRelay.test.ts +876 -0
  379. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  380. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +129 -0
  381. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  382. package/src/__tests__/ResolverBase.aggregateScreen.test.ts +174 -0
  383. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  384. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  385. package/src/__tests__/ResolverBase.clientOldValues.test.ts +212 -6
  386. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  387. package/src/__tests__/ReturningVisitorRecap.test.ts +115 -0
  388. package/src/__tests__/RunAIAgentResolver.branch.test.ts +170 -0
  389. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +6 -4
  390. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +258 -0
  391. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +166 -0
  392. package/src/__tests__/RunTemplateResolver.test.ts +132 -0
  393. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +63 -0
  394. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  395. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  396. package/src/__tests__/SessionJanitor.test.ts +1203 -21
  397. package/src/__tests__/SessionManager.test.ts +240 -17
  398. package/src/__tests__/SessionRunIDVerifier.test.ts +251 -0
  399. package/src/__tests__/SyncRolesUsersResolver.userRoles.test.ts +97 -0
  400. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  401. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  402. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +403 -22
  403. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  404. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  405. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  406. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  407. package/src/__tests__/UserAvatarResolver.test.ts +255 -0
  408. package/src/__tests__/UserResolver.currentUser.test.ts +16 -1
  409. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  410. package/src/__tests__/actionAuthorization.test.ts +130 -0
  411. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  412. package/src/__tests__/agentVisionConsentAudit.test.ts +118 -0
  413. package/src/__tests__/apiRootBoot.test.ts +202 -0
  414. package/src/__tests__/avatarInputValidation.test.ts +160 -0
  415. package/src/__tests__/cacheInvalidationRecordData.test.ts +64 -0
  416. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  417. package/src/__tests__/globalCors.test.ts +217 -0
  418. package/src/__tests__/isolatedProviderTransactions.test.ts +169 -0
  419. package/src/__tests__/magicLinkService.consume.test.ts +6 -0
  420. package/src/__tests__/magicLinkService.provisionLog.test.ts +5 -0
  421. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  422. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  423. package/src/__tests__/multiTenancy.test.ts +1 -1
  424. package/src/__tests__/newUsers.template.test.ts +151 -0
  425. package/src/__tests__/newUsers.test.ts +74 -5
  426. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +17 -2
  427. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  428. package/src/__tests__/pushStatusResolver.filter.test.ts +7 -0
  429. package/src/__tests__/realtimeRecordingHandler.test.ts +652 -0
  430. package/src/__tests__/realtimeSessions/RealtimeSessionEventService.test.ts +201 -0
  431. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +215 -0
  432. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationResolver.test.ts +102 -0
  433. package/src/__tests__/realtimeSessions/RealtimeSessionVerificationService.test.ts +349 -0
  434. package/src/__tests__/realtimeSessions/RealtimeVerifyRouter.test.ts +131 -0
  435. package/src/__tests__/realtimeSessions/SlidingWindowRateLimiter.test.ts +56 -0
  436. package/src/__tests__/realtimeSessions/helpers.ts +4 -0
  437. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +129 -0
  438. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +124 -0
  439. package/src/__tests__/realtimeSessions/verificationCore.test.ts +355 -0
  440. package/src/__tests__/realtimeSessions/verificationPages.test.ts +84 -0
  441. package/src/__tests__/realtimeSessions/verificationPolicy.test.ts +226 -0
  442. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +633 -0
  443. package/src/__tests__/resolverBase.fls.test.ts +87 -4
  444. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  445. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -96
  446. package/src/__tests__/sharedCache.test.ts +312 -0
  447. package/src/__tests__/widgetGuestElevation.test.ts +44 -0
  448. package/src/agentSessions/BridgeHostInstance.ts +32 -0
  449. package/src/agentSessions/HostInstance.ts +124 -28
  450. package/src/agentSessions/ReturningVisitorRecap.ts +7 -3
  451. package/src/agentSessions/SessionJanitor.ts +821 -59
  452. package/src/agentSessions/SessionManager.ts +138 -26
  453. package/src/agentSessions/SessionRunIDVerifier.ts +171 -0
  454. package/src/agentSessions/index.ts +10 -3
  455. package/src/apiRoot.ts +78 -0
  456. package/src/auth/AuthProviderCatalogRouter.ts +26 -0
  457. package/src/auth/LoginScreenEngine.ts +50 -0
  458. package/src/auth/actionAuthorization.ts +116 -0
  459. package/src/auth/index.ts +4 -8
  460. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  461. package/src/auth/magicLink/MagicLinkService.ts +6 -3
  462. package/src/auth/newUsers.ts +99 -11
  463. package/src/config.ts +171 -5
  464. package/src/context.ts +15 -10
  465. package/src/coreRoutes.ts +166 -0
  466. package/src/generated/graphql-schemas/__mj.ts +1815 -57
  467. package/src/generic/CacheInvalidationResolver.ts +73 -18
  468. package/src/generic/OrphanedConversationDetailReconciler.ts +3 -3
  469. package/src/generic/PushStatusResolver.ts +1 -1
  470. package/src/generic/ResolverBase.ts +335 -326
  471. package/src/generic/RunViewResolver.ts +21 -0
  472. package/src/globalCors.ts +71 -0
  473. package/src/index.ts +231 -300
  474. package/src/isolatedProvider.ts +22 -0
  475. package/src/multiTenancy/index.ts +24 -2
  476. package/src/realtimeProxy/RealtimeProxyServer.ts +148 -16
  477. package/src/realtimeProxy/RealtimeRelayTunnel.ts +645 -0
  478. package/src/realtimeSessions/README.md +179 -0
  479. package/src/realtimeSessions/RealtimeSessionEventService.ts +274 -0
  480. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +168 -0
  481. package/src/realtimeSessions/RealtimeVerifyRouter.ts +109 -0
  482. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  483. package/src/realtimeSessions/SessionVerificationPortsBase.ts +189 -0
  484. package/src/realtimeSessions/SlidingWindowRateLimiter.ts +92 -0
  485. package/src/realtimeSessions/index.ts +18 -0
  486. package/src/realtimeSessions/ownedRealtimeSession.ts +55 -0
  487. package/src/realtimeSessions/sessionAccess.ts +145 -0
  488. package/src/realtimeSessions/sessionConfigStamps.ts +123 -0
  489. package/src/realtimeSessions/subscriptionAuthorization.ts +116 -0
  490. package/src/realtimeSessions/verificationCore.ts +502 -0
  491. package/src/realtimeSessions/verificationPages.ts +149 -0
  492. package/src/realtimeSessions/verificationPolicy.ts +321 -0
  493. package/src/realtimeSessions/verificationWorkflow.ts +702 -0
  494. package/src/realtimeWidget/widgetGuestElevation.ts +34 -2
  495. package/src/resolvers/ActionResolver.ts +84 -21
  496. package/src/resolvers/AdhocQueryResolver.ts +24 -6
  497. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  498. package/src/resolvers/CacheStatsResolver.ts +89 -2
  499. package/src/resolvers/ClientToolRequestResolver.ts +25 -7
  500. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  501. package/src/resolvers/DatasetResolver.ts +70 -10
  502. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  503. package/src/resolvers/FetchEntityVectorsResolver.ts +179 -2
  504. package/src/resolvers/FileResolver.ts +321 -106
  505. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  506. package/src/resolvers/GetDataResolver.ts +6 -3
  507. package/src/resolvers/HumanHandoffResolver.ts +8 -0
  508. package/src/resolvers/IntegrationDiscoveryResolver.ts +4 -2
  509. package/src/resolvers/MCPResolver.ts +8 -5
  510. package/src/resolvers/QueryResolver.ts +11 -0
  511. package/src/resolvers/RealtimeBridgeResolver.ts +163 -11
  512. package/src/resolvers/RealtimeClientSessionResolver.ts +511 -129
  513. package/src/resolvers/RealtimeSessionEventsResolver.ts +133 -0
  514. package/src/resolvers/RealtimeSessionVerificationResolver.ts +174 -0
  515. package/src/resolvers/RemoteBrowserActionResolver.ts +11 -1
  516. package/src/resolvers/RunAIAgentResolver.ts +116 -14
  517. package/src/resolvers/RunTemplateResolver.ts +13 -12
  518. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  519. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  520. package/src/resolvers/SyncDataResolver.ts +6 -2
  521. package/src/resolvers/SyncRolesUsersResolver.ts +24 -6
  522. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  523. package/src/resolvers/TestQuerySQLResolver.ts +68 -13
  524. package/src/resolvers/TransactionGroupResolver.ts +131 -27
  525. package/src/resolvers/UserAvatarResolver.ts +111 -0
  526. package/src/resolvers/UserResolver.ts +2 -16
  527. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  528. package/src/resolvers/agent-run-data-guard.ts +89 -0
  529. package/src/resolvers/agentVisionConsentAudit.ts +82 -0
  530. package/src/resolvers/avatarInputValidation.ts +123 -0
  531. package/src/resolvers/currentUserRoles.ts +32 -1
  532. package/src/resolvers/meetings/meetingMutations.ts +21 -0
  533. package/src/rest/RealtimeRecordingHandler.ts +464 -0
  534. package/src/rest/ViewOperationsHandler.ts +29 -13
  535. package/src/rest/setupRESTEndpoints.ts +6 -0
  536. package/src/serverExtensionReservedRoots.ts +14 -17
  537. package/src/services/MJStorageBlobStore.ts +31 -30
  538. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  539. package/src/services/TaskGraphActionRunner.ts +25 -5
  540. package/src/services/TaskGraphAgentRunner.ts +188 -11
  541. package/src/services/TaskGraphContinuationDeliverer.ts +713 -17
  542. package/src/sharedCache.ts +305 -0
  543. package/src/types.ts +7 -0
@@ -11,7 +11,7 @@
11
11
  * load-truth-from-DB branch so denied fields hold real values a stripped SetMany never
12
12
  * touches.
13
13
  */
14
- import { describe, it, expect, vi, beforeEach } from 'vitest';
14
+ import { describe, it, expect, vi, beforeEach, beforeAll, afterAll } from 'vitest';
15
15
 
16
16
  // ─── Hoisted mocks ────────────────────────────────────────────────────────
17
17
  const { mockUserCacheUsers } = vi.hoisted(() => ({
@@ -116,7 +116,7 @@ vi.mock('@memberjunction/core-entities', () => ({}));
116
116
 
117
117
  // ─── Import after mocks ──────────────────────────────────────────────────
118
118
  import { ResolverBase } from '../generic/ResolverBase';
119
- import { EntityInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
119
+ import { EntityInfo, Metadata, RowLevelSecurityFilterInfo, UserInfo, UserRoleInfo } from '@memberjunction/core';
120
120
 
121
121
  // ─── Metadata builders (mirrors MJCore's fieldSecurity.enforcement tests) ──
122
122
 
@@ -192,8 +192,9 @@ class TestResolver extends ResolverBase {
192
192
  return this.StripDeniedReadFieldsFromClientInput(entityInfo, userInfo, input, clientNewValues);
193
193
  }
194
194
 
195
- public TestMustLoadTruth(entityInfo: EntityInfo, input: ClientInput, hasDeniedReadFields: boolean, hasNarrowedAuditPayload: boolean): boolean {
196
- return this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload);
195
+ /** The user defaults to HR, whose permission rows carry no row filter; pass null for a missing user. */
196
+ public TestMustLoadTruth(entityInfo: EntityInfo, input: ClientInput, hasDeniedReadFields: boolean, hasNarrowedAuditPayload: boolean, user: UserInfo | null = buildUser([HR_ROLE_ID])): boolean {
197
+ return this.MustLoadTruthFromDatabase(entityInfo, input, hasDeniedReadFields, hasNarrowedAuditPayload, user ?? undefined);
197
198
  }
198
199
  }
199
200
 
@@ -481,3 +482,85 @@ describe('ResolverBase.MustLoadTruthFromDatabase', () => {
481
482
  expect(resolver.TestMustLoadTruth(new EntityInfo(tracked), withOldValues(), false, false)).toBe(true);
482
483
  });
483
484
  });
485
+
486
+ /**
487
+ * #4919: a caller under an Update row filter must not be hydrated from its own OldValues.
488
+ *
489
+ * The save's post-image RLS check skips its query when no column the filter reads is dirty. Hydrated
490
+ * from OldValues, "dirty" is whatever the client claims: an Intern bound to `Status = 'Draft'` sends
491
+ * Status = 'Published' and claims 'Published' as the old value too, so Status is clean, the check is
492
+ * skipped, and the update moves the row out of the Intern's scope.
493
+ */
494
+ describe('ResolverBase.MustLoadTruthFromDatabase: Update row filters (#4919)', () => {
495
+ const DRAFTS_ONLY = 'F0000000-0000-0000-0000-000000004919';
496
+ let resolver: TestResolver;
497
+ let savedProvider: typeof Metadata.Provider;
498
+
499
+ beforeAll(() => {
500
+ savedProvider = Metadata.Provider;
501
+ Metadata.Provider = {
502
+ Entities: [],
503
+ RowLevelSecurityFilters: [new RowLevelSecurityFilterInfo({ ID: DRAFTS_ONLY, Name: 'Drafts only', FilterText: "Status = 'Draft'" })],
504
+ } as unknown as typeof Metadata.Provider;
505
+ });
506
+ afterAll(() => {
507
+ Metadata.Provider = savedProvider;
508
+ });
509
+ beforeEach(() => {
510
+ resolver = new TestResolver();
511
+ });
512
+
513
+ /** Untracked, no field security: the OldValues shortcut is open unless the row filter closes it. */
514
+ function entityWithInternFilter(filter: { UpdateRLSFilterID?: string; ReadRLSFilterID?: string }): EntityInfo {
515
+ const init = employeeEntityInit(false);
516
+ init['TrackRecordChanges'] = false;
517
+ init['Permissions'] = [
518
+ { EntityID: ENTITY_ID, RoleID: HR_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true },
519
+ { EntityID: ENTITY_ID, RoleID: INTERN_ROLE_ID, CanCreate: true, CanRead: true, CanUpdate: true, CanDelete: true, ...filter },
520
+ ];
521
+ return new EntityInfo(init);
522
+ }
523
+
524
+ const claimsNewStatusAsOld = (): ClientInput => ({
525
+ ID: '1',
526
+ Status: 'Published',
527
+ OldValues___: [
528
+ { Key: 'ID', Value: '1' },
529
+ { Key: 'Status', Value: 'Published' },
530
+ ],
531
+ });
532
+
533
+ it('loads the stored row for a caller under an Update row filter, whatever OldValues claim', () => {
534
+ const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
535
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID]))).toBe(true);
536
+ });
537
+
538
+ it('keeps the shortcut for a caller with no Update row filter, on the same entity', () => {
539
+ const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
540
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([HR_ROLE_ID]))).toBe(false);
541
+ // A role without the filter exempts the user from it (roles are additive).
542
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID, HR_ROLE_ID]))).toBe(false);
543
+ });
544
+
545
+ it('loads the stored row when the user is missing, instead of throwing or skipping the filter', () => {
546
+ const entity = entityWithInternFilter({ UpdateRLSFilterID: DRAFTS_ONLY });
547
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, null)).toBe(true);
548
+ });
549
+
550
+ it('loads the stored row under an API-key Update row filter, even for a role-exempt user', () => {
551
+ const entity = entityWithInternFilter({});
552
+ const keyBound = (permissionType: 'Read' | 'Update') => {
553
+ const user = buildUser([HR_ROLE_ID]); // no role filter: only the key binding narrows this caller
554
+ user.APIKeyRowFilters = [{ EntityID: ENTITY_ID, PermissionType: permissionType, FilterID: DRAFTS_ONLY }];
555
+ return user;
556
+ };
557
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([HR_ROLE_ID]))).toBe(false);
558
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, keyBound('Update'))).toBe(true);
559
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, keyBound('Read'))).toBe(false);
560
+ });
561
+
562
+ it('ignores a Read-only row filter: only the Update filter gates this save', () => {
563
+ const entity = entityWithInternFilter({ ReadRLSFilterID: DRAFTS_ONLY });
564
+ expect(resolver.TestMustLoadTruth(entity, claimsNewStatusAsOld(), false, false, buildUser([INTERN_ROLE_ID]))).toBe(false);
565
+ });
566
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * A host mj.config.cjs serverExtensions[] entry may be a partial OVERLAY of an extension an Open App
3
+ * package publishes: only DriverClass is required. The schema used to require RootPath and default
4
+ * Enabled to true, so `{ DriverClass, OwnsCORS: false }` failed config parsing at startup, and an
5
+ * overlay that omitted Enabled re-enabled an extension its package ships disabled. Completeness is
6
+ * checked after the merge instead.
7
+ */
8
+ import { describe, it, expect, vi } from 'vitest';
9
+
10
+ // config.ts evaluates `configInfo = LoadConfig()` at module load, and it rejects a configuration
11
+ // carrying no database credentials. Stubbing only the cosmiconfig search lets the module import so the
12
+ // REAL ServerExtensionSchema is under test (same pattern as newUserRolesDefault.test.ts).
13
+ vi.mock('cosmiconfig', () => ({
14
+ cosmiconfigSync: () => ({
15
+ search: () => ({
16
+ filepath: '/test/mj.config.cjs',
17
+ isEmpty: false,
18
+ config: { dbUsername: 'test-user', dbPassword: 'test-password', dbDatabase: 'test-db' },
19
+ }),
20
+ }),
21
+ }));
22
+
23
+ import { MergeServerExtensionConfigs, PrepareServerExtensionConfigs } from '@memberjunction/server-extensions-core';
24
+ import type { ServerExtensionConfig } from '@memberjunction/server-extensions-core';
25
+ import { ServerExtensionSchema } from '../config.js';
26
+
27
+ const published: ServerExtensionConfig = {
28
+ Enabled: true,
29
+ DriverClass: 'BallotEdge',
30
+ RootPath: '/vote',
31
+ OwnsCORS: true,
32
+ Settings: { RateLimitMax: 20 },
33
+ };
34
+
35
+ describe('host serverExtensions[] overlay schema', () => {
36
+ it('accepts an overlay that names only DriverClass and the fields it overrides', () => {
37
+ const parsed = ServerExtensionSchema.safeParse({ DriverClass: 'BallotEdge', OwnsCORS: false });
38
+ expect(parsed.success).toBe(true);
39
+ expect(parsed.success && parsed.data.RootPath).toBeUndefined();
40
+ expect(parsed.success && parsed.data.Enabled).toBeUndefined();
41
+ });
42
+
43
+ it('merges the overlay onto the published entry without losing RootPath or Enabled', () => {
44
+ const overlay = ServerExtensionSchema.parse({ DriverClass: 'BallotEdge', OwnsCORS: false, Settings: { RateLimitMax: 2000 } });
45
+ const [merged] = MergeServerExtensionConfigs([published], [overlay as ServerExtensionConfig]);
46
+ expect(merged).toMatchObject({ DriverClass: 'BallotEdge', RootPath: '/vote', Enabled: true, OwnsCORS: false });
47
+ expect(merged.Settings).toEqual({ RateLimitMax: 2000 });
48
+ });
49
+
50
+ it('does not re-enable an extension the package ships disabled', () => {
51
+ const overlay = ServerExtensionSchema.parse({ DriverClass: 'BallotEdge', Settings: { RateLimitMax: 5 } });
52
+ const [merged] = MergeServerExtensionConfigs([{ ...published, Enabled: false }], [overlay as ServerExtensionConfig]);
53
+ expect(merged.Enabled).toBe(false);
54
+ });
55
+
56
+ it('still rejects a host-only entry without a RootPath, after the merge', () => {
57
+ const overlay = ServerExtensionSchema.parse({ DriverClass: 'NotPublishedAnywhere' });
58
+ const problems: string[] = [];
59
+ const prepared = PrepareServerExtensionConfigs(
60
+ MergeServerExtensionConfigs([published], [overlay as ServerExtensionConfig]),
61
+ { onInvalid: (message: string) => problems.push(message) },
62
+ );
63
+ expect(prepared.map(c => c.DriverClass)).toEqual(['BallotEdge']);
64
+ expect(problems.some(p => p.includes('NotPublishedAnywhere') && p.includes('RootPath is empty'))).toBe(true);
65
+ });
66
+ });
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Pins the reserved-root set passed into prepareServerExtensionConfigs.
3
- * A new public mount in index.ts / a drifted `*_MOUNT_PATH` constant is a
4
- * contract failure — this file reads those sources as text so it never loads
5
- * `config.ts`.
3
+ * A drifted `*_MOUNT_PATH` constant is a contract failure — this file reads those
4
+ * sources as text so it never loads `config.ts`. That every core mount is reserved
5
+ * is checked against the real mounts in apiRootBoot.test.ts.
6
6
  */
7
7
  import { describe, it, expect } from 'vitest';
8
8
  import { readFileSync } from 'node:fs';
@@ -26,11 +26,6 @@ function exportedStringConst(relativeFile: string, name: string): string {
26
26
  return match[1];
27
27
  }
28
28
 
29
- function indexContainsLiteralPath(path: string): boolean {
30
- const src = readFileSync(join(SRC, 'index.ts'), 'utf8');
31
- return src.includes(`'${path}'`) || src.includes(`\`${path}\``) || src.includes(path.replace(/^\//, ''));
32
- }
33
-
34
29
  describe('coreReservedServerExtensionRoots', () => {
35
30
  it('stays in lockstep with exported *_MOUNT_PATH constants', () => {
36
31
  expect(CORE_CONSTANT_RESERVED_SERVER_EXTENSION_ROOTS).toEqual([
@@ -40,90 +35,11 @@ describe('coreReservedServerExtensionRoots', () => {
40
35
  ]);
41
36
  });
42
37
 
43
- it('includes the literal pre-auth paths serve() registers without a constant', () => {
44
- const indexSrc = readFileSync(join(SRC, 'index.ts'), 'utf8');
45
- for (const path of CORE_STATIC_RESERVED_SERVER_EXTENSION_ROOTS) {
46
- if (path === '/health') {
47
- expect(indexSrc, `${path} missing from serve()`).toContain("'/health/extensions'");
48
- } else if (path === '/realtime') {
49
- expect(indexSrc, `${path} missing from serve()`).toMatch(/REALTIME_SDP_EXCHANGE_PATH|'\/realtime'/);
50
- } else if (path === '/realtime-proxy') {
51
- expect(indexSrc, `${path} missing from serve()`).toContain('/realtime-proxy');
52
- } else {
53
- expect(indexSrc, `${path} missing from serve()`).toContain(`'${path}'`);
54
- }
55
- }
56
- expect(indexContainsLiteralPath('/healthcheck')).toBe(true);
57
- });
58
-
59
- it('structurally guarantees every pre-auth app.use / app.get / app.post mount in index.ts is reserved', () => {
60
- const indexSrc = readFileSync(join(SRC, 'index.ts'), 'utf8');
61
- const authMiddlewareIndex = indexSrc.indexOf('app.use(CreateUnifiedAuthMiddleware');
62
- expect(authMiddlewareIndex).toBeGreaterThan(0);
63
- const preAuthSrc = indexSrc.substring(0, authMiddlewareIndex);
64
-
65
- const mountRegex = /app\.(?:use|get|post)\s*\(\s*([^,\s)]+)/g;
66
- const reservedRoots = CoreReservedServerExtensionRoots('/');
67
-
68
- // Explicit allowlist of known non-path middleware mounts (e.g. app.use(mw), app.use(cors()), app.use(compression(...)))
69
- const knownNonPathTokens = [
70
- 'cors',
71
- 'express.',
72
- 'compression',
73
- 'cookieParser',
74
- 'CreateUnifiedAuthMiddleware',
75
- 'mw',
76
- ];
77
- const isKnownNonPath = (t: string): boolean =>
78
- knownNonPathTokens.some(k => t === k || t.startsWith(k));
79
-
80
- // Strip single-line and multi-line comments so comments like `app.use(...)` are not treated as code
81
- const cleanPreAuthSrc = preAuthSrc.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
82
-
83
- let match: RegExpExecArray | null;
84
- const mountedTokens: string[] = [];
85
- while ((match = mountRegex.exec(cleanPreAuthSrc)) !== null) {
86
- mountedTokens.push(match[1].trim());
87
- }
88
-
89
- expect(mountedTokens.length).toBeGreaterThan(0);
90
-
91
- const knownConstants: Record<string, string> = {
92
- REALTIME_SDP_EXCHANGE_PATH: '/realtime',
93
- MAGIC_LINK_MOUNT_PATH: '/magic-link',
94
- WIDGET_MOUNT_PATH: '/widget',
95
- AUTH_CATALOG_MOUNT_PATH: '/auth',
96
- };
97
-
98
- for (const token of mountedTokens) {
99
- const isLiteralPath = token.startsWith("'") || token.startsWith('"') || token.startsWith('`');
100
- const isPathConstant = /^[A-Z0-9_]+_PATH$/.test(token) || token in knownConstants;
101
- if (!isLiteralPath && !isPathConstant) {
102
- if (isKnownNonPath(token)) {
103
- continue;
104
- }
105
- throw new Error(`Unrecognized pre-auth mount argument '${token}' in index.ts. If this is a non-path middleware, add it to knownNonPathTokens; if it is a route, reserve its prefix or map its constant.`);
106
- }
107
-
108
- let pathPrefix: string;
109
- if (isLiteralPath) {
110
- pathPrefix = token.slice(1, -1);
111
- } else if (token in knownConstants) {
112
- pathPrefix = knownConstants[token];
113
- } else {
114
- throw new Error(`Unrecognized pre-auth mount constant '${token}' in index.ts. If this is a new pre-auth route, map its constant or reserve its prefix.`);
115
- }
116
-
117
- const isCovered = reservedRoots.some(root => pathPrefix === root || pathPrefix.startsWith(`${root}/`));
118
- expect(isCovered, `Pre-auth mount '${pathPrefix}' (from token '${token}') must be covered by reserved roots`).toBe(true);
119
- }
120
- });
121
-
122
- it('includes graphqlRootPath and every core mount', () => {
123
- const roots = CoreReservedServerExtensionRoots('/api');
38
+ it('includes every core mount, and no longer graphqlRootPath', () => {
39
+ const roots = CoreReservedServerExtensionRoots();
40
+ expect(roots).not.toContain('/api');
124
41
  expect(roots).toEqual(
125
42
  expect.arrayContaining([
126
- '/api',
127
43
  '/auth',
128
44
  '/magic-link',
129
45
  '/widget',
@@ -139,7 +55,7 @@ describe('coreReservedServerExtensionRoots', () => {
139
55
  });
140
56
 
141
57
  it('closes the F-M2 bypasses: cased /auth and sibling /healthcheck', () => {
142
- const extra = CoreReservedServerExtensionRoots('/');
58
+ const extra = CoreReservedServerExtensionRoots();
143
59
  expect(validateServerExtensionRootPath('/Auth', extra)).toMatch(/reserved prefix/);
144
60
  expect(validateServerExtensionRootPath('/healthcheck', extra)).toMatch(/reserved prefix/);
145
61
  expect(validateServerExtensionRootPath('/Healthcheck', extra)).toMatch(/reserved prefix/);
@@ -155,10 +71,9 @@ describe('coreReservedServerExtensionRoots', () => {
155
71
  expect(validateServerExtensionRootPath('/meetings/teams', extra)).toBeNull();
156
72
  });
157
73
 
158
- it('reserves a non-default graphqlRootPath and its nested paths', () => {
159
- const extra = CoreReservedServerExtensionRoots('/api');
160
- expect(validateServerExtensionRootPath('/api', extra)).toMatch(/reserved prefix/);
161
- expect(validateServerExtensionRootPath('/api/graphql', extra)).toMatch(/reserved prefix/);
162
- expect(validateServerExtensionRootPath('/apiv2', extra)).toBeNull();
74
+ it('lets extensions sit under the GraphQL path (#5674): RootPaths are relative to MJAPI_BASE_PATH', () => {
75
+ const extra = CoreReservedServerExtensionRoots();
76
+ expect(validateServerExtensionRootPath('/api', extra)).toBeNull();
77
+ expect(validateServerExtensionRootPath('/forms', extra)).toBeNull();
163
78
  });
164
79
  });
@@ -0,0 +1,312 @@
1
+ import 'reflect-metadata';
2
+ /**
3
+ * Routing of shared-cache events in MJAPI: metadata notices trigger a metadata check,
4
+ * everything reaches LocalCacheManager, and only RunView slot changes reach browsers.
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import type { CacheChangedEvent, ProviderBase } from '@memberjunction/core';
8
+
9
+ const dispatch = vi.fn();
10
+ const publish = vi.fn();
11
+ const startSweeper = vi.fn();
12
+
13
+ /** Intervals StartUserCacheChecks handed to the user cache. */
14
+ const userCacheInterval: number[] = [];
15
+
16
+ vi.mock('@memberjunction/core', () => ({
17
+ CacheCategory: { RunViewCache: 'RunViewCache', RunQueryCache: 'RunQueryCache', DatasetCache: 'DatasetCache', Metadata: 'Metadata', Default: 'default' },
18
+ LocalCacheManager: { Instance: {
19
+ DispatchCacheChange: (e: CacheChangedEvent) => dispatch(e),
20
+ // The metadata sweep takes a fleet lease before doing any work; grant it in tests.
21
+ TryAcquireSharedLease: vi.fn(async () => true),
22
+ } },
23
+ LogStatusEx: vi.fn(),
24
+ LogError: vi.fn(),
25
+ BaseEngineSweeper: { Instance: { Start: (ms: number) => startSweeper(ms) } },
26
+ }));
27
+ vi.mock('@memberjunction/redis-provider', () => ({ RedisLocalStorageProvider: vi.fn(function (this: Record<string, unknown>, cfg: unknown) { this.cfg = cfg; }) }));
28
+ vi.mock('../generic/PubSubManager.js', () => ({ PubSubManager: { Instance: { Publish: (...a: unknown[]) => publish(...a) } } }));
29
+ vi.mock('../generic/CacheInvalidationResolver.js', () => ({ CACHE_INVALIDATION_TOPIC: 'CACHE_INVALIDATION' }));
30
+ vi.mock('@memberjunction/generic-database-provider', () => ({
31
+ UserCache: { Instance: { StartStalenessChecks: (ms: number) => userCacheInterval.push(ms) } },
32
+ }));
33
+
34
+ import {
35
+ CacheManagerConfigFromSettings, CreateSharedCacheFromEnvironment, MJAPI_PUBLISH_MODES, ResolveSharedCacheTTLSeconds, RouteSharedCacheEvent,
36
+ StartEngineSweeper,
37
+ StartUserCacheChecks,
38
+ StartMetadataSweep,
39
+ WirePushStatusFanOut,
40
+ } from '../sharedCache.js';
41
+ import { MJGlobal } from '@memberjunction/global';
42
+ import type { PubSubEngine } from 'type-graphql';
43
+ import { PublishStatusUpdate, SetPushStatusPublishHook } from '../generic/PushStatusResolver.js';
44
+ import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
45
+ import type { CacheSettingsConfig } from '../config.js';
46
+
47
+ function settings(overrides: Partial<CacheSettingsConfig>): CacheSettingsConfig {
48
+ return { maxMemoryMB: 150, maxPercentOfCachePerEntity: 50, maxEntryPercentOfCache: 'auto', defaultTTLSeconds: 0, evictionSweepIntervalSeconds: 300, verboseLogging: false, engineSweepIntervalSeconds: 300, ...overrides };
49
+ }
50
+
51
+ function event(overrides: Partial<CacheChangedEvent>): CacheChangedEvent {
52
+ return { CacheKey: 'k', Category: 'RunViewCache', Action: 'set', Timestamp: 1, SourceServerId: 'peer-server', ...overrides };
53
+ }
54
+
55
+ function provider(isNotice: boolean): ProviderBase {
56
+ return { HandlePeerMetadataNotice: vi.fn(() => isNotice) } as unknown as ProviderBase;
57
+ }
58
+
59
+ describe('shared cache event routing', () => {
60
+ beforeEach(() => {
61
+ dispatch.mockReset();
62
+ publish.mockReset();
63
+ });
64
+
65
+ it('publishes RunView rows in full, metadata snapshots as notices, and nothing else', () => {
66
+ expect(MJAPI_PUBLISH_MODES).toEqual({ RunViewCache: 'full', default: 'notice' });
67
+ });
68
+
69
+ it('hands a metadata notice to the provider and stops there', () => {
70
+ const p = provider(true);
71
+ RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_Timestamps' }), p);
72
+ expect(p.HandlePeerMetadataNotice).toHaveBeenCalledOnce();
73
+ expect(dispatch).not.toHaveBeenCalled();
74
+ expect(publish).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it('dispatches a RunView change and relays it to browsers as an entity invalidation', () => {
78
+ RouteSharedCacheEvent(event({ CacheKey: 'MJ: AI Models|_|_|-1|0|_|_|imr:1' }), provider(false));
79
+ expect(dispatch).toHaveBeenCalledOnce();
80
+ expect(publish).toHaveBeenCalledWith('CACHE_INVALIDATION', expect.objectContaining({ entityName: 'MJ: AI Models', action: 'set', primaryKeyValues: null }));
81
+ });
82
+
83
+ it('does not relay other categories or category clears to browsers', () => {
84
+ RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_AllMetadata' }), provider(false));
85
+ RouteSharedCacheEvent(event({ Category: 'DatasetCache', CacheKey: 'ds' }), provider(false));
86
+ RouteSharedCacheEvent(event({ Category: 'RunViewCache', CacheKey: 'RunViewCache', Action: 'category_cleared' }), provider(false));
87
+ expect(dispatch).toHaveBeenCalledTimes(3);
88
+ expect(publish).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('ignores notices that arrive before the metadata provider exists', () => {
92
+ RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_Timestamps' }), undefined);
93
+ expect(dispatch).toHaveBeenCalledOnce();
94
+ expect(publish).not.toHaveBeenCalled();
95
+ });
96
+ });
97
+
98
+ describe('shared cache configuration', () => {
99
+ it('builds the provider only when REDIS_URL is set, with MJAPI publish modes', () => {
100
+ expect(CreateSharedCacheFromEnvironment(undefined, {})).toBeNull();
101
+ const provider = CreateSharedCacheFromEnvironment(settings({ verboseLogging: true, sharedCacheTTLSeconds: 120 }),
102
+ { REDIS_URL: 'redis://h:1', REDIS_KEY_PREFIX: 'fleet' }) as unknown as { cfg: Record<string, unknown> };
103
+ expect(provider).toBeInstanceOf(RedisLocalStorageProvider);
104
+ expect(provider.cfg).toMatchObject({
105
+ url: 'redis://h:1', keyPrefix: 'fleet', defaultTTLSeconds: 120, enablePubSub: true, enableLogging: true,
106
+ publishModes: { RunViewCache: 'full', default: 'notice' }, defaultPublishMode: 'none',
107
+ });
108
+ });
109
+
110
+ it('lets REDIS_TTL_SECONDS override the configured TTL, and ignores junk', () => {
111
+ expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: '60' })).toBe(60);
112
+ expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: '0' })).toBe(0);
113
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
114
+ expect(ResolveSharedCacheTTLSeconds(120, { REDIS_TTL_SECONDS: 'soon' })).toBe(120);
115
+ warn.mockRestore();
116
+ expect(ResolveSharedCacheTTLSeconds(undefined, {})).toBeUndefined();
117
+ });
118
+
119
+ it('maps cacheSettings onto LocalCacheManager settings', () => {
120
+ expect(CacheManagerConfigFromSettings(settings({ maxMemoryMB: 10, maxPercentOfCachePerEntity: 20, maxEntryPercentOfCache: 40, defaultTTLSeconds: 5, evictionSweepIntervalSeconds: 7, verboseLogging: true })))
121
+ .toEqual({ maxSizeBytes: 10 * 1024 * 1024, maxPercentOfCachePerEntity: 20, maxEntryPercentOfCache: 40, defaultTTLMs: 5000, evictionSweepIntervalMs: 7000, verboseLogging: true });
122
+ expect(CacheManagerConfigFromSettings(undefined).maxSizeBytes).toBe(150 * 1024 * 1024);
123
+ // The per-entry ceiling reaches the cache, and an unset one is derived rather than a fixed fraction.
124
+ expect(CacheManagerConfigFromSettings(undefined).maxEntryPercentOfCache).toBe('auto');
125
+ });
126
+
127
+ it('starts the engine sweep at the configured interval, off when 0', () => {
128
+ startSweeper.mockReset();
129
+ expect(StartEngineSweeper(settings({ engineSweepIntervalSeconds: 60 }))).toBe(60_000);
130
+ expect(startSweeper).toHaveBeenLastCalledWith(60_000);
131
+ expect(StartEngineSweeper(undefined)).toBe(300_000);
132
+ expect(StartEngineSweeper(settings({ engineSweepIntervalSeconds: 0 }))).toBe(0);
133
+ expect(startSweeper).toHaveBeenLastCalledWith(0);
134
+ });
135
+
136
+ it('starts the user-cache staleness check at the configured interval, off when 0', () => {
137
+ userCacheInterval.length = 0;
138
+ expect(StartUserCacheChecks(settings({ userCacheCheckIntervalSeconds: 60 }))).toBe(60_000);
139
+ expect(StartUserCacheChecks(undefined)).toBe(300_000);
140
+ expect(StartUserCacheChecks(settings({ userCacheCheckIntervalSeconds: 0 }))).toBe(0);
141
+ expect(userCacheInterval).toEqual([60_000, 300_000, 0]);
142
+ });
143
+ });
144
+
145
+ /**
146
+ * The metadata sweep is the backstop for metadata written outside MJ — and nothing else.
147
+ *
148
+ * Metadata staleness is otherwise event-driven: a `BaseEntity` write to a member entity schedules a
149
+ * refresh, a peer's snapshot write publishes a notice, a CLI write clears the cache. None of those
150
+ * fire for raw SQL, so metadata edited directly in the database is never noticed by a running
151
+ * process.
152
+ *
153
+ * It must not become an unconditional poll. A recurring query prevents a serverless database from
154
+ * pausing, and `TrustServerCacheCompletely = true` (every MJ entity, by default) states that the
155
+ * event paths already cover the entity — so there is nothing a query could find.
156
+ */
157
+ describe('StartMetadataSweep', () => {
158
+ afterEach(() => {
159
+ StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 0 }), () => undefined);
160
+ vi.useRealTimers();
161
+ });
162
+
163
+ it('returns the configured interval, and 0 disables it', () => {
164
+ expect(StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 45 }), () => undefined)).toBe(45_000);
165
+ expect(StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 0 }), () => undefined)).toBe(0);
166
+ });
167
+
168
+ it('asks the provider NOTHING when no metadata entity declares drift', async () => {
169
+ vi.useFakeTimers();
170
+ const sweep = vi.fn();
171
+ const provider = {
172
+ MetadataMembersDeclaringDrift: () => [],
173
+ SweepMetadataAgainstDatabase: sweep,
174
+ } as unknown as ProviderBase;
175
+
176
+ StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 10 }), () => provider);
177
+ await vi.advanceTimersByTimeAsync(10_000 * 3 + 100);
178
+
179
+ expect(sweep).not.toHaveBeenCalled();
180
+ });
181
+
182
+ it('sweeps when a metadata entity declares drift', async () => {
183
+ vi.useFakeTimers();
184
+ const sweep = vi.fn().mockResolvedValue({ Declared: ['MJ: Entities'], Checked: true, Refreshed: true });
185
+ const provider = {
186
+ MetadataMembersDeclaringDrift: () => ['MJ: Entities'],
187
+ SweepMetadataAgainstDatabase: sweep,
188
+ } as unknown as ProviderBase;
189
+
190
+ StartMetadataSweep(settings({ metadataSweepIntervalSeconds: 10 }), () => provider);
191
+ await vi.advanceTimersByTimeAsync(10_000 + 100);
192
+
193
+ expect(sweep).toHaveBeenCalled();
194
+ });
195
+ });
196
+
197
+ /**
198
+ * The push-status fan-out's WIRING (MJ #4222).
199
+ *
200
+ * `pushStatusFanOut.test.ts` covers the pieces — what is worth replicating, that every publish is
201
+ * stamped with the publishing instance, and every branch of `ParseReplicatedStatusUpdate`. What
202
+ * nothing covered is the wiring that joins them to Redis, which is the code that moved into this
203
+ * module: that a channel is actually subscribed, that inbound traffic is parsed against THIS
204
+ * process's id and handed to `PubSubManager`, that a local publish is forwarded on the same
205
+ * channel, and that a Redis failure degrades the feature instead of stopping the server.
206
+ *
207
+ * The parser and the publisher here are the real ones; mocking them would make these tests of the
208
+ * mock rather than of the wiring.
209
+ */
210
+ describe('push-status fan-out wiring', () => {
211
+ /** A stand-in for the shared cache provider, capturing what the fan-out does to it. */
212
+ class FakeSharedCache {
213
+ public SubscribedChannel: string | undefined;
214
+ public Published: Array<{ channel: string; payload: string }> = [];
215
+ private inboundHandler: ((raw: string) => void) | undefined;
216
+ constructor(private subscribeFails = false) {}
217
+
218
+ public SubscribeToChannel = vi.fn(async (channel: string, handler: (raw: string) => void) => {
219
+ if (this.subscribeFails) {
220
+ throw new Error('redis is down');
221
+ }
222
+ this.SubscribedChannel = channel;
223
+ this.inboundHandler = handler;
224
+ return () => undefined;
225
+ });
226
+
227
+ public PublishMessage = vi.fn((channel: string, payload: string) => {
228
+ this.Published.push({ channel, payload });
229
+ });
230
+
231
+ /** Simulates Redis delivering a message on the subscribed channel. */
232
+ public Deliver(raw: string): void {
233
+ this.inboundHandler?.(raw);
234
+ }
235
+
236
+ public AsProvider(): RedisLocalStorageProvider {
237
+ return this as unknown as RedisLocalStorageProvider;
238
+ }
239
+ }
240
+
241
+ /** A PubSubEngine the real publisher can publish locally on, which these tests ignore. */
242
+ function localTopic(): PubSubEngine {
243
+ return { publish: vi.fn(async () => undefined) } as unknown as PubSubEngine;
244
+ }
245
+
246
+ beforeEach(() => {
247
+ publish.mockReset();
248
+ SetPushStatusPublishHook(undefined);
249
+ });
250
+ afterEach(() => {
251
+ SetPushStatusPublishHook(undefined);
252
+ });
253
+
254
+ it('subscribes to the push-status channel on the shared connection', async () => {
255
+ const redis = new FakeSharedCache();
256
+ await WirePushStatusFanOut(redis.AsProvider());
257
+
258
+ expect(redis.SubscribeToChannel).toHaveBeenCalledTimes(1);
259
+ expect(redis.SubscribedChannel).toBe('push-status-updates');
260
+ });
261
+
262
+ it("hands a PEER's update to this process's local topic, identity fields intact", async () => {
263
+ const redis = new FakeSharedCache();
264
+ await WirePushStatusFanOut(redis.AsProvider());
265
+
266
+ redis.Deliver(JSON.stringify({
267
+ sessionId: 'session-1', ownerUserId: 'user-1', message: 'done', SourceServerId: 'some-other-replica',
268
+ }));
269
+
270
+ expect(publish).toHaveBeenCalledTimes(1);
271
+ expect(publish).toHaveBeenCalledWith('PUSH_STATUS_UPDATES', {
272
+ sessionId: 'session-1', ownerUserId: 'user-1', message: 'done', SourceServerId: 'some-other-replica',
273
+ });
274
+ });
275
+
276
+ it("drops this process's own update echoed back, so a push is not delivered twice", async () => {
277
+ // The parser owns the comparison; this pins that the wiring gives it THIS process's id.
278
+ const redis = new FakeSharedCache();
279
+ await WirePushStatusFanOut(redis.AsProvider());
280
+
281
+ redis.Deliver(JSON.stringify({
282
+ sessionId: 'session-1', ownerUserId: 'user-1', message: 'done',
283
+ SourceServerId: MJGlobal.Instance.ProcessUUID,
284
+ }));
285
+
286
+ expect(publish).not.toHaveBeenCalled();
287
+ });
288
+
289
+ it('forwards a real local publish onto the same channel', async () => {
290
+ const redis = new FakeSharedCache();
291
+ await WirePushStatusFanOut(redis.AsProvider());
292
+
293
+ PublishStatusUpdate(localTopic(), { sessionId: 'session-9', ownerUserId: 'user-9', message: 'agent finished' });
294
+
295
+ expect(redis.Published).toHaveLength(1);
296
+ expect(redis.Published[0].channel).toBe('push-status-updates');
297
+ expect(JSON.parse(redis.Published[0].payload)).toEqual({
298
+ sessionId: 'session-9', ownerUserId: 'user-9', message: 'agent finished',
299
+ SourceServerId: MJGlobal.Instance.ProcessUUID,
300
+ });
301
+ });
302
+
303
+ it('degrades instead of stopping the server when Redis cannot subscribe', async () => {
304
+ const redis = new FakeSharedCache(true);
305
+
306
+ await expect(WirePushStatusFanOut(redis.AsProvider())).resolves.toBeUndefined();
307
+
308
+ // No hook may be left pointing at a connection it could not subscribe on.
309
+ PublishStatusUpdate(localTopic(), { sessionId: 's', ownerUserId: 'u', message: 'x' });
310
+ expect(redis.PublishMessage).not.toHaveBeenCalled();
311
+ });
312
+ });