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

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 (463) hide show
  1. package/README.md +69 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
  5. package/dist/agentSessions/BridgeHostInstance.js +14 -4
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
  7. package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
  8. package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
  9. package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
  10. package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
  11. package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +31 -8
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +85 -41
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +14 -4
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +28 -4
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
  22. package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
  23. package/dist/agentSessions/conversationBrowsers.js +118 -0
  24. package/dist/agentSessions/conversationBrowsers.js.map +1 -0
  25. package/dist/agentSessions/sessionDeadline.d.ts +43 -0
  26. package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
  27. package/dist/agentSessions/sessionDeadline.js +68 -0
  28. package/dist/agentSessions/sessionDeadline.js.map +1 -0
  29. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  30. package/dist/auth/APIKeyScopeAuth.js +5 -4
  31. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  32. package/dist/auth/loginEmailResolution.d.ts +41 -0
  33. package/dist/auth/loginEmailResolution.d.ts.map +1 -0
  34. package/dist/auth/loginEmailResolution.js +47 -0
  35. package/dist/auth/loginEmailResolution.js.map +1 -0
  36. package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
  37. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
  39. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  40. package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
  41. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  42. package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
  43. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  44. package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
  45. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  46. package/dist/auth/magicLink/MagicLinkService.js +221 -9
  47. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  48. package/dist/auth/magicLink/index.d.ts +1 -1
  49. package/dist/auth/magicLink/index.d.ts.map +1 -1
  50. package/dist/auth/magicLink/index.js +1 -1
  51. package/dist/auth/magicLink/index.js.map +1 -1
  52. package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
  53. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  54. package/dist/auth/magicLink/redeemLanding.js +7 -4
  55. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  56. package/dist/auth/magicLink/types.d.ts +27 -3
  57. package/dist/auth/magicLink/types.d.ts.map +1 -1
  58. package/dist/auth/magicLink/types.js +0 -4
  59. package/dist/auth/magicLink/types.js.map +1 -1
  60. package/dist/auth/newUsers.d.ts.map +1 -1
  61. package/dist/auth/newUsers.js +1 -0
  62. package/dist/auth/newUsers.js.map +1 -1
  63. package/dist/backgroundSweepConfigUnits.d.ts +231 -0
  64. package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
  65. package/dist/backgroundSweepConfigUnits.js +180 -0
  66. package/dist/backgroundSweepConfigUnits.js.map +1 -0
  67. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
  68. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
  69. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
  70. package/dist/config.d.ts +906 -8
  71. package/dist/config.d.ts.map +1 -1
  72. package/dist/config.js +59 -0
  73. package/dist/config.js.map +1 -1
  74. package/dist/context.d.ts +2 -0
  75. package/dist/context.d.ts.map +1 -1
  76. package/dist/context.js +41 -12
  77. package/dist/context.js.map +1 -1
  78. package/dist/coreRoutes.d.ts +3 -1
  79. package/dist/coreRoutes.d.ts.map +1 -1
  80. package/dist/coreRoutes.js +6 -4
  81. package/dist/coreRoutes.js.map +1 -1
  82. package/dist/generated/graphql-schemas/__mj.d.ts +1072 -5
  83. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  84. package/dist/generated/graphql-schemas/__mj.js +10174 -4618
  85. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  87. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  88. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  89. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +50 -5
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +329 -132
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +4 -0
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +47 -7
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/globalCors.d.ts.map +1 -1
  99. package/dist/globalCors.js +1 -0
  100. package/dist/globalCors.js.map +1 -1
  101. package/dist/index.d.ts +3 -0
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +201 -43
  104. package/dist/index.js.map +1 -1
  105. package/dist/integration/ConnectionPause.d.ts +121 -0
  106. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  107. package/dist/integration/ConnectionPause.js +183 -0
  108. package/dist/integration/ConnectionPause.js.map +1 -0
  109. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +87 -46
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  114. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  115. package/dist/integration/EntityMapLifecycle.js +199 -5
  116. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  117. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  118. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  119. package/dist/integration/InactiveRowWarnings.js +29 -0
  120. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  121. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  122. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  123. package/dist/integration/ObjectNamesByID.js +87 -0
  124. package/dist/integration/ObjectNamesByID.js.map +1 -0
  125. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  126. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  127. package/dist/integration/PostRestartIntrospect.js +27 -0
  128. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  129. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  130. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  131. package/dist/integration/RSUPostRestartProgress.js +323 -0
  132. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  133. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  134. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  135. package/dist/integration/RSUProgressBridge.js +84 -1
  136. package/dist/integration/RSUProgressBridge.js.map +1 -1
  137. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  138. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  139. package/dist/integration/RunArtifactAuthorization.js +45 -0
  140. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  141. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  142. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  143. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  144. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  145. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  146. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  147. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  148. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  149. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  150. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  151. package/dist/integration/TargetConfigGuard.js +40 -0
  152. package/dist/integration/TargetConfigGuard.js.map +1 -0
  153. package/dist/logging/StartupLogger.d.ts +50 -0
  154. package/dist/logging/StartupLogger.d.ts.map +1 -1
  155. package/dist/logging/StartupLogger.js +15 -22
  156. package/dist/logging/StartupLogger.js.map +1 -1
  157. package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
  158. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
  159. package/dist/middleware/BaseServerMiddleware.js +4 -2
  160. package/dist/middleware/BaseServerMiddleware.js.map +1 -1
  161. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  162. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  163. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  164. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  165. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  166. package/dist/realtimeSessions/sessionAccess.js +22 -0
  167. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  168. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  169. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  170. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  171. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  172. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  173. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  174. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  175. package/dist/realtimeWidget/widgetCore.js +4 -7
  176. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  177. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  178. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  179. package/dist/requestOrigin.d.ts +22 -0
  180. package/dist/requestOrigin.d.ts.map +1 -0
  181. package/dist/requestOrigin.js +60 -0
  182. package/dist/requestOrigin.js.map +1 -0
  183. package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
  184. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  185. package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
  186. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  187. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  188. package/dist/resolvers/AgentSessionResolver.js +6 -2
  189. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  190. package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
  191. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  192. package/dist/resolvers/AutotagPipelineResolver.js +61 -16
  193. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  194. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  195. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  196. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  197. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  198. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  199. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  200. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  201. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  202. package/dist/resolvers/GetDataResolver.js +34 -13
  203. package/dist/resolvers/GetDataResolver.js.map +1 -1
  204. package/dist/resolvers/InfoResolver.js +1 -1
  205. package/dist/resolvers/InfoResolver.js.map +1 -1
  206. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  207. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  208. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  209. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  210. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  211. package/dist/resolvers/MCPResolver.js +13 -2
  212. package/dist/resolvers/MCPResolver.js.map +1 -1
  213. package/dist/resolvers/QuerySystemUserResolver.d.ts +38 -1
  214. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  215. package/dist/resolvers/QuerySystemUserResolver.js +181 -88
  216. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  217. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  218. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  219. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  220. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  221. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  222. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  223. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  224. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  225. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  226. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  227. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  228. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  229. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  230. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  231. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  232. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  233. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  234. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  235. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  236. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  237. package/dist/resolvers/RunTemplateResolver.js +3 -1
  238. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  239. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  240. package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
  241. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  242. package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
  243. package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
  244. package/dist/resolvers/TagGovernanceResolver.js +84 -0
  245. package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
  246. package/dist/resolvers/TelemetryResolver.d.ts +13 -8
  247. package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
  248. package/dist/resolvers/TelemetryResolver.js +40 -9
  249. package/dist/resolvers/TelemetryResolver.js.map +1 -1
  250. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  251. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  252. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  253. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  254. package/dist/resolvers/meetings/meetingMutations.js +34 -4
  255. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  256. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  257. package/dist/rest/MediaAccessKeys.js +6 -2
  258. package/dist/rest/MediaAccessKeys.js.map +1 -1
  259. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  260. package/dist/rest/OAuthCallbackHandler.js +11 -5
  261. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  262. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  263. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  264. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  265. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  266. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  267. package/dist/rest/ViewOperationsHandler.js +2 -1
  268. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  269. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  270. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  271. package/dist/rest/setupRESTEndpoints.js +11 -2
  272. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  273. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  274. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  275. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  276. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  277. package/dist/services/TaskGraphActionRunner.js +2 -26
  278. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  279. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  280. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  281. package/dist/services/TaskGraphAgentRunner.js +38 -0
  282. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  283. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  284. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  285. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  286. package/dist/services/WorkQueueHostService.d.ts +2 -1
  287. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  288. package/dist/services/WorkQueueHostService.js +19 -4
  289. package/dist/services/WorkQueueHostService.js.map +1 -1
  290. package/dist/services/workQueueConfig.d.ts +2 -2
  291. package/dist/services/workQueueConfig.js +5 -5
  292. package/dist/services/workQueueConfig.js.map +1 -1
  293. package/dist/sharedCache.js +1 -1
  294. package/dist/sharedCache.js.map +1 -1
  295. package/dist/types.d.ts +17 -0
  296. package/dist/types.d.ts.map +1 -1
  297. package/dist/types.js.map +1 -1
  298. package/dist/util.d.ts.map +1 -1
  299. package/dist/util.js +55 -2
  300. package/dist/util.js.map +1 -1
  301. package/package.json +107 -100
  302. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  303. package/src/__tests__/ActiveOperations.test.ts +68 -0
  304. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  305. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  306. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  307. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  308. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  309. package/src/__tests__/ConnectionPause.test.ts +192 -0
  310. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  311. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  312. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  313. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  314. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  315. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  316. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  317. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  318. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  319. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  320. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  321. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  322. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  323. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  324. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  325. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  326. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  327. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  328. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  329. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  330. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  331. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  332. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  333. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  334. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  335. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  336. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  337. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  338. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  339. package/src/__tests__/ResolverBase.clientOldValues.test.ts +327 -26
  340. package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
  341. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  342. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  343. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  344. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  345. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  346. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  347. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  348. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  349. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  350. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
  351. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  352. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  353. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  354. package/src/__tests__/SessionJanitor.test.ts +195 -4
  355. package/src/__tests__/SessionManager.test.ts +69 -0
  356. package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
  357. package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
  358. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  359. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  360. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  361. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  362. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  363. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  364. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  365. package/src/__tests__/globalCors.test.ts +12 -0
  366. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  367. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  368. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  369. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  370. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  371. package/src/__tests__/requestOrigin.test.ts +104 -0
  372. package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
  373. package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
  374. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  375. package/src/__tests__/sessionDeadline.test.ts +51 -0
  376. package/src/__tests__/sharedCache.test.ts +11 -0
  377. package/src/__tests__/unifiedAuth.test.ts +73 -1
  378. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  379. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  380. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  381. package/src/agentSessions/SessionJanitor.ts +92 -42
  382. package/src/agentSessions/SessionManager.ts +38 -8
  383. package/src/agentSessions/conversationBrowsers.ts +143 -0
  384. package/src/agentSessions/sessionDeadline.ts +81 -0
  385. package/src/auth/APIKeyScopeAuth.ts +5 -4
  386. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  387. package/src/auth/loginEmailResolution.ts +67 -0
  388. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  389. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  390. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  391. package/src/auth/magicLink/index.ts +1 -1
  392. package/src/auth/magicLink/redeemLanding.ts +7 -4
  393. package/src/auth/magicLink/types.ts +28 -3
  394. package/src/auth/newUsers.ts +1 -0
  395. package/src/backgroundSweepConfigUnits.ts +213 -0
  396. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
  397. package/src/config.ts +69 -0
  398. package/src/context.ts +46 -15
  399. package/src/coreRoutes.ts +14 -7
  400. package/src/generated/graphql-schemas/__mj.ts +10360 -6479
  401. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  402. package/src/generic/ResolverBase.ts +422 -143
  403. package/src/generic/RunViewResolver.ts +96 -49
  404. package/src/globalCors.ts +1 -0
  405. package/src/index.ts +246 -44
  406. package/src/integration/ConnectionPause.ts +222 -0
  407. package/src/integration/CustomColumnPromoter.ts +123 -53
  408. package/src/integration/EntityMapLifecycle.ts +229 -6
  409. package/src/integration/InactiveRowWarnings.ts +50 -0
  410. package/src/integration/ObjectNamesByID.ts +97 -0
  411. package/src/integration/PostRestartIntrospect.ts +49 -0
  412. package/src/integration/RSUPostRestartProgress.ts +359 -0
  413. package/src/integration/RSUProgressBridge.ts +85 -2
  414. package/src/integration/RunArtifactAuthorization.ts +65 -0
  415. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  416. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  417. package/src/integration/TargetConfigGuard.ts +71 -0
  418. package/src/logging/StartupLogger.ts +24 -0
  419. package/src/middleware/BaseServerMiddleware.ts +20 -3
  420. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  421. package/src/realtimeSessions/sessionAccess.ts +30 -0
  422. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  423. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  424. package/src/realtimeWidget/widgetCore.ts +4 -8
  425. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  426. package/src/requestOrigin.ts +64 -0
  427. package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
  428. package/src/resolvers/AgentSessionResolver.ts +6 -2
  429. package/src/resolvers/AutotagPipelineResolver.ts +59 -14
  430. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  431. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  432. package/src/resolvers/GetDataResolver.ts +35 -13
  433. package/src/resolvers/InfoResolver.ts +1 -1
  434. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  435. package/src/resolvers/MCPResolver.ts +15 -2
  436. package/src/resolvers/QuerySystemUserResolver.ts +211 -100
  437. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  438. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  439. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  440. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  441. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  442. package/src/resolvers/RunTemplateResolver.ts +3 -1
  443. package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
  444. package/src/resolvers/TagGovernanceResolver.ts +65 -0
  445. package/src/resolvers/TelemetryResolver.ts +44 -9
  446. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  447. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  448. package/src/resolvers/meetings/meetingMutations.ts +37 -7
  449. package/src/rest/MediaAccessKeys.ts +6 -2
  450. package/src/rest/OAuthCallbackHandler.ts +13 -5
  451. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  452. package/src/rest/ViewOperationsHandler.ts +2 -1
  453. package/src/rest/setupRESTEndpoints.ts +11 -2
  454. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  455. package/src/services/TaskGraphActionRunner.ts +2 -23
  456. package/src/services/TaskGraphAgentRunner.ts +43 -0
  457. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  458. package/src/services/WorkQueueHostService.ts +26 -4
  459. package/src/services/workQueueConfig.ts +5 -5
  460. package/src/sharedCache.ts +1 -1
  461. package/src/types.ts +18 -0
  462. package/src/util.ts +55 -3
  463. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -0,0 +1,413 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ const { mockCorsConfig } = vi.hoisted(() => ({
4
+ mockCorsConfig: {
5
+ allowedOrigins: ['https://portal.example.com', 'https://forms.example.com'],
6
+ },
7
+ }));
8
+
9
+ vi.mock('@memberjunction/generic-database-provider', () => ({
10
+ UserCache: { Instance: { Users: [] } },
11
+ }));
12
+ vi.mock('@memberjunction/communication-engine', () => ({
13
+ CommunicationEngine: {
14
+ Instance: {
15
+ Config: vi.fn(async () => undefined),
16
+ SendSingleMessage: vi.fn(async () => ({ Success: true })),
17
+ },
18
+ },
19
+ }));
20
+ vi.mock('@memberjunction/communication-types', () => ({
21
+ Message: class {
22
+ From = '';
23
+ To = '';
24
+ Subject = '';
25
+ Body = '';
26
+ HTMLBody = '';
27
+ },
28
+ }));
29
+ vi.mock('../config.js', () => ({
30
+ configInfo: {
31
+ cors: mockCorsConfig,
32
+ },
33
+ }));
34
+ vi.mock('../isolatedProvider.js', () => ({
35
+ CreateIsolatedProvider: async () => {
36
+ throw new Error('provisioning is not exercised in this suite');
37
+ },
38
+ }));
39
+
40
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
41
+ import type { MJMagicLinkInviteEntity } from '@memberjunction/core-entities';
42
+ import { CommunicationEngine } from '@memberjunction/communication-engine';
43
+ import type { MagicLinkConfig } from '../config.js';
44
+ import { MagicLinkKeyManager } from '../auth/magicLink/MagicLinkKeys.js';
45
+ import { MagicLinkService, IsMagicLinkRedirectUrlAllowed } from '../auth/magicLink/MagicLinkService.js';
46
+ import { BuildSessionClaims, HashToken } from '../auth/magicLink/magicLinkCore.js';
47
+
48
+ interface StoredInviteState {
49
+ ID: string;
50
+ TokenHash: string;
51
+ Email: string | null;
52
+ ApplicationID: string;
53
+ RoleID: string;
54
+ ExpiresAt: Date;
55
+ MaxUses: number;
56
+ UseCount: number;
57
+ CreatedByUserID: string;
58
+ Status: MJMagicLinkInviteEntity['Status'];
59
+ }
60
+
61
+ function makeConfig(overrides: Partial<MagicLinkConfig> = {}): MagicLinkConfig {
62
+ return {
63
+ enabled: true,
64
+ audience: 'mj-magic-link',
65
+ restrictedRoleName: 'External Guest',
66
+ grantableRoleNames: ['External Guest'],
67
+ inviteIssuerRoleNames: ['Invite Issuer'],
68
+ defaultExpiresInHours: 24,
69
+ sessionTokenTtlHours: 8,
70
+ explorerUrl: 'https://explorer.example.com',
71
+ communicationProvider: 'SendGrid',
72
+ fromAddress: 'noreply@example.com',
73
+ provisioningGuard: 'block',
74
+ rateLimitWindowMs: 60_000,
75
+ redeemRateLimitMax: 20,
76
+ createRateLimitMax: 20,
77
+ ...overrides,
78
+ };
79
+ }
80
+
81
+ function makeFakeProvider(storedInvites: Map<string, StoredInviteState>): IMetadataProvider {
82
+ let seq = 1;
83
+ const provider = {
84
+ Roles: [{ ID: 'role-guest', Name: 'External Guest' }],
85
+ Applications: [{ ID: 'app-forms', Name: 'Public Forms', Path: 'public-forms' }],
86
+ GetEntityObject: async (entityName: string) => {
87
+ if (entityName === 'MJ: Magic Link Invites') {
88
+ const record: StoredInviteState & {
89
+ NewRecord: () => void;
90
+ Load: (id: string) => Promise<boolean>;
91
+ Save: () => Promise<boolean>;
92
+ LatestResult?: { CompleteMessage: string };
93
+ } = {
94
+ ID: '',
95
+ TokenHash: '',
96
+ Email: null,
97
+ ApplicationID: '',
98
+ RoleID: '',
99
+ ExpiresAt: new Date(),
100
+ MaxUses: 1,
101
+ UseCount: 0,
102
+ CreatedByUserID: '',
103
+ Status: 'Active',
104
+ NewRecord: () => {
105
+ record.ID = `invite-${seq++}`;
106
+ },
107
+ Load: async (id: string) => {
108
+ const existing = storedInvites.get(id);
109
+ if (!existing) {
110
+ return false;
111
+ }
112
+ Object.assign(record, existing);
113
+ return true;
114
+ },
115
+ Save: async () => {
116
+ storedInvites.set(record.ID, {
117
+ ID: record.ID,
118
+ TokenHash: record.TokenHash,
119
+ Email: record.Email,
120
+ ApplicationID: record.ApplicationID,
121
+ RoleID: record.RoleID,
122
+ ExpiresAt: record.ExpiresAt,
123
+ MaxUses: record.MaxUses,
124
+ UseCount: record.UseCount,
125
+ CreatedByUserID: record.CreatedByUserID,
126
+ Status: record.Status,
127
+ });
128
+ return true;
129
+ },
130
+ };
131
+ return record;
132
+ }
133
+ return {
134
+ NewRecord: () => undefined,
135
+ Save: async () => true,
136
+ };
137
+ },
138
+ };
139
+ return provider as object as IMetadataProvider;
140
+ }
141
+
142
+ describe('MagicLinkService programmatic extension capabilities (Item 3.6)', () => {
143
+ const ownerUser = {
144
+ ID: 'user-owner',
145
+ Email: 'owner@example.com',
146
+ Type: 'Owner',
147
+ UserRoles: [],
148
+ } as object as UserInfo;
149
+
150
+ const regularUser = {
151
+ ID: 'user-guest',
152
+ Email: 'guest@example.com',
153
+ Type: 'User',
154
+ UserRoles: [{ Role: 'External Guest' }],
155
+ } as object as UserInfo;
156
+
157
+ beforeEach(() => {
158
+ mockCorsConfig.allowedOrigins = ['https://portal.example.com', 'https://forms.example.com'];
159
+ MagicLinkKeyManager.Instance.Initialize();
160
+ vi.clearAllMocks();
161
+ });
162
+
163
+ describe('IsMagicLinkRedirectUrlAllowed', () => {
164
+ it('allows matching http(s) origins and rejects non-http schemes, unlisted origins, and wildcard *', () => {
165
+ const allowed = ['https://explorer.example.com', 'https://forms.example.com:8443'];
166
+ expect(IsMagicLinkRedirectUrlAllowed('https://forms.example.com:8443/respond/abc?step=2', allowed)).toBe(true);
167
+ expect(IsMagicLinkRedirectUrlAllowed('https://evil.example.com/phish', allowed)).toBe(false);
168
+ expect(IsMagicLinkRedirectUrlAllowed('javascript:alert(1)', allowed)).toBe(false);
169
+ expect(IsMagicLinkRedirectUrlAllowed('https://anywhere.example/cb', ['*'])).toBe(false);
170
+ expect(IsMagicLinkRedirectUrlAllowed('https://anywhere.example.org/ok', ['*', ''])).toBe(false);
171
+ });
172
+ });
173
+
174
+ describe('CreateInvite RedirectUrl & SendEmail controls', () => {
175
+ it('binds a permitted RedirectUrl via RS256 and skips default email when SendEmail=false', async () => {
176
+ const invites = new Map<string, StoredInviteState>();
177
+ const provider = makeFakeProvider(invites);
178
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
179
+
180
+ const res = await service.CreateInvite(
181
+ {
182
+ email: 'respondent@example.com',
183
+ ApplicationId: 'app-forms',
184
+ RedirectUrl: 'https://forms.example.com/survey/42',
185
+ SendEmail: false,
186
+ },
187
+ ownerUser,
188
+ provider,
189
+ );
190
+
191
+ expect(res.success).toBe(true);
192
+ expect(res.emailSent).toBe(false);
193
+ expect(res.rawToken).toBeDefined();
194
+ expect(CommunicationEngine.Instance.SendSingleMessage).not.toHaveBeenCalled();
195
+
196
+ const url = new URL(res.redemptionUrl!);
197
+ const rt = url.searchParams.get('rt') ?? undefined;
198
+ expect(rt).toBeTruthy();
199
+
200
+ // Validated against matching rawToken
201
+ expect(service.ResolveVerifiedRedirectUrl(res.rawToken!, rt)).toBe('https://forms.example.com/survey/42');
202
+ // Fails closed if an attacker swaps `rt` onto a different invite's token
203
+ expect(service.ResolveVerifiedRedirectUrl('mj_ml_0000000000000000000000000000000000000000000000000000000000000000', rt)).toBeUndefined();
204
+ });
205
+
206
+ it('rejects disallowed RedirectUrl origins with invalid_redirect_url', async () => {
207
+ const invites = new Map<string, StoredInviteState>();
208
+ const provider = makeFakeProvider(invites);
209
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
210
+
211
+ const res = await service.CreateInvite(
212
+ {
213
+ email: 'respondent@example.com',
214
+ ApplicationId: 'app-forms',
215
+ RedirectUrl: 'https://attacker.invalid/steal',
216
+ },
217
+ ownerUser,
218
+ provider,
219
+ );
220
+
221
+ expect(res.success).toBe(false);
222
+ expect(res.errorCode).toBe('invalid_redirect_url');
223
+ expect(invites.size).toBe(0);
224
+ });
225
+
226
+ it('ignores wildcard * in cors.allowedOrigins while honoring explicit magicLink.allowedRedirectOrigins', async () => {
227
+ mockCorsConfig.allowedOrigins = ['*'];
228
+ const invites = new Map<string, StoredInviteState>();
229
+ const provider = makeFakeProvider(invites);
230
+ const service = new MagicLinkService(
231
+ 'https://api.example.com',
232
+ makeConfig({
233
+ explorerUrl: 'https://explorer.example.com',
234
+ allowedRedirectOrigins: ['https://surveys.example.com'],
235
+ }),
236
+ );
237
+
238
+ // Wildcard CORS does NOT open up arbitrary redirect origins
239
+ const rejected = await service.CreateInvite(
240
+ {
241
+ email: 'respondent@example.com',
242
+ ApplicationId: 'app-forms',
243
+ RedirectUrl: 'https://anywhere.example/cb',
244
+ SendEmail: false,
245
+ },
246
+ ownerUser,
247
+ provider,
248
+ );
249
+ expect(rejected.success).toBe(false);
250
+ expect(rejected.errorCode).toBe('invalid_redirect_url');
251
+
252
+ // Explicit magicLink.allowedRedirectOrigins works cleanly under wildcard CORS
253
+ const permitted = await service.CreateInvite(
254
+ {
255
+ email: 'respondent@example.com',
256
+ ApplicationId: 'app-forms',
257
+ RedirectUrl: 'https://surveys.example.com/respond/99',
258
+ SendEmail: false,
259
+ },
260
+ ownerUser,
261
+ provider,
262
+ );
263
+ expect(permitted.success).toBe(true);
264
+ const url = new URL(permitted.redemptionUrl!);
265
+ const rt = url.searchParams.get('rt') ?? undefined;
266
+ expect(service.ResolveVerifiedRedirectUrl(permitted.rawToken!, rt)).toBe('https://surveys.example.com/respond/99');
267
+ });
268
+ });
269
+
270
+ describe('RevokeInvite & UpdateInviteExpiry', () => {
271
+ it('revokes an active invite and enforces issuer authorization', async () => {
272
+ const invites = new Map<string, StoredInviteState>();
273
+ const provider = makeFakeProvider(invites);
274
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
275
+
276
+ const created = await service.CreateInvite(
277
+ { email: 'person@example.com', ApplicationId: 'app-forms', SendEmail: false },
278
+ ownerUser,
279
+ provider,
280
+ );
281
+ expect(created.success).toBe(true);
282
+
283
+ const denied = await service.RevokeInvite(created.inviteId!, regularUser, provider);
284
+ expect(denied.success).toBe(false);
285
+ expect(denied.errorCode).toBe('forbidden');
286
+
287
+ const revoked = await service.RevokeInvite(created.inviteId!, ownerUser, provider);
288
+ expect(revoked.success).toBe(true);
289
+ expect(revoked.status).toBe('Revoked');
290
+ expect(invites.get(created.inviteId!)?.Status).toBe('Revoked');
291
+
292
+ // Cannot extend a revoked invite
293
+ const extendRevoked = await service.UpdateInviteExpiry(
294
+ created.inviteId!,
295
+ new Date(Date.now() + 72 * 3600 * 1000),
296
+ ownerUser,
297
+ provider,
298
+ );
299
+ expect(extendRevoked.success).toBe(false);
300
+ expect(extendRevoked.errorCode).toBe('invalid_state');
301
+ });
302
+
303
+ it('updates expiry on an active or expired invite and re-activates expired status when extended', async () => {
304
+ const invites = new Map<string, StoredInviteState>();
305
+ invites.set('inv-expired', {
306
+ ID: 'inv-expired',
307
+ TokenHash: HashToken('mj_ml_sample'),
308
+ Email: 'late@example.com',
309
+ ApplicationID: 'app-forms',
310
+ RoleID: 'role-guest',
311
+ ExpiresAt: new Date(Date.now() - 3600_000),
312
+ MaxUses: 1,
313
+ UseCount: 0,
314
+ CreatedByUserID: ownerUser.ID,
315
+ Status: 'Expired',
316
+ });
317
+ const provider = makeFakeProvider(invites);
318
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
319
+
320
+ const newExpiry = new Date(Date.now() + 48 * 3600 * 1000);
321
+ const updated = await service.UpdateInviteExpiry('inv-expired', newExpiry, ownerUser, provider);
322
+ expect(updated.success).toBe(true);
323
+ expect(updated.status).toBe('Active');
324
+ expect(updated.expiresAt).toBe(newExpiry.toISOString());
325
+ });
326
+
327
+ it('keeps &rt= redirect binding valid after UpdateInviteExpiry extends an invite past its initial expiry, and guards UseCount/past-expiry transitions', async () => {
328
+ const invites = new Map<string, StoredInviteState>();
329
+ const provider = makeFakeProvider(invites);
330
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
331
+
332
+ const rawToken = 'mj_ml_1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff';
333
+ const tokenHash = HashToken(rawToken);
334
+ const pastExpiry = new Date(Date.now() - 3600_000);
335
+ // Mint redirect binding with a past ExpiresAt epoch
336
+ const rt = MagicLinkKeyManager.Instance.SignRedirectBinding(
337
+ tokenHash,
338
+ 'https://forms.example.com/survey/extended',
339
+ Math.floor(pastExpiry.getTime() / 1000),
340
+ );
341
+
342
+ invites.set('inv-extend-rt', {
343
+ ID: 'inv-extend-rt',
344
+ TokenHash: tokenHash,
345
+ Email: 'respondent@example.com',
346
+ ApplicationID: 'app-forms',
347
+ RoleID: 'role-guest',
348
+ ExpiresAt: pastExpiry,
349
+ MaxUses: 1,
350
+ UseCount: 0,
351
+ CreatedByUserID: ownerUser.ID,
352
+ Status: 'Expired',
353
+ });
354
+
355
+ // Extend invite into the future without re-issuing the URL
356
+ const extendedExpiry = new Date(Date.now() + 24 * 3600_000);
357
+ const extended = await service.UpdateInviteExpiry('inv-extend-rt', extendedExpiry, ownerUser, provider);
358
+ expect(extended.success).toBe(true);
359
+ expect(extended.status).toBe('Active');
360
+
361
+ // Original &rt= binding still resolves the allowlisted RedirectUrl
362
+ expect(service.ResolveVerifiedRedirectUrl(rawToken, rt)).toBe('https://forms.example.com/survey/extended');
363
+
364
+ // Shortening an Active invite to <= now transitions Status to Expired
365
+ const shortened = await service.UpdateInviteExpiry('inv-extend-rt', new Date(Date.now() - 1000), ownerUser, provider);
366
+ expect(shortened.success).toBe(true);
367
+ expect(shortened.status).toBe('Expired');
368
+
369
+ // An Expired invite whose UseCount >= MaxUses is NOT reactivated to Active
370
+ invites.set('inv-exhausted-expired', {
371
+ ID: 'inv-exhausted-expired',
372
+ TokenHash: HashToken('mj_ml_exhausted'),
373
+ Email: 'used@example.com',
374
+ ApplicationID: 'app-forms',
375
+ RoleID: 'role-guest',
376
+ ExpiresAt: pastExpiry,
377
+ MaxUses: 1,
378
+ UseCount: 1,
379
+ CreatedByUserID: ownerUser.ID,
380
+ Status: 'Expired',
381
+ });
382
+ const notReactivated = await service.UpdateInviteExpiry('inv-exhausted-expired', extendedExpiry, ownerUser, provider);
383
+ expect(notReactivated.success).toBe(true);
384
+ expect(notReactivated.status).toBe('Expired');
385
+ });
386
+ });
387
+
388
+ describe('VerifySessionToken', () => {
389
+ it('verifies a signed magic-link session JWT and rejects wrong audience or issuer', () => {
390
+ const service = new MagicLinkService('https://api.example.com', makeConfig());
391
+ const nowSeconds = Math.floor(Date.now() / 1000);
392
+ const claims = BuildSessionClaims({
393
+ issuer: 'https://api.example.com',
394
+ audience: 'mj-magic-link',
395
+ inviteId: 'inv-123',
396
+ email: 'person@example.com',
397
+ applicationId: 'app-forms',
398
+ roleName: 'External Guest',
399
+ nowSeconds,
400
+ ttlSeconds: 3600,
401
+ });
402
+ const jwtToken = MagicLinkKeyManager.Instance.Sign(claims);
403
+
404
+ const verified = service.VerifySessionToken(jwtToken);
405
+ expect(verified).not.toBeNull();
406
+ expect(verified?.email).toBe('person@example.com');
407
+ expect(verified?.mj_app_id).toBe('app-forms');
408
+
409
+ const wrongIssuerService = new MagicLinkService('https://other-api.example.com', makeConfig());
410
+ expect(wrongIssuerService.VerifySessionToken(jwtToken)).toBeNull();
411
+ });
412
+ });
413
+ });
@@ -0,0 +1,82 @@
1
+ /**
2
+ * `conversationDetailReconciler` settings: the reconciler always runs its startup pass, and repeats
3
+ * on the configured interval only while enabled. Disabled, an idle server sends it no periodic query.
4
+ */
5
+ import 'reflect-metadata';
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import type { UserInfo } from '@memberjunction/core';
8
+
9
+ const { mockRunView } = vi.hoisted(() => ({ mockRunView: vi.fn() }));
10
+
11
+ vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
12
+ UserCache: { Instance: { Users: [] } },
13
+ }));
14
+
15
+ vi.mock('@memberjunction/core', () => {
16
+ class MockRunView {
17
+ public static FromMetadataProvider(): MockRunView {
18
+ return new MockRunView();
19
+ }
20
+ public async RunView(...args: unknown[]): Promise<unknown> {
21
+ return mockRunView(...args);
22
+ }
23
+ }
24
+ return { LogError: vi.fn(), LogStatus: vi.fn(), RunView: MockRunView };
25
+ });
26
+
27
+ import { StartOrphanedConversationDetailReconciler } from '../generic/OrphanedConversationDetailReconciler.js';
28
+
29
+ const USER = { ID: 'U1' } as unknown as UserInfo;
30
+ const PROVIDER = {
31
+ EntityByName: (name: string) =>
32
+ name === 'MJ: AI Agent Runs' ? { SchemaName: '__mj', BaseView: 'vwAIAgentRuns' } : undefined,
33
+ QuoteIdentifier: (name: string) => `[${name}]`,
34
+ QuoteSchemaAndView: (schema: string, view: string) => `[${schema}].[${view}]`,
35
+ } as never;
36
+ const HOUR = 60 * 60_000;
37
+
38
+ /** Passes run so far: each pass starts with one read of in-progress conversation details. */
39
+ const passes = (): number => mockRunView.mock.calls.length;
40
+
41
+ describe('StartOrphanedConversationDetailReconciler', () => {
42
+ beforeEach(() => {
43
+ vi.useFakeTimers();
44
+ mockRunView.mockReset();
45
+ mockRunView.mockResolvedValue({ Success: true, Results: [] });
46
+ });
47
+ afterEach(() => {
48
+ vi.clearAllTimers();
49
+ vi.useRealTimers();
50
+ });
51
+
52
+ it('enabled: runs a pass now, then one every interval', async () => {
53
+ const timer = StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: true, sweepIntervalMs: 60_000 });
54
+ await vi.advanceTimersByTimeAsync(5 * 60_000);
55
+
56
+ expect(timer).not.toBeNull();
57
+ expect(passes()).toBe(6);
58
+ });
59
+
60
+ it('disabled: runs the startup pass only, and schedules nothing', async () => {
61
+ const timer = StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: false, sweepIntervalMs: 60_000 });
62
+ await vi.advanceTimersByTimeAsync(HOUR);
63
+
64
+ expect(passes()).toBe(1);
65
+ expect(timer).toBeNull();
66
+ });
67
+
68
+ it('with no settings keeps the shipped 5-minute repeat', async () => {
69
+ StartOrphanedConversationDetailReconciler(PROVIDER, USER);
70
+ await vi.advanceTimersByTimeAsync(HOUR);
71
+
72
+ expect(passes()).toBe(13);
73
+ });
74
+
75
+ it('a failing pass is logged, not thrown, and the next pass still runs', async () => {
76
+ mockRunView.mockRejectedValueOnce(new Error('db down'));
77
+ StartOrphanedConversationDetailReconciler(PROVIDER, USER, { enabled: true, sweepIntervalMs: 60_000 });
78
+ await vi.advanceTimersByTimeAsync(60_000);
79
+
80
+ expect(passes()).toBe(2);
81
+ });
82
+ });
@@ -36,4 +36,18 @@ describe('LoadOwnedRealtimeSession', () => {
36
36
  await expect(LoadOwnedRealtimeSession(SESSION, STRANGER, providerWith({ UserID: OWNER.ID })))
37
37
  .rejects.toThrow('Not authorized: you do not own this realtime session');
38
38
  });
