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

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 (560) hide show
  1. package/README.md +47 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +91 -30
  4. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  5. package/dist/agentSessions/SessionJanitor.js +311 -81
  6. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  7. package/dist/agentSessions/SessionManager.d.ts +17 -3
  8. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  9. package/dist/agentSessions/SessionManager.js +44 -10
  10. package/dist/agentSessions/SessionManager.js.map +1 -1
  11. package/dist/apiRoot.d.ts +32 -0
  12. package/dist/apiRoot.d.ts.map +1 -0
  13. package/dist/apiRoot.js +67 -0
  14. package/dist/apiRoot.js.map +1 -0
  15. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  16. package/dist/auth/AuthProviderCatalogRouter.js +27 -0
  17. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  18. package/dist/auth/LoginScreenEngine.d.ts +22 -0
  19. package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
  20. package/dist/auth/LoginScreenEngine.js +57 -0
  21. package/dist/auth/LoginScreenEngine.js.map +1 -0
  22. package/dist/auth/index.d.ts +1 -0
  23. package/dist/auth/index.d.ts.map +1 -1
  24. package/dist/auth/index.js +52 -10
  25. package/dist/auth/index.js.map +1 -1
  26. package/dist/auth/initializeProviders.d.ts +10 -0
  27. package/dist/auth/initializeProviders.d.ts.map +1 -1
  28. package/dist/auth/initializeProviders.js +37 -1
  29. package/dist/auth/initializeProviders.js.map +1 -1
  30. package/dist/auth/localDevAuth.d.ts +79 -0
  31. package/dist/auth/localDevAuth.d.ts.map +1 -0
  32. package/dist/auth/localDevAuth.js +439 -0
  33. package/dist/auth/localDevAuth.js.map +1 -0
  34. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  35. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  36. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  37. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  39. package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
  40. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  41. package/dist/auth/magicLink/types.d.ts +12 -0
  42. package/dist/auth/magicLink/types.d.ts.map +1 -1
  43. package/dist/auth/sessionUserClone.d.ts +1 -1
  44. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  45. package/dist/auth/sessionUserClone.js +3 -2
  46. package/dist/auth/sessionUserClone.js.map +1 -1
  47. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  48. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  49. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  50. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  51. package/dist/broadcast/index.d.ts +6 -0
  52. package/dist/broadcast/index.d.ts.map +1 -0
  53. package/dist/broadcast/index.js +6 -0
  54. package/dist/broadcast/index.js.map +1 -0
  55. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  56. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  57. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  58. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  59. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  60. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  61. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  62. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  63. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  64. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  65. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  66. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  67. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  68. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  69. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  70. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  71. package/dist/cache.d.ts +3 -2
  72. package/dist/cache.d.ts.map +1 -1
  73. package/dist/cache.js.map +1 -1
  74. package/dist/config.d.ts +632 -22
  75. package/dist/config.d.ts.map +1 -1
  76. package/dist/config.js +104 -4
  77. package/dist/config.js.map +1 -1
  78. package/dist/context.d.ts +3 -1
  79. package/dist/context.d.ts.map +1 -1
  80. package/dist/context.js +29 -8
  81. package/dist/context.js.map +1 -1
  82. package/dist/coreRoutes.d.ts +29 -0
  83. package/dist/coreRoutes.d.ts.map +1 -0
  84. package/dist/coreRoutes.js +156 -0
  85. package/dist/coreRoutes.js.map +1 -0
  86. package/dist/generated/graphql-schemas/__mj.d.ts +1016 -29
  87. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  88. package/dist/generated/graphql-schemas/__mj.js +5125 -434
  89. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  90. package/dist/generic/CacheInvalidationResolver.d.ts +85 -30
  91. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  92. package/dist/generic/CacheInvalidationResolver.js +175 -60
  93. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  94. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  95. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  96. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  97. package/dist/generic/PubSubManager.d.ts +10 -0
  98. package/dist/generic/PubSubManager.d.ts.map +1 -1
  99. package/dist/generic/PubSubManager.js +44 -1
  100. package/dist/generic/PubSubManager.js.map +1 -1
  101. package/dist/generic/PushStatusResolver.d.ts +39 -6
  102. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  103. package/dist/generic/PushStatusResolver.js +94 -12
  104. package/dist/generic/PushStatusResolver.js.map +1 -1
  105. package/dist/generic/ResolverBase.d.ts +168 -69
  106. package/dist/generic/ResolverBase.d.ts.map +1 -1
  107. package/dist/generic/ResolverBase.js +464 -405
  108. package/dist/generic/ResolverBase.js.map +1 -1
  109. package/dist/generic/RunViewResolver.d.ts +11 -3
  110. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  111. package/dist/generic/RunViewResolver.js +91 -13
  112. package/dist/generic/RunViewResolver.js.map +1 -1
  113. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  114. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  115. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  116. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  117. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  118. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  119. package/dist/generic/fieldSecurityTransport.js +30 -0
  120. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  121. package/dist/globalCors.d.ts +33 -0
  122. package/dist/globalCors.d.ts.map +1 -0
  123. package/dist/globalCors.js +58 -0
  124. package/dist/globalCors.js.map +1 -0
  125. package/dist/index.d.ts +14 -0
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +230 -222
  128. package/dist/index.js.map +1 -1
  129. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  130. package/dist/integration/CustomColumnPromoter.js +6 -1
  131. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  132. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  133. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  134. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  135. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  136. package/dist/multiTenancy/index.d.ts.map +1 -1
  137. package/dist/multiTenancy/index.js +22 -2
  138. package/dist/multiTenancy/index.js.map +1 -1
  139. package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
  140. package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
  141. package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
  142. package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
  143. package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
  144. package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
  145. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
  146. package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
  147. package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
  148. package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
  149. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  150. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  151. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  152. package/dist/realtimeSessions/index.d.ts +2 -0
  153. package/dist/realtimeSessions/index.d.ts.map +1 -1
  154. package/dist/realtimeSessions/index.js +2 -0
  155. package/dist/realtimeSessions/index.js.map +1 -1
  156. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +32 -0
  157. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
  158. package/dist/realtimeSessions/ownedRealtimeSession.js +56 -0
  159. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
  160. package/dist/realtimeSessions/sessionAccess.d.ts +91 -8
  161. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  162. package/dist/realtimeSessions/sessionAccess.js +127 -20
  163. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  164. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  165. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  166. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  167. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  168. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  169. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  170. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  171. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  172. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  173. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  174. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  175. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  176. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  177. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  178. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  179. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  180. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  181. package/dist/realtimeWidget/host-identity.js +1 -1
  182. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  183. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  184. package/dist/realtimeWidget/widgetCore.js +17 -1
  185. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  186. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  187. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  188. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  189. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  190. package/dist/resolvers/ActionResolver.d.ts +19 -1
  191. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  192. package/dist/resolvers/ActionResolver.js +63 -12
  193. package/dist/resolvers/ActionResolver.js.map +1 -1
  194. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  195. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  196. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  197. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  198. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  199. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  200. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  201. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  202. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  203. package/dist/resolvers/AgentSessionResolver.js +40 -17
  204. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  205. package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
  206. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
  207. package/dist/resolvers/ArtifactFileResolver.js +9 -7
  208. package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
  209. package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
  210. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  211. package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
  212. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  213. package/dist/resolvers/DatasetResolver.d.ts +24 -3
  214. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  215. package/dist/resolvers/DatasetResolver.js +57 -8
  216. package/dist/resolvers/DatasetResolver.js.map +1 -1
  217. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  218. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  219. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  220. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  221. package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
  222. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  223. package/dist/resolvers/EntityRecordNameResolver.js +3 -2
  224. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  225. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +46 -3
  226. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  227. package/dist/resolvers/FetchEntityVectorsResolver.js +211 -50
  228. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  229. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  230. package/dist/resolvers/FileCategoryResolver.js +1 -0
  231. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  232. package/dist/resolvers/FileResolver.d.ts +87 -11
  233. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  234. package/dist/resolvers/FileResolver.js +292 -105
  235. package/dist/resolvers/FileResolver.js.map +1 -1
  236. package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
  237. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  238. package/dist/resolvers/GetDataContextDataResolver.js +117 -38
  239. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  240. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  241. package/dist/resolvers/GetDataResolver.js +6 -3
  242. package/dist/resolvers/GetDataResolver.js.map +1 -1
  243. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  244. package/dist/resolvers/IntegrationDiscoveryResolver.js +49 -9
  245. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  246. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  247. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  248. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  249. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  250. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  251. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  252. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  253. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  254. package/dist/resolvers/QueryResolver.d.ts +2 -0
  255. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  256. package/dist/resolvers/QueryResolver.js +33 -6
  257. package/dist/resolvers/QueryResolver.js.map +1 -1
  258. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  259. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  260. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  261. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  262. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  263. package/dist/resolvers/RSUResolver.js +12 -0
  264. package/dist/resolvers/RSUResolver.js.map +1 -1
  265. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +18 -10
  266. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  267. package/dist/resolvers/RealtimeClientSessionResolver.js +92 -54
  268. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  269. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  270. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  271. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  272. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  273. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  274. package/dist/resolvers/RunAIAgentResolver.js +241 -67
  275. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  276. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  277. package/dist/resolvers/RunTemplateResolver.js +4 -4
  278. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  279. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  280. package/dist/resolvers/RunTestResolver.js +3 -1
  281. package/dist/resolvers/RunTestResolver.js.map +1 -1
  282. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
  283. package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
  284. package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
  285. package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
  286. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  287. package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
  288. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  289. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  290. package/dist/resolvers/SyncDataResolver.js +4 -0
  291. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  292. package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
  293. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  294. package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
  295. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  296. package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
  297. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  298. package/dist/resolvers/TestQuerySQLResolver.js +66 -13
  299. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  300. package/dist/resolvers/TransactionGroupResolver.d.ts +34 -2
  301. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  302. package/dist/resolvers/TransactionGroupResolver.js +150 -27
  303. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  304. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  305. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  306. package/dist/resolvers/UserViewResolver.js +2 -1
  307. package/dist/resolvers/UserViewResolver.js.map +1 -1
  308. package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
  309. package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
  310. package/dist/resolvers/agent-run-data-guard.js +72 -0
  311. package/dist/resolvers/agent-run-data-guard.js.map +1 -0
  312. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  313. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  314. package/dist/rest/EntityCRUDHandler.js +19 -5
  315. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  316. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  317. package/dist/rest/RESTEndpointHandler.js +8 -5
  318. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  319. package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
  320. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
  321. package/dist/rest/RealtimeRecordingHandler.js +383 -0
  322. package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
  323. package/dist/rest/ViewOperationsHandler.d.ts +9 -1
  324. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  325. package/dist/rest/ViewOperationsHandler.js +43 -17
  326. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  327. package/dist/rest/setupRESTEndpoints.d.ts +6 -0
  328. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  329. package/dist/rest/setupRESTEndpoints.js +6 -0
  330. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  331. package/dist/serverExtensionReservedRoots.d.ts +12 -10
  332. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  333. package/dist/serverExtensionReservedRoots.js +14 -17
  334. package/dist/serverExtensionReservedRoots.js.map +1 -1
  335. package/dist/serverRoleConfigUnits.d.ts +29 -0
  336. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  337. package/dist/serverRoleConfigUnits.js +33 -0
  338. package/dist/serverRoleConfigUnits.js.map +1 -0
  339. package/dist/services/MJStorageBlobStore.d.ts +8 -1
  340. package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
  341. package/dist/services/MJStorageBlobStore.js +25 -19
  342. package/dist/services/MJStorageBlobStore.js.map +1 -1
  343. package/dist/services/StartTaskGraphDispatcher.js +2 -2
  344. package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
  345. package/dist/services/TaskGraphActionRunner.d.ts +5 -3
  346. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  347. package/dist/services/TaskGraphActionRunner.js +27 -5
  348. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  349. package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
  350. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  351. package/dist/services/TaskGraphAgentRunner.js +157 -9
  352. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  353. package/dist/services/TaskGraphContinuationDeliverer.d.ts +59 -2
  354. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  355. package/dist/services/TaskGraphContinuationDeliverer.js +626 -16
  356. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  357. package/dist/sharedCache.d.ts.map +1 -1
  358. package/dist/sharedCache.js +3 -0
  359. package/dist/sharedCache.js.map +1 -1
  360. package/dist/types.d.ts +23 -1
  361. package/dist/types.d.ts.map +1 -1
  362. package/dist/types.js.map +1 -1
  363. package/package.json +99 -98
  364. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  365. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  366. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  367. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  368. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  369. package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
  370. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  371. package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
  372. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  373. package/src/__tests__/FetchEntityVectorsResolver.test.ts +369 -18
  374. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  375. package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
  376. package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
  377. package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
  378. package/src/__tests__/LoginScreenEngine.test.ts +86 -0
  379. package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
  380. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  381. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  382. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  383. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  384. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  385. package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
  386. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  387. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
  388. package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
  389. package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
  390. package/src/__tests__/ResolverBase.clientOldValues.test.ts +243 -7
  391. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  392. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  393. package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
  394. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  395. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  396. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  397. package/src/__tests__/RunAIAgentResolver.branch.test.ts +69 -13
  398. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  399. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  400. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +20 -28
  401. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +264 -0
  402. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  403. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  404. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  405. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  406. package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
  407. package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
  408. package/src/__tests__/SessionJanitor.test.ts +365 -10
  409. package/src/__tests__/SessionManager.test.ts +122 -0
  410. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
  411. package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
  412. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +468 -23
  413. package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
  414. package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
  415. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  416. package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
  417. package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
  418. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  419. package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
  420. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  421. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  422. package/src/__tests__/agent-run-data-guard.test.ts +130 -0
  423. package/src/__tests__/apiRootBoot.test.ts +202 -0
  424. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  425. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  426. package/src/__tests__/cacheInvalidationRecordData.test.ts +109 -0
  427. package/src/__tests__/conversationRunEvents.test.ts +56 -14
  428. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  429. package/src/__tests__/globalCors.test.ts +217 -0
  430. package/src/__tests__/localDevAuth.test.ts +196 -0
  431. package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
  432. package/src/__tests__/multiTenancy.security.test.ts +51 -0
  433. package/src/__tests__/multiTenancy.test.ts +1 -1
  434. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  435. package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
  436. package/src/__tests__/pushStatusResolver.filter.test.ts +64 -1
  437. package/src/__tests__/realtimeRecordingHandler.test.ts +669 -0
  438. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  439. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  440. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  441. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  442. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  443. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  444. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  445. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  446. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  447. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  448. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  449. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  450. package/src/__tests__/runViewProperties.test.ts +114 -0
  451. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  452. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  453. package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
  454. package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
  455. package/src/__tests__/sessionUserClone.test.ts +5 -1
  456. package/src/__tests__/sharedCache.test.ts +5 -3
  457. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  458. package/src/__tests__/unifiedAuth.test.ts +28 -0
  459. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  460. package/src/agentSessions/SessionJanitor.ts +369 -93
  461. package/src/agentSessions/SessionManager.ts +45 -10
  462. package/src/apiRoot.ts +78 -0
  463. package/src/auth/AuthProviderCatalogRouter.ts +29 -0
  464. package/src/auth/LoginScreenEngine.ts +50 -0
  465. package/src/auth/index.ts +57 -12
  466. package/src/auth/initializeProviders.ts +43 -1
  467. package/src/auth/localDevAuth.ts +528 -0
  468. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  469. package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
  470. package/src/auth/magicLink/types.ts +10 -0
  471. package/src/auth/sessionUserClone.ts +3 -2
  472. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  473. package/src/broadcast/index.ts +5 -0
  474. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  475. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  476. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  477. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  478. package/src/cache.ts +2 -1
  479. package/src/config.ts +115 -5
  480. package/src/context.ts +32 -8
  481. package/src/coreRoutes.ts +181 -0
  482. package/src/generated/graphql-schemas/__mj.ts +3684 -284
  483. package/src/generic/CacheInvalidationResolver.ts +172 -60
  484. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  485. package/src/generic/PubSubManager.ts +54 -1
  486. package/src/generic/PushStatusResolver.ts +109 -12
  487. package/src/generic/ResolverBase.ts +540 -431
  488. package/src/generic/RunViewResolver.ts +97 -15
  489. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  490. package/src/generic/fieldSecurityTransport.ts +62 -0
  491. package/src/globalCors.ts +71 -0
  492. package/src/index.ts +239 -238
  493. package/src/integration/CustomColumnPromoter.ts +6 -1
  494. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  495. package/src/multiTenancy/index.ts +24 -2
  496. package/src/realtimeSessions/README.md +7 -3
  497. package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
  498. package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
  499. package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
  500. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  501. package/src/realtimeSessions/index.ts +2 -0
  502. package/src/realtimeSessions/ownedRealtimeSession.ts +69 -0
  503. package/src/realtimeSessions/sessionAccess.ts +183 -23
  504. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  505. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  506. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  507. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  508. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  509. package/src/realtimeWidget/host-identity.ts +1 -1
  510. package/src/realtimeWidget/widgetCore.ts +30 -1
  511. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  512. package/src/resolvers/ActionResolver.ts +78 -15
  513. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  514. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  515. package/src/resolvers/AgentSessionResolver.ts +46 -18
  516. package/src/resolvers/ArtifactFileResolver.ts +9 -8
  517. package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
  518. package/src/resolvers/DatasetResolver.ts +70 -10
  519. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  520. package/src/resolvers/EntityRecordNameResolver.ts +3 -2
  521. package/src/resolvers/FetchEntityVectorsResolver.ts +248 -55
  522. package/src/resolvers/FileCategoryResolver.ts +1 -0
  523. package/src/resolvers/FileResolver.ts +334 -109
  524. package/src/resolvers/GetDataContextDataResolver.ts +139 -41
  525. package/src/resolvers/GetDataResolver.ts +6 -3
  526. package/src/resolvers/IntegrationDiscoveryResolver.ts +53 -9
  527. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  528. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  529. package/src/resolvers/QueryResolver.ts +31 -6
  530. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  531. package/src/resolvers/RSUResolver.ts +12 -0
  532. package/src/resolvers/RealtimeClientSessionResolver.ts +114 -66
  533. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  534. package/src/resolvers/RunAIAgentResolver.ts +296 -85
  535. package/src/resolvers/RunTemplateResolver.ts +6 -4
  536. package/src/resolvers/RunTestResolver.ts +3 -1
  537. package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
  538. package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
  539. package/src/resolvers/SyncDataResolver.ts +4 -0
  540. package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
  541. package/src/resolvers/TestQuerySQLResolver.ts +69 -13
  542. package/src/resolvers/TransactionGroupResolver.ts +185 -30
  543. package/src/resolvers/UserViewResolver.ts +2 -1
  544. package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
  545. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  546. package/src/resolvers/agent-run-data-guard.ts +89 -0
  547. package/src/rest/EntityCRUDHandler.ts +22 -5
  548. package/src/rest/RESTEndpointHandler.ts +8 -5
  549. package/src/rest/RealtimeRecordingHandler.ts +467 -0
  550. package/src/rest/ViewOperationsHandler.ts +52 -18
  551. package/src/rest/setupRESTEndpoints.ts +6 -0
  552. package/src/serverExtensionReservedRoots.ts +14 -17
  553. package/src/serverRoleConfigUnits.ts +48 -0
  554. package/src/services/MJStorageBlobStore.ts +31 -30
  555. package/src/services/StartTaskGraphDispatcher.ts +2 -2
  556. package/src/services/TaskGraphActionRunner.ts +25 -5
  557. package/src/services/TaskGraphAgentRunner.ts +188 -11
  558. package/src/services/TaskGraphContinuationDeliverer.ts +738 -16
  559. package/src/sharedCache.ts +3 -0
  560. package/src/types.ts +23 -1
