@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
@@ -1,7 +1,57 @@
1
1
  import { Field, ObjectType, Resolver, Root, Subscription } from 'type-graphql';
2
+ import { Metadata, UserInfo } from '@memberjunction/core';
2
3
 
3
4
  export const CACHE_INVALIDATION_TOPIC = 'CACHE_INVALIDATION';
4
5
 
6
+ /**
7
+ * Entities whose rows may ride along with an invalidation event. Empty until the server entry point
8
+ * supplies the configured list, so the default — including in any process that never configures it
9
+ * — is to broadcast nothing.
10
+ */
11
+ let allowedRecordDataEntities: readonly string[] = [];
12
+
13
+ /**
14
+ * Supply the configured allowlist. Called once from the server entry point during bootstrap.
15
+ *
16
+ * INJECTED rather than read from `configInfo` here, deliberately. `config.ts` runs `loadConfig()` at
17
+ * module scope, so importing it from this module would drag full config validation into every
18
+ * import chain that touches the resolver — including unit tests, which have no server config and
19
+ * would fail at import with "Configuration validation failed". This module deliberately imports
20
+ * nothing from MJServer itself — only type-graphql and `@memberjunction/core` (for the entity
21
+ * permission check in the subscription filter), neither of which loads config.
22
+ */
23
+ export function ConfigureRecordDataBroadcast(entities: readonly string[] | undefined | null): void {
24
+ // Normalised HERE, once, rather than at each comparison. The wildcard used to be matched raw
25
+ // (`includes('*')`) while entity names were trimmed and lowercased two lines below, so a
26
+ // hand-edited `[' * ']` matched neither branch and silently opted nothing in — a config that
27
+ // looks like it disables the gate while leaving it fully on. Empty entries are dropped so a
28
+ // stray `''` cannot match an entity whose name is somehow blank.
29
+ allowedRecordDataEntities = (entities ?? [])
30
+ .map((e) => e.trim().toLowerCase())
31
+ .filter((e) => e.length > 0);
32
+ }
33
+
34
+ /**
35
+ * May the full row of `entityName` be broadcast alongside its cache-invalidation event?
36
+ *
37
+ * The subscription below is filtered only by ENTITY-level read permission, so anything included
38
+ * here is disclosed to every signed-in session that may read the entity at all — regardless of
39
+ * row-level security or any tenant scoping a consumer applies on the read path, neither of which
40
+ * this push traverses. The answer is therefore NO unless a deployment has explicitly opted the
41
+ * entity in via `cacheSettings.recordDataBroadcastEntities`.
42
+ *
43
+ * Callers that get `false` simply omit `recordData`; the event still carries the entity name and
44
+ * primary key, so clients evict and re-fetch through the normal access-controlled read path.
45
+ *
46
+ * Matching is exact (trimmed, case-insensitive) rather than substring: a loose match would let an
47
+ * allowlisted name drag in every entity whose name contains it.
48
+ */
49
+ export function MayBroadcastRecordData(entityName: string): boolean {
50
+ if (allowedRecordDataEntities.length === 0) return false;
51
+ if (allowedRecordDataEntities.includes('*')) return true;
52
+ return allowedRecordDataEntities.includes(entityName.trim().toLowerCase());
53
+ }
54
+
5
55
  @ObjectType()
