@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
@@ -26,15 +26,18 @@ vi.mock('@memberjunction/core', () => ({
26
26
  },
27
27
  }));
28
28
 
29
+ const { OWNER, SUBMITTER } = vi.hoisted(() => ({ OWNER: { ID: 'owner-1' }, SUBMITTER: { ID: 'submitter-1' } }));
29
30
  vi.mock('@memberjunction/generic-database-provider', () => ({
30
31
  UserCache: {
31
- Instance: { Users: [{ ID: 'owner-user-99', Email: 'owner@example.com' }] },
32
- Users: [{ ID: 'owner-user-99', Email: 'owner@example.com' }],
32
+ Instance: { Users: [OWNER, SUBMITTER, { ID: 'owner-user-99', Email: 'owner@example.com' }] },
33
+ Users: [OWNER, SUBMITTER, { ID: 'owner-user-99', Email: 'owner@example.com' }],
33
34
  },
34
35
  }));
35
36
 
36
37
  vi.mock('@memberjunction/core-entities', () => ({
37
38
  MJConversationDetailEntity: class {},
39
+ MJConversationDetailEntityExtended: class {},
40
+ MJConversationEntity: class {},
38
41
  MJAIAgentRunEntity: class {},
39
42
  }));
40
43
 
@@ -56,15 +59,19 @@ import type { PubSubEngine } from 'type-graphql';
56
59
  /** A conversation-detail row that can be loaded (the source) and saved (the reply). */
57
60
  function detailRow(over: Partial<{ loads: boolean; saves: boolean; conversationID: string; branchID: string | null }> = {}) {
58
61
  const { loads = true, saves = true, conversationID = 'conv-1', branchID = null } = over;
62
+ let marked = false;
59
63
  return {
60
64
  ID: 'reply-detail-1',
61
65
  ConversationID: conversationID,
62
66
  BranchID: branchID as string | null,
67
+ UserID: 'owner-1',
63
68
  Role: '', Status: '', HiddenToUser: true, Message: '',
64
69
  AgentID: null as string | null,
65
70
  NewRecord: vi.fn(),
71
+ MarkServerAuthoredReply: vi.fn(() => { marked = true; }),
66
72
  Load: vi.fn().mockResolvedValue(loads),
67
- Save: vi.fn().mockResolvedValue(saves),
73
+ // Like MJConversationDetailEntityExtended, refuses to save an unmarked AI reply.
74
+ Save: vi.fn().mockImplementation(async () => saves && marked),
68
75
  LatestResult: { CompleteMessage: 'FK violation' },
69
76
  };
70
77
  }
@@ -88,6 +95,8 @@ function harness(over: Parameters<typeof detailRow>[0] = {}) {
88
95
  { ID: 'user-1' } as UserInfo,
89
96
  pubSub,
90
97
  );
98
+ // Rows in order: the source detail (Load), the reply (Save). The reply starts on a 'stale' fork, so a test sees
99
+ // the deliverer set it.
91
100
  return { deliverer, providerFactory, provider, rows, pubSub, reply: () => rows[1] };
92
101
  }
93
102
 
@@ -96,6 +105,7 @@ const params = (over: Partial<TaskContinuationParams> = {}): TaskContinuationPar
96
105
  WorkflowName: 'Weekly digest',
97
106
  ConversationDetailID: 'detail-1',
98
107
  SubmittedByAgentRunID: 'run-1',
108
+ SubmittedByUserID: 'submitter-1',
99
109
  ReinvokeDepth: 0,
