@memberjunction/server 6.2.0-edge.4 → 6.2.0-edge.6
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 +47 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/agentSessions/SessionJanitor.d.ts +91 -30
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +311 -81
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts +17 -3
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +44 -10
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/apiRoot.d.ts +32 -0
- package/dist/apiRoot.d.ts.map +1 -0
- package/dist/apiRoot.js +67 -0
- package/dist/apiRoot.js.map +1 -0
- package/dist/auth/AuthProviderCatalogRouter.d.ts.map +1 -1
- package/dist/auth/AuthProviderCatalogRouter.js +27 -0
- package/dist/auth/AuthProviderCatalogRouter.js.map +1 -1
- package/dist/auth/LoginScreenEngine.d.ts +22 -0
- package/dist/auth/LoginScreenEngine.d.ts.map +1 -0
- package/dist/auth/LoginScreenEngine.js +57 -0
- package/dist/auth/LoginScreenEngine.js.map +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +52 -10
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +10 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +37 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/localDevAuth.d.ts +79 -0
- package/dist/auth/localDevAuth.d.ts.map +1 -0
- package/dist/auth/localDevAuth.js +439 -0
- package/dist/auth/localDevAuth.js.map +1 -0
- package/dist/auth/magicLink/MagicLinkKeys.d.ts +2 -2
- package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js +1 -1
- package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +3 -2
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +12 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.d.ts +1 -1
- package/dist/auth/sessionUserClone.d.ts.map +1 -1
- package/dist/auth/sessionUserClone.js +3 -2
- package/dist/auth/sessionUserClone.js.map +1 -1
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts +292 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js +442 -0
- package/dist/broadcast/BaseCacheInvalidationBroadcastProvider.js.map +1 -0
- package/dist/broadcast/index.d.ts +6 -0
- package/dist/broadcast/index.d.ts.map +1 -0
- package/dist/broadcast/index.js +6 -0
- package/dist/broadcast/index.js.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts +43 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js +144 -0
- package/dist/broadcast/providers/AWSIoTCoreBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts +46 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js +149 -0
- package/dist/broadcast/providers/AzureWebPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts +49 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js +131 -0
- package/dist/broadcast/providers/GCPPubSubBroadcastProvider.js.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts +24 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.d.ts.map +1 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js +51 -0
- package/dist/broadcast/providers/SequencedRoleBucketBroadcastProvider.js.map +1 -0
- package/dist/cache.d.ts +3 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +632 -22
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +104 -4
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +3 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +29 -8
- package/dist/context.js.map +1 -1
- package/dist/coreRoutes.d.ts +29 -0
- package/dist/coreRoutes.d.ts.map +1 -0
- package/dist/coreRoutes.js +156 -0
- package/dist/coreRoutes.js.map +1 -0
- package/dist/generated/graphql-schemas/__mj.d.ts +1016 -29
- package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
- package/dist/generated/graphql-schemas/__mj.js +5125 -434
- package/dist/generated/graphql-schemas/__mj.js.map +1 -1
- package/dist/generic/CacheInvalidationResolver.d.ts +85 -30
- package/dist/generic/CacheInvalidationResolver.d.ts.map +1 -1
- package/dist/generic/CacheInvalidationResolver.js +175 -60
- package/dist/generic/CacheInvalidationResolver.js.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -1
- package/dist/generic/OrphanedConversationDetailReconciler.js +2 -0
- package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -1
- package/dist/generic/PubSubManager.d.ts +10 -0
- package/dist/generic/PubSubManager.d.ts.map +1 -1
- package/dist/generic/PubSubManager.js +44 -1
- package/dist/generic/PubSubManager.js.map +1 -1
- package/dist/generic/PushStatusResolver.d.ts +39 -6
- package/dist/generic/PushStatusResolver.d.ts.map +1 -1
- package/dist/generic/PushStatusResolver.js +94 -12
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +168 -69
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +464 -405
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +11 -3
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +91 -13
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/generic/actionOutputFieldSecurity.d.ts +26 -0
- package/dist/generic/actionOutputFieldSecurity.d.ts.map +1 -0
- package/dist/generic/actionOutputFieldSecurity.js +47 -0
- package/dist/generic/actionOutputFieldSecurity.js.map +1 -0
- package/dist/generic/fieldSecurityTransport.d.ts +30 -0
- package/dist/generic/fieldSecurityTransport.d.ts.map +1 -0
- package/dist/generic/fieldSecurityTransport.js +30 -0
- package/dist/generic/fieldSecurityTransport.js.map +1 -0
- package/dist/globalCors.d.ts +33 -0
- package/dist/globalCors.d.ts.map +1 -0
- package/dist/globalCors.js +58 -0
- package/dist/globalCors.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -222
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -1
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.d.ts +12 -0
- package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -1
- package/dist/logging/variablesLoggingMiddleware.js +50 -1
- package/dist/logging/variablesLoggingMiddleware.js.map +1 -1
- package/dist/multiTenancy/index.d.ts.map +1 -1
- package/dist/multiTenancy/index.js +22 -2
- package/dist/multiTenancy/index.js.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js +2 -1
- package/dist/realtimeSessions/RealtimeSessionVerificationService.js.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.d.ts.map +1 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js +2 -1
- package/dist/realtimeSessions/RealtimeVerifyRouter.js.map +1 -1
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts +100 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.d.ts.map +1 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js +173 -0
- package/dist/realtimeSessions/RecordingAssemblyScheduler.js.map +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.d.ts.map +1 -1
- package/dist/realtimeSessions/SessionVerificationPortsBase.js +1 -0
- package/dist/realtimeSessions/SessionVerificationPortsBase.js.map +1 -1
- package/dist/realtimeSessions/index.d.ts +2 -0
- package/dist/realtimeSessions/index.d.ts.map +1 -1
- package/dist/realtimeSessions/index.js +2 -0
- package/dist/realtimeSessions/index.js.map +1 -1
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts +32 -0
- package/dist/realtimeSessions/ownedRealtimeSession.d.ts.map +1 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js +56 -0
- package/dist/realtimeSessions/ownedRealtimeSession.js.map +1 -0
- package/dist/realtimeSessions/sessionAccess.d.ts +91 -8
- package/dist/realtimeSessions/sessionAccess.d.ts.map +1 -1
- package/dist/realtimeSessions/sessionAccess.js +127 -20
- package/dist/realtimeSessions/sessionAccess.js.map +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js +1 -1
- package/dist/realtimeSessions/sessionConfigStamps.js.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.d.ts.map +1 -1
- package/dist/realtimeSessions/subscriptionAuthorization.js +5 -2
- package/dist/realtimeSessions/subscriptionAuthorization.js.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.d.ts +2 -0
- package/dist/realtimeSessions/verificationWorkflow.d.ts.map +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js +1 -1
- package/dist/realtimeSessions/verificationWorkflow.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.js +19 -3
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.d.ts +67 -7
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -1
- package/dist/realtimeWidget/WidgetSessionService.js +201 -19
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -1
- package/dist/realtimeWidget/host-identity.d.ts +1 -1
- package/dist/realtimeWidget/host-identity.js +1 -1
- package/dist/realtimeWidget/widgetCore.d.ts +20 -1
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetCore.js +17 -1
- package/dist/realtimeWidget/widgetCore.js.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -1
- package/dist/realtimeWidget/widgetGuestElevation.js +2 -1
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +19 -1
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +63 -12
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +1 -0
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.d.ts +18 -4
- package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -1
- package/dist/resolvers/AgentRunStatusPublisher.js +32 -8
- package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -1
- package/dist/resolvers/AgentSessionResolver.d.ts +10 -4
- package/dist/resolvers/AgentSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/AgentSessionResolver.js +40 -17
- package/dist/resolvers/AgentSessionResolver.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +5 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.js +9 -7
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.d.ts +8 -4
- package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -1
- package/dist/resolvers/ConversationRunEventsResolver.js +6 -6
- package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts +24 -3
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +57 -8
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +13 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +50 -14
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +2 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +3 -2
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts +46 -3
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +211 -50
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -0
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +87 -11
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +292 -105
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +33 -7
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +117 -38
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +6 -3
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +49 -9
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +8 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +31 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.d.ts +6 -0
- package/dist/resolvers/PipelineProgressResolver.d.ts.map +1 -1
- package/dist/resolvers/PipelineProgressResolver.js +5 -3
- package/dist/resolvers/PipelineProgressResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +2 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +33 -6
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +2 -0
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -6
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RSUResolver.d.ts.map +1 -1
- package/dist/resolvers/RSUResolver.js +12 -0
- package/dist/resolvers/RSUResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +18 -10
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +92 -54
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.d.ts.map +1 -1
- package/dist/resolvers/RemoteBrowserActionResolver.js +11 -9
- package/dist/resolvers/RemoteBrowserActionResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +56 -8
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +241 -67
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +4 -4
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +3 -1
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts +12 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeStreamResolver.js +15 -2
- package/dist/resolvers/SearchKnowledgeStreamResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +2 -3
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +4 -0
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts +2 -1
- package/dist/resolvers/TaskGraphFrameResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskGraphFrameResolver.js +28 -1
- package/dist/resolvers/TaskGraphFrameResolver.js.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts +24 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +66 -13
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +34 -2
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +150 -27
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js +2 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/resolvers/agent-run-data-guard.d.ts +31 -0
- package/dist/resolvers/agent-run-data-guard.d.ts.map +1 -0
- package/dist/resolvers/agent-run-data-guard.js +72 -0
- package/dist/resolvers/agent-run-data-guard.js.map +1 -0
- package/dist/rest/EntityCRUDHandler.d.ts +6 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +19 -5
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +8 -5
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/RealtimeRecordingHandler.d.ts +49 -0
- package/dist/rest/RealtimeRecordingHandler.d.ts.map +1 -0
- package/dist/rest/RealtimeRecordingHandler.js +383 -0
- package/dist/rest/RealtimeRecordingHandler.js.map +1 -0
- package/dist/rest/ViewOperationsHandler.d.ts +9 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +43 -17
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +6 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +6 -0
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/serverExtensionReservedRoots.d.ts +12 -10
- package/dist/serverExtensionReservedRoots.d.ts.map +1 -1
- package/dist/serverExtensionReservedRoots.js +14 -17
- package/dist/serverExtensionReservedRoots.js.map +1 -1
- package/dist/serverRoleConfigUnits.d.ts +29 -0
- package/dist/serverRoleConfigUnits.d.ts.map +1 -0
- package/dist/serverRoleConfigUnits.js +33 -0
- package/dist/serverRoleConfigUnits.js.map +1 -0
- package/dist/services/MJStorageBlobStore.d.ts +8 -1
- package/dist/services/MJStorageBlobStore.d.ts.map +1 -1
- package/dist/services/MJStorageBlobStore.js +25 -19
- package/dist/services/MJStorageBlobStore.js.map +1 -1
- package/dist/services/StartTaskGraphDispatcher.js +2 -2
- package/dist/services/StartTaskGraphDispatcher.js.map +1 -1
- package/dist/services/TaskGraphActionRunner.d.ts +5 -3
- package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphActionRunner.js +27 -5
- package/dist/services/TaskGraphActionRunner.js.map +1 -1
- package/dist/services/TaskGraphAgentRunner.d.ts +14 -2
- package/dist/services/TaskGraphAgentRunner.d.ts.map +1 -1
- package/dist/services/TaskGraphAgentRunner.js +157 -9
- package/dist/services/TaskGraphAgentRunner.js.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.d.ts +59 -2
- package/dist/services/TaskGraphContinuationDeliverer.d.ts.map +1 -1
- package/dist/services/TaskGraphContinuationDeliverer.js +626 -16
- package/dist/services/TaskGraphContinuationDeliverer.js.map +1 -1
- package/dist/sharedCache.d.ts.map +1 -1
- package/dist/sharedCache.js +3 -0
- package/dist/sharedCache.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +99 -98
- package/src/__tests__/ActionResolver.fieldSecurity.test.ts +237 -0
- package/src/__tests__/AdhocQueryResolver.dialect.test.ts +137 -0
- package/src/__tests__/AgentRunStatusPublisher.test.ts +49 -0
- package/src/__tests__/AgentSessionResolver.ownership.test.ts +137 -0
- package/src/__tests__/CustomColumnPromoterOnePass.test.ts +15 -1
- package/src/__tests__/DatasetResolver.itemFilters.test.ts +201 -0
- package/src/__tests__/EntityCommunicationsResolver.actingUser.test.ts +151 -0
- package/src/__tests__/EntityRecordNameResolver.rowSecurity.test.ts +152 -0
- package/src/__tests__/EvolutionActsOnWhatItDetects.test.ts +113 -0
- package/src/__tests__/FetchEntityVectorsResolver.test.ts +369 -18
- package/src/__tests__/FileResolver.fieldSecurity.test.ts +82 -0
- package/src/__tests__/FileResolver.storageAccess.test.ts +562 -0
- package/src/__tests__/GetDataContextDataResolver.sqlItems.test.ts +416 -0
- package/src/__tests__/ListOperationsResolver.adhocScreen.test.ts +137 -0
- package/src/__tests__/LoginScreenEngine.test.ts +86 -0
- package/src/__tests__/MJStorageBlobStore.storageGate.test.ts +63 -0
- package/src/__tests__/PipelineProgressResolver.filter.test.ts +58 -0
- package/src/__tests__/QuerySystemUserResolver.fieldSecurity.test.ts +49 -0
- package/src/__tests__/RESTEndpointHandler.test.ts +25 -0
- package/src/__tests__/RealtimeClientSessionResolver.configTamper.test.ts +23 -3
- package/src/__tests__/RealtimeClientSessionResolver.test.ts +324 -46
- package/src/__tests__/RecordingAssemblyScheduler.test.ts +178 -0
- package/src/__tests__/RemoteBrowserActionResolver.ownership.test.ts +98 -0
- package/src/__tests__/ResolverBase.adhocReadScope.test.ts +133 -2
- package/src/__tests__/ResolverBase.balancedClause.test.ts +155 -0
- package/src/__tests__/ResolverBase.clauseUnscopedRead.test.ts +127 -0
- package/src/__tests__/ResolverBase.clientOldValues.test.ts +243 -7
- package/src/__tests__/ResolverBase.deleteReturnProjection.test.ts +52 -0
- package/src/__tests__/ResolverBase.entityMessageRelay.test.ts +176 -0
- package/src/__tests__/ResolverBase.filterEscaping.test.ts +5 -2
- package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +23 -1
- package/src/__tests__/ResolverBase.publishStatusUpdate.test.ts +68 -0
- package/src/__tests__/ResolverBase.userViewCacheAndDedup.test.ts +130 -0
- package/src/__tests__/RunAIAgentResolver.branch.test.ts +69 -13
- package/src/__tests__/RunAIAgentResolver.conversationWriter.test.ts +210 -0
- package/src/__tests__/RunAIAgentResolver.fireAndForgetCompletion.test.ts +209 -0
- package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +20 -28
- package/src/__tests__/RunAIAgentResolver.runDataGuard.test.ts +264 -0
- package/src/__tests__/RunAIAgentResolver.serverReply.test.ts +358 -0
- package/src/__tests__/RunAIAgentResolver.stoppedPredecessor.test.ts +4 -1
- package/src/__tests__/RunTemplateResolver.test.ts +35 -2
- package/src/__tests__/RunViewResolver.cacheCheckForwarding.test.ts +11 -0
- package/src/__tests__/RunViewResolver.cacheCheckScreen.test.ts +165 -0
- package/src/__tests__/SearchKnowledgeStreamResolver.test.ts +161 -0
- package/src/__tests__/SessionJanitor.test.ts +365 -10
- package/src/__tests__/SessionManager.test.ts +122 -0
- package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +25 -0
- package/src/__tests__/TaskGraphAgentRunner.test.ts +266 -0
- package/src/__tests__/TaskGraphContinuationDeliverer.test.ts +468 -23
- package/src/__tests__/TaskGraphFrameResolver.test.ts +22 -0
- package/src/__tests__/TestQuerySQLResolver.test.ts +274 -0
- package/src/__tests__/TransactionGroupResolver.fieldSecurity.test.ts +199 -0
- package/src/__tests__/TransactionGroupResolver.refusals.test.ts +98 -1
- package/src/__tests__/UpdateMJDataContextItem.oldValues.test.ts +220 -0
- package/src/__tests__/UserViewResolver.updateWhereClause.test.ts +15 -0
- package/src/__tests__/ViewOperationsHandler.clauseScreen.test.ts +157 -0
- package/src/__tests__/ViewOperationsHandler.test.ts +7 -2
- package/src/__tests__/WidgetSessionService.test.ts +583 -4
- package/src/__tests__/agent-run-data-guard.test.ts +130 -0
- package/src/__tests__/apiRootBoot.test.ts +202 -0
- package/src/__tests__/buildMagicLinkSessionUser.test.ts +96 -0
- package/src/__tests__/cacheBroadcastProviders.test.ts +403 -0
- package/src/__tests__/cacheInvalidationRecordData.test.ts +109 -0
- package/src/__tests__/conversationRunEvents.test.ts +56 -14
- package/src/__tests__/fieldSecurityFixtures.ts +91 -0
- package/src/__tests__/globalCors.test.ts +217 -0
- package/src/__tests__/localDevAuth.test.ts +196 -0
- package/src/__tests__/multiTenancy.postgresRewrite.test.ts +181 -0
- package/src/__tests__/multiTenancy.security.test.ts +51 -0
- package/src/__tests__/multiTenancy.test.ts +1 -1
- package/src/__tests__/orphanedConversationDetailReconciler.test.ts +10 -0
- package/src/__tests__/ownedRealtimeSession.test.ts +39 -0
- package/src/__tests__/pushStatusResolver.filter.test.ts +64 -1
- package/src/__tests__/realtimeRecordingHandler.test.ts +669 -0
- package/src/__tests__/realtimeSessions/RealtimeSessionEventsResolver.test.ts +29 -1
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.anonymous.test.ts +31 -0
- package/src/__tests__/realtimeSessions/ownedRealtimeSession.namedScoped.test.ts +37 -0
- package/src/__tests__/realtimeSessions/sessionAccess.test.ts +146 -7
- package/src/__tests__/realtimeSessions/sessionConfigStamps.test.ts +1 -1
- package/src/__tests__/realtimeSessions/verificationWorkflow.test.ts +25 -4
- package/src/__tests__/resolverBase.cloudEventChangedFields.test.ts +157 -0
- package/src/__tests__/resolverBase.fieldSecurityTransport.test.ts +222 -0
- package/src/__tests__/resolverBase.fls.test.ts +146 -97
- package/src/__tests__/restTransport.fieldSecurity.test.ts +148 -0
- package/src/__tests__/runParamsTimeZone.test.ts +179 -0
- package/src/__tests__/runStreams.fieldSecurity.test.ts +100 -0
- package/src/__tests__/runViewProperties.test.ts +114 -0
- package/src/__tests__/scalabilityBenchmarks.test.ts +765 -0
- package/src/__tests__/scalabilityDatabaseIntegration.test.ts +207 -0
- package/src/__tests__/serverExtensionHostOverlay.test.ts +66 -0
- package/src/__tests__/serverExtensionReservedRoots.test.ts +11 -98
- package/src/__tests__/sessionUserClone.test.ts +5 -1
- package/src/__tests__/sharedCache.test.ts +5 -3
- package/src/__tests__/startupSweepOrder.test.ts +40 -0
- package/src/__tests__/unifiedAuth.test.ts +28 -0
- package/src/__tests__/widgetGuestElevation.test.ts +2 -0
- package/src/agentSessions/SessionJanitor.ts +369 -93
- package/src/agentSessions/SessionManager.ts +45 -10
- package/src/apiRoot.ts +78 -0
- package/src/auth/AuthProviderCatalogRouter.ts +29 -0
- package/src/auth/LoginScreenEngine.ts +50 -0
- package/src/auth/index.ts +57 -12
- package/src/auth/initializeProviders.ts +43 -1
- package/src/auth/localDevAuth.ts +528 -0
- package/src/auth/magicLink/MagicLinkKeys.ts +2 -2
- package/src/auth/magicLink/MagicLinkRouter.ts +3 -2
- package/src/auth/magicLink/types.ts +10 -0
- package/src/auth/sessionUserClone.ts +3 -2
- package/src/broadcast/BaseCacheInvalidationBroadcastProvider.ts +674 -0
- package/src/broadcast/index.ts +5 -0
- package/src/broadcast/providers/AWSIoTCoreBroadcastProvider.ts +180 -0
- package/src/broadcast/providers/AzureWebPubSubBroadcastProvider.ts +182 -0
- package/src/broadcast/providers/GCPPubSubBroadcastProvider.ts +169 -0
- package/src/broadcast/providers/SequencedRoleBucketBroadcastProvider.ts +57 -0
- package/src/cache.ts +2 -1
- package/src/config.ts +115 -5
- package/src/context.ts +32 -8
- package/src/coreRoutes.ts +181 -0
- package/src/generated/graphql-schemas/__mj.ts +3684 -284
- package/src/generic/CacheInvalidationResolver.ts +172 -60
- package/src/generic/OrphanedConversationDetailReconciler.ts +4 -2
- package/src/generic/PubSubManager.ts +54 -1
- package/src/generic/PushStatusResolver.ts +109 -12
- package/src/generic/ResolverBase.ts +540 -431
- package/src/generic/RunViewResolver.ts +97 -15
- package/src/generic/actionOutputFieldSecurity.ts +75 -0
- package/src/generic/fieldSecurityTransport.ts +62 -0
- package/src/globalCors.ts +71 -0
- package/src/index.ts +239 -238
- package/src/integration/CustomColumnPromoter.ts +6 -1
- package/src/logging/variablesLoggingMiddleware.ts +59 -3
- package/src/multiTenancy/index.ts +24 -2
- package/src/realtimeSessions/README.md +7 -3
- package/src/realtimeSessions/RealtimeSessionVerificationService.ts +2 -1
- package/src/realtimeSessions/RealtimeVerifyRouter.ts +2 -1
- package/src/realtimeSessions/RecordingAssemblyScheduler.ts +234 -0
- package/src/realtimeSessions/SessionVerificationPortsBase.ts +1 -0
- package/src/realtimeSessions/index.ts +2 -0
- package/src/realtimeSessions/ownedRealtimeSession.ts +69 -0
- package/src/realtimeSessions/sessionAccess.ts +183 -23
- package/src/realtimeSessions/sessionConfigStamps.ts +1 -1
- package/src/realtimeSessions/subscriptionAuthorization.ts +4 -2
- package/src/realtimeSessions/verificationWorkflow.ts +3 -1
- package/src/realtimeWidget/WidgetRouter.ts +22 -4
- package/src/realtimeWidget/WidgetSessionService.ts +252 -17
- package/src/realtimeWidget/host-identity.ts +1 -1
- package/src/realtimeWidget/widgetCore.ts +30 -1
- package/src/realtimeWidget/widgetGuestElevation.ts +2 -1
- package/src/resolvers/ActionResolver.ts +78 -15
- package/src/resolvers/AdhocQueryResolver.ts +1 -0
- package/src/resolvers/AgentRunStatusPublisher.ts +36 -8
- package/src/resolvers/AgentSessionResolver.ts +46 -18
- package/src/resolvers/ArtifactFileResolver.ts +9 -8
- package/src/resolvers/ConversationRunEventsResolver.ts +15 -11
- package/src/resolvers/DatasetResolver.ts +70 -10
- package/src/resolvers/EntityCommunicationsResolver.ts +56 -15
- package/src/resolvers/EntityRecordNameResolver.ts +3 -2
- package/src/resolvers/FetchEntityVectorsResolver.ts +248 -55
- package/src/resolvers/FileCategoryResolver.ts +1 -0
- package/src/resolvers/FileResolver.ts +334 -109
- package/src/resolvers/GetDataContextDataResolver.ts +139 -41
- package/src/resolvers/GetDataResolver.ts +6 -3
- package/src/resolvers/IntegrationDiscoveryResolver.ts +53 -9
- package/src/resolvers/MergeRecordsResolver.ts +23 -2
- package/src/resolvers/PipelineProgressResolver.ts +12 -3
- package/src/resolvers/QueryResolver.ts +31 -6
- package/src/resolvers/QuerySystemUserResolver.ts +15 -6
- package/src/resolvers/RSUResolver.ts +12 -0
- package/src/resolvers/RealtimeClientSessionResolver.ts +114 -66
- package/src/resolvers/RemoteBrowserActionResolver.ts +15 -10
- package/src/resolvers/RunAIAgentResolver.ts +296 -85
- package/src/resolvers/RunTemplateResolver.ts +6 -4
- package/src/resolvers/RunTestResolver.ts +3 -1
- package/src/resolvers/SearchKnowledgeStreamResolver.ts +19 -3
- package/src/resolvers/SqlLoggingConfigResolver.ts +2 -4
- package/src/resolvers/SyncDataResolver.ts +4 -0
- package/src/resolvers/TaskGraphFrameResolver.ts +33 -1
- package/src/resolvers/TestQuerySQLResolver.ts +69 -13
- package/src/resolvers/TransactionGroupResolver.ts +185 -30
- package/src/resolvers/UserViewResolver.ts +2 -1
- package/src/resolvers/__tests__/GetDataResolver.validation.test.ts +95 -0
- package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +43 -0
- package/src/resolvers/agent-run-data-guard.ts +89 -0
- package/src/rest/EntityCRUDHandler.ts +22 -5
- package/src/rest/RESTEndpointHandler.ts +8 -5
- package/src/rest/RealtimeRecordingHandler.ts +467 -0
- package/src/rest/ViewOperationsHandler.ts +52 -18
- package/src/rest/setupRESTEndpoints.ts +6 -0
- package/src/serverExtensionReservedRoots.ts +14 -17
- package/src/serverRoleConfigUnits.ts +48 -0
- package/src/services/MJStorageBlobStore.ts +31 -30
- package/src/services/StartTaskGraphDispatcher.ts +2 -2
- package/src/services/TaskGraphActionRunner.ts +25 -5
- package/src/services/TaskGraphAgentRunner.ts +188 -11
- package/src/services/TaskGraphContinuationDeliverer.ts +738 -16
- package/src/sharedCache.ts +3 -0
- package/src/types.ts +23 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for SearchKnowledgeStreamResolver's wire mapping: a `provider` event is progress only.
|
|
5
|
+
*
|
|
6
|
+
* A provider's hits arrive before the engine's permission pass — rows the caller's row filters would drop,
|
|
7
|
+
* external-index hits not yet verified against the entity they name, content whose origin the caller may not
|
|
8
|
+
* read. The engine no longer puts them on its `provider` events, and the resolver does not forward them either,
|
|
9
|
+
* whatever the event holds: the client gets ProviderName, DurationMs and ResultCount, and results only in
|
|
10
|
+
* `fused`/`final`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { streamEvents, streamCalls, resolveEffectivePermission, logForbiddenSearch } = vi.hoisted(() => ({
|
|
14
|
+
streamEvents: { current: [] as unknown[] },
|
|
15
|
+
streamCalls: { count: 0 },
|
|
16
|
+
resolveEffectivePermission: vi.fn(),
|
|
17
|
+
logForbiddenSearch: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('@memberjunction/search-engine', () => ({
|
|
21
|
+
SearchEngine: {
|
|
22
|
+
Instance: {
|
|
23
|
+
streamSearch: async function* () {
|
|
24
|
+
streamCalls.count++;
|
|
25
|
+
for (const ev of streamEvents.current) yield ev;
|
|
26
|
+
},
|
|
27
|
+
LogForbiddenSearch: logForbiddenSearch,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
GetSearchScopePermissionResolver: () => ({ ResolveEffectivePermission: resolveEffectivePermission }),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
34
|
+
const actual = await importOriginal<Record<string, unknown>>();
|
|
35
|
+
return { ...actual, LogError: vi.fn(), LogStatus: vi.fn() };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Mock type-graphql to avoid the reflect-metadata dependency (the SearchKnowledgeResolver tests do the same).
|
|
39
|
+
vi.mock('type-graphql', () => {
|
|
40
|
+
const noop = () => () => undefined;
|
|
41
|
+
const passthrough = () => (target: unknown) => target;
|
|
42
|
+
return {
|
|
43
|
+
Resolver: passthrough, Mutation: noop, Query: noop, Subscription: noop, Arg: noop, Args: noop, Ctx: noop,
|
|
44
|
+
ObjectType: passthrough, InputType: passthrough, ArgsType: passthrough, Field: noop,
|
|
45
|
+
Float: Number, Int: Number, ID: String, Authorized: noop, PubSub: noop, Root: noop,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
import type { PubSubEngine } from 'type-graphql';
|
|
50
|
+
import type { UserInfo } from '@memberjunction/core';
|
|
51
|
+
import { SearchKnowledgeStreamResolver, SearchStreamNotification } from '../resolvers/SearchKnowledgeStreamResolver.js';
|
|
52
|
+
import type { UserPayload } from '../types.js';
|
|
53
|
+
|
|
54
|
+
class TestStreamResolver extends SearchKnowledgeStreamResolver {
|
|
55
|
+
protected override GetUserFromPayload(_userPayload: UserPayload): UserInfo | undefined {
|
|
56
|
+
return { ID: 'test-user-id', Email: 'test@test.com', Name: 'Test User' } as UserInfo;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const hit = (recordID: string) => ({
|
|
61
|
+
ID: `r-${recordID}`, EntityName: 'Customers', RecordID: recordID, SourceType: 'fulltext', ResultType: 'entity-record',
|
|
62
|
+
Title: `title of ${recordID}`, Snippet: `snippet of ${recordID}`, Score: 0.5, ScoreBreakdown: {}, Tags: [],
|
|
63
|
+
MatchedAt: new Date(), RawMetadata: `{"secret":"${recordID}"}`, ProviderId: 'prov-azure',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/** Run a stream to completion and return what the resolver published, in order. */
|
|
67
|
+
async function publishedFor(events: unknown[]): Promise<SearchStreamNotification[]> {
|
|
68
|
+
streamEvents.current = events;
|
|
69
|
+
const published: SearchStreamNotification[] = [];
|
|
70
|
+
const pubSub = { publish: vi.fn(async (_topic: string, payload: SearchStreamNotification) => { published.push(payload); }) };
|
|
71
|
+
const start = await new TestStreamResolver().StreamScopedSearch(
|
|
72
|
+
'budget', 20, undefined, undefined, undefined, undefined,
|
|
73
|
+
pubSub as unknown as PubSubEngine, { userPayload: {} } as never,
|
|
74
|
+
);
|
|
75
|
+
expect(start.Success).toBe(true);
|
|
76
|
+
await vi.waitFor(() => expect(published.some(p => p.Phase === 'final' || p.Phase === 'error')).toBe(true),
|
|
77
|
+
{ timeout: 500, interval: 5 });
|
|
78
|
+
return published;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe('SearchKnowledgeStreamResolver — provider events are progress only', () => {
|
|
82
|
+
beforeEach(() => { vi.clearAllMocks(); });
|
|
83
|
+
|
|
84
|
+
it('publishes the count and no result, even when a provider event carries hits', async () => {
|
|
85
|
+
// An engine that (wrongly) put hits on a provider event: the resolver still must not forward them.
|
|
86
|
+
const published = await publishedFor([
|
|
87
|
+
{ phase: 'provider', providerName: 'FullText', results: [hit('row-filtered')], resultCount: 1, durationMs: 7 },
|
|
88
|
+
{ phase: 'fused', results: [] },
|
|
89
|
+
{ phase: 'final', results: [], sourceCounts: { Vector: 0, FullText: 1, Entity: 0, Storage: 0 }, elapsedMs: 9 },
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
const progress = published.find(p => p.Phase === 'provider');
|
|
93
|
+
expect(progress).toMatchObject({ Phase: 'provider', ProviderName: 'FullText', DurationMs: 7, ResultCount: 1, Results: [] });
|
|
94
|
+
expect(JSON.stringify(progress)).not.toContain('row-filtered');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('carries results on final, mapped as the synchronous resolver maps them', async () => {
|
|
98
|
+
const published = await publishedFor([
|
|
99
|
+
{ phase: 'provider', providerName: 'FullText', results: [], resultCount: 1, durationMs: 7 },
|
|
100
|
+
{ phase: 'fused', results: [hit('readable')] },
|
|
101
|
+
{ phase: 'final', results: [hit('readable')], sourceCounts: { Vector: 0, FullText: 1, Entity: 0, Storage: 0 }, elapsedMs: 9 },
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
const final = published.find(p => p.Phase === 'final');
|
|
105
|
+
expect(final?.Results?.map(r => r.RecordID)).toEqual(['readable']);
|
|
106
|
+
expect(final?.ResultCount).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('SearchKnowledgeStreamResolver — the scope permission gate runs before the stream starts', () => {
|
|
111
|
+
const verdict = (Allowed: boolean, Level: string) => ({ Allowed, Level, Source: 'NoGrant', Reason: 'judged in test' });
|
|
112
|
+
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
streamEvents.current = [{ phase: 'final', results: [], sourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 }, elapsedMs: 1 }];
|
|
115
|
+
streamCalls.count = 0;
|
|
116
|
+
resolveEffectivePermission.mockReset();
|
|
117
|
+
logForbiddenSearch.mockReset();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
async function start(scopeIDs: string[]) {
|
|
121
|
+
const pubSub = { publish: vi.fn(async () => undefined) };
|
|
122
|
+
const result = await new TestStreamResolver().StreamScopedSearch(
|
|
123
|
+
'budget', 20, undefined, scopeIDs, undefined, undefined,
|
|
124
|
+
pubSub as unknown as PubSubEngine, { userPayload: {} } as never,
|
|
125
|
+
);
|
|
126
|
+
return { result, pubSub };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
it('refuses a denied scope, logs it, and never starts the stream or publishes', async () => {
|
|
130
|
+
resolveEffectivePermission.mockResolvedValue(verdict(false, 'None'));
|
|
131
|
+
|
|
132
|
+
const { result, pubSub } = await start(['scope-a']);
|
|
133
|
+
|
|
134
|
+
expect(result.Success).toBe(false);
|
|
135
|
+
expect(result.ErrorMessage).toMatch(/^Forbidden: refused for this user on scope 'scope-a'/);
|
|
136
|
+
expect(logForbiddenSearch).toHaveBeenCalledTimes(1);
|
|
137
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
138
|
+
expect(streamCalls.count).toBe(0);
|
|
139
|
+
expect(pubSub.publish).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('refuses a Read-level grant: Read is metadata only, not search', async () => {
|
|
143
|
+
resolveEffectivePermission.mockResolvedValue(verdict(true, 'Read'));
|
|
144
|
+
|
|
145
|
+
const { result } = await start(['scope-a']);
|
|
146
|
+
|
|
147
|
+
expect(result.Success).toBe(false);
|
|
148
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
149
|
+
expect(streamCalls.count).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('judges every requested scope and starts the stream when all may be searched', async () => {
|
|
153
|
+
resolveEffectivePermission.mockResolvedValue(verdict(true, 'Search'));
|
|
154
|
+
|
|
155
|
+
const { result } = await start(['scope-a', 'scope-b']);
|
|
156
|
+
|
|
157
|
+
expect(result.Success).toBe(true);
|
|
158
|
+
expect(resolveEffectivePermission).toHaveBeenCalledTimes(2);
|
|
159
|
+
await vi.waitFor(() => expect(streamCalls.count).toBe(1), { timeout: 500, interval: 5 });
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -6,12 +6,20 @@ vi.mock('@memberjunction/ai-engine-base', () => ({
|
|
|
6
6
|
}));
|
|
7
7
|
|
|
8
8
|
// Capture the ExtraFilter passed to each sweep page and return controllable rows.
|
|
9
|
-
const { runViewMock, logErrorMock, logStatusMock, recoverMock, resolveAccountMock } = vi.hoisted(() => ({
|
|
9
|
+
const { runViewMock, logErrorMock, logStatusMock, recoverMock, resolveAccountMock, assembleMock, ledgerMock } = vi.hoisted(() => ({
|
|
10
10
|
runViewMock: vi.fn(),
|
|
11
11
|
logErrorMock: vi.fn(),
|
|
12
12
|
logStatusMock: vi.fn(),
|
|
13
13
|
recoverMock: vi.fn(),
|
|
14
14
|
resolveAccountMock: vi.fn(),
|
|
15
|
+
assembleMock: vi.fn(),
|
|
16
|
+
/** The one fake shard ledger every `new SqlRealtimeRecordingLedger(...)` returns. */
|
|
17
|
+
ledgerMock: {
|
|
18
|
+
ClaimAssembly: vi.fn(),
|
|
19
|
+
PurgeClosedBefore: vi.fn(),
|
|
20
|
+
LoadShardMetadata: vi.fn(),
|
|
21
|
+
StampTerminal: vi.fn(),
|
|
22
|
+
},
|
|
15
23
|
}));
|
|
16
24
|
vi.mock('@memberjunction/core', async (importOriginal) => {
|
|
17
25
|
const actual = await importOriginal<typeof import('@memberjunction/core')>();
|
|
@@ -34,6 +42,8 @@ vi.mock('@memberjunction/ai-agents', async (importOriginal) => {
|
|
|
34
42
|
...actual,
|
|
35
43
|
RecoverRealtimeRecordingFromSegments: recoverMock,
|
|
36
44
|
ResolveRecordingStorageAccountID: resolveAccountMock,
|
|
45
|
+
AssembleRealtimeRecordingFromLedger: assembleMock,
|
|
46
|
+
SqlRealtimeRecordingLedger: vi.fn(function FakeLedger() { return ledgerMock; }),
|
|
37
47
|
};
|
|
38
48
|
});
|
|
39
49
|
|
|
@@ -44,7 +54,7 @@ import {
|
|
|
44
54
|
HostInstanceIdentity,
|
|
45
55
|
SetHostInstancePort,
|
|
46
56
|
} from '../agentSessions/HostInstance.js';
|
|
47
|
-
import type { UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
57
|
+
import type { UserInfo, IMetadataProvider, DatabaseProviderBase } from '@memberjunction/core';
|
|
48
58
|
import type { MJAIAgentSessionEntity } from '@memberjunction/core-entities';
|
|
49
59
|
|
|
50
60
|
/** The port this test process "serves" on, set the way `Serve` sets it, before anything reads the identity. */
|
|
@@ -53,6 +63,16 @@ const OUR_PORT = 4000;
|
|
|
53
63
|
const OTHER_PORT = 4100;
|
|
54
64
|
SetHostInstancePort(OUR_PORT);
|
|
55
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The janitor builds the shard-ledger view name from the provider's core schema and dialect quoting;
|
|
68
|
+
* fakes carry SQL Server's so filter assertions read naturally.
|
|
69
|
+
*/
|
|
70
|
+
const LEDGER_SCHEMA_PROVIDER_FIELDS = {
|
|
71
|
+
MJCoreSchemaName: '__mj',
|
|
72
|
+
QuoteSchemaAndView: (schema: string, view: string) => `[${schema}].[${view}]`,
|
|
73
|
+
};
|
|
74
|
+
const LEDGER_VIEW = '[__mj].[vwAIAgentSessionRecordingShards]';
|
|
75
|
+
|
|
56
76
|
interface FakeSession {
|
|
57
77
|
ID: string;
|
|
58
78
|
Status: 'Active' | 'Idle' | 'Closed';
|
|
@@ -73,7 +93,7 @@ function makeUser(): UserInfo {
|
|
|
73
93
|
* inside CloseSession uses runViewMock, which we default to an empty channel list.
|
|
74
94
|
*/
|
|
75
95
|
function makeProvider(store: Map<string, 'Active' | 'Idle' | 'Closed'>): {
|
|
76
|
-
provider:
|
|
96
|
+
provider: DatabaseProviderBase;
|
|
77
97
|
closedIds: string[];
|
|
78
98
|
closedReasons: Map<string, string | null>;
|
|
79
99
|
} {
|
|
@@ -105,7 +125,8 @@ function makeProvider(store: Map<string, 'Active' | 'Idle' | 'Closed'>): {
|
|
|
105
125
|
};
|
|
106
126
|
return entity as unknown as Awaited<ReturnType<IMetadataProvider['GetEntityObject']>>;
|
|
107
127
|
}),
|
|
108
|
-
|
|
128
|
+
...LEDGER_SCHEMA_PROVIDER_FIELDS,
|
|
129
|
+
} as unknown as DatabaseProviderBase;
|
|
109
130
|
return { provider, closedIds, closedReasons };
|
|
110
131
|
}
|
|
111
132
|
|
|
@@ -129,6 +150,11 @@ beforeEach(() => {
|
|
|
129
150
|
logStatusMock.mockReset();
|
|
130
151
|
recoverMock.mockReset();
|
|
131
152
|
resolveAccountMock.mockReset();
|
|
153
|
+
assembleMock.mockReset();
|
|
154
|
+
for (const method of Object.values(ledgerMock)) method.mockReset();
|
|
155
|
+
// Quiet ledger by default: nothing to purge, every claim lost.
|
|
156
|
+
ledgerMock.PurgeClosedBefore.mockResolvedValue(0);
|
|
157
|
+
ledgerMock.ClaimAssembly.mockResolvedValue(null);
|
|
132
158
|
sessionPages = [];
|
|
133
159
|
runViewMock.mockImplementation(async (params: { EntityName: string }) => {
|
|
134
160
|
if (params.EntityName === 'MJ: AI Agent Session Channels') {
|
|
@@ -379,7 +405,7 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
379
405
|
let nextSessionNumber = 0;
|
|
380
406
|
|
|
381
407
|
interface RecoveryHarness {
|
|
382
|
-
provider:
|
|
408
|
+
provider: DatabaseProviderBase;
|
|
383
409
|
/** Session id -> RecordingFileID the reload will report (default null). */
|
|
384
410
|
fileIds: Map<string, string | null>;
|
|
385
411
|
agentLoads: string[];
|
|
@@ -414,7 +440,8 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
414
440
|
};
|
|
415
441
|
return entity;
|
|
416
442
|
}),
|
|
417
|
-
|
|
443
|
+
...LEDGER_SCHEMA_PROVIDER_FIELDS,
|
|
444
|
+
} as unknown as DatabaseProviderBase;
|
|
418
445
|
return { provider, fileIds, agentLoads, failingAgents };
|
|
419
446
|
}
|
|
420
447
|
|
|
@@ -425,7 +452,7 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
425
452
|
}
|
|
426
453
|
|
|
427
454
|
/** Runs one sweep whose candidate page holds exactly `rows`. */
|
|
428
|
-
async function sweepWith(provider:
|
|
455
|
+
async function sweepWith(provider: DatabaseProviderBase, rows: unknown[]): Promise<number> {
|
|
429
456
|
sessionPages = [{ Success: true, Results: rows }];
|
|
430
457
|
return SessionJanitor.Instance.RunRecordingRecoverySweep(provider, makeUser());
|
|
431
458
|
}
|
|
@@ -455,6 +482,14 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
455
482
|
expect(filter).toContain(`ClosedAt >= '${new Date(NOW.getTime() - 168 * HOUR).toISOString()}'`);
|
|
456
483
|
});
|
|
457
484
|
|
|
485
|
+
it('leaves sessions on the shard ledger to the ledger sweep', async () => {
|
|
486
|
+
const { provider } = makeRecoveryProvider();
|
|
487
|
+
await sweepWith(provider, []);
|
|
488
|
+
|
|
489
|
+
const filter = recoveryFilter();
|
|
490
|
+
expect(filter).toContain(`RecordingStatus IS NULL AND ID NOT IN (SELECT AgentSessionID FROM ${LEDGER_VIEW})`);
|
|
491
|
+
});
|
|
492
|
+
|
|
458
493
|
it('honours Configure() overrides for grace and lookback', async () => {
|
|
459
494
|
SessionJanitor.Instance.Configure({ recordingRecoveryGraceMinutes: 30, recordingRecoveryLookbackHours: 24 });
|
|
460
495
|
const { provider } = makeRecoveryProvider();
|
|
@@ -878,7 +913,7 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
878
913
|
}
|
|
879
914
|
});
|
|
880
915
|
|
|
881
|
-
it('runs recovery after the close sweeps', async () => {
|
|
916
|
+
it('runs recovery after the close sweeps: ledger assembly first, then legacy segment recovery', async () => {
|
|
882
917
|
const { provider } = makeRecoveryProvider();
|
|
883
918
|
await SessionJanitor.Instance.Start(provider, makeUser(), 60_000);
|
|
884
919
|
runViewMock.mockClear();
|
|
@@ -886,10 +921,69 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
886
921
|
await vi.advanceTimersByTimeAsync(60_000);
|
|
887
922
|
|
|
888
923
|
const filters = sessionSweepCalls().map((c) => c[0].ExtraFilter);
|
|
889
|
-
expect(filters).toHaveLength(
|
|
924
|
+
expect(filters).toHaveLength(4);
|
|
890
925
|
expect(filters[0]).toContain('LastActiveAt <');
|
|
891
926
|
expect(filters[1]).toContain('maxSessionDeadlineIso');
|
|
892
|
-
expect(filters[2]).toContain('
|
|
927
|
+
expect(filters[2]).toContain("RecordingStatus IN ('Pending','Assembling')");
|
|
928
|
+
expect(filters[3]).toContain('RecordingStartedAt');
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
it('purges ledger shards past the lookback window once per pass', async () => {
|
|
932
|
+
const { provider } = makeRecoveryProvider();
|
|
933
|
+
await SessionJanitor.Instance.Start(provider, makeUser(), 60_000);
|
|
934
|
+
ledgerMock.PurgeClosedBefore.mockResolvedValueOnce(42);
|
|
935
|
+
|
|
936
|
+
await vi.advanceTimersByTimeAsync(60_000);
|
|
937
|
+
|
|
938
|
+
expect(ledgerMock.PurgeClosedBefore).toHaveBeenCalledTimes(1);
|
|
939
|
+
const [cutoff, maxRows] = ledgerMock.PurgeClosedBefore.mock.calls[0] as [Date, number];
|
|
940
|
+
expect(cutoff.toISOString()).toBe(new Date(Date.now() - 168 * HOUR).toISOString()); // now = the tick
|
|
941
|
+
expect(maxRows).toBe(500);
|
|
942
|
+
expect(logStatusMock).toHaveBeenCalledWith(expect.stringMatching(/purged 42 recording shard/i));
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
it('a Stop() during the pass ends it before the next step', async () => {
|
|
946
|
+
const { provider } = makeRecoveryProvider();
|
|
947
|
+
await SessionJanitor.Instance.Start(provider, makeUser(), 60_000);
|
|
948
|
+
runViewMock.mockClear();
|
|
949
|
+
let releasePurge: () => void = () => undefined;
|
|
950
|
+
ledgerMock.PurgeClosedBefore.mockImplementationOnce(
|
|
951
|
+
() => new Promise<number>((resolve) => { releasePurge = () => resolve(0); }),
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
await vi.advanceTimersByTimeAsync(60_000); // the pass parks in the purge
|
|
955
|
+
SessionJanitor.Instance.Stop();
|
|
956
|
+
releasePurge();
|
|
957
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
958
|
+
|
|
959
|
+
expect(sessionSweepCalls().filter((c) => c[0].ExtraFilter.includes('RecordingStatus'))).toHaveLength(0);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
it('a failed purge is logged with context and the sweeps still run', async () => {
|
|
963
|
+
const { provider } = makeRecoveryProvider();
|
|
964
|
+
await SessionJanitor.Instance.Start(provider, makeUser(), 60_000);
|
|
965
|
+
runViewMock.mockClear();
|
|
966
|
+
ledgerMock.PurgeClosedBefore.mockRejectedValueOnce(new Error('purge deadlock'));
|
|
967
|
+
|
|
968
|
+
await vi.advanceTimersByTimeAsync(60_000);
|
|
969
|
+
|
|
970
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringMatching(/purge.*purge deadlock/i));
|
|
971
|
+
expect(sessionSweepCalls().some((c) => c[0].ExtraFilter.includes('RecordingStartedAt'))).toBe(true);
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
it('a failed ledger sweep is logged and legacy recovery still runs', async () => {
|
|
975
|
+
const { provider } = makeRecoveryProvider();
|
|
976
|
+
await SessionJanitor.Instance.Start(provider, makeUser(), 60_000);
|
|
977
|
+
runViewMock.mockClear();
|
|
978
|
+
runViewMock.mockImplementation(async (params: { ExtraFilter?: string }) => {
|
|
979
|
+
if (params.ExtraFilter?.includes("RecordingStatus IN ('Pending','Assembling')")) throw new Error('ledger query blew up');
|
|
980
|
+
return { Success: true, Results: [] };
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
await vi.advanceTimersByTimeAsync(60_000);
|
|
984
|
+
|
|
985
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringMatching(/ledger.*ledger query blew up/i));
|
|
986
|
+
expect(sessionSweepCalls().some((c) => c[0].ExtraFilter.includes('RecordingStartedAt'))).toBe(true);
|
|
893
987
|
});
|
|
894
988
|
|
|
895
989
|
it('a slow recovery does not block the close sweeps on later ticks and is not started twice', async () => {
|
|
@@ -1018,6 +1112,267 @@ describe('SessionJanitor.RunRecordingRecoverySweep', () => {
|
|
|
1018
1112
|
});
|
|
1019
1113
|
});
|
|
1020
1114
|
|
|
1115
|
+
describe('SessionJanitor.RunRecordingLedgerSweep', () => {
|
|
1116
|
+
const NOW = new Date('2026-10-12T12:00:00.000Z');
|
|
1117
|
+
const MINUTE = 60_000;
|
|
1118
|
+
const HOUR = 60 * MINUTE;
|
|
1119
|
+
const STALE_CUTOFF = new Date(NOW.getTime() - 10 * MINUTE); // recordingRecoveryAttemptTimeoutMinutes
|
|
1120
|
+
let nextSessionNumber = 0;
|
|
1121
|
+
|
|
1122
|
+
type LedgerCandidate = {
|
|
1123
|
+
ID: string;
|
|
1124
|
+
AgentID: string;
|
|
1125
|
+
RecordingStatus: 'Pending' | 'Assembling' | null;
|
|
1126
|
+
RecordingExpectedShards: number | null;
|
|
1127
|
+
RecordingCapturedSamples: number | null;
|
|
1128
|
+
__mj_UpdatedAt: Date;
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
/** Session ids are unique per test: the janitor is a singleton whose backoff outlives a test. */
|
|
1132
|
+
function ledgerCandidate(overrides: Partial<LedgerCandidate> = {}): LedgerCandidate {
|
|
1133
|
+
return {
|
|
1134
|
+
ID: `ledger-session-${nextSessionNumber++}`,
|
|
1135
|
+
AgentID: 'agent-1',
|
|
1136
|
+
RecordingStatus: 'Pending',
|
|
1137
|
+
RecordingExpectedShards: null,
|
|
1138
|
+
RecordingCapturedSamples: null,
|
|
1139
|
+
__mj_UpdatedAt: new Date(NOW.getTime() - HOUR),
|
|
1140
|
+
...overrides,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function ledgerProvider(): DatabaseProviderBase {
|
|
1145
|
+
return { GetEntityObject: vi.fn(), ...LEDGER_SCHEMA_PROVIDER_FIELDS } as unknown as DatabaseProviderBase;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/** Runs one ledger sweep whose candidate page holds exactly `rows`. */
|
|
1149
|
+
async function ledgerSweepWith(rows: LedgerCandidate[], provider = ledgerProvider()): Promise<number> {
|
|
1150
|
+
sessionPages = [{ Success: true, Results: rows }];
|
|
1151
|
+
return SessionJanitor.Instance.RunRecordingLedgerSweep(provider, makeUser());
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function ledgerFilter(): string {
|
|
1155
|
+
const call = sessionSweepCalls().find((c) => c[0].ExtraFilter.includes('RecordingStatus'));
|
|
1156
|
+
if (!call) throw new Error('ledger sweep never queried sessions');
|
|
1157
|
+
return call[0].ExtraFilter;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function claimedIDs(): string[] {
|
|
1161
|
+
return ledgerMock.ClaimAssembly.mock.calls.map((c) => c[0] as string);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
beforeEach(() => {
|
|
1165
|
+
vi.useFakeTimers();
|
|
1166
|
+
vi.setSystemTime(NOW);
|
|
1167
|
+
SessionJanitor.Instance.Configure({ recordingRecoveryGraceMinutes: 10, recordingRecoveryLookbackHours: 168, recordingRecoveryAttemptTimeoutMinutes: 10 });
|
|
1168
|
+
ledgerMock.ClaimAssembly.mockResolvedValue(7);
|
|
1169
|
+
assembleMock.mockResolvedValue('Partial');
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
afterEach(() => {
|
|
1173
|
+
SessionJanitor.Instance.Stop();
|
|
1174
|
+
vi.useRealTimers();
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it('queries closed sessions past the grace and inside the lookback that are Pending, Assembling, or lost their status with shards on the ledger', async () => {
|
|
1178
|
+
await ledgerSweepWith([]);
|
|
1179
|
+
|
|
1180
|
+
const filter = ledgerFilter();
|
|
1181
|
+
expect(filter).toContain("Status = 'Closed'");
|
|
1182
|
+
expect(filter).toContain(`ClosedAt < '${new Date(NOW.getTime() - 10 * MINUTE).toISOString()}'`);
|
|
1183
|
+
expect(filter).toContain(`ClosedAt >= '${new Date(NOW.getTime() - 168 * HOUR).toISOString()}'`);
|
|
1184
|
+
expect(filter).toContain("RecordingStatus IN ('Pending','Assembling')");
|
|
1185
|
+
expect(filter).toContain(`RecordingStatus IS NULL AND ID IN (SELECT AgentSessionID FROM ${LEDGER_VIEW})`);
|
|
1186
|
+
expect(filter).not.toContain('RecordingStartedAt');
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it('assembles a closed Pending session with no terminal signal as Partial: stale-aware claim, then a forced assembly', async () => {
|
|
1190
|
+
const session = ledgerCandidate();
|
|
1191
|
+
|
|
1192
|
+
expect(await ledgerSweepWith([session])).toBe(1);
|
|
1193
|
+
|
|
1194
|
+
expect(ledgerMock.ClaimAssembly).toHaveBeenCalledWith(session.ID, STALE_CUTOFF);
|
|
1195
|
+
expect(assembleMock).toHaveBeenCalledTimes(1);
|
|
1196
|
+
expect(assembleMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
1197
|
+
SessionID: session.ID, Version: 7, Ledger: ledgerMock, ForcePartial: true,
|
|
1198
|
+
}));
|
|
1199
|
+
expect(logStatusMock).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`${session.ID}.*Partial`)));
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
it('re-claims an Assembling session whose assembler went quiet past the attempt timeout, but not a fresh one', async () => {
|
|
1203
|
+
const stale = ledgerCandidate({ RecordingStatus: 'Assembling', __mj_UpdatedAt: new Date(STALE_CUTOFF.getTime() - 1) });
|
|
1204
|
+
const fresh = ledgerCandidate({ RecordingStatus: 'Assembling', __mj_UpdatedAt: new Date(NOW.getTime() - MINUTE) });
|
|
1205
|
+
|
|
1206
|
+
expect(await ledgerSweepWith([fresh, stale])).toBe(1);
|
|
1207
|
+
|
|
1208
|
+
expect(claimedIDs()).toEqual([stale.ID]);
|
|
1209
|
+
expect(ledgerMock.ClaimAssembly).toHaveBeenCalledWith(stale.ID, STALE_CUTOFF);
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
it('skips a session whose claim another assembler won, without backing it off', async () => {
|
|
1213
|
+
const session = ledgerCandidate();
|
|
1214
|
+
ledgerMock.ClaimAssembly.mockResolvedValueOnce(null);
|
|
1215
|
+
|
|
1216
|
+
expect(await ledgerSweepWith([session])).toBe(0);
|
|
1217
|
+
expect(assembleMock).not.toHaveBeenCalled();
|
|
1218
|
+
expect(logErrorMock).not.toHaveBeenCalled();
|
|
1219
|
+
|
|
1220
|
+
await ledgerSweepWith([session]); // not backed off: the next sweep tries the claim again
|
|
1221
|
+
expect(claimedIDs()).toEqual([session.ID, session.ID]);
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
it('backs off a session whose assembly hit a transient error (Retry), and retries after the backoff', async () => {
|
|
1225
|
+
const session = ledgerCandidate();
|
|
1226
|
+
assembleMock.mockResolvedValueOnce('Retry');
|
|
1227
|
+
|
|
1228
|
+
expect(await ledgerSweepWith([session])).toBe(0);
|
|
1229
|
+
await ledgerSweepWith([session]);
|
|
1230
|
+
expect(claimedIDs()).toEqual([session.ID]);
|
|
1231
|
+
|
|
1232
|
+
vi.setSystemTime(NOW.getTime() + 11 * MINUTE); // first backoff is 2^1 * 5 min
|
|
1233
|
+
await ledgerSweepWith([session]);
|
|
1234
|
+
expect(claimedIDs()).toEqual([session.ID, session.ID]);
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
it('gives up on a session that keeps hitting Retry after 8 attempts, with one LogError', async () => {
|
|
1238
|
+
const session = ledgerCandidate();
|
|
1239
|
+
assembleMock.mockResolvedValue('Retry');
|
|
1240
|
+
|
|
1241
|
+
for (let i = 0; i < 12; i++) {
|
|
1242
|
+
await ledgerSweepWith([session]);
|
|
1243
|
+
vi.setSystemTime(Date.now() + 7 * HOUR); // past the longest (6 h) backoff
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
expect(assembleMock).toHaveBeenCalledTimes(8);
|
|
1247
|
+
expect(logErrorMock.mock.calls.filter((c) => /Giving up/.test(String(c[0])) && String(c[0]).includes(session.ID))).toHaveLength(1);
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
it('LogErrors a Failed assembly with the session id and counts it as a failure', async () => {
|
|
1251
|
+
const session = ledgerCandidate();
|
|
1252
|
+
assembleMock.mockResolvedValueOnce('Failed');
|
|
1253
|
+
|
|
1254
|
+
expect(await ledgerSweepWith([session])).toBe(0);
|
|
1255
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`${session.ID}.*Failed`)));
|
|
1256
|
+
await ledgerSweepWith([session]);
|
|
1257
|
+
expect(claimedIDs()).toEqual([session.ID]); // backed off
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
it('logs a Superseded assembly without treating it as a failure', async () => {
|
|
1261
|
+
const session = ledgerCandidate();
|
|
1262
|
+
assembleMock.mockResolvedValueOnce('Superseded');
|
|
1263
|
+
|
|
1264
|
+
expect(await ledgerSweepWith([session])).toBe(0);
|
|
1265
|
+
expect(logErrorMock).not.toHaveBeenCalled();
|
|
1266
|
+
expect(logStatusMock).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`${session.ID}.*Superseded`)));
|
|
1267
|
+
await ledgerSweepWith([session]);
|
|
1268
|
+
expect(claimedIDs()).toEqual([session.ID, session.ID]);
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
it('LogErrors an unexpected Deferred from a forced assembly and backs off', async () => {
|
|
1272
|
+
const session = ledgerCandidate();
|
|
1273
|
+
assembleMock.mockResolvedValueOnce('Deferred');
|
|
1274
|
+
|
|
1275
|
+
await ledgerSweepWith([session]);
|
|
1276
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`${session.ID}.*Deferred`)));
|
|
1277
|
+
await ledgerSweepWith([session]);
|
|
1278
|
+
expect(claimedIDs()).toEqual([session.ID]);
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
it('logs and backs off a claim that throws, then moves on to the next candidate', async () => {
|
|
1282
|
+
const [bad, good] = [ledgerCandidate(), ledgerCandidate()];
|
|
1283
|
+
ledgerMock.ClaimAssembly.mockRejectedValueOnce(new Error('claim deadlock'));
|
|
1284
|
+
|
|
1285
|
+
expect(await ledgerSweepWith([bad, good])).toBe(1);
|
|
1286
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`${bad.ID}.*claim deadlock`)));
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
it('restores the status a racing session save wiped with a (0, 0) stamp, never counts invented from the ledger, before claiming', async () => {
|
|
1290
|
+
// No terminal counts survive: stamping 0/0 sets NULL -> Pending, and the assembly (shard count != 0)
|
|
1291
|
+
// is Partial and keeps the shards, instead of a made-up terminal labelling it Complete.
|
|
1292
|
+
const session = ledgerCandidate({ RecordingStatus: null });
|
|
1293
|
+
ledgerMock.StampTerminal.mockResolvedValueOnce({ Outcome: 'Stamped', RecordingStatus: 'Pending' });
|
|
1294
|
+
|
|
1295
|
+
expect(await ledgerSweepWith([session])).toBe(1);
|
|
1296
|
+
|
|
1297
|
+
expect(ledgerMock.StampTerminal).toHaveBeenCalledWith(session.ID, 0, 0);
|
|
1298
|
+
expect(ledgerMock.LoadShardMetadata).not.toHaveBeenCalled();
|
|
1299
|
+
expect(ledgerMock.StampTerminal.mock.invocationCallOrder[0]).toBeLessThan(ledgerMock.ClaimAssembly.mock.invocationCallOrder[0]);
|
|
1300
|
+
expect(assembleMock).toHaveBeenCalledWith(expect.objectContaining({ SessionID: session.ID, ForcePartial: true }));
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
it('restores a lost status with the counts the session still stores', async () => {
|
|
1304
|
+
const session = ledgerCandidate({ RecordingStatus: null, RecordingExpectedShards: 9, RecordingCapturedSamples: 2_000_000 });
|
|
1305
|
+
ledgerMock.StampTerminal.mockResolvedValueOnce({ Outcome: 'Stamped', RecordingStatus: 'Pending' });
|
|
1306
|
+
|
|
1307
|
+
await ledgerSweepWith([session]);
|
|
1308
|
+
|
|
1309
|
+
expect(ledgerMock.StampTerminal).toHaveBeenCalledWith(session.ID, 9, 2_000_000);
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
it('stamps a BIGINT capture count that the candidate page delivers as a string as a number', async () => {
|
|
1313
|
+
// A RunView entity page keeps the driver's value for a BIGINT column: the SQL Server driver
|
|
1314
|
+
// returns it as a string, and the ledger's integer guard refused it (#5294 gauntlet, row S13 J3).
|
|
1315
|
+
const session = ledgerCandidate({ RecordingStatus: null, RecordingExpectedShards: 5, RecordingCapturedSamples: '1137600' as unknown as number });
|
|
1316
|
+
ledgerMock.StampTerminal.mockResolvedValueOnce({ Outcome: 'Stamped', RecordingStatus: 'Pending' });
|
|
1317
|
+
|
|
1318
|
+
await ledgerSweepWith([session]);
|
|
1319
|
+
|
|
1320
|
+
expect(ledgerMock.StampTerminal).toHaveBeenCalledWith(session.ID, 5, 1_137_600);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
it('LogErrors and backs off a lost status the stamp could not restore', async () => {
|
|
1324
|
+
const session = ledgerCandidate({ RecordingStatus: null });
|
|
1325
|
+
ledgerMock.StampTerminal.mockResolvedValueOnce({ Outcome: 'Ignored', RecordingStatus: null });
|
|
1326
|
+
|
|
1327
|
+
expect(await ledgerSweepWith([session])).toBe(0);
|
|
1328
|
+
expect(ledgerMock.ClaimAssembly).not.toHaveBeenCalled();
|
|
1329
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringContaining(session.ID));
|
|
1330
|
+
await ledgerSweepWith([session]);
|
|
1331
|
+
expect(ledgerMock.StampTerminal).toHaveBeenCalledTimes(1); // backed off
|
|
1332
|
+
});
|
|
1333
|
+
|
|
1334
|
+
it('counts failed status restores toward the per-sweep cap', async () => {
|
|
1335
|
+
const sessions = Array.from({ length: 7 }, () => ledgerCandidate({ RecordingStatus: null }));
|
|
1336
|
+
ledgerMock.StampTerminal.mockRejectedValue(new Error('stamp deadlock'));
|
|
1337
|
+
|
|
1338
|
+
await ledgerSweepWith(sessions);
|
|
1339
|
+
|
|
1340
|
+
expect(ledgerMock.StampTerminal).toHaveBeenCalledTimes(5);
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
it('attempts at most 5 assemblies per sweep', async () => {
|
|
1344
|
+
const sessions = Array.from({ length: 7 }, () => ledgerCandidate());
|
|
1345
|
+
|
|
1346
|
+
expect(await ledgerSweepWith(sessions)).toBe(5);
|
|
1347
|
+
expect(assembleMock).toHaveBeenCalledTimes(5);
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
it('abandons an assembly that outlives the attempt timeout, backs it off, and continues with the next candidate', async () => {
|
|
1351
|
+
const [hung, next] = [ledgerCandidate(), ledgerCandidate()];
|
|
1352
|
+
let release: () => void = () => undefined;
|
|
1353
|
+
assembleMock.mockImplementation(async (input: { SessionID: string }) => {
|
|
1354
|
+
if (input.SessionID === hung.ID) {
|
|
1355
|
+
await new Promise<void>((resolve) => { release = resolve; });
|
|
1356
|
+
return 'Retry';
|
|
1357
|
+
}
|
|
1358
|
+
return 'Complete';
|
|
1359
|
+
});
|
|
1360
|
+
try {
|
|
1361
|
+
const sweep = ledgerSweepWith([hung, next]);
|
|
1362
|
+
await vi.advanceTimersByTimeAsync(10 * MINUTE);
|
|
1363
|
+
expect(await sweep).toBe(1);
|
|
1364
|
+
expect(logErrorMock).toHaveBeenCalledWith(expect.stringMatching(new RegExp(`${hung.ID}.*did not finish within 10 minutes`)));
|
|
1365
|
+
|
|
1366
|
+
vi.setSystemTime(Date.now() + 11 * MINUTE); // past the backoff, but the abandoned call is still running
|
|
1367
|
+
await ledgerSweepWith([hung]);
|
|
1368
|
+
expect(assembleMock.mock.calls.map((c) => (c[0] as { SessionID: string }).SessionID)).toEqual([hung.ID, next.ID]);
|
|
1369
|
+
} finally {
|
|
1370
|
+
release();
|
|
1371
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1021
1376
|
describe('SessionJanitor.Configure', () => {
|
|
1022
1377
|
const DEFAULTS: SessionJanitorConfig = {
|
|
1023
1378
|
closeThresholdMinutes: 15, sweepIntervalMs: 60_000, recordingRecoveryGraceMinutes: 10, recordingRecoveryLookbackHours: 168,
|