@@ -0,0 +1,403 @@
1
+ import 'reflect-metadata';
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+ import type { EntityInfo, UserInfo } from '@memberjunction/core';
4
+ import type { PubSubEngine } from 'type-graphql';
5
+ import {
6
+ AzureWebPubSubBroadcastProvider,
7
+ AWSIoTCoreBroadcastProvider,
8
+ CacheInvalidationBroadcastManager,
9
+ GCPPubSubBroadcastProvider,
10
+ SequencedRoleBucketBroadcastProvider,
11
+ type AzureWebPubSubGroupMessage,
12
+ type AWSIoTCoreTopicMessage,
13
+ type GCPPubSubTopicMessage,
14
+ } from '../broadcast/index.js';
15
+ import type { CacheInvalidationPayload } from '../generic/CacheInvalidationResolver.js';
16
+ import {
17
+ GetSessionStatusTopic,
18
+ PUSH_STATUS_UPDATES_TOPIC,
19
+ ParseStatusUpdateMessage,
20
+ PublishStatusUpdate,
21
+ ShouldReplicateStatusUpdate,
22
+ type PushStatusNotificationPayload,
23
+ } from '../generic/PushStatusResolver.js';
24
+ import { CreateSessionRoutingPubSub, PubSubManager } from '../generic/PubSubManager.js';
25
+ import {
26
+ ResolveServerRole,
27
+ ShouldRunBackgroundWorkers,
28
+ ShouldServeInteractiveTraffic,
29
+ } from '../serverRoleConfigUnits.js';
30
+
31
+ function makeUser(id: string, roleIds: string[]): UserInfo {
32
+ return {
33
+ ID: id,
34
+ Name: `User ${id}`,
35
+ Email: `${id}@example.com`,
36
+ UserRoles: roleIds.map((r) => ({ UserID: id, RoleID: r, RoleName: r })),
37
+ } as unknown as UserInfo;
38
+ }
39
+
40
+ function makePayload(overrides: Partial<CacheInvalidationPayload> = {}): CacheInvalidationPayload {
41
+ return {
42
+ entityName: 'Invoices',
43
+ primaryKeyValues: '[{"FieldName":"ID","Value":"inv-1"}]',
44
+ action: 'save',
45
+ sourceServerId: 'srv-1',
46
+ timestamp: new Date('2026-10-20T12:00:00Z'),
47
+ recordData: '{"ID":"inv-1","Total":500}',
48
+ ...overrides,
49
+ };
50
+ }
51
+
52
+ describe('CacheInvalidationBroadcastManager & SequencedRoleBucketBroadcastProvider (Topologies 1 & 2)', () => {
53
+ let provider: SequencedRoleBucketBroadcastProvider;
54
+
55
+ beforeEach(() => {
56
+ provider = CacheInvalidationBroadcastManager.Instance.Configure({
57
+ provider: 'SequencedRoleBucket',
58
+ ringBufferSize: 5,
59
+ excludedEntities: ['MJ: Audit Logs'],
60
+ }) as SequencedRoleBucketBroadcastProvider;
61
+ provider.ResetStateForTesting();
62
+ });
63
+
64
+ it('defaults to SequencedRoleBucket and stamps monotonically increasing sequences', () => {
65
+ expect(provider.Topology).toBe('SequencedRoleBucket');
66
+ expect(provider.CurrentSequence).toBe(0);
67
+
68
+ const p1 = provider.RecordInvalidation(makePayload({ primaryKeyValues: 'pk-1' }));
69
+ const p2 = provider.RecordInvalidation(makePayload({ primaryKeyValues: 'pk-2' }));
70
+
71
+ expect(p1.sequence).toBe(1);
72
+ expect(p2.sequence).toBe(2);
73
+ expect(provider.CurrentSequence).toBe(2);
74
+ expect(provider.BufferedEventCount).toBe(2);
75
+ });
76
+
77
+ it('suppresses excluded high-churn entities from sequencing and broadcast', () => {
78
+ expect(provider.ShouldBroadcastEntity('MJ: Audit Logs')).toBe(false);
79
+ expect(provider.ShouldBroadcastEntity('Invoices')).toBe(true);
80
+
81
+ const ignored = provider.RecordInvalidation(makePayload({ entityName: 'MJ: Audit Logs' }));
82
+ expect(ignored.sequence).toBeUndefined();
83
+ expect(provider.CurrentSequence).toBe(0);
84
+ });
85
+
86
+ it('computes canonical, order-independent role fingerprints and memoizes per user object', () => {
87
+ const u1 = makeUser('u1', ['Role-B', 'role-a']);
88
+ const u2 = makeUser('u2', ['ROLE-A', ' role-b ']);
89
+
90
+ const fp1 = provider.GetRoleFingerprint(u1);
91
+ const fp2 = provider.GetRoleFingerprint(u2);
92
+
93
+ expect(fp1).toBe('roles:role-a|role-b');
94
+ expect(fp2).toBe('roles:role-a|role-b');
95
+ // Second call hits WeakMap cache
96
+ expect(provider.GetRoleFingerprint(u1)).toBe(fp1);
97
+ });
98
+
99
+ it('coalesces permission & RLS evaluation to O(Roles) instead of O(Subscribers)', () => {
100
+ const getUserPermisionsSpy = vi.fn((user: UserInfo) => {
101
+ const hasDeniedOnly = user.UserRoles.every((r) => r.RoleID === 'role-none');
102
+ return { CanRead: !hasDeniedOnly };
103
+ });
104
+
105
+ const fakeEntity = {
106
+ Name: 'Invoices',
107
+ GetUserPermisions: getUserPermisionsSpy,
108
+ Permissions: [
109
+ { RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
110
+ { RoleID: 'role-rls', CanRead: true, Type: 'Allow', ReadRLSFilterID: 'rls-filter-1' },
111
+ { RoleID: 'role-none', CanRead: false, Type: 'Allow', ReadRLSFilterID: null },
112
+ ],
113
+ } as unknown as EntityInfo;
114
+
115
+ vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
116
+ const rlsSpy = vi.spyOn(provider, 'ReadIsRowFiltered');
117
+
118
+ const payload = makePayload();
119
+
120
+ // Create 300 subscribers across only 3 distinct role buckets (100 per bucket)
121
+ const openUsers = Array.from({ length: 100 }, (_, i) => makeUser(`open-${i}`, ['role-open']));
122
+ const rlsUsers = Array.from({ length: 100 }, (_, i) => makeUser(`rls-${i}`, ['role-rls']));
123
+ const deniedUsers = Array.from({ length: 100 }, (_, i) => makeUser(`denied-${i}`, ['role-none']));
124
+
125
+ for (const u of openUsers) {
126
+ expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(true);
127
+ const shaped = provider.ShapeEventForSubscriber(payload, u, fakeEntity);
128
+ expect(shaped.PrimaryKeyValues).toBe(payload.primaryKeyValues);
129
+ expect(shaped.RecordData).toBe(payload.recordData);
130
+ }
131
+
132
+ for (const u of rlsUsers) {
133
+ expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(true);
134
+ const shaped = provider.ShapeEventForSubscriber(payload, u, fakeEntity);
135
+ expect(shaped.PrimaryKeyValues).toBeUndefined();
136
+ expect(shaped.RecordData).toBeUndefined();
137
+ }
138
+
139
+ for (const u of deniedUsers) {
140
+ expect(provider.EvaluateEntityReadAccess(payload, u)).toBe(false);
141
+ }
142
+
143
+ // Despite 300 subscribers, GetUserPermisions ran only 3 times (once per unique role bucket)
144
+ // and ReadIsRowFiltered ran only 2 times (for the 2 allowed role buckets)!
145
+ expect(getUserPermisionsSpy).toHaveBeenCalledTimes(3);
146
+ expect(rlsSpy).toHaveBeenCalledTimes(2);
147
+ });
148
+
149
+ it('serves delta catch-up from the circular ring buffer and coalesces duplicate PK updates', () => {
150
+ const fakeEntity = {
151
+ Name: 'Invoices',
152
+ GetUserPermisions: () => ({ CanRead: true }),
153
+ Permissions: [{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
154
+ } as unknown as EntityInfo;
155
+ vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
156
+
157
+ const user = makeUser('u1', ['role-open']);
158
+
159
+ // Record 4 events (two on inv-1, one on inv-2, one on inv-1 again)
160
+ provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-1', recordData: '{"v":1}' })); // seq 1
161
+ provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-2', recordData: '{"v":2}' })); // seq 2
162
+ provider.RecordInvalidation(makePayload({ primaryKeyValues: 'inv-1', recordData: '{"v":3}' })); // seq 3
163
+
164
+ const catchUp = provider.GetChangesSince(0, user);
165
+ expect(catchUp.RequiresFullRefresh).toBe(false);
166
+ expect(catchUp.CurrentSequence).toBe(3);
167
+ // inv-1 (seq 1 and seq 3) coalesced to latest (seq 3), so 2 events total
168
+ expect(catchUp.Events).toHaveLength(2);
169
+ const inv1Event = catchUp.Events.find((e) => e.PrimaryKeyValues === 'inv-1');
170
+ expect(inv1Event?.RecordData).toBe('{"v":3}');
171
+ expect(inv1Event?.Sequence).toBe(3);
172
+ });
173
+
174
+ it('signals RequiresFullRefresh when sinceSequence has been evicted from the circular ring buffer', () => {
175
+ const fakeEntity = {
176
+ Name: 'Invoices',
177
+ GetUserPermisions: () => ({ CanRead: true }),
178
+ Permissions: [{ RoleID: 'role-open', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
179
+ } as unknown as EntityInfo;
180
+ vi.spyOn(provider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
181
+
182
+ const user = makeUser('u1', ['role-open']);
183
+
184
+ // Ring buffer size is 5; push 7 events so sequences 1 and 2 are overwritten (oldest in buffer is 3)
185
+ for (let i = 1; i <= 7; i++) {
186
+ provider.RecordInvalidation(makePayload({ primaryKeyValues: `inv-${i}` }));
187
+ }
188
+
189
+ // Requesting sinceSequence = 1 (missed seq 2, which was evicted) -> RequiresFullRefresh: true
190
+ const evictedCatchUp = provider.GetChangesSince(1, user);
191
+ expect(evictedCatchUp.RequiresFullRefresh).toBe(true);
192
+ expect(evictedCatchUp.Events).toHaveLength(0);
193
+
194
+ // Requesting sinceSequence = 2 (oldest needed is seq 3, still in buffer) -> RequiresFullRefresh: false
195
+ const validCatchUp = provider.GetChangesSince(2, user);
196
+ expect(validCatchUp.RequiresFullRefresh).toBe(false);
197
+ expect(validCatchUp.Events).toHaveLength(5);
198
+ });
199
+ });
200
+
201
+ describe('Topology 3 Cloud Multiplexer Providers (AzureWebPubSub, AWSIoTCore, GCPPubSub)', () => {
202
+ afterEach(() => {
203
+ CacheInvalidationBroadcastManager.Instance.Configure({ provider: 'SequencedRoleBucket' });
204
+ });
205
+
206
+ it('AzureWebPubSubBroadcastProvider negotiates signed JWT URLs and publishes role-shaped group messages', async () => {
207
+ const azureProvider = CacheInvalidationBroadcastManager.Instance.Configure({
208
+ provider: 'AzureWebPubSub',
209
+ azureWebPubSub: {
210
+ endpoint: 'https://mj-test.webpubsub.azure.com',
211
+ hubName: 'mj_hub',
212
+ accessKey: 'test-secret-access-key-123456',
213
+ tokenTtlMinutes: 30,
214
+ },
215
+ }) as AzureWebPubSubBroadcastProvider;
216
+
217
+ expect(azureProvider).toBeInstanceOf(AzureWebPubSubBroadcastProvider);
218
+ expect(azureProvider.Topology).toBe('AzureWebPubSub');
219
+
220
+ const user = makeUser('u-azure', ['Role-Admin', 'Role-Finance']);
221
+ const negotiated = await azureProvider.NegotiateClientConnection(user);
222
+ expect(negotiated.Provider).toBe('AzureWebPubSub');
223
+ expect(negotiated.Url).toContain('wss://mj-test.webpubsub.azure.com/client/hubs/mj_hub?access_token=');
224
+ expect(negotiated.Token).toBeDefined();
225
+ expect(negotiated.Groups).toEqual(['mj-role-role-admin', 'mj-role-role-finance']);
226
+
227
+ const published: AzureWebPubSubGroupMessage[] = [];
228
+ azureProvider.SetGroupPublisher(async (msg) => {
229
+ published.push(msg);
230
+ });
231
+
232
+ const fakeEntity = {
233
+ Name: 'Invoices',
234
+ GetUserPermisions: () => ({ CanRead: true }),
235
+ Permissions: [
236
+ { RoleID: 'role-admin', CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
237
+ { RoleID: 'role-rls', CanRead: true, Type: 'Allow', ReadRLSFilterID: 'rls-1' },
238
+ ],
239
+ } as unknown as EntityInfo;
240
+ vi.spyOn(azureProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
241
+
242
+ azureProvider.RecordInvalidation(makePayload());
243
+ await new Promise((r) => setTimeout(r, 10));
244
+
245
+ expect(published).toHaveLength(2);
246
+ const adminMsg = published.find((m) => m.GroupName === 'mj-role-role-admin');
247
+ const rlsMsg = published.find((m) => m.GroupName === 'mj-role-role-rls');
248
+ expect(adminMsg?.Event.PrimaryKeyValues).toBeDefined();
249
+ expect(rlsMsg?.Event.PrimaryKeyValues).toBeUndefined();
250
+ });
251
+
252
+ it('AWSIoTCoreBroadcastProvider negotiates SigV4 presigned MQTT URLs and publishes role topics', async () => {
253
+ const awsProvider = CacheInvalidationBroadcastManager.Instance.Configure({
254
+ provider: 'AWSIoTCore',
255
+ awsIoTCore: {
256
+ endpoint: 'a1b2c3-ats.iot.us-east-1.amazonaws.com',
257
+ region: 'us-east-1',
258
+ topicPrefix: 'mj/prod/cache',
259
+ accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
260
+ secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
261
+ },
262
+ }) as AWSIoTCoreBroadcastProvider;
263
+
264
+ expect(awsProvider).toBeInstanceOf(AWSIoTCoreBroadcastProvider);
265
+ const user = makeUser('u-aws', ['role-ops']);
266
+ const negotiated = await awsProvider.NegotiateClientConnection(user);
267
+ expect(negotiated.Provider).toBe('AWSIoTCore');
268
+ expect(negotiated.Url).toContain('wss://a1b2c3-ats.iot.us-east-1.amazonaws.com/mqtt?X-Amz-Algorithm=AWS4-HMAC-SHA256');
269
+ expect(negotiated.Groups).toEqual(['mj/prod/cache/roles/role-ops']);
270
+
271
+ const published: AWSIoTCoreTopicMessage[] = [];
272
+ awsProvider.SetTopicPublisher(async (msg) => {
273
+ published.push(msg);
274
+ });
275
+ const fakeEntity = {
276
+ Name: 'Invoices',
277
+ GetUserPermisions: () => ({ CanRead: true }),
278
+ Permissions: [{ RoleID: 'role-ops', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
279
+ } as unknown as EntityInfo;
280
+ vi.spyOn(awsProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
281
+
282
+ awsProvider.RecordInvalidation(makePayload());
283
+ await new Promise((r) => setTimeout(r, 10));
284
+ expect(published).toHaveLength(1);
285
+ expect(published[0].Topic).toBe('mj/prod/cache/roles/role-ops');
286
+ });
287
+
288
+ it('GCPPubSubBroadcastProvider negotiates gateway tokens and publishes Pub/Sub topic messages', async () => {
289
+ const gcpProvider = CacheInvalidationBroadcastManager.Instance.Configure({
290
+ provider: 'GCPPubSub',
291
+ gcpPubSub: {
292
+ projectId: 'mj-cloud-prod',
293
+ topicPrefix: 'mj-invalidation',
294
+ gatewayUrl: 'https://cache-gw.run.app/ws',
295
+ signingSecret: 'gcp-gateway-secret-key',
296
+ },
297
+ }) as GCPPubSubBroadcastProvider;
298
+
299
+ expect(gcpProvider).toBeInstanceOf(GCPPubSubBroadcastProvider);
300
+ const user = makeUser('u-gcp', ['role-analyst']);
301
+ const negotiated = await gcpProvider.NegotiateClientConnection(user);
302
+ expect(negotiated.Provider).toBe('GCPPubSub');
303
+ expect(negotiated.Url).toContain('wss://cache-gw.run.app/ws?token=');
304
+ expect(negotiated.Groups).toEqual(['projects/mj-cloud-prod/topics/mj-invalidation-role-role-analyst']);
305
+
306
+ const published: GCPPubSubTopicMessage[] = [];
307
+ gcpProvider.SetTopicPublisher(async (msg) => {
308
+ published.push(msg);
309
+ });
310
+ const fakeEntity = {
311
+ Name: 'Invoices',
312
+ GetUserPermisions: () => ({ CanRead: true }),
313
+ Permissions: [{ RoleID: 'role-analyst', CanRead: true, Type: 'Allow', ReadRLSFilterID: null }],
314
+ } as unknown as EntityInfo;
315
+ vi.spyOn(gcpProvider, 'ResolveEntityForPayload').mockReturnValue(fakeEntity);
316
+
317
+ gcpProvider.RecordInvalidation(makePayload());
318
+ await new Promise((r) => setTimeout(r, 10));
319
+ expect(published).toHaveLength(1);
320
+ expect(published[0].TopicName).toBe('projects/mj-cloud-prod/topics/mj-invalidation-role-role-analyst');
321
+ });
322
+ });
323
+
324
+ describe('PushStatusResolver O(1) session routing and WeakMap JSON memoization', () => {
325
+ it('routes session-specific updates to PUSH_STATUS_UPDATES:session:<id> and wildcard updates to PUSH_STATUS_UPDATES', async () => {
326
+ const rawPublish = vi.fn(async () => undefined);
327
+ const rawPubSub: PubSubEngine = {
328
+ publish: rawPublish,
329
+ subscribe: vi.fn(),
330
+ unsubscribe: vi.fn(),
331
+ asyncIterator: vi.fn(),
332
+ } as unknown as PubSubEngine;
333
+
334
+ PubSubManager.Instance.SetPubSubEngine(rawPubSub);
335
+ const routedPubSub = PubSubManager.Instance.PubSubEngine!;
336
+
337
+ PublishStatusUpdate(routedPubSub, {
338
+ sessionId: 'tab-abc',
339
+ ownerUserId: 'user-1',
340
+ message: '{"type":"Progress"}',
341
+ });
342
+
343
+ PublishStatusUpdate(routedPubSub, {
344
+ sessionId: '*',
345
+ ownerUserId: 'user-1',
346
+ message: '{"type":"Broadcast"}',
347
+ });
348
+
349
+ expect(rawPublish).toHaveBeenCalledTimes(2);
350
+ expect(rawPublish).toHaveBeenNthCalledWith(
351
+ 1,
352
+ GetSessionStatusTopic('tab-abc'),
353
+ expect.objectContaining({ sessionId: 'tab-abc' })
354
+ );
355
+ expect(rawPublish).toHaveBeenNthCalledWith(
356
+ 2,
357
+ PUSH_STATUS_UPDATES_TOPIC,
358
+ expect.objectContaining({ sessionId: '*' })
359
+ );
360
+ });
361
+
362
+ it('memoizes JSON.parse per PushStatusNotificationPayload object via WeakMap', () => {
363
+ const payload: PushStatusNotificationPayload = {
364
+ sessionId: 'tab-1',
365
+ ownerUserId: 'u-1',
366
+ message: JSON.stringify({ type: 'StreamingContent', step: 1 }),
367
+ };
368
+
369
+ const parseSpy = vi.spyOn(JSON, 'parse');
370
+ try {
371
+ const first = ParseStatusUpdateMessage(payload);
372
+ const second = ParseStatusUpdateMessage(payload);
373
+ const shouldReplicate = ShouldReplicateStatusUpdate(payload.message, payload);
374
+
375
+ expect(first).toBe(second);
376
+ expect(shouldReplicate).toBe(false);
377
+ expect(parseSpy).toHaveBeenCalledTimes(1);
378
+ } finally {
379
+ parseSpy.mockRestore();
380
+ }
381
+ });
382
+ });
383
+
384
+ describe('ServerRole configuration units (Pillar 4)', () => {
385
+ it('resolves all, api, and worker roles case-insensitively and defaults to all', () => {
386
+ expect(ResolveServerRole(undefined)).toBe('all');
387
+ expect(ResolveServerRole('')).toBe('all');
388
+ expect(ResolveServerRole(' API ')).toBe('api');
389
+ expect(ResolveServerRole('WORKER')).toBe('worker');
390
+ expect(ResolveServerRole('all')).toBe('all');
391
+ expect(() => ResolveServerRole('invalid-role')).toThrow(/Invalid MJ_SERVER_ROLE/);
392
+ });
393
+
394
+ it('correctly gates background workers and interactive traffic by role', () => {
395
+ expect(ShouldRunBackgroundWorkers('all')).toBe(true);
396
+ expect(ShouldRunBackgroundWorkers('worker')).toBe(true);
397
+ expect(ShouldRunBackgroundWorkers('api')).toBe(false);
398
+
399
+ expect(ShouldServeInteractiveTraffic('all')).toBe(true);
400
+ expect(ShouldServeInteractiveTraffic('api')).toBe(true);
401
+ expect(ShouldServeInteractiveTraffic('worker')).toBe(false);
402
+ });
403
+ });
@@ -5,7 +5,11 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
5
5
  import {
6
6
  ConfigureRecordDataBroadcast,
7
7
  MayBroadcastRecordData,
8
+ ReadIsRowFiltered,
9
+ ShapeCacheInvalidationEvent,
8
10
  } from '../generic/CacheInvalidationResolver.js';
11
+ import { EntityInfo } from '@memberjunction/core';
12
+ import { EmployeeEntity } from './fieldSecurityFixtures.js';
9
13
 
10
14
  /**
11
15
  * The cache-invalidation subscription is delivered to every connected client with no per-user
@@ -60,6 +64,49 @@ describe('MayBroadcastRecordData', () => {
60
64
  });
61
65
  });
62
66
 
67
+ /**
68
+ * An entity under field-level security never broadcasts its row, whatever the allowlist says.
69
+ *
70
+ * The event reaches every signed-in session that may read the entity, with no per-user
71
+ * projection, so a row of an entity whose fields are restricted per role would hand every
72
+ * session the columns it may not read. The check is the IS-A-aware one: a child whose parent has
73
+ * field security carries the parent's restricted fields as its own.
74
+ */
75
+ describe('MayBroadcastRecordData: entities under field-level security', () => {
76
+ afterEach(() => ConfigureRecordDataBroadcast([]));
77
+
78
+ it("REGRESSION: refuses an FLS entity even under an explicit '*'", () => {
79
+ ConfigureRecordDataBroadcast(['*']);
80
+ expect(MayBroadcastRecordData('Employees', EmployeeEntity(true))).toBe(false);
81
+ });
82
+
83
+ it('REGRESSION: refuses an FLS entity that is named in the allowlist', () => {
84
+ ConfigureRecordDataBroadcast(['Employees']);
85
+ expect(MayBroadcastRecordData('Employees', EmployeeEntity(true))).toBe(false);
86
+ });
87
+
88
+ it('still allows the same entity with field security off', () => {
89
+ ConfigureRecordDataBroadcast(['Employees']);
90
+ expect(MayBroadcastRecordData('Employees', EmployeeEntity(false))).toBe(true);
91
+ });
92
+
93
+ it("REGRESSION: refuses MJ: Record Changes under '*', with or without its metadata", () => {
94
+ // A Record Change row carries other entities' values in ChangesJSON / FullRecordJSON, and its
95
+ // own FieldLevelSecurityApplies is false; the per-row projection never runs on this push.
96
+ ConfigureRecordDataBroadcast(['*']);
97
+ const recordChanges = new EntityInfo({ ID: 'rc', Name: 'MJ: Record Changes', SchemaName: '__mj', BaseTable: 'RecordChange', BaseView: 'vwRecordChanges', Fields: [] });
98
+ expect(recordChanges.FieldLevelSecurityApplies).toBe(false);
99
+ expect(MayBroadcastRecordData('MJ: Record Changes', recordChanges)).toBe(false);
100
+ expect(MayBroadcastRecordData('MJ: Record Changes')).toBe(false);
101
+ expect(MayBroadcastRecordData(' mj: record changes ')).toBe(false);
102
+ });
103
+
104
+ it('REGRESSION: refuses MJ: Record Changes even when named in the allowlist', () => {
105
+ ConfigureRecordDataBroadcast(['MJ: Record Changes']);
106
+ expect(MayBroadcastRecordData('MJ: Record Changes')).toBe(false);
107
+ });
108
+ });
109
+
63
110
  /**
64
111
  * Importing this module must not drag server config loading in with it.
65
112
  *
@@ -175,3 +222,65 @@ describe('cacheInvalidationFilter', () => {
175
222
  expect(filter({ payload: { ...event(''), entityName: '' } as never, context: session({ ID: 'u1' }) })).toBe(false);
176
223
  });
177
224
  });
225
+
226
+ /**
227
+ * Row-level narrowing (MJ#5241): a subscriber who reads the entity only through a row-level security filter learns that a
228
+ * row changed, not which one; a subscriber with an unfiltered read keeps the key; a subscriber with no read is the entity-level
229
+ * filter's business and gets nothing here to shape.
230
+ */
231
+ describe('ReadIsRowFiltered and ShapeCacheInvalidationEvent', () => {
232
+ const FILTERED_ROLE = 'role-participant';
233
+ const OPEN_ROLE = 'role-staff';
234
+ const entity = {
235
+ Permissions: [
236
+ { RoleID: FILTERED_ROLE, CanRead: true, Type: 'Allow', ReadRLSFilterID: 'filter-1' },
237
+ { RoleID: OPEN_ROLE, CanRead: true, Type: 'Allow', ReadRLSFilterID: null },
238
+ { RoleID: 'role-denied', CanRead: true, Type: 'Deny', ReadRLSFilterID: null },
239
+ ],
240
+ };
241
+ const payload = {
242
+ entityName: 'Spaces',
243
+ primaryKeyValues: 'ID|abc',
244
+ action: 'save',
245
+ sourceServerId: 'srv',
246
+ timestamp: new Date('2026-10-07T00:00:00Z'),
247
+ originSessionId: 'sess',
248
+ recordData: '{"Name":"x"}',
249
+ };
250
+
251
+ it('is row-filtered when every read the user holds carries a filter', () => {
252
+ expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: FILTERED_ROLE }] })).toBe(true);
253
+ });
254
+
255
+ it('is not row-filtered when any read the user holds is unfiltered, whichever role brings it', () => {
256
+ expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: OPEN_ROLE }] })).toBe(false);
257
+ expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: FILTERED_ROLE }, { RoleID: OPEN_ROLE }] })).toBe(false);
258
+ });
259
+
260
+ it('is not row-filtered when the user holds no read at all (the entity-level filter owns that case), and a Deny row grants nothing', () => {
261
+ expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: 'role-stranger' }] })).toBe(false);
262
+ expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: 'role-denied' }] })).toBe(false);
263
+ expect(ReadIsRowFiltered(entity, { UserRoles: null })).toBe(false);
264
+ });
265
+
266
+ it('matches role ids without regard to case or whitespace', () => {
267
+ expect(ReadIsRowFiltered(entity, { UserRoles: [{ RoleID: ` ${FILTERED_ROLE.toUpperCase()} ` }] })).toBe(true);
268
+ });
269
+
270
+ it('withholds the key and the row from a row-filtered subscriber, and keeps the rest of the event', () => {
271
+ const shaped = ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: FILTERED_ROLE }] }, entity);
272
+ expect(shaped.PrimaryKeyValues).toBeUndefined();
273
+ expect(shaped.RecordData).toBeUndefined();
274
+ expect(shaped.EntityName).toBe('Spaces');
275
+ expect(shaped.Action).toBe('save');
276
+ expect(shaped.SourceServerID).toBe('srv');
277
+ expect(shaped.OriginSessionID).toBe('sess');
278
+ });
279
+
280
+ it('delivers the event whole to an unfiltered reader, and when the entity or the user is unknown', () => {
281
+ expect(ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: OPEN_ROLE }] }, entity).PrimaryKeyValues).toBe('ID|abc');
282
+ expect(ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: OPEN_ROLE }] }, entity).RecordData).toBe('{"Name":"x"}');
283
+ expect(ShapeCacheInvalidationEvent(payload, { UserRoles: [{ RoleID: FILTERED_ROLE }] }, null).PrimaryKeyValues).toBe('ID|abc');
284
+ expect(ShapeCacheInvalidationEvent(payload, undefined, entity).PrimaryKeyValues).toBe('ID|abc');
285
+ });
286
+ });
@@ -128,7 +128,7 @@ describe('TailConversationEvents', () => {
128
128
 
129
129
  const runCall = mockRunView.mock.calls.find((c) => c[0].EntityName === 'MJ: AI Agent Runs');
130
130
  expect(runCall?.[0].ResultType).toBe('simple');
131
- expect([...(runCall?.[0].Fields ?? [])].sort()).toEqual(['ID', 'Result', 'Status']);
131
+ expect([...(runCall?.[0].Fields ?? [])].sort()).toEqual(['FinalPayload', 'ID', 'Result', 'Status']);
132
132
  });
