@harperfast/harper 5.2.10 → 5.3.0-alpha.1
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/mcpTools.ts +1 -1
- package/agent/session.ts +25 -14
- package/bin/cliOperations.ts +46 -9
- package/bin/copyDb.ts +282 -59
- package/bin/deploySetup.ts +16 -5
- package/bin/harper.ts +1 -1
- package/bin/help.ts +4 -1
- package/bin/lite.ts +4 -1
- package/bin/restart.ts +59 -3
- package/bin/run.ts +6 -11
- package/bin/upgrade.js +7 -3
- package/bin/workloadIdentity.ts +119 -0
- package/components/Application.ts +2414 -242
- package/components/ApplicationScope.ts +8 -0
- package/components/EntryHandler.ts +59 -39
- package/components/OptionsWatcher.ts +150 -74
- package/components/RuntimeModuleTracker.ts +38 -7
- package/components/Scope.ts +37 -15
- package/components/awaitRestart.ts +84 -0
- package/components/componentLoader.ts +340 -30
- package/components/componentPreparationLock.ts +16 -5
- package/components/mcp/adapters/harperHttp.ts +4 -0
- package/components/mcp/listChanged.ts +4 -0
- package/components/mcp/toolRegistry.ts +2 -0
- package/components/mcp/tools/operations.ts +9 -0
- package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
- package/components/operations.js +270 -78
- package/components/operationsValidation.js +49 -1
- package/components/status/ComponentStatusRegistry.ts +59 -0
- package/config/RootConfigWatcher.ts +80 -34
- package/config/configUtils.ts +291 -34
- package/config/harperConfigEnvVars.ts +170 -27
- package/config-root.schema.json +29 -0
- package/dataLayer/blobBackup.ts +160 -50
- package/dataLayer/delete.ts +6 -1
- package/dataLayer/harperBridge/ResourceBridge.ts +52 -8
- package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dataLayer/hdbInfoController.ts +34 -1
- package/dataLayer/insert.ts +44 -1
- package/dataLayer/rocksdbBackup.ts +53 -10
- package/dataLayer/schema.ts +11 -1
- package/dataLayer/schemaDescribe.ts +8 -1
- package/dist/agent/mcpTools.js +1 -1
- package/dist/agent/mcpTools.js.map +1 -1
- package/dist/agent/session.d.ts +22 -0
- package/dist/agent/session.js +26 -15
- package/dist/agent/session.js.map +1 -1
- package/dist/bin/cliOperations.js +48 -9
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/copyDb.d.ts +12 -1
- package/dist/bin/copyDb.js +248 -60
- package/dist/bin/copyDb.js.map +1 -1
- package/dist/bin/deploySetup.d.ts +2 -0
- package/dist/bin/deploySetup.js +11 -3
- package/dist/bin/deploySetup.js.map +1 -1
- package/dist/bin/harper.js +1 -1
- package/dist/bin/harper.js.map +1 -1
- package/dist/bin/help.js +4 -1
- package/dist/bin/help.js.map +1 -1
- package/dist/bin/lite.js +4 -1
- package/dist/bin/lite.js.map +1 -1
- package/dist/bin/restart.js +54 -5
- package/dist/bin/restart.js.map +1 -1
- package/dist/bin/run.js +4 -10
- package/dist/bin/run.js.map +1 -1
- package/dist/bin/upgrade.js +4 -3
- package/dist/bin/upgrade.js.map +1 -1
- package/dist/bin/workloadIdentity.d.ts +18 -0
- package/dist/bin/workloadIdentity.js +100 -0
- package/dist/bin/workloadIdentity.js.map +1 -0
- package/dist/components/Application.d.ts +139 -16
- package/dist/components/Application.js +2215 -267
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +8 -0
- package/dist/components/ApplicationScope.js +7 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/EntryHandler.js +26 -10
- package/dist/components/EntryHandler.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +1 -0
- package/dist/components/OptionsWatcher.js +141 -74
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/RuntimeModuleTracker.js +40 -6
- package/dist/components/RuntimeModuleTracker.js.map +1 -1
- package/dist/components/Scope.js +38 -13
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/awaitRestart.d.ts +33 -0
- package/dist/components/awaitRestart.js +61 -0
- package/dist/components/awaitRestart.js.map +1 -0
- package/dist/components/componentLoader.d.ts +38 -1
- package/dist/components/componentLoader.js +279 -22
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentPreparationLock.d.ts +5 -0
- package/dist/components/componentPreparationLock.js +14 -6
- package/dist/components/componentPreparationLock.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.js +4 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/listChanged.js +5 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +1 -0
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +5 -0
- package/dist/components/mcp/tools/operations.js +9 -0
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/operations.js +231 -77
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +49 -1
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
- package/dist/components/status/ComponentStatusRegistry.js +63 -0
- package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
- package/dist/config/RootConfigWatcher.d.ts +7 -1
- package/dist/config/RootConfigWatcher.js +64 -27
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configUtils.d.ts +9 -1
- package/dist/config/configUtils.js +254 -33
- package/dist/config/configUtils.js.map +1 -1
- package/dist/config/harperConfigEnvVars.d.ts +16 -0
- package/dist/config/harperConfigEnvVars.js +162 -25
- package/dist/config/harperConfigEnvVars.js.map +1 -1
- package/dist/dataLayer/blobBackup.d.ts +49 -20
- package/dist/dataLayer/blobBackup.js +139 -50
- package/dist/dataLayer/blobBackup.js.map +1 -1
- package/dist/dataLayer/delete.js +1 -1
- package/dist/dataLayer/delete.js.map +1 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
- package/dist/dataLayer/harperBridge/ResourceBridge.js +51 -10
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
- package/dist/dataLayer/hdbInfoController.d.ts +10 -0
- package/dist/dataLayer/hdbInfoController.js +30 -1
- package/dist/dataLayer/hdbInfoController.js.map +1 -1
- package/dist/dataLayer/insert.d.ts +9 -1
- package/dist/dataLayer/insert.js +30 -0
- package/dist/dataLayer/insert.js.map +1 -1
- package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
- package/dist/dataLayer/rocksdbBackup.js +45 -8
- package/dist/dataLayer/rocksdbBackup.js.map +1 -1
- package/dist/dataLayer/schema.js +8 -0
- package/dist/dataLayer/schema.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +8 -1
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +52 -0
- package/dist/resources/DatabaseTransaction.d.ts +55 -3
- package/dist/resources/DatabaseTransaction.js +460 -134
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.js +22 -4
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
- package/dist/resources/PrimaryRocksDatabase.js +30 -2
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +20 -0
- package/dist/resources/RecordEncoder.js +94 -9
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +2 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.js +20 -11
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +20 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +6 -1
- package/dist/resources/RocksIndexStore.js +24 -10
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +14 -1
- package/dist/resources/RocksTransactionLogStore.js +57 -17
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +112 -8
- package/dist/resources/Table.js +1071 -197
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/auditStore.d.ts +11 -2
- package/dist/resources/auditStore.js +183 -18
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/blob.d.ts +129 -9
- package/dist/resources/blob.js +992 -114
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/branchDatabase.d.ts +48 -0
- package/dist/resources/branchDatabase.js +892 -0
- package/dist/resources/branchDatabase.js.map +1 -0
- package/dist/resources/crdt.js +50 -12
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +150 -9
- package/dist/resources/databases.js +1247 -525
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/defineTable.d.ts +10 -2
- package/dist/resources/defineTable.js +9 -1
- package/dist/resources/defineTable.js.map +1 -1
- package/dist/resources/graphql.d.ts +1 -1
- package/dist/resources/graphql.js +50 -15
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +1 -0
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +6 -2
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/longLivedTransactions.d.ts +71 -0
- package/dist/resources/longLivedTransactions.js +358 -0
- package/dist/resources/longLivedTransactions.js.map +1 -0
- package/dist/resources/models/backendRegistry.d.ts +26 -0
- package/dist/resources/models/backendRegistry.js +60 -2
- package/dist/resources/models/backendRegistry.js.map +1 -1
- package/dist/resources/models/bootstrap.d.ts +33 -1
- package/dist/resources/models/bootstrap.js +416 -31
- package/dist/resources/models/bootstrap.js.map +1 -1
- package/dist/resources/recordLock.d.ts +80 -0
- package/dist/resources/recordLock.js +184 -0
- package/dist/resources/recordLock.js.map +1 -0
- package/dist/resources/replayLogs.d.ts +14 -1
- package/dist/resources/replayLogs.js +152 -24
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/replayLogsGuards.d.ts +94 -7
- package/dist/resources/replayLogsGuards.js +111 -7
- package/dist/resources/replayLogsGuards.js.map +1 -1
- package/dist/resources/search.js +158 -32
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/transactionBroadcast.d.ts +1 -1
- package/dist/resources/transactionBroadcast.js +2 -2
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +61 -30
- package/dist/security/auth.js.map +1 -1
- package/dist/security/authn/oidc/claims.d.ts +22 -0
- package/dist/security/authn/oidc/claims.js +71 -0
- package/dist/security/authn/oidc/claims.js.map +1 -0
- package/dist/security/authn/oidc/identityToken.d.ts +27 -0
- package/dist/security/authn/oidc/identityToken.js +111 -0
- package/dist/security/authn/oidc/identityToken.js.map +1 -0
- package/dist/security/authn/oidc/jwks.d.ts +25 -0
- package/dist/security/authn/oidc/jwks.js +261 -0
- package/dist/security/authn/oidc/jwks.js.map +1 -0
- package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
- package/dist/security/authn/oidc/providers/generic.js +34 -0
- package/dist/security/authn/oidc/providers/generic.js.map +1 -0
- package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
- package/dist/security/authn/oidc/providers/githubActions.js +129 -0
- package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
- package/dist/security/authn/oidc/providers/index.d.ts +37 -0
- package/dist/security/authn/oidc/providers/index.js +24 -0
- package/dist/security/authn/oidc/providers/index.js.map +1 -0
- package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
- package/dist/security/authn/oidc/tokenExchange.js +306 -0
- package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
- package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
- package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
- package/dist/security/authn/oidc/types.d.ts +38 -0
- package/dist/security/authn/oidc/types.js +6 -0
- package/dist/security/authn/oidc/types.js.map +1 -0
- package/dist/security/certificateVerification/index.js +40 -11
- package/dist/security/certificateVerification/index.js.map +1 -1
- package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
- package/dist/security/certificateVerification/trustedIssuers.js +79 -0
- package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
- package/dist/security/certificateVerification/types.d.ts +1 -0
- package/dist/security/credentialProvenance.d.ts +35 -0
- package/dist/security/credentialProvenance.js +51 -0
- package/dist/security/credentialProvenance.js.map +1 -0
- package/dist/security/credentialRejection.d.ts +4 -0
- package/dist/security/credentialRejection.js +24 -0
- package/dist/security/credentialRejection.js.map +1 -0
- package/dist/security/deferredAuthentication.d.ts +36 -0
- package/dist/security/deferredAuthentication.js +70 -0
- package/dist/security/deferredAuthentication.js.map +1 -0
- package/dist/security/impersonation.d.ts +21 -0
- package/dist/security/impersonation.js +108 -9
- package/dist/security/impersonation.js.map +1 -1
- package/dist/security/jsLoader.d.ts +6 -0
- package/dist/security/jsLoader.js +75 -15
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/keys.js +301 -71
- package/dist/security/keys.js.map +1 -1
- package/dist/security/operationScope.d.ts +21 -0
- package/dist/security/operationScope.js +36 -0
- package/dist/security/operationScope.js.map +1 -0
- package/dist/security/permissionsTranslator.js +21 -0
- package/dist/security/permissionsTranslator.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +19 -1
- package/dist/security/tokenAuthentication.js +191 -10
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/security/user.js +4 -3
- package/dist/security/user.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
- package/dist/server/DurableSubscriptionsSession.js +67 -10
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +73 -0
- package/dist/server/REST.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -0
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +11 -0
- package/dist/server/http.js +88 -15
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +8 -1
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/jobs/jobProcess.js +6 -2
- package/dist/server/jobs/jobProcess.js.map +1 -1
- package/dist/server/jobs/jobs.js +4 -1
- package/dist/server/jobs/jobs.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +26 -4
- package/dist/server/liveSubscriptionAuth.js +105 -39
- package/dist/server/liveSubscriptionAuth.js.map +1 -1
- package/dist/server/loadRootComponents.js +49 -10
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/mqtt.d.ts +2 -0
- package/dist/server/mqtt.js +165 -30
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/nodeName.d.ts +2 -0
- package/dist/server/nodeName.js +107 -23
- package/dist/server/nodeName.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +25 -0
- package/dist/server/serverHelpers/Headers.js +139 -1
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +9 -0
- package/dist/server/serverHelpers/contentTypes.js +32 -25
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
- package/dist/server/serverHelpers/deployValidationState.js +9 -19
- package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
- package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
- package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
- package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
- package/dist/server/serverHelpers/registeredOperations.js +74 -21
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
- package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
- package/dist/server/serverHelpers/requestSanitization.js +20 -0
- package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +6 -3
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
- package/dist/server/serverHelpers/serverUtilities.js +90 -20
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
- package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
- package/dist/server/serverHelpers/uwsServer.js +19 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/static.js +24 -28
- package/dist/server/static.js.map +1 -1
- package/dist/server/storageReclamation.d.ts +5 -0
- package/dist/server/storageReclamation.js +16 -0
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/itc.d.ts +7 -2
- package/dist/server/threads/itc.js +5 -1
- package/dist/server/threads/itc.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +26 -4
- package/dist/server/threads/manageThreads.js +517 -72
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +89 -26
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadHeapMemory.d.ts +2 -0
- package/dist/server/threads/threadHeapMemory.js +31 -0
- package/dist/server/threads/threadHeapMemory.js.map +1 -0
- package/dist/server/threads/threadServer.js +46 -15
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/sqlEngine/config.d.ts +1 -3
- package/dist/sqlEngine/config.js +19 -16
- package/dist/sqlEngine/config.js.map +1 -1
- package/dist/sqlTranslator/index.d.ts +1 -1
- package/dist/sqlTranslator/index.js +30 -7
- package/dist/sqlTranslator/index.js.map +1 -1
- package/dist/upgrade/directives/5-3-0.d.ts +7 -0
- package/dist/upgrade/directives/5-3-0.js +148 -0
- package/dist/upgrade/directives/5-3-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/OperationFunctionCaller.js +2 -1
- package/dist/utility/OperationFunctionCaller.js.map +1 -1
- package/dist/utility/common_utils.d.ts +16 -0
- package/dist/utility/common_utils.js +32 -6
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/componentNames.d.ts +8 -0
- package/dist/utility/componentNames.js +12 -1
- package/dist/utility/componentNames.js.map +1 -1
- package/dist/utility/environment/environmentManager.js +3 -6
- package/dist/utility/environment/environmentManager.js.map +1 -1
- package/dist/utility/environment/systemInformation.d.ts +1 -0
- package/dist/utility/environment/systemInformation.js +1 -0
- package/dist/utility/environment/systemInformation.js.map +1 -1
- package/dist/utility/errors/commonErrors.d.ts +2 -0
- package/dist/utility/errors/commonErrors.js +2 -0
- package/dist/utility/errors/commonErrors.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +17 -0
- package/dist/utility/errors/hdbError.js +30 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +16 -0
- package/dist/utility/hdbTerms.js +18 -2
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +2 -0
- package/dist/utility/logging/harper_logger.js +91 -14
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +76 -49
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/nodeIdentity.d.ts +9 -0
- package/dist/utility/nodeIdentity.js +58 -0
- package/dist/utility/nodeIdentity.js.map +1 -0
- package/dist/utility/npmUtilities.js +9 -7
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +3 -1
- package/dist/utility/operationPermissions.js +16 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +10 -7
- package/dist/utility/operation_authorization.js +212 -41
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/watchPath.d.ts +29 -0
- package/dist/utility/watchPath.js +68 -0
- package/dist/utility/watchPath.js.map +1 -0
- package/dist/utility/watcherFallback.d.ts +86 -0
- package/dist/utility/watcherFallback.js +278 -1
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.d.ts +12 -0
- package/dist/validation/configValidator.js +199 -72
- package/dist/validation/configValidator.js.map +1 -1
- package/dist/validation/installValidator.js +12 -0
- package/dist/validation/installValidator.js.map +1 -1
- package/dist/validation/validationWrapper.d.ts +11 -0
- package/dist/validation/validationWrapper.js +16 -3
- package/dist/validation/validationWrapper.js.map +1 -1
- package/index.ts +2 -0
- package/json/systemSchema.json +52 -0
- package/npm-shrinkwrap.json +235 -233
- package/package.json +8 -7
- package/resources/DESIGN.md +114 -52
- package/resources/DatabaseTransaction.ts +522 -130
- package/resources/LMDBTransaction.ts +26 -4
- package/resources/PrimaryRocksDatabase.ts +29 -3
- package/resources/RecordEncoder.ts +103 -8
- package/resources/RequestTarget.ts +2 -0
- package/resources/Resource.ts +17 -9
- package/resources/ResourceInterface.ts +23 -0
- package/resources/RocksIndexStore.ts +30 -10
- package/resources/RocksTransactionLogStore.ts +78 -21
- package/resources/Table.ts +1107 -164
- package/resources/auditStore.ts +187 -21
- package/resources/blob.ts +1029 -110
- package/resources/branchDatabase.ts +941 -0
- package/resources/crdt.ts +76 -12
- package/resources/databases.ts +1354 -522
- package/resources/defineTable.ts +18 -2
- package/resources/graphql.ts +70 -16
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +6 -2
- package/resources/longLivedTransactions.ts +360 -0
- package/resources/models/backendRegistry.ts +84 -2
- package/resources/models/bootstrap.ts +473 -28
- package/resources/recordLock.ts +253 -0
- package/resources/replayLogs.ts +147 -26
- package/resources/replayLogsGuards.ts +171 -8
- package/resources/search.ts +154 -32
- package/resources/transactionBroadcast.ts +3 -3
- package/security/auth.ts +68 -29
- package/security/authn/oidc/claims.ts +72 -0
- package/security/authn/oidc/identityToken.ts +129 -0
- package/security/authn/oidc/jwks.ts +260 -0
- package/security/authn/oidc/providers/generic.ts +40 -0
- package/security/authn/oidc/providers/githubActions.ts +137 -0
- package/security/authn/oidc/providers/index.ts +52 -0
- package/security/authn/oidc/tokenExchange.ts +300 -0
- package/security/authn/oidc/trustPolicyOperations.ts +343 -0
- package/security/authn/oidc/types.ts +41 -0
- package/security/certificateVerification/index.ts +54 -13
- package/security/certificateVerification/trustedIssuers.ts +76 -0
- package/security/certificateVerification/types.ts +1 -0
- package/security/credentialProvenance.ts +47 -0
- package/security/credentialRejection.ts +22 -0
- package/security/deferredAuthentication.ts +71 -0
- package/security/impersonation.ts +117 -12
- package/security/jsLoader.ts +81 -18
- package/security/keys.ts +298 -72
- package/security/operationScope.ts +33 -0
- package/security/permissionsTranslator.js +23 -0
- package/security/tokenAuthentication.ts +233 -12
- package/security/user.ts +4 -3
- package/server/DESIGN.md +183 -16
- package/server/DurableSubscriptionsSession.ts +71 -11
- package/server/REST.ts +79 -1
- package/server/graphqlQuerying.ts +4 -0
- package/server/http.ts +99 -18
- package/server/itc/serverHandlers.js +8 -1
- package/server/jobs/jobProcess.ts +8 -2
- package/server/jobs/jobs.ts +4 -1
- package/server/liveSubscriptionAuth.ts +129 -46
- package/server/loadRootComponents.js +50 -8
- package/server/mqtt.ts +179 -38
- package/server/nodeName.ts +103 -21
- package/server/serverHelpers/Headers.ts +131 -0
- package/server/serverHelpers/contentTypes.ts +29 -21
- package/server/serverHelpers/deployValidationState.ts +24 -13
- package/server/serverHelpers/operationAuthorizationState.ts +34 -3
- package/server/serverHelpers/registeredOperations.ts +79 -22
- package/server/serverHelpers/requestSanitization.ts +15 -0
- package/server/serverHelpers/serverHandlers.js +6 -3
- package/server/serverHelpers/serverUtilities.ts +104 -31
- package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
- package/server/serverHelpers/uwsServer.ts +17 -2
- package/server/static.ts +23 -29
- package/server/storageReclamation.ts +13 -0
- package/server/threads/itc.js +11 -1
- package/server/threads/manageThreads.js +526 -63
- package/server/threads/socketRouter.ts +81 -26
- package/server/threads/threadHeapMemory.ts +26 -0
- package/server/threads/threadServer.js +43 -15
- package/sqlTranslator/index.ts +31 -8
- package/studio/web/assets/{Chat-Br06zdMA.js → Chat-BnCBegQz.js} +1 -1
- package/studio/web/assets/{FloatingChat-BWImX5fA.js → FloatingChat-CoDW1ySS.js} +3 -3
- package/studio/web/assets/{apiToken-DN0nmDsq.js → apiToken-Bwk5BLXW.js} +1 -1
- package/studio/web/assets/{applications-kSxVoyeU.js → applications-DHxGi7JH.js} +1 -1
- package/studio/web/assets/{cssMode-C1JeufH5.js → cssMode-s0cWI-_M.js} +1 -1
- package/studio/web/assets/{editor-qoo9CrGO.js → editor-DNcRHK54.js} +1 -1
- package/studio/web/assets/{html-Dt4IIy04.js → html-Bdssedlg.js} +1 -1
- package/studio/web/assets/{htmlMode-DXgKKr4C.js → htmlMode-CoDlJ3fw.js} +1 -1
- package/studio/web/assets/{index-6onkYFOG.js → index-D6sxmFLR.js} +5 -5
- package/studio/web/assets/{index.lazy-BrCFnpNJ.js → index.lazy-tmU5BS8s.js} +1 -1
- package/studio/web/assets/{javascript-DNCQGUBc.js → javascript-B8meVSTH.js} +1 -1
- package/studio/web/assets/{jsonMode-CR6HWruP.js → jsonMode-DpIPd35T.js} +1 -1
- package/studio/web/assets/{languageServices-BM4fI4rS.js → languageServices-C_5FMJzQ.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-DSa1ttcD.js → lspLanguageFeatures-BIzNBzPK.js} +1 -1
- package/studio/web/assets/{notifications-BHXLnh6x.js → notifications-CQf18QKb.js} +1 -1
- package/studio/web/assets/{notifications-CUmtIA6z.js → notifications-CvZivSbh.js} +1 -1
- package/studio/web/assets/{profile-8BeFSF3j.js → profile-DdOwtntb.js} +1 -1
- package/studio/web/assets/{regions-C8qR0HhD.js → regions-n69fwagr.js} +1 -1
- package/studio/web/assets/{register-B4n5i0SD.js → register-PfWTCXWB.js} +2 -2
- package/studio/web/assets/{setComponentFile-g0_B0lgX.js → setComponentFile-Bg6O7X0S.js} +1 -1
- package/studio/web/assets/{setup-B0CTj_Q5.js → setup-CUx_aUDl.js} +2 -2
- package/studio/web/assets/{status-C6Yib7-K.js → status-D7BVKqX9.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-Dgta7bVi.js → toggleHighContrast-DBSyXzMr.js} +1 -1
- package/studio/web/assets/{tsMode-CH_jHvU-.js → tsMode-BByKCjBS.js} +1 -1
- package/studio/web/assets/{typescript-Co9LCXd5.js → typescript-DDLnLpw9.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-wKC8NsC_.js → useEntityRestURL-31CHGaHk.js} +1 -1
- package/studio/web/assets/{workers-CWeLxCXA.js → workers-pR3jRY9D.js} +1 -1
- package/studio/web/assets/{xml-BadC-0Rk.js → xml-2iRnMhQO.js} +1 -1
- package/studio/web/assets/{yaml-BiUfxPbC.js → yaml-Bf92gJpd.js} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-3-0.ts +132 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/OperationFunctionCaller.ts +2 -1
- package/utility/common_utils.ts +30 -5
- package/utility/componentNames.ts +12 -0
- package/utility/environment/environmentManager.ts +3 -7
- package/utility/environment/systemInformation.ts +7 -0
- package/utility/errors/commonErrors.ts +4 -0
- package/utility/errors/hdbError.ts +29 -0
- package/utility/hdbTerms.ts +17 -0
- package/utility/logging/harper_logger.ts +87 -14
- package/utility/logging/logRotator.ts +72 -45
- package/utility/nodeIdentity.ts +45 -0
- package/utility/npmUtilities.ts +10 -8
- package/utility/operationPermissions.ts +18 -1
- package/utility/operation_authorization.ts +231 -42
- package/utility/watchPath.ts +63 -0
- package/utility/watcherFallback.ts +270 -0
- package/validation/configValidator.ts +209 -72
- package/validation/installValidator.ts +15 -0
- package/validation/validationWrapper.ts +18 -4
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
// events. Polling-based watching doesn't consume inotify handles or per-watcher
|
|
6
6
|
// file descriptors, so we fall back to it once and warn — see harper#488.
|
|
7
7
|
|
|
8
|
+
import chokidar, { type ChokidarOptions, type FSWatcher } from 'chokidar';
|
|
8
9
|
import { loggerWithTag } from './logging/harper_logger.ts';
|
|
9
10
|
|
|
10
11
|
// One-time process-wide warning so a thundering herd of failing watchers doesn't
|
|
11
12
|
// produce hundreds of identical log lines.
|
|
12
13
|
let exhaustionWarned = false;
|
|
14
|
+
const partialReadWarned = new Set<string>();
|
|
13
15
|
|
|
14
16
|
const fallbackLogger = loggerWithTag('watcher');
|
|
15
17
|
|
|
@@ -71,4 +73,272 @@ export function warnWatcherFallback(watchedPath: string): void {
|
|
|
71
73
|
// Test-only hook to reset the one-time warning gate between cases.
|
|
72
74
|
export function _resetForTests(): void {
|
|
73
75
|
exhaustionWarned = false;
|
|
76
|
+
lostNativeWatchCount = 0;
|
|
77
|
+
lostNativeWatchWarnThreshold = 1;
|
|
78
|
+
// Also drop the process listener, so a case that installed the guard can't leave one
|
|
79
|
+
// attached to the test runner's process for every case that follows.
|
|
80
|
+
process.removeListener('uncaughtException', handleUncaughtException);
|
|
81
|
+
lostNativeWatchGuardInstalled = false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Lost native watch (predominantly Windows)
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
//
|
|
88
|
+
// Every Harper watcher runs `persistent: false`, which is the one chokidar
|
|
89
|
+
// branch that never attaches an 'error' listener to the underlying Node
|
|
90
|
+
// `fs.FSWatcher` (chokidar `handler.js`, `setFsWatchListener`; still so at
|
|
91
|
+
// 5.0.0). Its `errHandler` covers only a synchronous throw out of `fs.watch()`
|
|
92
|
+
// — the ENOSPC/EMFILE route above. An asynchronous failure is emitted on a
|
|
93
|
+
// handle with no listener, so Node makes it an uncaughtException without it
|
|
94
|
+
// ever reaching chokidar; `.on('error')` on the wrapper we hold cannot see it.
|
|
95
|
+
// The process is therefore the only place it is observable, which is what
|
|
96
|
+
// `installLostNativeWatchGuard()` uses.
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns `true` for the asynchronous "the watched path went away" error raised
|
|
100
|
+
* by Node's `fs.FSWatcher` — on Windows, `EPERM: operation not permitted, watch`
|
|
101
|
+
* (errno -4048) when a watched directory is removed or swapped out. Unlike
|
|
102
|
+
* {@link isWatcherExhaustionError} there is nothing to degrade to: polling a
|
|
103
|
+
* path that no longer exists only burns CPU.
|
|
104
|
+
*
|
|
105
|
+
* All three conditions are load-bearing, because whatever this claims is
|
|
106
|
+
* swallowed process-wide:
|
|
107
|
+
*
|
|
108
|
+
* - `syscall === 'watch'` is only ever set by fs watch handles.
|
|
109
|
+
* - `EPERM` only. An async `ENOENT` from a watch handle has never been
|
|
110
|
+
* observed, whereas a *synchronous* one is the ordinary "watch a path that
|
|
111
|
+
* isn't there" misconfiguration, which must stay fatal.
|
|
112
|
+
* - no `path`. Node populates `path` on the error it throws out of
|
|
113
|
+
* `fs.watch()` and leaves it absent on the one it delivers to the handle,
|
|
114
|
+
* so without this a raw `fs.watch(missingPath)` reaching the guard would be
|
|
115
|
+
* mistaken for a benign lost watch and swallowed.
|
|
116
|
+
*/
|
|
117
|
+
export function isLostNativeWatchError(error: unknown): boolean {
|
|
118
|
+
if (typeof error !== 'object' || error === null) return false;
|
|
119
|
+
const { code, syscall, path } = error as { code?: unknown; syscall?: unknown; path?: unknown };
|
|
120
|
+
return syscall === 'watch' && code === 'EPERM' && path == null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let lostNativeWatchCount = 0;
|
|
124
|
+
let lostNativeWatchWarnThreshold = 1;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* If `error` is a lost native watch error, mark it handled, log it, and report
|
|
128
|
+
* that it has been claimed. Exported so the per-watcher error routes can give it
|
|
129
|
+
* the same benign treatment on the day chokidar does deliver it to the wrapper
|
|
130
|
+
* (its polling and `persistent: true` branches already would).
|
|
131
|
+
*/
|
|
132
|
+
export function claimLostNativeWatchError(error: unknown): boolean {
|
|
133
|
+
if (!isLostNativeWatchError(error)) return false;
|
|
134
|
+
const claimed = error as { isHandled?: boolean };
|
|
135
|
+
// Counting one instance twice would trip the warn threshold early.
|
|
136
|
+
if (claimed.isHandled) return true;
|
|
137
|
+
lostNativeWatchCount++;
|
|
138
|
+
// The claim is the classification above; the bookkeeping that follows must not be able to
|
|
139
|
+
// undo it. A frozen error (`isHandled` unassignable) or a throwing logger would otherwise
|
|
140
|
+
// make a failure just classified as benign fatal — here, or in a caller's 'error' route.
|
|
141
|
+
try {
|
|
142
|
+
fallbackLogger.trace?.(`Lost native file watch handle (occurrence ${lostNativeWatchCount}):`, error);
|
|
143
|
+
// A subsystem that has silently stopped being watched is what an operator needs to see, and
|
|
144
|
+
// the default level is `warn`, so trace alone would hide every occurrence after the first.
|
|
145
|
+
// Warning at each decade keeps a delete storm bounded without ever going fully silent.
|
|
146
|
+
if (lostNativeWatchCount >= lostNativeWatchWarnThreshold) {
|
|
147
|
+
lostNativeWatchWarnThreshold *= 10;
|
|
148
|
+
fallbackLogger.warn?.(
|
|
149
|
+
`A native file watch handle failed asynchronously (EPERM, syscall=watch) and was suppressed to ` +
|
|
150
|
+
`keep the thread alive; occurrence ${lostNativeWatchCount}. Node reports no path for this error, ` +
|
|
151
|
+
`so it cannot be attributed to a specific watcher — whatever was watching that path has stopped ` +
|
|
152
|
+
`reporting changes until it is re-established. On Windows this is usually a watched directory ` +
|
|
153
|
+
`being deleted or replaced (a component redeploy, a package install, a test teardown). If file ` +
|
|
154
|
+
`changes stop being picked up somewhere, this is why. Subsequent occurrences log at trace, with ` +
|
|
155
|
+
`a warning at each tenfold increase.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
// Last: threadServer.js and socketRouter.ts skip errors already marked handled, so failing
|
|
159
|
+
// to mark one costs only a duplicate log line.
|
|
160
|
+
claimed.isHandled = true;
|
|
161
|
+
} catch {
|
|
162
|
+
// Marked or not, logged or not, the error stays claimed.
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let lostNativeWatchGuardInstalled = false;
|
|
168
|
+
|
|
169
|
+
function handleUncaughtException(error: unknown): void {
|
|
170
|
+
let claimed = false;
|
|
171
|
+
try {
|
|
172
|
+
claimed = claimLostNativeWatchError(error);
|
|
173
|
+
} catch {
|
|
174
|
+
// Classification is all that can still throw (a property getter that does), and a throw
|
|
175
|
+
// from inside an 'uncaughtException' listener would replace Node's report with it and
|
|
176
|
+
// exit 7. An error that cannot be classified is not ours to claim.
|
|
177
|
+
claimed = false;
|
|
178
|
+
}
|
|
179
|
+
if (claimed) return;
|
|
180
|
+
// Node suppresses its default fatal handling as soon as *any* 'uncaughtException' listener
|
|
181
|
+
// exists, so being the only listener would turn unrelated crashes into silent hangs.
|
|
182
|
+
if (process.listenerCount('uncaughtException') > 1) return;
|
|
183
|
+
process.removeListener('uncaughtException', handleUncaughtException);
|
|
184
|
+
// A process that somehow survives re-arms on its next guardedWatch() rather than running
|
|
185
|
+
// unguarded thereafter.
|
|
186
|
+
lostNativeWatchGuardInstalled = false;
|
|
187
|
+
// nextTick, not a throw from inside the handler: that keeps Node's report and exit code 1.
|
|
188
|
+
process.nextTick(() => {
|
|
189
|
+
throw error;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Idempotently install the process-level listener that supplies the 'error'
|
|
195
|
+
* handler chokidar omits for non-persistent watchers. Called by
|
|
196
|
+
* {@link guardedWatch} so no watcher call site can forget it.
|
|
197
|
+
*/
|
|
198
|
+
export function installLostNativeWatchGuard(): void {
|
|
199
|
+
if (lostNativeWatchGuardInstalled) return;
|
|
200
|
+
lostNativeWatchGuardInstalled = true;
|
|
201
|
+
// prepend, not append: threadServer.js already has an 'uncaughtException'
|
|
202
|
+
// listener, and the `isHandled` mark above only suppresses its log line if
|
|
203
|
+
// this guard runs first.
|
|
204
|
+
process.prependListener('uncaughtException', handleUncaughtException);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* `chokidar.watch()` with the lost-native-watch guard installed. Every Harper
|
|
209
|
+
* chokidar watcher must go through this — a raw `chokidar.watch(..., { persistent: false })`
|
|
210
|
+
* can take down the thread the first time its path is deleted.
|
|
211
|
+
*/
|
|
212
|
+
export function guardedWatch(paths: string | string[], options?: ChokidarOptions): FSWatcher {
|
|
213
|
+
installLostNativeWatchGuard();
|
|
214
|
+
// Deliberately a property access on the default export rather than a named `watch` import:
|
|
215
|
+
// unitTests/server/threads/watchDirFallback.test.js drives the reopen-on-exhaustion chain by
|
|
216
|
+
// swapping `chokidar.default.watch`, and a named import binds past that seam.
|
|
217
|
+
return chokidar.watch(paths, options);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Test-only: number of lost native watch errors claimed so far.
|
|
221
|
+
export function _lostNativeWatchCountForTests(): number {
|
|
222
|
+
return lostNativeWatchCount;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A config file replaced in place (truncate, then write) can be read back empty or
|
|
227
|
+
* half-written, and chokidar may emit nothing further for that write — so a watcher that
|
|
228
|
+
* simply drops the unusable read would serve stale config until something else touched the
|
|
229
|
+
* file. Re-read on a later turn instead, bounded so a genuinely empty or corrupt file cannot
|
|
230
|
+
* spin. Callers read synchronously, so the descriptor still never outlives a single turn.
|
|
231
|
+
*/
|
|
232
|
+
const PARTIAL_READ_REREAD_DELAY_MS = 20;
|
|
233
|
+
const PARTIAL_READ_MAX_REREADS = 10;
|
|
234
|
+
|
|
235
|
+
export class PartialReadRetry {
|
|
236
|
+
#filePath: string;
|
|
237
|
+
#timer?: ReturnType<typeof setTimeout>;
|
|
238
|
+
#remaining: number = PARTIAL_READ_MAX_REREADS;
|
|
239
|
+
#closed = false;
|
|
240
|
+
|
|
241
|
+
constructor(filePath: string) {
|
|
242
|
+
this.#filePath = filePath;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** False once the budget is spent, so the caller can fall back to its own error handling. */
|
|
246
|
+
schedule(reread: () => void): boolean {
|
|
247
|
+
if (this.#closed) return false;
|
|
248
|
+
if (this.#timer) return true;
|
|
249
|
+
if (this.#remaining <= 0) return false;
|
|
250
|
+
this.#remaining--;
|
|
251
|
+
this.#timer = setTimeout(() => {
|
|
252
|
+
this.#timer = undefined;
|
|
253
|
+
reread();
|
|
254
|
+
}, PARTIAL_READ_REREAD_DELAY_MS);
|
|
255
|
+
this.#timer.unref?.();
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** A usable read arrived, so any re-read still armed for the previous one would duplicate it. */
|
|
260
|
+
settled() {
|
|
261
|
+
if (this.#timer) clearTimeout(this.#timer);
|
|
262
|
+
this.#timer = undefined;
|
|
263
|
+
this.#remaining = PARTIAL_READ_MAX_REREADS;
|
|
264
|
+
// The file recovered, so the next time it breaks is a new incident and has to be reported
|
|
265
|
+
// again rather than silenced by the warning it emitted weeks ago.
|
|
266
|
+
partialReadWarned.delete(this.#filePath);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The budget is spent. Distinct from `settled()` in that the report stands — the file has not
|
|
271
|
+
* recovered, and it is shared with every other watcher of it. The budget itself is restored,
|
|
272
|
+
* because the next event may be the repair, and that repair can be observed mid-write too.
|
|
273
|
+
* Returns whether this give-up was the one reported.
|
|
274
|
+
*/
|
|
275
|
+
gaveUp(error?: unknown): boolean {
|
|
276
|
+
if (this.#closed) return false;
|
|
277
|
+
this.#remaining = PARTIAL_READ_MAX_REREADS;
|
|
278
|
+
return warnPartialReadGaveUp(this.#filePath, error);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Terminal: the watcher is closing, so nothing may re-arm the re-read or report on it. */
|
|
282
|
+
cancel() {
|
|
283
|
+
if (this.#timer) clearTimeout(this.#timer);
|
|
284
|
+
this.#timer = undefined;
|
|
285
|
+
this.#closed = true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* ENOENT is excluded not because it cannot be transient, but because it already has an answer:
|
|
291
|
+
* `OptionsWatcher` routes it to `remove` (env-only fallback at boot, then removal), and
|
|
292
|
+
* `RootConfigWatcher` keeps its last config rather than tearing down core features on a file
|
|
293
|
+
* that may just be mid-replace. Re-reading would only delay a decision already made.
|
|
294
|
+
*/
|
|
295
|
+
export function isPartialReadError(error: unknown): boolean {
|
|
296
|
+
return !(typeof error === 'object' && error !== null && (error as { code?: string }).code === 'ENOENT');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* A watcher that exhausts its re-read budget serves stale config from then on, so the give-up
|
|
301
|
+
* has to be visible — otherwise the only symptom is a config change that silently did nothing.
|
|
302
|
+
* Warned once per file: every root-config scope watches the same one and would otherwise report
|
|
303
|
+
* a single bad file once each, on every event.
|
|
304
|
+
*/
|
|
305
|
+
export function warnPartialReadGaveUp(filePath: string, error?: unknown): boolean {
|
|
306
|
+
if (partialReadWarned.has(filePath)) return false;
|
|
307
|
+
partialReadWarned.add(filePath);
|
|
308
|
+
// The cause matters to whoever has to fix it: a file that never parses is a typo to correct,
|
|
309
|
+
// while one that reads empty is a writer that never finished. Report the kind and position
|
|
310
|
+
// only — a YAML parse error's message quotes the offending source, and this file holds
|
|
311
|
+
// credentials.
|
|
312
|
+
const cause = error ? `: ${describeReadFailure(error)}` : ' that were empty or incomplete';
|
|
313
|
+
fallbackLogger.warn(`Gave up re-reading ${filePath} after ${PARTIAL_READ_MAX_REREADS} unusable reads${cause}`);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The kind and position of a failed config read, never its content: a YAML parse error's message
|
|
319
|
+
* quotes the offending source lines, and these files hold credentials.
|
|
320
|
+
*/
|
|
321
|
+
export function describeReadFailure(error: unknown): string {
|
|
322
|
+
if (typeof error !== 'object' || error === null) return 'unusable';
|
|
323
|
+
const { name, code, linePos } = error as { name?: string; code?: string; linePos?: { line: number; col: number }[] };
|
|
324
|
+
const at = linePos?.[0] ? ` at line ${linePos[0].line}, column ${linePos[0].col}` : '';
|
|
325
|
+
return `${code ?? name ?? 'unusable'}${at}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Test-only: whether a give-up warning for this file is currently suppressed as a duplicate. */
|
|
329
|
+
export function isPartialReadWarned(filePath: string): boolean {
|
|
330
|
+
return partialReadWarned.has(filePath);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Test-only: forget that this file was reported, so a suite can start from a known state. */
|
|
334
|
+
export function clearPartialReadWarning(filePath: string) {
|
|
335
|
+
partialReadWarned.delete(filePath);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* A listener that throws while applying new config is a bug in that listener, not evidence the
|
|
340
|
+
* file was half-written — the watcher's own state is already updated, so it keeps going.
|
|
341
|
+
*/
|
|
342
|
+
export function warnWatcherListenerError(filePath: string, error: unknown) {
|
|
343
|
+
fallbackLogger.warn(`Error applying a configuration change from ${filePath}`, error);
|
|
74
344
|
}
|
|
@@ -10,6 +10,7 @@ import * as hdbLogger from '../utility/logging/harper_logger.ts';
|
|
|
10
10
|
import * as hdbUtils from '../utility/common_utils.ts';
|
|
11
11
|
import * as hdbTerms from '../utility/hdbTerms.ts';
|
|
12
12
|
import { getDomainSocketPathMaxBytes } from '../utility/domainSocket.ts';
|
|
13
|
+
import { bareHostViolation } from '../utility/nodeIdentity.ts';
|
|
13
14
|
import * as validator from './validationWrapper.ts';
|
|
14
15
|
|
|
15
16
|
const DEFAULT_LOG_FOLDER = 'log';
|
|
@@ -29,6 +30,31 @@ const INVALID_RETENTION_VALUE_MSG =
|
|
|
29
30
|
const VALID_ROTATION_DURATION_UNITS = ['D', 'd', 'H', 'h', 'M', 'm'];
|
|
30
31
|
const UNDEFINED_OPS_API = 'rootPath config parameter is undefined';
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* What `deploy_component` writes into a root-config entry, plus the deployment-level keys
|
|
35
|
+
* componentLoader reads off it — the shape of a `sql` application deployed before the name was
|
|
36
|
+
* reserved. Closed to new keys: nothing new may take this shape.
|
|
37
|
+
*/
|
|
38
|
+
export const LEGACY_SQL_APPLICATION_KEYS = [
|
|
39
|
+
'package',
|
|
40
|
+
'files',
|
|
41
|
+
'path',
|
|
42
|
+
'install',
|
|
43
|
+
'credentials',
|
|
44
|
+
'loadComponent',
|
|
45
|
+
'urlPath',
|
|
46
|
+
'host',
|
|
47
|
+
'branchedDatabases',
|
|
48
|
+
'network',
|
|
49
|
+
'port',
|
|
50
|
+
'securePort',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export function isLegacySqlApplicationEntry(value: unknown): boolean {
|
|
54
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
55
|
+
return LEGACY_SQL_APPLICATION_KEYS.some((key) => Object.hasOwn(value, key));
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
// Directory-path validation. The previous `([...]+)+$` nested quantifier
|
|
33
59
|
// backtracked catastrophically (ReDoS), hanging the CLI at 100% CPU on any
|
|
34
60
|
// value with a character outside its allow-list after a run of valid ones — a
|
|
@@ -52,6 +78,10 @@ const DIRECTORY_PATH_PATTERN = /^(?!\s*$)[^\x00-\x1f\x7f\x80-\x9f\u2028\u2029]+$
|
|
|
52
78
|
const portConstraints = Joi.alternatives([number.min(0), string])
|
|
53
79
|
.optional()
|
|
54
80
|
.empty(null);
|
|
81
|
+
// node.hostname / replication.hostname must be a bare host, not a URL (utility/nodeIdentity.ts).
|
|
82
|
+
// `Joi.any` (not `string`) so validateBareHost reports a non-string with its own reason.
|
|
83
|
+
const bareHostConstraints = Joi.any().custom(validateBareHost).optional().empty(null);
|
|
84
|
+
const nodeUrlConstraints = string.custom(validateNodeUrl).optional().empty(null);
|
|
55
85
|
// Controlled-flow ("directional") replication fields. A route's `replicates` is either a boolean
|
|
56
86
|
// (full replication on/off) or an object describing per-direction flow; `sends`/`receives` and
|
|
57
87
|
// `sendsTo`/`receivesFrom` are also accepted as top-level route keys (iterateRoutes normalizes both
|
|
@@ -102,6 +132,88 @@ export const routeConstraints = Joi.alternatives([
|
|
|
102
132
|
array.items(string),
|
|
103
133
|
]);
|
|
104
134
|
|
|
135
|
+
// Models — `models:` block opts a deployment into the per-backend registry.
|
|
136
|
+
// Per-backend shape is validated by a discriminated alternative on the
|
|
137
|
+
// `backend` field. Phase 2 (#629) lands ollama; Phase 3 (#630) lands openai.
|
|
138
|
+
//
|
|
139
|
+
// `.unknown(false)` on each known backend's schema turns field-name typos
|
|
140
|
+
// (`bakend: ollama`, `hsot: ...`) into boot-blocking validation errors.
|
|
141
|
+
// Without it, Joi's top-level `allowUnknown: true` propagates and typos
|
|
142
|
+
// silently survive into bootstrap. Unknown backend types (anything not in
|
|
143
|
+
// the `switch` list) fall through to a permissive schema so future Harper
|
|
144
|
+
// versions or third-party components can register their own backends
|
|
145
|
+
// without core schema edits — `bootstrapModels` logs+skips at runtime.
|
|
146
|
+
//
|
|
147
|
+
// `requestTimeoutMs: min(1)` (not `min(0)`) so the meaning is unambiguous:
|
|
148
|
+
// omit the field for "no timeout". `0` would validate but `composeSignal`
|
|
149
|
+
// treats it as "no timeout" via `if (!timeoutMs)`, surprising a test that
|
|
150
|
+
// sets 0 to mean "fail immediately".
|
|
151
|
+
const commonEntryFields = {
|
|
152
|
+
model: string.optional(),
|
|
153
|
+
requestTimeoutMs: number.min(1).optional(),
|
|
154
|
+
// Ordered fallback group — other logical names tried, in order, after this one (#1326).
|
|
155
|
+
fallback: Joi.array().items(string).optional(),
|
|
156
|
+
};
|
|
157
|
+
const ollamaEntrySchema = Joi.object({
|
|
158
|
+
backend: string.valid('ollama').required(),
|
|
159
|
+
host: string.optional(),
|
|
160
|
+
...commonEntryFields,
|
|
161
|
+
}).unknown(false);
|
|
162
|
+
const openaiEntrySchema = Joi.object({
|
|
163
|
+
backend: string.valid('openai').required(),
|
|
164
|
+
// `apiKey` may be a literal secret or a `${ENV_VAR}` placeholder; both
|
|
165
|
+
// are syntactically strings. `bootstrap.ts` runs `expandEnvVarsDeep`
|
|
166
|
+
// before construction; the backend rejects unresolved placeholders
|
|
167
|
+
// with an explicit error pointing at the env-var name.
|
|
168
|
+
apiKey: string.required(),
|
|
169
|
+
baseUrl: string.optional(),
|
|
170
|
+
organization: string.optional(),
|
|
171
|
+
...commonEntryFields,
|
|
172
|
+
}).unknown(false);
|
|
173
|
+
const anthropicEntrySchema = Joi.object({
|
|
174
|
+
backend: string.valid('anthropic').required(),
|
|
175
|
+
// Same secret-handling posture as openai's `apiKey`.
|
|
176
|
+
apiKey: string.required(),
|
|
177
|
+
baseUrl: string.optional(),
|
|
178
|
+
...commonEntryFields,
|
|
179
|
+
}).unknown(false);
|
|
180
|
+
const bedrockEntrySchema = Joi.object({
|
|
181
|
+
backend: string.valid('bedrock').required(),
|
|
182
|
+
// AWS credentials resolve via the SDK chain (env / shared file / IAM
|
|
183
|
+
// roles for service accounts) — no apiKey field. `region` is
|
|
184
|
+
// effectively required (Bedrock is regional) but the backend can
|
|
185
|
+
// fall back to AWS_REGION env, so we leave it optional here.
|
|
186
|
+
region: string.optional(),
|
|
187
|
+
...commonEntryFields,
|
|
188
|
+
}).unknown(false);
|
|
189
|
+
const unknownBackendEntrySchema = Joi.object({
|
|
190
|
+
backend: string.required(),
|
|
191
|
+
}).unknown(true);
|
|
192
|
+
const modelEntrySchema = Joi.alternatives().conditional('.backend', {
|
|
193
|
+
switch: [
|
|
194
|
+
{ is: 'ollama', then: ollamaEntrySchema },
|
|
195
|
+
{ is: 'openai', then: openaiEntrySchema },
|
|
196
|
+
{ is: 'anthropic', then: anthropicEntrySchema },
|
|
197
|
+
{ is: 'bedrock', then: bedrockEntrySchema },
|
|
198
|
+
],
|
|
199
|
+
otherwise: unknownBackendEntrySchema,
|
|
200
|
+
});
|
|
201
|
+
const modelsSchema = Joi.object({
|
|
202
|
+
embedding: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
|
|
203
|
+
generative: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validate a `models:` block on its own — the hot-reload path applies a watched file's block
|
|
208
|
+
* without the full boot validation, and must enforce the same schema boot does.
|
|
209
|
+
*/
|
|
210
|
+
export function validateModelsBlock(models) {
|
|
211
|
+
// `allowUnknown: true` mirrors the boot path, whose top-level option propagates into this
|
|
212
|
+
// subtree: a sibling of embedding/generative that boots must not block a reload. Entry-level
|
|
213
|
+
// strictness is preserved by each backend schema's `.unknown(false)`.
|
|
214
|
+
return modelsSchema.validate(models, { abortEarly: false, allowUnknown: true, errors: { wrap: { label: "'" } } });
|
|
215
|
+
}
|
|
216
|
+
|
|
105
217
|
let hdbRoot;
|
|
106
218
|
let skipFsVal = false;
|
|
107
219
|
|
|
@@ -157,76 +269,42 @@ export function configValidator(configJson, skipFsValidation = false) {
|
|
|
157
269
|
session: mcpSessionSchema.optional(),
|
|
158
270
|
});
|
|
159
271
|
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
...commonEntryFields,
|
|
197
|
-
}).unknown(false);
|
|
198
|
-
const anthropicEntrySchema = Joi.object({
|
|
199
|
-
backend: string.valid('anthropic').required(),
|
|
200
|
-
// Same secret-handling posture as openai's `apiKey`.
|
|
201
|
-
apiKey: string.required(),
|
|
202
|
-
baseUrl: string.optional(),
|
|
203
|
-
...commonEntryFields,
|
|
204
|
-
}).unknown(false);
|
|
205
|
-
const bedrockEntrySchema = Joi.object({
|
|
206
|
-
backend: string.valid('bedrock').required(),
|
|
207
|
-
// AWS credentials resolve via the SDK chain (env / shared file / IAM
|
|
208
|
-
// roles for service accounts) — no apiKey field. `region` is
|
|
209
|
-
// effectively required (Bedrock is regional) but the backend can
|
|
210
|
-
// fall back to AWS_REGION env, so we leave it optional here.
|
|
211
|
-
region: string.optional(),
|
|
212
|
-
...commonEntryFields,
|
|
213
|
-
}).unknown(false);
|
|
214
|
-
const unknownBackendEntrySchema = Joi.object({
|
|
215
|
-
backend: string.required(),
|
|
216
|
-
}).unknown(true);
|
|
217
|
-
const modelEntrySchema = Joi.alternatives().conditional('.backend', {
|
|
218
|
-
switch: [
|
|
219
|
-
{ is: 'ollama', then: ollamaEntrySchema },
|
|
220
|
-
{ is: 'openai', then: openaiEntrySchema },
|
|
221
|
-
{ is: 'anthropic', then: anthropicEntrySchema },
|
|
222
|
-
{ is: 'bedrock', then: bedrockEntrySchema },
|
|
223
|
-
],
|
|
224
|
-
otherwise: unknownBackendEntrySchema,
|
|
225
|
-
});
|
|
226
|
-
const modelsSchema = Joi.object({
|
|
227
|
-
embedding: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
|
|
228
|
-
generative: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
|
|
229
|
-
});
|
|
272
|
+
// `convert: false` — validateConfig() only writes the coerced value back into configDoc for
|
|
273
|
+
// threads/componentsRoot/logging/storage/operationsApi, not sql, so leaving Joi's default
|
|
274
|
+
// convert:true on here would accept a quoted `allowFullScan: "true"` and then silently drop
|
|
275
|
+
// it (getSqlEngineConfig()'s typeof guard rejects the still-unconverted string).
|
|
276
|
+
const sqlSettingsSchema = Joi.object({
|
|
277
|
+
engine: string.valid('legacy', 'new', 'auto').optional(),
|
|
278
|
+
allowFullScan: boolean.optional(),
|
|
279
|
+
maxSortRows: number.integer().min(1).optional(),
|
|
280
|
+
maxHashRows: number.integer().min(1).optional(),
|
|
281
|
+
})
|
|
282
|
+
.unknown(false)
|
|
283
|
+
.prefs({ convert: false });
|
|
284
|
+
|
|
285
|
+
// An application deployed under the name before it was reserved still boots; validateConfig()
|
|
286
|
+
// warns to rename it. Selected positively so a typo'd setting is still held to the settings
|
|
287
|
+
// schema instead of passing as an application.
|
|
288
|
+
const legacySqlApplicationSchema = Joi.object({
|
|
289
|
+
engine: Joi.any().forbidden(),
|
|
290
|
+
allowFullScan: Joi.any().forbidden(),
|
|
291
|
+
maxSortRows: Joi.any().forbidden(),
|
|
292
|
+
maxHashRows: Joi.any().forbidden(),
|
|
293
|
+
})
|
|
294
|
+
.unknown(true)
|
|
295
|
+
.messages({
|
|
296
|
+
'any.unknown': `{#label} cannot be set while 'sql' names a deployed application; redeploy that application under a different name`,
|
|
297
|
+
});
|
|
298
|
+
const sqlSchema = Joi.alternatives()
|
|
299
|
+
.conditional(
|
|
300
|
+
Joi.object()
|
|
301
|
+
.or(...LEGACY_SQL_APPLICATION_KEYS)
|
|
302
|
+
.unknown(true),
|
|
303
|
+
{ then: legacySqlApplicationSchema, otherwise: sqlSettingsSchema }
|
|
304
|
+
)
|
|
305
|
+
// `false`/null is how componentLoader spells a disabled entry, so an operator who had already
|
|
306
|
+
// turned a pre-reservation `sql` application off keeps booting.
|
|
307
|
+
.allow(false, null);
|
|
230
308
|
|
|
231
309
|
const configSchema = Joi.object({
|
|
232
310
|
authentication: Joi.alternatives(
|
|
@@ -247,8 +325,8 @@ export function configValidator(configJson, skipFsValidation = false) {
|
|
|
247
325
|
replicate: boolean.optional(),
|
|
248
326
|
}),
|
|
249
327
|
replication: Joi.object({
|
|
250
|
-
hostname:
|
|
251
|
-
url:
|
|
328
|
+
hostname: bareHostConstraints,
|
|
329
|
+
url: nodeUrlConstraints,
|
|
252
330
|
port: portConstraints,
|
|
253
331
|
securePort: portConstraints,
|
|
254
332
|
routes: array.optional().empty(null),
|
|
@@ -265,6 +343,10 @@ export function configValidator(configJson, skipFsValidation = false) {
|
|
|
265
343
|
copyCursorFlushIntervalMs: number.min(1).optional().empty(null),
|
|
266
344
|
replayTimeout: number.min(1).optional().empty(null),
|
|
267
345
|
}).optional(),
|
|
346
|
+
node: Joi.object({
|
|
347
|
+
hostname: bareHostConstraints,
|
|
348
|
+
url: string.optional().empty(null),
|
|
349
|
+
}).optional(),
|
|
268
350
|
componentsRoot: rootConstraints.optional(),
|
|
269
351
|
localStudio: Joi.object({
|
|
270
352
|
enabled: enabledConstraints,
|
|
@@ -364,6 +446,30 @@ export function configValidator(configJson, skipFsValidation = false) {
|
|
|
364
446
|
writeAsync: boolean.required(),
|
|
365
447
|
overlappingSync: boolean.optional(),
|
|
366
448
|
caching: boolean.optional(),
|
|
449
|
+
blobs: Joi.object({
|
|
450
|
+
compression: Joi.object()
|
|
451
|
+
.pattern(
|
|
452
|
+
// exact content type, 'type/*' wildcard, or the 'default' entry
|
|
453
|
+
/^([\w.+-]+\/(\*|[\w.+-]+)|default)$/,
|
|
454
|
+
Joi.alternatives([
|
|
455
|
+
Joi.valid(false),
|
|
456
|
+
// .unknown(false) so a typo'd nested field (e.g. `treshold`) is rejected instead of
|
|
457
|
+
// silently inheriting the top-level validate() allowUnknown:true and falling back to
|
|
458
|
+
// the default threshold — matching config-root.schema.json.
|
|
459
|
+
Joi.object({ codec: Joi.valid('deflate').optional(), threshold: number.min(0).optional() }).unknown(
|
|
460
|
+
false
|
|
461
|
+
),
|
|
462
|
+
])
|
|
463
|
+
)
|
|
464
|
+
// fail on keys that are not a content type, a 'type/*' wildcard, or 'default' —
|
|
465
|
+
// a typo'd key would otherwise silently never match anything
|
|
466
|
+
.unknown(false)
|
|
467
|
+
.optional(),
|
|
468
|
+
})
|
|
469
|
+
// reject a misspelled property directly under storage.blobs (e.g. `compresion:`), which the
|
|
470
|
+
// top-level validate()'s allowUnknown:true would otherwise accept and silently leave off
|
|
471
|
+
.unknown(false)
|
|
472
|
+
.optional(),
|
|
367
473
|
compression: Joi.alternatives([
|
|
368
474
|
boolean.optional(),
|
|
369
475
|
Joi.object({ dictionary: string.optional(), threshold: number.optional() }),
|
|
@@ -378,6 +484,7 @@ export function configValidator(configJson, skipFsValidation = false) {
|
|
|
378
484
|
}).required(),
|
|
379
485
|
mcp: mcpSchema.optional(),
|
|
380
486
|
models: modelsSchema.optional(),
|
|
487
|
+
sql: sqlSchema.optional(),
|
|
381
488
|
ignoreScripts: boolean.optional(),
|
|
382
489
|
tls: Joi.alternatives([Joi.array().items(tlsConstraints), tlsConstraints]),
|
|
383
490
|
});
|
|
@@ -402,6 +509,36 @@ function doesPathExist(pathToCheck) {
|
|
|
402
509
|
return `Specified path ${pathToCheck} does not exist.`;
|
|
403
510
|
}
|
|
404
511
|
|
|
512
|
+
function validateBareHost(value, helpers) {
|
|
513
|
+
const violation = bareHostViolation(value);
|
|
514
|
+
if (violation) {
|
|
515
|
+
return helpers.message(
|
|
516
|
+
`{{#label}} ${violation}; it must be a bare hostname or IP literal (no scheme, port, or path) because it is this node's identity`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return value;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// A node URL supplies this node's identity via its host (server/nodeName.ts urlToNodeName), so it
|
|
523
|
+
// must have an authority. A hostless scheme ("mailto:", "data:", "file:") parses but has no host.
|
|
524
|
+
// A node URL supplies this node's identity via its host (server/nodeName.ts urlToNodeName). Only
|
|
525
|
+
// reject the unambiguous mistake: a value written as a real URL (with a "//" authority) that still
|
|
526
|
+
// parses to no host. Everything else is deliberately tolerated, because urlToNodeName skips a value
|
|
527
|
+
// it cannot take a host from and identity falls through to another source — a scheme-less
|
|
528
|
+
// "node1.example.com:9933" even parses as a *scheme*, so it is indistinguishable from "mailto:" here,
|
|
529
|
+
// and rejecting that shape would turn a previously harmless config into a boot failure.
|
|
530
|
+
function validateNodeUrl(value, helpers) {
|
|
531
|
+
if (!value.includes('//')) return value;
|
|
532
|
+
let host;
|
|
533
|
+
try {
|
|
534
|
+
host = new URL(value).hostname;
|
|
535
|
+
} catch {
|
|
536
|
+
return value; // unparseable — skipped at runtime, not worth failing the boot over
|
|
537
|
+
}
|
|
538
|
+
if (!host) return helpers.message('{{#label}} must be a URL with a host (e.g. "wss://node1.example.com:9933")');
|
|
539
|
+
return value;
|
|
540
|
+
}
|
|
541
|
+
|
|
405
542
|
function validatePath(value, helpers) {
|
|
406
543
|
Joi.assert(value, string.pattern(DIRECTORY_PATH_PATTERN, 'directory path'));
|
|
407
544
|
|
|
@@ -5,6 +5,7 @@ const { string, number } = Joi.types();
|
|
|
5
5
|
import * as fs from 'fs-extra';
|
|
6
6
|
import * as hdbTerms from '../utility/hdbTerms.ts';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
+
import { bareHostViolation } from '../utility/nodeIdentity.ts';
|
|
8
9
|
import * as validator from './validationWrapper.ts';
|
|
9
10
|
|
|
10
11
|
export default installValidator;
|
|
@@ -22,11 +23,25 @@ function installValidator(param) {
|
|
|
22
23
|
null
|
|
23
24
|
),
|
|
24
25
|
[(hdbTerms.INSTALL_PROMPTS as any).TC_AGREEMENT]: string.valid('yes', 'YES', 'Yes'),
|
|
26
|
+
// Catch a bad node identity here rather than at first boot. This runs after the v5 migration
|
|
27
|
+
// copies REPLICATION_HOSTNAME into NODE_HOSTNAME, so an upgrade carrying a URL-ish value is
|
|
28
|
+
// reported during install with the same reason the config boundary would give (harper#2218).
|
|
29
|
+
[hdbTerms.INSTALL_PROMPTS.NODE_HOSTNAME]: Joi.custom(validateNodeHostname).allow('null', null),
|
|
25
30
|
});
|
|
26
31
|
|
|
27
32
|
return validator.validateBySchema(param, installSchema);
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
function validateNodeHostname(value, helpers) {
|
|
36
|
+
const violation = bareHostViolation(value);
|
|
37
|
+
if (violation) {
|
|
38
|
+
return helpers.message(
|
|
39
|
+
`{{#label}} ${violation}; it must be a bare hostname or IP literal (no scheme, port, or path) because it is this node's identity`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
function validateRootAvailable(value, helpers) {
|
|
31
46
|
if (
|
|
32
47
|
fs.existsSync(path.join(value, 'system/hdb_user/data.mdb')) ||
|