@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
@@ -0,0 +1,183 @@
1
+ /**
2
+ * ConnectionPause — what "pause this connection" has to mean, beyond a flag.
3
+ *
4
+ * `IntegrationDeactivateConnection` used to set `IsActive = false` and stop. The engine reads
5
+ * that flag before a sync, so syncs did stop — but nothing else did:
6
+ *
7
+ * - The SCHEDULED jobs kept firing. A sync schedule woke on cron, was refused, and wrote a
8
+ * failed run. Every cron tick, for as long as the connection stayed paused, so a paused
9
+ * connector's history filled with failures it did not cause.
10
+ * - The DISCOVERY schedule kept firing and was NOT refused, because nothing gated discovery
11
+ * on IsActive. A paused connection went on rescanning the vendor on a timer — spending the
12
+ * customer's rate budget, and rewriting the very catalog the pause was meant to freeze.
13
+ * - An IN-FLIGHT sync ran to completion. "Paused" arrived only after the thing the operator
14
+ * was watching had finished.
15
+ *
16
+ * So pause has to touch three things, and resume has to put back exactly what pause took —
17
+ * which is the part that needs state rather than a rule.
18
+ *
19
+ * WHY THE PRIOR STATE IS RECORDED RATHER THAN INFERRED. "Resume = set every schedule Active" is
20
+ * wrong: a connection can legitimately be paused while one of its two schedules was ALREADY off
21
+ * because the operator turned it off weeks ago. Inferring would silently re-enable it, and the
22
+ * first symptom is a sync nobody asked for. So pause records which jobs it actually moved, and
23
+ * resume restores only those.
24
+ *
25
+ * WHY PAUSE IS IDEMPOTENT AT THE RECORD, NOT AT THE WRITE. Deactivating an already-deactivated
26
+ * connection finds every job already Paused, so the set it "moved" is empty. Writing that empty
27
+ * set over the real record is the bug that loses the state — resume would then restore nothing
28
+ * and both schedules would stay off forever, with no error anywhere. {@link DecidePauseWrite}
29
+ * is the guard: the FIRST pause's record stands until a resume consumes it, and a later pause only
30
+ * ever adds to it.
31
+ *
32
+ * Everything here is a pure function over plain values. The resolver owns the reads and writes.
33
+ */
34
+ import { UUIDSet } from '@memberjunction/global';
35
+ /** Key under CompanyIntegration.Configuration where the record lives. */
36
+ export const PAUSED_SCHEDULES_KEY = 'pausedSchedules';
37
+ /**
38
+ * Which schedules a pause should move, and the record to remember them by.
39
+ *
40
+ * Only jobs that are currently `Active` are moved. A job already `Paused`, or in any other
41
+ * status, is left exactly as found and is deliberately absent from the record — that absence
42
+ * is what stops resume from turning it on.
43
+ */
44
+ export function DecideSchedulesToPause(jobs) {
45
+ return jobs
46
+ .filter(j => j.Status === 'Active')
47
+ .map(j => ({ ID: j.ID, Kind: j.Kind }));
48
+ }
49
+ /**
50
+ * Whether this pause's record should be written, given whatever record is already stored.
51
+ *
52
+ * Returns the record to persist, or `null` to leave the stored one untouched. A second pause of
53
+ * an already-paused connection moves nothing, and must not overwrite the first pause's record
54
+ * with its own empty one — see the class comment.
55
+ *
56
+ * A pause that DOES move jobs while a record is stored adds them to it: the stored entries stay
57
+ * exactly as they were, and the new ones are appended by job ID. That happens when a job was
58
+ * created or switched back on while the connection was paused, or when a resume could not clear
59
+ * its record. A job paused here but left out of the record is one resume can never put back.
60
+ */
61
+ export function DecidePauseWrite(stored, newlyPaused) {
62
+ const known = new UUIDSet(stored.map(r => r.ID));
63
+ const added = newlyPaused.filter(r => !known.Has(r.ID));
64
+ if (added.length === 0)
65
+ return null; // nothing new moved — any stored record stands as it is
66
+ return [...stored, ...added];
67
+ }
68
+ /**
69
+ * Which schedules a resume should re-activate.
70
+ *
71
+ * A recorded job is restored only when it STILL EXISTS and is STILL `Paused`. Both guards matter:
72
+ * a job deleted while the connection was paused must not be resurrected, and a job someone has
73
+ * already moved back to Active must not be written again for no reason. Jobs absent from the
74
+ * record are never touched, however they look now.
75
+ */
76
+ export function DecideSchedulesToResume(stored, jobs) {
77
+ const live = new Map(jobs.map(j => [j.ID, j]));
78
+ return stored
79
+ .filter(r => live.get(r.ID)?.Status === 'Paused')
80
+ .map(r => r.ID);
81
+ }
82
+ /** Reads the stored record out of a CompanyIntegration.Configuration JSON blob. */
83
+ export function ReadPausedSchedules(configuration) {
84
+ if (!configuration)
85
+ return [];
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(configuration);
89
+ }
90
+ catch {
91
+ // A Configuration we cannot parse is not an error to raise here: pause/resume is not the
92
+ // operation that owns that blob. Report "no record" and let the rest of the flow proceed.
93
+ return [];
94
+ }
95
+ if (typeof parsed !== 'object' || parsed === null)
96
+ return [];
97
+ const raw = parsed[PAUSED_SCHEDULES_KEY];
98
+ if (!Array.isArray(raw))
99
+ return [];
100
+ const out = [];
101
+ for (const item of raw) {
102
+ if (typeof item !== 'object' || item === null)
103
+ continue;
104
+ const rec = item;
105
+ const id = rec.ID;
106
+ const kind = rec.Kind;
107
+ if (typeof id !== 'string' || id.length === 0)
108
+ continue;
109
+ out.push({ ID: id, Kind: kind === 'discovery' ? 'discovery' : 'sync' });
110
+ }
111
+ return out;
112
+ }
113
+ /**
114
+ * Returns the Configuration JSON with the record set (or cleared, when `record` is empty).
115
+ *
116
+ * Preserves every other key. An unparseable Configuration is REPLACED rather than merged — the
117
+ * alternative is refusing to pause because of unrelated corruption, and losing one broken blob
118
+ * is the better failure.
119
+ */
120
+ export function WritePausedSchedules(configuration, record) {
121
+ let base = {};
122
+ if (configuration) {
123
+ try {
124
+ const parsed = JSON.parse(configuration);
125
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
126
+ base = parsed;
127
+ }
128
+ }
129
+ catch {
130
+ base = {};
131
+ }
132
+ }
133
+ if (record.length === 0) {
134
+ delete base[PAUSED_SCHEDULES_KEY];
135
+ }
136
+ else {
137
+ base[PAUSED_SCHEDULES_KEY] = record.map(r => ({ ID: r.ID, Kind: r.Kind }));
138
+ }
139
+ return JSON.stringify(base);
140
+ }
141
+ /**
142
+ * Classifies the cancel from observable facts, not from which API was called.
143
+ *
144
+ * Deliberately derived rather than hardcoded per lineage: the durable path needs a run-row column
145
+ * that only exists on newer tenants, so the SAME code has to tell the truth on a tenant where
146
+ * that column is absent and the in-process registry is the only signal there is.
147
+ */
148
+ export function DescribeCancelScope(facts) {
149
+ if (!facts.liveRunPresent)
150
+ return 'none';
151
+ if (facts.durableRequestRecorded)
152
+ return 'durable';
153
+ if (facts.runningInThisProcess)
154
+ return 'this-process';
155
+ return 'unknown';
156
+ }
157
+ /** Operator-facing sentence for a cancel scope. Never claims a stop it cannot deliver. */
158
+ export function DescribeCancelOutcome(scope) {
159
+ switch (scope) {
160
+ case 'durable':
161
+ case 'this-process':
162
+ return 'A sync was running — it will stop after the current batch.';
163
+ case 'unknown':
164
+ return 'A sync is running in another process and will finish; no new sync will start.';
165
+ case 'none':
166
+ return '';
167
+ }
168
+ }
169
+ /** Operator-facing sentence for what pause did to the schedules. */
170
+ export function DescribePauseOutcome(paused, alreadyPaused) {
171
+ if (alreadyPaused)
172
+ return 'Schedules were already paused.';
173
+ if (paused.length === 0)
174
+ return 'No active schedules to pause.';
175
+ const kinds = paused.map(p => p.Kind);
176
+ const parts = [];
177
+ if (kinds.includes('sync'))
178
+ parts.push('data sync');
179
+ if (kinds.includes('discovery'))
180
+ parts.push('schema refresh');
181
+ return `Paused the ${parts.join(' and ')} schedule${parts.length > 1 ? 's' : ''}.`;
182
+ }
183
+ //# sourceMappingURL=ConnectionPause.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionPause.js","sourceRoot":"","sources":["../../src/integration/ConnectionPause.ts"],"names":[],"mappings":"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"}
@@ -24,12 +24,55 @@
24
24
  * of. The JSON spread of staged values into those columns happens here too.