100
110
  Tasks: [
101
111
  { TaskID: 't1', Name: 'Gather', Status: 'Complete', Summary: '412 rows' },
@@ -132,6 +142,51 @@ describe('posting the outcome', () => {
132
142
  expect(h.pubSub.publish).toHaveBeenCalledTimes(2);
133
143
  });
134
144
 
145
+ it("writes it as the graph's submitter, not the conversation's owner, marked as the server's reply", async () => {
146
+ // TaskGraph.Submit takes any conversation detail ID. Written as the owner, the gate would pass for a
147
+ // conversation the submitter cannot reach; written as the submitter, the gate checks their access.
148
+ const h = harness();
149
+ await h.deliverer.PostMessage(params());
150
+
151
+ expect(h.provider.GetEntityObject.mock.calls[0]).toEqual(['MJ: Conversation Details', SUBMITTER]);
152
+ expect(h.provider.GetEntityObject.mock.calls[1]).toEqual(['MJ: Conversation Details', SUBMITTER]);
153
+ expect(h.provider.GetEntityObject.mock.calls.some((call) => call[1] === OWNER)).toBe(false);
154
+ expect(h.rows[0].MarkServerAuthoredReply).toHaveBeenCalledOnce();
155
+ expect(h.reply().MarkServerAuthoredReply).toHaveBeenCalledOnce();
156
+ });
157
+
158
+ it("updates the originating message in-place as the graph's submitter, marked as the server's reply, when IsLatest is true", async () => {
159
+ const { RunView } = await import('@memberjunction/core');
160
+ vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
161
+ RunView: vi.fn().mockImplementation(async (rvParams: { EntityName: string }) => {
162
+ if (rvParams.EntityName === 'MJ: Conversation Details') {
163
+ return { Success: true, Results: [{ ID: 'detail-1' }] };
164
+ }
165
+ return { Success: false, Results: [] };
166
+ }),
167
+ } as never);
168
+
169
+ const h = harness();
170
+ await h.deliverer.PostMessage(params());
171
+
172
+ expect(h.rows.length).toBe(1);
173
+ expect(h.provider.GetEntityObject.mock.calls[0]).toEqual(['MJ: Conversation Details', SUBMITTER]);
174
+ expect(h.rows[0].MarkServerAuthoredReply).toHaveBeenCalledOnce();
175
+ expect(h.rows[0].Save).toHaveBeenCalledOnce();
176
+ expect(h.rows[0].Status).toBe('Complete');
177
+ expect(h.rows[0].Message).toContain('Weekly digest');
178
+ expect(h.pubSub.publish).toHaveBeenCalledTimes(1);
179
+ });
180
+
181
+ it("falls back to the deliverer's own user when the graph records no submitter it knows", async () => {
182
+ for (const submitter of [null, 'stranger-1']) {
183
+ const h = harness();
184
+ await h.deliverer.PostMessage(params({ SubmittedByUserID: submitter }));
185
+ expect(h.provider.GetEntityObject.mock.calls[0]).toEqual(['MJ: Conversation Details', { ID: 'user-1' }]);
186
+ expect(h.provider.GetEntityObject.mock.calls[1]).toEqual(['MJ: Conversation Details', { ID: 'user-1' }]);
187
+ }
188
+ });
189
+
135
190
  it('names the workflow and carries the roll-up', async () => {
136
191
  const h = harness();
137
192
  await h.deliverer.PostMessage(params());
@@ -409,6 +464,8 @@ describe('Reinvoke — on the fork of the message that started the graph', () =>
409
464
  // Only the originating detail was loaded and updated in-place — no second detail row created!
410
465
  expect(createdDetails.length).toBe(1);
411
466
  expect(originDetail.NewRecord).not.toHaveBeenCalled();
467
+ expect(originDetail.MarkServerAuthoredReply).toHaveBeenCalledOnce();
468
+ expect(originDetail.Save).toHaveBeenCalledOnce();
412
469
  expect(originDetail.Message).toBe('Synthesized workflow outcome');
413
470
  expect(originDetail.Status).toBe('Complete');
414
471
  expect(runAgent.mock.calls[0][0].conversationDetailId).toBe('detail-1');
@@ -460,8 +517,11 @@ describe('Reinvoke — on the fork of the message that started the graph', () =>
460
517
  await deliverer.Reinvoke(params());
461
518
 
462
519
  expect(replyDetail.NewRecord).toHaveBeenCalledTimes(1);
520
+ expect(replyDetail.MarkServerAuthoredReply).toHaveBeenCalledOnce();
463
521
  expect(replyDetail.ParentID).toBe('detail-1');
464
522
  expect(replyDetail.Message).toBe('Synthesized workflow outcome');
523
+ expect(originDetail.MarkServerAuthoredReply).toHaveBeenCalledOnce();
524
+ expect(originDetail.Save).toHaveBeenCalledOnce();
465
525
  expect(originDetail.Message).toContain('*(✅ **Weekly digest** completed — synthesized results posted below.)*');
466
526
  expect(originDetail.Message).not.toContain("I'll follow up when it finishes");
467
527
  expect(pubSub.publish).toHaveBeenCalledTimes(2);
@@ -625,6 +685,57 @@ describe('Reinvoke — on the fork of the message that started the graph', () =>
625
685
  expect(sentContent).toContain('- ⏭️ **Analyze Results** — Skipped: branch condition `output.rowCount > 0` on "Run Query" evaluated to false');
626
686
  expect(sentContent).toContain('briefly explaining any conditional steps that were skipped');
627
687
  });
688
+
689
+ it('logs an error and does not publish completion when an in-place (IsLatest = true) PostMessage save fails', async () => {
690
+ const { LogError, RunView } = await import('@memberjunction/core');
691
+ vi.mocked(RunView.FromMetadataProvider).mockReturnValue({
692
+ RunView: vi.fn().mockResolvedValue({
693
+ Success: true,
694
+ Results: [{ ID: 'detail-1' }],
695
+ }),
696
+ } as never);
697
+
698
+ const h = harness({ saves: false });
699
+ await h.deliverer.PostMessage(params());
700
+
701
+ expect(h.rows[0].Save).toHaveBeenCalledOnce();
702
+ expect(LogError).toHaveBeenCalledWith(expect.stringContaining('Could not update in-place outcome of "Weekly digest"'));
703
+ expect(h.pubSub.publish).not.toHaveBeenCalled();
704
+ });
705
+
706
+ it('loads the originating detail as SubmittedByUserID in Reinvoke when both run UserIDs are null', async () => {
707
+ const runRow = {
708
+ ID: 'run-1',
709
+ AgentID: 'agent-1',
710
+ ParentRunID: null as string | null,
711
+ UserID: null as string | null,
712
+ Load: vi.fn().mockResolvedValue(true),
713
+ };
714
+ const agentRow = {
715
+ ID: 'agent-1',
716
+ Name: 'Sage',
717
+ Load: vi.fn().mockResolvedValue(true),
718
+ };
719
+ const detail = detailRow();
720
+ const detailUsers: Array<UserInfo | undefined> = [];
721
+ const provider = {
722
+ GetEntityObject: vi.fn().mockImplementation(async (name: string, user?: UserInfo) => {
723
+ if (name === 'MJ: AI Agent Runs') return runRow;
724
+ if (name === 'MJ: AI Agents') return agentRow;
725
+ detailUsers.push(user);
726
+ return detail;
727
+ }),
728
+ };
729
+ const deliverer = new TaskGraphContinuationDeliverer(
730
+ { CreateProvider: vi.fn().mockResolvedValue(provider) } as never,
731
+ { ID: 'system-user' } as UserInfo,
732
+ );
733
+
734
+ await deliverer.Reinvoke(params({ SubmittedByUserID: SUBMITTER.ID }));
735
+
736
+ expect(detailUsers[0]).toBe(SUBMITTER);
737
+ expect(runAgent.mock.calls[0][0].contextUser).toBe(SUBMITTER);
738
+ });
628
739
  });
629
740
 
630
741
 
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Field-level security on the transaction-group transport.
3
+ *
4
+ * Output: every `ResultsJSON` entry is projected for the caller. The update entry is a DELTA,
5
+ * computed by comparing the post-save row with a pre-save snapshot. When the snapshot came from a
6
+ * load narrowed for the caller and the post-save row is full width, every non-null denied value
7
+ * differs from its (absent) snapshot and counts as "changed", so the delta itself carried the
8
+ * values the caller may not read. The entity objects here deliberately return full-width rows
9
+ * to stand in for any object not narrowed at its source.
10
+ *
11
+ * Input: a restricted caller's item payload, and the field a transaction variable names, are
12
+ * refused when they carry a key the caller may not read or a key that names no field, with the
13
+ * same message for both, so the refusal does not reveal which columns exist.
14
+ */
15
+ import 'reflect-metadata';
16
+ import { describe, it, expect, vi } from 'vitest';
17
+ import { BaseEntityResult, EntityInfo, UserInfo } from '@memberjunction/core';
18
+
19
+ vi.mock('@memberjunction/core', async (importOriginal) => {
20
+ const actual = await importOriginal<typeof import('@memberjunction/core')>();
21
+ return { ...actual, LogError: () => undefined };
22
+ });
23
+
24
+ import {
25
+ TransactionResolver,
26
+ TransactionInputType,
27
+ TransactionOperationType,
28
+ TransactionVariableType,
29
+ } from '../resolvers/TransactionGroupResolver.js';
30
+ import type { AppContext } from '../types.js';
31
+ import { EmployeeEntity, FullWidthEmployeeRow, HRUser, Intern, RefusalFor, SECRET_SALARY } from './fieldSecurityFixtures.js';
32
+
33
+ class FakeTransactionGroup {
34
+ public Enrolled = 0;
35
+ public AddTransaction(): void { this.Enrolled++; }
36
+ public AddVariable(): void { /* variables are not under test here */ }
37
+ public async Submit(): Promise<boolean> { return true; }
38
+ }
39
+
40
+ /**
41
+ * An entity object that is NOT narrowed at its source: its load returns `loaded`, and after the
42
+ * save it reports the full-width row, as a save's re-read does.
43
+ */
44
+ class FullWidthEntity {
45
+ public TransactionGroup: FakeTransactionGroup | null = null;
46
+ public LatestResult: BaseEntityResult | null = null;
47
+ public PrimaryKeys = [{ Name: 'ID' }];
48
+ public SetManyCalls: Array<Record<string, unknown>> = [];
49
+ private values: Record<string, unknown>;
50
+ private saved = false;
51
+
52
+ constructor(public readonly EntityInfo: EntityInfo, private readonly loaded: Record<string, unknown>) {
53
+ this.values = { ...loaded };
54
+ }
55
+ public SetMany(values: Record<string, unknown>): void {
56
+ this.SetManyCalls.push(values);
57
+ this.values = { ...this.values, ...values };
58
+ }
59
+ public async InnerLoad(): Promise<boolean> { this.values = { ...this.loaded }; return true; }
60
+ public async GetDataObject(): Promise<Record<string, unknown>> {
61
+ return this.saved ? { ...FullWidthEmployeeRow(), ...this.values, Salary: SECRET_SALARY } : { ...this.values };
62
+ }
63
+ public async GetDataObjectJSON(): Promise<string> { return JSON.stringify(await this.GetDataObject()); }
64
+ public async Save(): Promise<boolean> { return this.write(); }
65
+ public async Delete(): Promise<boolean> { return this.write(); }
66
+ private write(): boolean {
67
+ this.TransactionGroup?.AddTransaction();
68
+ this.saved = true;
69
+ const result = new BaseEntityResult();
70
+ result.Success = true;
71
+ this.LatestResult = result;
72
+ return true;
73
+ }
74
+ }
75
+
76
+ /** What a load narrowed for an intern holds: no salary keys at all. */
77
+ const NARROW_ROW = { ID: 'emp-1', Name: 'Ada', Notes: 'n' };
78
+
79
+ function buildContext(entities: FullWidthEntity[], user: UserInfo): AppContext {
80
+ let next = 0;
81
+ const provider = {
82
+ CreateTransactionGroup: async () => new FakeTransactionGroup(),
83
+ GetEntityObject: async () => entities[next++],
84
+ Entities: [] as EntityInfo[],
85
+ EntityByID: () => undefined,
86
+ };
87
+ return {
88
+ providers: [{ type: 'Read-Write', provider }],
89
+ userPayload: { userRecord: user, email: user.Email },
90
+ } as unknown as AppContext;
91
+ }
92
+
93
+ function group(operation: TransactionOperationType, payload: Record<string, unknown>): TransactionInputType {
94
+ return { Items: [{ EntityName: 'Employees', EntityObjectJSON: JSON.stringify(payload), OperationType: operation }], Variables: [] };
95
+ }
96
+
97
+ describe('ExecuteTransactionGroup: output is projected for the caller', () => {
98
+ it('REGRESSION: the update delta never carries a denied value, though the post-save row is full width', async () => {
99
+ const entity = new FullWidthEntity(EmployeeEntity(), NARROW_ROW);
100
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
101
+ group(TransactionOperationType.Update, { ID: 'emp-1', Notes: 'edited' }),
102
+ buildContext([entity], Intern())
103
+ );
104
+
105
+ expect(result.Success).toBe(true);
106
+ const delta = JSON.parse(result.ResultsJSON[0]);
107
+ expect(delta).toMatchObject({ ID: 'emp-1', Notes: 'edited' });
108
+ expect(delta).not.toHaveProperty('Salary');
109
+ expect(delta).not.toHaveProperty('Base Salary');
110
+ expect(result.ResultsJSON[0]).not.toContain(String(SECRET_SALARY));
111
+ });
112
+
113
+ it('REGRESSION: the create result never carries a denied value', async () => {
114
+ const entity = new FullWidthEntity(EmployeeEntity(), {});
115
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
116
+ group(TransactionOperationType.Create, { ID: 'emp-1', Name: 'Ada' }),
117
+ buildContext([entity], Intern())
118
+ );
119
+
120
+ expect(result.ResultsJSON[0]).not.toContain(String(SECRET_SALARY));
121
+ expect(JSON.parse(result.ResultsJSON[0])).toMatchObject({ ID: 'emp-1', Name: 'Ada' });
122
+ });
123
+
124
+ it('REGRESSION: the delete result never carries a denied value', async () => {
125
+ const entity = new FullWidthEntity(EmployeeEntity(), FullWidthEmployeeRow());
126
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
127
+ group(TransactionOperationType.Delete, { ID: 'emp-1' }),
128
+ buildContext([entity], Intern())
129
+ );
130
+
131
+ expect(result.ResultsJSON[0]).not.toContain(String(SECRET_SALARY));
132
+ expect(JSON.parse(result.ResultsJSON[0])).toMatchObject({ ID: 'emp-1', Name: 'Ada' });
133
+ });
134
+
135
+ it('a caller who may read the salary still gets it back', async () => {
136
+ const entity = new FullWidthEntity(EmployeeEntity(), {});
137
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
138
+ group(TransactionOperationType.Create, { ID: 'emp-1', Name: 'Ada', Salary: 1 }),
139
+ buildContext([entity], HRUser())
140
+ );
141
+
142
+ expect(JSON.parse(result.ResultsJSON[0]).Salary).toBe(SECRET_SALARY);
143
+ });
144
+ });
145
+
146
+ describe('ExecuteTransactionGroup: client input is gated for a restricted caller', () => {
147
+ it.each([
148
+ ['a denied field', 'Salary'],
149
+ ['a key that names no field', 'Bogus'],
150
+ ])('REGRESSION: refuses %s with the same message, before anything is applied', async (_label, key) => {
151
+ const entity = new FullWidthEntity(EmployeeEntity(), NARROW_ROW);
152
+ const run = new TransactionResolver().ExecuteTransactionGroup(
153
+ group(TransactionOperationType.Update, { ID: 'emp-1', Notes: 'edited', [key]: 1 }),
154
+ buildContext([entity], Intern())
155
+ );
156
+
157
+ await expect(run).rejects.toThrow(RefusalFor(key));
158
+ expect(entity.SetManyCalls).toHaveLength(0);
159
+ });
160
+
161
+ it('REGRESSION: refuses a transaction variable that names a denied field (it would copy the value elsewhere)', async () => {
162
+ const source = new FullWidthEntity(EmployeeEntity(), NARROW_ROW);
163
+ const target = new FullWidthEntity(EmployeeEntity(), NARROW_ROW);
164
+ const input: TransactionInputType = {
165
+ Items: [
166
+ { EntityName: 'Employees', EntityObjectJSON: JSON.stringify({ ID: 'emp-1', Notes: 'a' }), OperationType: TransactionOperationType.Update },
167
+ { EntityName: 'Employees', EntityObjectJSON: JSON.stringify({ ID: 'emp-2', Notes: 'b' }), OperationType: TransactionOperationType.Update },
168
+ ],
169
+ Variables: [
170
+ { Name: 'leak', ItemIndex: 0, FieldName: 'Salary', Type: TransactionVariableType.Define },
171
+ { Name: 'leak', ItemIndex: 1, FieldName: 'Notes', Type: TransactionVariableType.Use },
172
+ ],
173
+ };
174
+
175
+ await expect(new TransactionResolver().ExecuteTransactionGroup(input, buildContext([source, target], Intern())))
176
+ .rejects.toThrow(RefusalFor('Salary'));
177
+ });
178
+
179
+ it('accepts the keys a well-behaved client sends, plus transport keys', async () => {
180
+ const entity = new FullWidthEntity(EmployeeEntity(), NARROW_ROW);
181
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
182
+ group(TransactionOperationType.Update, { ID: 'emp-1', Notes: 'edited', __typename: 'Employee_' }),
183
+ buildContext([entity], Intern())
184
+ );
185
+
186
+ expect(result.Success).toBe(true);
187
+ expect(entity.SetManyCalls).toHaveLength(1);
188
+ });
189
+
190
+ it('does not gate a caller who is denied nothing', async () => {
191
+ const entity = new FullWidthEntity(EmployeeEntity(), FullWidthEmployeeRow());
192
+ const result = await new TransactionResolver().ExecuteTransactionGroup(
193
+ group(TransactionOperationType.Update, { ID: 'emp-1', Salary: 5 }),
194
+ buildContext([entity], HRUser())
195
+ );
196
+
197
+ expect(result.Success).toBe(true);
198
+ });
199
+ });
@@ -35,6 +35,7 @@ vi.mock('../resolvers/UserResolver.js', () => ({ UserResolver: class {} }));
35
35
 