39
+
40
+ it('skips loading MJ: Conversations when the caller is a standard named user without a scope identity', async () => {
41
+ const getEntityObject = vi.fn(async (name: string) => ({
42
+ UserID: OWNER.ID,
43
+ ConversationID: '22222222-2222-4222-8222-222222222222',
44
+ Load: vi.fn(async () => name === 'MJ: AI Agent Sessions'),
45
+ }));
46
+ const prov = { GetEntityObject: getEntityObject } as unknown as IMetadataProvider;
47
+
48
+ await LoadOwnedRealtimeSession(SESSION, OWNER, prov);
49
+
50
+ expect(getEntityObject).toHaveBeenCalledTimes(1);
51
+ expect(getEntityObject).toHaveBeenCalledWith('MJ: AI Agent Sessions', OWNER);
52
+ });
39
53
  });
@@ -25,6 +25,7 @@ import {
25
25
  CreateRealtimeRecordingRouter, FINAL_TERMINAL_RECHECK_MS, REALTIME_RECORDING_MOUNT_PATH, type RealtimeRecordingRouterDeps,
26
26
  } from '../rest/RealtimeRecordingHandler.js';
27
27
  import { RealtimeSessionNotFoundError, RealtimeSessionOwnershipError } from '../realtimeSessions/ownedRealtimeSession.js';