25
25
  */
26
26
  import { type UserInfo, type IMetadataProvider, type EntityInfo } from '@memberjunction/core';
27
- import { type SchemaPromotionResult, type CustomKeyStat } from '@memberjunction/integration-engine';
28
- import { type RSUPendingWork } from '@memberjunction/schema-engine';
27
+ import { type SchemaPromotionResult, type PromotionCandidate, type CustomKeyStat } from '@memberjunction/integration-engine';
28
+ import { type RSUPipelineInput, type RSUPendingWork } from '@memberjunction/schema-engine';
29
+ /** A coverage-passing key with its resolved column + what work it still needs (M4: promote OR recover). */
30
+ interface WorkItem {
31
+ candidate: PromotionCandidate;
32
+ /** Original source key — the field map's SourceFieldName + the IOF Name. */
33
+ sourceKey: string;
34
+ /** Sanitized, collision-resolved column / DestinationFieldName / EntityField name. */
35
+ columnName: string;
36
+ /** The real DB column does not exist yet → needs ADD COLUMN. */
37
+ needsColumn: boolean;
38
+ /** No active field map for this source key → needs one (covers the partial-promotion crash window). */
39
+ needsFieldMap: boolean;
40
+ /**
41
+ * Column AND field map already exist, but rows may still hold the value only in the overflow
42
+ * JSON — a run interrupted between ADD COLUMN and the value spread leaves exactly this state,
43
+ * and nothing else ever finishes it: the key is no longer "unmapped" so capture stops, and the
44
+ * old terminate check skipped it as done. A spread-recovery item re-runs ONLY the backfill
45
+ * (no DDL, no metadata writes); the idempotent spread settles it to a no-op once every row is
46
+ * filled. Never surfaced as a UI candidate and never counted as a column added.
47
+ */
48
+ recoverSpread?: boolean;
49
+ }
29
50
  /** Registers the post-sync custom-column promotion hook on the IntegrationEngine singleton. */
