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

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