@memberjunction/server 6.2.0-edge.0 → 6.2.0-edge.2

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 (459) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/agentSessions/ReturningVisitorRecap.d.ts +2 -0
  3. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -1
  4. package/dist/agentSessions/ReturningVisitorRecap.js +5 -1
  5. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  6. package/dist/agentSessions/SessionManager.js +2 -2
  7. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts +8 -0
  8. package/dist/agentSessions/remoteBrowserGoalEngine.d.ts.map +1 -1
  9. package/dist/agentSessions/remoteBrowserGoalEngine.js +21 -5
  10. package/dist/agentSessions/remoteBrowserGoalEngine.js.map +1 -1
  11. package/dist/auth/AuthProviderCatalogRouter.d.ts +2 -0
  12. package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
  13. package/dist/auth/AuthProviderCatalogRouter.js +5 -1
  14. package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
  15. package/dist/auth/index.d.ts +20 -1
  16. package/dist/auth/index.d.ts.map +1 -1
  17. package/dist/auth/index.js +20 -10
  18. package/dist/auth/index.js.map +1 -1
  19. package/dist/auth/initializeProviders.d.ts +8 -0
  20. package/dist/auth/initializeProviders.d.ts.map +1 -1
  21. package/dist/auth/initializeProviders.js +21 -5
  22. package/dist/auth/initializeProviders.js.map +1 -1
  23. package/dist/auth/magicLink/MagicLinkRouter.d.ts +9 -0
  24. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  25. package/dist/auth/magicLink/MagicLinkRouter.js +22 -10
  26. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  27. package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
  28. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  29. package/dist/auth/magicLink/MagicLinkService.js +65 -51
  30. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  31. package/dist/auth/magicLink/index.d.ts +2 -2
  32. package/dist/auth/magicLink/index.d.ts.map +1 -1
  33. package/dist/auth/magicLink/index.js +2 -2
  34. package/dist/auth/magicLink/index.js.map +1 -1
  35. package/dist/auth/magicLink/magicLinkCore.d.ts +54 -33
  36. package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
  37. package/dist/auth/magicLink/magicLinkCore.js +61 -61
  38. package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
  39. package/dist/auth/magicLink/redeemLanding.d.ts +4 -0
  40. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  41. package/dist/auth/magicLink/redeemLanding.js +12 -4
  42. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  43. package/dist/auth/magicLink/types.d.ts +4 -4
  44. package/dist/auth/principals.d.ts +2 -0
  45. package/dist/auth/principals.d.ts.map +1 -1
  46. package/dist/auth/principals.js +6 -2
  47. package/dist/auth/principals.js.map +1 -1
  48. package/dist/cache.d.ts +2 -0
  49. package/dist/cache.d.ts.map +1 -1
  50. package/dist/cache.js +3 -1
  51. package/dist/cache.js.map +1 -1
  52. package/dist/config.d.ts +346 -2
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +34 -4
  55. package/dist/config.js.map +1 -1
  56. package/dist/context.d.ts +20 -0
  57. package/dist/context.d.ts.map +1 -1
  58. package/dist/context.js +27 -19
  59. package/dist/context.js.map +1 -1
  60. package/dist/directives/Public.d.ts +2 -0
  61. package/dist/directives/Public.d.ts.map +1 -1
  62. package/dist/directives/Public.js +3 -1
  63. package/dist/directives/Public.js.map +1 -1
  64. package/dist/directives/RequireSystemUser.d.ts +2 -0
  65. package/dist/directives/RequireSystemUser.d.ts.map +1 -1
  66. package/dist/directives/RequireSystemUser.js +3 -1
  67. package/dist/directives/RequireSystemUser.js.map +1 -1
  68. package/dist/generated/graphql-schemas/__mj.d.ts +1021 -0
  69. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  70. package/dist/generated/graphql-schemas/__mj.js +7368 -2145
  71. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  72. package/dist/generic/CacheInvalidationResolver.d.ts +94 -4
  73. package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
  74. package/dist/generic/CacheInvalidationResolver.js +122 -3
  75. package/dist/generic/CacheInvalidationResolver.js.map +1 -1
  76. package/dist/generic/DeleteOptionsInput.d.ts +8 -1
  77. package/dist/generic/DeleteOptionsInput.d.ts.map +1 -1
  78. package/dist/generic/DeleteOptionsInput.js +1 -1
  79. package/dist/generic/DeleteOptionsInput.js.map +1 -1
  80. package/dist/generic/FireAndForgetHeartbeat.d.ts +18 -2
  81. package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
  82. package/dist/generic/FireAndForgetHeartbeat.js +24 -6
  83. package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
  84. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +31 -0
  85. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -0
  86. package/dist/generic/OrphanedConversationDetailReconciler.js +215 -0
  87. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -0
  88. package/dist/generic/PushStatusResolver.d.ts +48 -1
  89. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  90. package/dist/generic/PushStatusResolver.js +95 -4
  91. package/dist/generic/PushStatusResolver.js.map +1 -1
  92. package/dist/generic/ResolverBase.d.ts +18 -2
  93. package/dist/generic/ResolverBase.d.ts.map +1 -1
  94. package/dist/generic/ResolverBase.js +64 -55
  95. package/dist/generic/ResolverBase.js.map +1 -1
  96. package/dist/index.d.ts +9 -0
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +120 -11
  99. package/dist/index.js.map +1 -1
  100. package/dist/integration/CustomColumnPromoter.d.ts +2 -0
  101. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  102. package/dist/integration/CustomColumnPromoter.js +5 -1
  103. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  104. package/dist/integration/EntityMapLifecycle.d.ts +4 -0
  105. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  106. package/dist/integration/EntityMapLifecycle.js +10 -2
  107. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  108. package/dist/logging/NoLog.d.ts +4 -0
  109. package/dist/logging/NoLog.d.ts.map +1 -1
  110. package/dist/logging/NoLog.js +10 -2
  111. package/dist/logging/NoLog.js.map +1 -1
  112. package/dist/logging/StartupLogger.d.ts +2 -0
  113. package/dist/logging/StartupLogger.d.ts.map +1 -1
  114. package/dist/logging/StartupLogger.js +5 -1
  115. package/dist/logging/StartupLogger.js.map +1 -1
  116. package/dist/logging/bootAudit.d.ts +11 -7
  117. package/dist/logging/bootAudit.d.ts.map +1 -1
  118. package/dist/logging/bootAudit.js +15 -7
  119. package/dist/logging/bootAudit.js.map +1 -1
  120. package/dist/logging/boundaryLogPayload.d.ts +4 -0
  121. package/dist/logging/boundaryLogPayload.d.ts.map +1 -1
  122. package/dist/logging/boundaryLogPayload.js +5 -1
  123. package/dist/logging/boundaryLogPayload.js.map +1 -1
  124. package/dist/logging/secretRedactor.d.ts +2 -0
  125. package/dist/logging/secretRedactor.d.ts.map +1 -1
  126. package/dist/logging/secretRedactor.js +8 -4
  127. package/dist/logging/secretRedactor.js.map +1 -1
  128. package/dist/logging/shortenForLog.d.ts +2 -0
  129. package/dist/logging/shortenForLog.d.ts.map +1 -1
  130. package/dist/logging/shortenForLog.js +7 -3
  131. package/dist/logging/shortenForLog.js.map +1 -1
  132. package/dist/logging/variablesLoggingMiddleware.d.ts +2 -0
  133. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
  134. package/dist/logging/variablesLoggingMiddleware.js +8 -6
  135. package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
  136. package/dist/metadataRefreshSignal.d.ts +19 -0
  137. package/dist/metadataRefreshSignal.d.ts.map +1 -0
  138. package/dist/metadataRefreshSignal.js +45 -0
  139. package/dist/metadataRefreshSignal.js.map +1 -0
  140. package/dist/middleware/MJTenantFilterMiddleware.js +4 -4
  141. package/dist/multiTenancy/index.d.ts +8 -0
  142. package/dist/multiTenancy/index.d.ts.map +1 -1
  143. package/dist/multiTenancy/index.js +21 -5
  144. package/dist/multiTenancy/index.js.map +1 -1
  145. package/dist/realtimeWidget/WidgetRouter.d.ts +5 -0
  146. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
  147. package/dist/realtimeWidget/WidgetRouter.js +14 -10
  148. package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
  149. package/dist/realtimeWidget/WidgetSessionService.d.ts +6 -6
  150. package/dist/realtimeWidget/WidgetSessionService.js +27 -27
  151. package/dist/realtimeWidget/host-identity.d.ts +5 -1
  152. package/dist/realtimeWidget/host-identity.d.ts.map +1 -1
  153. package/dist/realtimeWidget/host-identity.js +11 -3
  154. package/dist/realtimeWidget/host-identity.js.map +1 -1
  155. package/dist/realtimeWidget/index.d.ts +2 -2
  156. package/dist/realtimeWidget/index.d.ts.map +1 -1
  157. package/dist/realtimeWidget/index.js +2 -2
  158. package/dist/realtimeWidget/index.js.map +1 -1
  159. package/dist/realtimeWidget/visitorIdentity.d.ts +21 -1
  160. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  161. package/dist/realtimeWidget/visitorIdentity.js +16 -4
  162. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  163. package/dist/realtimeWidget/widgetCore.d.ts +48 -0
  164. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  165. package/dist/realtimeWidget/widgetCore.js +38 -10
  166. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  167. package/dist/realtimeWidget/widgetGuestElevation.d.ts +10 -6
  168. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
  169. package/dist/realtimeWidget/widgetGuestElevation.js +14 -6
  170. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  171. package/dist/resolvers/AdhocQueryResolver.js +3 -3
  172. package/dist/resolvers/AgentRunStatusPublisher.d.ts +55 -0
  173. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -0
  174. package/dist/resolvers/AgentRunStatusPublisher.js +141 -0
  175. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -0
  176. package/dist/resolvers/ConversationRunEventsResolver.d.ts +90 -0
  177. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -0
  178. package/dist/resolvers/ConversationRunEventsResolver.js +283 -0
  179. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -0
  180. package/dist/resolvers/DuplicateEntryCheckResolver.d.ts +98 -0
  181. package/dist/resolvers/DuplicateEntryCheckResolver.d.ts.map +1 -0
  182. package/dist/resolvers/DuplicateEntryCheckResolver.js +227 -0
  183. package/dist/resolvers/DuplicateEntryCheckResolver.js.map +1 -0
  184. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  185. package/dist/resolvers/FetchEntityVectorsResolver.js +2 -1
  186. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  187. package/dist/resolvers/FileResolver.js +3 -3
  188. package/dist/resolvers/GeoResolver.d.ts +2 -2
  189. package/dist/resolvers/GeoResolver.js +4 -4
  190. package/dist/resolvers/GetDataResolver.d.ts +14 -0
  191. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  192. package/dist/resolvers/GetDataResolver.js +43 -15
  193. package/dist/resolvers/GetDataResolver.js.map +1 -1
  194. package/dist/resolvers/IntegrationDiscoveryResolver.js +2 -2
  195. package/dist/resolvers/ListOperationsResolver.d.ts +20 -0
  196. package/dist/resolvers/ListOperationsResolver.d.ts.map +1 -1
  197. package/dist/resolvers/ListOperationsResolver.js +68 -28
  198. package/dist/resolvers/ListOperationsResolver.js.map +1 -1
  199. package/dist/resolvers/MCPResolver.d.ts +11 -1
  200. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  201. package/dist/resolvers/MCPResolver.js +7 -3
  202. package/dist/resolvers/MCPResolver.js.map +1 -1
  203. package/dist/resolvers/MergeRecordsResolver.d.ts +7 -0
  204. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  205. package/dist/resolvers/MergeRecordsResolver.js +16 -2
  206. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  207. package/dist/resolvers/RealtimeBridgeResolver.js +3 -3
  208. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +44 -6
  209. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  210. package/dist/resolvers/RealtimeClientSessionResolver.js +93 -37
  211. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  212. package/dist/resolvers/RemoteBrowserActionResolver.js +5 -5
  213. package/dist/resolvers/RunAIAgentResolver.d.ts +8 -18
  214. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  215. package/dist/resolvers/RunAIAgentResolver.js +85 -184
  216. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  217. package/dist/resolvers/RunAIPromptResolver.d.ts +1 -1
  218. package/dist/resolvers/RunAIPromptResolver.js +2 -2
  219. package/dist/resolvers/RunDecisionResolver.d.ts +149 -0
  220. package/dist/resolvers/RunDecisionResolver.d.ts.map +1 -0
  221. package/dist/resolvers/RunDecisionResolver.js +396 -0
  222. package/dist/resolvers/RunDecisionResolver.js.map +1 -0
  223. package/dist/resolvers/RunTestResolver.js +5 -5
  224. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +8 -4
  225. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  226. package/dist/resolvers/SqlLoggingConfigResolver.js +5 -5
  227. package/dist/resolvers/TaskGraphFrameResolver.d.ts +8 -1
  228. package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
  229. package/dist/resolvers/TaskGraphFrameResolver.js +6 -2
  230. package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
  231. package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
  232. package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
  233. package/dist/resolvers/VectorizeEntityResolver.js +14 -10
  234. package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
  235. package/dist/resolvers/adhoc-query-helpers.d.ts +6 -0
  236. package/dist/resolvers/adhoc-query-helpers.d.ts.map +1 -1
  237. package/dist/resolvers/adhoc-query-helpers.js +10 -2
  238. package/dist/resolvers/adhoc-query-helpers.js.map +1 -1
  239. package/dist/resolvers/artifact-routing.d.ts +8 -8
  240. package/dist/resolvers/artifact-routing.js +2 -2
  241. package/dist/resolvers/artifact-routing.js.map +1 -1
  242. package/dist/resolvers/meetingRecordingRegistration.d.ts +10 -2
  243. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  244. package/dist/resolvers/meetingRecordingRegistration.js +24 -8
  245. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  246. package/dist/resolvers/peaksSidecar.d.ts +4 -0
  247. package/dist/resolvers/peaksSidecar.d.ts.map +1 -1
  248. package/dist/resolvers/peaksSidecar.js +10 -2
  249. package/dist/resolvers/peaksSidecar.js.map +1 -1
  250. package/dist/rest/EntityCRUDHandler.d.ts +28 -0
  251. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  252. package/dist/rest/EntityCRUDHandler.js +20 -4
  253. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  254. package/dist/rest/MediaStreamHandler.d.ts +2 -0
  255. package/dist/rest/MediaStreamHandler.d.ts.map +1 -1
  256. package/dist/rest/MediaStreamHandler.js +10 -6
  257. package/dist/rest/MediaStreamHandler.js.map +1 -1
  258. package/dist/rest/OAuthCallbackHandler.d.ts +9 -0
  259. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  260. package/dist/rest/OAuthCallbackHandler.js +15 -3
  261. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  262. package/dist/rest/RESTEndpointHandler.d.ts +6 -4
  263. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  264. package/dist/rest/RESTEndpointHandler.js +15 -11
  265. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  266. package/dist/rest/RealtimeSdpBrokerHandler.d.ts +5 -1
  267. package/dist/rest/RealtimeSdpBrokerHandler.d.ts.map +1 -1
  268. package/dist/rest/RealtimeSdpBrokerHandler.js +12 -4
  269. package/dist/rest/RealtimeSdpBrokerHandler.js.map +1 -1
  270. package/dist/rest/SignatureWebhookHandler.d.ts +2 -0
  271. package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -1
  272. package/dist/rest/SignatureWebhookHandler.js +5 -1
  273. package/dist/rest/SignatureWebhookHandler.js.map +1 -1
  274. package/dist/rest/UploadTokenManager.d.ts +24 -8
  275. package/dist/rest/UploadTokenManager.d.ts.map +1 -1
  276. package/dist/rest/UploadTokenManager.js +53 -21
  277. package/dist/rest/UploadTokenManager.js.map +1 -1
  278. package/dist/rest/ViewOperationsHandler.d.ts +20 -0
  279. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  280. package/dist/rest/ViewOperationsHandler.js +20 -4
  281. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  282. package/dist/rest/mediaRange.d.ts +4 -0
  283. package/dist/rest/mediaRange.d.ts.map +1 -1
  284. package/dist/rest/mediaRange.js +10 -2
  285. package/dist/rest/mediaRange.js.map +1 -1
  286. package/dist/rest/setupRESTEndpoints.d.ts +4 -0
  287. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  288. package/dist/rest/setupRESTEndpoints.js +13 -7
  289. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  290. package/dist/serverExtensionReservedRoots.d.ts +2 -0
  291. package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
  292. package/dist/serverExtensionReservedRoots.js +5 -1
  293. package/dist/serverExtensionReservedRoots.js.map +1 -1
  294. package/dist/services/TaskGraphActionRunner.d.ts +13 -8
  295. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  296. package/dist/services/TaskGraphActionRunner.js +16 -15
  297. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  298. package/dist/types.d.ts +2 -0
  299. package/dist/types.d.ts.map +1 -1
  300. package/dist/types.js +7 -3
  301. package/dist/types.js.map +1 -1
  302. package/dist/util.d.ts +6 -0
  303. package/dist/util.d.ts.map +1 -1
  304. package/dist/util.js +13 -5
  305. package/dist/util.js.map +1 -1
  306. package/package.json +94 -92
  307. package/src/__tests__/AgentRunStatusPublisher.test.ts +175 -0
  308. package/src/__tests__/DeleteOptionsInput.wireShape.test.ts +62 -0
  309. package/src/__tests__/DuplicateEntryCheckResolver.test.ts +300 -0
  310. package/src/__tests__/FileResolverPeaks.test.ts +13 -13
  311. package/src/__tests__/ListOperationsResolver.test.ts +17 -17
  312. package/src/__tests__/MediaStreamHandler.test.ts +28 -28
  313. package/src/__tests__/NoLog.test.ts +11 -11
  314. package/src/__tests__/PrimaryKeyAlwaysMapped.test.ts +10 -10
  315. package/src/__tests__/RESTEndpointHandler.test.ts +14 -14
  316. package/src/__tests__/RealtimeBridgeResolver.test.ts +4 -2
  317. package/src/__tests__/RealtimeClientSessionResolver.test.ts +216 -2
  318. package/src/__tests__/RealtimeSdpBrokerHandler.test.ts +6 -6
  319. package/src/__tests__/RefreshAdoptsNewColumns.test.ts +8 -8
  320. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +7 -1
  321. package/src/__tests__/RemoteBrowserAudioStream.test.ts +7 -1
  322. package/src/__tests__/RemoteBrowserGoalEngine.test.ts +18 -18
  323. package/src/__tests__/RemoteBrowserSnapshot.test.ts +7 -1
  324. package/src/__tests__/ResolverBase.clientOldValues.test.ts +52 -0
  325. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +398 -0
  326. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +18 -27
  327. package/src/__tests__/RunDecisionResolver.test.ts +699 -0
  328. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +131 -0
  329. package/src/__tests__/TaskGraphFrameResolver.test.ts +2 -2
  330. package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
  331. package/src/__tests__/WidgetSessionService.test.ts +8 -4
  332. package/src/__tests__/adhoc-query-helpers.test.ts +21 -21
  333. package/src/__tests__/bootAudit.test.ts +11 -11
  334. package/src/__tests__/boundaryLogPayload.test.ts +6 -6
  335. package/src/__tests__/cacheInvalidationRecordData.test.ts +177 -0
  336. package/src/__tests__/conversationRunEvents.test.ts +399 -0
  337. package/src/__tests__/databaseAbstraction.test.ts +12 -6
  338. package/src/__tests__/getDataTokenRedaction.test.ts +7 -7
  339. package/src/__tests__/initializeProviders.refresh.test.ts +4 -4
  340. package/src/__tests__/magicLink.test.ts +73 -134
  341. package/src/__tests__/magicLinkService.consume.test.ts +224 -0
  342. package/src/__tests__/meetingRecordingRegistration.test.ts +12 -12
  343. package/src/__tests__/metadataRefreshSignal.test.ts +78 -0
  344. package/src/__tests__/multiTenancy.security.test.ts +40 -40
  345. package/src/__tests__/multiTenancy.test.ts +18 -18
  346. package/src/__tests__/newUsers.test.ts +24 -23
  347. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +283 -0
  348. package/src/__tests__/principals.test.ts +22 -22
  349. package/src/__tests__/pushStatusFanOut.test.ts +206 -0
  350. package/src/__tests__/pushStatusResolver.filter.test.ts +8 -8
  351. package/src/__tests__/secretRedactor.failOpen.test.ts +12 -12
  352. package/src/__tests__/secretRedactor.test.ts +12 -12
  353. package/src/__tests__/serverExtensionReservedRoots.test.ts +7 -7
  354. package/src/__tests__/subscriptionRedaction.test.ts +5 -5
  355. package/src/__tests__/unifiedAuth.test.ts +15 -9
  356. package/src/__tests__/visitorIdentity.resolve.test.ts +6 -6
  357. package/src/__tests__/widget.test.ts +45 -45
  358. package/src/__tests__/widgetGuestElevation.test.ts +7 -7
  359. package/src/__tests__/widgetHostIdentity.test.ts +14 -14
  360. package/src/agentSessions/ReturningVisitorRecap.ts +12 -1
  361. package/src/agentSessions/SessionManager.ts +2 -2
  362. package/src/agentSessions/remoteBrowserGoalEngine.ts +30 -5
  363. package/src/auth/AuthProviderCatalogRouter.ts +6 -1
  364. package/src/auth/__tests__/backward-compatibility.test.ts +4 -3
  365. package/src/auth/index.ts +43 -28
  366. package/src/auth/initializeProviders.ts +25 -5
  367. package/src/auth/magicLink/MagicLinkRouter.ts +28 -10
  368. package/src/auth/magicLink/MagicLinkService.ts +71 -52
  369. package/src/auth/magicLink/index.ts +7 -7
  370. package/src/auth/magicLink/magicLinkCore.ts +96 -64
  371. package/src/auth/magicLink/redeemLanding.ts +14 -4
  372. package/src/auth/magicLink/types.ts +54 -54
  373. package/src/auth/newUsers.ts +1 -1
  374. package/src/auth/principals.ts +11 -2
  375. package/src/cache.ts +4 -1
  376. package/src/config.ts +37 -4
  377. package/src/context.ts +37 -24
  378. package/src/directives/Public.ts +4 -1
  379. package/src/directives/RequireSystemUser.ts +4 -1
  380. package/src/generated/graphql-schemas/__mj.ts +3674 -4
  381. package/src/generic/CacheInvalidationResolver.ts +148 -4
  382. package/src/generic/DeleteOptionsInput.ts +9 -2
  383. package/src/generic/FireAndForgetHeartbeat.ts +35 -16
  384. package/src/generic/OrphanedConversationDetailReconciler.ts +267 -0
  385. package/src/generic/PushStatusResolver.ts +118 -8
  386. package/src/generic/ResolverBase.ts +65 -57
  387. package/src/generic/RunViewResolver.ts +22 -22
  388. package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +7 -7
  389. package/src/index.ts +137 -11
  390. package/src/integration/CustomColumnPromoter.ts +6 -1
  391. package/src/integration/EntityMapLifecycle.ts +20 -2
  392. package/src/logging/NoLog.ts +16 -2
  393. package/src/logging/StartupLogger.ts +6 -1
  394. package/src/logging/bootAudit.ts +26 -16
  395. package/src/logging/boundaryLogPayload.ts +8 -1
  396. package/src/logging/secretRedactor.ts +14 -9
  397. package/src/logging/shortenForLog.ts +8 -3
  398. package/src/logging/variablesLoggingMiddleware.ts +9 -6
  399. package/src/metadataRefreshSignal.ts +45 -0
  400. package/src/middleware/MJTenantFilterMiddleware.ts +4 -4
  401. package/src/multiTenancy/index.ts +29 -5
  402. package/src/realtimeWidget/WidgetRouter.ts +15 -10
  403. package/src/realtimeWidget/WidgetSessionService.ts +76 -76
  404. package/src/realtimeWidget/host-identity.ts +24 -10
  405. package/src/realtimeWidget/index.ts +3 -3
  406. package/src/realtimeWidget/visitorIdentity.ts +39 -8
  407. package/src/realtimeWidget/widgetCore.ts +75 -10
  408. package/src/realtimeWidget/widgetGuestElevation.ts +25 -12
  409. package/src/resolvers/AdhocQueryResolver.ts +3 -3
  410. package/src/resolvers/AgentRunStatusPublisher.ts +175 -0
  411. package/src/resolvers/AgentSessionResolver.ts +3 -3
  412. package/src/resolvers/ColorResolver.ts +6 -6
  413. package/src/resolvers/ComponentRegistryResolver.ts +36 -36
  414. package/src/resolvers/ConversationRunEventsResolver.ts +308 -0
  415. package/src/resolvers/DuplicateEntryCheckResolver.ts +232 -0
  416. package/src/resolvers/EntityCommunicationsResolver.ts +4 -4
  417. package/src/resolvers/ExecuteRemoteOperationResolver.ts +9 -9
  418. package/src/resolvers/FetchEntityVectorsResolver.ts +2 -1
  419. package/src/resolvers/FileResolver.ts +42 -42
  420. package/src/resolvers/GeoResolver.ts +4 -4
  421. package/src/resolvers/GetDataResolver.ts +50 -15
  422. package/src/resolvers/IntegrationDiscoveryResolver.ts +2 -2
  423. package/src/resolvers/ListOperationsResolver.ts +78 -28
  424. package/src/resolvers/MCPResolver.ts +30 -15
  425. package/src/resolvers/MergeRecordsResolver.ts +14 -2
  426. package/src/resolvers/QueryResolver.ts +13 -13
  427. package/src/resolvers/RealtimeBridgeResolver.ts +3 -3
  428. package/src/resolvers/RealtimeClientSessionResolver.ts +96 -38
  429. package/src/resolvers/RemoteBrowserActionResolver.ts +5 -5
  430. package/src/resolvers/RunAIAgentResolver.ts +134 -239
  431. package/src/resolvers/RunAIPromptResolver.ts +22 -22
  432. package/src/resolvers/RunDecisionResolver.ts +435 -0
  433. package/src/resolvers/RunTemplateResolver.ts +4 -4
  434. package/src/resolvers/RunTestResolver.ts +25 -25
  435. package/src/resolvers/SqlLoggingConfigResolver.ts +56 -56
  436. package/src/resolvers/TaskGraphFrameResolver.ts +37 -28
  437. package/src/resolvers/TelemetryResolver.ts +79 -79
  438. package/src/resolvers/VectorizeEntityResolver.ts +16 -11
  439. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +40 -38
  440. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +93 -0
  441. package/src/resolvers/__tests__/artifact-routing.test.ts +23 -23
  442. package/src/resolvers/adhoc-query-helpers.ts +19 -2
  443. package/src/resolvers/artifact-routing.ts +19 -19
  444. package/src/resolvers/meetingRecordingRegistration.ts +44 -10
  445. package/src/resolvers/peaksSidecar.ts +12 -2
  446. package/src/rest/EntityCRUDHandler.ts +24 -4
  447. package/src/rest/MediaStreamHandler.ts +11 -6
  448. package/src/rest/OAuthCallbackHandler.ts +25 -7
  449. package/src/rest/RESTEndpointHandler.ts +20 -15
  450. package/src/rest/RealtimeSdpBrokerHandler.ts +14 -4
  451. package/src/rest/SignatureWebhookHandler.ts +6 -1
  452. package/src/rest/UploadTokenManager.ts +66 -30
  453. package/src/rest/ViewOperationsHandler.ts +24 -4
  454. package/src/rest/mediaRange.ts +16 -6
  455. package/src/rest/setupRESTEndpoints.ts +24 -12
  456. package/src/serverExtensionReservedRoots.ts +6 -1
  457. package/src/services/TaskGraphActionRunner.ts +16 -15
  458. package/src/types.ts +21 -16
  459. package/src/util.ts +16 -6