36
36
  import { UserViewResolver } from '../resolvers/UserViewResolver.js';
37
37
  import type { AppContext, ProviderInfo, UserPayload } from '../types.js';
38
+ import { EmployeeEntity, FullWidthEmployeeRow, Intern, SECRET_SALARY } from './fieldSecurityFixtures.js';
38
39
 
39
40
  const VIEW_ID = '0F8FAD5B-D9CB-469F-A165-70867728950E';
40
41
  const USER = { ID: 'user-1', Email: 'dev@example.com' } as unknown as UserInfo;
@@ -111,4 +112,18 @@ describe('UserViewResolver.UpdateWhereClause', () => {
111
112
  const ctx = makeContext([{ provider: makeProvider(view), type: 'Read-Write' }]);
112
113
  await expect(new UserViewResolver().UpdateWhereClause(VIEW_ID, ctx)).rejects.toThrow(/Failed to update where clause/);
113
114
  });
115
+
116
+ it('REGRESSION: returns the saved view projected for the caller, never a denied value', async () => {
117
+ const { view } = makeView({ loadOk: true });
118
+ const fullWidth = view as FakeView & { EntityInfo?: unknown };
119
+ fullWidth.EntityInfo = EmployeeEntity();
120
+ fullWidth.GetAll = vi.fn(() => FullWidthEmployeeRow());
121
+ const ctx = makeContext([{ provider: makeProvider(view), type: 'Read-Write' }]);
122
+ (ctx.userPayload as { userRecord: unknown }).userRecord = Intern();
123
+
124
+ const result = await new UserViewResolver().UpdateWhereClause(VIEW_ID, ctx);
125
+
126
+ expect(JSON.stringify(result)).not.toContain(String(SECRET_SALARY));
127
+ expect(result).toMatchObject({ ID: 'emp-1', Name: 'Ada' });
128
+ });
114
129
  });