30
51
  export declare function RegisterIntegrationCustomColumnPromoter(): void;
31
52
  /** @deprecated Use {@link RegisterIntegrationCustomColumnPromoter}. */
32
53
  export declare function registerIntegrationCustomColumnPromoter(): void;
54
+ interface EntityPlan {
55
+ entityName: string;
56
+ entityInfo: EntityInfo;
57
+ entityMap: {
58
+ ID: string;
59
+ ExternalObjectName: string;
60
+ };
61
+ work: WorkItem[];
62
+ /** Index of this entity's input in the RSU batch; -1 when it needs no DDL. */
63
+ batchIndex: number;
64
+ }
65
+ /** What {@link IntegrationCustomColumnPromoter.PlanPromotion} worked out, before anything runs. */
66
+ export interface PromotionPlan {
67
+ IntegrationID: string;
68
+ Plans: EntityPlan[];
69
+ /**
70
+ * The ADD COLUMN migrations, ready to run. The FIRST entry carries the durable post-restart
71
+ * PendingWork for the whole plan — RSU registers PendingWork from every input in a batch, so
72
+ * these can be appended to someone else's batch without losing it.
73
+ */
74
+ BatchInputs: RSUPipelineInput[];
75
+ }
33
76
  /**
34
77
  * Orchestrates promotion for one sync run. One instance per invocation (carries the per-sync
35
78
  * context user + provider so it respects the bound provider, never the global default blindly).
@@ -40,6 +83,24 @@ export declare class IntegrationCustomColumnPromoter {
40
83
  constructor(user: UserInfo, providerOverride?: IMetadataProvider);
41
84
  private get provider();
42
85
  private get dbProvider();
86
+ /**
87
+ * PHASE 1 only: work out what WOULD be promoted, and the ADD COLUMN migrations that would do
88
+ * it, without running a pipeline.
89
+ *
90
+ * Split out so a caller that is ALREADY running an RSU batch can fold these migrations into
91
+ * it. A schema refresh that offers the accumulated overflow columns and then promotes them in
92
+ * its own second batch would restart the workspace twice in a row for one user action, and a
93
+ * restart storm is its own incident class here. One batch, one migration, one restart.
94
+ *
95
+ * Side effects are deliberate and belong to planning, not to promotion: stale already-promoted
96
+ * keys are purged from the staging JSON here, BEFORE any column is created and before the RSU
97
+ * pass that would restart this process. That runs even when nothing new is promoted, which is
98
+ * the only way residue on rows a sync never rewrites gets cleaned rather than endlessly
99
+ * re-detected as a phantom "new column".
100
+ *
101
+ * Returns null when there is nothing to do.
102
+ */
103
+ PlanPromotion(companyIntegrationID: string, entityNames: string[], customKeyStats?: Record<string, CustomKeyStat[]>): Promise<PromotionPlan | null>;
43
104
  /** Entry point: promote custom columns for every entity touched by the sync. */