28
+ import { AuthorizeSessionAccess } from '../realtimeSessions/sessionAccess.js';
28
29
  import { RecordingAssemblyScheduler, type IRecordingAssemblyScheduler } from '../realtimeSessions/RecordingAssemblyScheduler.js';
29
30
 
30
31
  const SESSION = '11111111-1111-4111-8111-111111111111';
@@ -274,9 +275,13 @@ describe('realtime recording REST routes', () => {
274
275
  };
275
276
  return id ? { ContextUser: user as unknown as UserInfo, Provider: {} as unknown as DatabaseProviderBase } : null;
276
277
  },
277
- LoadConversationScope: async session => {
278
+ // The real scope rule over this test's conversation rows (no database behind them).
279
+ AuthorizeSession: async (session, contextUser) => {
278
280
  const row = ledger.Sessions.get(session.ID);
279
- return { ExternalID: row?.ConversationExternalID ?? null, Readable: row?.ConversationExternalID !== undefined };
281
+ return AuthorizeSessionAccess(
282
+ { SessionUserID: session.UserID, ConversationExternalID: row?.ConversationExternalID ?? null, ConversationReadable: row?.ConversationExternalID !== undefined },
283
+ contextUser,
284
+ );
280
285
  },
281
286
  LoadOwnedSession: async (agentSessionId, contextUser) => {
282
287
  const row = ledger.Sessions.get(agentSessionId);
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
3
- import { AuthorizeSessionAccess, AuthorizeConversationTurn, AuthorizeAnonymousRoomSessions, LoadSessionForAccess, LoadConversationScope } from '../../realtimeSessions/sessionAccess.js';
3
+ import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
4
+ import { AuthorizeSessionAccess, AuthorizeConversationTurn, AuthorizeAnonymousRoomSessions, LoadSessionForAccess, LoadSessionConversationScope, LoadConversationScope, AuthorizeLoadedSession } from '../../realtimeSessions/sessionAccess.js';
4
5
 
5
6
  const OWNER = 'AAAAAAAA-0000-4000-8000-000000000001';
6
7
  const OTHER = 'AAAAAAAA-0000-4000-8000-000000000002';
@@ -190,6 +191,25 @@ describe('LoadSessionForAccess', () => {
190
191
  });
191
192
  });