@@ -107,14 +107,19 @@ describe('ViewOperationsHandler.runView', () => {
107
107
  });
108
108
 
109
109
  describe('parameter sanitization', () => {
110
- it('defaults ResultType to "simple" when unset and preserves an explicit one', async () => {
110
+ it('defaults ResultType to "simple" when unset, forces "simple" over "entity_object", and keeps "count_only"', async () => {
111
111
  const defaulted: RunViewParams = { EntityName: 'Customers' };
112
112
  await ViewOperationsHandler.runView(defaulted, makeUser());
113
113
  expect((mockRunViewFn.mock.calls[0][0] as RunViewParams).ResultType).toBe('simple');
114
114
 
115
+ // Entity objects cannot cross the wire: serializing one sends its internal state, not its fields.
115
116
  const explicit: RunViewParams = { EntityName: 'Customers', ResultType: 'entity_object' };
116
117
  await ViewOperationsHandler.runView(explicit, makeUser());
117
- expect((mockRunViewFn.mock.calls[1][0] as RunViewParams).ResultType).toBe('entity_object');
118
+ expect((mockRunViewFn.mock.calls[1][0] as RunViewParams).ResultType).toBe('simple');
119
+
120
+ const countOnly: RunViewParams = { EntityName: 'Customers', ResultType: 'count_only' };
121
+ await ViewOperationsHandler.runView(countOnly, makeUser());
122
+ expect((mockRunViewFn.mock.calls[2][0] as RunViewParams).ResultType).toBe('count_only');
118
123
  });
119
124
 
120
125
  it('splits comma-separated Fields strings and coerces numeric strings for MaxRows/StartRow', async () => {