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

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 (407) 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/config.d.ts +906 -8
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +59 -0
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +2 -0
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +35 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/coreRoutes.d.ts +3 -1
  76. package/dist/coreRoutes.d.ts.map +1 -1
  77. package/dist/coreRoutes.js +6 -4
  78. package/dist/coreRoutes.js.map +1 -1
  79. package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
  80. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  81. package/dist/generated/graphql-schemas/__mj.js +1163 -6
  82. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  83. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  85. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  87. package/dist/generic/ResolverBase.d.ts +50 -5
  88. package/dist/generic/ResolverBase.d.ts.map +1 -1
  89. package/dist/generic/ResolverBase.js +321 -132
  90. package/dist/generic/ResolverBase.js.map +1 -1
  91. package/dist/generic/RunViewResolver.d.ts +4 -0
  92. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  93. package/dist/generic/RunViewResolver.js +47 -7
  94. package/dist/generic/RunViewResolver.js.map +1 -1
  95. package/dist/index.d.ts +1 -0
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +157 -35
  98. package/dist/index.js.map +1 -1
  99. package/dist/integration/ConnectionPause.d.ts +121 -0
  100. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  101. package/dist/integration/ConnectionPause.js +183 -0
  102. package/dist/integration/ConnectionPause.js.map +1 -0
  103. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  104. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  105. package/dist/integration/CustomColumnPromoter.js +87 -46
  106. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  107. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  108. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  109. package/dist/integration/EntityMapLifecycle.js +199 -5
  110. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  111. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  112. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  113. package/dist/integration/InactiveRowWarnings.js +29 -0
  114. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  115. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  116. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  117. package/dist/integration/ObjectNamesByID.js +87 -0
  118. package/dist/integration/ObjectNamesByID.js.map +1 -0
  119. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  120. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  121. package/dist/integration/PostRestartIntrospect.js +27 -0
  122. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  123. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  124. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  125. package/dist/integration/RSUPostRestartProgress.js +323 -0
  126. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  127. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  128. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  129. package/dist/integration/RSUProgressBridge.js +84 -1
  130. package/dist/integration/RSUProgressBridge.js.map +1 -1
  131. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  132. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  133. package/dist/integration/RunArtifactAuthorization.js +45 -0
  134. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  135. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  136. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  137. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  138. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  139. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  140. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  141. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  142. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  143. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  144. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  145. package/dist/integration/TargetConfigGuard.js +40 -0
  146. package/dist/integration/TargetConfigGuard.js.map +1 -0
  147. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  148. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  149. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  150. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  151. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  152. package/dist/realtimeSessions/sessionAccess.js +22 -0
  153. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  154. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  155. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  156. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  157. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  158. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  159. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  160. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  161. package/dist/realtimeWidget/widgetCore.js +4 -7
  162. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  163. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  164. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  165. package/dist/requestOrigin.d.ts +22 -0
  166. package/dist/requestOrigin.d.ts.map +1 -0
  167. package/dist/requestOrigin.js +60 -0
  168. package/dist/requestOrigin.js.map +1 -0
  169. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  170. package/dist/resolvers/AgentSessionResolver.js +6 -2
  171. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  172. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  173. package/dist/resolvers/AutotagPipelineResolver.js +2 -1
  174. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  175. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  176. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  178. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  179. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  180. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  181. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  182. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  183. package/dist/resolvers/GetDataResolver.js +34 -13
  184. package/dist/resolvers/GetDataResolver.js.map +1 -1
  185. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  186. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  187. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  188. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  189. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  190. package/dist/resolvers/MCPResolver.js +13 -2
  191. package/dist/resolvers/MCPResolver.js.map +1 -1
  192. package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
  193. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  194. package/dist/resolvers/QuerySystemUserResolver.js +174 -87
  195. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  196. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  197. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  198. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  199. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  200. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  201. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  202. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  203. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  204. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  205. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  206. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  207. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  208. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  209. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  210. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  211. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  212. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  213. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  214. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  215. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  216. package/dist/resolvers/RunTemplateResolver.js +3 -1
  217. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  218. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  219. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  220. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  221. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  222. package/dist/rest/MediaAccessKeys.js +6 -2
  223. package/dist/rest/MediaAccessKeys.js.map +1 -1
  224. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.js +11 -5
  226. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  227. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  228. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  229. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  230. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  231. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  232. package/dist/rest/ViewOperationsHandler.js +2 -1
  233. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  234. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  235. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  236. package/dist/rest/setupRESTEndpoints.js +11 -2
  237. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  238. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  239. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  240. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  241. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  242. package/dist/services/TaskGraphActionRunner.js +2 -26
  243. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  244. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  245. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  246. package/dist/services/TaskGraphAgentRunner.js +38 -0
  247. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  248. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  249. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  250. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  251. package/dist/services/WorkQueueHostService.d.ts +2 -1
  252. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  253. package/dist/services/WorkQueueHostService.js +19 -4
  254. package/dist/services/WorkQueueHostService.js.map +1 -1
  255. package/dist/services/workQueueConfig.d.ts +2 -2
  256. package/dist/services/workQueueConfig.js +5 -5
  257. package/dist/services/workQueueConfig.js.map +1 -1
  258. package/dist/sharedCache.js +1 -1
  259. package/dist/sharedCache.js.map +1 -1
  260. package/dist/types.d.ts +17 -0
  261. package/dist/types.d.ts.map +1 -1
  262. package/dist/types.js.map +1 -1
  263. package/package.json +98 -98
  264. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  265. package/src/__tests__/ActiveOperations.test.ts +68 -0
  266. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  267. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  268. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  269. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  270. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  271. package/src/__tests__/ConnectionPause.test.ts +192 -0
  272. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  273. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  274. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  275. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  276. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  277. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  278. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  279. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  280. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  281. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  282. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  283. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  284. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  285. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  286. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  287. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  288. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  289. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  290. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  291. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  292. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  293. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  294. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  295. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  296. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  297. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  298. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  299. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  300. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  301. package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
  302. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  303. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  304. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  305. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  306. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  307. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  308. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  309. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  310. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  311. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  312. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  313. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  314. package/src/__tests__/SessionJanitor.test.ts +195 -4
  315. package/src/__tests__/SessionManager.test.ts +69 -0
  316. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  317. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  318. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  319. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  320. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  321. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  322. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  323. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  324. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  325. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  326. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  327. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  328. package/src/__tests__/requestOrigin.test.ts +104 -0
  329. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  330. package/src/__tests__/sessionDeadline.test.ts +51 -0
  331. package/src/__tests__/sharedCache.test.ts +11 -0
  332. package/src/__tests__/unifiedAuth.test.ts +73 -1
  333. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  334. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  335. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  336. package/src/agentSessions/SessionJanitor.ts +92 -42
  337. package/src/agentSessions/SessionManager.ts +38 -8
  338. package/src/agentSessions/conversationBrowsers.ts +143 -0
  339. package/src/agentSessions/sessionDeadline.ts +81 -0
  340. package/src/auth/APIKeyScopeAuth.ts +5 -4
  341. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  342. package/src/auth/loginEmailResolution.ts +67 -0
  343. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  344. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  345. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  346. package/src/auth/magicLink/index.ts +1 -1
  347. package/src/auth/magicLink/redeemLanding.ts +7 -4
  348. package/src/auth/magicLink/types.ts +28 -3
  349. package/src/auth/newUsers.ts +1 -0
  350. package/src/backgroundSweepConfigUnits.ts +213 -0
  351. package/src/config.ts +69 -0
  352. package/src/context.ts +39 -6
  353. package/src/coreRoutes.ts +14 -7
  354. package/src/generated/graphql-schemas/__mj.ts +822 -7
  355. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  356. package/src/generic/ResolverBase.ts +414 -143
  357. package/src/generic/RunViewResolver.ts +96 -49
  358. package/src/index.ts +201 -35
  359. package/src/integration/ConnectionPause.ts +222 -0
  360. package/src/integration/CustomColumnPromoter.ts +123 -53
  361. package/src/integration/EntityMapLifecycle.ts +229 -6
  362. package/src/integration/InactiveRowWarnings.ts +50 -0
  363. package/src/integration/ObjectNamesByID.ts +97 -0
  364. package/src/integration/PostRestartIntrospect.ts +49 -0
  365. package/src/integration/RSUPostRestartProgress.ts +359 -0
  366. package/src/integration/RSUProgressBridge.ts +85 -2
  367. package/src/integration/RunArtifactAuthorization.ts +65 -0
  368. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  369. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  370. package/src/integration/TargetConfigGuard.ts +71 -0
  371. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  372. package/src/realtimeSessions/sessionAccess.ts +30 -0
  373. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  374. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  375. package/src/realtimeWidget/widgetCore.ts +4 -8
  376. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  377. package/src/requestOrigin.ts +64 -0
  378. package/src/resolvers/AgentSessionResolver.ts +6 -2
  379. package/src/resolvers/AutotagPipelineResolver.ts +2 -1
  380. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  381. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  382. package/src/resolvers/GetDataResolver.ts +35 -13
  383. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  384. package/src/resolvers/MCPResolver.ts +15 -2
  385. package/src/resolvers/QuerySystemUserResolver.ts +197 -98
  386. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  387. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  388. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  389. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  390. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  391. package/src/resolvers/RunTemplateResolver.ts +3 -1
  392. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  393. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  394. package/src/rest/MediaAccessKeys.ts +6 -2
  395. package/src/rest/OAuthCallbackHandler.ts +13 -5
  396. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  397. package/src/rest/ViewOperationsHandler.ts +2 -1
  398. package/src/rest/setupRESTEndpoints.ts +11 -2
  399. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  400. package/src/services/TaskGraphActionRunner.ts +2 -23
  401. package/src/services/TaskGraphAgentRunner.ts +43 -0
  402. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  403. package/src/services/WorkQueueHostService.ts +26 -4
  404. package/src/services/workQueueConfig.ts +5 -5
  405. package/src/sharedCache.ts +1 -1
  406. package/src/types.ts +18 -0
  407. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -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
