harper 5.1.20 → 5.2.0-alpha.5
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/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +13 -8
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
|
@@ -67,23 +67,23 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
|
|
|
67
67
|
let lastRotationTime = Date.now();
|
|
68
68
|
hdbLogger.trace('Log rotate enabled, maxSize:', maxSize, 'interval:', interval);
|
|
69
69
|
const setIntervalId = setInterval(async () => {
|
|
70
|
+
// A missing/relocated active log file only invalidates the rotation checks below — retention cleanup
|
|
71
|
+
// must still run. So skip the individual check on ENOENT rather than returning from the whole tick.
|
|
70
72
|
if (maxBytes) {
|
|
71
73
|
let fileStats;
|
|
72
74
|
try {
|
|
73
75
|
fileStats = await fsProm.stat(logger.path);
|
|
74
76
|
} catch (err) {
|
|
75
|
-
// If the log file doesn't exist, skip rotation check
|
|
76
|
-
if (err.code
|
|
77
|
-
throw err;
|
|
77
|
+
// If the log file doesn't exist, skip the size-based rotation check
|
|
78
|
+
if (err.code !== 'ENOENT') throw err;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
if (fileStats.size >= maxBytes) {
|
|
81
|
+
if (fileStats && fileStats.size >= maxBytes) {
|
|
81
82
|
try {
|
|
82
|
-
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir);
|
|
83
|
+
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger);
|
|
83
84
|
} catch (err) {
|
|
84
85
|
// If the log file doesn't exist, skip rotation
|
|
85
|
-
if (err.code
|
|
86
|
-
throw err;
|
|
86
|
+
if (err.code !== 'ENOENT') throw err;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -92,12 +92,11 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
|
|
|
92
92
|
const minSinceLastRotate = Date.now() - lastRotationTime;
|
|
93
93
|
if (minSinceLastRotate >= maxInterval) {
|
|
94
94
|
try {
|
|
95
|
-
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir);
|
|
95
|
+
lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger);
|
|
96
96
|
lastRotationTime = Date.now();
|
|
97
97
|
} catch (err) {
|
|
98
98
|
// If the log file doesn't exist, skip rotation
|
|
99
|
-
if (err.code
|
|
100
|
-
throw err;
|
|
99
|
+
if (err.code !== 'ENOENT') throw err;
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
102
|
}
|
|
@@ -106,7 +105,14 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
|
|
|
106
105
|
// adjust retention time if there is a reclamation priority in place
|
|
107
106
|
const retentionMs = convertToMS(retention ?? '1M') / (1 + reclamationPriority);
|
|
108
107
|
reclamationPriority = 0; // reset it after use
|
|
109
|
-
|
|
108
|
+
let files;
|
|
109
|
+
try {
|
|
110
|
+
files = await fsProm.readdir(rotatedLogDir);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
// The rotated log dir may not exist yet (nothing rotated so far); nothing to clean up
|
|
113
|
+
if (err.code !== 'ENOENT') hdbLogger.error('Error reading rotated log directory', rotatedLogDir, err);
|
|
114
|
+
files = [];
|
|
115
|
+
}
|
|
110
116
|
for (const file of files) {
|
|
111
117
|
try {
|
|
112
118
|
const fileStats = await fsProm.stat(path.join(rotatedLogDir, file));
|
|
@@ -129,7 +135,7 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
|
|
|
129
135
|
};
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
async function moveLogFile(logPath: string, rotatedLogPath: string) {
|
|
138
|
+
async function moveLogFile(logPath: string, rotatedLogPath: string, logger?: any) {
|
|
133
139
|
const compress = envMgr.get(CONFIG_PARAMS.LOGGING_ROTATION_COMPRESS);
|
|
134
140
|
let fullRotateLogPath = path.join(
|
|
135
141
|
rotatedLogPath,
|
|
@@ -138,6 +144,12 @@ async function moveLogFile(logPath: string, rotatedLogPath: string) {
|
|
|
138
144
|
// Move log file to rotated log path first (if we crash
|
|
139
145
|
// during compression, we don't want to restart the compression with a new file)
|
|
140
146
|
await fsProm.rename(logPath, fullRotateLogPath);
|
|
147
|
+
// Close the rotating logger's own file descriptor now that the file has moved. This must be the
|
|
148
|
+
// logger's own closeLogFile (which resets its internal logFD), not the module-global one — otherwise
|
|
149
|
+
// the descriptor stays open on the moved (and, when compressing, subsequently unlinked) inode until the
|
|
150
|
+
// logger's safety timeout fires, pinning disk space and sending any writes in that window into the
|
|
151
|
+
// rotated/deleted file. Closing it here makes the next write reopen a fresh log file immediately.
|
|
152
|
+
(logger?.closeLogFile ?? hdbLogger.closeLogFile)();
|
|
141
153
|
if (compress) {
|
|
142
154
|
logPath = fullRotateLogPath;
|
|
143
155
|
fullRotateLogPath += '.gz';
|
|
@@ -145,8 +157,6 @@ async function moveLogFile(logPath: string, rotatedLogPath: string) {
|
|
|
145
157
|
await fsProm.unlink(logPath);
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
// Close old log file.
|
|
149
|
-
hdbLogger.closeLogFile();
|
|
150
160
|
// This notify log will create a new log file after the previous one has been rotated. It's important to keep this log as notify
|
|
151
161
|
hdbLogger.notify(`hdb.log rotated, old log moved to ${fullRotateLogPath}`);
|
|
152
162
|
return fullRotateLogPath;
|
|
@@ -9,6 +9,7 @@ import { once } from 'events';
|
|
|
9
9
|
import { getConfigPath } from '../../config/configUtils.ts';
|
|
10
10
|
import { handleHDBError, hdbErrors } from '../errors/hdbError.ts';
|
|
11
11
|
import { server } from '../../server/Server.ts';
|
|
12
|
+
import { StringDecoder } from 'string_decoder';
|
|
12
13
|
|
|
13
14
|
const DEFAULT_READ_LOG_LIMIT = 1000;
|
|
14
15
|
const ESTIMATED_AVERAGE_ENTRY_SIZE = 200;
|
|
@@ -33,8 +34,10 @@ async function readLog(request: any) {
|
|
|
33
34
|
true
|
|
34
35
|
);
|
|
35
36
|
}
|
|
36
|
-
//
|
|
37
|
-
|
|
37
|
+
// Start pulling logs from the other nodes now so it can be done in parallel. A live SSE
|
|
38
|
+
// tail (below) is local-only and never fans out, so skip replication on that path — the
|
|
39
|
+
// buffered read still aggregates the cluster for point-in-time queries.
|
|
40
|
+
let whenReplicatedResponse = isStreamingRequest(request) ? undefined : server.replication.replicateOperation(request);
|
|
38
41
|
|
|
39
42
|
const logPath = getConfigPath(hdbTerms.HDB_SETTINGS_NAMES.LOG_PATH_KEY);
|
|
40
43
|
const rawLogName = request.log_name === undefined ? hdbTerms.LOG_NAMES.HDB : request.log_name;
|
|
@@ -57,6 +60,26 @@ async function readLog(request: any) {
|
|
|
57
60
|
const start = request.start === undefined ? 0 : request.start;
|
|
58
61
|
const max = start + limit;
|
|
59
62
|
const filter = request.filter;
|
|
63
|
+
|
|
64
|
+
// SSE mode: the server attached a ProgressEmitter as `request.progress` (see
|
|
65
|
+
// serverHandlers.js) because the client sent `Accept: text/event-stream`. Instead of a
|
|
66
|
+
// one-shot array, stream the recent backlog and then tail new lines live until the client
|
|
67
|
+
// disconnects. Scoped to this node's log file; the buffered path above is what aggregates
|
|
68
|
+
// the cluster for point-in-time reads.
|
|
69
|
+
if (isStreamingRequest(request)) {
|
|
70
|
+
return streamLogTail(request.progress, {
|
|
71
|
+
readLogPath,
|
|
72
|
+
level,
|
|
73
|
+
levelDefined,
|
|
74
|
+
from,
|
|
75
|
+
fromDefined,
|
|
76
|
+
to,
|
|
77
|
+
toDefined,
|
|
78
|
+
limit,
|
|
79
|
+
filter,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
let fileStart = 0;
|
|
61
84
|
if (order === 'desc' && !from && !to) {
|
|
62
85
|
fileStart = Math.max(fs.statSync(readLogPath).size - (max + 5) * ESTIMATED_AVERAGE_ENTRY_SIZE, 0);
|
|
@@ -348,3 +371,301 @@ function insertAscending(value: any, result: any[]) {
|
|
|
348
371
|
|
|
349
372
|
result.splice(low, 0, value);
|
|
350
373
|
}
|
|
374
|
+
|
|
375
|
+
interface LogEntry {
|
|
376
|
+
timestamp: string;
|
|
377
|
+
thread: string;
|
|
378
|
+
level: string;
|
|
379
|
+
tags: string[];
|
|
380
|
+
message: string;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
interface TailFilterParams {
|
|
384
|
+
readLogPath: string;
|
|
385
|
+
level: string | undefined;
|
|
386
|
+
levelDefined: boolean;
|
|
387
|
+
from: Date | undefined;
|
|
388
|
+
fromDefined: boolean;
|
|
389
|
+
to: Date | undefined;
|
|
390
|
+
toDefined: boolean;
|
|
391
|
+
limit: number;
|
|
392
|
+
filter: string | undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// The marker that begins every log line — `TIMESTAMP [thread] [level]...: ` — matching the
|
|
396
|
+
// buffered reader's regex so the two paths parse identically.
|
|
397
|
+
const LOG_ENTRY_MARKER = /(?:^|\n)(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:[\d.]+Z) \[(.+?)]: /g;
|
|
398
|
+
// How often a live tail polls the log file for appended bytes. fs.watchFile is stat-poll
|
|
399
|
+
// based, which is far more robust across platforms and log rotation than fs.watch's rename
|
|
400
|
+
// events; sub-second latency is plenty for a human watching logs.
|
|
401
|
+
const TAIL_POLL_INTERVAL_MS = 250;
|
|
402
|
+
// Cap the live-tail backlog regardless of a caller-supplied `limit`, so an unbounded `limit`
|
|
403
|
+
// can neither seek-read a huge slice into memory nor drive an O(n·limit) eviction on connect.
|
|
404
|
+
// Deeper history is the buffered read_log's job; the tail is about what happens next.
|
|
405
|
+
const MAX_SSE_BACKLOG_ENTRIES = 1000;
|
|
406
|
+
// A transient delta-read failure is retried this many times (with this delay) before the tail
|
|
407
|
+
// gives up and emits a terminal error so the client can fall back to polling.
|
|
408
|
+
const TAIL_READ_MAX_RETRIES = 3;
|
|
409
|
+
const TAIL_READ_RETRY_MS = 250;
|
|
410
|
+
|
|
411
|
+
function isStreamingRequest(request: any): boolean {
|
|
412
|
+
return !!request.progress && typeof request.progress.emit === 'function';
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function makeLogEntry(timestamp: string, tagsString: string, message: string): LogEntry {
|
|
416
|
+
const tags = tagsString.split('] [');
|
|
417
|
+
const thread = tags[0];
|
|
418
|
+
const level = tags[1];
|
|
419
|
+
tags.splice(0, 2);
|
|
420
|
+
return { timestamp, thread, level, tags, message };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function matchesLogFilters(entry: LogEntry, params: TailFilterParams): boolean {
|
|
424
|
+
const { level, levelDefined, from, fromDefined, to, toDefined, filter } = params;
|
|
425
|
+
if (filter !== undefined) {
|
|
426
|
+
const hit = (['timestamp', 'thread', 'level', 'tags', 'message'] as const).some((attr) => {
|
|
427
|
+
const value = entry[attr];
|
|
428
|
+
if (Array.isArray(value)) return value.some((v) => v.includes(filter));
|
|
429
|
+
return typeof value === 'string' && value.includes(filter);
|
|
430
|
+
});
|
|
431
|
+
if (!hit) return false;
|
|
432
|
+
}
|
|
433
|
+
if (levelDefined && entry.level !== level) return false;
|
|
434
|
+
if (fromDefined || toDefined) {
|
|
435
|
+
const when = new Date(entry.timestamp);
|
|
436
|
+
if (fromDefined && when < (from as Date)) return false;
|
|
437
|
+
if (toDefined && when > (to as Date)) return false;
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Stateful incremental parser: fed appended chunks over time, it emits an entry only once the
|
|
443
|
+
// next marker delimits its (possibly multi-line) message. It never force-emits a pending
|
|
444
|
+
// entry — the same instance spans the backlog read and the live tail (see streamLogTail), so a
|
|
445
|
+
// line torn across the snapshot boundary stays pending and is completed by the tail's next
|
|
446
|
+
// read rather than emitted truncated with its continuation dropped. `reset()` clears state on
|
|
447
|
+
// log rotation.
|
|
448
|
+
function createIncrementalLogParser(onEntry: (entry: LogEntry) => void) {
|
|
449
|
+
let remaining = '';
|
|
450
|
+
let pending: { timestamp: string; tagsString: string } | undefined;
|
|
451
|
+
// One RegExp per parser instance. Each push runs `exec` to completion (until null), which
|
|
452
|
+
// resets lastIndex to 0; we also reset it explicitly at the top of push for robustness.
|
|
453
|
+
const reader = new RegExp(LOG_ENTRY_MARKER);
|
|
454
|
+
return {
|
|
455
|
+
push(text: string) {
|
|
456
|
+
const data = remaining + text;
|
|
457
|
+
reader.lastIndex = 0;
|
|
458
|
+
let lastPosition = 0;
|
|
459
|
+
let parsed;
|
|
460
|
+
while ((parsed = reader.exec(data))) {
|
|
461
|
+
if (pending) {
|
|
462
|
+
onEntry(makeLogEntry(pending.timestamp, pending.tagsString, data.slice(lastPosition, parsed.index)));
|
|
463
|
+
}
|
|
464
|
+
const [intro, timestamp, tagsString] = parsed;
|
|
465
|
+
pending = { timestamp, tagsString };
|
|
466
|
+
lastPosition = parsed.index + intro.length;
|
|
467
|
+
}
|
|
468
|
+
remaining = data.slice(lastPosition);
|
|
469
|
+
},
|
|
470
|
+
reset() {
|
|
471
|
+
pending = undefined;
|
|
472
|
+
remaining = '';
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Stream the local log over SSE: replay the recent (bounded) backlog, then tail newly-appended
|
|
479
|
+
* lines until the client disconnects (the emitter's `signal` aborts) — or, if reads fail past
|
|
480
|
+
* retry, emit a terminal `error` so the client falls back to polling instead of watching a
|
|
481
|
+
* silently-stalled "live" stream. Resolves when the tail ends so the SSE wrapper can close.
|
|
482
|
+
*
|
|
483
|
+
* A single parser and decoder span both the backlog read and the live tail with no flush at
|
|
484
|
+
* the seam, so a line (or multi-byte character) torn across the snapshot boundary stays pending
|
|
485
|
+
* and is completed by the tail's next read — never emitted truncated with its continuation
|
|
486
|
+
* dropped. (A consequence: the newest backlog line is delivered once the following line
|
|
487
|
+
* delimits it, matching how the tail delimits every entry.)
|
|
488
|
+
*/
|
|
489
|
+
function streamLogTail(progress: any, params: TailFilterParams): Promise<void> {
|
|
490
|
+
const { readLogPath, limit } = params;
|
|
491
|
+
const signal: AbortSignal | undefined = progress.signal;
|
|
492
|
+
const emit = (entry: LogEntry) => progress.emit('log', entry);
|
|
493
|
+
// The tail never shows more backlog than this, whatever `limit` asks for (see the constant).
|
|
494
|
+
const backlogLimit = Math.min(limit, MAX_SSE_BACKLOG_ENTRIES);
|
|
495
|
+
|
|
496
|
+
return new Promise<void>((resolve) => {
|
|
497
|
+
if (signal?.aborted) return resolve();
|
|
498
|
+
|
|
499
|
+
let offset = 0;
|
|
500
|
+
let pumping = false;
|
|
501
|
+
let pumpQueued = false;
|
|
502
|
+
let watching = false;
|
|
503
|
+
let finished = false;
|
|
504
|
+
let readFailures = 0;
|
|
505
|
+
// While replaying the backlog, matching entries are collected here (capped) rather than
|
|
506
|
+
// emitted; once the boundary is passed `backlogPhase` flips and entries emit live.
|
|
507
|
+
let backlogPhase = true;
|
|
508
|
+
const backlogEntries: LogEntry[] = [];
|
|
509
|
+
// One decoder + one parser for the whole session (backlog + tail): a multi-byte char (or a
|
|
510
|
+
// line) split across the boundary is held/pending and finished by the next read, not
|
|
511
|
+
// flushed as U+FFFD or truncated. Reset together only on rotation.
|
|
512
|
+
let decoder = new StringDecoder('utf8');
|
|
513
|
+
const parser = createIncrementalLogParser((entry) => {
|
|
514
|
+
if (!matchesLogFilters(entry, params)) return;
|
|
515
|
+
if (backlogPhase) {
|
|
516
|
+
backlogEntries.push(entry);
|
|
517
|
+
if (backlogEntries.length > backlogLimit) backlogEntries.shift();
|
|
518
|
+
} else {
|
|
519
|
+
emit(entry);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
function finish() {
|
|
524
|
+
if (finished) return;
|
|
525
|
+
finished = true;
|
|
526
|
+
if (watching) fs.unwatchFile(readLogPath, onChange);
|
|
527
|
+
resolve();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Wait `ms`, or resolve early on disconnect so a retry delay never outlives the connection.
|
|
531
|
+
const abortableDelay = (ms: number) =>
|
|
532
|
+
new Promise<void>((res) => {
|
|
533
|
+
if (signal?.aborted) return res();
|
|
534
|
+
const timer = setTimeout(() => {
|
|
535
|
+
signal?.removeEventListener('abort', onAbort);
|
|
536
|
+
res();
|
|
537
|
+
}, ms);
|
|
538
|
+
timer.unref?.();
|
|
539
|
+
const onAbort = () => {
|
|
540
|
+
clearTimeout(timer);
|
|
541
|
+
res();
|
|
542
|
+
};
|
|
543
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Stream [offset, end] inclusive through the session decoder + shared parser, decoding and
|
|
547
|
+
// pushing each fs chunk as it arrives (never materializing the whole delta) and advancing
|
|
548
|
+
// `offset` per chunk, so peak memory is one chunk and a mid-range error simply resumes from
|
|
549
|
+
// where it stopped. Yields to backpressure between chunks. Returns false if the read errored.
|
|
550
|
+
async function drainTo(end: number): Promise<boolean> {
|
|
551
|
+
const rs = fs.createReadStream(readLogPath, { start: offset, end });
|
|
552
|
+
try {
|
|
553
|
+
for await (const chunk of rs) {
|
|
554
|
+
if (signal?.aborted) {
|
|
555
|
+
rs.destroy();
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
parser.push(decoder.write(chunk as Buffer));
|
|
559
|
+
offset += (chunk as Buffer).length;
|
|
560
|
+
if (progress.paused) await progress.whenWritable();
|
|
561
|
+
}
|
|
562
|
+
return true;
|
|
563
|
+
} catch (err: any) {
|
|
564
|
+
hdbLogger.warn(
|
|
565
|
+
`read_log SSE tail: failed reading ${readLogPath} near offset ${offset}: ${err?.message ?? err}`
|
|
566
|
+
);
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Single-flight pump: drain all appended bytes into the parser. A change arriving while a
|
|
572
|
+
// pump runs sets `pumpQueued` so the loop runs once more, rather than being missed in the
|
|
573
|
+
// window between "caught up" and clearing `pumping`.
|
|
574
|
+
async function pump() {
|
|
575
|
+
if (pumping) {
|
|
576
|
+
pumpQueued = true;
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
pumping = true;
|
|
580
|
+
try {
|
|
581
|
+
do {
|
|
582
|
+
pumpQueued = false;
|
|
583
|
+
while (!signal?.aborted) {
|
|
584
|
+
let size: number;
|
|
585
|
+
try {
|
|
586
|
+
size = (await fs.promises.stat(readLogPath)).size;
|
|
587
|
+
} catch {
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
if (size < offset) {
|
|
591
|
+
// Truncation or rotation: restart from the new file and reset decode/parse state.
|
|
592
|
+
offset = 0;
|
|
593
|
+
decoder = new StringDecoder('utf8');
|
|
594
|
+
parser.reset();
|
|
595
|
+
}
|
|
596
|
+
if (size <= offset) break;
|
|
597
|
+
if (progress.paused) {
|
|
598
|
+
await progress.whenWritable();
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
const ok = await drainTo(size - 1);
|
|
602
|
+
if (signal?.aborted) break;
|
|
603
|
+
if (!ok) {
|
|
604
|
+
if (++readFailures > TAIL_READ_MAX_RETRIES) {
|
|
605
|
+
// Don't leave the client staring at a silently-stalled "live" stream: a
|
|
606
|
+
// terminal error is its cue to fall back to polling.
|
|
607
|
+
progress.emit('error', {
|
|
608
|
+
message: `read_log live tail could not read ${readLogPath} after ${TAIL_READ_MAX_RETRIES} retries`,
|
|
609
|
+
code: 'READ_LOG_TAIL_READ_ERROR',
|
|
610
|
+
});
|
|
611
|
+
finish();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
await abortableDelay(TAIL_READ_RETRY_MS);
|
|
615
|
+
continue; // resume from the advanced offset
|
|
616
|
+
}
|
|
617
|
+
readFailures = 0;
|
|
618
|
+
}
|
|
619
|
+
} while (pumpQueued && !signal?.aborted);
|
|
620
|
+
} finally {
|
|
621
|
+
pumping = false;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function onChange() {
|
|
626
|
+
void pump();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const startTail = () => {
|
|
630
|
+
// `offset` is already the backlog boundary, so the tail picks up strictly from there.
|
|
631
|
+
// Without a disconnect signal we can't safely tail forever; degrade to backlog-only so
|
|
632
|
+
// the client polls instead.
|
|
633
|
+
if (!signal || signal.aborted) return finish();
|
|
634
|
+
fs.watchFile(readLogPath, { interval: TAIL_POLL_INTERVAL_MS }, onChange);
|
|
635
|
+
watching = true;
|
|
636
|
+
signal.addEventListener('abort', finish, { once: true });
|
|
637
|
+
// Catch anything appended between the backlog snapshot and arming the watcher.
|
|
638
|
+
void pump();
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Replay the backlog through the shared parser/decoder, then tail from the same boundary.
|
|
642
|
+
(async () => {
|
|
643
|
+
let startSize = 0;
|
|
644
|
+
try {
|
|
645
|
+
startSize = fs.statSync(readLogPath).size;
|
|
646
|
+
} catch {
|
|
647
|
+
startSize = 0;
|
|
648
|
+
}
|
|
649
|
+
if (startSize > 0) {
|
|
650
|
+
// Bound the backlog read to roughly the last `backlogLimit` entries so a large log
|
|
651
|
+
// file can't be seek-read into memory all at once (mirrors the buffered path). A
|
|
652
|
+
// partial first line the seek lands in is dropped by the parser, as in that path.
|
|
653
|
+
offset = Math.max(startSize - (backlogLimit + 5) * ESTIMATED_AVERAGE_ENTRY_SIZE, 0);
|
|
654
|
+
await drainTo(startSize - 1); // collects into backlogEntries; read errors are logged, best-effort
|
|
655
|
+
}
|
|
656
|
+
// Hand off to the live tail: flip to emit mode and deliver the collected backlog. The
|
|
657
|
+
// parser's pending entry (the newest, possibly boundary-torn line) is deliberately left
|
|
658
|
+
// pending — the tail finishes it — so nothing is emitted truncated.
|
|
659
|
+
backlogPhase = false;
|
|
660
|
+
for (const entry of backlogEntries) {
|
|
661
|
+
if (signal?.aborted) break;
|
|
662
|
+
emit(entry);
|
|
663
|
+
if (progress.paused) await progress.whenWritable();
|
|
664
|
+
}
|
|
665
|
+
startTail();
|
|
666
|
+
})().catch((err) => {
|
|
667
|
+
hdbLogger.warn(`read_log SSE tail: backlog replay failed for ${readLogPath}: ${err?.message ?? err}`);
|
|
668
|
+
startTail();
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
}
|
|
@@ -76,10 +76,44 @@ export const OPERATION_PERMISSION_GROUPS = {
|
|
|
76
76
|
OPERATIONS_ENUM.CSV_URL_LOAD,
|
|
77
77
|
OPERATIONS_ENUM.IMPORT_FROM_S3,
|
|
78
78
|
],
|
|
79
|
+
/**
|
|
80
|
+
* Drive the built-in agent (#626): send prompts, read/list sessions, and approve/cancel
|
|
81
|
+
* runs. Grants a scoped, non-super_user "delegation" role access to the agent without full
|
|
82
|
+
* super_user (all agent ops are super_user by default). Deliberately EXCLUDES set_agent_config
|
|
83
|
+
* — changing the agent's model/approval policy stays an operator (super_user) action.
|
|
84
|
+
*/
|
|
85
|
+
agent: [
|
|
86
|
+
OPERATIONS_ENUM.AGENT_PROMPT,
|
|
87
|
+
OPERATIONS_ENUM.GET_AGENT_SESSION,
|
|
88
|
+
OPERATIONS_ENUM.LIST_AGENT_SESSIONS,
|
|
89
|
+
OPERATIONS_ENUM.CANCEL_AGENT_RUN,
|
|
90
|
+
OPERATIONS_ENUM.APPROVE_AGENT_ACTION,
|
|
91
|
+
],
|
|
79
92
|
} as const;
|
|
80
93
|
|
|
81
94
|
const validOps: Set<string> = new Set(Object.values(OPERATIONS_ENUM));
|
|
82
95
|
const validGroups: Set<string> = new Set(Object.keys(OPERATION_PERMISSION_GROUPS));
|
|
96
|
+
// Operations registered dynamically at runtime (server.registerOperation with a declared
|
|
97
|
+
// permission) whose names may fall outside OPERATIONS_ENUM. Tracked so they're grantable in a
|
|
98
|
+
// role's `operations` allowlist (add_role/alter_role/impersonation validate against this set too).
|
|
99
|
+
const dynamicallyRegisteredOps: Set<string> = new Set();
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Mark a dynamically-registered operation name as a valid target for role `operations` grants.
|
|
103
|
+
* Called by registerOperationPermission so a component op declaring a permission can actually be
|
|
104
|
+
* granted (not just enforced). Enum-backed op names are already valid; this covers custom names.
|
|
105
|
+
*/
|
|
106
|
+
export function registerGrantableOperation(name: string): void {
|
|
107
|
+
dynamicallyRegisteredOps.add(name);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Remove a dynamically-registered grantable operation name. Mirrors registerGrantableOperation;
|
|
112
|
+
* primarily for tests that register throwaway ops and must not leak them into the global set.
|
|
113
|
+
*/
|
|
114
|
+
export function unregisterGrantableOperation(name: string): void {
|
|
115
|
+
dynamicallyRegisteredOps.delete(name);
|
|
116
|
+
}
|
|
83
117
|
|
|
84
118
|
/**
|
|
85
119
|
* Validates that every entry in an operations array is a known operation name or group name.
|
|
@@ -87,7 +121,7 @@ const validGroups: Set<string> = new Set(Object.keys(OPERATION_PERMISSION_GROUPS
|
|
|
87
121
|
*/
|
|
88
122
|
export function validateOperations(operations: readonly unknown[]): string | null {
|
|
89
123
|
for (const op of operations) {
|
|
90
|
-
if (typeof op !== 'string' || (!validOps.has(op) && !validGroups.has(op))) {
|
|
124
|
+
if (typeof op !== 'string' || (!validOps.has(op) && !validGroups.has(op) && !dynamicallyRegisteredOps.has(op))) {
|
|
91
125
|
return String(op);
|
|
92
126
|
}
|
|
93
127
|
}
|
|
@@ -25,7 +25,11 @@ import readLog from '../utility/logging/readLog.ts';
|
|
|
25
25
|
import * as commonUtils from './common_utils.ts';
|
|
26
26
|
import * as restart from '../bin/restart.ts';
|
|
27
27
|
import * as terms from './hdbTerms.ts';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
expandOperationsPerms,
|
|
30
|
+
registerGrantableOperation,
|
|
31
|
+
unregisterGrantableOperation,
|
|
32
|
+
} from './operationPermissions.ts';
|
|
29
33
|
import * as permsTranslator from '../security/permissionsTranslator.js';
|
|
30
34
|
import { systemInformation } from '../utility/environment/systemInformation.ts';
|
|
31
35
|
import * as tokenAuthentication from '../security/tokenAuthentication.ts';
|
|
@@ -41,6 +45,7 @@ import { handleHDBError, hdbErrors } from '../utility/errors/hdbError.ts';
|
|
|
41
45
|
|
|
42
46
|
import * as regDeprecated from '../resources/registrationDeprecated.ts';
|
|
43
47
|
import * as deploymentOperations from '../components/deploymentOperations.ts';
|
|
48
|
+
import * as secretOperations from '../components/secretOperations.ts';
|
|
44
49
|
|
|
45
50
|
const requiredPermissions = new Map();
|
|
46
51
|
const DELETE_PERM = 'delete';
|
|
@@ -108,6 +113,33 @@ class permission {
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Register an authorization entry for a dynamically-registered operation (via
|
|
118
|
+
* server.registerOperation), so it flows through verifyPerms and the role `operations`
|
|
119
|
+
* allowlist exactly like a built-in op — instead of hand-rolling an inline super_user check.
|
|
120
|
+
* Keyed by the snake_case API operation name (which registerOperation also forces onto the
|
|
121
|
+
* handler's `.name`, the key verifyPerms looks up). SU-only by default.
|
|
122
|
+
*/
|
|
123
|
+
export function registerOperationPermission(
|
|
124
|
+
apiName: string,
|
|
125
|
+
{ requiresSu = false, perms = [] }: { requiresSu?: boolean; perms?: string[] } = {}
|
|
126
|
+
) {
|
|
127
|
+
requiredPermissions.set(apiName, new (permission as any)(requiresSu, perms, apiName));
|
|
128
|
+
// Also make the op grantable in a role's `operations` allowlist (validateOperations), so a
|
|
129
|
+
// declared permission can be both enforced AND granted — not just enforced.
|
|
130
|
+
registerGrantableOperation(apiName);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Remove an authorization entry registered via registerOperationPermission (both the verifyPerms
|
|
135
|
+
* entry and the grantable-op mark). Mirrors registerOperationPermission; primarily for tests that
|
|
136
|
+
* register throwaway ops and must not leak them into the process-global registries.
|
|
137
|
+
*/
|
|
138
|
+
export function unregisterOperationPermission(apiName: string) {
|
|
139
|
+
requiredPermissions.delete(apiName);
|
|
140
|
+
unregisterGrantableOperation(apiName);
|
|
141
|
+
}
|
|
142
|
+
|
|
111
143
|
requiredPermissions.set(write.insert.name, new (permission as any)(false, [INSERT_PERM], terms.OPERATIONS_ENUM.INSERT));
|
|
112
144
|
requiredPermissions.set(write.update.name, new (permission as any)(false, [UPDATE_PERM], terms.OPERATIONS_ENUM.UPDATE));
|
|
113
145
|
requiredPermissions.set(
|
|
@@ -231,6 +263,18 @@ requiredPermissions.set(
|
|
|
231
263
|
new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_COMPONENT_FILE)
|
|
232
264
|
);
|
|
233
265
|
requiredPermissions.set(functionsOperations.setComponentFile.name, new (permission as any)(true, []));
|
|
266
|
+
requiredPermissions.set(
|
|
267
|
+
functionsOperations.getEnvKeys.name,
|
|
268
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_ENV_KEYS)
|
|
269
|
+
);
|
|
270
|
+
requiredPermissions.set(
|
|
271
|
+
functionsOperations.setEnvValue.name,
|
|
272
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.SET_ENV_VALUE)
|
|
273
|
+
);
|
|
274
|
+
requiredPermissions.set(
|
|
275
|
+
functionsOperations.deleteEnvValue.name,
|
|
276
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.DELETE_ENV_VALUE)
|
|
277
|
+
);
|
|
234
278
|
requiredPermissions.set(functionsOperations.dropComponent.name, new (permission as any)(true, []));
|
|
235
279
|
requiredPermissions.set(
|
|
236
280
|
functionsOperations.getCustomFunction.name,
|
|
@@ -251,6 +295,33 @@ requiredPermissions.set(
|
|
|
251
295
|
new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_DEPLOYMENT)
|
|
252
296
|
);
|
|
253
297
|
|
|
298
|
+
// Secrets-store operations. All SU-only; the handlers ALSO enforce super_user directly, so these
|
|
299
|
+
// cannot be delegated through a role's `operations` allowlist (gate-2 bypass below).
|
|
300
|
+
requiredPermissions.set(
|
|
301
|
+
secretOperations.setSecret.name,
|
|
302
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.SET_SECRET)
|
|
303
|
+
);
|
|
304
|
+
requiredPermissions.set(
|
|
305
|
+
secretOperations.grantSecret.name,
|
|
306
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.GRANT_SECRET)
|
|
307
|
+
);
|
|
308
|
+
requiredPermissions.set(
|
|
309
|
+
secretOperations.revokeSecret.name,
|
|
310
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.REVOKE_SECRET)
|
|
311
|
+
);
|
|
312
|
+
requiredPermissions.set(
|
|
313
|
+
secretOperations.listSecrets.name,
|
|
314
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.LIST_SECRETS)
|
|
315
|
+
);
|
|
316
|
+
requiredPermissions.set(
|
|
317
|
+
secretOperations.deleteSecret.name,
|
|
318
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.DELETE_SECRET)
|
|
319
|
+
);
|
|
320
|
+
requiredPermissions.set(
|
|
321
|
+
secretOperations.getSecretsPublicKey.name,
|
|
322
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_SECRETS_PUBLIC_KEY)
|
|
323
|
+
);
|
|
324
|
+
|
|
254
325
|
//Below are functions that are currently open to all roles
|
|
255
326
|
requiredPermissions.set(regDeprecated.getRegistrationInfo.name, new (permission as any)(false, []));
|
|
256
327
|
requiredPermissions.set(user.userInfo.name, new (permission as any)(false, [], terms.OPERATIONS_ENUM.USER_INFO));
|
|
@@ -299,6 +370,8 @@ module.exports = {
|
|
|
299
370
|
verifyPerms,
|
|
300
371
|
verifyPermsAST,
|
|
301
372
|
verifyBulkLoadAttributePerms,
|
|
373
|
+
registerOperationPermission,
|
|
374
|
+
unregisterOperationPermission,
|
|
302
375
|
};
|
|
303
376
|
|
|
304
377
|
/**
|