133
133
 
134
134
  it('returns real dates when a plain row carries them as strings', async () => {
@@ -261,20 +261,20 @@ describe('TailConversationEvents', () => {
261
261
  it('marks a Running run in flight and withholds the payload', async () => {
262
262
  scriptRunView({
263
263
  detail: { ID: DETAIL_ID, Status: 'In-Progress' },
264
- run: { ID: RUN_ID, Status: 'Running', Result: '{"partial":true}' },
264
+ run: { ID: RUN_ID, Status: 'Running', FinalPayload: '{"partial":true}' },
265
265
  steps: [step(1)],
266
266
  });
267
267
  const out = await resolver.TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
268
268
 
269
269
  expect(out.IsInFlight).toBe(true);
270
- // A partial Result mid-run could be mistaken for the final answer.
270
+ // FinalPayload is filled during the run. A partial one could be mistaken for the final answer.
271
271
  expect(out.FinalPayload).toBeUndefined();
272
272
  });
273
273
 
274
274
  it('releases the payload once the run is terminal, so one call ends the wait', async () => {
275
275
  scriptRunView({
276
276
  detail: { ID: DETAIL_ID, Status: 'Complete' },
277
- run: { ID: RUN_ID, Status: 'Completed', Result: '{"answer":"done"}' },
277
+ run: { ID: RUN_ID, Status: 'Completed', FinalPayload: '{"answer":"done"}', Result: null },
278
278
  steps: [step(1)],
279
279
  });
280
280
  const out = await resolver.TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
@@ -302,7 +302,7 @@ describe('TailConversationEvents', () => {
302
302
  // Reporting it as terminal would tell a client to complete a message mid-workflow.
303
303
  scriptRunView({
304
304
  detail: { ID: DETAIL_ID, Status: 'In-Progress' },
305
- run: { ID: RUN_ID, Status: 'Paused', Result: '{"started":"workflow"}' },
305
+ run: { ID: RUN_ID, Status: 'Paused', FinalPayload: '{"started":"workflow"}' },
306
306
  steps: [step(1)],
307
307
  });
308
308
  const out = await resolver.TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
@@ -353,16 +353,19 @@ describe('TailConversationEvents', () => {
353
353
  });
354
354
 
355
355
  /**
356
- * REGRESSION, found in manual testing. `FinalPayload` read only `AIAgentRun.Result`, which is
357
- * agent-dependent — null on 16 of 40 completed runs in a real database, including every run of the
358
- * Loop-type agent used to reproduce #4222. A caller waiting on this field to decide a message was
359
- * finished would wait forever on those agents.
356
+ * `FinalPayload` comes from `AIAgentRun.FinalPayload`, then `Result` (some older rows have no
357
+ * `FinalPayload`), then the conversation detail's message.
358
+ *
359
+ * REGRESSION, found in manual testing: the run payload is agent-dependent — null on 16 of 40
360
+ * completed runs in a real database, including every run of the Loop-type agent used to reproduce
361
+ * #4222. Without the detail-message fallback, a caller waiting on this field to decide a message
362
+ * was finished would wait forever on those agents.
360
363
  */
361
364
  describe('TailConversationEvents FinalPayload sourcing', () => {
362
- it('falls back to the conversation detail message when the run has no Result', async () => {
365
+ it('falls back to the conversation detail message when the run has no payload', async () => {
363
366
  scriptRunView({
364
367
  detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'the answer text' },
365
- run: { ID: RUN_ID, Status: 'Completed', Result: null },
368
+ run: { ID: RUN_ID, Status: 'Completed', FinalPayload: null, Result: null },
366
369
  steps: [],
367
370
  });
368
371
 
@@ -372,10 +375,34 @@ describe('TailConversationEvents FinalPayload sourcing', () => {
372
375
  expect(result.FinalPayload).toBe('the answer text');
373
376
  });
374
377
 
375
- it('prefers the run Result when the agent does write one', async () => {
378
+ it('prefers the run FinalPayload over the detail message', async () => {
379
+ scriptRunView({
380
+ detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'detail message' },
381
+ run: { ID: RUN_ID, Status: 'Completed', FinalPayload: 'run payload', Result: null },
382
+ steps: [],
383
+ });
384
+
385
+ const result = await new ConversationRunEventsResolver().TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
386
+
387
+ expect(result.FinalPayload).toBe('run payload');
388
+ });
389
+
390
+ it('prefers FinalPayload over Result when a row has both', async () => {
391
+ scriptRunView({
392
+ detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'detail message' },
393
+ run: { ID: RUN_ID, Status: 'Completed', FinalPayload: 'run payload', Result: 'run result' },
394
+ steps: [],
395
+ });
396
+
397
+ const result = await new ConversationRunEventsResolver().TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
398
+
399
+ expect(result.FinalPayload).toBe('run payload');
400
+ });
401
+
402
+ it('falls back to Result on an older row that has no FinalPayload', async () => {
376
403
  scriptRunView({
377
404
  detail: { ID: DETAIL_ID, Status: 'Complete', Message: 'detail message' },
378
- run: { ID: RUN_ID, Status: 'Completed', Result: 'run result' },
405
+ run: { ID: RUN_ID, Status: 'Completed', FinalPayload: null, Result: 'run result' },
379
406
  steps: [],
380
407
  });
381
408
 
@@ -384,10 +411,25 @@ describe('TailConversationEvents FinalPayload sourcing', () => {
384
411
  expect(result.FinalPayload).toBe('run result');
385
412
  });
386
413
 
414
+ it('returns the partial FinalPayload a Cancelled run saved, with RunStatus telling the caller', async () => {
415
+ // Cancelling saves the run with no Result, but it keeps the FinalPayload saved during the run.
416
+ scriptRunView({
417
+ detail: { ID: DETAIL_ID, Status: 'Error', Message: 'detail message' },
418
+ run: { ID: RUN_ID, Status: 'Cancelled', FinalPayload: '{"partial":true}', Result: null },
419
+ steps: [],
420
+ });
421
+
422
+ const result = await new ConversationRunEventsResolver().TailConversationEvents(DETAIL_ID, ctxFor(USER), 0);
423
+
424
+ expect(result.IsInFlight).toBe(false);
425
+ expect(result.RunStatus).toBe('Cancelled');
426
+ expect(result.FinalPayload).toBe('{"partial":true}');
427
+ });
428
+
387
429
  it('still withholds the payload while the run is in flight', async () => {
388
430
  scriptRunView({
389
431
  detail: { ID: DETAIL_ID, Status: 'In-Progress', Message: 'partial text' },
390
- run: { ID: RUN_ID, Status: 'Running', Result: null },
432
+ run: { ID: RUN_ID, Status: 'Running', FinalPayload: '{"partial":true}', Result: null },
391
433
  steps: [],
392
434
  });
393
435