+ });
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { NormalizeOrigin, ReadRequestOrigin } from '../requestOrigin.js';
3
+ import { IsOriginAllowed, ParseAllowedOrigins } from '../realtimeWidget/widgetCore.js';
4
+
5
+ describe('NormalizeOrigin', () => {
6
+ it('trims, lowercases and strips trailing slashes', () => {
7
+ expect(NormalizeOrigin(' HTTPS://App.Example.COM// ')).toBe('https://app.example.com');
8
+ });
9
+
10
+ it('keeps an explicit port, default or not', () => {
11
+ expect(NormalizeOrigin('https://a.com:443')).toBe('https://a.com:443');
12
+ expect(NormalizeOrigin('http://localhost:4200/')).toBe('http://localhost:4200');
13
+ });
14
+
15
+ it('returns a value made only of slashes as empty', () => {
16
+ expect(NormalizeOrigin('///')).toBe('');
17
+ });
18
+
19
+ it('handles a long run of slashes that is not at the end in linear time', () => {
20
+ const hostile = 'https://a.com' + '/'.repeat(100_000) + 'x';
21
+ const started = Date.now();
22
+ expect(NormalizeOrigin(hostile)).toBe(hostile);
23
+ expect(ReadRequestOrigin(hostile)).toBeUndefined();
24
+ expect(Date.now() - started).toBeLessThan(1000);
25
+ });
26
+ });
27
+
28
+ describe('ReadRequestOrigin', () => {
29
+ describe('origin present', () => {
30
+ it('returns the normalized origin', () => {
31
+ expect(ReadRequestOrigin('https://Embed.Example.com')).toBe('https://embed.example.com');
32
+ });
33
+
34
+ it('strips a trailing slash', () => {
35
+ expect(ReadRequestOrigin('https://embed.example.com/')).toBe('https://embed.example.com');
36
+ });
37
+
38
+ it('accepts an IPv6 host with a port', () => {
39
+ expect(ReadRequestOrigin('http://[::1]:3000')).toBe('http://[::1]:3000');
40
+ });
41
+ });
42
+
43
+ describe('origin absent', () => {
44
+ it('returns undefined for a missing header', () => {
45
+ expect(ReadRequestOrigin(undefined)).toBeUndefined();
46
+ });
47
+
48
+ it('returns undefined for a blank header', () => {
49
+ expect(ReadRequestOrigin('')).toBeUndefined();
50
+ expect(ReadRequestOrigin(' ')).toBeUndefined();
51
+ });
52
+
53
+ it('returns undefined for a repeated header', () => {
54
+ expect(ReadRequestOrigin(['https://a.com', 'https://b.com'])).toBeUndefined();
55
+ });
56
+ });
57
+
58
+ describe('origin malformed', () => {
59
+ it('returns undefined for the opaque "null" origin', () => {
60
+ expect(ReadRequestOrigin('null')).toBeUndefined();
61
+ });
62
+
63
+ it('returns undefined for a value that is not a URL', () => {
64
+ expect(ReadRequestOrigin('not an origin')).toBeUndefined();
65
+ expect(ReadRequestOrigin('https://')).toBeUndefined();
66
+ });
67
+
68
+ it('returns undefined for a non-http(s) scheme', () => {
69
+ expect(ReadRequestOrigin('file://')).toBeUndefined();
70
+ expect(ReadRequestOrigin('chrome-extension://abcdef')).toBeUndefined();
71
+ });
72
+
73
+ it('returns undefined when a path, query, fragment or credentials are present', () => {
74
+ expect(ReadRequestOrigin('https://a.com/path')).toBeUndefined();
75
+ expect(ReadRequestOrigin('https://a.com?x=1')).toBeUndefined();
76
+ expect(ReadRequestOrigin('https://a.com#frag')).toBeUndefined();
77
+ expect(ReadRequestOrigin('https://user:pw@a.com')).toBeUndefined();
78
+ });
79
+ });
80
+
81
+ describe('default ports', () => {
82
+ it('keeps an explicit default port as sent', () => {
83
+ expect(ReadRequestOrigin('https://a.com:443')).toBe('https://a.com:443');
84
+ expect(ReadRequestOrigin('http://a.com:80')).toBe('http://a.com:80');
85
+ });
86
+
87
+ it('keeps a non-default port', () => {
88
+ expect(ReadRequestOrigin('https://a.com:8443')).toBe('https://a.com:8443');
89
+ });
90
+
91
+ it('does not add a default port when none was sent', () => {
92
+ expect(ReadRequestOrigin('https://a.com')).toBe('https://a.com');
93
+ });
94
+ });
95
+
96
+ describe('agreement with the widget allowlist', () => {
97
+ it('a request origin matches the allowlist entry it was normalized against', () => {
98
+ const allowed = ParseAllowedOrigins('["https://Embed.Example.com/"]');
99
+ const origin = ReadRequestOrigin('https://embed.example.com');
100
+ expect(origin).toBe(allowed[0]);
101
+ expect(IsOriginAllowed(origin, allowed)).toBe(true);
102
+ });
103
+ });
104
+ });
@@ -0,0 +1,120 @@
1
+ import 'reflect-metadata';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import type express from 'express';
4
+
5
+ const logStatusExCalls: Array<{ message: string; verboseOnly?: boolean }> = [];
6
+ const runViewSpy = vi.fn();
7
+
8
+ vi.mock('@memberjunction/core', async () => {
9
+ const actual = await vi.importActual<typeof import('@memberjunction/core')>('@memberjunction/core');
10
+ return {
11
+ ...actual,
12
+ LogStatusEx: vi.fn((entry: { message: string; verboseOnly?: boolean }) => {
13
+ logStatusExCalls.push(entry);
14
+ }),
15
+ RunView: class MockRunView {
16
+ public RunView = runViewSpy;
17
+ },
18
+ };
19
+ });
20
+
21
+ import {
22
+ DEFAULT_REST_API_OPTIONS,
23
+ REST_API_BASE_PATH,
24
+ SetupRESTEndpoints,
25
+ } from '../rest/setupRESTEndpoints.js';
26
+ import { OAuthCallbackHandler } from '../rest/OAuthCallbackHandler.js';
27
+ import type { UserInfo } from '@memberjunction/core';
28
+
29
+ function createSpyExpressApp(): { app: express.Application; useCalls: express.RequestHandler[][] } {
30
+ const useCalls: express.RequestHandler[][] = [];
31
+ const partialApp = {
32
+ use: vi.fn((...args: express.RequestHandler[]) => {
33
+ useCalls.push(args);
34
+ return partialApp as express.Application;
35
+ }),
36
+ };
37
+ return { app: partialApp as express.Application, useCalls };
38
+ }
39
+
40
+ describe('PR #6034 security hardening in MJServer', () => {
41
+ beforeEach(() => {
42
+ logStatusExCalls.length = 0;
43
+ runViewSpy.mockReset();
44
+ });
45
+
46
+ describe('SetupRESTEndpoints fail-closed default & auth warning', () => {
47
+ it('defaults DEFAULT_REST_API_OPTIONS.enabled to false and mounts nothing when called without options', () => {
48
+ expect(DEFAULT_REST_API_OPTIONS.enabled).toBe(false);
49
+
50
+ const { app, useCalls } = createSpyExpressApp();
51
+ SetupRESTEndpoints(app);
52
+
53
+ expect(useCalls).toHaveLength(0);
54
+ });
55
+
56
+ it('logs a non-verbose warning when enabled without a route-level authMiddleware', () => {
57
+ const { app, useCalls } = createSpyExpressApp();
58
+ SetupRESTEndpoints(app, { enabled: true });
59
+
60
+ expect(useCalls).toHaveLength(1);
61
+ expect(useCalls[0][0]).toBe(REST_API_BASE_PATH);
62
+ const warning = logStatusExCalls.find((c) =>
63
+ c.message.includes('WITHOUT a route-level auth middleware'),
64
+ );
65
+ expect(warning).toBeDefined();
66
+ expect(warning?.verboseOnly).toBeUndefined();
67
+ });
68
+
69
+ it('mounts authMiddleware and omits the unauthenticated warning when authMiddleware is supplied', () => {
70
+ const { app, useCalls } = createSpyExpressApp();
71
+ const authMw: express.RequestHandler = (_req, _res, next) => next();
72
+
73
+ SetupRESTEndpoints(app, { enabled: true }, authMw);
74
+
75
+ expect(useCalls).toHaveLength(1);
76
+ expect(useCalls[0]).toContain(authMw);
77
+ const warning = logStatusExCalls.find((c) =>
78
+ c.message.includes('WITHOUT a route-level auth middleware'),
79
+ );
80
+ expect(warning).toBeUndefined();
81
+ });
82
+ });
83
+
84
+ describe('OAuthCallbackHandler UUID validation on ExtraFilter IDs', () => {
85
+ it('refuses SQL injection connectionId before executing RunView', async () => {
86
+ const handler = new OAuthCallbackHandler({ baseUrl: 'https://api.example.com' });
87
+ const fakeUser = { ID: '00000000-0000-0000-0000-000000000001' } as UserInfo;
88
+
89
+ const loader = Reflect.get(handler, 'loadConnectionConfig') as (
90
+ connectionId: string,
91
+ contextUser: UserInfo,
92
+ ) => Promise<Record<string, string> | null>;
93
+
94
+ const injectedResult = await loader.call(handler, "' OR '1'='1", fakeUser);
95
+ expect(injectedResult).toBeNull();
96
+ expect(runViewSpy).not.toHaveBeenCalled();
97
+ });
98
+
99
+ it('refuses non-UUID MCPServerID returned from connection lookup before querying servers', async () => {
100
+ runViewSpy.mockResolvedValueOnce({
101
+ Success: true,
102
+ Results: [{ MCPServerID: "1234' UNION SELECT * FROM __mj.vwUsers --" }],
103
+ });
104
+
105
+ const handler = new OAuthCallbackHandler({ baseUrl: 'https://api.example.com' });
106
+ const fakeUser = { ID: '00000000-0000-0000-0000-000000000001' } as UserInfo;
107
+ const validConnId = '11111111-2222-3333-4444-555555555555';
108
+
109
+ const loader = Reflect.get(handler, 'loadConnectionConfig') as (
110
+ connectionId: string,
111
+ contextUser: UserInfo,
112
+ ) => Promise<Record<string, string> | null>;
113
+
114
+ const result = await loader.call(handler, validConnId, fakeUser);
115
+ expect(result).toBeNull();
116
+ // First query ran with validated connectionId; second query was blocked before executing
117
+ expect(runViewSpy).toHaveBeenCalledTimes(1);
118
+ });
119
+ });
120
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ExpiryCloseReason, ParseSessionDeadlineMs, VerifyTimeLimitClaim } from '../agentSessions/sessionDeadline.js';
3
+
4
+ const T = Date.parse('2026-10-29T12:00:00Z');
5
+ const MIN = 60_000;
6
+ const row = (lastActive: number | null, deadline?: number): { LastActiveAt: Date | string | null; Config_: string } => ({
7
+ LastActiveAt: lastActive === null ? null : new Date(lastActive),
8
+ Config_: deadline === undefined ? '{}' : JSON.stringify({ maxSessionDeadlineIso: new Date(deadline).toISOString() }),
9
+ });
10
+
11
+ describe('ExpiryCloseReason', () => {
12
+ it('deadline passed, recently active → TimeLimit', () => expect(ExpiryCloseReason(row(T - MIN, T - MIN), T, 15 * MIN)).toBe('TimeLimit'));
13
+ it('idle past threshold, no deadline → Idle', () => expect(ExpiryCloseReason(row(T - 16 * MIN), T, 15 * MIN)).toBe('Idle'));
14
+ it('both passed, idle moment earlier → Idle', () => expect(ExpiryCloseReason(row(T - 20 * MIN, T - 2 * MIN), T, 15 * MIN)).toBe('Idle'));
15
+ it('both passed, deadline moment earlier → TimeLimit', () => expect(ExpiryCloseReason(row(T - 16 * MIN, T - 5 * MIN), T, 15 * MIN)).toBe('TimeLimit'));
16
+ it('same instant → TimeLimit', () => expect(ExpiryCloseReason(row(T - 15 * MIN, T), T, 15 * MIN)).toBe('TimeLimit'));
17
+ it('nothing passed → null', () => expect(ExpiryCloseReason(row(T - MIN, T + MIN), T, 15 * MIN)).toBeNull());
18
+ it('a row without LastActiveAt is never Idle', () => {
19
+ expect(ExpiryCloseReason(row(null), T, 15 * MIN)).toBeNull();
20
+ expect(ExpiryCloseReason(row(null, T - MIN), T, 15 * MIN)).toBe('TimeLimit');
21
+ });
22
+ it('nowMs exactly at the deadline → TimeLimit', () => expect(ExpiryCloseReason(row(T - MIN, T), T, 15 * MIN)).toBe('TimeLimit'));
23
+ it('a string LastActiveAt past the threshold → Idle', () => {
24
+ const r = { ...row(null), LastActiveAt: new Date(T - 16 * MIN).toISOString() };
25
+ expect(ExpiryCloseReason(r, T, 15 * MIN)).toBe('Idle');
26
+ });
27
+ it('an invalid LastActiveAt string is never Idle, TimeLimit still works', () => {
28
+ expect(ExpiryCloseReason({ ...row(null), LastActiveAt: 'garbage' }, T, 15 * MIN)).toBeNull();
29
+ expect(ExpiryCloseReason({ ...row(null, T - MIN), LastActiveAt: 'garbage' }, T, 15 * MIN)).toBe('TimeLimit');
30
+ });
31
+ it('a non-finite closeThresholdMs is never Idle', () => expect(ExpiryCloseReason(row(T - 99 * MIN), T, Number.NaN)).toBeNull());
32
+ });
33
+
34
+ describe('ParseSessionDeadlineMs', () => {
35
+ it('reads an ISO deadline', () => expect(ParseSessionDeadlineMs(JSON.stringify({ maxSessionDeadlineIso: '2026-10-29T12:00:00Z' }))).toBe(T));
36
+ it('treats a malformed deadline as absent', () => {
37
+ expect(ParseSessionDeadlineMs(JSON.stringify({ maxSessionDeadlineIso: 'soon' }))).toBeNull();
38
+ expect(ParseSessionDeadlineMs(JSON.stringify({ maxSessionDeadlineIso: 42 }))).toBeNull();
39
+ expect(ParseSessionDeadlineMs('not json')).toBeNull();
40
+ expect(ParseSessionDeadlineMs(null)).toBeNull();
41
+ });
42
+ });
43
+
44
+ describe('VerifyTimeLimitClaim', () => {
45
+ it('deadline passed → TimeLimit', () => expect(VerifyTimeLimitClaim(row(T - MIN, T - MIN), T)).toBe('TimeLimit'));
46
+ it('50 s ahead (inside the skew) → TimeLimit', () => expect(VerifyTimeLimitClaim(row(T - MIN, T + 50_000), T)).toBe('TimeLimit'));
47
+ it('10 min ahead → Explicit', () => expect(VerifyTimeLimitClaim(row(T - MIN, T + 10 * MIN), T)).toBe('Explicit'));
48
+ it('no stored deadline → Explicit', () => expect(VerifyTimeLimitClaim(row(T - MIN), T)).toBe('Explicit'));
49
+ it('Config_ null → Explicit', () => expect(VerifyTimeLimitClaim({ LastActiveAt: new Date(T), Config_: null }, T)).toBe('Explicit'));
50
+ it('a long-idle session is never promoted to TimeLimit by idleness', () => expect(VerifyTimeLimitClaim(row(T - 999 * MIN), T)).toBe('Explicit'));
51
+ });
@@ -80,6 +80,17 @@ describe('shared cache event routing', () => {
80
80
  expect(publish).toHaveBeenCalledWith('CACHE_INVALIDATION', expect.objectContaining({ entityName: 'MJ: AI Models', action: 'set', primaryKeyValues: null }));
81
81
  });
82
82
 
83
+ it('dispatches a RunView row_delta to LocalCacheManager while normalizing browser CACHE_INVALIDATION action to set', () => {
84
+ const deltaEvt = event({
85
+ CacheKey: 'MJ: AI Models|_|_|-1|0|_|_|imr:1',
86
+ Action: 'row_delta',
87
+ Data: JSON.stringify({ primaryKeyFields: ['ID'], upsertedRows: [{ ID: '1' }], deletedPrimaryKeys: [], rowCount: 1, maxUpdatedAt: '' }),
88
+ });
89
+ RouteSharedCacheEvent(deltaEvt, provider(false));
90
+ expect(dispatch).toHaveBeenCalledWith(deltaEvt);
91
+ expect(publish).toHaveBeenCalledWith('CACHE_INVALIDATION', expect.objectContaining({ entityName: 'MJ: AI Models', action: 'set', primaryKeyValues: null }));
92
+ });
93
+
83
94
  it('does not relay other categories or category clears to browsers', () => {
84
95
  RouteSharedCacheEvent(event({ Category: 'default', CacheKey: '___MJCore_Metadata_AllMetadata' }), provider(false));
85
96
  RouteSharedCacheEvent(event({ Category: 'DatasetCache', CacheKey: 'ds' }), provider(false));
@@ -8,7 +8,7 @@
8
8
  * module (context.ts), vi.mock cannot intercept the intra-module call.
9
9
  * Instead we mock the LOWER-LEVEL dependencies that `getUserPayload` calls.
10
10
  */
11
- import { describe, it, expect, vi, beforeEach } from 'vitest';
11
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
12
12
 
13
13
  // ─── Hoisted mocks (available inside vi.mock factories) ─────────────────────
14
14
  const {
@@ -102,6 +102,7 @@ vi.mock('@memberjunction/api-keys', () => ({
102
102
  }));
103
103
 
104
104
  import { CreateUnifiedAuthMiddleware } from '../context.js';
105
+ import { userEmailMap } from '../config.js';
105
106
  import type { Request, Response, NextFunction } from 'express';
106
107
  import type { DataSourceInfo } from '../types.js';
107
108
 
@@ -136,6 +137,8 @@ function makeMockRes(): Response & { _status: number; _json: Record<string, unkn
136
137
  }
137
138
 
138
139
  const mockDataSources: DataSourceInfo[] = [];
140
+ /** The mocked config's map object; tests add entries and the afterEach clears them. */
141
+ const mockedUserEmailMap = userEmailMap as Record<string, string>;
139
142
  const MOCK_POOL = {} as unknown as import('mssql').ConnectionPool;
140
143
 
141
144
  const mockUserRecord = {
@@ -464,3 +467,72 @@ describe('createUnifiedAuthMiddleware', () => {
464
467
  });
465
468
  });
466
469
  });
470
+
471
+ // ─── Login identity reporting ───────────────────────────────────────────────
472
+ // The session-audit block runs once per issued token (deduped on iss/sub/iat), so each test
473
+ // presents a token with its own `iat`.
474
+
475
+ describe('login identity reporting', () => {
476
+ let middleware: ReturnType<typeof CreateUnifiedAuthMiddleware>;
477
+ let iat = 1_700_000_000;
478
+
479
+ function presentToken(email: string): void {
480
+ iat += 1;
481
+ mockJwtDecode.mockReturnValue({
482
+ iss: 'https://test-issuer.com',
483
+ sub: 'user-1',
484
+ iat,
485
+ exp: Math.floor(Date.now() / 1000) + 3600,
486
+ });
487
+ mockExtractUserInfo.mockReturnValue({ email, preferredUsername: email });
488
+ }
489
+
490
+ async function authenticate(): Promise<void> {
491
+ const req = makeMockReq({ headers: { authorization: `Bearer token-${iat}` } });
492
+ await middleware(req, makeMockRes(), vi.fn());
493
+ }
494
+
495
+ function warnings(spy: ReturnType<typeof vi.spyOn>): string[] {
496
+ return spy.mock.calls.map((call) => String(call[0]));
497
+ }
498
+
499
+ beforeEach(() => {
500
+ vi.clearAllMocks();
501
+ setupSuccessfulAuth();
502
+ middleware = CreateUnifiedAuthMiddleware(mockDataSources);
503
+ });
504
+
505
+ afterEach(() => {
506
+ for (const key of Object.keys(mockedUserEmailMap)) {
507
+ delete mockedUserEmailMap[key];
508
+ }
509
+ });
510
+
511
+ it('logs nothing when the token identity and the user record agree', async () => {
512
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
513
+ presentToken('test@example.com');
514
+ await authenticate();
515
+ expect(warnings(warn).filter((w) => w.startsWith('[Auth]'))).toEqual([]);
516
+ warn.mockRestore();
517
+ });
518
+
519
+ it('looks the user up by the mapped email and logs the remap', async () => {
520
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
521
+ mockedUserEmailMap['other@example.org'] = 'test@example.com';
522
+ presentToken('other@example.org');
523
+ await authenticate();
524
+ expect(mockVerifyUserRecord.mock.calls[0][0]).toBe('test@example.com');
525
+ expect(warnings(warn)).toContainEqual(expect.stringContaining("USER_EMAIL_MAP remapped login 'other@example.org' to 'test@example.com'"));
526
+ warn.mockRestore();
527
+ });
528
+
529
+ it('logs a login that resolved to a record with a different email', async () => {
530
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
531
+ presentToken('someone-else@example.org');
532
+ await authenticate();
533
+ expect(warnings(warn)).toContainEqual(
534
+ expect.stringContaining("Login identity 'someone-else@example.org' resolved to user user-1 with a different email (test@example.com)"),
535
+ );
536
+ warn.mockRestore();
537
+ });
538
+ });
@@ -1,6 +1,7 @@
1
- import { IMetadataProvider, UserInfo } from '@memberjunction/core';
1
+ import { IMetadataProvider, LogError, UserInfo } from '@memberjunction/core';
2
2
  import { AIBridgeEngine } from '@memberjunction/ai-bridge-server';
3
3
  import { GetCurrentHostInstance } from './HostInstance.js';
4
+ import { DEFAULT_BRIDGE_RECONCILER_SETTINGS, type BridgeReconcilerSettings } from '../backgroundSweepConfigUnits.js';
4
5
 
5
6
  /**
6
7
  * Gives the realtime bridge engine (`AIBridgeEngine`) this MJAPI instance's identity, the one its agent-session rows
@@ -17,8 +18,11 @@ export function BindBridgeEngineHostInstance(): void {
17
18
  }
18
19
 
19
20
  /**
20
- * Starts the realtime bridge engine's orphan reconcile: now, then every 10 minutes, it closes the bridges an earlier
21
- * boot of this instance left `Connecting` or `Connected` (reason `Janitor`) and finalizes their co-agent runs.
21
+ * Starts the realtime bridge engine's orphan reconcile: now, then every `settings.sweepIntervalMs` (10 minutes by
22
+ * default), it closes the bridges an earlier boot of this instance left `Connecting` or `Connected` (reason `Janitor`)
23
+ * and finalizes their co-agent runs. With `settings.enabled` false it reconciles once now and schedules no repeat, so an
24
+ * idle server sends no periodic queries for it (`agentSessions.bridgeReconciler`; see the Serverless Database
25
+ * Auto-Pause guide).
22
26
  *
23
27
  * `Serve` calls it once the server listens on its port, where it starts the session janitor. Holding the port is what
24
28
  * makes another boot of this instance a process that has ended; before that, an MJAPI started on a port another MJAPI
@@ -26,7 +30,18 @@ export function BindBridgeEngineHostInstance(): void {
26
30
  *
27
31
  * @param provider The server's metadata provider, for the reads and writes.
28
32
  * @param systemUser The user the writes run as.
33
+ * @param settings Whether to repeat the reconcile, and how often. Defaults to the shipped settings.
29
34
  */
30
- export function StartBridgeOrphanReconciliation(provider: IMetadataProvider, systemUser: UserInfo): void {
31
- AIBridgeEngine.Instance.StartOrphanReconciliation(systemUser, provider);
35
+ export function StartBridgeOrphanReconciliation(
36
+ provider: IMetadataProvider,
37
+ systemUser: UserInfo,
38
+ settings: BridgeReconcilerSettings = DEFAULT_BRIDGE_RECONCILER_SETTINGS,
39
+ ): void {
40
+ if (settings.enabled) {
41
+ AIBridgeEngine.Instance.StartOrphanReconciliation(systemUser, provider, settings.sweepIntervalMs);
42
+ return;
43
+ }
44
+ void AIBridgeEngine.Instance.ReconcileOrphans(systemUser, provider).catch((err) =>
45
+ LogError(`[AIBridgeEngine] orphan reconciliation failed: ${err instanceof Error ? err.message : String(err)}`),
46
+ );
32
47
  }
@@ -0,0 +1,18 @@
1
+ import { HandoffOfferRegistry } from '@memberjunction/livekit-room-server';
2
+ import type { HandoffOfferSweepSettings } from '../backgroundSweepConfigUnits.js';
3
+
4
+ /**
5
+ * Applies `agentSessions.handoffOfferExpiry` to the process-wide handoff offer registry. The registry
6
+ * starts its expiry sweep (a read of `MJ: Interaction Offers` every 15 seconds) as soon as it loads, on
7
+ * every MJAPI whatever its role; this either stops that sweep, so an idle server sends no periodic
8
+ * query for it, or restarts it on the configured interval. Overdue offers still read as expired when
9
+ * the sweep is off. See the Serverless Database Auto-Pause guide.
10
+ */
11
+ export function ApplyHandoffOfferExpirySettings(settings: HandoffOfferSweepSettings): void {
12
+ const registry = HandoffOfferRegistry.Instance;
13
+ if (settings.enabled) {
14
+ registry.StartPeriodicSweep(settings.sweepIntervalMs);
15
+ } else {
16
+ registry.StopPeriodicSweep();
17
+ }
18
+ }
@@ -146,6 +146,7 @@ async function recapAlreadyExists(conversationId: string, contextUser: UserInfo)
146
146
  ExtraFilter: `SourceConversationID = '${conversationId}' AND IsAutoGenerated = 1 AND Type = 'Context'`,
147
147
  Fields: ['ID'],
148
148
  MaxRows: 1,
149
+ SkipTotalRowCount: true,
149
150
  ResultType: 'simple',
150
151
  },
151
152
  contextUser,