44
105
  PromoteForSync(companyIntegrationID: string, syncedEntityNames: string[], customKeyStats?: Record<string, CustomKeyStat[]>): Promise<SchemaPromotionResult>;
45
106
  /**
@@ -54,6 +115,12 @@ export declare class IntegrationCustomColumnPromoter {
54
115
  * Idempotent, because a pending row is marked Completed only after success and so stays
55
116
  * re-processable after a crash: field maps are filtered against those already active, and the
56
117
  * spread only fills a destination that is still empty.
118
+ *
119
+ * One entity's failure does not stop the others, but it is not swallowed either: after every
120
+ * entry has run, any failure is THROWN, so the caller fails the pending row instead of marking
121
+ * it Completed over columns that exist in the database and are never mapped.
122
+ *
123
+ * @throws PROMOTION_INCOMPLETE naming each entity that failed.
57
124
  */
58
125
  CompletePromotion(promoted: NonNullable<RSUPendingWork['PromotedColumns']>, knownEntities?: ReadonlyMap<string, EntityInfo>): Promise<Array<{
59
126
  EntityName: string;
@@ -122,6 +189,8 @@ export declare class IntegrationCustomColumnPromoter {
122
189
  private toTargetColumn;
123
190
  /** Creates an IOF row per promoted field (IsCustom, MetadataSource='Discovered'). */
124
191
  private createIntegrationObjectFields;
192
+ /** The catalog writes of {@link createIntegrationObjectFields}, run inside the connection's scope. */
193
+ private writeIntegrationObjectFields;
125
194
  /** Creates a field map (source key → new column) so the next sync maps natively → terminate. */
126
195
  private createFieldMaps;
127
196
  /**
@@ -177,11 +246,8 @@ export declare class IntegrationCustomColumnPromoter {
177
246
  private resolveIntegrationID;
178
247
  /** Finds the entity map (ExternalObject↔Entity) for this CI + target entity. */
179
248
  private findEntityMap;
180
- /** Existing IOF field names (lowercased) for an object — for idempotent IOF creation. */
181
- private existingIOFsByName;
182
- /** Resolves the IntegrationObject ID for an external object name under an integration. */
183
- private resolveIntegrationObjectID;
184
249
  private escape;
185
250
  private msg;
186
251
  }
252
+ export {};
187
253
  //# sourceMappingURL=CustomColumnPromoter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CustomColumnPromoter.d.ts","sourceRoot":"","sources":["../../src/integration/CustomColumnPromoter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CustomColumnPromoter.d.ts","sourceRoot":"","sources":["../../src/integration/CustomColumnPromoter.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,9 @@
24
24
  * of. The JSON spread of staged values into those columns happens here too.
25
25
  */
26
26
  import { LogError, LogStatus, LogStatusEx, Metadata, RunView, } from '@memberjunction/core';
27
- import { IntegrationEngine, CUSTOM_OVERFLOW_COLUMN, CONTENT_HASH_COLUMN, computeContentHash, buildOverflowStats, planPromotions, sanitizeColumnName, inferColumnTypeFromStats, } from '@memberjunction/integration-engine';
27
+ import { IntegrationEngine, CUSTOM_OVERFLOW_COLUMN, CONTENT_HASH_COLUMN, computeContentHash, buildOverflowStats, planPromotions, sanitizeColumnName, inferColumnTypeFromStats, BuildCatalogWriter, WithCatalogScope, } from '@memberjunction/integration-engine';
28
+ import { UUIDsEqual } from '@memberjunction/global';
29
+ import { IntegrationEngineBase } from '@memberjunction/integration-engine-base';
28
30
  import { DDLGenerator } from '@memberjunction/integration-schema-builder';
29
31
  import { RuntimeSchemaManager } from '@memberjunction/schema-engine';
30
32
  /** Max rows sampled from the overflow column to compute coverage + infer types (bounded memory). */
@@ -114,14 +116,34 @@ export class IntegrationCustomColumnPromoter {
114
116
  get dbProvider() {
115
117
  return this.provider;
116
118
  }
117
- /** Entry point: promote custom columns for every entity touched by the sync. */
118
- async PromoteForSync(companyIntegrationID, syncedEntityNames, customKeyStats) {
119
+ /**
120
+ * PHASE 1 only: work out what WOULD be promoted, and the ADD COLUMN migrations that would do
121
+ * it, without running a pipeline.
122
+ *
123
+ * Split out so a caller that is ALREADY running an RSU batch can fold these migrations into
124
+ * it. A schema refresh that offers the accumulated overflow columns and then promotes them in
125
+ * its own second batch would restart the workspace twice in a row for one user action, and a
126
+ * restart storm is its own incident class here. One batch, one migration, one restart.
127
+ *
128
+ * Side effects are deliberate and belong to planning, not to promotion: stale already-promoted
129
+ * keys are purged from the staging JSON here, BEFORE any column is created and before the RSU
130
+ * pass that would restart this process. That runs even when nothing new is promoted, which is
131
+ * the only way residue on rows a sync never rewrites gets cleaned rather than endlessly
132
+ * re-detected as a phantom "new column".
133
+ *
134
+ * Returns null when there is nothing to do.
135
+ */
136
+ async PlanPromotion(companyIntegrationID, entityNames, customKeyStats) {
119
137
  const integrationID = await this.resolveIntegrationID(companyIntegrationID);
120
138
  if (!integrationID)
121
- return NOT_PROMOTED;
139
+ return null;
140
+ // ── PHASE 1: PLAN — build every entity's work list + its RSU input; run NO pipeline. ──
141
+ // The old shape ran the FULL RSU pipeline per entity — a sync touching N entities with
142
+ // candidates paid N CodeGen + compile passes where the batch API exists precisely to pay
143
+ // one. Plan everything first, then promote once.
122
144
  const plans = [];
123
145
  const batchInputs = [];
124
- for (const entityName of syncedEntityNames) {
146
+ for (const entityName of entityNames) {
125
147
  try {
126
148
  const planned = await this.planWorkForEntity(companyIntegrationID, entityName, customKeyStats?.[entityName]);
127
149
  if (!planned)
@@ -158,7 +180,7 @@ export class IntegrationCustomColumnPromoter {
158
180
  }
159
181
  }
160
182
  if (plans.length === 0)
161
- return NOT_PROMOTED;
183
+ return null;
162
184
  // Register the follow-up DURABLY before the restart, the way the apply-objects path does
163
185
  // (IntegrationDiscoveryResolver: `rsuInput.PendingWork = [pendingPayload]`). The restart is
164
186
  // what loads the regenerated entity classes, so everything downstream of the DDL — the IOF
@@ -191,6 +213,15 @@ export class IntegrationCustomColumnPromoter {
191
213
  }];
192
214
  batchInputs[0].ContextUser = this.user;
193
215
  }
216
+ return { IntegrationID: integrationID, Plans: plans, BatchInputs: batchInputs };
217
+ }
218
+ /** Entry point: promote custom columns for every entity touched by the sync. */
219
+ async PromoteForSync(companyIntegrationID, syncedEntityNames, customKeyStats) {
220
+ const promotionPlan = await this.PlanPromotion(companyIntegrationID, syncedEntityNames, customKeyStats);
221
+ if (!promotionPlan)
222
+ return NOT_PROMOTED;
223
+ const plans = promotionPlan.Plans;
224
+ const batchInputs = promotionPlan.BatchInputs;
194
225
  // ── PHASE 2: ONE batched RSU pass for ALL entities' ADD COLUMN migrations. ──
195
226
  // RunPipelineBatch runs every migration under one lock, then ONE CodeGen + compile +
196
227
  // restart + git commit — which is now genuinely one restart and one commit for the whole
@@ -260,7 +291,7 @@ export class IntegrationCustomColumnPromoter {
260
291
  EntityName: plan.entityName,
261
292
  EntityMapID: plan.entityMap.ID,
262
293
  ExternalObjectName: plan.entityMap.ExternalObjectName,
263
- IntegrationID: integrationID,
294
+ IntegrationID: promotionPlan.IntegrationID,
264
295
  Columns: plan.work.map(w => ({
265
296
  SourceKey: w.sourceKey,
266
297
  ColumnName: w.columnName,
@@ -294,9 +325,16 @@ export class IntegrationCustomColumnPromoter {
294
325
  * Idempotent, because a pending row is marked Completed only after success and so stays
295
326
  * re-processable after a crash: field maps are filtered against those already active, and the
296
327
  * spread only fills a destination that is still empty.
328
+ *
329
+ * One entity's failure does not stop the others, but it is not swallowed either: after every
330
+ * entry has run, any failure is THROWN, so the caller fails the pending row instead of marking
331
+ * it Completed over columns that exist in the database and are never mapped.
332
+ *
333
+ * @throws PROMOTION_INCOMPLETE naming each entity that failed.
297
334
  */
298
335
  async CompletePromotion(promoted, knownEntities) {
299
336
  const columnsAdded = [];
337
+ const failures = [];
300
338
  for (const entry of promoted) {
301
339
  try {
302
340
  // Provider first — post-restart it holds the REGENERATED class, which is the whole
@@ -305,6 +343,7 @@ export class IntegrationCustomColumnPromoter {
305
343
  const entityInfo = this.provider.EntityByName(entry.EntityName) ?? knownEntities?.get(entry.EntityName);
306
344
  if (!entityInfo) {
307
345
  LogError(`[CustomColumnPromoter] Post-restart: entity '${entry.EntityName}' not found; skipping.`);
346
+ failures.push(`${entry.EntityName}: entity not found`);
308
347
  continue;
309
348
  }
310
349
  // Destination names are CARRIED, never recomputed: uniqueColumnName may have
@@ -329,17 +368,21 @@ export class IntegrationCustomColumnPromoter {
329
368
  needsColumn: false,
330
369
  needsFieldMap: !existingMaps.has(c.SourceKey.toLowerCase()),
331
370
  }));
332
- await this.createIntegrationObjectFields(entry.IntegrationID, entry.ExternalObjectName, named);
371
+ await this.createIntegrationObjectFields(entry.IntegrationID, entry.EntityMapID, entry.ExternalObjectName, named);
333
372
  await this.createFieldMaps(entry.EntityMapID, named.filter(n => n.needsFieldMap));
334
373
  await this.spreadAndRebaseline(entry.EntityName, entry.EntityMapID, entityInfo, named);
335
374
  LogStatus(`[CustomColumnPromoter] Post-restart: completed ${named.length} promoted column(s) on ${entry.EntityName}: ${named.map(n => n.columnName).join(', ')}`);
336
375
  columnsAdded.push(...named.map(n => ({ EntityName: entry.EntityName, ColumnName: n.columnName })));
337
376
  }
338
377
  catch (err) {
339
- // One entity must not abort the others; the pending row stays open for retry.
378
+ // One entity must not abort the others; the failure is thrown once they have all run.
340
379
  LogError(`[CustomColumnPromoter] Post-restart completion failed for '${entry.EntityName}': ${this.msg(err)}`);
380
+ failures.push(`${entry.EntityName}: ${this.msg(err)}`);
341
381
  }
342
382
  }
383
+ if (failures.length > 0) {
384
+ throw new Error(`PROMOTION_INCOMPLETE: ${failures.length} of ${promoted.length} entity(ies) failed to complete — ${failures.join('; ')}`);
385
+ }
343
386
  return columnsAdded;
344
387
  }
345
388
  /**
@@ -668,10 +711,31 @@ export class IntegrationCustomColumnPromoter {
668
711
  };
669
712
  }
670
713
  /** Creates an IOF row per promoted field (IsCustom, MetadataSource='Discovered'). */
671
- async createIntegrationObjectFields(integrationID, externalObjectName, named) {
672
- const objectID = await this.resolveIntegrationObjectID(integrationID, externalObjectName);
714
+ async createIntegrationObjectFields(integrationID, entityMapID, externalObjectName, named) {
715
+ // Resolve the CONNECTION from the entity map rather than hopping connection → integration
716
+ // and writing the shared rows. That hop is what let one tenant's promoted column appear in
717
+ // the catalog every other tenant on the same connector reads.
718
+ const entityMap = IntegrationEngineBase.Instance.EntityMaps.find(m => UUIDsEqual(m.ID, entityMapID));
719
+ const companyIntegration = entityMap
720
+ ? IntegrationEngineBase.Instance.GetCompanyIntegrationByID(entityMap.CompanyIntegrationID)
721
+ : undefined;
722
+ if (!companyIntegration) {
723
+ LogError(`[CustomColumnPromoter] Could not resolve the connection behind entity map ${entityMapID}; catalog rows skipped for '${externalObjectName}'.`);
724
+ return;
725
+ }
726
+ // In the connection's own scope, entered here per entry: the post-restart consumer calls
727
+ // in with no scope, and unscoped a connection with per-connection rows reads as having none,
728
+ // so BuildCatalogWriter throws PER_CONNECTION_CATALOG_MISSING. Per entry, so a batch that
729
+ // spans connections resolves each one to its own catalog.
730
+ await WithCatalogScope(companyIntegration.ID, () => this.writeIntegrationObjectFields(companyIntegration, integrationID, externalObjectName, named), this.user);
731
+ }
732
+ /** The catalog writes of {@link createIntegrationObjectFields}, run inside the connection's scope. */
733
+ async writeIntegrationObjectFields(companyIntegration, integrationID, externalObjectName, named) {
734
+ const writer = BuildCatalogWriter(this.provider, companyIntegration, this.user);
735
+ const objectID = (await writer.ObjectsInScope())
736
+ .find(o => o.Name.toLowerCase() === externalObjectName.toLowerCase())?.ID ?? null;
673
737
  if (!objectID) {
674
- LogError(`[CustomColumnPromoter] No IntegrationObject '${externalObjectName}' for integration ${integrationID}; IOF rows skipped.`);
738
+ LogError(`[CustomColumnPromoter] No catalog object '${externalObjectName}' for integration ${integrationID}; catalog field rows skipped.`);
675
739
  return;
676
740
  }
677
741
  // Lookup-or-reactivate-or-create — NEVER blind-create over an existing field. A field the source
@@ -679,13 +743,16 @@ export class IntegrationCustomColumnPromoter {
679
743
  // key reappears in the payload and reaches promotion, the IOF already exists: reactivate it (so the
680
744
  // active-filtered ApplyAll re-materializes the still-present column) instead of creating a duplicate.
681
745
  // This is the "removed-then-re-added" case + the recovery case (column existed, field map missing).
682
- const existingIOFs = await this.existingIOFsByName(objectID);
746
+ const existingIOFs = new Map();
747
+ for (const f of await writer.FieldsForObject(objectID)) {
748
+ existingIOFs.set((f.Name ?? '').toLowerCase(), { ID: f.ID, Status: f.Status ?? 'Active' });
749
+ }
683
750
  for (const n of named) {
684
751
  const existing = existingIOFs.get(n.sourceKey.toLowerCase());
685
752
  if (existing) {
686
753
  if (existing.Status !== 'Active') {
687
- const reIof = await this.provider.GetEntityObject('MJ: Integration Object Fields', this.user);
688
- if (await reIof.Load(existing.ID)) {
754
+ const reIof = await writer.LoadField(existing.ID);
755
+ if (reIof) {
689
756
  reIof.Status = 'Active';
690
757
  const ok = await reIof.Save();
691
758
  if (!ok)
@@ -694,8 +761,10 @@ export class IntegrationCustomColumnPromoter {
694
761
  }
695
762
  continue;
696
763
  }
697
- const iof = await this.provider.GetEntityObject('MJ: Integration Object Fields', this.user);
698
- iof.NewRecord();
764
+ const iof = await writer.NewFieldRow();
765
+ // An overflow-promoted column was seen in live data, never described by an endpoint and
766
+ // never declared — so its provenance is 'Sampled', and it has no declared row behind it.
767
+ writer.StampNewField(iof, null, 'Sampled');
699
768
  iof.IntegrationObjectID = objectID;
700
769
  iof.Name = n.sourceKey;
701
770
  iof.DisplayName = n.sourceKey;
@@ -957,39 +1026,11 @@ export class IntegrationCustomColumnPromoter {
957
1026
  ExtraFilter: `CompanyIntegrationID='${companyIntegrationID}' AND Entity='${this.escape(entityName)}'`,
958
1027
  ResultType: 'simple',
959
1028
  MaxRows: 1,
1029
+ SkipTotalRowCount: true,
960
1030
  }, this.user);
961
1031
  const row = res.Success ? res.Results?.[0] : undefined;
962
1032
  return row ? { ID: row.ID, ExternalObjectName: row.ExternalObjectName } : null;
963
1033
  }
964
- /** Existing IOF field names (lowercased) for an object — for idempotent IOF creation. */
965
- async existingIOFsByName(objectID) {
966
- const rv = new RunView();
967
- const res = await rv.RunView({
968
- EntityName: 'MJ: Integration Object Fields',
969
- ExtraFilter: `IntegrationObjectID='${objectID}'`,
970
- Fields: ['ID', 'Name', 'Status'],
971
- ResultType: 'simple',
972
- }, this.user);
973
- const map = new Map();
974
- if (res.Success)
975
- for (const r of res.Results ?? []) {
976
- map.set((r.Name ?? '').toLowerCase(), { ID: String(r.ID), Status: r.Status ?? 'Active' });
977
- }
978
- return map;
979
- }
980
- /** Resolves the IntegrationObject ID for an external object name under an integration. */
981
- async resolveIntegrationObjectID(integrationID, externalObjectName) {
982
- const rv = new RunView();
983
- const res = await rv.RunView({
984
- EntityName: 'MJ: Integration Objects',
985
- ExtraFilter: `IntegrationID='${integrationID}' AND Name='${this.escape(externalObjectName)}'`,
986
- Fields: ['ID', 'Name', 'IntegrationID'],
987
- ResultType: 'simple',
988
- MaxRows: 1,
989
- }, this.user);
990
- const row = res.Success ? res.Results?.[0] : undefined;
991
- return row ? row.ID : null;
992
- }
993
1034
  escape(value) {
994
1035
  return value.replace(/'/g, "''");
995
1036
  }