192
193
 
194
+ describe('LoadSessionConversationScope', () => {
195
+ const sessionWithConversation = { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID } as unknown as MJAIAgentSessionEntity;
196
+
197
+ it('reads the conversation for an unscoped named user too, so its result is the conversation as that user sees it', async () => {
198
+ const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
199
+ const scope = await LoadSessionConversationScope(sessionWithConversation, USER, provider);
200
+ expect(scope).toEqual({ ExternalID: 'scope-1', Readable: true });
201
+ expect(provider.GetEntityObject).toHaveBeenCalledTimes(1);
202
+ });
203
+
204
+ it('reads the conversation when the caller carries a scope identity', async () => {
205
+ const scopedUser = { ID: OWNER, MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo;
206
+ const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
207
+ const scope = await LoadSessionConversationScope(sessionWithConversation, scopedUser, provider);
208
+ expect(scope).toEqual({ ExternalID: 'scope-1', Readable: true });
209
+ expect(provider.GetEntityObject).toHaveBeenCalledTimes(1);
210
+ });
211
+ });
212
+
193
213
  describe('LoadConversationScope (a conversation addressed directly, e.g. by a text agent turn)', () => {
194
214
  it('reports the owner and scope id of a readable conversation', async () => {
195
215
  const scope = await LoadConversationScope(CONVERSATION_ID, USER, fakeProvider({ conversation: { UserID: OWNER, ExternalID: 'scope-1' } }));
@@ -266,3 +286,61 @@ describe('AuthorizeAnonymousRoomSessions (who of the anonymous visitors owns a L
266
286
  });
267
287
  });
268
288
 
289
+
290
+ describe('AuthorizeLoadedSession', () => {
291
+ const principals: Array<[string, UserInfo]> = [
292
+ ['named owner, no scope', { ID: OWNER } as UserInfo],
293
+ ['named owner, scope-1', { ID: OWNER, MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo],
294
+ ['named owner, scope-2', { ID: OWNER, MagicLinkScope: { ResourceID: 'scope-2' } } as unknown as UserInfo],
295
+ ['anonymous, scope-1', { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-1', MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo],
296
+ ['anonymous, scope-2', { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-2', MagicLinkScope: { ResourceID: 'scope-2' } } as unknown as UserInfo],
297
+ ['anonymous, visitor id only', { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-1' } as unknown as UserInfo],
298
+ ['anonymous, no scope', { ID: OWNER, IsMagicLinkAnonymous: true } as unknown as UserInfo],
299
+ ['named non-owner', { ID: OTHER } as UserInfo],
300
+ ];
301
+ const conversations: Array<[string, { conversationId: string | null; rows: Parameters<typeof fakeProvider>[0] }]> = [
302
+ ['no conversation', { conversationId: null, rows: {} }],
303
+ ['missing or refused', { conversationId: CONVERSATION_ID, rows: { conversation: null } }],
304
+ ['read fails', { conversationId: CONVERSATION_ID, rows: { conversation: {}, conversationThrows: true } }],
305
+ ['unstamped', { conversationId: CONVERSATION_ID, rows: { conversation: { ExternalID: null } } }],
306
+ ['stamped scope-1', { conversationId: CONVERSATION_ID, rows: { conversation: { ExternalID: 'scope-1' } } }],
307
+ ['stamped scope-2', { conversationId: CONVERSATION_ID, rows: { conversation: { ExternalID: 'scope-2' } } }],
308
+ ];
309
+
310
+ for (const [principalName, principal] of principals) {
311
+ for (const [conversationName, { conversationId, rows }] of conversations) {
312
+ it(`decides as if it always read the conversation: ${principalName} × ${conversationName}`, async () => {
313
+ const session = { ID: SESSION_ID, UserID: OWNER, ConversationID: conversationId } as unknown as MJAIAgentSessionEntity;
314
+ const scope = await LoadConversationScope(conversationId, principal, fakeProvider(rows));
315
+ const alwaysRead = AuthorizeSessionAccess(
316
+ { SessionUserID: session.UserID, ConversationExternalID: scope.ExternalID, ConversationReadable: scope.Readable },
317
+ principal,
318
+ );
319
+ expect(await AuthorizeLoadedSession(session, principal, fakeProvider(rows))).toEqual(alwaysRead);
320
+ });
321
+ }
322
+ }
323
+
324
+ const sessionWithConversation = { ID: SESSION_ID, UserID: OWNER, ConversationID: CONVERSATION_ID } as unknown as MJAIAgentSessionEntity;
325
+ const conversationReads = (provider: IMetadataProvider) =>
326
+ vi.mocked(provider.GetEntityObject).mock.calls.filter(([name]) => name === 'MJ: Conversations').length;
327
+
328
+ it('does not read the conversation for a named owner without a scope, the usual signed-in caller', async () => {
329
+ const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
330
+ expect(await AuthorizeLoadedSession(sessionWithConversation, USER, provider)).toEqual({ Allowed: true });
331
+ expect(conversationReads(provider)).toBe(0);
332
+ });
333
+
334
+ it('does not read the conversation for a caller who does not own the session', async () => {
335
+ const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
336
+ expect(await AuthorizeLoadedSession(sessionWithConversation, { ID: OTHER } as UserInfo, provider)).toEqual({ Allowed: false, Reason: 'not_owner' });
337
+ expect(conversationReads(provider)).toBe(0);
338
+ });
339
+
340
+ it('reads the conversation once for a caller with a scope identity', async () => {
341
+ const provider = fakeProvider({ conversation: { ExternalID: 'scope-1' } });
342
+ const guest = { ID: OWNER, IsMagicLinkAnonymous: true, AnonymousVisitorID: 'scope-1', MagicLinkScope: { ResourceID: 'scope-1' } } as unknown as UserInfo;
343
+ expect(await AuthorizeLoadedSession(sessionWithConversation, guest, provider)).toEqual({ Allowed: true });
344
+ expect(conversationReads(provider)).toBe(1);
345
+ });
346
+ });