6
56
  export class CacheInvalidationNotification {
7
57
  @Field(() => String)
@@ -40,17 +90,111 @@ export interface CacheInvalidationPayload {
40
90
  recordData?: string;
41
91
  }
42
92
 
93
+ /**
94
+ * What the subscription filter needs off the WS connection.
95
+ *
96
+ * Declared structurally rather than importing MJServer's `UserPayload`, which reaches `types.ts`
97
+ * and through it `auth/index.js` and config. Keeping this module's imports minimal is not fussiness
98
+ * — importing `configInfo` here is what previously broke every unit test that touched the resolver,
99
+ * and the import-surface test in this package exists to keep it that way.
100
+ */
101
+ export interface CacheInvalidationFilterContext {
102
+ userPayload?: { userRecord?: unknown };
103
+ }
104
+
105
+ /**
106
+ * Should this subscriber hear about this event at all?
107
+ *
108
+ * Entity-level narrowing, raised in review: an event for an entity a session has no read
109
+ * permission on tells that session the record exists, its stable key, and when it changed — and
110
+ * the session could never have read it anyway, so nothing is lost by withholding it. This is a
111
+ * monotonic reduction in disclosure that needs no tenancy model, which is why it is here and the
112
+ * row-level question is not.
113
+ *
114
+ * What it does NOT close: two sessions that both hold read permission on an entity still see each
115
+ * other's keys, because what separates them is row-level. That predicate is tracked separately —
116
+ * see the class docblock below.
117
+ *
118
+ * `GetUserPermisions` is an in-memory match of the entity's permission rows against the user's
119
+ * roles, so this stays cheap enough to run per subscriber per event. It is not free: it runs for
120
+ * every connected socket on every save, so keep it free of I/O.
121
+ */
122
+ export function CacheInvalidationFilter(data: {
123
+ payload: CacheInvalidationPayload;
124
+ context: CacheInvalidationFilterContext | undefined;
125
+ }): boolean {
126
+ const user = data.context?.userPayload?.userRecord as UserInfo | undefined;
127
+ // FAIL CLOSED on identity, matching `statusUpdatesFilter`. `onConnect` rejects a socket whose
128
+ // token does not validate, so a subscriber with no user record should not exist; if one does,
129
+ // it is the case least deserving of an unfiltered firehose.
130
+ if (!user) {
131
+ return false;
132
+ }
133
+
134
+ const entityName = data.payload?.entityName;
135
+ if (!entityName) {
136
+ return false;
137
+ }
138
+
139
+ // A pub/sub subscription filter has no per-request provider to thread: it runs on the server's
140
+ // own metadata, which is the one place the entity's permission rows can be answered from.
141
+ const entity = new Metadata().EntityByName(entityName); // global-provider-ok: subscription filter — server-wide metadata, no request-scoped provider exists here
142
+ // DELIVER when the entity is unknown here, deliberately — the opposite of the choice above.
143
+ // A name absent from metadata cannot be permission-checked, and withholding would silently
144
+ // stop invalidating a legitimately cacheable entity, which is a correctness bug rather than a
145
+ // disclosure one. All it discloses is the name and key of something this server cannot
146
+ // describe.
147
+ if (!entity) {
148
+ return true;
149
+ }
150
+
151
+ return entity.GetUserPermisions(user).CanRead;
152
+ }
153
+
154
+ /** @deprecated Use {@link CacheInvalidationFilter}. */
155
+ export function cacheInvalidationFilter(data: {
156
+ payload: CacheInvalidationPayload;
157
+ context: CacheInvalidationFilterContext | undefined;
158
+ }): boolean {
159
+ return CacheInvalidationFilter(data);
160
+ }
161
+
43
162
  @Resolver()
44
163
  export class CacheInvalidationResolver {
45
164
  /**
46
- * Subscription that broadcasts cache invalidation events to ALL connected clients.
47
- * No session filter — every browser connected via WebSocket receives every event.
48
- * This enables cross-server cache invalidation to propagate to browser clients.
165
+ * Subscription that broadcasts cache invalidation events to connected clients, so cross-server
166
+ * cache invalidation reaches browsers.
167
+ *
168
+ * Delivery is filtered only by ENTITY-level read permission (see {@link CacheInvalidationFilter}).
169
+ * Every session that may read an entity at all still receives every event for it, from every
170
+ * other session and tenant.
171
+ *
172
+ * Because that narrowing stops well short of row level, `RecordData` is only populated for entities a
173
+ * deployment has opted in through `cacheSettings.recordDataBroadcastEntities` — see
174
+ * {@link MayBroadcastRecordData}, applied at both publish sites.
175
+ *
176
+ * `EntityName`, `PrimaryKeyValues`, `Action` and `Timestamp` ARE still broadcast to every
177
+ * session, and that is a disclosure, not a safe residue. An earlier version of this comment
178
+ * called them "safe to broadcast" on the grounds that reading the record still goes through
179
+ * the access-controlled path — true of the row's CONTENTS, and beside the point for the
180
+ * metadata itself. A session with no relationship to a record still learns that it exists,
181
+ * its stable key, and every time it changes; across entities it learns the cadence and volume
182
+ * of another tenant's activity.
183
+ *
184
+ * The key is kept deliberately, because consumers need it to re-read the record they were
185
+ * told about (see `ResolveEntityEventKey` in @memberjunction/core) and because the alternative
186
+ * — a coarse "something in this entity changed" — forces whole-entity invalidation on the
187
+ * hottest write paths. That is a trade, not an absence of cost. Narrowing it means filtering
188
+ * delivery per subscriber, which needs an authorization predicate cheap enough to run per
189
+ * subscriber per event; entity-level permissions (`EntityInfo.GetUserPermisions`, an in-memory
190
+ * role match) would cover part of it, row-level tenancy would not.
49
191
  */
50
192
  @Subscription(() => CacheInvalidationNotification, {
51
193
  topics: CACHE_INVALIDATION_TOPIC,
194
+ filter: (data: { payload: CacheInvalidationPayload; context: CacheInvalidationFilterContext | undefined }) =>
195
+ CacheInvalidationFilter(data),
52
196
  })
53
- cacheInvalidation(
197
+ cacheInvalidation( // case-violation-ok-legacy-back-compat: the property name is the GraphQL schema field name — renaming it breaks every client query
54
198
  @Root() payload: CacheInvalidationPayload
55
199
  ): CacheInvalidationNotification {
56
200
  return {
@@ -50,9 +50,16 @@ export class DeleteOptionsInput {
50
50
  * schema-sync gate requires `DeleteOptionsInput` to carry every `EntityDeleteOptions` field;
51
51
  * {@link DeleteOptionsInput.SanitizeFromWire} forces it back to false on every request. See
52
52
  * that method for why.
53
+ *
54
+ * **Optional on the wire, on purpose.** This field did not exist in 5.51.x, and every other
55
+ * field here is required, so making it required too turned a 6.1 upgrade into a hard failure
56
+ * for every client that spells out `options___` on a delete (`Field
57
+ * "DeleteOptionsInput.SkipRecordChanges" of required type "Boolean!" was not provided`). A
58
+ * 5.51 client that omits it and a 6.1 client that sends `false` mean the same thing, and the
59
+ * server forces it to `false` regardless, so `null`/absent defaults to `false` here.
53
60
  */
54
- @Field(() => Boolean)
55
- SkipRecordChanges: boolean;
61
+ @Field(() => Boolean, { nullable: true, defaultValue: false })
62
+ SkipRecordChanges?: boolean;
56
63
 
57
64
  /**
58
65
  * Strips wire-unreachable capabilities from client-supplied delete options.
@@ -1,9 +1,23 @@
1
1
  import { PubSubEngine } from 'type-graphql';
2
2
  import { LogError } from '@memberjunction/core';
3
- import { publishStatusUpdate } from './PushStatusResolver.js';
3
+ import { PublishStatusUpdate } from './PushStatusResolver.js';
4
4
 
5
- /** Default cadence for fire-and-forget liveness pulses (5 minutes). */
6
- export const DEFAULT_PULSE_INTERVAL_MS = 5 * 60 * 1000;
5
+ /**
6
+ * Default cadence for fire-and-forget liveness pulses (60 seconds).
7
+ *
8
+ * MATCHED PAIR — do not change alone. The client's inactivity window
9
+ * (`DEFAULT_IDLE_TIMEOUT_MS` in GraphQLDataProvider's fireAndForgetHelper) is sized at roughly
10
+ * 3x this value, so a healthy run always refreshes the timer well before it expires. Lengthening
11
+ * this without lengthening that window makes every quiet stretch of a long agent run look like a
12
+ * stall and trips reconciliation; shortening the window without shortening this does the same.
13
+ *
14
+ * WAS 5 MINUTES (MJ #4222). Paired with the old 12-minute window, a client whose transport had
15
+ * silently died waited 12 minutes to notice — on runs that typically finish in 20 seconds. The
16
+ * pulse is a few dozen bytes and exists only while an operation is actually running, so the
17
+ * added traffic is negligible: a 40-minute run now sends ~40 pulses instead of ~8, and the
18
+ * 20-second run that motivated the change still sends none.
19
+ */
20
+ export const DEFAULT_PULSE_INTERVAL_MS = 60 * 1000;
7
21
 
8
22
  /** The `type` discriminator carried by liveness pulse messages. */
9
23
  export const HEARTBEAT_MESSAGE_TYPE = 'Heartbeat';
@@ -15,38 +29,38 @@ export const HEARTBEAT_MESSAGE_TYPE = 'Heartbeat';
15
29
  */
16
30
  export interface PulseStatus {
17
31
  /** Primary key of the persisted run record (AIAgentRun, TestRun, TestSuiteRun). */
18
- runId?: string;
32
+ runId?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
19
33
  /** Current run status, e.g. 'Running'. */
20
- status?: string;
34
+ status?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
21
35
  /** Optional human-readable current step for UI display. */
22
- currentStep?: string;
36
+ currentStep?: string; // case-violation-ok-legacy-back-compat: optional, and the old name is also read off a value the checker cannot type; renaming it stays assignable and silently yields undefined
23
37
  }
24
38
 
25
39
  /** Handle returned by {@link startLivenessPulse}; call `stop()` when the work settles. */
26
40
  export interface LivenessPulseHandle {
27
- stop(): void;
41
+ Stop(): void;
28
42
  }
29
43
 
30
44
  export interface LivenessPulseOptions {
31
45
  /** PubSub engine used to publish on the shared push-status topic. */
32
- pubSub: PubSubEngine;
46
+ pubSub: PubSubEngine; // case-violation-ok-legacy-back-compat: renaming it broke a use the checker could not see from the declaration — the compile proved it
33
47
  /** Session the client is subscribed on (used by the subscription filter). */
34
- sessionId: string;
48
+ sessionId: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined
35
49
  /**
36
50
  * Authenticated user the operation belongs to (B49). Stamped on every pulse so the
37
51
  * subscription filter binds delivery to identity, not just the client-chosen sessionId.
38
52
  */
39
- ownerUserId: string;
53
+ ownerUserId: string; // case-violation-ok-legacy-back-compat: renaming it broke a use the checker could not see from the declaration — the compile proved it
40
54
  /** Resolver label echoed in the message envelope (e.g. 'RunAIAgentResolver'). */
41
- resolver: string;
55
+ resolver: string; // case-violation-ok-legacy-back-compat: the old name is also read off a value typed `any`, where a rename would compile and silently return undefined
42
56
  /** Pulse cadence in ms. Defaults to {@link DEFAULT_PULSE_INTERVAL_MS}. */
43
- intervalMs?: number;
57
+ intervalMs?: number; // case-violation-ok-legacy-back-compat: renaming it broke a use the checker could not see from the declaration — the compile proved it
44
58
  /**
45
59
  * Optional cheap status reader invoked on each tick. Should read from an
46
60
  * in-memory ref, not the database. Errors are swallowed so a transient read
47
61
  * never kills the pulse loop.
48
62
  */
49
- readStatus?: () => PulseStatus | undefined;
63
+ readStatus?: () => PulseStatus | undefined; // case-violation-ok-legacy-back-compat: renaming it broke a use the checker could not see from the declaration — the compile proved it
50
64
  }
51
65
 
52
66
  /**
@@ -60,7 +74,7 @@ export interface LivenessPulseOptions {
60
74
  * (`{ message: JSON.stringify({ resolver, type, status, data }), sessionId }`),
61
75
  * so the client receives it through the same subscription with no special parsing.
62
76
  */
63
- export function startLivenessPulse(options: LivenessPulseOptions): LivenessPulseHandle {
77
+ export function StartLivenessPulse(options: LivenessPulseOptions): LivenessPulseHandle {
64
78
  const { pubSub, sessionId, ownerUserId, resolver, readStatus } = options;
65
79
  const intervalMs = options.intervalMs ?? DEFAULT_PULSE_INTERVAL_MS;
66
80
 
@@ -73,7 +87,7 @@ export function startLivenessPulse(options: LivenessPulseOptions): LivenessPulse
73
87
  LogError(`[LivenessPulse:${resolver}] readStatus failed: ${(e as Error).message}`);
74
88
  }
75
89
 
76
- publishStatusUpdate(pubSub, {
90
+ PublishStatusUpdate(pubSub, {
77
91
  sessionId,
78
92
  ownerUserId,
79
93
  message: JSON.stringify({
@@ -86,6 +100,11 @@ export function startLivenessPulse(options: LivenessPulseOptions): LivenessPulse
86
100
  }, intervalMs);
87
101
 
88
102
  return {
89
- stop: () => clearInterval(timer),
103
+ Stop: () => clearInterval(timer),
90
104
  };
91
105
  }
106
+
107
+ /** @deprecated Use {@link StartLivenessPulse}. */
108
+ export function startLivenessPulse(options: LivenessPulseOptions): LivenessPulseHandle {
109
+ return StartLivenessPulse(options);
110
+ }
@@ -0,0 +1,267 @@
1
+ import { LogError, LogStatus, RunView } from '@memberjunction/core';
2
+ import { UUIDsEqual } from '@memberjunction/global';
3
+ import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
+ import type { IMetadataProvider, UserInfo } from '@memberjunction/core';
5
+ import type { MJAIAgentRunEntity, MJConversationDetailEntity } from '@memberjunction/core-entities';
6
+
7
+ /**
8
+ * Grace period after a run reaches a terminal state before its conversation detail is considered
9
+ * abandoned. The normal path closes the detail moments after the run finishes; this waits well past
10
+ * that so a reconcile can never race a completion that is already on its way.
11
+ */
12
+ export const ORPHAN_DETAIL_GRACE_MS = 2 * 60 * 1000;
13
+
14
+ /** Ceiling on details closed per pass, so a large backlog cannot stall startup. */
15
+ const MAX_DETAILS_PER_PASS = 200;
16
+
17
+ /**
18
+ * Agent-run statuses that mean the run succeeded. `AwaitingFeedback` is the normal end of a chat
19
+ * turn: the agent replied and the next move is the user's.
20
+ */
21
+ const SUCCESSFUL_RUN_STATUSES: MJAIAgentRunEntity['Status'][] = ['Completed', 'AwaitingFeedback'];
22
+
23
+ /**
24
+ * Agent-run statuses that mean execution is over, whatever the outcome. `Paused` is absent: the
25
+ * run is parked on a workflow that is still executing.
26
+ */
27
+ const TERMINAL_RUN_STATUSES: MJAIAgentRunEntity['Status'][] = [...SUCCESSFUL_RUN_STATUSES, 'Failed', 'Cancelled'];
28
+
29
+ /**
30
+ * Close conversation details left `In-Progress` by a run that is already over (MJ #4222).
31
+ *
32
+ * WHY THIS EXISTS. `AgentRunner` closes the detail as the final step of a run, so a process that
33
+ * dies mid-run never reaches it. The agent-run watchdog repairs the RUN — force-failing it once the
34
+ * heartbeat goes stale — but nothing repairs the detail, and the detail is the row the chat renders
35
+ * from. The result is durable state that disagrees with itself: a terminal run beside a message
36
+ * that still says it is being generated, spinning forever for anyone who opens it.
37
+ *
38
+ * WHY NOT IN THE SWEEP PROC. `spSweepStaleAIAgentRuns` is a single set-based UPDATE, and extending
39
+ * it to the detail would be the obvious fix. It would also be the wrong one: `MJ: Conversation
40
+ * Details` declares `TrustServerCacheCompletely`, so the server's RunView cache relies on
41
+ * `BaseEntity` events for invalidation and a stored procedure fires none. The row would change
42
+ * while every instance kept serving `In-Progress` from cache — a worse failure than the one being
43
+ * fixed, because reconciliation itself would read the stale value back. Going through
44
+ * `BaseEntity.Save()` costs a per-row write and buys correct invalidation everywhere.
45
+ *
46
+ * Idempotent and safe to run concurrently on several instances: the write is a no-op once the
47
+ * status is terminal, and each pass re-reads state before deciding.
48
+ *
49
+ * @returns How many details were closed.
50
+ */
51
+ export async function ReconcileOrphanedConversationDetails(
52
+ provider: IMetadataProvider,
53
+ contextUser: UserInfo,
54
+ graceMs: number = ORPHAN_DETAIL_GRACE_MS
55
+ ): Promise<number> {
56
+ try {
57
+ const rv = RunView.FromMetadataProvider(provider);
58
+
59
+ const detailResult = await rv.RunView<MJConversationDetailEntity>(
60
+ {
61
+ EntityName: 'MJ: Conversation Details',
62
+ ExtraFilter: `Status = 'In-Progress' AND Role = 'AI'${hasTerminalRunClause(provider)}`,
63
+ OrderBy: '__mj_CreatedAt ASC',
64
+ MaxRows: MAX_DETAILS_PER_PASS,
65
+ ResultType: 'entity_object',
66
+ },
67
+ contextUser
68
+ );
69
+ if (!detailResult.Success || !detailResult.Results?.length) {
70
+ return 0;
71
+ }
72
+
73
+ const runsByDetail = await loadTerminalRuns(rv, detailResult.Results.map(d => d.ID), contextUser);
74
+ if (runsByDetail.size === 0) {
75
+ return 0;
76
+ }
77
+
78
+ const ownersByConversation = await loadConversationOwners(
79
+ rv,
80
+ detailResult.Results.map(d => d.ConversationID).filter((id): id is string => !!id),
81
+ contextUser
82
+ );
83
+
84
+ const cutoff = Date.now() - graceMs;
85
+ let closed = 0;
86
+
87
+ for (const detail of detailResult.Results) {
88
+ const run = runsByDetail.get(detail.ID);
89
+ if (!run) {
90
+ // No run yet, or its run is still executing. Both are legitimately in progress.
91
+ continue;
92
+ }
93
+ const completedAt = run.CompletedAt ? new Date(run.CompletedAt).getTime() : NaN;
94
+ if (!Number.isFinite(completedAt) || completedAt > cutoff) {
95
+ // Inside the grace window — the owning process may still be closing this itself.
96
+ continue;
97
+ }
98
+
99
+ // Write as the conversation's OWNER, not as the system user.
100
+ //
101
+ // `MJConversationDetailEntityExtended.Save()` gates writes on conversation ownership: a
102
+ // non-owner without a resource grant is refused, returning false with no result
103
+ // registered anywhere — indistinguishable from a crash, and the reason this pass
104
+ // silently wrote nothing at all until it was traced. The system user owns no
105
+ // conversations, so it is exactly the caller that gate exists to stop. Saving as the
106
+ // owner satisfies the gate on its own terms rather than routing around it, and keeps
107
+ // the write attributable to the person whose message it is.
108
+ const owner = detail.ConversationID ? ownersByConversation.get(detail.ConversationID) : undefined;
109
+ if (!owner) {
110
+ LogError(`[OrphanDetailReconciler] No owner resolved for conversation ${detail.ConversationID}; leaving detail ${detail.ID}`);
111
+ continue;
112
+ }
113
+ const writable = await provider.GetEntityObject<MJConversationDetailEntity>('MJ: Conversation Details', owner);
114
+ if (!(await writable.Load(detail.ID))) {
115
+ LogError(`[OrphanDetailReconciler] Could not load detail ${detail.ID} for write`);
116
+ continue;
117
+ }
118
+ if (writable.Status !== 'In-Progress') {
119
+ // Closed by something else since the list query. Its outcome stands.
120
+ continue;
121
+ }
122
+ // The failure marker belongs only to the error branch. A completed run has nothing to
123
+ // report, so an empty message stays empty rather than contradicting its own status.
124
+ if (SUCCESSFUL_RUN_STATUSES.includes(run.Status)) {
125
+ writable.Status = 'Complete';
126
+ } else {
127
+ writable.Status = 'Error';
128
+ if (!writable.Message) {
129
+ writable.Message = run.ErrorMessage ?? '❌ Failed';
130
+ }
131
+ }
132
+
133
+ if (await writable.Save()) {
134
+ closed++;
135
+ } else {
136
+ const result = writable.LatestResult;
137
+ LogError(
138
+ `[OrphanDetailReconciler] Failed to close detail ${detail.ID}: ` +
139
+ `${result?.CompleteMessage || result?.Message || 'no message'}`
140
+ );
141
+ }
142
+ }
143
+
144
+ if (closed > 0) {
145
+ LogStatus(`[OrphanDetailReconciler] Closed ${closed} conversation detail(s) left behind by finished runs`);
146
+ }
147
+ return closed;
148
+ } catch (err) {
149
+ // Maintenance work must never take the server down with it.
150
+ LogError(`[OrphanDetailReconciler] Pass failed: ${err instanceof Error ? err.message : String(err)}`);
151
+ return 0;
152
+ }
153
+ }
154
+
155
+ /** The quoting a database provider offers. Present on every server-side provider. */
156
+ interface QuotingProvider {
157
+ QuoteIdentifier(name: string): string;
158
+ QuoteSchemaAndView(schemaName: string, objectName: string): string;
159
+ }
160
+
161
+ function isQuotingProvider(provider: IMetadataProvider): provider is IMetadataProvider & QuotingProvider {
162
+ const p = provider as IMetadataProvider & Partial<QuotingProvider>;
163
+ return typeof p.QuoteIdentifier === 'function' && typeof p.QuoteSchemaAndView === 'function';
164
+ }
165
+
166
+ /**
167
+ * Filter clause that keeps only details with at least one finished run.
168
+ *
169
+ * Details with no run, or whose run is still executing, are never closed here. Without this clause
170
+ * they fill the oldest-first window, and once 200 of them accumulate no closable orphan is reached.
171
+ * The newest-run check in {@link loadTerminalRuns} still decides each remaining detail.
172
+ *
173
+ * @returns An ` AND ...` clause, or an empty string when the provider cannot quote names.
174
+ */
175
+ function hasTerminalRunClause(provider: IMetadataProvider): string {
176
+ const runs = provider.EntityByName('MJ: AI Agent Runs');
177
+ if (!runs || !isQuotingProvider(provider)) {
178
+ return '';
179
+ }
180
+ const statuses = TERMINAL_RUN_STATUSES.map(s => `'${s}'`).join(',');
181
+ return (
182
+ ` AND ID IN (SELECT ${provider.QuoteIdentifier('ConversationDetailID')}` +
183
+ ` FROM ${provider.QuoteSchemaAndView(runs.SchemaName, runs.BaseView)}` +
184
+ ` WHERE ${provider.QuoteIdentifier('Status')} IN (${statuses}))`
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Newest terminal run per conversation detail. Details whose newest run is still executing are
190
+ * deliberately absent, so a retried message is judged on its latest attempt rather than an older
191
+ * finished one.
192
+ */
193
+ async function loadTerminalRuns(
194
+ rv: RunView,
195
+ detailIds: string[],
196
+ contextUser: UserInfo
197
+ ): Promise<Map<string, MJAIAgentRunEntity>> {
198
+ const quoted = detailIds.map(id => `'${id.replace(/'/g, "''")}'`).join(',');
199
+ const result = await rv.RunView<MJAIAgentRunEntity>(
200
+ {
201
+ EntityName: 'MJ: AI Agent Runs',
202
+ ExtraFilter: `ConversationDetailID IN (${quoted})`,
203
+ OrderBy: '__mj_CreatedAt DESC',
204
+ ResultType: 'entity_object',
205
+ },
206
+ contextUser
207
+ );
208
+ if (!result.Success) {
209
+ return new Map();
210
+ }
211
+
212
+ const newest = new Map<string, MJAIAgentRunEntity>();
213
+ for (const run of result.Results ?? []) {
214
+ const detailId = run.ConversationDetailID;
215
+ if (!detailId || newest.has(detailId)) {
216
+ continue; // DESC order puts the newest run first
217
+ }
218
+ newest.set(detailId, run);
219
+ }
220
+
221
+ // Drop any detail whose newest attempt is still running.
222
+ for (const [detailId, run] of newest) {
223
+ if (!TERMINAL_RUN_STATUSES.includes(run.Status)) {
224
+ newest.delete(detailId);
225
+ }
226
+ }
227
+ return newest;
228
+ }
229
+
230
+ /**
231
+ * Owner `UserInfo` per conversation, resolved through {@link UserCache}. The reconciler writes as
232
+ * the owner so the conversation-detail permission gate is satisfied on its own terms.
233
+ */
234
+ async function loadConversationOwners(
235
+ rv: RunView,
236
+ conversationIds: string[],
237
+ contextUser: UserInfo
238
+ ): Promise<Map<string, UserInfo>> {
239
+ const owners = new Map<string, UserInfo>();
240
+ if (conversationIds.length === 0) {
241
+ return owners;
242
+ }
243
+ const quoted = [...new Set(conversationIds)].map(id => `'${id.replace(/'/g, "''")}'`).join(',');
244
+ const result = await rv.RunView<{ ID: string; UserID: string }>(
245
+ {
246
+ EntityName: 'MJ: Conversations',
247
+ ExtraFilter: `ID IN (${quoted})`,
248
+ Fields: ['ID', 'UserID'],
249
+ ResultType: 'simple',
250
+ },
251
+ contextUser
252
+ );
253
+ if (!result.Success) {
254
+ return owners;
255
+ }
256
+ const users = UserCache.Instance.Users;
257
+ for (const row of result.Results ?? []) {
258
+ if (!row.UserID) {
259
+ continue;
260
+ }
261
+ const user = users.find(u => UUIDsEqual(u.ID, row.UserID));
262
+ if (user) {
263
+ owners.set(row.ID, user);
264
+ }
265
+ }
266
+ return owners;
267
+ }