@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.
- package/README.md +69 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/BridgeHostInstance.d.ts +8 -3
- package/dist/agentSessions/BridgeHostInstance.d.ts.map +1 -1
- package/dist/agentSessions/BridgeHostInstance.js +14 -4
- package/dist/agentSessions/BridgeHostInstance.js.map +1 -1
- package/dist/agentSessions/HandoffOfferExpiry.d.ts +10 -0
- package/dist/agentSessions/HandoffOfferExpiry.d.ts.map +1 -0
- package/dist/agentSessions/HandoffOfferExpiry.js +18 -0
- package/dist/agentSessions/HandoffOfferExpiry.js.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +31 -8
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +85 -41
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +14 -4
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +28 -4
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/agentSessions/conversationBrowsers.d.ts +61 -0
- package/dist/agentSessions/conversationBrowsers.d.ts.map +1 -0
- package/dist/agentSessions/conversationBrowsers.js +118 -0
- package/dist/agentSessions/conversationBrowsers.js.map +1 -0
- package/dist/agentSessions/sessionDeadline.d.ts +43 -0
- package/dist/agentSessions/sessionDeadline.d.ts.map +1 -0
- package/dist/agentSessions/sessionDeadline.js +68 -0
- package/dist/agentSessions/sessionDeadline.js.map +1 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +5 -4
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/loginEmailResolution.d.ts +41 -0
- package/dist/auth/loginEmailResolution.d.ts.map +1 -0
- package/dist/auth/loginEmailResolution.js +47 -0
- package/dist/auth/loginEmailResolution.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +50 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +129 -12
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +3 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +23 -5
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +39 -5
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +221 -9
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/redeemLanding.d.ts +2 -2
- package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -1
- package/dist/auth/magicLink/redeemLanding.js +7 -4
- package/dist/auth/magicLink/redeemLanding.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +27 -3
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/magicLink/types.js +0 -4
- package/dist/auth/magicLink/types.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +1 -0
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/backgroundSweepConfigUnits.d.ts +231 -0
- package/dist/backgroundSweepConfigUnits.d.ts.map +1 -0
- package/dist/backgroundSweepConfigUnits.js +180 -0
- package/dist/backgroundSweepConfigUnits.js.map +1 -0
- package/dist/config.d.ts +906 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +35 -4
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +3 -1
- package/dist/coreRoutes.d.ts.map +1 -1
- package/dist/coreRoutes.js +6 -4
- package/dist/coreRoutes.js.map +1 -1
- package/dist/generated/graphql-schemas/__mj.d.ts +228 -0
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +1163 -6
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts +11 -0
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +27 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +50 -5
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +321 -132
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +4 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +47 -7
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +157 -35
- package/dist/index.js.map +1 -1
- package/dist/integration/ConnectionPause.d.ts +121 -0
- package/dist/integration/ConnectionPause.d.ts.map +1 -0
- package/dist/integration/ConnectionPause.js +183 -0
- package/dist/integration/ConnectionPause.js.map +1 -0
- package/dist/integration/CustomColumnPromoter.d.ts +72 -6
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +87 -46
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/integration/EntityMapLifecycle.d.ts +78 -4
- package/dist/integration/EntityMapLifecycle.d.ts.map +1 -1
- package/dist/integration/EntityMapLifecycle.js +199 -5
- package/dist/integration/EntityMapLifecycle.js.map +1 -1
- package/dist/integration/InactiveRowWarnings.d.ts +31 -0
- package/dist/integration/InactiveRowWarnings.d.ts.map +1 -1
- package/dist/integration/InactiveRowWarnings.js +29 -0
- package/dist/integration/InactiveRowWarnings.js.map +1 -1
- package/dist/integration/ObjectNamesByID.d.ts +28 -0
- package/dist/integration/ObjectNamesByID.d.ts.map +1 -0
- package/dist/integration/ObjectNamesByID.js +87 -0
- package/dist/integration/ObjectNamesByID.js.map +1 -0
- package/dist/integration/PostRestartIntrospect.d.ts +26 -0
- package/dist/integration/PostRestartIntrospect.d.ts.map +1 -0
- package/dist/integration/PostRestartIntrospect.js +27 -0
- package/dist/integration/PostRestartIntrospect.js.map +1 -0
- package/dist/integration/RSUPostRestartProgress.d.ts +176 -0
- package/dist/integration/RSUPostRestartProgress.d.ts.map +1 -0
- package/dist/integration/RSUPostRestartProgress.js +323 -0
- package/dist/integration/RSUPostRestartProgress.js.map +1 -0
- package/dist/integration/RSUProgressBridge.d.ts +41 -2
- package/dist/integration/RSUProgressBridge.d.ts.map +1 -1
- package/dist/integration/RSUProgressBridge.js +84 -1
- package/dist/integration/RSUProgressBridge.js.map +1 -1
- package/dist/integration/RunArtifactAuthorization.d.ts +39 -0
- package/dist/integration/RunArtifactAuthorization.d.ts.map +1 -0
- package/dist/integration/RunArtifactAuthorization.js +45 -0
- package/dist/integration/RunArtifactAuthorization.js.map +1 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts +9 -0
- package/dist/integration/SchemaRefreshLaunch.d.ts.map +1 -1
- package/dist/integration/SchemaRefreshLaunch.js +3 -2
- package/dist/integration/SchemaRefreshLaunch.js.map +1 -1
- package/dist/integration/ScopedFieldsForBuild.d.ts +46 -0
- package/dist/integration/ScopedFieldsForBuild.d.ts.map +1 -0
- package/dist/integration/ScopedFieldsForBuild.js +91 -0
- package/dist/integration/ScopedFieldsForBuild.js.map +1 -0
- package/dist/integration/TargetConfigGuard.d.ts +40 -0
- package/dist/integration/TargetConfigGuard.d.ts.map +1 -0
- package/dist/integration/TargetConfigGuard.js +40 -0
- package/dist/integration/TargetConfigGuard.js.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.js +2 -3
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -1
- package/dist/realtimeSessions/sessionAccess.d.ts +15 -0
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +22 -0
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +2 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -1
- package/dist/realtimeWidget/visitorIdentity.js +1 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +4 -7
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/requestOrigin.d.ts +22 -0
- package/dist/requestOrigin.d.ts.map +1 -0
- package/dist/requestOrigin.js +60 -0
- package/dist/requestOrigin.js.map +1 -0
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +6 -2
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +2 -1
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +2 -0
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts +6 -0
- package/dist/resolvers/ExecuteRemoteOperationResolver.d.ts.map +1 -1
- package/dist/resolvers/ExecuteRemoteOperationResolver.js +30 -7
- package/dist/resolvers/ExecuteRemoteOperationResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +34 -13
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +401 -11
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +2690 -1305
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +13 -2
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +36 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +174 -87
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeBridgeResolver.js +1 -0
- package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +6 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +36 -5
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts +62 -10
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +217 -34
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +24 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +185 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +5 -3
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +19 -18
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +3 -1
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.d.ts.map +1 -1
- package/dist/resolvers/meetingRecordingRegistration.js +2 -0
- package/dist/resolvers/meetingRecordingRegistration.js.map +1 -1
- package/dist/rest/MediaAccessKeys.d.ts.map +1 -1
- package/dist/rest/MediaAccessKeys.js +6 -2
- package/dist/rest/MediaAccessKeys.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/rest/OAuthCallbackHandler.js +11 -5
- package/dist/rest/OAuthCallbackHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +3 -3
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.js +2 -4
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +2 -1
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +3 -1
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +11 -2
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.d.ts.map +1 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js +6 -1
- package/dist/services/DurableEntityActionTaskSubmitter.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +2 -26
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +6 -0
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +38 -0
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +1 -0
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/services/WorkQueueHostService.d.ts +2 -1
- package/dist/services/WorkQueueHostService.d.ts.map +1 -1
- package/dist/services/WorkQueueHostService.js +19 -4
- package/dist/services/WorkQueueHostService.js.map +1 -1
- package/dist/services/workQueueConfig.d.ts +2 -2
- package/dist/services/workQueueConfig.js +5 -5
- package/dist/services/workQueueConfig.js.map +1 -1
- package/dist/sharedCache.js +1 -1
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +98 -98
- package/src/__tests__/APIKeyScopeAuth.test.ts +4 -8
- package/src/__tests__/ActiveOperations.test.ts +68 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +13 -0
- package/src/__tests__/AssertTargetConfigs.test.ts +75 -0
- package/src/__tests__/BatchApplyParity.test.ts +109 -0
- package/src/__tests__/BridgeHostInstance.test.ts +42 -0
- package/src/__tests__/CollectInactiveRowWarnings.test.ts +115 -0
- package/src/__tests__/ConnectionPause.test.ts +192 -0
- package/src/__tests__/ConnectionPauseResolver.test.ts +166 -0
- package/src/__tests__/ConnectionPauseSave.test.ts +143 -0
- package/src/__tests__/CustomColumnPromoterCompletion.test.ts +142 -0
- package/src/__tests__/DeleteConnectionRemovesPerConnectionCatalog.test.ts +51 -0
- package/src/__tests__/DetachedDiscoveryStart.test.ts +65 -0
- package/src/__tests__/DurableEntityActionTaskSubmitter.test.ts +73 -0
- package/src/__tests__/EntityMapLifecycle.rekey.test.ts +252 -0
- package/src/__tests__/ExecuteRemoteOperationResolver.test.ts +213 -0
- package/src/__tests__/HandoffOfferExpiry.test.ts +40 -0
- package/src/__tests__/IntegrationRunArtifactAuthorization.test.ts +126 -0
- package/src/__tests__/MagicLinkKeys.test.ts +102 -0
- package/src/__tests__/MediaAccessKeys.test.ts +57 -10
- package/src/__tests__/NewArrivalsDefaultDisabled.test.ts +50 -0
- package/src/__tests__/ObjectNamesByID.test.ts +176 -0
- package/src/__tests__/PostRestartMapsInCatalogScope.test.ts +146 -0
- package/src/__tests__/QuerySystemUserResolver.childRows.test.ts +80 -0
- package/src/__tests__/QuerySystemUserResolver.collision.test.ts +200 -0
- package/src/__tests__/QuerySystemUserResolver.permissions.test.ts +132 -0
- package/src/__tests__/RSUPostRestartProgress.test.ts +343 -0
- package/src/__tests__/RSUProgressBridge.test.ts +188 -5
- package/src/__tests__/RSUStartupSweepWiring.test.ts +30 -0
- package/src/__tests__/ReactivateRefreshMessage.test.ts +10 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +22 -0
- package/src/__tests__/{RefreshAdoptsNewColumns.test.ts → RefreshReportsNewColumns.test.ts} +25 -16
- package/src/__tests__/RemoteBrowserActionRecovery.test.ts +287 -0
- package/src/__tests__/RemoteBrowserActionResolver.goalRunLink.test.ts +3 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +6 -4
- package/src/__tests__/RemoteBrowserAudioStream.test.ts +3 -0
- package/src/__tests__/RemoteBrowserSnapshot.test.ts +3 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +323 -26
- package/src/__tests__/ResolverBase.encryptedApiZeroReencrypt.test.ts +307 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +53 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +60 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +4 -0
- package/src/__tests__/RunAIAgentResolver.consultPredecessor.test.ts +289 -0
- package/src/__tests__/RunAIAgentResolver.handoff.test.ts +649 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +2 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +87 -12
- package/src/__tests__/SchemaEvolutionCustomColumns.test.ts +119 -0
- package/src/__tests__/SchemaRefreshLaunch.test.ts +24 -2
- package/src/__tests__/ScopedFieldsForBuild.test.ts +192 -0
- package/src/__tests__/SessionJanitor.test.ts +195 -4
- package/src/__tests__/SessionManager.test.ts +69 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +2 -2
- package/src/__tests__/TaskGraphAgentRunner.test.ts +272 -16
- package/src/__tests__/ViewOperationsHandler.test.ts +2 -5
- package/src/__tests__/WorkQueueHostService.test.ts +25 -7
- package/src/__tests__/backgroundSweepConfigUnits.test.ts +153 -0
- package/src/__tests__/contextRequestOrigin.test.ts +105 -0
- package/src/__tests__/conversationBrowsers.test.ts +62 -0
- package/src/__tests__/magicLinkExtensionCapabilities.test.ts +413 -0
- package/src/__tests__/orphanedConversationDetailReconciler.start.test.ts +82 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +14 -0
- package/src/__tests__/realtimeRecordingHandler.test.ts +7 -2
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +79 -1
- package/src/__tests__/requestOrigin.test.ts +104 -0
- package/src/__tests__/securityHardeningPr6034.test.ts +120 -0
- package/src/__tests__/sessionDeadline.test.ts +51 -0
- package/src/__tests__/sharedCache.test.ts +11 -0
- package/src/__tests__/unifiedAuth.test.ts +73 -1
- package/src/agentSessions/BridgeHostInstance.ts +20 -5
- package/src/agentSessions/HandoffOfferExpiry.ts +18 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +1 -0
- package/src/agentSessions/SessionJanitor.ts +92 -42
- package/src/agentSessions/SessionManager.ts +38 -8
- package/src/agentSessions/conversationBrowsers.ts +143 -0
- package/src/agentSessions/sessionDeadline.ts +81 -0
- package/src/auth/APIKeyScopeAuth.ts +5 -4
- package/src/auth/__tests__/loginEmailResolution.test.ts +67 -0
- package/src/auth/loginEmailResolution.ts +67 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +146 -14
- package/src/auth/magicLink/MagicLinkRouter.ts +33 -6
- package/src/auth/magicLink/MagicLinkService.ts +250 -10
- package/src/auth/magicLink/index.ts +1 -1
- package/src/auth/magicLink/redeemLanding.ts +7 -4
- package/src/auth/magicLink/types.ts +28 -3
- package/src/auth/newUsers.ts +1 -0
- package/src/backgroundSweepConfigUnits.ts +213 -0
- package/src/config.ts +69 -0
- package/src/context.ts +39 -6
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +822 -7
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +414 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/index.ts +201 -35
- package/src/integration/ConnectionPause.ts +222 -0
- package/src/integration/CustomColumnPromoter.ts +123 -53
- package/src/integration/EntityMapLifecycle.ts +229 -6
- package/src/integration/InactiveRowWarnings.ts +50 -0
- package/src/integration/ObjectNamesByID.ts +97 -0
- package/src/integration/PostRestartIntrospect.ts +49 -0
- package/src/integration/RSUPostRestartProgress.ts +359 -0
- package/src/integration/RSUProgressBridge.ts +85 -2
- package/src/integration/RunArtifactAuthorization.ts +65 -0
- package/src/integration/SchemaRefreshLaunch.ts +12 -2
- package/src/integration/ScopedFieldsForBuild.ts +113 -0
- package/src/integration/TargetConfigGuard.ts +71 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +2 -6
- package/src/realtimeSessions/sessionAccess.ts +30 -0
- package/src/realtimeWidget/WidgetSessionService.ts +2 -0
- package/src/realtimeWidget/visitorIdentity.ts +1 -0
- package/src/realtimeWidget/widgetCore.ts +4 -8
- package/src/realtimeWidget/widgetGuestElevation.ts +1 -0
- package/src/requestOrigin.ts +64 -0
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +2 -1
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +197 -98
- package/src/resolvers/RealtimeBridgeResolver.ts +1 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +40 -7
- package/src/resolvers/RemoteBrowserActionResolver.ts +242 -32
- package/src/resolvers/RunAIAgentResolver.ts +265 -86
- package/src/resolvers/RunAIPromptResolver.ts +18 -18
- package/src/resolvers/RunTemplateResolver.ts +3 -1
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/rest/MediaAccessKeys.ts +6 -2
- package/src/rest/OAuthCallbackHandler.ts +13 -5
- package/src/rest/RealtimeRecordingHandler.ts +5 -9
- package/src/rest/ViewOperationsHandler.ts +2 -1
- package/src/rest/setupRESTEndpoints.ts +11 -2
- package/src/services/DurableEntityActionTaskSubmitter.ts +10 -2
- package/src/services/TaskGraphActionRunner.ts +2 -23
- package/src/services/TaskGraphAgentRunner.ts +43 -0
- package/src/services/TaskGraphContinuationDeliverer.ts +1 -0
- package/src/services/WorkQueueHostService.ts +26 -4
- package/src/services/workQueueConfig.ts +5 -5
- package/src/sharedCache.ts +1 -1
- package/src/types.ts +18 -0
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `CreateQueryExtended` / `UpdateQueryExtended`: a query-permission row that is not saved or not
|
|
3
|
+
* deleted fails the mutation with the entity's own reason, instead of reporting success.
|
|
4
|
+
*
|
|
5
|
+
* The provider is a stub; permission rows are stubs whose Save / Delete report failure on demand.
|
|
6
|
+
*/
|
|
7
|
+
import 'reflect-metadata';
|
|
8
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
9
|
+
import type { RunViewParams } from '@memberjunction/core';
|
|
10
|
+
import { QueryEngine } from '@memberjunction/core-entities';
|
|
11
|
+
|
|
12
|
+
vi.mock('type-graphql', () => {
|
|
13
|
+
const decoratorFactory = (..._args: unknown[]) => (..._decorated: unknown[]) => undefined;
|
|
14
|
+
const exportNames = ['Arg', 'Ctx', 'Field', 'InputType', 'Mutation', 'ObjectType', 'Resolver', 'PubSub', 'Int', 'Float'];
|
|
15
|
+
return { ...Object.fromEntries(exportNames.map((name) => [name, decoratorFactory])), registerEnumType: () => undefined };
|
|
16
|
+
});
|
|
17
|
+
vi.mock('@memberjunction/core-entities-server', () => ({ MJQueryEntityServer: class {} }));
|
|
18
|
+
vi.mock('../generic/DeleteOptionsInput.js', () => ({ DeleteOptionsInput: class {} }));
|
|
19
|
+
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
20
|
+
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
21
|
+
return { ...actual, AssertClientInputWritableForUser: () => undefined, LogStatus: () => undefined, LogError: () => undefined };
|
|
22
|
+
});
|
|
23
|
+
vi.mock('../generated/generated.js', () => ({
|
|
24
|
+
MJQuery_: class {}, MJQueryField_: class {}, MJQueryParameter_: class {}, MJQueryEntity_: class {}, MJQueryPermission_: class {},
|
|
25
|
+
MJQueryResolver: class {
|
|
26
|
+
protected async CheckAPIKeyScopeAuthorization(): Promise<void> { /* authorized */ }
|
|
27
|
+
protected async AfterCreate(): Promise<void> { /* no event */ }
|
|
28
|
+
protected ListenForEntityMessages(): void { /* no pub/sub */ }
|
|
29
|
+
protected async MapFieldNamesToCodeNames(_entityName: string, values: Record<string, unknown>) { return values; }
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
import { MJQueryResolverExtended, CreateQuerySystemUserInput, UpdateQuerySystemUserInput } from '../resolvers/QuerySystemUserResolver.js';
|
|
34
|
+
import type { AppContext } from '../types.js';
|
|
35
|
+
|
|
36
|
+
const QUERY_ID = 'Q0000000-0000-0000-0000-000000000001';
|
|
37
|
+
const ROLE = 'R0000000-0000-0000-0000-000000000001';
|
|
38
|
+
const REFUSED = 'The INSERT statement conflicted with the FOREIGN KEY constraint "FK_QueryPermission_Role".';
|
|
39
|
+
|
|
40
|
+
/** A query or permission row as the stub provider hands out. */
|
|
41
|
+
class StubRecord {
|
|
42
|
+
public ID = '';
|
|
43
|
+
public Name = 'Revenue';
|
|
44
|
+
public CategoryID: string | null = null;
|
|
45
|
+
public QueryID = '';
|
|
46
|
+
public RoleID = '';
|
|
47
|
+
public EntityInfo = {};
|
|
48
|
+
public LatestResult: { Message: string; CompleteMessage: string } | null = null;
|
|
49
|
+
public QueryFields = [];
|
|
50
|
+
public QueryParameters = [];
|
|
51
|
+
public QueryEntities = [];
|
|
52
|
+
public QueryPermissions = [];
|
|
53
|
+
constructor(private readonly entityName: string) {}
|
|
54
|
+
SetMany(): void { /* fields are not inspected */ }
|
|
55
|
+
async Load(id: string): Promise<boolean> { this.ID = id; return true; }
|
|
56
|
+
async Save(): Promise<boolean> {
|
|
57
|
+
if (this.entityName === 'MJ: Query Permissions' && failPermissionSave) {
|
|
58
|
+
this.LatestResult = { Message: REFUSED, CompleteMessage: REFUSED };
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (!this.ID) this.ID = QUERY_ID;
|
|
62
|
+
permissionWrites.push(`${this.entityName}:save`);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
GetAll(): Record<string, unknown> { return { ID: this.ID, Name: this.Name }; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let failPermissionSave: boolean;
|
|
69
|
+
let permissionWrites: string[];
|
|
70
|
+
|
|
71
|
+
const provider = {
|
|
72
|
+
async GetEntityObject(entityName: string): Promise<StubRecord> { return new StubRecord(entityName); },
|
|
73
|
+
async RunView(_params: RunViewParams) {
|
|
74
|
+
return { Success: true, Results: [], RowCount: 0, TotalRowCount: 0, ExecutionTime: 0, ErrorMessage: '', UserViewRunID: '' };
|
|
75
|
+
},
|
|
76
|
+
async RunViews(params: RunViewParams[]) {
|
|
77
|
+
return params.map(() => ({ Success: true, Results: [], RowCount: 0, TotalRowCount: 0, ExecutionTime: 0, ErrorMessage: '', UserViewRunID: '' }));
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const context = {
|
|
82
|
+
providers: [{ provider, type: 'Read-Write' }],
|
|
83
|
+
userPayload: { userRecord: { ID: 'user-1', Email: 'u@example.com', UserRoles: [] } },
|
|
84
|
+
} as unknown as AppContext;
|
|
85
|
+
const pubSub = { publish: async () => undefined } as unknown as Parameters<MJQueryResolverExtended['CreateQueryExtended']>[2];
|
|
86
|
+
|
|
87
|
+
/** A stored permission row whose Delete fails with the entity's reason. */
|
|
88
|
+
function undeletablePermission() {
|
|
89
|
+
return {
|
|
90
|
+
RoleID: ROLE,
|
|
91
|
+
LatestResult: { Message: 'Delete refused by row-level security', CompleteMessage: 'Delete refused by row-level security' },
|
|
92
|
+
Delete: async () => false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
failPermissionSave = false;
|
|
98
|
+
permissionWrites = [];
|
|
99
|
+
vi.restoreAllMocks();
|
|
100
|
+
vi.spyOn(QueryEngine, 'Instance', 'get').mockReturnValue({ GetQueryPermissions: () => [] } as unknown as QueryEngine);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('query permission writes report their failures', () => {
|
|
104
|
+
it('a create whose permission row is not saved fails, naming the query, the role and the reason', async () => {
|
|
105
|
+
failPermissionSave = true;
|
|
106
|
+
const input = Object.assign(new CreateQuerySystemUserInput(), { Name: 'Revenue', SQL: 'SELECT 1 AS One', Permissions: [{ RoleID: ROLE }] });
|
|
107
|
+
const result = await new MJQueryResolverExtended().CreateQueryExtended(input, context, pubSub);
|
|
108
|
+
|
|
109
|
+
expect(result.Success).toBe(false);
|
|
110
|
+
expect(result.ErrorMessage).toContain(`Query 'Revenue' (ID ${QUERY_ID}) was created, but its permissions were not`);
|
|
111
|
+
expect(result.ErrorMessage).toContain(`role ${ROLE}: ${REFUSED}`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('an update whose new permission row is not saved fails with the reason', async () => {
|
|
115
|
+
failPermissionSave = true;
|
|
116
|
+
const input = Object.assign(new UpdateQuerySystemUserInput(), { ID: QUERY_ID, Permissions: [{ RoleID: ROLE }] });
|
|
117
|
+
const result = await new MJQueryResolverExtended().UpdateQueryExtended(input, context, pubSub);
|
|
118
|
+
|
|
119
|
+
expect(result.Success).toBe(false);
|
|
120
|
+
expect(result.ErrorMessage).toBe(`Failed to update query permissions: Failed to save the query permission for role ${ROLE}: ${REFUSED}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('an update whose old permission row is not deleted fails with the reason and saves no new row', async () => {
|
|
124
|
+
vi.spyOn(QueryEngine, 'Instance', 'get').mockReturnValue({ GetQueryPermissions: () => [undeletablePermission()] } as unknown as QueryEngine);
|
|
125
|
+
const input = Object.assign(new UpdateQuerySystemUserInput(), { ID: QUERY_ID, Permissions: [{ RoleID: ROLE }] });
|
|
126
|
+
const result = await new MJQueryResolverExtended().UpdateQueryExtended(input, context, pubSub);
|
|
127
|
+
|
|
128
|
+
expect(result.Success).toBe(false);
|
|
129
|
+
expect(result.ErrorMessage).toBe(`Failed to update query permissions: Failed to delete the query permission for role ${ROLE}: Delete refused by row-level security`);
|
|
130
|
+
expect(permissionWrites).not.toContain('MJ: Query Permissions:save');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { IntegrationProgressEmitter, IntegrationProgressReader, SweepInterruptedRuns } from '@memberjunction/integration-progress-artifacts';
|
|
6
|
+
import type { IntegrationProgressEvent, IntegrationRunManifest, IntegrationRunResult } from '@memberjunction/integration-progress-artifacts';
|
|
7
|
+
import { RSUPostRestartProgressSession } from '../integration/RSUPostRestartProgress.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The half of an RSU run that happens after the pipeline restarts the API on itself.
|
|
11
|
+
*
|
|
12
|
+
* What must hold, in order of how badly it hurts when it doesn't:
|
|
13
|
+
* 1. the post-restart events land in the SAME run, above a tailing client's cursor — otherwise the
|
|
14
|
+
* client polls forever and never learns the connector went live;
|
|
15
|
+
* 2. the run reaches a terminal state — otherwise it stays flagged in-flight forever;
|
|
16
|
+
* 3. a failure names the objects that were never mapped — otherwise the operator's only signal that
|
|
17
|
+
* something needs re-applying is a log line nobody reads.
|
|
18
|
+
*/
|
|
19
|
+
describe('RSUPostRestartProgress', () => {
|
|
20
|
+
let rootDir: string;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
rootDir = mkdtempSync(join(tmpdir(), 'rsu-post-'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Leaves behind exactly what a killed pre-restart process leaves: a manifest, a journal ending at
|
|
32
|
+
* the restart boundary, and NO result. Returns the client's cursor at the moment of the kill.
|
|
33
|
+
*/
|
|
34
|
+
async function killedRunAt(runID: string, companyIntegrationIDs: string[], opts: { checkpoint?: boolean } = {}): Promise<number> {
|
|
35
|
+
const manifest: IntegrationRunManifest = {
|
|
36
|
+
runID,
|
|
37
|
+
runKind: 'RSU',
|
|
38
|
+
triggerType: 'Pipeline',
|
|
39
|
+
startedAt: new Date().toISOString(),
|
|
40
|
+
companyIntegrationIDs,
|
|
41
|
+
companyIntegrationID: companyIntegrationIDs.length === 1 ? companyIntegrationIDs[0] : undefined,
|
|
42
|
+
};
|
|
43
|
+
const e = new IntegrationProgressEmitter(manifest, { rootDir });
|
|
44
|
+
e.runStart('RSU pipeline started');
|
|
45
|
+
e.stageStart('RunCodeGen', 'step 7 of 12');
|
|
46
|
+
e.stageComplete('RunCodeGen');
|
|
47
|
+
e.stageStart('RestartMJAPI', 'step 10 of 12');
|
|
48
|
+
e.stageComplete('RestartMJAPI');
|
|
49
|
+
if (opts.checkpoint !== false) {
|
|
50
|
+
e.checkpoint('RestartMJAPI', {
|
|
51
|
+
runID,
|
|
52
|
+
remainingSteps: ['CreateEntityMaps', 'StartSync'],
|
|
53
|
+
stepIndex: 10,
|
|
54
|
+
stepTotal: 12,
|
|
55
|
+
companyIntegrationIDs,
|
|
56
|
+
pendingWorkIDs: ['PW-1'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
await e.flush();
|
|
60
|
+
return e.LatestSeq;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function events(runID: string): IntegrationProgressEvent[] {
|
|
64
|
+
const out: IntegrationProgressEvent[] = [];
|
|
65
|
+
for (const line of readFileSync(join(rootDir, runID, 'progress.jsonl'), 'utf-8').split('\n')) {
|
|
66
|
+
if (!line.trim()) continue;
|
|
67
|
+
try { out.push(JSON.parse(line) as IntegrationProgressEvent); } catch { /* torn */ }
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function result(runID: string): IntegrationRunResult {
|
|
73
|
+
return JSON.parse(readFileSync(join(rootDir, runID, 'result.json'), 'utf-8')) as IntegrationRunResult;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
it('the startup sweep leaves a restart-killed run in flight when told it is a continuation, so For() re-attaches', async () => {
|
|
77
|
+
// #4469's SweepInterruptedRuns runs at boot, BEFORE processRSUPendingWork. Without an
|
|
78
|
+
// exemption it closes the very run this process is meant to continue.
|
|
79
|
+
await killedRunAt('rsu-s1', ['CI-S1']);
|
|
80
|
+
|
|
81
|
+
const sweep = await SweepInterruptedRuns(rootDir, undefined, RSUPostRestartProgressSession.WasKilledAtRestart);
|
|
82
|
+
|
|
83
|
+
expect(sweep.Skipped).toEqual(['rsu-s1']);
|
|
84
|
+
expect(sweep.Swept).toEqual([]);
|
|
85
|
+
expect(existsSync(join(rootDir, 'rsu-s1', 'result.json'))).toBe(false);
|
|
86
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
87
|
+
expect(await session.For('CI-S1')).not.toBeNull();
|
|
88
|
+
await session.FinishAll();
|
|
89
|
+
expect(existsSync(join(rootDir, 'rsu-s1', 'result.json'))).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('without that exemption the sweep closes the run first and For() finds nothing', async () => {
|
|
93
|
+
await killedRunAt('rsu-s2', ['CI-S2']);
|
|
94
|
+
|
|
95
|
+
await SweepInterruptedRuns(rootDir);
|
|
96
|
+
|
|
97
|
+
expect(result('rsu-s2').exitReason).toBe('killed');
|
|
98
|
+
expect(await new RSUPostRestartProgressSession({ rootDir }).For('CI-S2')).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('after the resuming pass, a restart-killed run nothing re-attached to is swept as killed; an attached one is left to its session', async () => {
|
|
102
|
+
await killedRunAt('rsu-a', ['CI-A']);
|
|
103
|
+
await killedRunAt('rsu-b', ['CI-B']);
|
|
104
|
+
const boot = await SweepInterruptedRuns(rootDir, undefined, RSUPostRestartProgressSession.WasKilledAtRestart);
|
|
105
|
+
expect([...boot.Skipped].sort()).toEqual(['rsu-a', 'rsu-b']);
|
|
106
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
107
|
+
expect(await session.For('CI-A')).not.toBeNull(); // only A has pending work to resume it
|
|
108
|
+
|
|
109
|
+
const after = await RSUPostRestartProgressSession.SweepUnattached(boot.Skipped, rootDir);
|
|
110
|
+
|
|
111
|
+
expect(after.Swept).toEqual(['rsu-b']);
|
|
112
|
+
expect(result('rsu-b').exitReason).toBe('killed');
|
|
113
|
+
expect(existsSync(join(rootDir, 'rsu-a', 'result.json'))).toBe(false);
|
|
114
|
+
await session.FinishAll();
|
|
115
|
+
expect(existsSync(join(rootDir, 'rsu-a', 'result.json'))).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('SweepUnattached judges only the runs the startup sweep left in flight — never a run this process started', async () => {
|
|
119
|
+
await killedRunAt('rsu-c', ['CI-C']);
|
|
120
|
+
|
|
121
|
+
const after = await RSUPostRestartProgressSession.SweepUnattached(['some-other-run'], rootDir);
|
|
122
|
+
|
|
123
|
+
expect(after.Swept).toEqual([]);
|
|
124
|
+
expect(existsSync(join(rootDir, 'rsu-c', 'result.json'))).toBe(false);
|
|
125
|
+
expect(await RSUPostRestartProgressSession.SweepUnattached([], rootDir)).toEqual({ Swept: [], Failed: [], Skipped: [] });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('emits both missing steps into the original run, visible to a client tailing past the restart', async () => {
|
|
129
|
+
const cursor = await killedRunAt('rsu-1', ['CI-1']);
|
|
130
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
131
|
+
|
|
132
|
+
const progress = await session.For('CI-1');
|
|
133
|
+
expect(progress).not.toBeNull();
|
|
134
|
+
progress!.BeginEntityMaps(3);
|
|
135
|
+
progress!.ObjectMapped('Contacts', 1, 3);
|
|
136
|
+
progress!.ObjectMapped('Accounts', 2, 3);
|
|
137
|
+
progress!.ObjectMapped('Orders', 3, 3);
|
|
138
|
+
progress!.CompleteEntityMaps(3, 3, 0);
|
|
139
|
+
progress!.BeginStartSync();
|
|
140
|
+
progress!.CompleteStartSync('SYNC-RUN-42');
|
|
141
|
+
await session.FinishAll();
|
|
142
|
+
|
|
143
|
+
const reader = new IntegrationProgressReader(rootDir);
|
|
144
|
+
const tail = await reader.Tail('rsu-1', cursor);
|
|
145
|
+
|
|
146
|
+
// Everything the second process wrote is ABOVE the cursor the first process left the client on.
|
|
147
|
+
expect(tail.length).toBeGreaterThan(0);
|
|
148
|
+
for (const ev of tail) expect(ev.seq).toBeGreaterThan(cursor);
|
|
149
|
+
|
|
150
|
+
const kinds = tail.map(e => `${e.eventType}:${e.stage ?? ''}`);
|
|
151
|
+
expect(kinds).toContain('run.resumed:');
|
|
152
|
+
expect(kinds).toContain('stage.start:CreateEntityMaps');
|
|
153
|
+
expect(kinds).toContain('stage.complete:CreateEntityMaps');
|
|
154
|
+
expect(kinds).toContain('stage.start:StartSync');
|
|
155
|
+
expect(kinds).toContain('stage.complete:StartSync');
|
|
156
|
+
expect(kinds).toContain('run.complete:');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('reports per-object heartbeats while mapping, without double-counting the stage rollup', async () => {
|
|
160
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
161
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
162
|
+
const progress = (await session.For('CI-1'))!;
|
|
163
|
+
progress.BeginEntityMaps(2);
|
|
164
|
+
progress.ObjectMapped('Contacts', 1, 2);
|
|
165
|
+
progress.ObjectMapped('Accounts', 2, 2);
|
|
166
|
+
progress.CompleteEntityMaps(2, 2, 0);
|
|
167
|
+
progress.BeginStartSync();
|
|
168
|
+
progress.CompleteStartSync(null);
|
|
169
|
+
await session.FinishAll();
|
|
170
|
+
|
|
171
|
+
const beats = events('rsu-1').filter(e => e.eventType === 'progress.heartbeat' && e.stage === 'CreateEntityMaps');
|
|
172
|
+
expect(beats.length).toBe(3); // one on open + one per object
|
|
173
|
+
expect(beats.at(-1)?.counts).toMatchObject({ processed: 2, totalKnown: 2 });
|
|
174
|
+
|
|
175
|
+
// Heartbeat counts must NOT feed the aggregate; only the stage rollup does. 2, not 5.
|
|
176
|
+
expect(result('rsu-1').aggregateCounts).toMatchObject({ processed: 2, succeeded: 2, failed: 0 });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('carries the sync run id on StartSync so a client can hop to the sync stream', async () => {
|
|
180
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
181
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
182
|
+
const progress = (await session.For('CI-1'))!;
|
|
183
|
+
progress.BeginEntityMaps(1);
|
|
184
|
+
progress.CompleteEntityMaps(1, 1, 0);
|
|
185
|
+
progress.BeginStartSync();
|
|
186
|
+
progress.CompleteStartSync('SYNC-RUN-42');
|
|
187
|
+
await session.FinishAll();
|
|
188
|
+
|
|
189
|
+
const done = events('rsu-1').find(e => e.eventType === 'stage.complete' && e.stage === 'StartSync');
|
|
190
|
+
expect(done?.data).toMatchObject({ syncRunID: 'SYNC-RUN-42' });
|
|
191
|
+
// No counts on this stage — the records belong to the sync's own run.
|
|
192
|
+
expect(done?.counts).toBeUndefined();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('continues the determinate stepper from the checkpoint the killed process left', async () => {
|
|
196
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
197
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
198
|
+
const progress = (await session.For('CI-1'))!;
|
|
199
|
+
progress.BeginEntityMaps(1);
|
|
200
|
+
progress.CompleteEntityMaps(1, 1, 0);
|
|
201
|
+
progress.BeginStartSync();
|
|
202
|
+
progress.CompleteStartSync(null);
|
|
203
|
+
await session.FinishAll();
|
|
204
|
+
|
|
205
|
+
const all = events('rsu-1');
|
|
206
|
+
expect(all.find(e => e.eventType === 'stage.start' && e.stage === 'CreateEntityMaps')?.message).toBe('step 11 of 12');
|
|
207
|
+
expect(all.find(e => e.eventType === 'stage.start' && e.stage === 'StartSync')?.message).toBe('step 12 of 12');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('still re-attaches when the checkpoint did NOT survive the kill — it just loses the step numbers', async () => {
|
|
211
|
+
// Re-attachment keys off the manifest, not the checkpoint, precisely so a checkpoint racing
|
|
212
|
+
// the kill signal cannot cost the whole continuation.
|
|
213
|
+
const cursor = await killedRunAt('rsu-1', ['CI-1'], { checkpoint: false });
|
|
214
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
215
|
+
const progress = await session.For('CI-1');
|
|
216
|
+
expect(progress).not.toBeNull();
|
|
217
|
+
progress!.BeginEntityMaps(1);
|
|
218
|
+
progress!.CompleteEntityMaps(1, 1, 0);
|
|
219
|
+
progress!.BeginStartSync();
|
|
220
|
+
progress!.CompleteStartSync(null);
|
|
221
|
+
await session.FinishAll();
|
|
222
|
+
|
|
223
|
+
const reader = new IntegrationProgressReader(rootDir);
|
|
224
|
+
const tail = await reader.Tail('rsu-1', cursor);
|
|
225
|
+
expect(tail.some(e => e.eventType === 'stage.start' && e.stage === 'CreateEntityMaps')).toBe(true);
|
|
226
|
+
expect(tail.find(e => e.eventType === 'stage.start' && e.stage === 'CreateEntityMaps')?.message).toBeUndefined();
|
|
227
|
+
expect(result('rsu-1').success).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('names the objects that were never mapped when the work fails', async () => {
|
|
231
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
232
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
233
|
+
const progress = (await session.For('CI-1'))!;
|
|
234
|
+
progress.BeginEntityMaps(3);
|
|
235
|
+
progress.ObjectMapped('Contacts', 1, 3);
|
|
236
|
+
progress.FailEntityMaps('provider timed out', ['Accounts', 'Orders']);
|
|
237
|
+
await session.FinishAll();
|
|
238
|
+
|
|
239
|
+
const err = events('rsu-1').find(e => e.eventType === 'stage.error' && e.stage === 'CreateEntityMaps');
|
|
240
|
+
expect(err?.message).toContain('provider timed out');
|
|
241
|
+
expect(err?.message).toContain('Accounts');
|
|
242
|
+
expect(err?.message).toContain('Orders');
|
|
243
|
+
expect(err?.data).toMatchObject({ code: 'RSU_POST_RESTART_ENTITY_MAPS_FAILED', unmappedObjects: ['Accounts', 'Orders'] });
|
|
244
|
+
|
|
245
|
+
const res = result('rsu-1');
|
|
246
|
+
expect(res.success).toBe(false);
|
|
247
|
+
expect(res.exitReason).toBe('failed');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('never leaves a stage open when the run terminates', async () => {
|
|
251
|
+
// A stage left open pins a client's stepper on it forever.
|
|
252
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
253
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
254
|
+
const progress = (await session.For('CI-1'))!;
|
|
255
|
+
progress.BeginEntityMaps(2); // and then the process gives up mid-stage
|
|
256
|
+
await session.FinishAll();
|
|
257
|
+
|
|
258
|
+
const all = events('rsu-1');
|
|
259
|
+
const opened = all.filter(e => e.eventType === 'stage.start' && e.stage === 'CreateEntityMaps').length;
|
|
260
|
+
const closed = all.filter(e => (e.eventType === 'stage.complete' || e.eventType === 'stage.error') && e.stage === 'CreateEntityMaps').length;
|
|
261
|
+
expect(opened).toBe(closed);
|
|
262
|
+
expect(result('rsu-1').success).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('terminates the run EXACTLY once even when several pending-work items share it', async () => {
|
|
266
|
+
// One RSU batch registers one pending-work row per input; attaching per row would try to
|
|
267
|
+
// resume and terminate the same run repeatedly, and the second attempt would report nothing.
|
|
268
|
+
const cursor = await killedRunAt('rsu-batch', ['CI-1', 'CI-2']);
|
|
269
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
270
|
+
|
|
271
|
+
const a = await session.For('CI-1');
|
|
272
|
+
const b = await session.For('CI-2');
|
|
273
|
+
expect(a).not.toBeNull();
|
|
274
|
+
expect(b).toBe(a); // the SAME continuation, not a second one
|
|
275
|
+
|
|
276
|
+
a!.BeginEntityMaps(1);
|
|
277
|
+
a!.CompleteEntityMaps(1, 1, 0);
|
|
278
|
+
a!.BeginStartSync();
|
|
279
|
+
a!.CompleteStartSync('SYNC-A');
|
|
280
|
+
await session.FinishAll();
|
|
281
|
+
await session.FinishAll(); // idempotent
|
|
282
|
+
|
|
283
|
+
const terminals = events('rsu-batch').filter(e => e.eventType === 'run.complete' || e.eventType === 'run.fail');
|
|
284
|
+
expect(terminals).toHaveLength(1);
|
|
285
|
+
expect(result('rsu-batch').success).toBe(true);
|
|
286
|
+
|
|
287
|
+
const reader = new IntegrationProgressReader(rootDir);
|
|
288
|
+
expect((await reader.Tail('rsu-batch', cursor)).length).toBeGreaterThan(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('returns null — and never throws — when there is no run to continue', async () => {
|
|
292
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
293
|
+
expect(await session.For('CI-UNKNOWN')).toBeNull();
|
|
294
|
+
await expect(session.FinishAll()).resolves.toBeUndefined();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('does not adopt a run that was not killed at the restart', async () => {
|
|
298
|
+
// An RSU run still legitimately in flight at an earlier step belongs to another process; the
|
|
299
|
+
// last-stage check is what keeps this one from hijacking and terminating it.
|
|
300
|
+
const e = new IntegrationProgressEmitter(
|
|
301
|
+
{ runID: 'rsu-early', runKind: 'RSU', startedAt: new Date().toISOString(), companyIntegrationIDs: ['CI-1'] },
|
|
302
|
+
{ rootDir }
|
|
303
|
+
);
|
|
304
|
+
e.runStart();
|
|
305
|
+
e.stageStart('RunCodeGen');
|
|
306
|
+
await e.flush();
|
|
307
|
+
|
|
308
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
309
|
+
expect(await session.For('CI-1')).toBeNull();
|
|
310
|
+
expect(existsSync(join(rootDir, 'rsu-early', 'result.json'))).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('does not adopt an already-finished run', async () => {
|
|
314
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
315
|
+
const first = new RSUPostRestartProgressSession({ rootDir });
|
|
316
|
+
const progress = (await first.For('CI-1'))!;
|
|
317
|
+
progress.BeginEntityMaps(1);
|
|
318
|
+
progress.CompleteEntityMaps(1, 1, 0);
|
|
319
|
+
progress.BeginStartSync();
|
|
320
|
+
progress.CompleteStartSync(null);
|
|
321
|
+
await first.FinishAll();
|
|
322
|
+
|
|
323
|
+
// A later boot re-reading the queue must not reopen the closed run.
|
|
324
|
+
const second = new RSUPostRestartProgressSession({ rootDir });
|
|
325
|
+
expect(await second.For('CI-1')).toBeNull();
|
|
326
|
+
expect(result('rsu-1').success).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('records a skipped sync as a completed stage, not a silent gap', async () => {
|
|
330
|
+
await killedRunAt('rsu-1', ['CI-1']);
|
|
331
|
+
const session = new RSUPostRestartProgressSession({ rootDir });
|
|
332
|
+
const progress = (await session.For('CI-1'))!;
|
|
333
|
+
progress.BeginEntityMaps(1);
|
|
334
|
+
progress.CompleteEntityMaps(1, 1, 0);
|
|
335
|
+
progress.BeginStartSync();
|
|
336
|
+
progress.SkipStartSync('the caller asked for no initial sync (StartSync=false)');
|
|
337
|
+
await session.FinishAll();
|
|
338
|
+
|
|
339
|
+
const done = events('rsu-1').find(e => e.eventType === 'stage.complete' && e.stage === 'StartSync');
|
|
340
|
+
expect(done?.data).toMatchObject({ skipped: true, syncRunID: null });
|
|
341
|
+
expect(result('rsu-1').success).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
});
|