@memberjunction/server 6.2.0-edge.6 → 6.2.0-edge.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +2 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -1
- 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 +41 -12
- 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 +1072 -5
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +10174 -4618
- 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 +329 -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/globalCors.d.ts.map +1 -1
- package/dist/globalCors.js +1 -0
- package/dist/globalCors.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +201 -43
- 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/logging/StartupLogger.d.ts +50 -0
- package/dist/logging/StartupLogger.d.ts.map +1 -1
- package/dist/logging/StartupLogger.js +15 -22
- package/dist/logging/StartupLogger.js.map +1 -1
- package/dist/middleware/BaseServerMiddleware.d.ts +21 -3
- package/dist/middleware/BaseServerMiddleware.d.ts.map +1 -1
- package/dist/middleware/BaseServerMiddleware.js +4 -2
- package/dist/middleware/BaseServerMiddleware.js.map +1 -1
- 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/AgentRunStatusPublisher.d.ts +10 -0
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +18 -2
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- 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 +1 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +61 -16
- 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/InfoResolver.js +1 -1
- package/dist/resolvers/InfoResolver.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 +38 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +181 -88
- 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/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +7 -8
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.d.ts +12 -0
- package/dist/resolvers/TagGovernanceResolver.d.ts.map +1 -1
- package/dist/resolvers/TagGovernanceResolver.js +84 -0
- package/dist/resolvers/TagGovernanceResolver.js.map +1 -1
- package/dist/resolvers/TelemetryResolver.d.ts +13 -8
- package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
- package/dist/resolvers/TelemetryResolver.js +40 -9
- package/dist/resolvers/TelemetryResolver.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/resolvers/meetings/meetingMutations.d.ts.map +1 -1
- package/dist/resolvers/meetings/meetingMutations.js +34 -4
- package/dist/resolvers/meetings/meetingMutations.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/dist/util.d.ts.map +1 -1
- package/dist/util.js +55 -2
- package/dist/util.js.map +1 -1
- package/package.json +107 -100
- 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 +327 -26
- package/src/__tests__/ResolverBase.diagnosticSenderRegistrations.test.ts +26 -0
- 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__/RunAIAgentResolver.streaming.test.ts +28 -4
- 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__/TagGovernanceAndAutotagResolvers.test.ts +261 -0
- package/src/__tests__/TagGovernanceResolver.test.ts +60 -2
- 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__/globalCors.test.ts +12 -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__/review-capture-valid-token-log.test.ts +98 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +5 -5
- 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/broadcast/BaseCacheInvalidationBroadcastProvider.ts +2 -1
- package/src/config.ts +69 -0
- package/src/context.ts +46 -15
- package/src/coreRoutes.ts +14 -7
- package/src/generated/graphql-schemas/__mj.ts +10360 -6479
- package/src/generic/OrphanedConversationDetailReconciler.ts +32 -1
- package/src/generic/ResolverBase.ts +422 -143
- package/src/generic/RunViewResolver.ts +96 -49
- package/src/globalCors.ts +1 -0
- package/src/index.ts +246 -44
- 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/logging/StartupLogger.ts +24 -0
- package/src/middleware/BaseServerMiddleware.ts +20 -3
- 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/AgentRunStatusPublisher.ts +27 -2
- package/src/resolvers/AgentSessionResolver.ts +6 -2
- package/src/resolvers/AutotagPipelineResolver.ts +59 -14
- package/src/resolvers/ConversationRunEventsResolver.ts +2 -0
- package/src/resolvers/ExecuteRemoteOperationResolver.ts +41 -9
- package/src/resolvers/GetDataResolver.ts +35 -13
- package/src/resolvers/InfoResolver.ts +1 -1
- package/src/resolvers/IntegrationDiscoveryResolver.ts +1522 -203
- package/src/resolvers/MCPResolver.ts +15 -2
- package/src/resolvers/QuerySystemUserResolver.ts +211 -100
- 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/SqlLoggingConfigResolver.ts +7 -9
- package/src/resolvers/TagGovernanceResolver.ts +65 -0
- package/src/resolvers/TelemetryResolver.ts +44 -9
- package/src/resolvers/__tests__/GetDataResolver.tokens.test.ts +52 -0
- package/src/resolvers/meetingRecordingRegistration.ts +2 -0
- package/src/resolvers/meetings/meetingMutations.ts +37 -7
- 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/util.ts +55 -3
- package/src/services/TaskOrchestration-Integration.md +0 -188
|
@@ -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
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { mkdtempSync, rmSync, readFileSync, existsSync, statSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { RSUProgressBridge } from '../integration/RSUProgressBridge.js';
|
|
@@ -44,12 +44,39 @@ describe('RSUProgressBridge', () => {
|
|
|
44
44
|
ItemCount: 2,
|
|
45
45
|
Descriptions: ['add Orders', 'add OrderItems'],
|
|
46
46
|
AffectedTables: ['Orders', 'OrderItems'],
|
|
47
|
-
StepTotal:
|
|
47
|
+
StepTotal: 14,
|
|
48
|
+
CompanyIntegrationIDs: ['CI-1'],
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Waits for the emitter's append chain to quiesce.
|
|
53
|
+
*
|
|
54
|
+
* This was a flat 50 ms sleep, which is plenty on an idle machine and NOT always enough with
|
|
55
|
+
* the whole suite running in parallel — the emitter appends through a promise chain the test
|
|
56
|
+
* cannot await, so a slow write made the assertion read "the event was never emitted" when it
|
|
57
|
+
* simply had not landed yet. Observed as an intermittent failure of the stage.error test.
|
|
58
|
+
* Poll for the journal to stop growing instead of guessing how long that takes.
|
|
59
|
+
*/
|
|
51
60
|
async function settle(): Promise<void> {
|
|
52
|
-
|
|
61
|
+
const path = join(rootDir, bridge.CurrentRunID ?? '', 'progress.jsonl');
|
|
62
|
+
const deadline = Date.now() + 5000;
|
|
63
|
+
let last = -1;
|
|
64
|
+
let stableReads = 0;
|
|
65
|
+
while (Date.now() < deadline) {
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
67
|
+
let size = -1;
|
|
68
|
+
try { size = statSync(path).size; } catch { /* not written yet */ }
|
|
69
|
+
if (size === last) {
|
|
70
|
+
stableReads++;
|
|
71
|
+
// A journal that exists and has stopped growing is settled. One that does not
|
|
72
|
+
// exist at all gets a short grace period and then counts as settled too, so a
|
|
73
|
+
// test asserting that NOTHING was written does not pay the full deadline.
|
|
74
|
+
if (stableReads >= 3 && (size >= 0 || stableReads >= 20)) return;
|
|
75
|
+
} else {
|
|
76
|
+
stableReads = 0;
|
|
77
|
+
last = size;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
function runDir(): string {
|
|
@@ -100,7 +127,7 @@ describe('RSUProgressBridge', () => {
|
|
|
100
127
|
expect(manifest.context).toMatchObject({
|
|
101
128
|
itemCount: 2,
|
|
102
129
|
affectedTables: ['Orders', 'OrderItems'],
|
|
103
|
-
stepTotal:
|
|
130
|
+
stepTotal: 14,
|
|
104
131
|
});
|
|
105
132
|
|
|
106
133
|
const events = readEvents(dir);
|
|
@@ -237,6 +264,162 @@ describe('RSUProgressBridge', () => {
|
|
|
237
264
|
expect(start?.message).toBe('step 0 of 12');
|
|
238
265
|
});
|
|
239
266
|
|
|
267
|
+
// ── Connection identity: what makes an RSU run readable over the API at all ──────────────
|
|
268
|
+
|
|
269
|
+
it('stamps the batch connections onto the manifest, which is what the read check tests', async () => {
|
|
270
|
+
// Before this, an RSU manifest carried no connection, `userCanReadRunArtifact` returned false
|
|
271
|
+
// on the missing field, and EVERY RSU run answered "not authorized" to IntegrationGetRun and
|
|
272
|
+
// IntegrationTailRunEvents. The manifest field is the fix.
|
|
273
|
+
bridge.Observe({ ...RUN_START, CompanyIntegrationIDs: ['CI-1'] });
|
|
274
|
+
const dir = runDir();
|
|
275
|
+
await settle();
|
|
276
|
+
|
|
277
|
+
const manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf-8')) as IntegrationRunManifest;
|
|
278
|
+
expect(manifest.companyIntegrationIDs).toEqual(['CI-1']);
|
|
279
|
+
// Singular is set too, so the per-connection list filter and the GQL summary keep working.
|
|
280
|
+
expect(manifest.companyIntegrationID).toBe('CI-1');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('leaves the SINGULAR connection empty for a batch spanning several, keeping the full set', async () => {
|
|
284
|
+
// Picking one member of the set to report as "the" connection would be a lie a client could
|
|
285
|
+
// act on; the AND authorization rule reads the set instead.
|
|
286
|
+
bridge.Observe({ ...RUN_START, CompanyIntegrationIDs: ['CI-1', 'CI-2'] });
|
|
287
|
+
const dir = runDir();
|
|
288
|
+
await settle();
|
|
289
|
+
|
|
290
|
+
const manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf-8')) as IntegrationRunManifest;
|
|
291
|
+
expect(manifest.companyIntegrationIDs).toEqual(['CI-1', 'CI-2']);
|
|
292
|
+
expect(manifest.companyIntegrationID).toBeUndefined();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('records no connection when the batch supplied none, rather than inventing one', async () => {
|
|
296
|
+
bridge.Observe({ ...RUN_START, CompanyIntegrationIDs: [] });
|
|
297
|
+
const dir = runDir();
|
|
298
|
+
await settle();
|
|
299
|
+
|
|
300
|
+
const manifest = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf-8')) as IntegrationRunManifest;
|
|
301
|
+
expect(manifest.companyIntegrationIDs).toEqual([]);
|
|
302
|
+
expect(manifest.companyIntegrationID).toBeUndefined();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ── The restart boundary ─────────────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
it('closes the RestartMJAPI stage and checkpoints BEFORE the process is killed', async () => {
|
|
308
|
+
// `step.end` for RestartMJAPI is unreachable — pm2 kills the process inside the step — so
|
|
309
|
+
// without this the stream ends on an open stage and the run is in-flight forever.
|
|
310
|
+
bridge.Observe(RUN_START);
|
|
311
|
+
const dir = runDir();
|
|
312
|
+
bridge.Observe({ Kind: 'step.start', Name: 'RestartMJAPI', StepIndex: 10, StepTotal: 14 });
|
|
313
|
+
|
|
314
|
+
await bridge.Observe({
|
|
315
|
+
Kind: 'restart.pending',
|
|
316
|
+
RemainingSteps: ['CreateEntityMaps', 'StartSync'],
|
|
317
|
+
StepIndex: 10,
|
|
318
|
+
StepTotal: 14,
|
|
319
|
+
CompanyIntegrationIDs: ['CI-1'],
|
|
320
|
+
PendingWorkIDs: ['PW-1'],
|
|
321
|
+
TotalCount: 2,
|
|
322
|
+
SuccessCount: 2,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const events = readEvents(dir);
|
|
326
|
+
const done = events.find(e => e.eventType === 'stage.complete' && e.stage === 'RestartMJAPI');
|
|
327
|
+
const checkpoint = events.find(e => e.eventType === 'checkpoint' && e.stage === 'RestartMJAPI');
|
|
328
|
+
expect(done).toBeDefined();
|
|
329
|
+
expect(checkpoint?.resumableState).toMatchObject({
|
|
330
|
+
remainingSteps: ['CreateEntityMaps', 'StartSync'],
|
|
331
|
+
stepIndex: 10,
|
|
332
|
+
stepTotal: 14,
|
|
333
|
+
companyIntegrationIDs: ['CI-1'],
|
|
334
|
+
pendingWorkIDs: ['PW-1'],
|
|
335
|
+
});
|
|
336
|
+
// Still in flight: the run is finished by the process on the other side of the restart.
|
|
337
|
+
expect(existsSync(join(dir, 'result.json'))).toBe(false);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('AWAITS the restart checkpoint to disk — an unflushed write would be lost to the kill', async () => {
|
|
341
|
+
// Deliberately does NOT call settle(): the returned promise must have flushed already.
|
|
342
|
+
bridge.Observe(RUN_START);
|
|
343
|
+
const dir = runDir();
|
|
344
|
+
await bridge.Observe({
|
|
345
|
+
Kind: 'restart.pending',
|
|
346
|
+
RemainingSteps: ['CreateEntityMaps', 'StartSync'],
|
|
347
|
+
CompanyIntegrationIDs: ['CI-1'],
|
|
348
|
+
PendingWorkIDs: [],
|
|
349
|
+
TotalCount: 1,
|
|
350
|
+
SuccessCount: 1,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const events = readEvents(dir);
|
|
354
|
+
expect(events.some(e => e.eventType === 'checkpoint' && e.stage === 'RestartMJAPI')).toBe(true);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('ignores a restart.pending that arrives with no run open', async () => {
|
|
358
|
+
await expect(bridge.Observe({
|
|
359
|
+
Kind: 'restart.pending',
|
|
360
|
+
RemainingSteps: [],
|
|
361
|
+
CompanyIntegrationIDs: [],
|
|
362
|
+
PendingWorkIDs: [],
|
|
363
|
+
TotalCount: 0,
|
|
364
|
+
SuccessCount: 0,
|
|
365
|
+
})).resolves.toBeUndefined();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// ── Heartbeat staleness ──────────────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
it('never lets one run heartbeat stamp its stage name onto the NEXT run', async () => {
|
|
371
|
+
// The timer captures its stage; if it outlives the run it was armed for, it writes that
|
|
372
|
+
// stale name into whatever emitter the bridge points at next.
|
|
373
|
+
vi.useFakeTimers();
|
|
374
|
+
try {
|
|
375
|
+
bridge.Observe(RUN_START);
|
|
376
|
+
bridge.Observe({ Kind: 'step.start', Name: 'CompileTypeScript', StepIndex: 8, StepTotal: 14 });
|
|
377
|
+
|
|
378
|
+
// A retry starts a brand-new run while the previous step's heartbeat is still armed.
|
|
379
|
+
bridge.Observe(RUN_START);
|
|
380
|
+
const secondDir = runDir();
|
|
381
|
+
vi.advanceTimersByTime(300_000); // ten ticks' worth
|
|
382
|
+
|
|
383
|
+
vi.useRealTimers();
|
|
384
|
+
await settle();
|
|
385
|
+
|
|
386
|
+
const beats = readEvents(secondDir).filter(e => e.eventType === 'progress.heartbeat');
|
|
387
|
+
expect(beats.filter(b => b.stage === 'CompileTypeScript')).toHaveLength(0);
|
|
388
|
+
expect(beats).toHaveLength(0);
|
|
389
|
+
} finally {
|
|
390
|
+
vi.useRealTimers();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('stops heart-beating once the restart boundary is published', async () => {
|
|
395
|
+
vi.useFakeTimers();
|
|
396
|
+
try {
|
|
397
|
+
bridge.Observe(RUN_START);
|
|
398
|
+
const dir = runDir();
|
|
399
|
+
bridge.Observe({ Kind: 'step.start', Name: 'RestartMJAPI', StepIndex: 10, StepTotal: 14 });
|
|
400
|
+
const pending = bridge.Observe({
|
|
401
|
+
Kind: 'restart.pending',
|
|
402
|
+
RemainingSteps: ['CreateEntityMaps', 'StartSync'],
|
|
403
|
+
CompanyIntegrationIDs: ['CI-1'],
|
|
404
|
+
PendingWorkIDs: [],
|
|
405
|
+
TotalCount: 1,
|
|
406
|
+
SuccessCount: 1,
|
|
407
|
+
});
|
|
408
|
+
vi.useRealTimers();
|
|
409
|
+
await pending;
|
|
410
|
+
|
|
411
|
+
vi.useFakeTimers();
|
|
412
|
+
vi.advanceTimersByTime(300_000);
|
|
413
|
+
vi.useRealTimers();
|
|
414
|
+
await settle();
|
|
415
|
+
|
|
416
|
+
const beats = readEvents(dir).filter(e => e.eventType === 'progress.heartbeat');
|
|
417
|
+
expect(beats).toHaveLength(0);
|
|
418
|
+
} finally {
|
|
419
|
+
vi.useRealTimers();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
240
423
|
it('ignores step and run.end events that arrive with no run open', async () => {
|
|
241
424
|
// Defensive: an observer registered mid-run would see a trailing run.end for a run it never
|
|
242
425
|
// saw start. That must be a no-op, not a crash.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Startup order in index.ts: the boot sweep must exempt restart-killed RSU runs on a replica that
|
|
3
|
+
* runs background workers (processRSUPendingWork is their continuation), and whichever of those
|
|
4
|
+
* nothing re-attached to must be swept once the resuming pass is over. The behaviour of each half
|
|
5
|
+
* is tested in RSUPostRestartProgress.test.ts; this pins the wiring between them, which lives in
|
|
6
|
+
* a bootstrap function no unit test can run.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
const src = readFileSync(join(__dirname, '..', 'index.ts'), 'utf-8');
|
|
13
|
+
|
|
14
|
+
describe('RSU startup sweep wiring', () => {
|
|
15
|
+
it('the boot sweep exempts restart-killed RSU runs only where background workers will resume them', () => {
|
|
16
|
+
expect(src).toMatch(
|
|
17
|
+
/const bootSweep = await SweepInterruptedRuns\(\s*undefined,\s*msg => console\.warn\(msg\),\s*runBackgroundWorkers \? RSUPostRestartProgressSession\.WasKilledAtRestart : undefined\s*\);/
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('the leftover sweep runs after the resuming pass, over exactly the runs the boot sweep left in flight', () => {
|
|
22
|
+
const pending = src.indexOf('processRSUPendingWork().catch(');
|
|
23
|
+
const leftover = src.indexOf('RSUPostRestartProgressSession.SweepUnattached(bootSweep.Skipped');
|
|
24
|
+
const boot = src.indexOf('const bootSweep = await SweepInterruptedRuns(');
|
|
25
|
+
expect(boot).toBeGreaterThan(-1);
|
|
26
|
+
expect(pending).toBeGreaterThan(boot);
|
|
27
|
+
expect(leftover).toBeGreaterThan(pending);
|
|
28
|
+
expect(src.slice(pending, leftover)).toMatch(/\.finally\(/);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -19,6 +19,8 @@ function summary(over: Partial<SchemaRefreshSummaryLike> = {}): SchemaRefreshSum
|
|
|
19
19
|
Succeeded: true,
|
|
20
20
|
ObjectsCreated: 12,
|
|
21
21
|
ObjectsUpdated: 3,
|
|
22
|
+
FieldsCreated: 69,
|
|
23
|
+
FieldsUpdated: 487,
|
|
22
24
|
UnresolvedObjects: ['Widgets'],
|
|
23
25
|
...over,
|
|
24
26
|
};
|
|
@@ -28,7 +30,7 @@ describe('BuildReactivateMessage', () => {
|
|
|
28
30
|
it('leads with the reactivation on every path', () => {
|
|
29
31
|
const paths = [
|
|
30
32
|
BuildReactivateMessage(undefined),
|
|
31
|
-
BuildReactivateMessage(summary({ InProgress: true, ObjectsCreated: 0, ObjectsUpdated: 0, UnresolvedObjects: [] })),
|
|
33
|
+
BuildReactivateMessage(summary({ InProgress: true, ObjectsCreated: 0, ObjectsUpdated: 0, FieldsCreated: 0, FieldsUpdated: 0, UnresolvedObjects: [] })),
|
|
32
34
|
BuildReactivateMessage(summary()),
|
|
33
35
|
BuildReactivateMessage(summary({ Succeeded: false, FailureMessage: 'boom' })),
|
|
34
36
|
];
|
|
@@ -40,6 +42,8 @@ describe('BuildReactivateMessage', () => {
|
|
|
40
42
|
InProgress: true,
|
|
41
43
|
ObjectsCreated: 0,
|
|
42
44
|
ObjectsUpdated: 0,
|
|
45
|
+
FieldsCreated: 0,
|
|
46
|
+
FieldsUpdated: 0,
|
|
43
47
|
UnresolvedObjects: [],
|
|
44
48
|
}));
|
|
45
49
|
|
|
@@ -50,10 +54,11 @@ describe('BuildReactivateMessage', () => {
|
|
|
50
54
|
expect(msg).not.toMatch(/PK-unresolved/);
|
|
51
55
|
});
|
|
52
56
|
|
|
53
|
-
it('reports real counts when the caller asked to block', () => {
|
|
57
|
+
it('reports real counts when the caller asked to block — fields included', () => {
|
|
54
58
|
const msg = BuildReactivateMessage(summary());
|
|
55
|
-
expect(msg).toContain('12 created');
|
|
59
|
+
expect(msg).toContain('12 objects created');
|
|
56
60
|
expect(msg).toContain('3 updated');
|
|
61
|
+
expect(msg).toContain('69 fields created');
|
|
57
62
|
expect(msg).toContain('1 PK-unresolved');
|
|
58
63
|
expect(msg).not.toMatch(/running/i);
|
|
59
64
|
});
|
|
@@ -67,6 +72,8 @@ describe('BuildReactivateMessage', () => {
|
|
|
67
72
|
FailureMessage: 'ConnectionTest failed: No Totara credentials found',
|
|
68
73
|
ObjectsCreated: 0,
|
|
69
74
|
ObjectsUpdated: 0,
|
|
75
|
+
FieldsCreated: 0,
|
|
76
|
+
FieldsUpdated: 0,
|
|
70
77
|
UnresolvedObjects: [],
|
|
71
78
|
}));
|
|
72
79
|
|
|
@@ -2695,6 +2695,28 @@ describe('RealtimeClientSessionResolver.RelayRealtimeUsage', () => {
|
|
|
2695
2695
|
expect(promptRun.Save).not.toHaveBeenCalled();
|
|
2696
2696
|
});
|
|
2697
2697
|
|
|
2698
|
+
it('logs a missing co-agent promptRunID once per session, not once per relay', async () => {
|
|
2699
|
+
errSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
2700
|
+
const missingRunLines = (): string[] =>
|
|
2701
|
+
(errSpy?.mock.calls ?? []).map((c) => c.map(String).join(' ')).filter((m) => m.includes('has no co-agent promptRunID'));
|
|
2702
|
+
const resolver = makeResolver();
|
|
2703
|
+
|
|
2704
|
+
for (const sessionID of ['session-no-run-a', 'session-no-run-b']) {
|
|
2705
|
+
const { provider } = makeUsageProvider({
|
|
2706
|
+
session: makeSessionEntity({ ID: sessionID, Config_: JSON.stringify({ targetAgentID: 'target-1' }) }),
|
|
2707
|
+
});
|
|
2708
|
+
currentProvider = provider;
|
|
2709
|
+
for (let i = 0; i < 3; i++) {
|
|
2710
|
+
await expect(resolver.RelayRealtimeUsage(sessionID, 10, 5, makeCtx())).resolves.toBe(false);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
const lines = missingRunLines();
|
|
2715
|
+
expect(lines).toHaveLength(2);
|
|
2716
|
+
expect(lines[0]).toContain('session-no-run-a');
|
|
2717
|
+
expect(lines[1]).toContain('session-no-run-b');
|
|
2718
|
+
});
|
|
2719
|
+
|
|
2698
2720
|
it('returns false (tolerant, no throw) when the session config is missing/malformed', async () => {
|
|
2699
2721
|
const { provider } = makeUsageProvider({ session: makeSessionEntity({ Config_: '{broken json' }) });
|
|
2700
2722
|
currentProvider = provider;
|