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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/README.md +69 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
  4. package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
  5. package/dist/agentSessions/BridgeHostInstance.js +14 -4
  6. package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
  7. package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
  8. package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
  9. package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
  10. package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
  11. package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
  12. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
  13. package/dist/agentSessions/SessionJanitor.d.ts +31 -8
  14. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  15. package/dist/agentSessions/SessionJanitor.js +85 -41
  16. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  17. package/dist/agentSessions/SessionManager.d.ts +14 -4
  18. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  19. package/dist/agentSessions/SessionManager.js +28 -4
  20. package/dist/agentSessions/SessionManager.js.map +1 -1
  21. package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
  22. package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
  23. package/dist/agentSessions/conversationBrowsers.js +118 -0
  24. package/dist/agentSessions/conversationBrowsers.js.map +1 -0
  25. package/dist/agentSessions/sessionDeadline.d.ts +43 -0
  26. package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
  27. package/dist/agentSessions/sessionDeadline.js +68 -0
  28. package/dist/agentSessions/sessionDeadline.js.map +1 -0
  29. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  30. package/dist/auth/APIKeyScopeAuth.js +5 -4
  31. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  32. package/dist/auth/loginEmailResolution.d.ts +41 -0
  33. package/dist/auth/loginEmailResolution.d.ts.map +1 -0
  34. package/dist/auth/loginEmailResolution.js +47 -0
  35. package/dist/auth/loginEmailResolution.js.map +1 -0
  36. package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
  37. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
  38. package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
  39. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
  40. package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
  41. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  42. package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
  43. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  44. package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
  45. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  46. package/dist/auth/magicLink/MagicLinkService.js +221 -9
  47. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  48. package/dist/auth/magicLink/index.d.ts +1 -1
  49. package/dist/auth/magicLink/index.d.ts.map +1 -1
  50. package/dist/auth/magicLink/index.js +1 -1
  51. package/dist/auth/magicLink/index.js.map +1 -1
  52. package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
  53. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
  54. package/dist/auth/magicLink/redeemLanding.js +7 -4
  55. package/dist/auth/magicLink/redeemLanding.js.map +1 -1
  56. package/dist/auth/magicLink/types.d.ts +27 -3
  57. package/dist/auth/magicLink/types.d.ts.map +1 -1
  58. package/dist/auth/magicLink/types.js +0 -4
  59. package/dist/auth/magicLink/types.js.map +1 -1
  60. package/dist/auth/newUsers.d.ts.map +1 -1
  61. package/dist/auth/newUsers.js +1 -0
  62. package/dist/auth/newUsers.js.map +1 -1
  63. package/dist/backgroundSweepConfigUnits.d.ts +231 -0
  64. package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
  65. package/dist/backgroundSweepConfigUnits.js +180 -0
  66. package/dist/backgroundSweepConfigUnits.js.map +1 -0
  67. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
  68. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
  69. package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
  70. package/dist/config.d.ts +906 -8
  71. package/dist/config.d.ts.map +1 -1
  72. package/dist/config.js +59 -0
  73. package/dist/config.js.map +1 -1
  74. package/dist/context.d.ts +2 -0
  75. package/dist/context.d.ts.map +1 -1
  76. package/dist/context.js +41 -12
  77. package/dist/context.js.map +1 -1
  78. package/dist/coreRoutes.d.ts +3 -1
  79. package/dist/coreRoutes.d.ts.map +1 -1
  80. package/dist/coreRoutes.js +6 -4
  81. package/dist/coreRoutes.js.map +1 -1
  82. package/dist/generated/graphql-schemas/__mj.d.ts +1072 -5
  83. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  84. package/dist/generated/graphql-schemas/__mj.js +10174 -4618
  85. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  86. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
  87. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
  88. package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
  89. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
  90. package/dist/generic/ResolverBase.d.ts +50 -5
  91. package/dist/generic/ResolverBase.d.ts.map +1 -1
  92. package/dist/generic/ResolverBase.js +329 -132
  93. package/dist/generic/ResolverBase.js.map +1 -1
  94. package/dist/generic/RunViewResolver.d.ts +4 -0
  95. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  96. package/dist/generic/RunViewResolver.js +47 -7
  97. package/dist/generic/RunViewResolver.js.map +1 -1
  98. package/dist/globalCors.d.ts.map +1 -1
  99. package/dist/globalCors.js +1 -0
  100. package/dist/globalCors.js.map +1 -1
  101. package/dist/index.d.ts +3 -0
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +201 -43
  104. package/dist/index.js.map +1 -1
  105. package/dist/integration/ConnectionPause.d.ts +121 -0
  106. package/dist/integration/ConnectionPause.d.ts.map +1 -0
  107. package/dist/integration/ConnectionPause.js +183 -0
  108. package/dist/integration/ConnectionPause.js.map +1 -0
  109. package/dist/integration/CustomColumnPromoter.d.ts +72 -6
  110. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  111. package/dist/integration/CustomColumnPromoter.js +87 -46
  112. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  113. package/dist/integration/EntityMapLifecycle.d.ts +78 -4
  114. package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
  115. package/dist/integration/EntityMapLifecycle.js +199 -5
  116. package/dist/integration/EntityMapLifecycle.js.map +1 -1
  117. package/dist/integration/InactiveRowWarnings.d.ts +31 -0
  118. package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
  119. package/dist/integration/InactiveRowWarnings.js +29 -0
  120. package/dist/integration/InactiveRowWarnings.js.map +1 -1
  121. package/dist/integration/ObjectNamesByID.d.ts +28 -0
  122. package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
  123. package/dist/integration/ObjectNamesByID.js +87 -0
  124. package/dist/integration/ObjectNamesByID.js.map +1 -0
  125. package/dist/integration/PostRestartIntrospect.d.ts +26 -0
  126. package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
  127. package/dist/integration/PostRestartIntrospect.js +27 -0
  128. package/dist/integration/PostRestartIntrospect.js.map +1 -0
  129. package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
  130. package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
  131. package/dist/integration/RSUPostRestartProgress.js +323 -0
  132. package/dist/integration/RSUPostRestartProgress.js.map +1 -0
  133. package/dist/integration/RSUProgressBridge.d.ts +41 -2
  134. package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
  135. package/dist/integration/RSUProgressBridge.js +84 -1
  136. package/dist/integration/RSUProgressBridge.js.map +1 -1
  137. package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
  138. package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
  139. package/dist/integration/RunArtifactAuthorization.js +45 -0
  140. package/dist/integration/RunArtifactAuthorization.js.map +1 -0
  141. package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
  142. package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
  143. package/dist/integration/SchemaRefreshLaunch.js +3 -2
  144. package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
  145. package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
  146. package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
  147. package/dist/integration/ScopedFieldsForBuild.js +91 -0
  148. package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
  149. package/dist/integration/TargetConfigGuard.d.ts +40 -0
  150. package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
  151. package/dist/integration/TargetConfigGuard.js +40 -0
  152. package/dist/integration/TargetConfigGuard.js.map +1 -0
  153. package/dist/logging/StartupLogger.d.ts +50 -0
  154. package/dist/logging/StartupLogger.d.ts.map +1 -1
  155. package/dist/logging/StartupLogger.js +15 -22
  156. package/dist/logging/StartupLogger.js.map +1 -1
  157. package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
  158. package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
  159. package/dist/middleware/BaseServerMiddleware.js +4 -2
  160. package/dist/middleware/BaseServerMiddleware.js.map +1 -1
  161. package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
  162. package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
  163. package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
  164. package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
  165. package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
  166. package/dist/realtimeSessions/sessionAccess.js +22 -0
  167. package/dist/realtimeSessions/sessionAccess.js.map +1 -1
  168. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
  169. package/dist/realtimeWidget/WidgetSessionService.js +2 -0
  170. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
  171. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
  172. package/dist/realtimeWidget/visitorIdentity.js +1 -0
  173. package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
  174. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
  175. package/dist/realtimeWidget/widgetCore.js +4 -7
  176. package/dist/realtimeWidget/widgetCore.js.map +1 -1
  177. package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
  178. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
  179. package/dist/requestOrigin.d.ts +22 -0
  180. package/dist/requestOrigin.d.ts.map +1 -0
  181. package/dist/requestOrigin.js +60 -0
  182. package/dist/requestOrigin.js.map +1 -0
  183. package/dist/resolvers/AgentRunStatusPublisher.d.ts +10 -0
  184. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
  185. package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
  186. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
  187. package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
  188. package/dist/resolvers/AgentSessionResolver.js +6 -2
  189. package/dist/resolvers/AgentSessionResolver.js.map +1 -1
  190. package/dist/resolvers/AutotagPipelineResolver.d.ts +1 -1
  191. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  192. package/dist/resolvers/AutotagPipelineResolver.js +61 -16
  193. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  194. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
  195. package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
  196. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
  197. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
  198. package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
  199. package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
  200. package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
  201. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  202. package/dist/resolvers/GetDataResolver.js +34 -13
  203. package/dist/resolvers/GetDataResolver.js.map +1 -1
  204. package/dist/resolvers/InfoResolver.js +1 -1
  205. package/dist/resolvers/InfoResolver.js.map +1 -1
  206. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
  207. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  208. package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
  209. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  210. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  211. package/dist/resolvers/MCPResolver.js +13 -2
  212. package/dist/resolvers/MCPResolver.js.map +1 -1
  213. package/dist/resolvers/QuerySystemUserResolver.d.ts +38 -1
  214. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  215. package/dist/resolvers/QuerySystemUserResolver.js +181 -88
  216. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  217. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  218. package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
  219. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  220. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
  221. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  222. package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
  223. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  224. package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
  225. package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
  226. package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
  227. package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
  228. package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
  229. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  230. package/dist/resolvers/RunAIAgentResolver.js +185 -67
  231. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  232. package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
  233. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  234. package/dist/resolvers/RunAIPromptResolver.js +19 -18
  235. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  236. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  237. package/dist/resolvers/RunTemplateResolver.js +3 -1
  238. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  239. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  240. package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
  241. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  242. package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
  243. package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
  244. package/dist/resolvers/TagGovernanceResolver.js +84 -0
  245. package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
  246. package/dist/resolvers/TelemetryResolver.d.ts +13 -8
  247. package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
  248. package/dist/resolvers/TelemetryResolver.js +40 -9
  249. package/dist/resolvers/TelemetryResolver.js.map +1 -1
  250. package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
  251. package/dist/resolvers/meetingRecordingRegistration.js +2 -0
  252. package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
  253. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -1
  254. package/dist/resolvers/meetings/meetingMutations.js +34 -4
  255. package/dist/resolvers/meetings/meetingMutations.js.map +1 -1
  256. package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
  257. package/dist/rest/MediaAccessKeys.js +6 -2
  258. package/dist/rest/MediaAccessKeys.js.map +1 -1
  259. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
  260. package/dist/rest/OAuthCallbackHandler.js +11 -5
  261. package/dist/rest/OAuthCallbackHandler.js.map +1 -1
  262. package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
  263. package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
  264. package/dist/rest/RealtimeRecordingHandler.js +2 -4
  265. package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
  266. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  267. package/dist/rest/ViewOperationsHandler.js +2 -1
  268. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  269. package/dist/rest/setupRESTEndpoints.d.ts +3 -1
  270. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  271. package/dist/rest/setupRESTEndpoints.js +11 -2
  272. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  273. package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
  274. package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
  275. package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
  276. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  277. package/dist/services/TaskGraphActionRunner.js +2 -26
  278. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  279. package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
  280. package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
  281. package/dist/services/TaskGraphAgentRunner.js +38 -0
  282. package/dist/services/TaskGraphAgentRunner.js.map +1 -1
  283. package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
  284. package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
  285. package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
  286. package/dist/services/WorkQueueHostService.d.ts +2 -1
  287. package/dist/services/WorkQueueHostService.d.ts.map +1 -1
  288. package/dist/services/WorkQueueHostService.js +19 -4
  289. package/dist/services/WorkQueueHostService.js.map +1 -1
  290. package/dist/services/workQueueConfig.d.ts +2 -2
  291. package/dist/services/workQueueConfig.js +5 -5
  292. package/dist/services/workQueueConfig.js.map +1 -1
  293. package/dist/sharedCache.js +1 -1
  294. package/dist/sharedCache.js.map +1 -1
  295. package/dist/types.d.ts +17 -0
  296. package/dist/types.d.ts.map +1 -1
  297. package/dist/types.js.map +1 -1
  298. package/dist/util.d.ts.map +1 -1
  299. package/dist/util.js +55 -2
  300. package/dist/util.js.map +1 -1
  301. package/package.json +107 -100
  302. package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
  303. package/src/__tests__/ActiveOperations.test.ts +68 -0
  304. package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
  305. package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
  306. package/src/__tests__/BatchApplyParity.test.ts +109 -0
  307. package/src/__tests__/BridgeHostInstance.test.ts +42 -0
  308. package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
  309. package/src/__tests__/ConnectionPause.test.ts +192 -0
  310. package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
  311. package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
  312. package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
  313. package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
  314. package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
  315. package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
  316. package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
  317. package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
  318. package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
  319. package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
  320. package/src/__tests__/MagicLinkKeys.test.ts +102 -0
  321. package/src/__tests__/MediaAccessKeys.test.ts +57 -10
  322. package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
  323. package/src/__tests__/ObjectNamesByID.test.ts +176 -0
  324. package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
  325. package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
  326. package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
  327. package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
  328. package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
  329. package/src/__tests__/RSUProgressBridge.test.ts +188 -5
  330. package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
  331. package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
  332. package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
  333. package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
  334. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
  335. package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
  336. package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
  337. package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
  338. package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
  339. package/src/__tests__/ResolverBase.clientOldValues.test.ts +327 -26
  340. package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
  341. package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
  342. package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
  343. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
  344. package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
  345. package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
  346. package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
  347. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
  348. package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
  349. package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
  350. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +28 -4
  351. package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
  352. package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
  353. package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
  354. package/src/__tests__/SessionJanitor.test.ts +195 -4
  355. package/src/__tests__/SessionManager.test.ts +69 -0
  356. package/src/__tests__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
  357. package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
  358. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
  359. package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
  360. package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
  361. package/src/__tests__/WorkQueueHostService.test.ts +25 -7
  362. package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
  363. package/src/__tests__/contextRequestOrigin.test.ts +105 -0
  364. package/src/__tests__/conversationBrowsers.test.ts +62 -0
  365. package/src/__tests__/globalCors.test.ts +12 -0
  366. package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
  367. package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
  368. package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
  369. package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
  370. package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
  371. package/src/__tests__/requestOrigin.test.ts +104 -0
  372. package/src/__tests__/review-capture-valid-token-log.test.ts +98 -0
  373. package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
  374. package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
  375. package/src/__tests__/sessionDeadline.test.ts +51 -0
  376. package/src/__tests__/sharedCache.test.ts +11 -0
  377. package/src/__tests__/unifiedAuth.test.ts +73 -1
  378. package/src/agentSessions/BridgeHostInstance.ts +20 -5
  379. package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
  380. package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
  381. package/src/agentSessions/SessionJanitor.ts +92 -42
  382. package/src/agentSessions/SessionManager.ts +38 -8
  383. package/src/agentSessions/conversationBrowsers.ts +143 -0
  384. package/src/agentSessions/sessionDeadline.ts +81 -0
  385. package/src/auth/APIKeyScopeAuth.ts +5 -4
  386. package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
  387. package/src/auth/loginEmailResolution.ts +67 -0
  388. package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
  389. package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
  390. package/src/auth/magicLink/MagicLinkService.ts +250 -10
  391. package/src/auth/magicLink/index.ts +1 -1
  392. package/src/auth/magicLink/redeemLanding.ts +7 -4
  393. package/src/auth/magicLink/types.ts +28 -3
  394. package/src/auth/newUsers.ts +1 -0
  395. package/src/backgroundSweepConfigUnits.ts +213 -0
  396. package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
  397. package/src/config.ts +69 -0
  398. package/src/context.ts +46 -15
  399. package/src/coreRoutes.ts +14 -7
  400. package/src/generated/graphql-schemas/__mj.ts +10360 -6479
  401. package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
  402. package/src/generic/ResolverBase.ts +422 -143
  403. package/src/generic/RunViewResolver.ts +96 -49
  404. package/src/globalCors.ts +1 -0
  405. package/src/index.ts +246 -44
  406. package/src/integration/ConnectionPause.ts +222 -0
  407. package/src/integration/CustomColumnPromoter.ts +123 -53
  408. package/src/integration/EntityMapLifecycle.ts +229 -6
  409. package/src/integration/InactiveRowWarnings.ts +50 -0
  410. package/src/integration/ObjectNamesByID.ts +97 -0
  411. package/src/integration/PostRestartIntrospect.ts +49 -0
  412. package/src/integration/RSUPostRestartProgress.ts +359 -0
  413. package/src/integration/RSUProgressBridge.ts +85 -2
  414. package/src/integration/RunArtifactAuthorization.ts +65 -0
  415. package/src/integration/SchemaRefreshLaunch.ts +12 -2
  416. package/src/integration/ScopedFieldsForBuild.ts +113 -0
  417. package/src/integration/TargetConfigGuard.ts +71 -0
  418. package/src/logging/StartupLogger.ts +24 -0
  419. package/src/middleware/BaseServerMiddleware.ts +20 -3
  420. package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
  421. package/src/realtimeSessions/sessionAccess.ts +30 -0
  422. package/src/realtimeWidget/WidgetSessionService.ts +2 -0
  423. package/src/realtimeWidget/visitorIdentity.ts +1 -0
  424. package/src/realtimeWidget/widgetCore.ts +4 -8
  425. package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
  426. package/src/requestOrigin.ts +64 -0
  427. package/src/resolvers/AgentRunStatusPublisher.ts +27 -2
  428. package/src/resolvers/AgentSessionResolver.ts +6 -2
  429. package/src/resolvers/AutotagPipelineResolver.ts +59 -14
  430. package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
  431. package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
  432. package/src/resolvers/GetDataResolver.ts +35 -13
  433. package/src/resolvers/InfoResolver.ts +1 -1
  434. package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
  435. package/src/resolvers/MCPResolver.ts +15 -2
  436. package/src/resolvers/QuerySystemUserResolver.ts +211 -100
  437. package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
  438. package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
  439. package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
  440. package/src/resolvers/RunAIAgentResolver.ts +265 -86
  441. package/src/resolvers/RunAIPromptResolver.ts +18 -18
  442. package/src/resolvers/RunTemplateResolver.ts +3 -1
  443. package/src/resolvers/SqlLoggingConfigResolver.ts +7 -9
  444. package/src/resolvers/TagGovernanceResolver.ts +65 -0
  445. package/src/resolvers/TelemetryResolver.ts +44 -9
  446. package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
  447. package/src/resolvers/meetingRecordingRegistration.ts +2 -0
  448. package/src/resolvers/meetings/meetingMutations.ts +37 -7
  449. package/src/rest/MediaAccessKeys.ts +6 -2
  450. package/src/rest/OAuthCallbackHandler.ts +13 -5
  451. package/src/rest/RealtimeRecordingHandler.ts +5 -9
  452. package/src/rest/ViewOperationsHandler.ts +2 -1
  453. package/src/rest/setupRESTEndpoints.ts +11 -2
  454. package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
  455. package/src/services/TaskGraphActionRunner.ts +2 -23
  456. package/src/services/TaskGraphAgentRunner.ts +43 -0
  457. package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
  458. package/src/services/WorkQueueHostService.ts +26 -4
  459. package/src/services/workQueueConfig.ts +5 -5
  460. package/src/sharedCache.ts +1 -1
  461. package/src/types.ts +18 -0
  462. package/src/util.ts +55 -3
  463. package/src/services/TaskOrchestration-Integration.md +0 -188
