@harperfast/harper 5.2.13 → 5.3.0-beta.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 +58 -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 +120 -4
- package/bin/run.ts +6 -11
- package/bin/upgrade.js +7 -3
- package/bin/workloadIdentity.ts +119 -0
- package/components/Application.ts +3436 -242
- package/components/ApplicationScope.ts +8 -0
- package/components/EntryHandler.ts +59 -39
- package/components/OptionsWatcher.ts +440 -98
- package/components/RuntimeModuleTracker.ts +38 -7
- package/components/Scope.ts +56 -15
- package/components/awaitRestart.ts +84 -0
- package/components/componentLoader.ts +381 -32
- package/components/componentPreparationLock.ts +16 -5
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- 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 +537 -113
- package/components/operationsValidation.js +98 -3
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/components/status/ComponentStatusRegistry.ts +59 -0
- package/config/RootConfigWatcher.ts +240 -40
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +357 -48
- package/config/harperConfigEnvVars.ts +170 -27
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +33 -0
- package/dataLayer/blobBackup.ts +160 -50
- package/dataLayer/delete.ts +6 -1
- package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
- 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 +61 -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 +91 -6
- 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 +235 -17
- package/dist/components/Application.js +3038 -238
- 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 +4 -0
- package/dist/components/OptionsWatcher.js +440 -99
- 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 +52 -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 +313 -24
- 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/deploymentOperations.js +4 -1
- package/dist/components/deploymentOperations.js.map +1 -1
- package/dist/components/deploymentRecorder.d.ts +4 -2
- package/dist/components/deploymentRecorder.js +1 -0
- package/dist/components/deploymentRecorder.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.d.ts +28 -0
- package/dist/components/operations.js +476 -113
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +97 -3
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/packageComponent.js +24 -0
- package/dist/components/packageComponent.js.map +1 -1
- package/dist/components/requestRestart.d.ts +1 -0
- package/dist/components/requestRestart.js +7 -0
- package/dist/components/requestRestart.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 +9 -1
- package/dist/config/RootConfigWatcher.js +228 -37
- package/dist/config/RootConfigWatcher.js.map +1 -1
- package/dist/config/configReadRetry.d.ts +8 -0
- package/dist/config/configReadRetry.js +62 -0
- package/dist/config/configReadRetry.js.map +1 -0
- package/dist/config/configUtils.d.ts +11 -2
- package/dist/config/configUtils.js +304 -47
- 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/config/parseConfigFile.d.ts +4 -0
- package/dist/config/parseConfigFile.js +35 -0
- package/dist/config/parseConfigFile.js.map +1 -0
- package/dist/config/readConfigFileSync.d.ts +1 -0
- package/dist/config/readConfigFileSync.js +47 -0
- package/dist/config/readConfigFileSync.js.map +1 -0
- package/dist/config/watcherArming.d.ts +15 -0
- package/dist/config/watcherArming.js +59 -0
- package/dist/config/watcherArming.js.map +1 -0
- 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 +72 -12
- 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 +2 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +55 -0
- package/dist/resources/DatabaseTransaction.d.ts +77 -0
- package/dist/resources/DatabaseTransaction.js +586 -54
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +43 -6
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
- package/dist/resources/PrimaryRocksDatabase.js +49 -5
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +20 -0
- package/dist/resources/RecordEncoder.js +110 -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 +117 -24
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +28 -1
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/RocksIndexStore.d.ts +6 -1
- package/dist/resources/RocksIndexStore.js +24 -9
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
- package/dist/resources/RocksTransactionLogStore.js +160 -49
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +165 -10
- package/dist/resources/Table.js +1805 -303
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.js +10 -3
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +181 -2
- package/dist/resources/auditStore.js +640 -29
- 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/dataLoader.js +3 -4
- package/dist/resources/dataLoader.js.map +1 -1
- package/dist/resources/databases.d.ts +157 -14
- package/dist/resources/databases.js +1462 -325
- 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/derivedIndexRegistry.d.ts +5 -0
- package/dist/resources/derivedIndexRegistry.js +68 -0
- package/dist/resources/derivedIndexRegistry.js.map +1 -0
- package/dist/resources/derivedIndexRuntime.d.ts +215 -0
- package/dist/resources/derivedIndexRuntime.js +2027 -0
- package/dist/resources/derivedIndexRuntime.js.map +1 -0
- package/dist/resources/graphql.d.ts +1 -1
- package/dist/resources/graphql.js +52 -16
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
- package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
- package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
- package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
- package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
- 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/nodeIdMapping.d.ts +5 -0
- package/dist/resources/nodeIdMapping.js +49 -0
- package/dist/resources/nodeIdMapping.js.map +1 -1
- package/dist/resources/recordLock.d.ts +123 -0
- package/dist/resources/recordLock.js +315 -0
- package/dist/resources/recordLock.js.map +1 -0
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2565 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.d.ts +14 -1
- package/dist/resources/replayLogs.js +157 -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/replicatedApplyFailure.d.ts +16 -0
- package/dist/resources/replicatedApplyFailure.js +63 -0
- package/dist/resources/replicatedApplyFailure.js.map +1 -0
- package/dist/resources/scheduler/scheduler.js +3 -3
- package/dist/resources/scheduler/scheduler.js.map +1 -1
- package/dist/resources/search.d.ts +10 -4
- package/dist/resources/search.js +283 -37
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/tracked.d.ts +5 -1
- package/dist/resources/tracked.js +74 -23
- package/dist/resources/tracked.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 +77 -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 +106 -2
- 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 +16 -1
- package/dist/server/http.js +122 -17
- 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 +27 -0
- package/dist/server/serverHelpers/Headers.js +145 -1
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
- package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
- package/dist/server/serverHelpers/Request.d.ts +5 -10
- package/dist/server/serverHelpers/Request.js +38 -136
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
- package/dist/server/serverHelpers/contentTypes.js +221 -40
- 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 +178 -29
- 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 +17 -1
- package/dist/server/storageReclamation.js.map +1 -1
- package/dist/server/threads/isolatedApplications.d.ts +47 -0
- package/dist/server/threads/isolatedApplications.js +171 -0
- package/dist/server/threads/isolatedApplications.js.map +1 -0
- 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/logRotationTransport.d.ts +1 -0
- package/dist/server/threads/logRotationTransport.js +33 -0
- package/dist/server/threads/logRotationTransport.js.map +1 -0
- package/dist/server/threads/manageThreads.d.ts +83 -3
- package/dist/server/threads/manageThreads.js +769 -75
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +277 -31
- 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 +76 -22
- 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 +33 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/globalSchema.d.ts +18 -0
- package/dist/utility/hdbTerms.d.ts +18 -0
- package/dist/utility/hdbTerms.js +20 -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 +286 -30
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
- package/dist/utility/logging/logGenerationCoordinator.js +184 -0
- package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
- package/dist/utility/logging/logRotation.d.ts +46 -0
- package/dist/utility/logging/logRotation.js +365 -0
- package/dist/utility/logging/logRotation.js.map +1 -0
- package/dist/utility/logging/logRotator.d.ts +1 -1
- package/dist/utility/logging/logRotator.js +192 -85
- 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 +11 -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 +41 -0
- package/dist/utility/watcherFallback.js +153 -0
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.d.ts +12 -0
- package/dist/validation/configValidator.js +205 -75
- 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 +8 -0
- package/json/systemSchema.json +55 -0
- package/npm-shrinkwrap.json +286 -194
- package/package.json +15 -7
- package/resources/DESIGN.md +219 -52
- package/resources/DatabaseTransaction.ts +657 -52
- package/resources/LMDBTransaction.ts +46 -6
- package/resources/PrimaryRocksDatabase.ts +46 -7
- package/resources/RecordEncoder.ts +124 -9
- package/resources/RequestTarget.ts +2 -0
- package/resources/Resource.ts +114 -22
- package/resources/ResourceInterface.ts +31 -0
- package/resources/RocksIndexStore.ts +30 -9
- package/resources/RocksTransactionLogStore.ts +188 -51
- package/resources/Table.ts +1960 -301
- package/resources/analytics/write.ts +10 -3
- package/resources/auditStore.ts +647 -32
- package/resources/blob.ts +1029 -110
- package/resources/branchDatabase.ts +941 -0
- package/resources/crdt.ts +76 -12
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +1584 -320
- package/resources/defineTable.ts +18 -2
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +72 -17
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/longLivedTransactions.ts +360 -0
- package/resources/models/backendRegistry.ts +84 -2
- package/resources/models/bootstrap.ts +473 -28
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/recordLock.ts +419 -0
- package/resources/recordLockCoordinator.ts +3043 -0
- package/resources/replayLogs.ts +152 -26
- package/resources/replayLogsGuards.ts +171 -8
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +288 -46
- package/resources/tracked.ts +73 -22
- 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 +83 -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 +194 -16
- package/server/DurableSubscriptionsSession.ts +71 -11
- package/server/REST.ts +115 -4
- package/server/graphqlQuerying.ts +4 -0
- package/server/http.ts +133 -20
- 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 +135 -0
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +217 -36
- 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 +232 -40
- package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
- package/server/serverHelpers/uwsServer.ts +17 -2
- package/server/static.ts +23 -29
- package/server/storageReclamation.ts +15 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/itc.js +11 -1
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +771 -66
- package/server/threads/socketRouter.ts +291 -30
- package/server/threads/threadHeapMemory.ts +26 -0
- package/server/threads/threadServer.js +73 -22
- package/sqlTranslator/index.ts +31 -8
- package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
- package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
- package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
- package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
- package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
- package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
- package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
- package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
- package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
- package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
- package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
- package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
- package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
- package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
- package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
- package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
- package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
- package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
- package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
- package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
- package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
- package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
- package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
- package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
- package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
- package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.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 +57 -0
- package/utility/hdbTerms.ts +19 -0
- package/utility/logging/harper_logger.ts +278 -26
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +213 -81
- package/utility/nodeIdentity.ts +45 -0
- package/utility/npmUtilities.ts +12 -8
- package/utility/operationPermissions.ts +18 -1
- package/utility/operation_authorization.ts +231 -42
- package/utility/watchPath.ts +63 -0
- package/utility/watcherFallback.ts +148 -0
- package/validation/configValidator.ts +215 -75
- package/validation/installValidator.ts +15 -0
- package/validation/validationWrapper.ts +18 -4
package/components/operations.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const { isMainThread } = require('node:worker_threads');
|
|
4
|
+
const { isMainThread, parentPort } = require('node:worker_threads');
|
|
5
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
5
6
|
const fs = require('fs-extra');
|
|
6
7
|
const fg = require('fast-glob');
|
|
7
8
|
const normalize = require('normalize-path');
|
|
@@ -29,8 +30,14 @@ const {
|
|
|
29
30
|
streamPackagedDirectory,
|
|
30
31
|
} = require('../components/packageComponent.ts');
|
|
31
32
|
const { Resources } = require('../resources/Resources.ts');
|
|
32
|
-
const {
|
|
33
|
-
|
|
33
|
+
const {
|
|
34
|
+
Application,
|
|
35
|
+
prepareApplication,
|
|
36
|
+
ASIDE_STAGING_DIR,
|
|
37
|
+
DEPLOY_STAGING_DIR,
|
|
38
|
+
dropComponentDirectory,
|
|
39
|
+
} = require('./Application.ts');
|
|
40
|
+
const { COMPONENT_PREPARATION_LOCK_DIR, withComponentPreparationLock } = require('./componentPreparationLock.ts');
|
|
34
41
|
const { server } = require('../server/Server.ts');
|
|
35
42
|
const {
|
|
36
43
|
DeploymentRecorder,
|
|
@@ -41,6 +48,22 @@ const {
|
|
|
41
48
|
} = require('./deploymentRecorder.ts');
|
|
42
49
|
const { ProgressEmitter } = require('../server/serverHelpers/progressEmitter.ts');
|
|
43
50
|
|
|
51
|
+
const DROP_COMPONENT_LOCK_TIMEOUT_MS = 5 * 60 * 1000;
|
|
52
|
+
const ISOLATED_TOPOLOGY_REQUEST_TIMEOUT_MS = 5000;
|
|
53
|
+
|
|
54
|
+
function componentDropLockOptions(project) {
|
|
55
|
+
return {
|
|
56
|
+
timeoutMs: DROP_COMPONENT_LOCK_TIMEOUT_MS,
|
|
57
|
+
onWait: (owner) =>
|
|
58
|
+
log.info(
|
|
59
|
+
`Waiting to drop ${project} while component preparation is in progress` +
|
|
60
|
+
(owner ? ` in process ${owner.pid}, thread ${owner.threadId}` : '')
|
|
61
|
+
),
|
|
62
|
+
onReleaseError: (error) => log.error(`Failed to release the component preparation lock for ${project}:`, error),
|
|
63
|
+
isOwnerAlive: (owner) => owner.pid !== process.pid || manageThreads.isThreadRunning(owner.threadId),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
/**
|
|
45
68
|
* Read the settings.js file and return the
|
|
46
69
|
*
|
|
@@ -238,14 +261,12 @@ async function addComponent(req) {
|
|
|
238
261
|
}
|
|
239
262
|
|
|
240
263
|
log.trace(`adding component`);
|
|
241
|
-
const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
|
|
242
264
|
const { project, install_command, install_timeout, install_allow_scripts } = req;
|
|
243
265
|
|
|
244
266
|
const template = req.template || 'https://github.com/harperdb/application-template';
|
|
245
267
|
|
|
246
268
|
try {
|
|
247
|
-
|
|
248
|
-
fs.mkdirSync(projectDir, { recursive: true });
|
|
269
|
+
await fs.mkdir(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), { recursive: true });
|
|
249
270
|
const application = new Application({
|
|
250
271
|
name: project,
|
|
251
272
|
packageIdentifier: template,
|
|
@@ -310,8 +331,16 @@ async function dropCustomFunctionProject(req) {
|
|
|
310
331
|
|
|
311
332
|
try {
|
|
312
333
|
const projectDir = path.join(cfDir, project);
|
|
313
|
-
|
|
314
|
-
|
|
334
|
+
const stagingDir = path.join(cfDir, ASIDE_STAGING_DIR, project);
|
|
335
|
+
if (!(await fs.pathExists(projectDir)) && !(await fs.pathExists(stagingDir))) await fs.stat(projectDir);
|
|
336
|
+
await withComponentPreparationLock(
|
|
337
|
+
projectDir,
|
|
338
|
+
async () => {
|
|
339
|
+
await dropComponentDirectory(projectDir, project, log);
|
|
340
|
+
},
|
|
341
|
+
componentDropLockOptions(project)
|
|
342
|
+
);
|
|
343
|
+
const response = await server.replication.replicateOperation(req);
|
|
315
344
|
response.message = `Successfully deleted project: ${project}`;
|
|
316
345
|
return response;
|
|
317
346
|
} catch (err) {
|
|
@@ -410,6 +439,134 @@ async function packageComponent(req) {
|
|
|
410
439
|
return { project, payload };
|
|
411
440
|
}
|
|
412
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Load the built candidate to surface load-time errors. A load-ERROR PROBE, not a safety guarantee: it runs
|
|
444
|
+
* the component's own top-level code with incomplete side-effect isolation.
|
|
445
|
+
*
|
|
446
|
+
* A no-op on the main thread, and the operations API deploys there — so operator deploys are unvalidated
|
|
447
|
+
* (#2315 step 2). What this guarantees is ORDER: where validation runs, a rejected candidate never goes live.
|
|
448
|
+
*/
|
|
449
|
+
// `componentLoader.setErrorReporter` is ONE process-global callback, so two components validating
|
|
450
|
+
// concurrently on the same worker cross-attribute their failures: B installs its reporter while A is
|
|
451
|
+
// loading, A's load error lands in B, and A then activates broken code while B rejects a good candidate.
|
|
452
|
+
// Validation is serialized (it is already the slow path) and the previous reporter is restored, so the
|
|
453
|
+
// global is only ever owned by one in-flight validation.
|
|
454
|
+
let validationChain = Promise.resolve();
|
|
455
|
+
|
|
456
|
+
async function validateComponentLoads(candidateDirPath, emit) {
|
|
457
|
+
const run = validationChain.then(
|
|
458
|
+
() => validateComponentLoadsExclusive(candidateDirPath, emit),
|
|
459
|
+
() => validateComponentLoadsExclusive(candidateDirPath, emit)
|
|
460
|
+
);
|
|
461
|
+
validationChain = run.then(
|
|
462
|
+
() => {},
|
|
463
|
+
() => {}
|
|
464
|
+
);
|
|
465
|
+
return run;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function validateComponentLoadsExclusive(candidateDirPath, emit) {
|
|
469
|
+
// now we attempt to actually load the component in case there is
|
|
470
|
+
// an error we can immediately detect and report, but app code should not run on the main thread
|
|
471
|
+
if (!isMainThread && !process.env.HARPER_SAFE_MODE) {
|
|
472
|
+
const pseudoResources = new Resources();
|
|
473
|
+
pseudoResources.isWorker = true;
|
|
474
|
+
|
|
475
|
+
const componentLoader = require('./componentLoader.ts').default || require('./componentLoader.ts');
|
|
476
|
+
const { trackScopeClose } = require('./scopeShutdown.ts');
|
|
477
|
+
let lastError;
|
|
478
|
+
const priorErrorReporter = componentLoader.getErrorReporter?.();
|
|
479
|
+
componentLoader.setErrorReporter((error) => (lastError = error));
|
|
480
|
+
emit('phase', { phase: 'load', status: 'start' });
|
|
481
|
+
// This load exists only to surface load-time errors early; the Scopes it creates are
|
|
482
|
+
// throwaway. They are collected (instead of registered for worker-shutdown auto-close) so we
|
|
483
|
+
// can close them here once validation completes — otherwise each deploy leaks the Scope's
|
|
484
|
+
// deploy-lifecycle listeners on this worker, eventually tripping MaxListenersExceededWarning
|
|
485
|
+
// (#1462).
|
|
486
|
+
const validationScopes = new Set();
|
|
487
|
+
// Process-wide `server.*` registrations (registerOperation, setMcpQuotaHandler) are not owned by
|
|
488
|
+
// a Scope, so a candidate's top-level registration during this throwaway load would otherwise
|
|
489
|
+
// outlive it and pollute the live worker on a failed/rolled-back deploy. The guard makes those
|
|
490
|
+
// registration methods no-op for the duration of the load.
|
|
491
|
+
const { runWithDeployValidationGuard } = require('../server/serverHelpers/deployValidationState.ts');
|
|
492
|
+
// The candidate loads under the REAL component's name, so a candidate that throws would mark the live
|
|
493
|
+
// component ERROR. Its status writes are diverted into the guard's throwaway sink instead — see
|
|
494
|
+
// `deployValidationState.ts` for why this is context-scoped rather than captured and reverted here.
|
|
495
|
+
const componentName = path.basename(candidateDirPath);
|
|
496
|
+
// Extension modules the candidate load pulls in are registered in the loader's module registry keyed by
|
|
497
|
+
// module, so forgetting only the candidate's realpath leaves those behind — one set per deploy. Their
|
|
498
|
+
// identities are collected by the load itself; diffing the global registry instead would delete a live
|
|
499
|
+
// module registered by an interleaving real load, since validations serialize only with each other.
|
|
500
|
+
const validationModules = new Set();
|
|
501
|
+
const validation = runWithDeployValidationGuard(async () => {
|
|
502
|
+
try {
|
|
503
|
+
await componentLoader.loadComponent(candidateDirPath, pseudoResources, undefined, {
|
|
504
|
+
collectScopes: validationScopes,
|
|
505
|
+
collectLoadedModules: validationModules,
|
|
506
|
+
});
|
|
507
|
+
} finally {
|
|
508
|
+
const closeResults = await Promise.allSettled(Array.from(validationScopes, (scope) => scope.close()));
|
|
509
|
+
const failedCloses = closeResults.filter((result) => result.status === 'rejected');
|
|
510
|
+
for (const result of failedCloses) {
|
|
511
|
+
log.warn('Failed to close a deploy-validation Scope', result.reason);
|
|
512
|
+
}
|
|
513
|
+
// A rejected close is a REJECTED VALIDATION, not a warning. `Scope.close()` stops at the
|
|
514
|
+
// throwing listener, so its remaining internal listener removal and subscription-hold release
|
|
515
|
+
// never run and the throwaway scope stays partially live — one leak per deploy, on the worker
|
|
516
|
+
// that serves the component.
|
|
517
|
+
if (failedCloses.length) {
|
|
518
|
+
throw new AggregateError(
|
|
519
|
+
failedCloses.map((result) => result.reason),
|
|
520
|
+
`Could not tear down deploy validation for ${componentName}: ${failedCloses.length} scope(s) failed to close`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
// Track the load+close so a concurrent worker shutdown waits for these scopes to finish
|
|
526
|
+
// disposing — a plugin may start a native runtime in handleApplication — before realExit.
|
|
527
|
+
trackScopeClose(validation);
|
|
528
|
+
try {
|
|
529
|
+
await validation;
|
|
530
|
+
} finally {
|
|
531
|
+
componentLoader.setErrorReporter(priorErrorReporter);
|
|
532
|
+
// The candidate path is unique per deploy, so leaving it in the loader's realpath registry leaks
|
|
533
|
+
// one dead entry per deploy for the life of the process.
|
|
534
|
+
componentLoader.forgetLoadedPath?.(candidateDirPath);
|
|
535
|
+
componentLoader.forgetLoadedModules?.(validationModules);
|
|
536
|
+
}
|
|
537
|
+
emit('phase', { phase: 'load', status: 'done' });
|
|
538
|
+
|
|
539
|
+
if (lastError) throw lastError;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const BRANCH_STORAGE_RETAINED =
|
|
544
|
+
'. Any branched database storage this application owns was left in place; drop it again with restart: true to discard that data';
|
|
545
|
+
|
|
546
|
+
/** Report a restart outcome the operation's own success message cannot convey. */
|
|
547
|
+
function logRestartOutcome(restart, what) {
|
|
548
|
+
const { RESTART_IDLE_TIMEOUT_MS, RESTART_WAIT_CEILING_MS } = require('./awaitRestart.ts');
|
|
549
|
+
if (restart.replacementsNotStarted)
|
|
550
|
+
log.warn(
|
|
551
|
+
`${restart.replacementsNotStarted} replacement worker thread(s) did not report starting after ${what}; the pool is short until they are restarted`
|
|
552
|
+
);
|
|
553
|
+
if (restart.error) log.error(`Restart after ${what} failed`, restart.error);
|
|
554
|
+
else if (restart.workersKeptOnOldCode)
|
|
555
|
+
log.warn(
|
|
556
|
+
`${restart.workersKeptOnOldCode} worker thread(s) could not be replaced after ${what} and are still running the previous code`
|
|
557
|
+
);
|
|
558
|
+
else if (restart.declined) log.warn(`No restart was performed after ${what}: the process is already shutting down`);
|
|
559
|
+
else if (restart.handedOff)
|
|
560
|
+
log.debug?.(`The restart after ${what} was handed to the main thread, which reports its own completion`);
|
|
561
|
+
else if (restart.stalled)
|
|
562
|
+
log.warn(
|
|
563
|
+
`The restart after ${what} stopped making progress for ${RESTART_IDLE_TIMEOUT_MS}ms; worker threads may still be running the previous code`
|
|
564
|
+
);
|
|
565
|
+
else if (!restart.completed)
|
|
566
|
+
log.warn(
|
|
567
|
+
`The restart after ${what} was still running after ${RESTART_WAIT_CEILING_MS}ms; worker threads may still be running the previous code`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
413
570
|
/**
|
|
414
571
|
* Can deploy a component in multiple ways. If a 'package' is provided all it will do is write that package to
|
|
415
572
|
* harperdb-config, when HDB is restarted the package will be installed in hdb/nodeModules. If a base64 encoded string is passed it
|
|
@@ -433,13 +590,67 @@ async function deployComponent(req) {
|
|
|
433
590
|
// replicated ciphertext (reference, not embed); already-reference entries pass through, and with
|
|
434
591
|
// no custody a literal token stays as a transient, this-node-only fallback (#1158). Peers
|
|
435
592
|
// re-running a replicated deploy already carry references and never re-ingest.
|
|
593
|
+
const { isIsolatedApplication } = require('../server/threads/isolatedApplications.ts');
|
|
594
|
+
const requestedIsolation = req.isolated;
|
|
595
|
+
const isReplicatedExecution = typeof req._deploymentId === 'string';
|
|
596
|
+
const mode = req.deployment_id ? 'activate' : req.activate === false ? 'stage' : 'deploy';
|
|
597
|
+
const isActivation = mode === 'activate';
|
|
598
|
+
// this thread's cached config may predate an earlier deploy that changed the entry without a restart
|
|
599
|
+
env.initSync(true);
|
|
600
|
+
const initiallyIsolated = isIsolatedApplication(req.project);
|
|
601
|
+
if (requestedIsolation !== undefined && !req.package) {
|
|
602
|
+
throw handleHDBError(
|
|
603
|
+
new Error(),
|
|
604
|
+
`'isolated' is only supported for package deployments; set it on the application's root config entry instead`,
|
|
605
|
+
HTTP_STATUS_CODES.BAD_REQUEST
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
// Only the originating node rejects admission: replicated peers must commit the same desired config,
|
|
609
|
+
// then report any node-local inability to run it through component lifecycle status.
|
|
610
|
+
const assertIsolationAdmission = async (isolated) => {
|
|
611
|
+
if (!isolated || isReplicatedExecution) return;
|
|
612
|
+
const {
|
|
613
|
+
isolatedApplicationRefusal,
|
|
614
|
+
isolatedApplicationCapacityRefusal,
|
|
615
|
+
presentIsolatedApplicationNames,
|
|
616
|
+
} = require('../server/threads/isolatedApplications.ts');
|
|
617
|
+
let runningApplications;
|
|
618
|
+
try {
|
|
619
|
+
runningApplications = await manageThreads.getRunningIsolatedApplications(ISOLATED_TOPOLOGY_REQUEST_TIMEOUT_MS);
|
|
620
|
+
} catch (error) {
|
|
621
|
+
throw handleHDBError(
|
|
622
|
+
error,
|
|
623
|
+
`Cannot deploy '${req.project}' as an isolated application: the main thread's worker topology is unavailable: ${error.message}`,
|
|
624
|
+
HTTP_STATUS_CODES.SERVICE_UNAVAILABLE
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
runningApplications = new Set([
|
|
628
|
+
...runningApplications,
|
|
629
|
+
...presentIsolatedApplicationNames().filter((application) => !isolatedApplicationRefusal(application)),
|
|
630
|
+
]);
|
|
631
|
+
const refusal =
|
|
632
|
+
isolatedApplicationRefusal(req.project) ?? isolatedApplicationCapacityRefusal(req.project, runningApplications);
|
|
633
|
+
if (refusal) {
|
|
634
|
+
throw handleHDBError(
|
|
635
|
+
new Error(),
|
|
636
|
+
`Cannot deploy '${req.project}' as an isolated application: ${refusal}`,
|
|
637
|
+
HTTP_STATUS_CODES.CONFLICT
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
// Skipped for an activation: the artifact's descriptor is the authority for the isolation it wants, and
|
|
642
|
+
// `admitIsolation` runs the real check under the preparation lock. Admitting the CURRENT value here would
|
|
643
|
+
// refuse an activation whose artifact turns isolation OFF for a component whose present isolated state is
|
|
644
|
+
// itself the refusal — a 409 for the request that would fix it.
|
|
645
|
+
if (!isActivation) await assertIsolationAdmission(requestedIsolation ?? initiallyIsolated);
|
|
436
646
|
const { ingestCredentials, resolveCredentials } = require('./secretOperations.ts');
|
|
437
|
-
|
|
647
|
+
// An activation resolves, fetches and installs nothing, so there is no credential for it to carry.
|
|
648
|
+
// The validator rejects one; this keeps the ingest itself off the path rather than relying on that.
|
|
649
|
+
if (!isActivation) req.credentials = await ingestCredentials(req, req.credentials, req.project);
|
|
438
650
|
// References are safe to persist (config + deployment row) and replicate; a no-custody literal
|
|
439
651
|
// token is not — it is used only for this node's install below, then stripped before replication.
|
|
440
652
|
const credentialReferences = (req.credentials ?? []).filter((entry) => entry && entry.secret !== undefined);
|
|
441
653
|
|
|
442
|
-
// Write to root config if the request contains a package identifier
|
|
443
654
|
if (req.package) {
|
|
444
655
|
// Check if trying to overwrite a core component (requires force)
|
|
445
656
|
// Lazy-load to avoid circular dependency with componentLoader
|
|
@@ -451,23 +662,9 @@ async function deployComponent(req) {
|
|
|
451
662
|
HTTP_STATUS_CODES.CONFLICT
|
|
452
663
|
);
|
|
453
664
|
}
|
|
454
|
-
|
|
455
|
-
const applicationConfig = { package: req.package };
|
|
456
|
-
// Avoid writing an empty `install:` block
|
|
457
|
-
if (req.install_command || req.install_timeout || req.install_allow_scripts !== undefined) {
|
|
458
|
-
applicationConfig.install = {
|
|
459
|
-
command: req.install_command,
|
|
460
|
-
timeout: req.install_timeout,
|
|
461
|
-
allowInstallScripts: req.install_allow_scripts,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
if (req.urlPath !== undefined) applicationConfig.urlPath = req.urlPath;
|
|
465
|
-
if (req.host !== undefined) applicationConfig.host = req.host;
|
|
466
|
-
// Persist credential references (never tokens) so every cold install of this component —
|
|
467
|
-
// reboot, new peer, rollback — re-resolves the credential from the store.
|
|
468
|
-
if (credentialReferences.length) applicationConfig.credentials = credentialReferences;
|
|
469
|
-
await configUtils.addConfig(req.project, applicationConfig);
|
|
470
665
|
}
|
|
666
|
+
let wasIsolated;
|
|
667
|
+
let nowIsolated;
|
|
471
668
|
|
|
472
669
|
// Create a hdb_deployment row up front so the deploy is observable and auditable
|
|
473
670
|
// even if the CLI disconnects. The row also holds the payload in a Blob attribute,
|
|
@@ -477,7 +674,6 @@ async function deployComponent(req) {
|
|
|
477
674
|
// recording so we don't accumulate one row per node for the same deploy. The row
|
|
478
675
|
// reaches peers via the table's standard replication; the peer-side branch below
|
|
479
676
|
// reads payload_blob back from there.
|
|
480
|
-
const isReplicatedExecution = typeof req._deploymentId === 'string';
|
|
481
677
|
// An SSE-bound caller already attached a ProgressEmitter (created in the server
|
|
482
678
|
// handler so it can also drive the response stream). Reuse it; otherwise spin up a
|
|
483
679
|
// fresh emitter so the recorder still gets phase events for non-SSE deploys.
|
|
@@ -489,6 +685,7 @@ async function deployComponent(req) {
|
|
|
489
685
|
project: req.project,
|
|
490
686
|
package_identifier: req.package ?? null,
|
|
491
687
|
user: req.hdb_user?.username,
|
|
688
|
+
activated_from: req.deployment_id ?? null,
|
|
492
689
|
restart_mode: req.restart === 'rolling' ? 'rolling' : req.restart ? 'immediate' : null,
|
|
493
690
|
// Reference form only — the rollback source for re-resolving the credential.
|
|
494
691
|
credentials: credentialReferences.length ? credentialReferences : null,
|
|
@@ -513,12 +710,18 @@ async function deployComponent(req) {
|
|
|
513
710
|
// Bounded ring buffer of install stdout/stderr so a non-SSE caller sees the tail
|
|
514
711
|
// in the thrown error. SSE callers still stream every line live.
|
|
515
712
|
const installCapture = createInstallCapture();
|
|
713
|
+
let stagedOnOrigin = false;
|
|
516
714
|
try {
|
|
517
715
|
// On the origin, tee the tarball (Buffer or Readable from the multipart parser)
|
|
518
716
|
// through a hash-and-size tap into the row's payload_blob, then re-source extraction
|
|
519
717
|
// from the persisted blob. When `system` replicates, the blob becomes the channel
|
|
520
718
|
// peers read from; when it doesn't, the blob stays local for audit and rollback.
|
|
521
|
-
if (
|
|
719
|
+
if (isActivation) {
|
|
720
|
+
// A replicated activation has exactly the shape the peer-side blob branch below keys on — neither
|
|
721
|
+
// `package` nor `payload` — so without this branch every peer would wait out `deployment_timeout`
|
|
722
|
+
// for a payload its row never had while the origin swapped successfully.
|
|
723
|
+
extractionPayload = undefined;
|
|
724
|
+
} else if (recorder && req.payload != null) {
|
|
522
725
|
await recorder.ingestPayload(req.payload);
|
|
523
726
|
extractionPayload = recorder.row.payload_blob.stream();
|
|
524
727
|
} else if (isReplicatedExecution && req.payload == null && !req.package) {
|
|
@@ -554,9 +757,13 @@ async function deployComponent(req) {
|
|
|
554
757
|
if (isReplicatedExecution) {
|
|
555
758
|
credentialsWaitMs = coerceTimeoutMs(req.deployment_timeout, DEFAULT_AWAIT_ROW_TIMEOUT_MS);
|
|
556
759
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
760
|
+
// Skipped for an activation, which runs no pack, clone or install: waiting for an hdb_secret row to
|
|
761
|
+
// replicate in would only delay a swap that has nothing to authenticate to.
|
|
762
|
+
const resolvedCredentials = isActivation
|
|
763
|
+
? undefined
|
|
764
|
+
: await resolveCredentials(req.credentials, req.project, {
|
|
765
|
+
waitMs: credentialsWaitMs,
|
|
766
|
+
});
|
|
560
767
|
|
|
561
768
|
const application = new Application({
|
|
562
769
|
name: req.project,
|
|
@@ -587,52 +794,97 @@ async function deployComponent(req) {
|
|
|
587
794
|
if (credentialReferences.length) req.credentials = credentialReferences;
|
|
588
795
|
else delete req.credentials;
|
|
589
796
|
|
|
797
|
+
// Validated INSIDE preparation, between build and swap, so a component that installs cleanly but
|
|
798
|
+
// throws at load never becomes live.
|
|
799
|
+
//
|
|
800
|
+
// Phase ORDER is unchanged for clients, but `prepare:done` means "candidate built", not "swap
|
|
801
|
+
// committed" — so a later failure arrives after both phases reported success. The operation's error
|
|
802
|
+
// is the authority on whether the deploy landed, not the phase stream.
|
|
590
803
|
emit('phase', { phase: 'prepare', status: 'start' });
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
for (const result of closeResults) {
|
|
624
|
-
if (result.status === 'rejected') log.warn('Failed to close a deploy-validation Scope', result.reason);
|
|
804
|
+
// The root-config entry a package deploy publishes. A stage records it with the artifact instead of
|
|
805
|
+
// publishing it, so a staged release nobody activated cannot leave config naming it — and the
|
|
806
|
+
// activation that eventually publishes it does so in the same order a normal deploy does.
|
|
807
|
+
let stagedRootConfig = null;
|
|
808
|
+
await prepareApplication(application, {
|
|
809
|
+
// `.deploy-staging/<artifactId>`. The public deployment id, so the id the caller was handed is
|
|
810
|
+
// the id a later `deployment_id` request can name; an activation names the artifact's own id,
|
|
811
|
+
// not the new row's.
|
|
812
|
+
artifactId: req.deployment_id ?? req._deploymentId,
|
|
813
|
+
mode,
|
|
814
|
+
describeArtifact: () => ({ rootConfig: stagedRootConfig, isolated: Boolean(nowIsolated) }),
|
|
815
|
+
// Returns its own undo. Publication happens BEFORE the swap, in the same order a normal deploy
|
|
816
|
+
// uses, so an activation that fails pre-commit would otherwise leave config naming a release that
|
|
817
|
+
// is not live — and `installApplications()` would resolve and install that package from scratch at
|
|
818
|
+
// the next boot, over a component whose certified artifact is sitting right there. Restoring is
|
|
819
|
+
// best-effort and not durable (that is #2315 step 3's work); it is still the difference between a
|
|
820
|
+
// failed activation that changed nothing and one that re-points the component.
|
|
821
|
+
publishRootConfig: async (entry) => {
|
|
822
|
+
const previous = configUtils.getConfigObj()?.[req.project];
|
|
823
|
+
await configUtils.addConfig(req.project, entry);
|
|
824
|
+
env.initSync(true);
|
|
825
|
+
// Read BACK, not `entry` — see `publishedEntryStillStands`.
|
|
826
|
+
const published = configUtils.getConfigObj()?.[req.project];
|
|
827
|
+
return async () => {
|
|
828
|
+
if (
|
|
829
|
+
!publishedEntryStillStands(
|
|
830
|
+
published,
|
|
831
|
+
() => env.initSync(true),
|
|
832
|
+
() => configUtils.getConfigObj()?.[req.project]
|
|
833
|
+
)
|
|
834
|
+
) {
|
|
835
|
+
return;
|
|
625
836
|
}
|
|
837
|
+
if (previous === undefined) configUtils.deleteConfigFromFile([req.project]);
|
|
838
|
+
else await configUtils.addConfig(req.project, previous);
|
|
839
|
+
env.initSync(true);
|
|
840
|
+
};
|
|
841
|
+
},
|
|
842
|
+
admitIsolation: async (descriptor) => {
|
|
843
|
+
env.initSync(true);
|
|
844
|
+
wasIsolated = isIsolatedApplication(req.project);
|
|
845
|
+
// A package artifact restores its own root-config entry, so its recorded intent is what will
|
|
846
|
+
// be in force. A payload artifact publishes no config, so the effective configuration is the
|
|
847
|
+
// only authority there — a saved `false` must not admit past an isolation the operator set
|
|
848
|
+
// between the stage and this call.
|
|
849
|
+
nowIsolated = descriptor.rootConfig ? descriptor.isolated : wasIsolated;
|
|
850
|
+
await assertIsolationAdmission(nowIsolated);
|
|
851
|
+
},
|
|
852
|
+
beforePrepare: async () => {
|
|
853
|
+
if (isActivation) return;
|
|
854
|
+
env.initSync(true);
|
|
855
|
+
wasIsolated = isIsolatedApplication(req.project);
|
|
856
|
+
nowIsolated = requestedIsolation ?? wasIsolated;
|
|
857
|
+
await assertIsolationAdmission(nowIsolated);
|
|
858
|
+
if (!isReplicatedExecution && req.package) req.isolated = nowIsolated;
|
|
859
|
+
if (!req.package) return;
|
|
860
|
+
const applicationConfig = { package: req.package };
|
|
861
|
+
if (req.install_command || req.install_timeout || req.install_allow_scripts !== undefined) {
|
|
862
|
+
applicationConfig.install = {
|
|
863
|
+
command: req.install_command,
|
|
864
|
+
timeout: req.install_timeout,
|
|
865
|
+
allowInstallScripts: req.install_allow_scripts,
|
|
866
|
+
};
|
|
626
867
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
868
|
+
if (req.urlPath !== undefined) applicationConfig.urlPath = req.urlPath;
|
|
869
|
+
if (req.host !== undefined) applicationConfig.host = req.host;
|
|
870
|
+
if (req.branchedDatabases !== undefined) applicationConfig.branchedDatabases = req.branchedDatabases;
|
|
871
|
+
if (nowIsolated) applicationConfig.isolated = true;
|
|
872
|
+
if (credentialReferences.length) applicationConfig.credentials = credentialReferences;
|
|
873
|
+
if (mode === 'stage') {
|
|
874
|
+
stagedRootConfig = applicationConfig;
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
await configUtils.addConfig(req.project, applicationConfig);
|
|
878
|
+
env.initSync(true);
|
|
879
|
+
},
|
|
880
|
+
validateCandidate: async (candidateDirPath) => {
|
|
881
|
+
emit('phase', { phase: 'prepare', status: 'done' });
|
|
882
|
+
await validateComponentLoads(candidateDirPath, emit);
|
|
883
|
+
},
|
|
884
|
+
});
|
|
885
|
+
// The build is certified on disk from here on, so every later failure — a peer result, or a rejection
|
|
886
|
+
// thrown by the replication layer itself — still leaves an artifact this id can activate.
|
|
887
|
+
if (mode === 'stage') stagedOnOrigin = true;
|
|
636
888
|
const rollingRestart = req.restart === 'rolling';
|
|
637
889
|
// if doing a rolling restart set restart to false so that other nodes don't also restart.
|
|
638
890
|
req.restart = rollingRestart ? false : req.restart;
|
|
@@ -675,10 +927,29 @@ async function deployComponent(req) {
|
|
|
675
927
|
// when the per-peer callback already fired for these.
|
|
676
928
|
recorder.recordPeers(response.replicated);
|
|
677
929
|
}
|
|
678
|
-
|
|
930
|
+
// A still-isolated application restarts only its own dedicated worker. Everything else restarts the
|
|
931
|
+
// pool: a shared application, and either direction of an isolation flip, where the reconcile in
|
|
932
|
+
// restartWorkers starts or stops the moving application's own worker.
|
|
933
|
+
const restartScope = wasIsolated && nowIsolated ? application.name : undefined;
|
|
934
|
+
if (mode === 'stage') {
|
|
935
|
+
// No restart and no restart-required flag: nothing about the running component changed. The
|
|
936
|
+
// marker is what tells the origin which peers understood the request — see the confirmation
|
|
937
|
+
// check below — and it is set on every node, origin included, since a peer returns this same
|
|
938
|
+
// object as its operation response.
|
|
939
|
+
response.staged = true;
|
|
940
|
+
response.message = `Staged: ${application.name}`;
|
|
941
|
+
} else if (req.restart === true) {
|
|
679
942
|
emit('phase', { phase: 'restart', status: 'start' });
|
|
680
|
-
|
|
943
|
+
// Workers not yet replaced keep serving the pre-deploy component set, and where the OS lets
|
|
944
|
+
// replacements share a port they keep accepting connections for the whole rolling restart, so
|
|
945
|
+
// a caller that reads success as "the component is live" can be served by a worker that has
|
|
946
|
+
// never heard of it.
|
|
947
|
+
const { awaitRestart } = require('./awaitRestart.ts');
|
|
948
|
+
const restart = await awaitRestart((onProgress) =>
|
|
949
|
+
manageThreads.restartWorkers('http', undefined, undefined, onProgress, restartScope)
|
|
950
|
+
);
|
|
681
951
|
emit('phase', { phase: 'restart', status: 'done' });
|
|
952
|
+
logRestartOutcome(restart, `deploying ${application.name}`);
|
|
682
953
|
response.message = `Successfully deployed: ${application.name}, restarting Harper`;
|
|
683
954
|
} else if (rollingRestart) {
|
|
684
955
|
const serverUtilities = require('../server/serverHelpers/serverUtilities.ts');
|
|
@@ -686,6 +957,8 @@ async function deployComponent(req) {
|
|
|
686
957
|
const jobResponse = await serverUtilities.executeJob({
|
|
687
958
|
operation: 'restart_service',
|
|
688
959
|
service: 'http',
|
|
960
|
+
scope: manageThreads.encodeRestartScope(restartScope),
|
|
961
|
+
scopeFallback: restartScope === undefined ? undefined : manageThreads.encodeRestartScope(undefined),
|
|
689
962
|
replicated: true,
|
|
690
963
|
});
|
|
691
964
|
emit('phase', { phase: 'restart', status: 'done' });
|
|
@@ -705,10 +978,13 @@ async function deployComponent(req) {
|
|
|
705
978
|
// An existing component's watched files are handled by Scope/EntryHandler. Package
|
|
706
979
|
// metadata is deliberately outside most plugin globs, so compare it across the atomic
|
|
707
980
|
// swap as well: a dependency or module-entry change also invalidates loaded code.
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
981
|
+
const { requestRestartAfterDeploy } = require('./requestRestart.ts');
|
|
982
|
+
requestRestartAfterDeploy(
|
|
983
|
+
application.isNewComponent,
|
|
984
|
+
application.packageMetadataChanged,
|
|
985
|
+
wasIsolated,
|
|
986
|
+
nowIsolated
|
|
987
|
+
);
|
|
712
988
|
response.message = `Successfully deployed: ${application.name}`;
|
|
713
989
|
}
|
|
714
990
|
|
|
@@ -724,12 +1000,37 @@ async function deployComponent(req) {
|
|
|
724
1000
|
const detail = failedPeers
|
|
725
1001
|
.map((peer) => `${peer.node ?? 'unknown'} (${peer.error?.message ?? 'unknown error'})`)
|
|
726
1002
|
.join(', ');
|
|
727
|
-
|
|
728
|
-
`Component '${application.name}' was deployed on the origin node
|
|
729
|
-
|
|
730
|
-
`See deployment ${recorder.deploymentId} (get_deployment) for details, or
|
|
731
|
-
`ignore_replication_errors: true to treat replication failures as non-fatal.`
|
|
1003
|
+
const replicationError = new ServerError(
|
|
1004
|
+
`Component '${application.name}' was ${mode === 'stage' ? 'staged' : 'deployed'} on the origin node ` +
|
|
1005
|
+
`but failed to replicate to ${failedPeers.length} of ${recorder.row.peer_results.length} peer ` +
|
|
1006
|
+
`node(s): ${detail}. See deployment ${recorder.deploymentId} (get_deployment) for details, or ` +
|
|
1007
|
+
`pass ignore_replication_errors: true to treat replication failures as non-fatal.`
|
|
732
1008
|
);
|
|
1009
|
+
throw replicationError;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// A peer running a build that predates staged deploys accepts `activate: false` as an unknown
|
|
1013
|
+
// field and performs its ordinary deploy, so it is now serving a release the operator asked only
|
|
1014
|
+
// to stage. Nothing can prevent that — the operations channel carries no peer version or
|
|
1015
|
+
// capability — so the origin reports it, from the one signal that separates the two cases: a node
|
|
1016
|
+
// that staged says so in its response. Read from the raw aggregate, because the recorder's peer
|
|
1017
|
+
// normalization keeps only node/status/error.
|
|
1018
|
+
//
|
|
1019
|
+
// The wording stays at "did not confirm". An unreachable peer and one that dropped the request
|
|
1020
|
+
// look identical from here, and telling an operator those nodes went live would be a guess.
|
|
1021
|
+
if (mode === 'stage') {
|
|
1022
|
+
const unconfirmed = unconfirmedStagingPeers(response?.replicated);
|
|
1023
|
+
if (unconfirmed.length > 0) {
|
|
1024
|
+
const detail = unconfirmed.map((peer) => peer.node ?? 'unknown').join(', ');
|
|
1025
|
+
const unconfirmedError = new ServerError(
|
|
1026
|
+
`Component '${application.name}' was staged on the origin node, but ${unconfirmed.length} peer ` +
|
|
1027
|
+
`node(s) did not confirm staging: ${detail}. Either they are unreachable, or they run a build ` +
|
|
1028
|
+
`that predates staged deploys — which treats this request as an ordinary deploy, so the ` +
|
|
1029
|
+
`release is already serving there. Check those nodes before activating deployment ` +
|
|
1030
|
+
`${recorder.deploymentId}, or pass ignore_replication_errors: true to accept the difference.`
|
|
1031
|
+
);
|
|
1032
|
+
throw unconfirmedError;
|
|
1033
|
+
}
|
|
733
1034
|
}
|
|
734
1035
|
}
|
|
735
1036
|
|
|
@@ -743,14 +1044,28 @@ async function deployComponent(req) {
|
|
|
743
1044
|
// the tarball when it's still the artifact you'd debug or retry with: failed deploys
|
|
744
1045
|
// don't reach this branch, and a deploy that reached here only because
|
|
745
1046
|
// ignore_replication_errors masked failed peers keeps its payload for those peers.
|
|
1047
|
+
// Never for a stage: the artifact on disk is not yet a release anyone can re-derive, and retention
|
|
1048
|
+
// can evict it (it shares `deployment_stagingRetention_maxCount` with incidental dormant builds),
|
|
1049
|
+
// so dropping the tarball here is how a staged release becomes unrecoverable — no tree, no bytes,
|
|
1050
|
+
// and a row still reporting `staged`. An operator can still reclaim it deliberately with
|
|
1051
|
+
// delete_deployment_payload once they accept that.
|
|
746
1052
|
const payloadSize = recorder.row.payload_size;
|
|
747
1053
|
const retentionMaxSize = getPayloadRetentionMaxSize();
|
|
748
|
-
if (
|
|
1054
|
+
if (
|
|
1055
|
+
mode !== 'stage' &&
|
|
1056
|
+
typeof payloadSize === 'number' &&
|
|
1057
|
+
payloadSize > retentionMaxSize &&
|
|
1058
|
+
recorder.getFailedPeers().length === 0
|
|
1059
|
+
) {
|
|
749
1060
|
const freed = recorder.dropPayload();
|
|
750
1061
|
if (freed > 0) emit('payload_dropped', { payload_size: freed, max_size: retentionMaxSize });
|
|
751
1062
|
}
|
|
752
|
-
emit('phase', { phase: 'success', status: 'done' });
|
|
753
|
-
await recorder.finish('success');
|
|
1063
|
+
emit('phase', { phase: mode === 'stage' ? 'staged' : 'success', status: 'done' });
|
|
1064
|
+
await recorder.finish(mode === 'stage' ? 'staged' : 'success');
|
|
1065
|
+
if (mode === 'stage') {
|
|
1066
|
+
response.message =
|
|
1067
|
+
`Staged: ${application.name}. Deploy it with deploy_component ` + `deployment_id=${recorder.deploymentId}`;
|
|
1068
|
+
}
|
|
754
1069
|
}
|
|
755
1070
|
return response;
|
|
756
1071
|
} catch (err) {
|
|
@@ -791,7 +1106,7 @@ async function deployComponent(req) {
|
|
|
791
1106
|
// install output, and failed_peers the caller needs.
|
|
792
1107
|
if (recorder) {
|
|
793
1108
|
try {
|
|
794
|
-
await recorder.finish('failed', err);
|
|
1109
|
+
await recorder.finish(stagedOnOrigin ? 'staged' : 'failed', err);
|
|
795
1110
|
} catch (finishErr) {
|
|
796
1111
|
log.warn('Failed to record deployment failure row', finishErr);
|
|
797
1112
|
}
|
|
@@ -800,6 +1115,43 @@ async function deployComponent(req) {
|
|
|
800
1115
|
}
|
|
801
1116
|
}
|
|
802
1117
|
|
|
1118
|
+
/**
|
|
1119
|
+
* Whether the root-config entry an activation published is still the one on disk — the condition for that
|
|
1120
|
+
* activation's failure to take it back. A `set_configuration` acknowledged while the swap was retrying is
|
|
1121
|
+
* not this operation's to overwrite, and restoring a snapshot taken before it would silently drop it.
|
|
1122
|
+
*
|
|
1123
|
+
* Split out from the caller so it can be tested without a deploy: this is the only guard on a hazard the
|
|
1124
|
+
* preparation lock does not cover, and both ways it can regress fail silently. `refreshConfig` is called
|
|
1125
|
+
* BEFORE the read and is load-bearing — `set_configuration` writes the config file without refreshing this
|
|
1126
|
+
* process's config object, so a cached read compares against a value that predates the very change the
|
|
1127
|
+
* guard exists to protect and always concludes nothing moved. It narrows the window rather than closing
|
|
1128
|
+
* it; serializing config publication is #2315 step 3.
|
|
1129
|
+
*
|
|
1130
|
+
* `published` must be the entry as read back after publication, not as passed in: only two values that went
|
|
1131
|
+
* through the same write-and-parse round trip are comparable, and comparing against the argument would read
|
|
1132
|
+
* any serialization difference as a concurrent change and skip every undo.
|
|
1133
|
+
*/
|
|
1134
|
+
function publishedEntryStillStands(published, refreshConfig, readCurrentEntry) {
|
|
1135
|
+
refreshConfig();
|
|
1136
|
+
return isDeepStrictEqual(readCurrentEntry(), published);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Peers that did not answer a stage with `staged: true` — either unreachable, or running a build that
|
|
1141
|
+
* predates staged deploys and therefore treated the request as an ordinary deploy.
|
|
1142
|
+
*
|
|
1143
|
+
* Split out from the caller so it can be tested without a cluster: this is the only safety net for the
|
|
1144
|
+
* mixed-version hazard #2315 records as accepted, and a regression in it fails silently. The entry shape
|
|
1145
|
+
* belongs to harper-pro's replicator rather than this repo — `normalizePeerResult` already tolerates more
|
|
1146
|
+
* than one — so the marker is read flat or from a wrapped body; assuming flat would report every peer in a
|
|
1147
|
+
* fully-upgraded cluster as unconfirmed.
|
|
1148
|
+
*/
|
|
1149
|
+
function unconfirmedStagingPeers(replicated) {
|
|
1150
|
+
if (!Array.isArray(replicated)) return [];
|
|
1151
|
+
const confirmed = (peer) => peer?.staged === true || peer?.value?.staged === true || peer?.body?.staged === true;
|
|
1152
|
+
return replicated.filter((peer) => peer && !confirmed(peer));
|
|
1153
|
+
}
|
|
1154
|
+
|
|
803
1155
|
// Ring buffer of install stdout/stderr lines, capped by both line count and bytes so
|
|
804
1156
|
// a chatty install can't unbounded-grow the error response. snapshot() reports whether
|
|
805
1157
|
// the head was dropped so callers can flag truncation.
|
|
@@ -869,9 +1221,13 @@ async function getComponents() {
|
|
|
869
1221
|
const list = await fs.readdir(dir, { withFileTypes: true });
|
|
870
1222
|
for (let item of list) {
|
|
871
1223
|
const itemName = item.name;
|
|
1224
|
+
// Deny-list, not a dot-prefix skip: component CONTENTS legitimately include dot-files
|
|
1225
|
+
// (`.aiignore`, `.env.example`) that callers expect to see, so only Harper's own
|
|
1226
|
+
// bookkeeping directories are excluded by name.
|
|
872
1227
|
if (
|
|
873
1228
|
itemName === 'node_modules' ||
|
|
874
1229
|
itemName === ASIDE_STAGING_DIR ||
|
|
1230
|
+
itemName === DEPLOY_STAGING_DIR ||
|
|
875
1231
|
itemName === COMPONENT_PREPARATION_LOCK_DIR
|
|
876
1232
|
)
|
|
877
1233
|
continue;
|
|
@@ -1168,33 +1524,99 @@ async function dropComponent(req) {
|
|
|
1168
1524
|
|
|
1169
1525
|
const { project, file } = req;
|
|
1170
1526
|
const projectPath = req.file ? path.join(project, file) : project;
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1527
|
+
const componentsRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
|
|
1528
|
+
const componentPath = path.join(componentsRoot, project);
|
|
1529
|
+
const pathToComponent = path.join(componentsRoot, projectPath);
|
|
1530
|
+
let restartScope;
|
|
1531
|
+
|
|
1532
|
+
let response;
|
|
1533
|
+
await withComponentPreparationLock(
|
|
1534
|
+
componentPath,
|
|
1535
|
+
async () => {
|
|
1536
|
+
if (req.restart === true) {
|
|
1537
|
+
let runningApplications;
|
|
1538
|
+
try {
|
|
1539
|
+
runningApplications = await manageThreads.getRunningIsolatedApplications(
|
|
1540
|
+
ISOLATED_TOPOLOGY_REQUEST_TIMEOUT_MS
|
|
1541
|
+
);
|
|
1542
|
+
} catch (error) {
|
|
1543
|
+
throw handleHDBError(
|
|
1544
|
+
error,
|
|
1545
|
+
`Cannot drop '${project}' with a restart: the main thread's worker topology is unavailable: ${error.message}`,
|
|
1546
|
+
HTTP_STATUS_CODES.SERVICE_UNAVAILABLE
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
if (runningApplications.includes(project)) restartScope = project;
|
|
1550
|
+
}
|
|
1551
|
+
const componentSymlink = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'node_modules', project);
|
|
1552
|
+
if (!file && (await fs.pathExists(componentSymlink))) {
|
|
1553
|
+
await fs.unlink(componentSymlink);
|
|
1554
|
+
}
|
|
1177
1555
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1556
|
+
if (!file) {
|
|
1557
|
+
await dropComponentDirectory(componentPath, project, log);
|
|
1558
|
+
} else if (await fs.pathExists(pathToComponent)) {
|
|
1559
|
+
await fs.remove(pathToComponent);
|
|
1560
|
+
}
|
|
1181
1561
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
}
|
|
1562
|
+
const packageJsonPath = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'package.json');
|
|
1563
|
+
if (!file && (await fs.pathExists(packageJsonPath))) {
|
|
1564
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
1565
|
+
if (packageJson?.dependencies?.[project]) {
|
|
1566
|
+
delete packageJson.dependencies[project];
|
|
1567
|
+
}
|
|
1568
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
|
|
1569
|
+
}
|
|
1191
1570
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1571
|
+
if (!file) configUtils.deleteConfigFromFile([project]);
|
|
1572
|
+
if (!file && isMainThread) env.initSync(true);
|
|
1573
|
+
response = await server.replication.replicateOperation(req);
|
|
1574
|
+
const { applicationHasBranchStorage, removeBranchesForApplication } = require('../resources/branchDatabase.ts');
|
|
1575
|
+
const branched = !file && applicationHasBranchStorage(project);
|
|
1576
|
+
if (req.restart !== true) {
|
|
1577
|
+
response.message = `Successfully dropped: ${projectPath}`;
|
|
1578
|
+
if (branched) response.message += BRANCH_STORAGE_RETAINED;
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
// The branches go after the restart, under this lock; a worker is one of the threads being
|
|
1582
|
+
// replaced, so it hands both to the main thread.
|
|
1583
|
+
response.message = `Successfully dropped: ${projectPath}, restarting Harper`;
|
|
1584
|
+
if (!isMainThread) {
|
|
1585
|
+
parentPort.postMessage({
|
|
1586
|
+
type: hdbTerms.ITC_EVENT_TYPES.RESTART,
|
|
1587
|
+
workerType: 'http',
|
|
1588
|
+
scope: manageThreads.encodeRestartScope(restartScope),
|
|
1589
|
+
removeBranchesFor: file ? undefined : project,
|
|
1590
|
+
});
|
|
1591
|
+
if (branched) {
|
|
1592
|
+
response.message +=
|
|
1593
|
+
'; the main thread removes its branched database storage after the restart and logs the outcome';
|
|
1594
|
+
}
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
const { awaitRestart } = require('./awaitRestart.ts');
|
|
1598
|
+
const restart = await awaitRestart((onProgress) =>
|
|
1599
|
+
manageThreads.restartWorkers('http', undefined, undefined, onProgress, restartScope)
|
|
1600
|
+
);
|
|
1601
|
+
logRestartOutcome(restart, `dropping ${projectPath}`);
|
|
1602
|
+
if (!branched) return;
|
|
1603
|
+
if (restart.completed && !restart.workersKeptOnOldCode) {
|
|
1604
|
+
try {
|
|
1605
|
+
await removeBranchesForApplication(project);
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
log.error(`Branched database storage of ${project} could not be removed`, error);
|
|
1608
|
+
throw new Error(
|
|
1609
|
+
`Successfully dropped: ${projectPath}, but its branched database storage could not be removed and ` +
|
|
1610
|
+
`was left in place: ${error.message}. Drop it again with restart: true to retry.`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
} else {
|
|
1614
|
+
log.warn(`Branched database storage of ${project} was left in place: the restart did not complete`);
|
|
1615
|
+
response.message += BRANCH_STORAGE_RETAINED;
|
|
1616
|
+
}
|
|
1617
|
+
},
|
|
1618
|
+
componentDropLockOptions(project)
|
|
1619
|
+
);
|
|
1198
1620
|
return response;
|
|
1199
1621
|
}
|
|
1200
1622
|
|
|
@@ -1207,6 +1629,8 @@ exports.addComponent = addComponent;
|
|
|
1207
1629
|
exports.dropCustomFunctionProject = dropCustomFunctionProject;
|
|
1208
1630
|
exports.packageComponent = packageComponent;
|
|
1209
1631
|
exports.deployComponent = deployComponent;
|
|
1632
|
+
exports.unconfirmedStagingPeers = unconfirmedStagingPeers;
|
|
1633
|
+
exports.publishedEntryStillStands = publishedEntryStillStands;
|
|
1210
1634
|
exports.getComponents = getComponents;
|
|
1211
1635
|
exports.getComponentFile = getComponentFile;
|
|
1212
1636
|
exports.setComponentFile = setComponentFile;
|