@memberjunction/server 6.2.0-edge.5 → 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 (401) hide show
  1. package/README.md +27 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/SessionJanitor.d.ts +3 -3
  4. package/dist/agentSessions/SessionJanitor.js +3 -3
  5. package/dist/agentSessions/SessionManager.d.ts +15 -5
  6. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionManager.js +34 -12
  8. package/dist/agentSessions/SessionManager.js.map +1 -1
  9. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  10. package/dist/auth/AuthProviderCatalogRouter.js +2 -0
  11. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  12. package/dist/auth/index.d.ts.map +1 -1
  13. package/dist/auth/index.js +51 -10
  14. package/dist/auth/index.js.map +1 -1
  15. package/dist/auth/initializeProviders.d.ts +10 -0
  16. package/dist/auth/initializeProviders.d.ts.map +1 -1
  17. package/dist/auth/initializeProviders.js +37 -1
  18. package/dist/auth/initializeProviders.js.map +1 -1
  19. package/dist/auth/localDevAuth.d.ts +79 -0
  20. package/dist/auth/localDevAuth.d.ts.map +1 -0
  21. package/dist/auth/localDevAuth.js +439 -0
  22. package/dist/auth/localDevAuth.js.map +1 -0
  23. package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
  24. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
  26. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  27. package/dist/auth/magicLink/types.d.ts +12 -0
  28. package/dist/auth/magicLink/types.d.ts.map +1 -1
  29. package/dist/auth/sessionUserClone.d.ts +1 -1
  30. package/dist/auth/sessionUserClone.d.ts.map +1 -1
  31. package/dist/auth/sessionUserClone.js +3 -2
  32. package/dist/auth/sessionUserClone.js.map +1 -1
  33. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
  34. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
  35. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
  36. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
  37. package/dist/broadcast/index.d.ts +6 -0
  38. package/dist/broadcast/index.d.ts.map +1 -0
  39. package/dist/broadcast/index.js +6 -0
  40. package/dist/broadcast/index.js.map +1 -0
  41. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
  42. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
  43. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
  44. package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
  45. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
  46. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
  47. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
  48. package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
  49. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
  50. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
  51. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
  52. package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
  53. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
  54. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
  55. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
  56. package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
  57. package/dist/cache.d.ts +3 -2
  58. package/dist/cache.d.ts.map +1 -1
  59. package/dist/cache.js.map +1 -1
  60. package/dist/config.d.ts +496 -0
  61. package/dist/config.d.ts.map +1 -1
  62. package/dist/config.js +61 -0
  63. package/dist/config.js.map +1 -1
  64. package/dist/context.d.ts +3 -1
  65. package/dist/context.d.ts.map +1 -1
  66. package/dist/context.js +29 -8
  67. package/dist/context.js.map +1 -1
  68. package/dist/coreRoutes.d.ts.map +1 -1
  69. package/dist/coreRoutes.js +14 -0
  70. package/dist/coreRoutes.js.map +1 -1
  71. package/dist/generated/graphql-schemas/__mj.d.ts +635 -14
  72. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  73. package/dist/generated/graphql-schemas/__mj.js +3366 -418
  74. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  75. package/dist/generic/CacheInvalidationResolver.d.ts +50 -30
  76. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  77. package/dist/generic/CacheInvalidationResolver.js +154 -79
  78. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  79. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  80. package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
  81. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  82. package/dist/generic/PubSubManager.d.ts +10 -0
  83. package/dist/generic/PubSubManager.d.ts.map +1 -1
  84. package/dist/generic/PubSubManager.js +44 -1
  85. package/dist/generic/PubSubManager.js.map +1 -1
  86. package/dist/generic/PushStatusResolver.d.ts +39 -6
  87. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  88. package/dist/generic/PushStatusResolver.js +93 -11
  89. package/dist/generic/PushStatusResolver.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +65 -25
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +201 -101
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +11 -3
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +79 -13
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
  99. package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
  100. package/dist/generic/actionOutputFieldSecurity.js +47 -0
  101. package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
  102. package/dist/generic/fieldSecurityTransport.d.ts +30 -0
  103. package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
  104. package/dist/generic/fieldSecurityTransport.js +30 -0
  105. package/dist/generic/fieldSecurityTransport.js.map +1 -0
  106. package/dist/index.d.ts +3 -0
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +122 -66
  109. package/dist/index.js.map +1 -1
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +6 -1
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
  114. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  115. package/dist/logging/variablesLoggingMiddleware.js +50 -1
  116. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  117. package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
  118. package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
  119. package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
  120. package/dist/realtimeSessions/ownedRealtimeSession.d.ts +8 -6
  121. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  122. package/dist/realtimeSessions/ownedRealtimeSession.js +15 -6
  123. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  124. package/dist/realtimeSessions/sessionAccess.d.ts +88 -11
  125. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  126. package/dist/realtimeSessions/sessionAccess.js +125 -22
  127. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  128. package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
  129. package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
  130. package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
  131. package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
  132. package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
  133. package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
  134. package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
  135. package/dist/realtimeSessions/verificationWorkflow.js +1 -1
  136. package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
  137. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  138. package/dist/realtimeWidget/WidgetRouter.js +19 -3
  139. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  140. package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
  141. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  142. package/dist/realtimeWidget/WidgetSessionService.js +201 -19
  143. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  144. package/dist/realtimeWidget/host-identity.d.ts +1 -1
  145. package/dist/realtimeWidget/host-identity.js +1 -1
  146. package/dist/realtimeWidget/widgetCore.d.ts +20 -1
  147. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  148. package/dist/realtimeWidget/widgetCore.js +17 -1
  149. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  150. package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
  151. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  152. package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
  153. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  154. package/dist/resolvers/ActionResolver.d.ts +19 -1
  155. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  156. package/dist/resolvers/ActionResolver.js +63 -12
  157. package/dist/resolvers/ActionResolver.js.map +1 -1
  158. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  159. package/dist/resolvers/AdhocQueryResolver.js +1 -0
  160. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  161. package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
  162. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  163. package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
  164. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  165. package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
  166. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  167. package/dist/resolvers/AgentSessionResolver.js +40 -17
  168. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  169. package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
  170. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  171. package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
  172. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  173. package/dist/resolvers/FetchEntityVectorsResolver.d.ts +28 -0
  174. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  175. package/dist/resolvers/FetchEntityVectorsResolver.js +117 -21
  176. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  177. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  178. package/dist/resolvers/FileCategoryResolver.js +1 -0
  179. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  180. package/dist/resolvers/FileResolver.d.ts +5 -0
  181. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  182. package/dist/resolvers/FileResolver.js +13 -4
  183. package/dist/resolvers/FileResolver.js.map +1 -1
  184. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.js +45 -7
  186. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  187. package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
  188. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  189. package/dist/resolvers/MergeRecordsResolver.js +31 -3
  190. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  191. package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
  192. package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
  193. package/dist/resolvers/PipelineProgressResolver.js +5 -3
  194. package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
  195. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  196. package/dist/resolvers/QueryResolver.js +20 -6
  197. package/dist/resolvers/QueryResolver.js.map +1 -1
  198. package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
  199. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  200. package/dist/resolvers/QuerySystemUserResolver.js +15 -6
  201. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  202. package/dist/resolvers/RSUResolver.d.ts.map +1 -1
  203. package/dist/resolvers/RSUResolver.js +12 -0
  204. package/dist/resolvers/RSUResolver.js.map +1 -1
  205. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +16 -2
  206. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  207. package/dist/resolvers/RealtimeClientSessionResolver.js +82 -28
  208. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  209. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
  211. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  212. package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
  213. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  214. package/dist/resolvers/RunAIAgentResolver.js +209 -64
  215. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  217. package/dist/resolvers/RunTemplateResolver.js +4 -4
  218. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  219. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  220. package/dist/resolvers/RunTestResolver.js +3 -1
  221. package/dist/resolvers/RunTestResolver.js.map +1 -1
  222. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  223. package/dist/resolvers/SyncDataResolver.js +4 -0
  224. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  225. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  226. package/dist/resolvers/TestQuerySQLResolver.js +1 -0
  227. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  228. package/dist/resolvers/TransactionGroupResolver.d.ts +17 -2
  229. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  230. package/dist/resolvers/TransactionGroupResolver.js +60 -15
  231. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  232. package/dist/resolvers/UserViewResolver.d.ts +1 -1
  233. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  234. package/dist/resolvers/UserViewResolver.js +2 -1
  235. package/dist/resolvers/UserViewResolver.js.map +1 -1
  236. package/dist/rest/EntityCRUDHandler.d.ts +6 -0
  237. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  238. package/dist/rest/EntityCRUDHandler.js +19 -5
  239. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  240. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  241. package/dist/rest/RESTEndpointHandler.js +8 -5
  242. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  243. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  244. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  245. package/dist/rest/RealtimeRecordingHandler.js +7 -7
  246. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  247. package/dist/rest/ViewOperationsHandler.d.ts +6 -0
  248. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  249. package/dist/rest/ViewOperationsHandler.js +22 -5
  250. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  251. package/dist/serverRoleConfigUnits.d.ts +29 -0
  252. package/dist/serverRoleConfigUnits.d.ts.map +1 -0
  253. package/dist/serverRoleConfigUnits.js +33 -0
  254. package/dist/serverRoleConfigUnits.js.map +1 -0
  255. package/dist/services/TaskGraphContinuationDeliverer.d.ts +5 -0
  256. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  257. package/dist/services/TaskGraphContinuationDeliverer.js +42 -15
  258. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  259. package/dist/types.d.ts +23 -1
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js.map +1 -1
  262. package/package.json +98 -98
  263. package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
  264. package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
  265. package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
  267. package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
  268. package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
  269. package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
  270. package/src/__tests__/FetchEntityVectorsResolver.test.ts +206 -3
  271. package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
  272. package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
  273. package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
  274. package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
  275. package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
  276. package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
  277. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
  278. package/src/__tests__/ResolverBase.clientOldValues.test.ts +37 -4
  279. package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
  280. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
  281. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
  282. package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
  283. package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
  284. package/src/__tests__/RunAIAgentResolver.branch.test.ts +68 -13
  285. package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
  286. package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
  287. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +17 -27
  288. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +8 -2
  289. package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
  290. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
  291. package/src/__tests__/RunTemplateResolver.test.ts +35 -2
  292. package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
  293. package/src/__tests__/SessionManager.test.ts +95 -0
  294. package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +114 -3
  295. package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
  296. package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
  297. package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
  298. package/src/__tests__/WidgetSessionService.test.ts +583 -4
  299. package/src/__tests__/apiRootBoot.test.ts +1 -1
  300. package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
  301. package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
  302. package/src/__tests__/cacheInvalidationRecordData.test.ts +45 -0
  303. package/src/__tests__/fieldSecurityFixtures.ts +91 -0
  304. package/src/__tests__/globalCors.test.ts +3 -3
  305. package/src/__tests__/localDevAuth.test.ts +196 -0
  306. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
  307. package/src/__tests__/pushStatusResolver.filter.test.ts +57 -1
  308. package/src/__tests__/realtimeRecordingHandler.test.ts +19 -2
  309. package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
  310. package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
  311. package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
  312. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
  313. package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
  314. package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
  315. package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
  316. package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
  317. package/src/__tests__/resolverBase.fls.test.ts +146 -97
  318. package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
  319. package/src/__tests__/runParamsTimeZone.test.ts +179 -0
  320. package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
  321. package/src/__tests__/runViewProperties.test.ts +114 -0
  322. package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
  323. package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
  324. package/src/__tests__/sessionUserClone.test.ts +5 -1
  325. package/src/__tests__/startupSweepOrder.test.ts +40 -0
  326. package/src/__tests__/unifiedAuth.test.ts +28 -0
  327. package/src/__tests__/widgetGuestElevation.test.ts +2 -0
  328. package/src/agentSessions/SessionJanitor.ts +3 -3
  329. package/src/agentSessions/SessionManager.ts +35 -12
  330. package/src/auth/AuthProviderCatalogRouter.ts +3 -0
  331. package/src/auth/index.ts +56 -12
  332. package/src/auth/initializeProviders.ts +43 -1
  333. package/src/auth/localDevAuth.ts +528 -0
  334. package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
  335. package/src/auth/magicLink/types.ts +10 -0
  336. package/src/auth/sessionUserClone.ts +3 -2
  337. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
  338. package/src/broadcast/index.ts +5 -0
  339. package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
  340. package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
  341. package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
  342. package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
  343. package/src/cache.ts +2 -1
  344. package/src/config.ts +69 -0
  345. package/src/context.ts +32 -8
  346. package/src/coreRoutes.ts +15 -0
  347. package/src/generated/graphql-schemas/__mj.ts +2239 -102
  348. package/src/generic/CacheInvalidationResolver.ts +135 -78
  349. package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
  350. package/src/generic/PubSubManager.ts +54 -1
  351. package/src/generic/PushStatusResolver.ts +108 -11
  352. package/src/generic/ResolverBase.ts +237 -107
  353. package/src/generic/RunViewResolver.ts +80 -15
  354. package/src/generic/actionOutputFieldSecurity.ts +75 -0
  355. package/src/generic/fieldSecurityTransport.ts +62 -0
  356. package/src/index.ts +127 -68
  357. package/src/integration/CustomColumnPromoter.ts +6 -1
  358. package/src/logging/variablesLoggingMiddleware.ts +59 -3
  359. package/src/realtimeSessions/README.md +7 -3
  360. package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
  361. package/src/realtimeSessions/ownedRealtimeSession.ts +20 -6
  362. package/src/realtimeSessions/sessionAccess.ts +181 -25
  363. package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
  364. package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
  365. package/src/realtimeSessions/verificationWorkflow.ts +3 -1
  366. package/src/realtimeWidget/WidgetRouter.ts +22 -4
  367. package/src/realtimeWidget/WidgetSessionService.ts +252 -17
  368. package/src/realtimeWidget/host-identity.ts +1 -1
  369. package/src/realtimeWidget/widgetCore.ts +30 -1
  370. package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
  371. package/src/resolvers/ActionResolver.ts +78 -15
  372. package/src/resolvers/AdhocQueryResolver.ts +1 -0
  373. package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
  374. package/src/resolvers/AgentSessionResolver.ts +46 -18
  375. package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
  376. package/src/resolvers/FetchEntityVectorsResolver.ts +132 -21
  377. package/src/resolvers/FileCategoryResolver.ts +1 -0
  378. package/src/resolvers/FileResolver.ts +14 -4
  379. package/src/resolvers/IntegrationDiscoveryResolver.ts +49 -7
  380. package/src/resolvers/MergeRecordsResolver.ts +23 -2
  381. package/src/resolvers/PipelineProgressResolver.ts +12 -3
  382. package/src/resolvers/QueryResolver.ts +20 -6
  383. package/src/resolvers/QuerySystemUserResolver.ts +15 -6
  384. package/src/resolvers/RSUResolver.ts +12 -0
  385. package/src/resolvers/RealtimeClientSessionResolver.ts +104 -33
  386. package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
  387. package/src/resolvers/RunAIAgentResolver.ts +258 -82
  388. package/src/resolvers/RunTemplateResolver.ts +6 -4
  389. package/src/resolvers/RunTestResolver.ts +3 -1
  390. package/src/resolvers/SyncDataResolver.ts +4 -0
  391. package/src/resolvers/TestQuerySQLResolver.ts +1 -0
  392. package/src/resolvers/TransactionGroupResolver.ts +66 -15
  393. package/src/resolvers/UserViewResolver.ts +2 -1
  394. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
  395. package/src/rest/EntityCRUDHandler.ts +22 -5
  396. package/src/rest/RESTEndpointHandler.ts +8 -5
  397. package/src/rest/RealtimeRecordingHandler.ts +13 -10
  398. package/src/rest/ViewOperationsHandler.ts +24 -6
  399. package/src/serverRoleConfigUnits.ts +48 -0
  400. package/src/services/TaskGraphContinuationDeliverer.ts +52 -20
  401. package/src/types.ts +23 -1