@@ -2,6 +2,7 @@
2
2
  import 'reflect-metadata';
3
3
  import { describe, it, expect, vi } from 'vitest';
4
4
  import { BaseEntity, EntityFieldTSType, EntityInfo, UserInfo, type EntityFieldInfo, type IMetadataProvider } from '@memberjunction/core';
5
+ import { ENCRYPTED_SENTINEL } from '@memberjunction/global';
5
6
  import { ResolverBase } from '../generic/ResolverBase.js';
6
7
 
7
8
  /**
@@ -102,6 +103,10 @@ describe('ResolverBase.MustLoadTruthFromDatabase & hasCompleteOldValues', () =>
102
103
  expect(probe.MustLoad({ Name: 'MJ: AI Agent Sessions', TrackRecordChanges: false }, true)).toBe(true);
103
104
  });
104
105
 
106
+ it("always loads a conversation folder, so client OldValues cannot pose as its stored owner", () => {
107
+ expect(probe.MustLoad({ Name: 'MJ: Projects', TrackRecordChanges: false }, true)).toBe(true);
108
+ });
109
+
105
110
  it('still trusts OldValues on other untracked entities without field security when OldValues___ covers all writable fields', () => {
106
111
  const fields = buildTagEntityFields();
107
112
  const completeOldValues = [
@@ -208,16 +213,52 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
208
213
  expect(errorLogSaves).toHaveLength(0);
209
214
  });
210
215
 
211
- it('detects a concurrent modification when the dirty field DB value differs from OldValues___', async () => {
216
+ it('rejects a conflicting concurrent modification with CONCURRENT_MODIFICATION_CONFLICT while logging SAVE_INCONSISTENCY', async () => {
217
+ const errorLogSaves: Array<Record<string, unknown>> = [];
218
+ const { entity, user } = createLoadedTagEntity(errorLogSaves);
219
+
220
+ // Client thinks Display_Name was 'Stale Client Display', DB has 'Original Display', and client tries to write 'Client New Display'
221
+ await expect(
222
+ probe.TestAndSet(
223
+ entity,
224
+ {
225
+ ID: '11111111-2222-3333-4444-555555555555',
226
+ Display_Name: 'Client New Display',
227
+ OldValues___: [
228
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
229
+ { Key: 'Display_Name', Value: 'Stale Client Display' },
230
+ ],
231
+ },
232
+ user
233
+ )
234
+ ).rejects.toMatchObject({
235
+ extensions: {
236
+ code: 'CONCURRENT_MODIFICATION_CONFLICT',
237
+ entityName: 'MJ: Tags',
238
+ conflictingFields: ['Display_Name'],
239
+ },
240
+ });
241
+
242
+ // Warning is logged to ErrorLog, and the conflicting DB field is NOT overwritten
243
+ expect(errorLogSaves).toHaveLength(1);
244
+ expect(errorLogSaves[0].Code).toBe('SAVE_INCONSISTENCY');
245
+ // ErrorLog.Code is NCHAR(20)
246
+ expect(String(errorLogSaves[0].Code).length).toBeLessThanOrEqual(20);
247
+ expect(String(errorLogSaves[0].Message)).toContain('Display_Name');
248
+ expect(entity.Get('Display Name')).toBe('Original Display');
249
+ expect(entity.Get('Description')).toBe('Untouched DB Description');
250
+ });
251
+
252
+ it('allows idempotent concurrent convergence when ClientNewValue already matches the current DB value', async () => {
212
253
  const errorLogSaves: Array<Record<string, unknown>> = [];
213
254
  const { entity, user } = createLoadedTagEntity(errorLogSaves);
214
255
 
215
- // Client thinks Display_Name was 'Stale Client Display', but DB currently has 'Original Display'
256
+ // Client thinks Display_Name was 'Stale Client Display', and wants 'Original Display' — which DB already holds
216
257
  await probe.TestAndSet(
217
258
  entity,
218
259
  {
219
260
  ID: '11111111-2222-3333-4444-555555555555',
220
- Display_Name: 'Client New Display',
261
+ Display_Name: 'Original Display',
221
262
  OldValues___: [
222
263
  { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
223
264
  { Key: 'Display_Name', Value: 'Stale Client Display' },
@@ -226,32 +267,28 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
226
267
  user
227
268
  );
228
269
 
229
- // Warning is logged to ErrorLog, and save still applies the new value
230
- expect(errorLogSaves).toHaveLength(1);
231
- expect(errorLogSaves[0].Code).toBe('SAVE_INCONSISTENCY');
232
- // ErrorLog.Code is NCHAR(20)
233
- expect(String(errorLogSaves[0].Code).length).toBeLessThanOrEqual(20);
234
- expect(String(errorLogSaves[0].Message)).toContain('Display_Name');
235
- expect(entity.Get('Display Name')).toBe('Client New Display');
236
- expect(entity.Get('Description')).toBe('Untouched DB Description');
270
+ expect(errorLogSaves).toHaveLength(0);
271
+ expect(entity.Get('Display Name')).toBe('Original Display');
237
272
  });
238
273
 
239
- it('reports the entity name and the reason when the ErrorLog save fails', async () => {
274
+ it('reports the entity name and the reason when the ErrorLog save fails before throwing CONCURRENT_MODIFICATION_CONFLICT', async () => {
240
275
  const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
241
276
  const { entity, user } = createLoadedTagEntity([], 'value too long for Code');
242
277
  try {
243
- await probe.TestAndSet(
244
- entity,
245
- {
246
- ID: '11111111-2222-3333-4444-555555555555',
247
- Display_Name: 'Client New Display',
248
- OldValues___: [
249
- { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
250
- { Key: 'Display_Name', Value: 'Stale Client Display' },
251
- ],
252
- },
253
- user
254
- );
278
+ await expect(
279
+ probe.TestAndSet(
280
+ entity,
281
+ {
282
+ ID: '11111111-2222-3333-4444-555555555555',
283
+ Display_Name: 'Client New Display',
284
+ OldValues___: [
285
+ { Key: 'ID', Value: '11111111-2222-3333-4444-555555555555' },
286
+ { Key: 'Display_Name', Value: 'Stale Client Display' },
287
+ ],
288
+ },
289
+ user
290
+ )
291
+ ).rejects.toThrow(/Concurrent modification conflict/);
255
292
  const message = errorSpy.mock.calls.map((c) => String(c[0])).join('\n');
256
293
  expect(message).toContain('Failed to save ErrorLog record');
257
294
  expect(message).toContain('value too long for Code');
@@ -261,7 +298,7 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
261
298
  }
262
299
  });
263
300
 
264
- it('does not false-positive when a non-overlapping field differs in the DB', async () => {
301
+ it('merges non-overlapping concurrent edits without overwriting fields that changed in the DB', async () => {
265
302
  const errorLogSaves: Array<Record<string, unknown>> = [];
266
303
  const { entity, user } = createLoadedTagEntity(errorLogSaves);
267
304
 
@@ -283,9 +320,273 @@ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — partial delta Upd
283
320
  );
284
321
 
285
322
  // Description had a DB difference ('Untouched DB Description' !== 'Old Desc'),
286
- // but client was NOT changing Description ('Old Desc' === 'Old Desc'), so overlap is empty!
323
+ // and client was NOT changing Description ('Old Desc' === 'Old Desc'): overlap is empty,
324
+ // Name is updated, AND Description keeps the concurrent DB value instead of being clobbered!
287
325
  expect(errorLogSaves).toHaveLength(0);
288
326
  expect(entity.Get('Name')).toBe('RenamedTag');
327
+ expect(entity.Get('Description')).toBe('Untouched DB Description');
289
328
  });
290
329
  });
291
330
 
331
+
332
+ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — representation drift is not a conflict', () => {
333
+ const entityId = 'E0000000-0000-4000-8000-000000000002';
334
+ const jobEntityInfo = new EntityInfo({
335
+ ID: entityId,
336
+ Name: 'MJ: Probe Jobs',
337
+ BaseTable: 'ProbeJob',
338
+ BaseView: 'vwProbeJobs',
339
+ SchemaName: '__mj',
340
+ Status: 'Active',
341
+ EntityFields: [
342
+ { ID: 'J-1', EntityID: entityId, Name: 'ID', Type: 'uniqueidentifier', IsPrimaryKey: true, AllowsNull: false, AllowUpdateAPI: false, IsVirtual: false, Sequence: 1, Status: 'Active' },
343
+ { ID: 'J-2', EntityID: entityId, Name: 'Name', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: false, AllowUpdateAPI: true, IsVirtual: false, Sequence: 2, Status: 'Active' },
344
+ { ID: 'J-3', EntityID: entityId, Name: 'Config', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: true, IsVirtual: false, Sequence: 3, Status: 'Active' },
345
+ { ID: 'J-4', EntityID: entityId, Name: 'ScheduledAt', Type: 'datetime', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: true, IsVirtual: false, Sequence: 4, Status: 'Active' },
346
+ { ID: 'J-5', EntityID: entityId, Name: 'CompletedAt', Type: 'datetimeoffset', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: true, IsVirtual: false, Sequence: 5, Status: 'Active' },
347
+ { ID: 'J-6', EntityID: entityId, Name: '__mj_UpdatedAt', Type: 'datetimeoffset', IsPrimaryKey: false, AllowsNull: false, AllowUpdateAPI: false, IsVirtual: false, Sequence: 6, Status: 'Active' },
348
+ ],
349
+ });
350
+ const jobId = '22222222-3333-4444-5555-666666666666';
351
+ const dbScheduledAt = new Date('2026-03-01T00:00:00.000Z');
352
+ const dbCompletedAt = new Date('2026-03-02T00:00:00.000Z');
353
+
354
+ function createLoadedJobEntity(errorLogSaves: Array<Record<string, unknown>>): { entity: BaseEntity; user: UserInfo } {
355
+ const fakeProvider = {
356
+ Entities: [jobEntityInfo],
357
+ GetEntityObject: async () => {
358
+ const errEntity: Record<string, unknown> = {
359
+ Save: async () => {
360
+ errorLogSaves.push({ ...errEntity });
361
+ return true;
362
+ },
363
+ LatestResult: null,
364
+ };
365
+ return errEntity;
366
+ },
367
+ } as unknown as IMetadataProvider;
368
+ const entity = new BaseEntity(jobEntityInfo, fakeProvider);
369
+ entity.LoadFromData({
370
+ ID: jobId,
371
+ Name: 'Nightly',
372
+ Config: '{"retries":3,"targets":["a","b"]}',
373
+ ScheduledAt: dbScheduledAt,
374
+ CompletedAt: dbCompletedAt,
375
+ __mj_UpdatedAt: new Date('2026-03-01T00:00:00.000Z'),
376
+ });
377
+ const user = new UserInfo(null, { ID: 'u-1', Name: 'Probe', Email: 'probe@example.test', IsActive: true, UserRoles: [] });
378
+ return { entity, user };
379
+ }
380
+
381
+ it('saves an edited JSON field whose old value differs from the DB only in whitespace and key order', async () => {
382
+ const errorLogSaves: Array<Record<string, unknown>> = [];
383
+ const { entity, user } = createLoadedJobEntity(errorLogSaves);
384
+
385
+ await probe.TestAndSet(
386
+ entity,
387
+ {
388
+ ID: jobId,
389
+ Config: '{"retries":5,"targets":["a","b"]}',
390
+ OldValues___: [
391
+ { Key: 'ID', Value: jobId },
392
+ { Key: 'Config', Value: '{ "targets": ["a", "b"],\n "retries": 3 }' },
393
+ ],
394
+ },
395
+ user
396
+ );
397
+
398
+ expect(errorLogSaves).toHaveLength(0);
399
+ expect(entity.Get('Config')).toBe('{"retries":5,"targets":["a","b"]}');
400
+ });
401
+
402
+ it('still rejects an edited JSON field when the DB content really changed', async () => {
403
+ const errorLogSaves: Array<Record<string, unknown>> = [];
404
+ const { entity, user } = createLoadedJobEntity(errorLogSaves);
405
+
406
+ await expect(
407
+ probe.TestAndSet(
408
+ entity,
409
+ {
410
+ ID: jobId,
411
+ Config: '{"retries":5,"targets":["a","b"]}',
412
+ OldValues___: [
413
+ { Key: 'ID', Value: jobId },
414
+ { Key: 'Config', Value: '{"retries":3,"targets":["a"]}' },
415
+ ],
416
+ },
417
+ user
418
+ )
419
+ ).rejects.toMatchObject({ extensions: { code: 'CONCURRENT_MODIFICATION_CONFLICT', conflictingFields: ['Config'] } });
420
+ expect(errorLogSaves).toHaveLength(1);
421
+ expect(entity.Get('Config')).toBe('{"retries":3,"targets":["a","b"]}');
422
+ });
423
+
424
+ it('saves an edited datetime field whose old value is within the column resolution of the DB value', async () => {
425
+ const errorLogSaves: Array<Record<string, unknown>> = [];
426
+ const { entity, user } = createLoadedJobEntity(errorLogSaves);
427
+ const newScheduledAt = new Date('2026-04-01T00:00:00.000Z');
428
+
429
+ // SQL Server datetime resolves to 1/300 s: a 3 ms drift is below one tick.
430
+ await probe.TestAndSet(
431
+ entity,
432
+ {
433
+ ID: jobId,
434
+ ScheduledAt: newScheduledAt,
435
+ OldValues___: [
436
+ { Key: 'ID', Value: jobId },
437
+ { Key: 'ScheduledAt', Value: String(dbScheduledAt.getTime() + 3) },
438
+ ],
439
+ },
440
+ user
441
+ );
442
+
443
+ expect(errorLogSaves).toHaveLength(0);
444
+ expect((entity.Get('ScheduledAt') as Date).getTime()).toBe(newScheduledAt.getTime());
445
+ });
446
+
447
+ it('still rejects an edited datetime field when the DB value moved by a full second', async () => {
448
+ const errorLogSaves: Array<Record<string, unknown>> = [];
449
+ const { entity, user } = createLoadedJobEntity(errorLogSaves);
450
+
451
+ await expect(
452
+ probe.TestAndSet(
453
+ entity,
454
+ {
455
+ ID: jobId,
456
+ ScheduledAt: new Date('2026-04-01T00:00:00.000Z'),
457
+ OldValues___: [
458
+ { Key: 'ID', Value: jobId },
459
+ { Key: 'ScheduledAt', Value: String(dbScheduledAt.getTime() + 1000) },
460
+ ],
461
+ },
462
+ user
463
+ )
464
+ ).rejects.toMatchObject({ extensions: { code: 'CONCURRENT_MODIFICATION_CONFLICT', conflictingFields: ['ScheduledAt'] } });
465
+ expect(errorLogSaves).toHaveLength(1);
466
+ });
467
+
468
+ it('keeps millisecond precision on a datetimeoffset column, so a 1 ms drift there is a real conflict', async () => {
469
+ const errorLogSaves: Array<Record<string, unknown>> = [];
470
+ const { entity, user } = createLoadedJobEntity(errorLogSaves);
471
+
472
+ await expect(
473
+ probe.TestAndSet(
474
+ entity,
475
+ {
476
+ ID: jobId,
477
+ CompletedAt: new Date('2026-04-02T00:00:00.000Z'),
478
+ OldValues___: [
479
+ { Key: 'ID', Value: jobId },
480
+ { Key: 'CompletedAt', Value: String(dbCompletedAt.getTime() + 1) },
481
+ ],
482
+ },
483
+ user
484
+ )
485
+ ).rejects.toMatchObject({ extensions: { code: 'CONCURRENT_MODIFICATION_CONFLICT', conflictingFields: ['CompletedAt'] } });
486
+ expect(errorLogSaves).toHaveLength(1);
487
+ });
488
+ });
489
+
490
+ describe('ResolverBase.TestAndSetClientOldValuesToDBValues — encrypted fields', () => {
491
+ const entityId = 'E0000000-0000-4000-8000-000000000003';
492
+ const credentialEntityInfo = new EntityInfo({
493
+ ID: entityId,
494
+ Name: 'MJ: Probe Credentials',
495
+ BaseTable: 'ProbeCredential',
496
+ BaseView: 'vwProbeCredentials',
497
+ SchemaName: '__mj',
498
+ Status: 'Active',
499
+ EntityFields: [
500
+ { ID: 'C-1', EntityID: entityId, Name: 'ID', Type: 'uniqueidentifier', IsPrimaryKey: true, AllowsNull: false, AllowUpdateAPI: false, IsVirtual: false, Sequence: 1, Status: 'Active' },
501
+ { ID: 'C-2', EntityID: entityId, Name: 'Name', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: false, AllowUpdateAPI: true, IsVirtual: false, Sequence: 2, Status: 'Active' },
502
+ { ID: 'C-3', EntityID: entityId, Name: 'Secret', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: true, IsVirtual: false, Sequence: 3, Status: 'Active', Encrypt: true, AllowDecryptInAPI: false, SendEncryptedValue: false },
503
+ { ID: 'C-4', EntityID: entityId, Name: 'Token', Type: 'nvarchar', IsPrimaryKey: false, AllowsNull: true, AllowUpdateAPI: true, IsVirtual: false, Sequence: 4, Status: 'Active', Encrypt: true, AllowDecryptInAPI: true, SendEncryptedValue: false },
504
+ ],
505
+ });
506
+ const credentialId = '33333333-4444-5555-6666-777777777777';
507
+ const dbSecret = 'decrypted-plaintext-secret';
508
+ const dbToken = 'decrypted-plaintext-token';
509
+
510
+ function createLoadedCredential(errorLogSaves: Array<Record<string, unknown>>): { entity: BaseEntity; user: UserInfo } {
511
+ const fakeProvider = {
512
+ Entities: [credentialEntityInfo],
513
+ GetEntityObject: async () => {
514
+ const errEntity: Record<string, unknown> = {
515
+ Save: async () => {
516
+ errorLogSaves.push({ ...errEntity });
517
+ return true;
518
+ },
519
+ LatestResult: null,
520
+ };
521
+ return errEntity;
522
+ },
523
+ } as unknown as IMetadataProvider;
524
+ const entity = new BaseEntity(credentialEntityInfo, fakeProvider);
525
+ entity.LoadFromData({ ID: credentialId, Name: 'Vendor key', Secret: dbSecret, Token: dbToken });
526
+ const user = new UserInfo(null, { ID: 'u-1', Name: 'Probe', Email: 'probe@example.test', IsActive: true, UserRoles: [] });
527
+ return { entity, user };
528
+ }
529
+
530
+ it.each([
531
+ ['the sentinel', ENCRYPTED_SENTINEL],
532
+ ['re-encrypted ciphertext', '$ENC$11111111-2222-3333-4444-555555555555$AES-256-GCM$iv$cipher$tag'],
533
+ ])('saves a rotated value for a field the API never decrypts, when the old value is %s', async (_label, oldValue) => {
534
+ const errorLogSaves: Array<Record<string, unknown>> = [];
535
+ const { entity, user } = createLoadedCredential(errorLogSaves);
536
+
537
+ await probe.TestAndSet(
538
+ entity,
539
+ {
540
+ ID: credentialId,
541
+ Secret: 'rotated-secret',
542
+ OldValues___: [
543
+ { Key: 'ID', Value: credentialId },
544
+ { Key: 'Secret', Value: oldValue },
545
+ ],
546
+ },
547
+ user
548
+ );
549
+
550
+ expect(entity.Get('Secret')).toBe('rotated-secret');
551
+ expect(errorLogSaves).toHaveLength(0);
552
+ });
553
+
554
+ it('keeps encrypted values out of the conflict diagnostics', async () => {
555
+ const errorLogSaves: Array<Record<string, unknown>> = [];
556
+ const { entity, user } = createLoadedCredential(errorLogSaves);
557
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
558
+ const error = vi.spyOn(console, 'error').mockImplementation(() => undefined);
559
+ const log = vi.spyOn(console, 'log').mockImplementation(() => undefined);
560
+
561
+ try {
562
+ await expect(
563
+ probe.TestAndSet(
564
+ entity,
565
+ {
566
+ ID: credentialId,
567
+ Token: 'client-new-token',
568
+ OldValues___: [
569
+ { Key: 'ID', Value: credentialId },
570
+ { Key: 'Token', Value: 'client-stale-token' },
571
+ ],
572
+ },
573
+ user
574
+ )
575
+ ).rejects.toMatchObject({ extensions: { code: 'CONCURRENT_MODIFICATION_CONFLICT', conflictingFields: ['Token'] } });
576
+
577
+ const written = [
578
+ ...errorLogSaves.map((s) => String(s.Message)),
579
+ ...[...warn.mock.calls, ...error.mock.calls, ...log.mock.calls].map((args) => args.map(String).join(' ')),
580
+ ].join('\n');
581
+ expect(errorLogSaves).toHaveLength(1);
582
+ expect(String(errorLogSaves[0].Message)).toContain('Token');
583
+ for (const secret of [dbToken, 'client-stale-token', 'client-new-token']) {
584
+ expect(written).not.toContain(secret);
585
+ }
586
+ } finally {
587
+ warn.mockRestore();
588
+ error.mockRestore();
589
+ log.mockRestore();
590
+ }
591
+ });
592
+ });
@@ -0,0 +1,26 @@
1
+ import 'reflect-metadata';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { EntityInfo, UserInfo } from '@memberjunction/core';
4
+ import { ResolverBase } from '../generic/ResolverBase.js';
5
+
6
+ class Probe extends ResolverBase {
7
+ /** Asks whether an untracked entity with complete client OldValues must load the stored row. */
8
+ public MustLoad(entityName: string): boolean {
9
+ const user = new UserInfo(null, { ID: 'u-1', Name: 'Probe', Email: 'probe@example.test', IsActive: true, UserRoles: [] });
10
+ return this.MustLoadTruthFromDatabase(
11
+ new EntityInfo({ EntityFields: [], Name: entityName, TrackRecordChanges: false }),
12
+ { OldValues___: [{ Key: 'Comments', Value: null }] },
13
+ false,
14
+ false,
15
+ user
16
+ );
17
+ }
18
+ }
19
+
20
+ describe('ResolverBase.MustLoadTruthFromDatabase for diagnostic sender registrations', () => {
21
+ it('always loads a sender registration, so client OldValues cannot hide a change to a guarded field', () => {
22
+ const probe = new Probe();
23
+ expect(probe.MustLoad('MJ: Diagnostic Sender Registrations')).toBe(true);
24
+ expect(probe.MustLoad(' mj: diagnostic sender registrations ')).toBe(true);
25
+ });
26
+ });