@@ -168,7 +168,8 @@ vi.mock('@memberjunction/global', async (importOriginal) => {
168
168
  };
169
169
  });
170
170
 
171
- vi.mock('../auth/initializeProviders.js', () => ({ initializeAuthProviders: vi.fn() }));
171
+ vi.mock('../auth/initializeProviders.js', () => ({ InitializeAuthProviders: vi.fn(),
172
+ get initializeAuthProviders() { return this.InitializeAuthProviders; } }));
172
173
 
173
174
  vi.mock('@memberjunction/auth-providers', () => ({
174
175
  AuthProviderFactory: {
@@ -195,7 +196,7 @@ vi.mock('@memberjunction/api-keys', () => ({
195
196
 
196
197
  // ─── Import after mocks ─────────────────────────────────────────────────────
197
198
  import { NewUserBase } from '../auth/newUsers.js';
198
- import { verifyUserRecord } from '../auth/index.js';
199
+ import { VerifyUserRecord } from '../auth/index.js';
199
200
 
200
201
  // ─── Mock-entity plumbing ───────────────────────────────────────────────────
201
202
 
@@ -557,7 +558,7 @@ describe('verifyUserRecord', () => {
557
558
  }
558
559
 
559
560
  it('returns undefined when no email is supplied', async () => {
560
- const user = await verifyUserRecord(undefined, 'A', 'B');
561
+ const user = await VerifyUserRecord(undefined, 'A', 'B');
561
562
 
562
563
  expect(user).toBeUndefined();
563
564
  expect(mockGetEntityObject).not.toHaveBeenCalled();
@@ -566,7 +567,7 @@ describe('verifyUserRecord', () => {
566
567
  it('returns the cached user, matching email case-insensitively and trimmed', async () => {
567
568
  addCacheUser('someone@example.com');
568
569
 
569
- const user = await verifyUserRecord(' SomeOne@Example.COM ', 'A', 'B');
570
+ const user = await VerifyUserRecord(' SomeOne@Example.COM ', 'A', 'B');
570
571
 
571
572
  expect(user).toBeDefined();
572
573
  expect((user as unknown as { Email: string }).Email).toBe('someone@example.com');
@@ -577,7 +578,7 @@ describe('verifyUserRecord', () => {
577
578
  mockCacheUsers.push({ ID: 'broken-1', Name: 'Broken', Email: ' ', Type: 'User' });
578
579
  const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
579
580
 
580
- const user = await verifyUserRecord('missing@example.com', undefined, undefined);
581
+ const user = await VerifyUserRecord('missing@example.com', undefined, undefined);
581
582
 
582
583
  expect(user).toBeUndefined();
583
584
  expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('broken-1'));
@@ -587,15 +588,15 @@ describe('verifyUserRecord', () => {
587
588
  it('does NOT create a user when autoCreateNewUsers is false', async () => {
588
589
  mockConfig.userHandling.autoCreateNewUsers = false;
589
590
 
590
- const user = await verifyUserRecord('new@example.com', 'New', 'User');
591
+ const user = await VerifyUserRecord('new@example.com', 'New', 'User');
591
592
 
592
593
  expect(user).toBeUndefined();
593
594
  expect(mockGetEntityObject).not.toHaveBeenCalled();
594
595
  });
595
596
 
596
597
  it('does NOT create a user when firstName or lastName is missing', async () => {
597
- const missingFirst = await verifyUserRecord('new@example.com', undefined, 'User');
598
- const missingLast = await verifyUserRecord('new@example.com', 'New', undefined);
598
+ const missingFirst = await VerifyUserRecord('new@example.com', undefined, 'User');
599
+ const missingLast = await VerifyUserRecord('new@example.com', 'New', undefined);
599
600
 
600
601
  expect(missingFirst).toBeUndefined();
601
602
  expect(missingLast).toBeUndefined();
@@ -605,7 +606,7 @@ describe('verifyUserRecord', () => {
605
606
  it('creates the user when enabled and domains are unrestricted, stamping config roles onto the returned UserInfo', async () => {
606
607
  mockConfig.userHandling.newUserRoles = ['UI', 'Developer'];
607
608
 
608
- const user = await verifyUserRecord('new@example.com', 'New', 'User');
609
+ const user = await VerifyUserRecord('new@example.com', 'New', 'User');
609
610
 
610
611
  expect(user).toBeDefined();
611
612
  const stamped = user as unknown as { Email: string; UserRoles: Array<{ Role: string; RoleID: string; UserID: string }> };
@@ -621,7 +622,7 @@ describe('verifyUserRecord', () => {
621
622
  it('returns undefined when the underlying createNewUser fails', async () => {
622
623
  saveBehavior['MJ: Users'] = () => false;
623
624
 
624
- const user = await verifyUserRecord('new@example.com', 'New', 'User');
625
+ const user = await VerifyUserRecord('new@example.com', 'New', 'User');
625
626
 
626
627
  expect(user).toBeUndefined();
627
628
  // The cache is no longer empty at rest (it holds the configured context user), so
@@ -637,35 +638,35 @@ describe('verifyUserRecord', () => {
637
638
  });
638
639
 
639
640
  it('creates the user when the email domain is authorized', async () => {
640
- const user = await verifyUserRecord('new@example.com', 'New', 'User');
641
+ const user = await VerifyUserRecord('new@example.com', 'New', 'User');
641
642
 
642
643
  expect(user).toBeDefined();
643
644
  expect(entitiesOf('MJ: Users')).toHaveLength(1);
644
645
  });
645
646
 
646
647
  it('does NOT create when the email domain is not authorized', async () => {
647
- const user = await verifyUserRecord('new@evil.com', 'New', 'User');
648
+ const user = await VerifyUserRecord('new@evil.com', 'New', 'User');
648
649
 
649
650
  expect(user).toBeUndefined();
650
651
  expect(mockGetEntityObject).not.toHaveBeenCalled();
651
652
  });
652
653
 
653
654
  it('creates when restricted and no request Origin is supplied, if the email domain is authorized', async () => {
654
- const user = await verifyUserRecord('new@example.com', 'New', 'User', undefined);
655
+ const user = await VerifyUserRecord('new@example.com', 'New', 'User', undefined);
655
656
 
656
657
  expect(user).toBeDefined();
657
658
  expect(entitiesOf('MJ: Users')).toHaveLength(1);
658
659
  });
659
660
 
660
661
  it('does NOT create when a forged Origin is authorized but the email domain is not', async () => {
661
- const user = await verifyUserRecord('new@evil.com', 'New', 'User', 'example.com');
662
+ const user = await VerifyUserRecord('new@evil.com', 'New', 'User', 'example.com');
662
663
 
663
664
  expect(user).toBeUndefined();
664
665
  expect(mockGetEntityObject).not.toHaveBeenCalled();
665
666
  });
666
667
 
667
668
  it('matches email domains case-insensitively', async () => {
668
- const user = await verifyUserRecord('new@EXAMPLE.COM', 'New', 'User');
669
+ const user = await VerifyUserRecord('new@EXAMPLE.COM', 'New', 'User');
669
670
 
670
671
  expect(user).toBeDefined();
671
672
  });
@@ -673,7 +674,7 @@ describe('verifyUserRecord', () => {
673
674
  it('supports wildcard patterns for email subdomains', async () => {
674
675
  mockConfig.userHandling.newUserAuthorizedDomains = ['*.example.com'];
675
676
 
676
- const user = await verifyUserRecord('new@mail.example.com', 'New', 'User');
677
+ const user = await VerifyUserRecord('new@mail.example.com', 'New', 'User');
677
678
 
678
679
  expect(user).toBeDefined();
679
680
  });
@@ -681,7 +682,7 @@ describe('verifyUserRecord', () => {
681
682
  it('does NOT match the apex against "*.example.com" (pattern is matched in full)', async () => {
682
683
  mockConfig.userHandling.newUserAuthorizedDomains = ['*.example.com'];
683
684
 
684
- const user = await verifyUserRecord('new@example.com', 'New', 'User');
685
+ const user = await VerifyUserRecord('new@example.com', 'New', 'User');
685
686
 
686
687
  expect(user).toBeUndefined();
687
688
  expect(mockGetEntityObject).not.toHaveBeenCalled();
@@ -690,27 +691,27 @@ describe('verifyUserRecord', () => {
690
691
  it('rejects prefix confusion: "*.example.com" does not match "evilexample.com" (dot is escaped)', async () => {
691
692
  mockConfig.userHandling.newUserAuthorizedDomains = ['*.example.com'];
692
693
 
693
- const user = await verifyUserRecord('new@evilexample.com', 'New', 'User');
694
+ const user = await VerifyUserRecord('new@evilexample.com', 'New', 'User');
694
695
 
695
696
  expect(user).toBeUndefined();
696
697
  expect(mockGetEntityObject).not.toHaveBeenCalled();
697
698
  });
698
699
 
699
700
  it('rejects suffix confusion: the pattern is anchored, so "example.com.evil.com" is not authorized', async () => {
700
- const user = await verifyUserRecord('new@example.com.evil.com', 'New', 'User');
701
+ const user = await VerifyUserRecord('new@example.com.evil.com', 'New', 'User');
701
702
 
702
703
  expect(user).toBeUndefined();
703
704
  expect(mockGetEntityObject).not.toHaveBeenCalled();
704
705
  });
705
706
 
706
707
  it('rejects "evilexample.com" against the plain "example.com" pattern (anchored at both ends)', async () => {
707
- const user = await verifyUserRecord('x@evilexample.com', 'New', 'User');
708
+ const user = await VerifyUserRecord('x@evilexample.com', 'New', 'User');
708
709
 
709
710
  expect(user).toBeUndefined();
710
711
  });
711
712
 
712
713
  it('does NOT create when the identity has no email domain (username-only IdP)', async () => {
713
- const user = await verifyUserRecord('bare-username', 'New', 'User');
714
+ const user = await VerifyUserRecord('bare-username', 'New', 'User');
714
715
 
715
716
  expect(user).toBeUndefined();
716
717
  expect(mockGetEntityObject).not.toHaveBeenCalled();
@@ -727,7 +728,7 @@ describe('verifyUserRecord', () => {
727
728
  });
728
729
  const dataSource = {} as unknown as import('mssql').ConnectionPool;
729
730
 
730
- const user = await verifyUserRecord('late@example.com', undefined, undefined, undefined, dataSource);
731
+ const user = await VerifyUserRecord('late@example.com', undefined, undefined, undefined, dataSource);
731
732
 
732
733
  expect(user).toBeDefined();
733
734
  expect((user as unknown as { Email: string }).Email).toBe('late@example.com');
@@ -739,7 +740,7 @@ describe('verifyUserRecord', () => {
739
740
  mockConfig.userHandling.updateCacheWhenNotFound = true;
740
741
  const dataSource = {} as unknown as import('mssql').ConnectionPool;
741
742
 
742
- const user = await verifyUserRecord('never@example.com', undefined, undefined, undefined, dataSource);
743
+ const user = await VerifyUserRecord('never@example.com', undefined, undefined, undefined, dataSource);
743
744
 
744
745
  expect(user).toBeUndefined();
745
746
  expect(mockRefresh).toHaveBeenCalledTimes(1);
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Conversation details left behind by a finished run must be closed (MJ #4222).
3
+ *
4
+ * `AgentRunner` closes the detail as the last step of a run, so a process that dies mid-run never
5
+ * gets there. The agent-run watchdog repairs the RUN but nothing repaired the detail — and the
6
+ * detail is the row the chat renders from. Observed directly: run B3E67FE3 force-failed by the
7
+ * watchdog while its detail stayed `In-Progress`, spinning for anyone who opened it.
8
+ *
9
+ * The properties that keep this safe to run unattended are pinned here: it never closes a message
10
+ * whose work is still going, and it never races the normal completion path.
11
+ */
12
+ import 'reflect-metadata';
13
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
14
+ import type { RunViewParams, UserInfo } from '@memberjunction/core';
15
+
16
+ const { mockRunView } = vi.hoisted(() => ({ mockRunView: vi.fn() }));
17
+
18
+ const { mockGetEntityObject, writableStore } = vi.hoisted(() => ({
19
+ mockGetEntityObject: vi.fn(),
20
+ writableStore: { last: null as Record<string, unknown> | null },
21
+ }));
22
+
23
+ vi.mock('@memberjunction/global', () => ({
24
+ UUIDsEqual: (a: string, b: string) => String(a).toLowerCase() === String(b).toLowerCase(),
25
+ }));
26
+
27
+ vi.mock('@memberjunction/sqlserver-dataprovider', () => ({
28
+ UserCache: { Instance: { Users: [{ ID: 'OWNER-1', Email: 'owner@example.com' }] } },
29
+ }));
30
+
31
+ vi.mock('@memberjunction/core', () => {
32
+ class MockRunView {
33
+ public static FromMetadataProvider(): MockRunView {
34
+ return new MockRunView();
35
+ }
36
+ public async RunView(...args: unknown[]): Promise<unknown> {
37
+ return mockRunView(...args);
38
+ }
39
+ }
40
+ return { LogError: vi.fn(), LogStatus: vi.fn(), RunView: MockRunView };
41
+ });
42
+
43
+ import { ReconcileOrphanedConversationDetails, ORPHAN_DETAIL_GRACE_MS } from '../generic/OrphanedConversationDetailReconciler.js';
44
+
45
+ const USER = { ID: 'U1' } as unknown as UserInfo;
46
+ const CONVERSATION_ID = 'C1';
47
+
48
+ /**
49
+ * Provider stub: hands back the entity the reconciler actually writes through, and quotes names the
50
+ * way the SQL Server provider does.
51
+ */
52
+ const PROVIDER = {
53
+ GetEntityObject: mockGetEntityObject,
54
+ EntityByName: (name: string) =>
55
+ name === 'MJ: AI Agent Runs' ? { SchemaName: '__mj', BaseView: 'vwAIAgentRuns' } : undefined,
56
+ QuoteIdentifier: (name: string) => `[${name}]`,
57
+ QuoteSchemaAndView: (schema: string, view: string) => `[${schema}].[${view}]`,
58
+ } as never;
59
+ const DETAIL_ID = 'D1';
60
+
61
+ function detail(over: Record<string, unknown> = {}) {
62
+ return { ID: DETAIL_ID, ConversationID: CONVERSATION_ID, Status: 'In-Progress', Message: '', ...over };
63
+ }
64
+
65
+ /**
66
+ * The record the reconciler loads and saves. Separate from the RunView projection on purpose: the
67
+ * reconciler deliberately does NOT write the entity RunView returned, because that one carries the
68
+ * system user and the conversation-detail permission gate refuses a non-owner.
69
+ */
70
+ function writable(saveResult = true, loadedStatus = 'In-Progress') {
71
+ const w = {
72
+ ID: DETAIL_ID,
73
+ Status: loadedStatus,
74
+ Message: '',
75
+ Load: vi.fn(async () => true),
76
+ Save: vi.fn(async () => saveResult),
77
+ LatestResult: { CompleteMessage: 'denied' },
78
+ };
79
+ writableStore.last = w;
80
+ return w;
81
+ }
82
+
83
+ function script(details: unknown[], runs: unknown[], ownerId: string | null = 'OWNER-1', loadedStatus = 'In-Progress') {
84
+ mockRunView.mockImplementation(async (...args: unknown[]) => {
85
+ const params = args[0] as RunViewParams | undefined;
86
+ if (!params) throw new Error('RunView called with no params');
87
+ if (params.EntityName === 'MJ: Conversation Details') return { Success: true, Results: details };
88
+ if (params.EntityName === 'MJ: AI Agent Runs') return { Success: true, Results: runs };
89
+ if (params.EntityName === 'MJ: Conversations') {
90
+ return { Success: true, Results: ownerId ? [{ ID: CONVERSATION_ID, UserID: ownerId }] : [] };
91
+ }
92
+ throw new Error(`unexpected entity ${params.EntityName}`);
93
+ });
94
+ mockGetEntityObject.mockImplementation(async () => writable(true, loadedStatus));
95
+ }
96
+
97
+ const longAgo = () => new Date(Date.now() - ORPHAN_DETAIL_GRACE_MS - 60_000).toISOString();
98
+ const justNow = () => new Date().toISOString();
99
+
100
+ describe('ReconcileOrphanedConversationDetails', () => {
101
+ // Block body, NOT a concise arrow: `mockReset()` returns the mock, which is callable, and
102
+ // vitest treats a function returned from beforeEach as a teardown hook — so a concise arrow
103
+ // hands vitest the mock itself to invoke with no arguments after every test.
104
+ beforeEach(() => {
105
+ mockRunView.mockReset();
106
+ mockGetEntityObject.mockReset();
107
+ writableStore.last = null;
108
+ });
109
+
110
+ it('closes a detail whose run failed long ago', async () => {
111
+ const d = detail();
112
+ script([d], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo(), ErrorMessage: 'watchdog force-fail' }]);
113
+
114
+ const closed = await ReconcileOrphanedConversationDetails(PROVIDER, USER);
115
+
116
+ expect(closed).toBe(1);
117
+ expect(writableStore.last?.Status).toBe('Error');
118
+ expect(writableStore.last?.Message).toBe('watchdog force-fail');
119
+ });
120
+
121
+ it('falls back to a failure marker only when the failed run carries no error text', async () => {
122
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }]);
123
+
124
+ await ReconcileOrphanedConversationDetails(PROVIDER, USER);
125
+
126
+ expect(writableStore.last?.Status).toBe('Error');
127
+ expect(writableStore.last?.Message).toBe('❌ Failed');
128
+ });
129
+
130
+ it('marks a completed run as Complete, not Error', async () => {
131
+ const d = detail();
132
+ script([d], [{ ConversationDetailID: DETAIL_ID, Status: 'Completed', CompletedAt: longAgo() }]);
133
+
134
+ await ReconcileOrphanedConversationDetails(PROVIDER, USER);
135
+
136
+ expect(writableStore.last?.Status).toBe('Complete');
137
+ // A successful run has no error to report, so an empty message stays empty rather than
138
+ // carrying a failure marker that contradicts the status beside it.
139
+ expect(writableStore.last?.Message).toBe('');
140
+ });
141
+
142
+ it('marks a run that ended awaiting the user\'s reply as Complete', async () => {
143
+ // AwaitingFeedback is how a normal chat turn ends: the run succeeded and the agent's
144
+ // reply is the message. A process that dies before closing the detail leaves exactly
145
+ // this pair behind, and the stale-run sweep never touches it because it is not Running.
146
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'AwaitingFeedback', CompletedAt: longAgo() }]);
147
+
148
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(1);
149
+ expect(writableStore.last?.Status).toBe('Complete');
150
+ expect(writableStore.last?.Message).toBe('');
151
+ });
152
+
153
+ it('leaves a message alone while its run is parked on a workflow', async () => {
154
+ // Paused means a task graph is still executing on the run's behalf.
155
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Paused', CompletedAt: null }]);
156
+
157
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
158
+ expect(mockGetEntityObject).not.toHaveBeenCalled();
159
+ });
160
+
161
+ it('leaves a message alone while its run is still executing', async () => {
162
+ const d = detail();
163
+ script([d], [{ ConversationDetailID: DETAIL_ID, Status: 'Running', CompletedAt: null }]);
164
+
165
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
166
+ expect(mockGetEntityObject).not.toHaveBeenCalled();
167
+ });
168
+
169
+ it('does not race the normal completion path inside the grace window', async () => {
170
+ const d = detail();
171
+ script([d], [{ ConversationDetailID: DETAIL_ID, Status: 'Completed', CompletedAt: justNow() }]);
172
+
173
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
174
+ expect(mockGetEntityObject).not.toHaveBeenCalled();
175
+ });
176
+
177
+ it('leaves a message alone when no run exists yet', async () => {
178
+ const d = detail();
179
+ script([d], []);
180
+
181
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
182
+ expect(mockGetEntityObject).not.toHaveBeenCalled();
183
+ });
184
+
185
+ it('judges a retried message on its newest attempt, not an older finished one', async () => {
186
+ const d = detail();
187
+ // DESC order: newest first. The newest attempt is still running.
188
+ script([d], [
189
+ { ConversationDetailID: DETAIL_ID, Status: 'Running', CompletedAt: null },
190
+ { ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() },
191
+ ]);
192
+
193
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
194
+ });
195
+
196
+ it('preserves an existing message rather than overwriting the answer', async () => {
197
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Completed', CompletedAt: longAgo() }]);
198
+ mockGetEntityObject.mockImplementation(async () => {
199
+ const w = writable();
200
+ w.Message = 'the real answer';
201
+ return w;
202
+ });
203
+
204
+ await ReconcileOrphanedConversationDetails(PROVIDER, USER);
205
+
206
+ // The loaded record already carries the answer; only the status should change.
207
+ expect(writableStore.last?.Message).toBe('the real answer');
208
+ expect(writableStore.last?.Status).toBe('Complete');
209
+ });
210
+
211
+ it('reports a refused save without throwing', async () => {
212
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }]);
213
+ mockGetEntityObject.mockImplementation(async () => writable(false));
214
+
215
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
216
+ });
217
+
218
+ it('writes as the conversation OWNER, never as the calling system user', async () => {
219
+ // REGRESSION, found only by running it against real data. The permission gate on
220
+ // MJ: Conversation Details refuses a non-owner and returns false with NO LatestResult, so
221
+ // every save was silently rejected while the reconciler reported success-shaped logs. The
222
+ // system user this pass runs as owns nothing, which is exactly who that gate stops.
223
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }]);
224
+
225
+ await ReconcileOrphanedConversationDetails(PROVIDER, USER);
226
+
227
+ expect(mockGetEntityObject).toHaveBeenCalledWith(
228
+ 'MJ: Conversation Details',
229
+ expect.objectContaining({ ID: 'OWNER-1' })
230
+ );
231
+ });
232
+
233
+ it('leaves the detail alone when no owner can be resolved', async () => {
234
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }], null);
235
+
236
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
237
+ expect(mockGetEntityObject).not.toHaveBeenCalled();
238
+ });
239
+
240
+ it('never throws out of a maintenance pass', async () => {
241
+ mockRunView.mockImplementation(async () => { throw new Error('db down'); });
242
+
243
+ await expect(ReconcileOrphanedConversationDetails(PROVIDER, USER)).resolves.toBe(0);
244
+ });
245
+
246
+ it('asks only for details that have a finished run, so stuck rows cannot fill the window', async () => {
247
+ // Details with no run, or with a run still executing, are never closed here. Read without
248
+ // this filter, 200 of them pin the oldest-first window and no closable orphan is reached.
249
+ script([detail()], [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo() }]);
250
+
251
+ await ReconcileOrphanedConversationDetails(PROVIDER, USER);
252
+
253
+ const detailCall = mockRunView.mock.calls.find(
254
+ c => (c[0] as RunViewParams).EntityName === 'MJ: Conversation Details'
255
+ );
256
+ expect((detailCall?.[0] as RunViewParams).ExtraFilter).toBe(
257
+ "Status = 'In-Progress' AND Role = 'AI' AND ID IN (SELECT [ConversationDetailID] FROM [__mj].[vwAIAgentRuns] " +
258
+ "WHERE [Status] IN ('Completed','AwaitingFeedback','Failed','Cancelled'))"
259
+ );
260
+ });
261
+
262
+ it('leaves a detail alone when something else closed it after the list query', async () => {
263
+ // The normal completion path can close the row between our read and our write. Writing
264
+ // anyway would stamp this pass's outcome over the one that actually happened.
265
+ script(
266
+ [detail()],
267
+ [{ ConversationDetailID: DETAIL_ID, Status: 'Failed', CompletedAt: longAgo(), ErrorMessage: 'late' }],
268
+ 'OWNER-1',
269
+ 'Complete'
270
+ );
271
+
272
+ const closed = await ReconcileOrphanedConversationDetails(PROVIDER, USER);
273
+
274
+ expect(closed).toBe(0);
275
+ expect(writableStore.last?.Status).toBe('Complete');
276
+ expect((writableStore.last?.Save as ReturnType<typeof vi.fn>)).not.toHaveBeenCalled();
277
+ });
278
+
279
+ it('does nothing when no message is in progress', async () => {
280
+ script([], []);
281
+ expect(await ReconcileOrphanedConversationDetails(PROVIDER, USER)).toBe(0);
282
+ });
283
+ });
@@ -13,7 +13,7 @@
13
13
  * singleton nor a database — the seam is `(candidate, users) -> resolution`.
14
14
  */
15
15
  import { describe, it, expect } from 'vitest';
16
- import { resolvePrincipalFrom, type ResolvablePrincipal } from '../auth/principals.js';
16
+ import { ResolvePrincipalFrom, type ResolvablePrincipal } from '../auth/principals.js';
17
17
 
18
18
  /**
19
19
  * The system user's ID, pinned by `UserCache` (`SYSTEM_USER_ID`) and seeded by every baseline.
@@ -63,14 +63,14 @@ const STOCK_HOST: ResolvablePrincipal[] = [SYSTEM, ANONYMOUS, HOST_ADMIN];
63
63
  describe('resolvePrincipalFrom', () => {
64
64
  describe('the shipped default (#4209)', () => {
65
65
  it('resolves the email-shaped default to the seeded System user', () => {
66
- const result = resolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
66
+ const result = ResolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
67
67
 
68
68
  expect(result.user).toBe(SYSTEM);
69
69
  expect(result.reason).toBe('email');
70
70
  });
71
71
 
72
72
  it('reports no warning when the default resolves, so a stock host logs nothing', () => {
73
- const result = resolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
73
+ const result = ResolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
74
74
 
75
75
  expect(result.warning).toBeUndefined();
76
76
  });
@@ -81,8 +81,8 @@ describe('resolvePrincipalFrom', () => {
81
81
  // `SELECT * FROM vwUsers` carries no ORDER BY and the cache is mutated in place at
82
82
  // runtime, so row order differs between boots AND within a process. Attribution must
83
83
  // not depend on it: this is what made CreatedByUserID incidental.
84
- const ordered = resolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
85
- const reversed = resolvePrincipalFrom(SHIPPED_DEFAULT, [...STOCK_HOST].reverse(), SYSTEM_USER_ID);
84
+ const ordered = ResolvePrincipalFrom(SHIPPED_DEFAULT, STOCK_HOST, SYSTEM_USER_ID);
85
+ const reversed = ResolvePrincipalFrom(SHIPPED_DEFAULT, [...STOCK_HOST].reverse(), SYSTEM_USER_ID);
86
86
 
87
87
  expect(reversed.user).toBe(ordered.user);
88
88
  });
@@ -107,7 +107,7 @@ describe('resolvePrincipalFrom', () => {
107
107
  IsActive: true,
108
108
  };
109
109
 
110
- const result = resolvePrincipalFrom('support@acme.com', [emailSupport, namedSupport], SYSTEM_USER_ID);
110
+ const result = ResolvePrincipalFrom('support@acme.com', [emailSupport, namedSupport], SYSTEM_USER_ID);
111
111
 
112
112
  expect(result.user).toBe(namedSupport);
113
113
  expect(result.reason).toBe('name');
@@ -125,15 +125,15 @@ describe('resolvePrincipalFrom', () => {
125
125
  const HIGH: ResolvablePrincipal = { ID: 'FFFFFFFF-0000-0000-0000-00000000000F', Name: 'shared.name', Email: 'high@acme.com', Type: 'User', IsActive: true };
126
126
 
127
127
  it('picks the lowest-ID user when two users share the configured Name', () => {
128
- const result = resolvePrincipalFrom('shared.name', [HIGH, LOW, SYSTEM], SYSTEM_USER_ID);
128
+ const result = ResolvePrincipalFrom('shared.name', [HIGH, LOW, SYSTEM], SYSTEM_USER_ID);
129
129
 
130
130
  expect(result.user).toBe(LOW);
131
131
  expect(result.reason).toBe('name');
132
132
  });
133
133
 
134
134
  it('resolves a duplicated Name identically however the cache is ordered', () => {
135
- const forward = resolvePrincipalFrom('shared.name', [HIGH, LOW, SYSTEM], SYSTEM_USER_ID);
136
- const reversed = resolvePrincipalFrom('shared.name', [SYSTEM, LOW, HIGH], SYSTEM_USER_ID);
135
+ const forward = ResolvePrincipalFrom('shared.name', [HIGH, LOW, SYSTEM], SYSTEM_USER_ID);
136
+ const reversed = ResolvePrincipalFrom('shared.name', [SYSTEM, LOW, HIGH], SYSTEM_USER_ID);
137
137
 
138
138
  expect(reversed.user).toBe(forward.user);
139
139
  });
@@ -145,7 +145,7 @@ describe('resolvePrincipalFrom', () => {
145
145
  const lowEmail: ResolvablePrincipal = { ...LOW, Name: 'a.person', Email: 'shared@acme.com' };
146
146
  const highEmail: ResolvablePrincipal = { ...HIGH, Name: 'b.person', Email: 'shared@acme.com' };
147
147
 
148
- const result = resolvePrincipalFrom('shared@acme.com', [highEmail, lowEmail, SYSTEM], SYSTEM_USER_ID);
148
+ const result = ResolvePrincipalFrom('shared@acme.com', [highEmail, lowEmail, SYSTEM], SYSTEM_USER_ID);
149
149
 
150
150
  expect(result.user).toBe(lowEmail);
151
151
  expect(result.reason).toBe('email');
@@ -169,7 +169,7 @@ describe('resolvePrincipalFrom', () => {
169
169
  };
170
170
 
171
171
  it('does not resolve a deactivated candidate by Email', () => {
172
- const result = resolvePrincipalFrom('departed@acme.com', [...STOCK_HOST, RETIRED], SYSTEM_USER_ID);
172
+ const result = ResolvePrincipalFrom('departed@acme.com', [...STOCK_HOST, RETIRED], SYSTEM_USER_ID);
173
173
 
174
174
  expect(result.user).toBe(SYSTEM);
175
175
  expect(result.reason).toBe('system');
@@ -180,14 +180,14 @@ describe('resolvePrincipalFrom', () => {
180
180
  // most: without it the guarantee holds only for the rungs nobody configures.
181
181
  const renamed: ResolvablePrincipal = { ...RETIRED, Name: 'ops-service-account', Email: 'ops@acme.com' };
182
182
 
183
- const result = resolvePrincipalFrom('ops-service-account', [...STOCK_HOST, renamed], SYSTEM_USER_ID);
183
+ const result = ResolvePrincipalFrom('ops-service-account', [...STOCK_HOST, renamed], SYSTEM_USER_ID);
184
184
 
185
185
  expect(result.user).toBe(SYSTEM);
186
186
  expect(result.reason).toBe('system');
187
187
  });
188
188
 
189
189
  it('reports that the candidate matched but is inactive, not that it matched nothing', () => {
190
- const result = resolvePrincipalFrom('departed@acme.com', [...STOCK_HOST, RETIRED], SYSTEM_USER_ID);
190
+ const result = ResolvePrincipalFrom('departed@acme.com', [...STOCK_HOST, RETIRED], SYSTEM_USER_ID);
191
191
 
192
192
  // "matched no user's Name or Email" would send the operator hunting for a typo in a
193
193
  // value that is spelled correctly. The fix here is to reactivate the account or name a
@@ -215,7 +215,7 @@ describe('resolvePrincipalFrom', () => {
215
215
  IsActive: true,
216
216
  };
217
217
 
218
- const result = resolvePrincipalFrom('ops@acme.com', [inactiveByName, activeByEmail], SYSTEM_USER_ID);
218
+ const result = ResolvePrincipalFrom('ops@acme.com', [inactiveByName, activeByEmail], SYSTEM_USER_ID);
219
219
 
220
220
  expect(result.user).toBe(activeByEmail);
221
221
  expect(result.reason).toBe('email');
@@ -224,14 +224,14 @@ describe('resolvePrincipalFrom', () => {
224
224
  });
225
225
  describe('fallbacks', () => {
226
226
  it('falls back to the System user rather than an arbitrary Owner when the candidate is unresolvable', () => {
227
- const result = resolvePrincipalFrom('nobody@nowhere.example', STOCK_HOST, SYSTEM_USER_ID);
227
+ const result = ResolvePrincipalFrom('nobody@nowhere.example', STOCK_HOST, SYSTEM_USER_ID);
228
228
 
229
229
  expect(result.user).toBe(SYSTEM);
230
230
  expect(result.reason).toBe('system');
231
231
  });
232
232
 
233
233
  it('warns when a configured candidate did not resolve, naming the candidate', () => {
234
- const result = resolvePrincipalFrom('nobody@nowhere.example', STOCK_HOST, SYSTEM_USER_ID);
234
+ const result = ResolvePrincipalFrom('nobody@nowhere.example', STOCK_HOST, SYSTEM_USER_ID);
235
235
 
236
236
  expect(result.warning).toContain('nobody@nowhere.example');
237
237
  });
@@ -239,7 +239,7 @@ describe('resolvePrincipalFrom', () => {
239
239
  it('falls back to the System user WITHOUT warning when no candidate is configured', () => {
240
240
  // Zod defaults this setting to '', so an unset/blank value must not be reported as a
241
241
  // misconfiguration — but it must still land somewhere deterministic.
242
- const result = resolvePrincipalFrom('', STOCK_HOST, SYSTEM_USER_ID);
242
+ const result = ResolvePrincipalFrom('', STOCK_HOST, SYSTEM_USER_ID);
243
243
 
244
244
  expect(result.user).toBe(SYSTEM);
245
245
  expect(result.warning).toBeUndefined();
@@ -256,7 +256,7 @@ describe('resolvePrincipalFrom', () => {
256
256
  };
257
257
  const users = [laterOwner, HOST_ADMIN, ANONYMOUS];
258
258
 
259
- const result = resolvePrincipalFrom('nobody@nowhere.example', users, SYSTEM_USER_ID);
259
+ const result = ResolvePrincipalFrom('nobody@nowhere.example', users, SYSTEM_USER_ID);
260
260
 
261
261
  expect(result.user).toBe(HOST_ADMIN);
262
262
  expect(result.reason).toBe('owner');
@@ -271,7 +271,7 @@ describe('resolvePrincipalFrom', () => {
271
271
  IsActive: false,
272
272
  };
273
273
 
274
- const result = resolvePrincipalFrom('nobody@nowhere.example', [inactiveOwner, HOST_ADMIN], SYSTEM_USER_ID);
274
+ const result = ResolvePrincipalFrom('nobody@nowhere.example', [inactiveOwner, HOST_ADMIN], SYSTEM_USER_ID);
275
275
 
276
276
  expect(result.user).toBe(HOST_ADMIN);
277
277
  });
@@ -281,7 +281,7 @@ describe('resolvePrincipalFrom', () => {
281
281
  // "we never act as a disabled user, except as the one user we reach first".
282
282
  const inactiveSystem: ResolvablePrincipal = { ...SYSTEM, IsActive: false };
283
283
 
284
- const result = resolvePrincipalFrom('nobody@nowhere.example', [inactiveSystem, HOST_ADMIN], SYSTEM_USER_ID);
284
+ const result = ResolvePrincipalFrom('nobody@nowhere.example', [inactiveSystem, HOST_ADMIN], SYSTEM_USER_ID);
285
285
 
286
286
  expect(result.user).toBe(HOST_ADMIN);
287
287
  expect(result.reason).toBe('owner');
@@ -290,13 +290,13 @@ describe('resolvePrincipalFrom', () => {
290
290
  it('returns no principal when the system user is deactivated and no active Owner remains', () => {
291
291
  const inactiveSystem: ResolvablePrincipal = { ...SYSTEM, IsActive: false };
292
292
 
293
- const result = resolvePrincipalFrom('nobody@nowhere.example', [inactiveSystem, ANONYMOUS], SYSTEM_USER_ID);
293
+ const result = ResolvePrincipalFrom('nobody@nowhere.example', [inactiveSystem, ANONYMOUS], SYSTEM_USER_ID);
294
294
 
295
295
  expect(result.user).toBeNull();
296
296
  });
297
297
 
298
298
  it('returns no principal at all when the cache is empty', () => {
299
- const result = resolvePrincipalFrom(SHIPPED_DEFAULT, [], SYSTEM_USER_ID);
299
+ const result = ResolvePrincipalFrom(SHIPPED_DEFAULT, [], SYSTEM_USER_ID);
300
300
 
301
301
  expect(result.user).toBeNull();
302
302
  expect(result.reason).toBe('none');