@@ -14,8 +14,10 @@ import type {
14
14
  AgentExecutionStreamingCallback,
15
15
  ExecuteAgentResult,
16
16
  } from '@memberjunction/ai-core-plus';
17
+ import type { EntityInfo } from '@memberjunction/core';
17
18
  import type { UserPayload } from '../types.js';
18
- import { PublishStatusUpdate } from '../generic/PushStatusResolver.js';
19
+ import { PublishStatusUpdate, StatusUpdateOwnerID } from '../generic/PushStatusResolver.js';
20
+ import { ProjectEntityObjectForCaller } from '../generic/fieldSecurityTransport.js';
19
21
 
20
22
  const SIGNIFICANT_STEPS = ['prompt_execution', 'action_execution', 'subagent_execution', 'decision_processing'];
21
23
 
@@ -25,7 +27,8 @@ interface StatusRun {
25
27
  ConversationDetailID?: string;
26
28
  ErrorMessage?: string;
27
29
  Steps?: Array<{ StepName?: string }>;
28
- GetAll?: () => unknown;
30
+ EntityInfo?: EntityInfo | null;
31
+ GetAll?: () => Record<string, unknown>;
29
32
  }
30
33
 
31
34
  export class AgentRunStatusPublisher {
@@ -34,11 +37,15 @@ export class AgentRunStatusPublisher {
34
37
  /**
35
38
  * @param sessionId Session stamped on the message body. Defaults to the payload's session.
36
39
  * The push itself is always addressed to `userPayload.sessionId`, matching the resolver.
40
+ * @param conversationDetailId The run's reply row. When set, every message carries it as
41
+ * `conversationDetailId`, not only the completion, so a client that reconnects mid-run can
42
+ * find its row. When omitted, messages are as before.
37
43
  */
38
44
  constructor(
39
45
  private readonly pubSub: PubSubEngine,
40
46
  private readonly userPayload: UserPayload,
41
47
  private readonly sessionId: string = userPayload.sessionId,
48
+ private readonly conversationDetailId?: string,
42
49
  ) {}
43
50
 
44
51
  /** Latest run observed from a progress event, including steps that are not published. */
@@ -66,7 +73,8 @@ export class AgentRunStatusPublisher {
66
73
  sessionId: this.sessionId,
67
74
  agentRunId: agentRun.ID,
68
75
  type: 'progress',
69
- agentRun: agentRun.GetAll ? agentRun.GetAll() : agentRun,
76
+ ...this.replyRow(),
77
+ agentRun: this.runValues(agentRun),
70
78
  progress: {
71
79
  currentStep: progress.step,
72
80
  percentage: progress.percentage,
@@ -93,7 +101,8 @@ export class AgentRunStatusPublisher {
93
101
  sessionId: this.sessionId,
94
102
  agentRunId: agentRun.ID,
95
103
  type: 'streaming',
96
- agentRun: agentRun.GetAll ? agentRun.GetAll() : agentRun,
104
+ ...this.replyRow(),
105
+ agentRun: this.runValues(agentRun),
97
106
  streaming: {
98
107
  content: chunk.content,
99
108
  isPartial: !chunk.isComplete,
@@ -122,6 +131,7 @@ export class AgentRunStatusPublisher {
122
131
  sessionId: this.sessionId,
123
132
  agentRunId: agentRun.ID,
124
133
  type: 'partial_result',
134
+ ...this.replyRow(),
125
135
  partialResult: {
126
136
  currentStep: lastStep,
127
137
  partialOutput: result.payload || undefined,
@@ -134,7 +144,7 @@ export class AgentRunStatusPublisher {
134
144
  agentRunId: agentRun?.ID || 'unknown',
135
145
  type: 'complete',
136
146
  timestamp: new Date(),
137
- conversationDetailId: conversationDetailId || agentRun?.ConversationDetailID,
147
+ conversationDetailId: conversationDetailId || agentRun?.ConversationDetailID || this.conversationDetailId,
138
148
  success: result.success,
139
149
  errorMessage: agentRun?.ErrorMessage || undefined,
140
150
  result: resultJson || undefined,
@@ -142,8 +152,9 @@ export class AgentRunStatusPublisher {
142
152
  }
143
153
 
144
154
  /**
145
- * Completion for a failure that has no run. The fire-and-forget path uses this when
146
- * `executeAIAgent` rejects. A host uses it the same way: when the turn throws
155
+ * Completion for a failure that has no run result. `executeAIAgent` sends it when the run
156
+ * throws (the agent could not start, the run failed, or its reply could not be saved), so a
157
+ * fire-and-forget client learns the reason. A host uses it the same way: when the turn throws
147
158
  * instead of returning a result, call `PublishFailure` with the reply row's id
148
159
  * and the error. That completion is what tells the chat the reply finished.
149
160
  */
@@ -160,10 +171,27 @@ export class AgentRunStatusPublisher {
160
171
  });
161
172
  }
162
173
 
174
+ /** `{ conversationDetailId }` when this run has a reply row, otherwise nothing, so the message keeps its old shape. */
175
+ private replyRow(): { conversationDetailId?: string } {
176
+ return this.conversationDetailId ? { conversationDetailId: this.conversationDetailId } : {};
177
+ }
178
+
179
+ /**
180
+ * The run's field values as the recipient may read them. The run object comes from the agent
181
+ * engine, which is not bound to this session's user, so it is projected here.
182
+ */
183
+ private runValues(agentRun: StatusRun): unknown {
184
+ if (!agentRun.GetAll) {
185
+ return agentRun;
186
+ }
187
+ const getAll = agentRun.GetAll;
188
+ return ProjectEntityObjectForCaller({ EntityInfo: agentRun.EntityInfo, GetAll: () => getAll.call(agentRun) }, this.userPayload?.userRecord);
189
+ }
190
+
163
191
  private publish(type: 'ExecutionProgress' | 'StreamingContent', data: unknown): void {
164
192
  PublishStatusUpdate(this.pubSub, {
165
193
  sessionId: this.userPayload.sessionId,
166
- ownerUserId: this.userPayload?.userRecord?.ID ?? '',
194
+ ownerUserId: StatusUpdateOwnerID(this.userPayload),
167
195
  message: JSON.stringify({
168
196
  resolver: 'RunAIAgentResolver',
169
197
  type,
@@ -13,12 +13,30 @@
13
13
  */
14
14
  import { Resolver, Mutation, Arg, Ctx, ObjectType, Field } from 'type-graphql';
15
15
  import { AppContext } from '../types.js';
16
- import { UserInfo, IMetadataProvider, LogError } from '@memberjunction/core';
17
- import { UUIDsEqual } from '@memberjunction/global';
18
- import { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
16
+ import { UserInfo, IMetadataProvider } from '@memberjunction/core';
19
17
  import { ResolverBase } from '../generic/ResolverBase.js';
20
18
  import { GetReadWriteProvider } from '../util.js';
21
- import { SessionManager, SessionAuthorizationError } from '../agentSessions/index.js';
19
+ import { SessionManager, SessionAuthorizationError, type SessionCloseReason } from '../agentSessions/index.js';
20
+ import {
21
+ LoadOwnedRealtimeSession,
22
+ RealtimeSessionNotFoundError,
23
+ RealtimeSessionOwnershipError,
24
+ } from '../realtimeSessions/ownedRealtimeSession.js';
25
+
26
+ /** The `CloseReason` values a client may stamp on its own session; `Janitor` and `Shutdown` are the server's. */
27
+ const CLIENT_CLOSE_REASONS: readonly SessionCloseReason[] = ['Explicit', 'Error'];
28
+
29
+ /** Reads the mutation's optional `closeReason`: absent means `Explicit`; a server-only or unknown value is refused. */
30
+ function ParseClientCloseReason(closeReason: string | null | undefined): SessionCloseReason {
31
+ if (closeReason == null || closeReason === '') {
32
+ return 'Explicit';
33
+ }
34
+ const match = CLIENT_CLOSE_REASONS.find((r) => r === closeReason);
35
+ if (!match) {
36
+ throw new Error(`Unsupported close reason '${closeReason}': a client may close its session as ${CLIENT_CLOSE_REASONS.join(' or ')}`);
37
+ }
38
+ return match;
39
+ }
22
40
 
23
41
  /** Result of {@link AgentSessionResolver.StartAgentSession}. */
24
42
  @ObjectType()
@@ -77,15 +95,22 @@ export class AgentSessionResolver extends ResolverBase {
77
95
  /**
78
96
  * Close a session. Rejects unless the caller owns it. Returns `true` on a successful (or
79
97
  * idempotent) close, `false` if the session can't be loaded.
98
+ *
99
+ * `closeReason` is what the row records as its `CloseReason`: `Explicit` (the default, and what
100
+ * every client sent before the argument existed) when the user or host ended the call, `Error`
101
+ * when the client ended it because the start or the call failed (MJ#6013). The other reasons are
102
+ * the server's own (janitor, shutdown) and are refused from a client.
80
103
  */
81
104
  @Mutation(() => Boolean)
82
105
  async CloseAgentSession(
83
106
  @Arg('agentSessionId') agentSessionId: string,
84
107
  @Ctx() { userPayload, providers }: AppContext,
108
+ @Arg('closeReason', () => String, { nullable: true }) closeReason?: string | null,
85
109
  ): Promise<boolean> {
110
+ const reason = ParseClientCloseReason(closeReason);
86
111
  const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
87
- await this.assertOwnership(agentSessionId, contextUser, provider);
88
- return this.sessionManager.CloseSession(agentSessionId, contextUser, provider);
112
+ await this.loadOwnedSession(agentSessionId, contextUser, provider);
113
+ return this.sessionManager.CloseSession(agentSessionId, contextUser, provider, reason);
89
114
  }
90
115
 
91
116
  /**
@@ -98,7 +123,7 @@ export class AgentSessionResolver extends ResolverBase {
98
123
  @Ctx() { userPayload, providers }: AppContext,
99
124
  ): Promise<boolean> {
100
125
  const { contextUser, provider } = this.requireUserAndProvider(userPayload, providers);
101
- await this.assertOwnership(agentSessionId, contextUser, provider);
126
+ await this.loadOwnedSession(agentSessionId, contextUser, provider);
102
127
  return this.sessionManager.Heartbeat(agentSessionId, contextUser, provider);
103
128
  }
104
129
 
@@ -117,22 +142,25 @@ export class AgentSessionResolver extends ResolverBase {
117
142
  }
118
143
 
119
144
  /**
120
- * Inbound ownership gate (the one genuinely new session-authz primitive): the session's `UserID`
121
- * must equal the caller's. Throws on mismatch or when the session can't be loaded.
145
+ * Inbound ownership gate: delegates to {@link LoadOwnedRealtimeSession} (which enforces both
146
+ * `UserID === contextUser.ID` and `AuthorizeSessionAccess` scope/visitor binding) and maps its
147
+ * typed errors to this resolver's message format.
122
148
  */
123
- private async assertOwnership(
149
+ private async loadOwnedSession(
124
150
  agentSessionId: string,
125
151
  contextUser: UserInfo,
126
152
  provider: IMetadataProvider,
127
153
  ): Promise<void> {
128
- const session = await provider.GetEntityObject<MJAIAgentSessionEntity>('MJ: AI Agent Sessions', contextUser);
129
- const loaded = await session.Load(agentSessionId);
130
- if (!loaded) {
131
- throw new Error(`Agent session ${agentSessionId} not found`);
132
- }
133
- if (!UUIDsEqual(session.UserID, contextUser.ID)) {
134
- LogError(`AgentSessionResolver: ownership check failed — user ${contextUser.ID} attempted to operate on session ${agentSessionId} owned by ${session.UserID}`);
135
- throw new Error('Not authorized: you do not own this agent session');
154
+ try {
155
+ await LoadOwnedRealtimeSession(agentSessionId, contextUser, provider);
156
+ } catch (err) {
157
+ if (err instanceof RealtimeSessionNotFoundError) {
158
+ throw new Error(`Agent session ${agentSessionId} not found`);
159
+ }
160
+ if (err instanceof RealtimeSessionOwnershipError) {
161
+ throw new Error('Not authorized: you do not own this agent session');
162
+ }
163
+ throw err;
136
164
  }
137
165
  }
138
166
  }
@@ -3,12 +3,14 @@ import { AppContext } from '../types.js';
3
3
  import { RunViewByIDInput } from '../generic/RunViewResolver.js';
4
4
  import { Message } from '@memberjunction/communication-types';
5
5
  import { EntityCommunicationsEngine } from '@memberjunction/entity-communications-server';
6
- import { RunViewParams } from '@memberjunction/core';
6
+ import { DatabaseProviderBase, IMetadataProvider, RunViewParams, UserInfo } from '@memberjunction/core';
7
+ import { UUIDsEqual } from '@memberjunction/global';
7
8
  import { GraphQLJSONObject } from 'graphql-type-json';
8
9
  import { TemplateEngineServer } from '@memberjunction/templates';
9
10
  import { EntityCommunicationParams } from '@memberjunction/entity-communications-base';
10
11
  import { z } from 'zod';
11
12
  import { ResolverBase } from '../generic/ResolverBase.js';
13
+ import { GetReadOnlyProvider } from '../util.js';
12
14
 
13
15
  @InputType()
14
16
  export class CommunicationProviderMessageType {
@@ -168,6 +170,11 @@ export class RunEntityCommunicationResultType {
168
170
 
169
171
  @Resolver(RunEntityCommunicationResultType)
170
172
  export class ReportResolver extends ResolverBase {
173
+ /**
174
+ * Runs a message against a view of records, as the caller. The recipient and related-entity reads
175
+ * run as the authenticated user, so a preview returns and a send merges only rows and fields that
176
+ * user may read. The view's client-supplied clauses get the same screen as a RunView request.
177
+ */
171
178
  @Query(() => RunEntityCommunicationResultType)
172
179
  async RunEntityCommunicationByViewID(
173
180
  @Arg('entityID', () => String) entityID: string,
@@ -177,25 +184,29 @@ export class ReportResolver extends ResolverBase {
177
184
  @Arg('message', () => CommunicationMessageInput) message: CommunicationMessageInput,
178
185
  @Arg('previewOnly', () => Boolean) previewOnly: boolean,
179
186
  @Arg('includeProcessedMessages', () => Boolean) includeProcessedMessages: boolean,
180
- @Ctx() { userPayload }: AppContext
187
+ @Ctx() { userPayload, providers }: AppContext
181
188
  ): Promise<RunEntityCommunicationResultType> {
182
189
  // Check API key scope authorization for communication send
183
190
  await this.CheckAPIKeyScopeAuthorization('communication:send', entityID, userPayload);
184
191
 
185
192
  try {
186
- await EntityCommunicationsEngine.Instance.Config(false, userPayload.userRecord);
187
- const newMessage = new Message(message as unknown as Message);
188
- await TemplateEngineServer.Instance.Config(false, userPayload.userRecord);
189
- // for the templates, replace the values from the input with the objects from the Template Engine we have here
190
- if (newMessage.BodyTemplate) {
191
- newMessage.BodyTemplate = TemplateEngineServer.Instance.FindTemplate(newMessage.BodyTemplate.Name);
192
- }
193
- if (newMessage.HTMLBodyTemplate) {
194
- newMessage.HTMLBodyTemplate = TemplateEngineServer.Instance.FindTemplate(newMessage.HTMLBodyTemplate.Name);
195
- }
196
- if (newMessage.SubjectTemplate) {
197
- newMessage.SubjectTemplate = TemplateEngineServer.Instance.FindTemplate(newMessage.SubjectTemplate.Name);
198
- }
193
+ const contextUser = this.GetUserFromPayload(userPayload);
194
+ if (!contextUser) throw new Error('Unable to determine the acting user for this communication');
195
+ const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
196
+ this.screenClientViewClauses(
197
+ {
198
+ extraFilter: runViewByIDInput.ExtraFilter,
199
+ orderBy: runViewByIDInput.OrderBy,
200
+ userSearchString: runViewByIDInput.UserSearchString,
201
+ overrideExcludeFilter: runViewByIDInput.OverrideExcludeFilter,
202
+ },
203
+ provider as unknown as IMetadataProvider,
204
+ contextUser,
205
+ );
206
+ await this.assertViewBelongsToEntity(runViewByIDInput.ViewID, entityID, provider, contextUser);
207
+
208
+ await EntityCommunicationsEngine.Instance.Config(false, contextUser);
209
+ const newMessage = await this.resolveMessageTemplates(message, contextUser);
199
210
  const params: EntityCommunicationParams = {
200
211
  EntityID: entityID,
201
212
  RunViewParams: <RunViewParams>runViewByIDInput,
@@ -204,6 +215,8 @@ export class ReportResolver extends ResolverBase {
204
215
  Message: newMessage,
205
216
  PreviewOnly: previewOnly,
206
217
  IncludeProcessedMessages: includeProcessedMessages,
218
+ ContextUser: contextUser,
219
+ Provider: provider,
207
220
  };
208
221
  const result = await EntityCommunicationsEngine.Instance.RunEntityCommunication(params);
209
222
  return {
@@ -222,4 +235,32 @@ export class ReportResolver extends ResolverBase {
222
235
  };
223
236
  }
224
237
  }
238
+
239
+ /**
240
+ * Refuses a view that is not a view of `entityID`, with the same message as for a view that does
241
+ * not exist or that the caller cannot load, so the refusal does not reveal which views exist.
242
+ */
243
+ private async assertViewBelongsToEntity(viewID: string | undefined, entityID: string, provider: DatabaseProviderBase, contextUser: UserInfo): Promise<void> {
244
+ if (!viewID) return;
245
+ const view = await this.resolveUserViewByID(viewID, provider, contextUser);
246
+ if (!view?.ID || !UUIDsEqual(view.ID, viewID) || !UUIDsEqual(view.EntityID, entityID)) {
247
+ throw new Error('The requested view does not exist or you do not have access to it');
248
+ }
249
+ }
250
+
251
+ /** Builds the message, replacing each template the client named with the server's copy of it. */
252
+ private async resolveMessageTemplates(message: CommunicationMessageInput, contextUser: UserInfo): Promise<Message> {
253
+ const newMessage = new Message(message as unknown as Message);
254
+ await TemplateEngineServer.Instance.Config(false, contextUser);
255
+ if (newMessage.BodyTemplate) {
256
+ newMessage.BodyTemplate = TemplateEngineServer.Instance.FindTemplate(newMessage.BodyTemplate.Name);
257
+ }
258
+ if (newMessage.HTMLBodyTemplate) {
259
+ newMessage.HTMLBodyTemplate = TemplateEngineServer.Instance.FindTemplate(newMessage.HTMLBodyTemplate.Name);
260
+ }
261
+ if (newMessage.SubjectTemplate) {
262
+ newMessage.SubjectTemplate = TemplateEngineServer.Instance.FindTemplate(newMessage.SubjectTemplate.Name);
263
+ }
264
+ return newMessage;
265
+ }
225
266
  }
@@ -50,6 +50,12 @@ type LabelledVector = { row: StoredVectorRow; labels: Record<string, string> };
50
50
  /** Most Entity Record Document IDs named in one `ID IN (...)` filter when fetching VectorJSON. */
51
51
  const VECTOR_FETCH_CHUNK_SIZE = 1000;
52
52
 
53
+ /** Most record keys named in one view when checking which matched records the caller may read. */
54
+ const READABLE_KEY_CHECK_CHUNK_SIZE = 1000;
55
+
56
+ /** A match returned by an external vector index's `QueryIndex`. */
57
+ type IndexMatch = { id: string; values?: number[]; metadata?: Record<string, unknown>; score?: number };
58
+
53
59
  /* ───── Resolver ───── */
54
60
 
55
61
  @Resolver()
@@ -70,15 +76,18 @@ export class FetchEntityVectorsResolver extends ResolverBase {
70
76
  }
71
77
 
72
78
  const limit = maxRecords ?? 1000;
79
+ // Every read runs on this request's provider.
80
+ const md = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true }) ?? Metadata.Provider;
81
+ const rv = RunView.FromMetadataProvider(md);
73
82
 
74
83
  // Step 1: Load the EntityDocument
75
- const entityDoc = await this.loadEntityDocument(entityDocumentID, currentUser);
84
+ const entityDoc = await this.loadEntityDocument(entityDocumentID, currentUser, rv);
76
85
  if (!entityDoc) {
77
86
  return this.errorResult(`EntityDocument not found: ${entityDocumentID}`, startTime);
78
87
  }
79
88
 
80
89
  // Step 2: Resolve the VectorIndex
81
- const vectorIndex = await this.resolveVectorIndex(entityDoc, currentUser);
90
+ const vectorIndex = await this.resolveVectorIndex(entityDoc, currentUser, rv);
82
91
  if (!vectorIndex) {
83
92
  return this.errorResult(
84
93
  `Could not resolve VectorIndex for EntityDocument "${entityDoc.Name}"`,
@@ -87,7 +96,7 @@ export class FetchEntityVectorsResolver extends ResolverBase {
87
96
  }
88
97
 
89
98
  // Step 3: Create VectorDB provider instance
90
- const vectorDBInstance = await this.createVectorDBInstance(vectorIndex, currentUser);
99
+ const vectorDBInstance = await this.createVectorDBInstance(vectorIndex, currentUser, rv);
91
100
  if (!vectorDBInstance) {
92
101
  return this.errorResult(
93
102
  `Could not create VectorDB provider for index "${vectorIndex.Name}"`,
@@ -100,12 +109,21 @@ export class FetchEntityVectorsResolver extends ResolverBase {
100
109
  // document ID rather than the index name, and return matches without vector values.
101
110
  // Read the stored Entity Record Document rows instead.
102
111
  if (vectorDBInstance.QueryKeyIsEntityDocumentID) {
103
- const md = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true }) ?? Metadata.Provider;
104
- const results = await this.fetchStoredVectors(entityDoc, limit, filter, currentUser, md);
112
+ const results = await this.fetchStoredVectors(entityDoc, limit, filter, currentUser, md, rv);
105
113
  LogStatus(`FetchEntityVectors: Loaded ${results.length} stored vectors for entity "${entityDoc.Entity}" in ${Date.now() - startTime}ms`);
106
114
  return { Success: true, Results: results, TotalCount: results.length, ElapsedMs: Date.now() - startTime };
107
115
  }
108
116
 
117
+ // The external index was built under whoever ran the sync and is not filtered for this
118
+ // caller, so check here that they may read the source entity at all.
119
+ const entityInfo = this.readableEntity(entityDoc.Entity, currentUser, md);
120
+ if (!entityInfo) {
121
+ return this.errorResult(
122
+ `Entity "${entityDoc.Entity}" does not exist or you do not have permission to read it`,
123
+ startTime
124
+ );
125
+ }
126
+
109
127
  // Step 4: Query with zero vector + Entity metadata filter.
110
128
  // Pinecone's list API doesn't support metadata filtering, but query does.
111
129
  // A zero vector returns results in arbitrary order (similarity is meaningless),
@@ -136,12 +154,15 @@ export class FetchEntityVectorsResolver extends ResolverBase {
136
154
  );
137
155
  }
138
156
 
139
- // Step 5: Convert query matches to result format
140
- const matches = (queryResponse.data as { matches?: Array<{ id: string; values?: number[]; metadata?: Record<string, unknown>; score?: number }> }).matches ?? [];
157
+ // Step 5: Keep only matches for records this caller may read (the index is not filtered
158
+ // by row-level security), then convert them without the fields this caller may not read
159
+ const allMatches = (queryResponse.data as { matches?: IndexMatch[] }).matches ?? [];
160
+ const matches = await this.readableMatches(entityInfo, allMatches, currentUser, rv);
161
+ const denied = entityInfo.GetDeniedReadFields(currentUser);
141
162
  const results: EntityVectorItem[] = matches.map(match => ({
142
163
  ID: match.id,
143
164
  Values: match.values ?? [],
144
- Metadata: JSON.stringify(match.metadata ?? {}),
165
+ Metadata: JSON.stringify(this.withoutDeniedFields(match.metadata ?? {}, denied)),
145
166
  }));
146
167
  LogStatus(`FetchEntityVectors: Queried ${results.length} vectors for entity "${entityName}" in ${Date.now() - startTime}ms`);
147
168
 
@@ -158,12 +179,100 @@ export class FetchEntityVectorsResolver extends ResolverBase {
158
179
  }
159
180
  }
160
181
 
182
+ /**
183
+ * The entity's metadata when `contextUser` may read it, else null — whether it is missing or
184
+ * unreadable is deliberately not distinguished.
185
+ */
186
+ private readableEntity(entityName: string, contextUser: UserInfo, md: IMetadataProvider): EntityInfo | null {
187
+ const entityInfo = md.EntityByName(entityName);
188
+ if (!entityInfo) return null;
189
+ return entityInfo.GetUserPermisions(contextUser)?.CanRead ? entityInfo : null;
190
+ }
191
+
192
+ /**
193
+ * The matches whose record `contextUser` may read, in their original order. A match is kept only
194
+ * when its `RecordID` metadata names a key of the entity and a view run as `contextUser` returns
195
+ * that record; a match without a usable record ID cannot be checked, so it is dropped.
196
+ */
197
+ private async readableMatches(entityInfo: EntityInfo, matches: IndexMatch[], contextUser: UserInfo, rv: RunView): Promise<IndexMatch[]> {
198
+ const keyed = matches
199
+ .map(match => ({ match, key: this.matchRecordKey(entityInfo, match) }))
200
+ .filter((m): m is { match: IndexMatch; key: CompositeKey } => m.key !== null);
201
+ if (keyed.length === 0) return [];
202
+ const readable = await this.loadReadableKeys(entityInfo, keyed.map(k => k.key), contextUser, rv);
203
+ return keyed.filter(k => readable.has(this.canonicalKey(k.key))).map(k => k.match);
204
+ }
205
+
206
+ /** The record key a match's `RecordID` metadata names, or null when it names no key of the entity. */
207
+ private matchRecordKey(entityInfo: EntityInfo, match: IndexMatch): CompositeKey | null {
208
+ const recordID = match.metadata?.RecordID;
209
+ if (typeof recordID !== 'string' || recordID.length === 0) return null;
210
+ try {
211
+ return CompositeKey.FromRecordID(entityInfo, recordID);
212
+ } catch {
213
+ return null; // field names that are not the entity's primary key: never put into SQL
214
+ }
215
+ }
216
+
217
+ /**
218
+ * The canonical forms of the keys among `keys` whose record a view run as `contextUser` returns.
219
+ * The keys are checked in lists of at most {@link READABLE_KEY_CHECK_CHUNK_SIZE}, all sent in one
220
+ * `RunViews` batch.
221
+ */
222
+ private async loadReadableKeys(entityInfo: EntityInfo, keys: CompositeKey[], contextUser: UserInfo, rv: RunView): Promise<Set<string>> {
223
+ const views: RunViewParams[] = [];
224
+ for (let i = 0; i < keys.length; i += READABLE_KEY_CHECK_CHUNK_SIZE) {
225
+ views.push({
226
+ EntityName: entityInfo.Name,
227
+ ExtraFilter: this.keyFilter(entityInfo, keys.slice(i, i + READABLE_KEY_CHECK_CHUNK_SIZE)),
228
+ Fields: entityInfo.PrimaryKeys.map(pk => pk.Name),
229
+ IgnoreMaxRows: true,
230
+ BypassCache: true, // one-off key lists: a cache slot per call would only hold memory
231
+ ResultType: 'simple',
232
+ });
233
+ }
234
+ const readable = new Set<string>();
235
+ const results = await rv.RunViews<Record<string, unknown>>(views, contextUser);
236
+ for (const result of results) {
237
+ if (!result.Success) {
238
+ throw new Error(`Failed to check access to "${entityInfo.Name}" records: ${result.ErrorMessage}`);
239
+ }
240
+ for (const row of result.Results) {
241
+ readable.add(this.canonicalKey(CompositeKey.FromEntityRecord(entityInfo, row)));
242
+ }
243
+ }
244
+ return readable;
245
+ }
246
+
247
+ /** A filter matching exactly `keys`: `PK IN (...)` for a single-column key, one clause per key otherwise. */
248
+ private keyFilter(entityInfo: EntityInfo, keys: CompositeKey[]): string {
249
+ if (entityInfo.PrimaryKeys.length === 1) {
250
+ const values = keys.map(k => `'${EscapeSQLString(String(k.KeyValuePairs[0].Value))}'`).join(',');
251
+ return `${entityInfo.FirstPrimaryKey.Name} IN (${values})`; // first-pk-ok: single-column key, checked above
252
+ }
253
+ return keys.map(k => `(${k.ToWhereClause()})`).join(' OR ');
254
+ }
255
+
256
+ /** A key's field names and values, lower-cased, so keys from the index and from a view compare equal. */
257
+ private canonicalKey(key: CompositeKey): string {
258
+ return key.KeyValuePairs.map(kv => `${kv.FieldName.toLowerCase()}|${String(kv.Value).toLowerCase()}`).join('||');
259
+ }
260
+
261
+ /**
262
+ * A copy of a vector's metadata without the keys naming fields in `denied` (lowercased field
263
+ * names, as `EntityInfo.GetDeniedReadFields` returns them), compared case-insensitively.
264
+ */
265
+ private withoutDeniedFields(metadata: Record<string, unknown>, denied: Set<string>): Record<string, unknown> {
266
+ if (denied.size === 0) return metadata;
267
+ return Object.fromEntries(Object.entries(metadata).filter(([key]) => !denied.has(key.toLowerCase())));
268
+ }
269
+
161
270
  /** Load EntityDocument by ID */
162
271
  private async loadEntityDocument(
163
272
  entityDocumentID: string,
164
- contextUser: UserInfo
273
+ contextUser: UserInfo,
274
+ rv: RunView
165
275
  ): Promise<MJEntityDocumentEntity | null> {
166
- const rv = new RunView();
167
276
  const result = await rv.RunView<MJEntityDocumentEntity>({
168
277
  EntityName: 'MJ: Entity Documents',
169
278
  ExtraFilter: `ID='${EscapeSQLString(entityDocumentID)}'`,
@@ -183,9 +292,9 @@ export class FetchEntityVectorsResolver extends ResolverBase {
183
292
  */
184
293
  private async resolveVectorIndex(
185
294
  entityDoc: MJEntityDocumentEntity,
186
- contextUser: UserInfo
295
+ contextUser: UserInfo,
296
+ rv: RunView
187
297
  ): Promise<MJVectorIndexEntity | null> {
188
- const rv = new RunView();
189
298
 
190
299
  // If the EntityDocument has an explicit VectorIndexID, use it directly
191
300
  if (entityDoc.VectorIndexID) {
@@ -222,9 +331,9 @@ export class FetchEntityVectorsResolver extends ResolverBase {
222
331
  /** Create a VectorDBBase provider instance for a given VectorIndex */
223
332
  private async createVectorDBInstance(
224
333
  vectorIndex: MJVectorIndexEntity,
225
- contextUser: UserInfo
334
+ contextUser: UserInfo,
335
+ rv: RunView
226
336
  ): Promise<VectorDBBase | null> {
227
- const rv = new RunView();
228
337
  const dbResult = await rv.RunView<MJVectorDatabaseEntity>({
229
338
  EntityName: 'MJ: Vector Databases',
230
339
  ExtraFilter: `ID='${vectorIndex.VectorDatabaseID}'`,
@@ -258,19 +367,20 @@ export class FetchEntityVectorsResolver extends ResolverBase {
258
367
  limit: number,
259
368
  filter: string | undefined,
260
369
  contextUser: UserInfo,
261
- md: IMetadataProvider
370
+ md: IMetadataProvider,
371
+ rv: RunView
262
372
  ): Promise<EntityVectorItem[]> {
263
373
  const entityInfo = md.EntityByName(entityDoc.Entity);
264
374
  if (!entityInfo) {
265
375
  throw new Error(`Entity "${entityDoc.Entity}" not found in metadata`);
266
376
  }
267
377
  const nameFields = this.labelFieldNames(entityInfo);
268
- const { records, vectors } = await this.loadRecordsAndStoredVectors(entityInfo, entityDoc, nameFields, filter, contextUser);
378
+ const { records, vectors } = await this.loadRecordsAndStoredVectors(entityInfo, entityDoc, nameFields, filter, contextUser, rv);
269
379
  const labelsByRecordID = this.mapRecordLabels(entityInfo, nameFields, records);
270
380
 
271
381
  // VectorJSON is the heavy column: list without it, then fetch it only for the items returned.
272
382
  const chosen = this.selectLabelledVectors(vectors, labelsByRecordID, limit);
273
- const valuesByID = await this.loadVectorValues(chosen.map(c => c.row.ID), contextUser);
383
+ const valuesByID = await this.loadVectorValues(chosen.map(c => c.row.ID), contextUser, rv);
274
384
 
275
385
  const results: EntityVectorItem[] = [];
276
386
  for (const { row, labels } of chosen) {
@@ -305,7 +415,7 @@ export class FetchEntityVectorsResolver extends ResolverBase {
305
415
  * Fetch `VectorJSON` for the given Entity Record Document IDs, keyed by ID. The ID lists are
306
416
  * capped at {@link VECTOR_FETCH_CHUNK_SIZE} per view and all sent in one `RunViews` batch.
307
417
  */
308
- private async loadVectorValues(ids: string[], contextUser: UserInfo): Promise<Map<string, string>> {
418
+ private async loadVectorValues(ids: string[], contextUser: UserInfo, rv: RunView): Promise<Map<string, string>> {
309
419
  const valuesByID = new Map<string, string>();
310
420
  if (ids.length === 0) return valuesByID;
311
421
 
@@ -321,7 +431,7 @@ export class FetchEntityVectorsResolver extends ResolverBase {
321
431
  ResultType: 'simple',
322
432
  });
323
433
  }
324
- const results = await new RunView().RunViews<{ ID: string; VectorJSON: string | null }>(views, contextUser);
434
+ const results = await rv.RunViews<{ ID: string; VectorJSON: string | null }>(views, contextUser);
325
435
  for (const result of results) {
326
436
  if (!result.Success) {
327
437
  throw new Error(`Failed to load vector values: ${result.ErrorMessage}`);
@@ -352,10 +462,11 @@ export class FetchEntityVectorsResolver extends ResolverBase {
352
462
  entityDoc: MJEntityDocumentEntity,
353
463
  nameFields: string[],
354
464
  filter: string | undefined,
355
- contextUser: UserInfo
465
+ contextUser: UserInfo,
466
+ rv: RunView
356
467
  ): Promise<{ records: Record<string, unknown>[]; vectors: StoredVectorRow[] }> {
357
468
  const keyFields = entityInfo.PrimaryKeys.map(pk => pk.Name);
358
- const [recordResult, docResult] = await new RunView().RunViews<Record<string, unknown>>([
469
+ const [recordResult, docResult] = await rv.RunViews<Record<string, unknown>>([
359
470
  {
360
471
  EntityName: entityInfo.Name,
361
472
  ExtraFilter: filter || undefined,
@@ -34,6 +34,7 @@ export class FileCategoryResolver extends FileCategoryResolverBase {
34
34
  fileCategoryEntity.CheckPermissions(EntityPermissionType.Delete, true);
35
35
 
36
36
  await fileCategoryEntity.Load(ID);
37
+ // fls-guard: loaded as the caller; width follows its user
37
38
  const returnValue = fileCategoryEntity.GetAll();
38
39
 
39
40
  // Any files using the deleted category fall back to its parent