@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseSingleton, ShutdownRegistry } from '@memberjunction/global';
|
|
2
2
|
import { CompositeKey, RunView, LogError, LogStatus, } from '@memberjunction/core';
|
|
3
|
-
import { RecoverRealtimeRecordingFromSegments, ResolveRecordingStorageAccountID, } from '@memberjunction/ai-agents';
|
|
3
|
+
import { AssembleRealtimeRecordingFromLedger, RecoverRealtimeRecordingFromSegments, ResolveRecordingStorageAccountID, SqlRealtimeRecordingLedger, } from '@memberjunction/ai-agents';
|
|
4
4
|
import { GetCurrentHostInstance, GetHostInstanceID } from './HostInstance.js';
|
|
5
5
|
import { SessionManager } from './SessionManager.js';
|
|
6
6
|
/** Entity name for session rows (kept in sync with {@link SessionManager}). */
|
|
@@ -11,8 +11,16 @@ const SWEEP_PAGE_SIZE = 200;
|
|
|
11
11
|
const AGENT_ENTITY = 'MJ: AI Agents';
|
|
12
12
|
/** A session whose recording recovery has failed this many times is given up on for the life of the process. */
|
|
13
13
|
const MAX_RECOVERY_ATTEMPTS = 8;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Recovery attempts (calls into the recovery routine) per sweep; bounds the I/O one tick can trigger.
|
|
16
|
+
* The ledger sweep and the legacy segment sweep each get this many: a backlog in one must not starve
|
|
17
|
+
* the other, so a pass makes at most twice this many attempts.
|
|
18
|
+
*/
|
|
15
19
|
const MAX_RECOVERIES_PER_SWEEP = 5;
|
|
20
|
+
/** The shard-ledger view (core schema); a session with rows in it is assembled by the ledger sweep. */
|
|
21
|
+
const RECORDING_SHARD_VIEW = 'vwAIAgentSessionRecordingShards';
|
|
22
|
+
/** Ledger shard rows each recovery pass deletes, at most, for sessions closed before the lookback window. */
|
|
23
|
+
const MAX_PURGED_SHARDS_PER_PASS = 500;
|
|
16
24
|
/**
|
|
17
25
|
* Abandoned (timed-out, still running) recovery attempts allowed at once. Each keeps its socket and any
|
|
18
26
|
* shard buffers it had read (up to the 384 MiB recovery cap) until it settles, so recovery pauses at this
|
|
@@ -42,6 +50,69 @@ const CONFIG_KEYS = [
|
|
|
42
50
|
'recordingRecoveryLookbackHours',
|
|
43
51
|
'recordingRecoveryAttemptTimeoutMinutes',
|
|
44
52
|
];
|
|
53
|
+
/**
|
|
54
|
+
* Per-session retry state of one recovery sweep, keyed by lowercase session id (process-local: a restart
|
|
55
|
+
* gets fresh attempts). A failing session backs off exponentially (`min(2^k * 5 min, 6 h)`) and is given
|
|
56
|
+
* up on after {@link MAX_RECOVERY_ATTEMPTS} failures with one log line.
|
|
57
|
+
*/
|
|
58
|
+
class RecoveryBackoffTracker {
|
|
59
|
+
constructor() {
|
|
60
|
+
this.states = new Map();
|
|
61
|
+
}
|
|
62
|
+
/** True while a session's backoff (or give-up) window has not elapsed. */
|
|
63
|
+
IsBackedOff(sessionID) {
|
|
64
|
+
const state = this.states.get(sessionID.toLowerCase());
|
|
65
|
+
return state != null && Date.now() < state.NextAttemptAt;
|
|
66
|
+
}
|
|
67
|
+
/** Forgets a session's failures (it succeeded, or another writer took care of it). */
|
|
68
|
+
Clear(sessionID) {
|
|
69
|
+
this.states.delete(sessionID.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Defers a session that cannot be recovered until configuration changes (its agent has no recording
|
|
73
|
+
* storage account). That is not a failed attempt, so it never counts toward the give-up: the session
|
|
74
|
+
* is re-checked every {@link RECOVERY_BACKOFF_MAX_MS} and logged only the first time.
|
|
75
|
+
*/
|
|
76
|
+
Defer(sessionID, reason) {
|
|
77
|
+
const key = sessionID.toLowerCase();
|
|
78
|
+
const state = this.states.get(key);
|
|
79
|
+
if (!state?.Deferred) {
|
|
80
|
+
LogStatus(`[SessionJanitor] Recording recovery skipped for session ${sessionID}: ${reason}; re-checked every ${RECOVERY_BACKOFF_MAX_MS / 3_600_000} h`);
|
|
81
|
+
}
|
|
82
|
+
this.states.set(key, { Failures: state?.Failures ?? 0, NextAttemptAt: Date.now() + RECOVERY_BACKOFF_MAX_MS, Deferred: true });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Counts one more unsuccessful recovery for a session and schedules its next attempt
|
|
86
|
+
* (`min(2^k * 5 min, 6 h)`); at {@link MAX_RECOVERY_ATTEMPTS} it stops checking the session for this
|
|
87
|
+
* process with a single log line: a `LogError` when the last attempt failed, a `LogStatus` when it
|
|
88
|
+
* simply found no shards (nothing to recover is not an error).
|
|
89
|
+
*/
|
|
90
|
+
RecordFailure(sessionID, kind) {
|
|
91
|
+
const key = sessionID.toLowerCase();
|
|
92
|
+
const failures = (this.states.get(key)?.Failures ?? 0) + 1;
|
|
93
|
+
if (failures >= MAX_RECOVERY_ATTEMPTS) {
|
|
94
|
+
this.states.set(key, { Failures: failures, NextAttemptAt: Infinity, Deferred: false });
|
|
95
|
+
if (kind === 'NoSegments') {
|
|
96
|
+
LogStatus(`[SessionJanitor] Session ${sessionID} had no recoverable segments in ${failures} checks; not checking it again until restart`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
LogError(`[SessionJanitor] Giving up on recording recovery for session ${sessionID} after ${failures} failed attempts`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const waitMs = Math.min(2 ** failures * RECOVERY_BACKOFF_BASE_MS, RECOVERY_BACKOFF_MAX_MS);
|
|
104
|
+
this.states.set(key, { Failures: failures, NextAttemptAt: Date.now() + waitMs, Deferred: false });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Drops state for sessions that were not in the latest complete candidate set (lowercase ids). */
|
|
108
|
+
Prune(candidateKeys) {
|
|
109
|
+
for (const key of this.states.keys()) {
|
|
110
|
+
if (!candidateKeys.has(key)) {
|
|
111
|
+
this.states.delete(key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
45
116
|
/**
|
|
46
117
|
* `BaseSingleton` background reconciler that keeps the durable `AIAgentSession` state from drifting
|
|
47
118
|
* away from volatile process reality. A crash/redeploy vaporizes a host's in-memory sockets but
|
|
@@ -65,13 +136,17 @@ const CONFIG_KEYS = [
|
|
|
65
136
|
* invoked from {@link Shutdown} during the graceful ShutdownRegistry drain — closes this exact host
|
|
66
137
|
* instance's own live sessions with `CloseReason = 'Shutdown'`.
|
|
67
138
|
*
|
|
68
|
-
* Separately from closing,
|
|
69
|
-
*
|
|
70
|
-
*
|
|
139
|
+
* Separately from closing, a recording recovery pass runs after the close sweeps on every periodic tick,
|
|
140
|
+
* without blocking them, and closes nothing. It purges shard-ledger rows past the lookback window, then
|
|
141
|
+
* {@link RunRecordingLedgerSweep} assembles the recordings of `Closed` sessions on the shard ledger that
|
|
142
|
+
* no terminal signal (or a dead assembler) left unassembled, then {@link RunRecordingRecoverySweep}
|
|
143
|
+
* rebuilds, from legacy storage segments, the recording of `Closed` sessions not on the ledger whose
|
|
144
|
+
* end-of-call upload never arrived.
|
|
71
145
|
*
|
|
72
146
|
* The close sweeps are **idempotent and safe to run concurrently** on every instance: closing an
|
|
73
|
-
* already-`Closed` session is a no-op, and the close path is last-writer-wins.
|
|
74
|
-
*
|
|
147
|
+
* already-`Closed` session is a no-op, and the close path is last-writer-wins. Ledger assembly is
|
|
148
|
+
* arbitrated by the ledger's atomic claim. Legacy recovery is not: two instances can recover the same
|
|
149
|
+
* session at once (there is no claim column for it). Each
|
|
75
150
|
* attempt uploads its own file and defers to an already-stamped recording on a best-effort basis, so
|
|
76
151
|
* the cost is duplicate work and an extra file, not a lost recording. The extra file is an orphan when the
|
|
77
152
|
* second attempt's re-check sees the first stamp; when both re-checks pass first, both files are stamped in
|
|
@@ -95,8 +170,13 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
95
170
|
this._provider = null;
|
|
96
171
|
this._systemUser = null;
|
|
97
172
|
this.sessionManager = new SessionManager();
|
|
98
|
-
/**
|
|
99
|
-
this.recoveryBackoff = new
|
|
173
|
+
/** Retry state of the legacy storage-segment recovery. */
|
|
174
|
+
this.recoveryBackoff = new RecoveryBackoffTracker();
|
|
175
|
+
/**
|
|
176
|
+
* Retry state of the ledger sweep, kept apart from {@link recoveryBackoff}: each sweep prunes its
|
|
177
|
+
* tracker by its own candidate set, so a shared one would forget the other sweep's failures.
|
|
178
|
+
*/
|
|
179
|
+
this.ledgerBackoff = new RecoveryBackoffTracker();
|
|
100
180
|
/**
|
|
101
181
|
* Lowercase ids of sessions whose recovery attempt timed out but has not settled. Skipped by later
|
|
102
182
|
* sweeps so a second attempt never runs alongside the abandoned one; removed when it settles.
|
|
@@ -136,7 +216,8 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
136
216
|
* close sweeps, then recording recovery). Idempotent: a second call does not stack a second timer.
|
|
137
217
|
* Captures the provider + system user so the timer callback can run without re-supplying them, and
|
|
138
218
|
* re-enables recording recovery after a {@link Stop}. Throws, before doing any work, when
|
|
139
|
-
* `intervalMs` is invalid (see {@link Configure}).
|
|
219
|
+
* `intervalMs` is invalid (see {@link Configure}). Takes the server's database provider because
|
|
220
|
+
* recording recovery calls the shard ledger's stored procedures.
|
|
140
221
|
*/
|
|
141
222
|
async Start(provider, systemUser, intervalMs) {
|
|
142
223
|
if (intervalMs != null) {
|
|
@@ -271,31 +352,96 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
271
352
|
* sessions that left the candidate set are pruned, but only after a pass that saw every candidate, so
|
|
272
353
|
* a capped pass never forgets sessions it did not reach.
|
|
273
354
|
*
|
|
355
|
+
* Sessions on the shard ledger (a `RecordingStatus`, or any ledger row) are left to
|
|
356
|
+
* {@link RunRecordingLedgerSweep}: their audio is in SQL, not in storage segments.
|
|
357
|
+
*
|
|
274
358
|
* Never throws for a single bad session. Returns the number of recordings recovered.
|
|
275
359
|
*/
|
|
276
360
|
async RunRecordingRecoverySweep(provider, systemUser) {
|
|
277
361
|
if (this.isRecoveryPausedForAbandonedAttempts()) {
|
|
278
362
|
return 0;
|
|
279
363
|
}
|
|
280
|
-
const
|
|
281
|
-
const graceCutoff = new Date(nowMs - this._config.recordingRecoveryGraceMinutes * 60_000).toISOString();
|
|
282
|
-
const lookbackCutoff = new Date(nowMs - this._config.recordingRecoveryLookbackHours * 3_600_000).toISOString();
|
|
364
|
+
const { graceCutoff, lookbackCutoff } = this.recoveryWindow();
|
|
283
365
|
const filter = `Status = 'Closed' AND RecordingStartedAt IS NOT NULL AND RecordingFileID IS NULL ` +
|
|
284
|
-
`AND ClosedAt < '${graceCutoff}' AND ClosedAt >= '${lookbackCutoff}'
|
|
366
|
+
`AND ClosedAt < '${graceCutoff.toISOString()}' AND ClosedAt >= '${lookbackCutoff.toISOString()}' ` +
|
|
367
|
+
`AND RecordingStatus IS NULL AND ID NOT IN (SELECT AgentSessionID FROM ${this.shardViewName(provider)})`;
|
|
285
368
|
const context = { provider, systemUser, storage: new Map() };
|
|
369
|
+
const recovered = await this.sweepRecoveryCandidates(filter, provider, systemUser, this.recoveryBackoff, session => this.recoverSessionRecording(session, context));
|
|
370
|
+
if (recovered > 0) {
|
|
371
|
+
LogStatus(`[SessionJanitor] Recording recovery rebuilt ${recovered} recording(s)`);
|
|
372
|
+
}
|
|
373
|
+
return recovered;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Backstop for client-direct recordings on the shard ledger: assembles the recording of `Closed`
|
|
377
|
+
* sessions that closed between the lookback window and the grace period and are still
|
|
378
|
+
* - `Pending` (shards, or a terminal signal, with no assembly since: the browser died before its
|
|
379
|
+
* terminal request, or the in-process assembly request was lost with its host),
|
|
380
|
+
* - `Assembling` with no write for `recordingRecoveryAttemptTimeoutMinutes` (the assembler died, or
|
|
381
|
+
* it hit a transient error and left the claim for a re-claim), or
|
|
382
|
+
* - status-less while the ledger holds shards for them (see {@link restoreLostRecordingStatus}).
|
|
383
|
+
*
|
|
384
|
+
* Each candidate is claimed with the ledger's atomic, stale-aware claim (a lost claim is skipped: another
|
|
385
|
+
* assembler has it) and assembled with `ForcePartial`, so missing shards become silence. An attempt is
|
|
386
|
+
* bounded by `recordingRecoveryAttemptTimeoutMinutes` and shares the legacy sweep's abandoned-attempt
|
|
387
|
+
* pause; failures back off and are given up on exactly like legacy recovery, in their own tracker.
|
|
388
|
+
* At most {@link MAX_RECOVERIES_PER_SWEEP} assemblies per call. Never throws for a single bad session.
|
|
389
|
+
* Returns the number of recordings assembled (`Complete` or `Partial`).
|
|
390
|
+
*/
|
|
391
|
+
async RunRecordingLedgerSweep(provider, systemUser) {
|
|
392
|
+
if (this.isRecoveryPausedForAbandonedAttempts()) {
|
|
393
|
+
return 0;
|
|
394
|
+
}
|
|
395
|
+
const { graceCutoff, lookbackCutoff } = this.recoveryWindow();
|
|
396
|
+
const staleCutoff = new Date(Date.now() - this._config.recordingRecoveryAttemptTimeoutMinutes * 60_000);
|
|
397
|
+
// Every Assembling session is fetched, not only stale ones, and a fresh one is skipped below: a
|
|
398
|
+
// session whose assembly left a Retry claim is fresh for a while, and dropping it from the walk
|
|
399
|
+
// would prune its backoff, so the give-up cap would never be reached.
|
|
400
|
+
const filter = `Status = 'Closed' AND ClosedAt < '${graceCutoff.toISOString()}' AND ClosedAt >= '${lookbackCutoff.toISOString()}' ` +
|
|
401
|
+
`AND (RecordingStatus IN ('Pending','Assembling') ` +
|
|
402
|
+
`OR (RecordingStatus IS NULL AND ID IN (SELECT AgentSessionID FROM ${this.shardViewName(provider)})))`;
|
|
403
|
+
const ledger = new SqlRealtimeRecordingLedger(provider, systemUser);
|
|
404
|
+
const assembled = await this.sweepRecoveryCandidates(filter, provider, systemUser, this.ledgerBackoff, session => this.assembleSessionFromLedger(session, ledger, staleCutoff, provider, systemUser),
|
|
405
|
+
// The claim proc re-checks staleness atomically; this only spares a claim that would lose.
|
|
406
|
+
session => session.RecordingStatus === 'Assembling' && session.__mj_UpdatedAt.getTime() >= staleCutoff.getTime());
|
|
407
|
+
if (assembled > 0) {
|
|
408
|
+
LogStatus(`[SessionJanitor] Recording ledger sweep assembled ${assembled} recording(s)`);
|
|
409
|
+
}
|
|
410
|
+
return assembled;
|
|
411
|
+
}
|
|
412
|
+
/** The closed-at window recording recovery considers: older than the grace, newer than the lookback. */
|
|
413
|
+
recoveryWindow() {
|
|
414
|
+
const nowMs = Date.now();
|
|
415
|
+
return {
|
|
416
|
+
graceCutoff: new Date(nowMs - this._config.recordingRecoveryGraceMinutes * 60_000),
|
|
417
|
+
lookbackCutoff: new Date(nowMs - this._config.recordingRecoveryLookbackHours * 3_600_000),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/** The schema-qualified, dialect-quoted shard-ledger view, for filter sub-queries. */
|
|
421
|
+
shardViewName(provider) {
|
|
422
|
+
return provider.QuoteSchemaAndView(provider.MJCoreSchemaName, RECORDING_SHARD_VIEW);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Walks every session matching `filter` and runs `recover` on each one that is not backed off, not
|
|
426
|
+
* abandoned, and not `isBusy`, sequentially, stopping at {@link MAX_RECOVERIES_PER_SWEEP} attempts or
|
|
427
|
+
* {@link MAX_ABANDONED_RECOVERIES} abandoned attempts. Backoff entries of sessions that left the
|
|
428
|
+
* candidate set are pruned, but only after a walk that saw every candidate, so a capped walk never
|
|
429
|
+
* forgets sessions it did not reach. Returns the number recovered.
|
|
430
|
+
*/
|
|
431
|
+
async sweepRecoveryCandidates(filter, provider, systemUser, backoff, recover, isBusy = () => false) {
|
|
286
432
|
const seen = new Set();
|
|
287
433
|
let attempts = 0;
|
|
288
434
|
let recovered = 0;
|
|
289
435
|
const walk = await this.forEachSessionPage(filter, provider, systemUser, async (page) => {
|
|
290
436
|
for (const session of page) {
|
|
291
437
|
seen.add(session.ID.toLowerCase());
|
|
292
|
-
if (
|
|
438
|
+
if (backoff.IsBackedOff(session.ID) || this.abandonedRecoveries.has(session.ID.toLowerCase()) || isBusy(session)) {
|
|
293
439
|
continue;
|
|
294
440
|
}
|
|
295
441
|
if (attempts >= MAX_RECOVERIES_PER_SWEEP || this.abandonedRecoveries.size >= MAX_ABANDONED_RECOVERIES) {
|
|
296
442
|
return false; // per-sweep cap, or too many abandoned attempts still running: stop paging
|
|
297
443
|
}
|
|
298
|
-
const visit = await
|
|
444
|
+
const visit = await recover(session);
|
|
299
445
|
attempts += visit.Attempted ? 1 : 0;
|
|
300
446
|
recovered += visit.Recovered ? 1 : 0;
|
|
301
447
|
}
|
|
@@ -303,13 +449,120 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
303
449
|
// Only a walk that reached the end saw every candidate. A capped or failed-load walk has an
|
|
304
450
|
// incomplete `seen`; pruning on it would wipe backoff and give-up state for unseen sessions.
|
|
305
451
|
if (walk === 'complete') {
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
if (recovered > 0) {
|
|
309
|
-
LogStatus(`[SessionJanitor] Recording recovery rebuilt ${recovered} recording(s)`);
|
|
452
|
+
backoff.Prune(seen);
|
|
310
453
|
}
|
|
311
454
|
return recovered;
|
|
312
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Claims and assembles one ledger candidate and updates its backoff. Never throws: any exception is
|
|
458
|
+
* logged with the session id and counted as a failure.
|
|
459
|
+
*/
|
|
460
|
+
async assembleSessionFromLedger(candidate, ledger, staleCutoff, provider, systemUser) {
|
|
461
|
+
const sessionID = candidate.ID;
|
|
462
|
+
let attempted = false;
|
|
463
|
+
try {
|
|
464
|
+
if (candidate.RecordingStatus == null) {
|
|
465
|
+
// The restore is a write; a failing one counts toward the per-sweep cap like an assembly.
|
|
466
|
+
attempted = true;
|
|
467
|
+
if (!(await this.restoreLostRecordingStatus(candidate, ledger))) {
|
|
468
|
+
return this.recordLedgerFailure(sessionID, true);
|
|
469
|
+
}
|
|
470
|
+
attempted = false;
|
|
471
|
+
}
|
|
472
|
+
const version = await ledger.ClaimAssembly(sessionID, staleCutoff);
|
|
473
|
+
if (version === null) {
|
|
474
|
+
LogStatus(`[SessionJanitor] Recording ledger sweep skipped session ${sessionID}: another assembler holds its claim`);
|
|
475
|
+
return { Attempted: false, Recovered: false };
|
|
476
|
+
}
|
|
477
|
+
attempted = true;
|
|
478
|
+
const outcome = await this.runWithinAttemptTimeout(sessionID, AssembleRealtimeRecordingFromLedger({
|
|
479
|
+
SessionID: sessionID, Version: version, Ledger: ledger,
|
|
480
|
+
ContextUser: systemUser, Provider: provider, ForcePartial: true,
|
|
481
|
+
}), late => late);
|
|
482
|
+
switch (outcome) {
|
|
483
|
+
case 'Complete':
|
|
484
|
+
case 'Partial':
|
|
485
|
+
this.ledgerBackoff.Clear(sessionID);
|
|
486
|
+
LogStatus(`[SessionJanitor] Assembled the recording of session ${sessionID} from its shard ledger: ${outcome} (version ${version})`);
|
|
487
|
+
return { Attempted: true, Recovered: true };
|
|
488
|
+
case 'Superseded':
|
|
489
|
+
this.ledgerBackoff.Clear(sessionID);
|
|
490
|
+
LogStatus(`[SessionJanitor] Ledger assembly of session ${sessionID} (version ${version}) was Superseded by a newer claim; left to it`);
|
|
491
|
+
return { Attempted: true, Recovered: false };
|
|
492
|
+
case 'Retry':
|
|
493
|
+
// The assembler already logged the error; the claim stays Assembling for a re-claim after the backoff.
|
|
494
|
+
LogStatus(`[SessionJanitor] Ledger assembly of session ${sessionID} (version ${version}) hit a transient error; retried after its backoff`);
|
|
495
|
+
return this.recordLedgerFailure(sessionID, true);
|
|
496
|
+
case 'Deferred':
|
|
497
|
+
LogError(`[SessionJanitor] Forced ledger assembly of session ${sessionID} (version ${version}) returned Deferred, which only an unforced assembly should; backing off`);
|
|
498
|
+
return this.recordLedgerFailure(sessionID, true);
|
|
499
|
+
case 'Failed':
|
|
500
|
+
LogError(`[SessionJanitor] Ledger assembly of session ${sessionID} (version ${version}) Failed; its recording status is now Failed`);
|
|
501
|
+
return this.recordLedgerFailure(sessionID, true);
|
|
502
|
+
case 'TimedOut':
|
|
503
|
+
LogError(`[SessionJanitor] Ledger assembly of session ${sessionID} (version ${version}) did not finish within ` +
|
|
504
|
+
`${this._config.recordingRecoveryAttemptTimeoutMinutes} minutes; abandoning this attempt (it may still finish) ` +
|
|
505
|
+
`and continuing the sweep. The session is not retried until it settles.`);
|
|
506
|
+
return this.recordLedgerFailure(sessionID, true);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch (err) {
|
|
510
|
+
LogError(`[SessionJanitor] Recording ledger sweep failed for session ${sessionID}: ${err instanceof Error ? err.message : String(err)}`);
|
|
511
|
+
return this.recordLedgerFailure(sessionID, attempted);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Gives a status-less session with ledger shards back a `Pending` status so it can be claimed (the
|
|
516
|
+
* claim only takes Pending/Partial/stale Assembling). That is how statuses were lost before #6010
|
|
517
|
+
* made the ledger columns not updatable through an entity save (a stale `Save` of the session, e.g. its
|
|
518
|
+
* close, put back the NULL status and counts). The restore stays for rows hit before then.
|
|
519
|
+
*
|
|
520
|
+
* The terminal proc is the existing way to set a NULL status to `Pending`. It is stamped with the
|
|
521
|
+
* counts the session still stores, or 0 for a NULL count (the proc accepts any count over NULL), and
|
|
522
|
+
* never with counts invented from the ledger: a made-up terminal could make a recording whose tail
|
|
523
|
+
* never arrived assemble as Complete, which deletes its shards. With no real terminal the stored shard
|
|
524
|
+
* count differs from the stamped one, so the assembly is Partial and keeps the shards; a later real
|
|
525
|
+
* terminal (higher counts) or late shard re-opens it intact.
|
|
526
|
+
* Returns false (logged) when the status could not be restored.
|
|
527
|
+
*/
|
|
528
|
+
async restoreLostRecordingStatus(candidate, ledger) {
|
|
529
|
+
const sessionID = candidate.ID;
|
|
530
|
+
const expectedShards = candidate.RecordingExpectedShards ?? 0;
|
|
531
|
+
// Number(): the candidate comes from a RunView entity page, which keeps the driver's value for a
|
|
532
|
+
// BIGINT column, and the SQL Server driver returns BIGINT as a string (the ledger refuses strings).
|
|
533
|
+
const capturedSamples = Number(candidate.RecordingCapturedSamples ?? 0);
|
|
534
|
+
const stamped = await ledger.StampTerminal(sessionID, expectedShards, capturedSamples);
|
|
535
|
+
if (stamped.RecordingStatus == null) {
|
|
536
|
+
LogError(`[SessionJanitor] Could not restore the recording status of session ${sessionID} ` +
|
|
537
|
+
`(terminal stamp of ${expectedShards} shard(s), ${capturedSamples} sample(s) was ${stamped.Outcome}); backing off`);
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
LogStatus(`[SessionJanitor] Restored recording status ${stamped.RecordingStatus} for session ${sessionID} ` +
|
|
541
|
+
`(stamped its stored counts: ${expectedShards} shard(s), ${capturedSamples} sample(s))`);
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
/** Counts a failed ledger assembly in its backoff (see {@link RecoveryBackoffTracker.RecordFailure}). */
|
|
545
|
+
recordLedgerFailure(sessionID, attempted) {
|
|
546
|
+
this.ledgerBackoff.RecordFailure(sessionID, 'Failed');
|
|
547
|
+
return { Attempted: attempted, Recovered: false };
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Deletes up to {@link MAX_PURGED_SHARDS_PER_PASS} ledger shards of sessions closed before the lookback
|
|
551
|
+
* window, which no sweep assembles any more. Never throws: a failure is logged and retried next pass.
|
|
552
|
+
*/
|
|
553
|
+
async purgeExpiredLedgerShards(provider, systemUser) {
|
|
554
|
+
const { lookbackCutoff } = this.recoveryWindow();
|
|
555
|
+
try {
|
|
556
|
+
const purged = await new SqlRealtimeRecordingLedger(provider, systemUser).PurgeClosedBefore(lookbackCutoff, MAX_PURGED_SHARDS_PER_PASS);
|
|
557
|
+
if (purged > 0) {
|
|
558
|
+
LogStatus(`[SessionJanitor] Purged ${purged} recording shard(s) of sessions closed before ${lookbackCutoff.toISOString()}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
LogError(`[SessionJanitor] Recording shard purge (sessions closed before ${lookbackCutoff.toISOString()}) failed: ` +
|
|
563
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
313
566
|
/** Parses the absolute deadline (ms) from a session's `Config_` JSON, or null when absent/malformed. */
|
|
314
567
|
parseSessionDeadlineMs(configJson) {
|
|
315
568
|
if (!configJson) {
|
|
@@ -372,6 +625,29 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
372
625
|
}
|
|
373
626
|
this.startRecoveryPass(this._provider, this._systemUser);
|
|
374
627
|
}
|
|
628
|
+
/**
|
|
629
|
+
* One recording recovery pass: purge expired ledger shards, then the ledger sweep, then legacy
|
|
630
|
+
* segment recovery. The ledger sweep goes first because its sessions are the current recording path;
|
|
631
|
+
* a failed ledger sweep is logged here so legacy recovery still runs. A legacy failure rejects (logged
|
|
632
|
+
* by {@link startRecoveryPass}).
|
|
633
|
+
*/
|
|
634
|
+
async runRecoveryPass(provider, systemUser) {
|
|
635
|
+
await this.purgeExpiredLedgerShards(provider, systemUser);
|
|
636
|
+
// A Stop() during a step ends the pass before the next one, so Shutdown waits on less work.
|
|
637
|
+
if (this._stopped) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
await this.RunRecordingLedgerSweep(provider, systemUser);
|
|
642
|
+
}
|
|
643
|
+
catch (err) {
|
|
644
|
+
LogError(`SessionJanitor recording ledger sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
645
|
+
}
|
|
646
|
+
if (this._stopped) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
await this.RunRecordingRecoverySweep(provider, systemUser);
|
|
650
|
+
}
|
|
375
651
|
/**
|
|
376
652
|
* Fire-and-forget recording recovery, skipped when the previous pass is still running or the janitor
|
|
377
653
|
* has been stopped (a tick that was mid-close-sweep when {@link Stop} ran must not start one).
|
|
@@ -380,7 +656,7 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
380
656
|
if (this._stopped || this._recoveryPass) {
|
|
381
657
|
return;
|
|
382
658
|
}
|
|
383
|
-
this._recoveryPass = this.
|
|
659
|
+
this._recoveryPass = this.runRecoveryPass(provider, systemUser)
|
|
384
660
|
.then(() => undefined, err => {
|
|
385
661
|
LogError(`SessionJanitor recording recovery sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
386
662
|
})
|
|
@@ -437,7 +713,8 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
437
713
|
}
|
|
438
714
|
const storage = await this.resolveRecoveryStorage(candidate.AgentID, context);
|
|
439
715
|
if (storage.Kind === 'NoAccount') {
|
|
440
|
-
|
|
716
|
+
this.recoveryBackoff.Defer(candidate.ID, `no recording storage account for agent ${candidate.AgentID}`);
|
|
717
|
+
return { Attempted: false, Recovered: false };
|
|
441
718
|
}
|
|
442
719
|
if (storage.Kind === 'AgentUnavailable') {
|
|
443
720
|
LogStatus(`[SessionJanitor] Recording recovery skipped for session ${candidate.ID}: agent ${candidate.AgentID} could not be loaded`);
|
|
@@ -445,13 +722,13 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
445
722
|
}
|
|
446
723
|
const accountID = storage.AccountID;
|
|
447
724
|
attempted = true;
|
|
448
|
-
const result = await this.
|
|
725
|
+
const result = await this.runWithinAttemptTimeout(candidate.ID, RecoverRealtimeRecordingFromSegments({
|
|
449
726
|
SessionID: candidate.ID,
|
|
450
727
|
StorageAccountID: accountID,
|
|
451
728
|
StartedAt: startedAt,
|
|
452
729
|
ContextUser: context.systemUser,
|
|
453
730
|
Provider: context.provider,
|
|
454
|
-
});
|
|
731
|
+
}), late => late.Outcome);
|
|
455
732
|
if (result === 'TimedOut') {
|
|
456
733
|
LogError(`[SessionJanitor] Recording recovery for session ${candidate.ID} did not finish within ` +
|
|
457
734
|
`${this._config.recordingRecoveryAttemptTimeoutMinutes} minutes; abandoning this attempt (its storage call may ` +
|
|
@@ -460,11 +737,11 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
460
737
|
}
|
|
461
738
|
switch (result.Outcome) {
|
|
462
739
|
case 'Recovered':
|
|
463
|
-
this.recoveryBackoff.
|
|
740
|
+
this.recoveryBackoff.Clear(candidate.ID);
|
|
464
741
|
LogStatus(`[SessionJanitor] Recovered recording for session ${candidate.ID} from ${result.SegmentCount} segment(s) (file ${result.FileID})`);
|
|
465
742
|
return { Attempted: true, Recovered: true };
|
|
466
743
|
case 'Superseded':
|
|
467
|
-
this.recoveryBackoff.
|
|
744
|
+
this.recoveryBackoff.Clear(candidate.ID);
|
|
468
745
|
LogStatus(`[SessionJanitor] Session ${candidate.ID} was given a recording by another writer; recovery left it alone`);
|
|
469
746
|
return { Attempted: true, Recovered: false };
|
|
470
747
|
case 'NoSegments':
|
|
@@ -483,13 +760,12 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
483
760
|
}
|
|
484
761
|
}
|
|
485
762
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
* it settles (later sweeps skip it), and its late outcome is logged then.
|
|
763
|
+
* Waits for one session's recovery attempt for at most `recordingRecoveryAttemptTimeoutMinutes`.
|
|
764
|
+
* An attempt cannot be cancelled, so a timed-out one keeps running: the session is marked abandoned
|
|
765
|
+
* until it settles (later sweeps skip it), and its late outcome (`describeLate`) is logged then.
|
|
489
766
|
*/
|
|
490
|
-
async
|
|
491
|
-
const key =
|
|
492
|
-
const attempt = RecoverRealtimeRecordingFromSegments(input);
|
|
767
|
+
async runWithinAttemptTimeout(sessionID, attempt, describeLate) {
|
|
768
|
+
const key = sessionID.toLowerCase();
|
|
493
769
|
let timer;
|
|
494
770
|
const timedOut = new Promise(resolve => {
|
|
495
771
|
timer = setTimeout(() => resolve('TimedOut'), this._config.recordingRecoveryAttemptTimeoutMinutes * 60_000);
|
|
@@ -499,7 +775,7 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
499
775
|
const outcome = await Promise.race([attempt, timedOut]);
|
|
500
776
|
if (outcome === 'TimedOut') {
|
|
501
777
|
this.abandonedRecoveries.add(key);
|
|
502
|
-
attempt.then(late => LogStatus(`[SessionJanitor] Abandoned recording recovery for session ${
|
|
778
|
+
attempt.then(late => LogStatus(`[SessionJanitor] Abandoned recording recovery for session ${sessionID} finished late: ${describeLate(late)}`), err => LogError(`[SessionJanitor] Abandoned recording recovery for session ${sessionID} failed late: ${err instanceof Error ? err.message : String(err)}`)).finally(() => this.abandonedRecoveries.delete(key));
|
|
503
779
|
}
|
|
504
780
|
return outcome;
|
|
505
781
|
}
|
|
@@ -523,20 +799,6 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
523
799
|
}
|
|
524
800
|
return true;
|
|
525
801
|
}
|
|
526
|
-
/**
|
|
527
|
-
* Defers a session that cannot be recovered until configuration changes (its agent has no recording
|
|
528
|
-
* storage account). That is not a failed attempt, so it never counts toward the give-up: the session
|
|
529
|
-
* is re-checked every {@link RECOVERY_BACKOFF_MAX_MS} and logged only the first time.
|
|
530
|
-
*/
|
|
531
|
-
deferRecovery(sessionID, reason) {
|
|
532
|
-
const key = sessionID.toLowerCase();
|
|
533
|
-
const state = this.recoveryBackoff.get(key);
|
|
534
|
-
if (!state?.Deferred) {
|
|
535
|
-
LogStatus(`[SessionJanitor] Recording recovery skipped for session ${sessionID}: ${reason}; re-checked every ${RECOVERY_BACKOFF_MAX_MS / 3_600_000} h`);
|
|
536
|
-
}
|
|
537
|
-
this.recoveryBackoff.set(key, { Failures: state?.Failures ?? 0, NextAttemptAt: Date.now() + RECOVERY_BACKOFF_MAX_MS, Deferred: true });
|
|
538
|
-
return { Attempted: false, Recovered: false };
|
|
539
|
-
}
|
|
540
802
|
/**
|
|
541
803
|
* Loads the agent carrying a session's recording storage config and resolves its storage account,
|
|
542
804
|
* memoized per agent for the sweep: the resolve may force a storage-engine reload, so it must not
|
|
@@ -561,43 +823,11 @@ export class SessionJanitor extends BaseSingleton {
|
|
|
561
823
|
context.storage.set(key, storage);
|
|
562
824
|
return storage;
|
|
563
825
|
}
|
|
564
|
-
/**
|
|
565
|
-
isRecoveryBackedOff(sessionID) {
|
|
566
|
-
const state = this.recoveryBackoff.get(sessionID.toLowerCase());
|
|
567
|
-
return state != null && Date.now() < state.NextAttemptAt;
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Counts one more unsuccessful recovery for a session and schedules its next attempt
|
|
571
|
-
* (`min(2^k * 5 min, 6 h)`); at {@link MAX_RECOVERY_ATTEMPTS} it stops checking the session for this
|
|
572
|
-
* process with a single log line: a `LogError` when the last attempt failed, a `LogStatus` when it
|
|
573
|
-
* simply found no shards (nothing to recover is not an error).
|
|
574
|
-
*/
|
|
826
|
+
/** Counts a failed legacy recovery in its backoff (see {@link RecoveryBackoffTracker.RecordFailure}). */
|
|
575
827
|
recordRecoveryFailure(sessionID, attempted, kind = 'Failed') {
|
|
576
|
-
|
|
577
|
-
const failures = (this.recoveryBackoff.get(key)?.Failures ?? 0) + 1;
|
|
578
|
-
if (failures >= MAX_RECOVERY_ATTEMPTS) {
|
|
579
|
-
this.recoveryBackoff.set(key, { Failures: failures, NextAttemptAt: Infinity, Deferred: false });
|
|
580
|
-
if (kind === 'NoSegments') {
|
|
581
|
-
LogStatus(`[SessionJanitor] Session ${sessionID} had no recoverable segments in ${failures} checks; not checking it again until restart`);
|
|
582
|
-
}
|
|
583
|
-
else {
|
|
584
|
-
LogError(`[SessionJanitor] Giving up on recording recovery for session ${sessionID} after ${failures} failed attempts`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
const waitMs = Math.min(2 ** failures * RECOVERY_BACKOFF_BASE_MS, RECOVERY_BACKOFF_MAX_MS);
|
|
589
|
-
this.recoveryBackoff.set(key, { Failures: failures, NextAttemptAt: Date.now() + waitMs, Deferred: false });
|
|
590
|
-
}
|
|
828
|
+
this.recoveryBackoff.RecordFailure(sessionID, kind);
|
|
591
829
|
return { Attempted: attempted, Recovered: false };
|
|
592
830
|
}
|
|
593
|
-
/** Drops backoff state for sessions that were not in the latest complete candidate set. */
|
|
594
|
-
pruneRecoveryBackoff(candidateKeys) {
|
|
595
|
-
for (const key of this.recoveryBackoff.keys()) {
|
|
596
|
-
if (!candidateKeys.has(key)) {
|
|
597
|
-
this.recoveryBackoff.delete(key);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
831
|
/**
|
|
602
832
|
* Page through every session matching `filter` with keyset (`AfterKey`) pagination and close each
|
|
603
833
|
* via {@link SessionManager.CloseSession}, stamping `closeReason` on every row it transitions.
|