@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
|
@@ -47,6 +47,8 @@ import { handleHDBError, hdbErrors } from '../utility/errors/hdbError.ts';
|
|
|
47
47
|
import * as regDeprecated from '../resources/registrationDeprecated.ts';
|
|
48
48
|
import * as deploymentOperations from '../components/deploymentOperations.ts';
|
|
49
49
|
import * as secretOperations from '../components/secretOperations.ts';
|
|
50
|
+
import * as trustPolicyOperations from '../security/authn/oidc/trustPolicyOperations.ts';
|
|
51
|
+
import * as tokenExchange from '../security/authn/oidc/tokenExchange.ts';
|
|
50
52
|
|
|
51
53
|
const requiredPermissions = new Map();
|
|
52
54
|
const DELETE_PERM = 'delete';
|
|
@@ -88,6 +90,8 @@ const BULK_OPS = {
|
|
|
88
90
|
CSV_FILE_LOAD: 'csvFileLoad',
|
|
89
91
|
IMPORT_FROM_S3: 'importFromS3',
|
|
90
92
|
};
|
|
93
|
+
/** The same set, precomputed: `isBulkLoadOperation` runs on every operations authorization. */
|
|
94
|
+
const BULK_OP_NAMES = new Set(Object.values(BULK_OPS));
|
|
91
95
|
|
|
92
96
|
const STRUCTURE_USER_OPS = [
|
|
93
97
|
schema.createTable.name,
|
|
@@ -147,6 +151,13 @@ requiredPermissions.set(
|
|
|
147
151
|
write.upsert.name,
|
|
148
152
|
new (permission as any)(false, [INSERT_PERM, UPDATE_PERM], terms.OPERATIONS_ENUM.UPSERT)
|
|
149
153
|
);
|
|
154
|
+
// Same grants as `upsert`, and the same as REST `PUT` enforces: creating needs insert, replacing an
|
|
155
|
+
// existing record needs update. A full replace can DROP attributes, which is why the
|
|
156
|
+
// attribute-scoped denial below applies to it.
|
|
157
|
+
requiredPermissions.set(
|
|
158
|
+
write.put.name,
|
|
159
|
+
new (permission as any)(false, [INSERT_PERM, UPDATE_PERM], terms.OPERATIONS_ENUM.PUT)
|
|
160
|
+
);
|
|
150
161
|
requiredPermissions.set(
|
|
151
162
|
search.searchByConditions.name,
|
|
152
163
|
new (permission as any)(false, [READ_PERM], terms.OPERATIONS_ENUM.SEARCH_BY_CONDITIONS)
|
|
@@ -359,6 +370,28 @@ requiredPermissions.set(
|
|
|
359
370
|
new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_SECRETS_PUBLIC_KEY)
|
|
360
371
|
);
|
|
361
372
|
|
|
373
|
+
// OIDC trust policies (#2171). A policy lets an external CI run authenticate as a Harper user, so
|
|
374
|
+
// these are SU-only; the handlers ALSO enforce super_user directly so they cannot be delegated
|
|
375
|
+
// through a role's `operations` allowlist (gate-2 bypass below).
|
|
376
|
+
requiredPermissions.set(
|
|
377
|
+
trustPolicyOperations.addOidcTrust.name,
|
|
378
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.ADD_OIDC_TRUST)
|
|
379
|
+
);
|
|
380
|
+
requiredPermissions.set(
|
|
381
|
+
trustPolicyOperations.listOidcTrust.name,
|
|
382
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.LIST_OIDC_TRUST)
|
|
383
|
+
);
|
|
384
|
+
requiredPermissions.set(
|
|
385
|
+
trustPolicyOperations.dropOidcTrust.name,
|
|
386
|
+
new (permission as any)(true, [], terms.OPERATIONS_ENUM.DROP_OIDC_TRUST)
|
|
387
|
+
);
|
|
388
|
+
// The exchange is unauthenticated by design — it authenticates its own caller against a trust
|
|
389
|
+
// policy, the way create_authentication_tokens does against a password.
|
|
390
|
+
requiredPermissions.set(
|
|
391
|
+
tokenExchange.exchangeOidcToken.name,
|
|
392
|
+
new (permission as any)(false, [], terms.OPERATIONS_ENUM.EXCHANGE_OIDC_TOKEN)
|
|
393
|
+
);
|
|
394
|
+
|
|
362
395
|
//Below are functions that are currently open to all roles
|
|
363
396
|
requiredPermissions.set(regDeprecated.getRegistrationInfo.name, new (permission as any)(false, []));
|
|
364
397
|
requiredPermissions.set(user.userInfo.name, new (permission as any)(false, [], terms.OPERATIONS_ENUM.USER_INFO));
|
|
@@ -406,6 +439,7 @@ requiredPermissions.set(terms.VALID_SQL_OPS_ENUM.UPDATE, new (permission as any)
|
|
|
406
439
|
module.exports = {
|
|
407
440
|
verifyPerms,
|
|
408
441
|
verifyPermsAST,
|
|
442
|
+
verifyOperationsAllowlist,
|
|
409
443
|
verifyBulkLoadAttributePerms,
|
|
410
444
|
registerOperationPermission,
|
|
411
445
|
unregisterOperationPermission,
|
|
@@ -418,7 +452,77 @@ module.exports = {
|
|
|
418
452
|
* @param operation - The operation specified in the call.
|
|
419
453
|
* @returns {null | PermissionResponseObject} - null if permissions match, errors returned in the PermissionResponseObject
|
|
420
454
|
*/
|
|
421
|
-
|
|
455
|
+
/**
|
|
456
|
+
* Token-scoped narrowing: a minted operation token may carry a subset of what its user's role allows,
|
|
457
|
+
* so one credential can be handed out with less authority than the user has (an OIDC trust policy
|
|
458
|
+
* uses this to scope a single workflow). Returns a denial, or undefined when the scope permits — or
|
|
459
|
+
* when there is no scope, which is every token minted before this existed.
|
|
460
|
+
*
|
|
461
|
+
* Shared by verifyPerms AND verifyPermsAST, and called first in both. Four ways this gets bypassed
|
|
462
|
+
* if it moves or is incomplete:
|
|
463
|
+
*
|
|
464
|
+
* 1. `sql` dispatches to verifyPermsAST, in a branch mutually exclusive with the verifyPerms call
|
|
465
|
+
* (server/serverHelpers/serverUtilities.ts). A gate in only one of them means a token scoped to
|
|
466
|
+
* `get_status` can still run arbitrary SQL against whatever its role can reach.
|
|
467
|
+
* 2. Both functions `return null` early for a super_user — the identity that most needs constraining.
|
|
468
|
+
* 3. verifyPerms' `operations` gate 2 also returns null, treating an explicit listing of an SU-only
|
|
469
|
+
* operation as a deliberate grant.
|
|
470
|
+
* 4. Operations that never reach verifyPerms at all: create_authentication_tokens (NO_AUTH),
|
|
471
|
+
* refresh_operation_token, and impersonation each PRODUCE a new credential or principal, so the
|
|
472
|
+
* scope has to be carried forward there too or a scoped token mints an unscoped one. That carry-
|
|
473
|
+
* forward lives in tokenAuthentication.ts and impersonation.ts, not here.
|
|
474
|
+
*
|
|
475
|
+
* It can only subtract. The scope is deliberately NOT merged into `permission.operations`, because
|
|
476
|
+
* that field is not purely narrowing (see gate 2) — merging into it could widen instead.
|
|
477
|
+
*
|
|
478
|
+
* `apiOperation` MUST be the snake_case API operation the caller actually invoked (`deploy_component`,
|
|
479
|
+
* `sql`, `export_local`, ...) — i.e. `requestJson.operation`, the same namespace the policy's scope is
|
|
480
|
+
* written in. It is emphatically NOT the handler function name: many handlers have no `api_name`
|
|
481
|
+
* mapping (deploy_component → `deployComponent`) and some are shared across operations
|
|
482
|
+
* (`search_by_id`/`search_by_hash`), so resolving the scope name from the handler both denies the
|
|
483
|
+
* feature's own headline op and conflates aliases. Callers pass the real operation.
|
|
484
|
+
*/
|
|
485
|
+
function tokenScopeDenial(userObject: any, apiOperation: string) {
|
|
486
|
+
// `!= null`, not `!== undefined`: an unscoped policy stores `operations: null`, and expanding a
|
|
487
|
+
// null would throw rather than fall through to the role.
|
|
488
|
+
const tokenOperations = userObject?.tokenOperations;
|
|
489
|
+
if (tokenOperations == null) return undefined;
|
|
490
|
+
|
|
491
|
+
// `instanceof Set`, not just presence: this memo rides on hdb_user, and a job persists that user
|
|
492
|
+
// into hdb_job.request, where msgpackr round-trips a Set back as a plain Array. Trusting the
|
|
493
|
+
// memo's presence would then call .has() on an Array and throw a TypeError out of the auth gate
|
|
494
|
+
// — still fail-closed, but as a 500 rather than a denial, and only inside a job worker.
|
|
495
|
+
let scopedOps = userObject._expandedTokenOperations;
|
|
496
|
+
if (!(scopedOps instanceof Set)) {
|
|
497
|
+
scopedOps = userObject._expandedTokenOperations = expandOperationsPerms(tokenOperations);
|
|
498
|
+
}
|
|
499
|
+
if (scopedOps.has(apiOperation)) return undefined;
|
|
500
|
+
|
|
501
|
+
harperLogger.info(`Operation '${apiOperation}' is outside the scope of the presented token`);
|
|
502
|
+
return new PermissionResponseObject().handleUnauthorizedItem(HDB_ERROR_MSGS.OP_NOT_IN_OPERATIONS(apiOperation));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* A scope naming `sql` grants the SQL interface, not unrestricted DML through it.
|
|
507
|
+
*
|
|
508
|
+
* `read_only` expands to include `sql` — the group defers DML enforcement to table CRUD permissions
|
|
509
|
+
* (see its note in operationPermissions.ts) — but verifyPermsAST returns null outright for a
|
|
510
|
+
* super_user before any table check runs. Without this, a token scoped to `read_only` could DELETE,
|
|
511
|
+
* which is the one thing that name promises it cannot do.
|
|
512
|
+
*
|
|
513
|
+
* So a write statement additionally requires its matching data operation in scope. That is exactly
|
|
514
|
+
* what separates `read_only` (no insert/update/delete) from `standard_user` (all three), with no
|
|
515
|
+
* need to track which group admitted `sql`. A bare `['sql']` therefore admits SELECT only; name the
|
|
516
|
+
* write operations alongside it to allow more. Anything that is not a recognized SELECT falls
|
|
517
|
+
* through to the same check and is denied unless named, so an unfamiliar statement type fails closed.
|
|
518
|
+
*/
|
|
519
|
+
function sqlWriteScopeDenial(userObject: any, sqlVariant: string) {
|
|
520
|
+
if (userObject?.tokenOperations == null) return undefined;
|
|
521
|
+
if (sqlVariant === terms.VALID_SQL_OPS_ENUM.SELECT) return undefined;
|
|
522
|
+
return tokenScopeDenial(userObject, sqlVariant);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function verifyPermsAST(ast, userObject, operation, apiOperation = terms.OPERATIONS_ENUM.SQL) {
|
|
422
526
|
//TODO - update these validation checks to use validate.js
|
|
423
527
|
if (commonUtils.isEmptyOrZeroLength(ast)) {
|
|
424
528
|
harperLogger.info('verify_perms_ast has an empty user parameter');
|
|
@@ -432,6 +536,15 @@ export function verifyPermsAST(ast, userObject, operation) {
|
|
|
432
536
|
harperLogger.info('verify_perms_ast has a null operation parameter');
|
|
433
537
|
throw handleHDBError(new Error());
|
|
434
538
|
}
|
|
539
|
+
|
|
540
|
+
// `operation` here is the SQL statement variant (select/insert/...), not the API operation. The
|
|
541
|
+
// scope is checked against `apiOperation` — the top-level operation the caller invoked, which is
|
|
542
|
+
// `sql` for a direct SQL call but `export_local`/`export_to_s3` for a job whose inner
|
|
543
|
+
// search_operation is SQL. Checking a hardcoded `sql` would let a token scoped only to `sql` start
|
|
544
|
+
// an export that its scope excludes. Ahead of the super_user bypass, as on the NoSQL path.
|
|
545
|
+
const scopeDenial = tokenScopeDenial(userObject, apiOperation) ?? sqlWriteScopeDenial(userObject, operation);
|
|
546
|
+
if (scopeDenial) return scopeDenial;
|
|
547
|
+
|
|
435
548
|
try {
|
|
436
549
|
const bucketModule = require('../sqlTranslator/sql_statement_bucket');
|
|
437
550
|
const bucket = bucketModule.default || bucketModule;
|
|
@@ -520,6 +633,28 @@ export function verifyPermsAST(ast, userObject, operation) {
|
|
|
520
633
|
}
|
|
521
634
|
}
|
|
522
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Gate 1 of the role `operations` allowlist, callable from every authorization path. The SQL path
|
|
638
|
+
* (chooseOperation → checkASTPermissions) never reaches verifyPerms, so it must call this
|
|
639
|
+
* directly — otherwise an allowlisted role could reach unlisted operations through `sql`.
|
|
640
|
+
* Returns null when allowed (or no allowlist present), a PermissionResponseObject denial otherwise.
|
|
641
|
+
*/
|
|
642
|
+
export function verifyOperationsAllowlist(requestJson: any, operationFunctionName: string) {
|
|
643
|
+
const rolePermission = requestJson.hdb_user?.role?.permission;
|
|
644
|
+
const allowedOperationsList = rolePermission?.operations;
|
|
645
|
+
if (allowedOperationsList == null) return null;
|
|
646
|
+
// _expandedOperations is pre-built at cache-load time (O(1) lookup).
|
|
647
|
+
// Fall back to on-demand expansion for inline-asserted roles (e.g. impersonation via hdb_user in body).
|
|
648
|
+
const allowedOps = rolePermission._expandedOperations ?? expandOperationsPerms(allowedOperationsList);
|
|
649
|
+
// operationFunctionName is the internal camelCase function name; allowedOps contains snake_case
|
|
650
|
+
// API names. Resolve via the api_name stored on the permission entry (set at registration time).
|
|
651
|
+
const opApiName = requiredPermissions.get(operationFunctionName)?.api_name ?? operationFunctionName;
|
|
652
|
+
if (!allowedOps.has(opApiName)) {
|
|
653
|
+
return new PermissionResponseObject().handleUnauthorizedItem(HDB_ERROR_MSGS.OP_NOT_IN_OPERATIONS(opApiName));
|
|
654
|
+
}
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
|
|
523
658
|
/**
|
|
524
659
|
* Verifies permissions and restrictions for the NoSQL operation based on the user's assigned role.
|
|
525
660
|
*
|
|
@@ -527,7 +662,7 @@ export function verifyPermsAST(ast, userObject, operation) {
|
|
|
527
662
|
* @param operation - The name of the operation specified in the request.
|
|
528
663
|
* @returns { null | PermissionResponseObject } - null if permissions match, errors are consolidated into PermissionResponseObj.
|
|
529
664
|
*/
|
|
530
|
-
export function verifyPerms(requestJson: any, operation: any,
|
|
665
|
+
export function verifyPerms(requestJson: any, operation: any, options?: { apiOperation?: string }) {
|
|
531
666
|
if (
|
|
532
667
|
requestJson === null ||
|
|
533
668
|
operation === null ||
|
|
@@ -546,18 +681,51 @@ export function verifyPerms(requestJson: any, operation: any, _options?: any) {
|
|
|
546
681
|
op = operation;
|
|
547
682
|
}
|
|
548
683
|
//we need to use the action value, if present, to ensure the correct permission is checked below
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
684
|
+
// `action` narrows the required permissions to just that one — `hasPermissions` does it for table
|
|
685
|
+
// permissions and `checkAttributePerms` for attribute permissions — because a bulk load carries
|
|
686
|
+
// `action: insert|update|upsert` and only needs the permission for what it will actually do.
|
|
687
|
+
//
|
|
688
|
+
// Honoured ONLY for a real bulk-load operation. `action` is an unknown-but-accepted key on the
|
|
689
|
+
// shared write validator, so taking it from any request let a caller pick which half of an
|
|
690
|
+
// operation's permissions to be checked against: `{operation:'put', action:'insert'}` from a role
|
|
691
|
+
// with `insert: true, update: false` replaced an existing record, and `action:'update'` created a
|
|
692
|
+
// missing one with no insert permission. `upsert` had the same shape before `put` existed.
|
|
693
|
+
//
|
|
694
|
+
// Both narrowing sites read this one variable, so gating it here covers both. The bulk path is
|
|
695
|
+
// unaffected: `verifyBulkLoadAttributePerms` receives the real action from
|
|
696
|
+
// `bulkLoad.validateChunk`, not from here.
|
|
697
|
+
let action = isBulkLoadOperation(op) ? requestJson.action : undefined;
|
|
698
|
+
|
|
699
|
+
// Resolved through the same helper the handlers use (`transformReq` delegates to it), because
|
|
700
|
+
// authorization runs BEFORE `transformReq` and any disagreement about the target means the
|
|
701
|
+
// permissions checked are not the permissions for the write that happens. This used to be a
|
|
702
|
+
// local `schema ?? database`, which diverged from the handlers in two separately exploitable
|
|
703
|
+
// ways: it kept a falsy-but-present `database: 0` instead of defaulting, and it preferred
|
|
704
|
+
// `schema` where the handlers prefer `database` — so a request could be authorized against one
|
|
705
|
+
// database and written to another. With no target resolved at all, `schemaTableMap` stayed empty
|
|
706
|
+
// and `hasPermissions` iterated nothing, authorizing by vacuous truth.
|
|
707
|
+
let operationSchema = commonUtils.resolveTargetDatabase(requestJson);
|
|
552
708
|
let table = requestJson.table;
|
|
553
709
|
|
|
554
710
|
let schemaTableMap = new Map();
|
|
555
|
-
|
|
711
|
+
// `table != undefined`, not truthy: `hdbTable` is `Joi.alternatives(Joi.string(), Joi.number())`
|
|
712
|
+
// (validation/common_validators.ts), so a table named `0` validates and a truthy test drops it
|
|
713
|
+
// from the map — leaving `hasPermissions` nothing to iterate, which authorizes by vacuous truth.
|
|
714
|
+
// The same falsy-vs-nullish distinction the database resolution above turns on.
|
|
715
|
+
if (operationSchema != undefined && table != undefined) {
|
|
556
716
|
schemaTableMap.set(operationSchema, [table]);
|
|
557
717
|
}
|
|
558
718
|
|
|
559
719
|
const permsResponse = new PermissionResponseObject();
|
|
560
720
|
|
|
721
|
+
// The top-level API operation the caller invoked — the namespace the policy scope is written in.
|
|
722
|
+
// For a job (export_local/export_to_s3), the dispatcher passes the nested search_operation as
|
|
723
|
+
// requestJson, so requestJson.operation is the *inner* op (e.g. search_by_conditions); the caller
|
|
724
|
+
// threads the real top-level op through `options.apiOperation` so a read-scoped token cannot ride
|
|
725
|
+
// an export. Never `op` (the handler name), which would deny deploy_component and conflate aliases.
|
|
726
|
+
const scopeDenial = tokenScopeDenial(requestJson.hdb_user, options?.apiOperation ?? requestJson.operation);
|
|
727
|
+
if (scopeDenial) return scopeDenial;
|
|
728
|
+
|
|
561
729
|
if (
|
|
562
730
|
commonUtils.isEmptyOrZeroLength(requestJson.hdb_user?.role) ||
|
|
563
731
|
commonUtils.isEmptyOrZeroLength(requestJson.hdb_user?.role?.permission)
|
|
@@ -568,6 +736,15 @@ export function verifyPerms(requestJson: any, operation: any, _options?: any) {
|
|
|
568
736
|
return permsResponse.handleUnauthorizedItem(HDB_ERROR_MSGS.USER_HAS_NO_PERMS(requestJson.hdb_user?.username));
|
|
569
737
|
}
|
|
570
738
|
|
|
739
|
+
// Gate 1 of the optional `operations` allowlist: when present, only ops explicitly listed (or
|
|
740
|
+
// expanded from a group) are reachable. This must run before EVERY privilege early-return below
|
|
741
|
+
// (super_user, structure_user, system-table allowances) — otherwise a role combining one of
|
|
742
|
+
// those flags with a restrictive allowlist could reach ops outside its list. Gate 2 (the
|
|
743
|
+
// SU-only-op bypass for explicitly listed ops) stays below, after the ambient privilege checks.
|
|
744
|
+
const allowlistDenial = verifyOperationsAllowlist(requestJson, op);
|
|
745
|
+
if (allowlistDenial) return allowlistDenial;
|
|
746
|
+
const allowedOperationsList = requestJson.hdb_user?.role?.permission?.operations;
|
|
747
|
+
|
|
571
748
|
const isSuperUser = !!requestJson.hdb_user?.role?.permission?.super_user;
|
|
572
749
|
const structureUser = requestJson.hdb_user?.role?.permission?.structure_user;
|
|
573
750
|
// set to true if this operation affects a system table. Only su can read from system tables, but can't update/delete.
|
|
@@ -618,36 +795,18 @@ export function verifyPerms(requestJson: any, operation: any, _options?: any) {
|
|
|
618
795
|
);
|
|
619
796
|
}
|
|
620
797
|
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
// op is the internal camelCase function name; allowedOps contains snake_case API names.
|
|
634
|
-
// Resolve via the api_name stored on the permission entry (set at registration time).
|
|
635
|
-
const opApiName = requiredPermissions.get(op)?.api_name ?? op;
|
|
636
|
-
// Gate 1: op not in allowlist — deny regardless of table CRUD permissions.
|
|
637
|
-
if (!allowedOps.has(opApiName)) {
|
|
638
|
-
return permsResponse.handleUnauthorizedItem(HDB_ERROR_MSGS.OP_NOT_IN_OPERATIONS(opApiName));
|
|
639
|
-
}
|
|
640
|
-
// Gate 2: op is SU-only but was explicitly granted via operations — allow without super_user.
|
|
641
|
-
// Without this, the SU check further below would still deny it even though it passed gate 1.
|
|
642
|
-
// TODO: ops registered with both requires_su AND non-empty CRUD perms have their table-level
|
|
643
|
-
// CRUD check bypassed here. Should fall through for those instead of returning null
|
|
644
|
-
// unconditionally. The managed-backup ops share this shape but self-enforce super_user in their
|
|
645
|
-
// handlers/validators (dataLayer/rocksdbBackup.ts requireSuperUser), so they are not delegable
|
|
646
|
-
// regardless; get_backup remains the one that relies solely on this gate. Low risk today but
|
|
647
|
-
// worth tightening.
|
|
648
|
-
if (requiredPermissions.get(op)?.requires_su) {
|
|
649
|
-
return null;
|
|
650
|
-
}
|
|
798
|
+
// Gate 2 of the `operations` allowlist (gate 1 ran above, before the privilege early-returns):
|
|
799
|
+
// the op passed the allowlist, so if it is normally restricted to super_user, the explicit
|
|
800
|
+
// listing is treated as a deliberate admin grant and allowed immediately (return null).
|
|
801
|
+
// Non-SU ops that passed gate 1 fall through to the normal table CRUD checks below.
|
|
802
|
+
// TODO: ops registered with both requires_su AND non-empty CRUD perms have their table-level
|
|
803
|
+
// CRUD check bypassed here. Should fall through for those instead of returning null
|
|
804
|
+
// unconditionally. The managed-backup ops share this shape but self-enforce super_user in their
|
|
805
|
+
// handlers/validators (dataLayer/rocksdbBackup.ts requireSuperUser), so they are not delegable
|
|
806
|
+
// regardless; get_backup remains the one that relies solely on this gate. Low risk today but
|
|
807
|
+
// worth tightening.
|
|
808
|
+
if (allowedOperationsList !== undefined && requiredPermissions.get(op)?.requires_su) {
|
|
809
|
+
return null;
|
|
651
810
|
}
|
|
652
811
|
|
|
653
812
|
const fullRolePerms = permsTranslator.getRolePermissions(requestJson.hdb_user?.role);
|
|
@@ -676,6 +835,16 @@ export function verifyPerms(requestJson: any, operation: any, _options?: any) {
|
|
|
676
835
|
}
|
|
677
836
|
}
|
|
678
837
|
|
|
838
|
+
// Fail closed on an unresolved target. `hasPermissions` iterates `schemaTableMap`, so an empty map
|
|
839
|
+
// authorizes by vacuous truth — the shape of this whole bug class, and of the SQL path's
|
|
840
|
+
// GHSA-5c29-q62v-jrwf, whose fix carries the same backstop. `resolveTargetDatabase` always
|
|
841
|
+
// returns a database, so a named table always populates the map and this is unreachable today; it
|
|
842
|
+
// is here so that a future change to target resolution fails safe instead of silently authorizing
|
|
843
|
+
// everything. That also means no test can cover it, which is the point rather than an omission.
|
|
844
|
+
if (table != undefined && schemaTableMap.size === 0) {
|
|
845
|
+
return permsResponse.handleUnauthorizedItem(HDB_ERROR_MSGS.UNKNOWN_OP_AUTH_ERROR(op, operationSchema, table));
|
|
846
|
+
}
|
|
847
|
+
|
|
679
848
|
let failedPermissions = hasPermissions(requestJson.hdb_user, op, schemaTableMap, permsResponse, action);
|
|
680
849
|
//check if failedTablePerms are back and return them B/C it will be an op-level permission issue
|
|
681
850
|
if (failedPermissions) {
|
|
@@ -706,8 +875,17 @@ export function verifyPerms(requestJson: any, operation: any, _options?: any) {
|
|
|
706
875
|
}
|
|
707
876
|
}
|
|
708
877
|
|
|
709
|
-
const recordAttrs = getRecordAttributes(requestJson);
|
|
878
|
+
const recordAttrs = getRecordAttributes(requestJson, op);
|
|
710
879
|
const attrPermissions = getAttributePermissions(requestJson.hdb_user?.role?.permission, operationSchema, table);
|
|
880
|
+
// A `put` replaces the whole record, so it REMOVES every attribute the request omits — and
|
|
881
|
+
// `checkAttributePerms` below only sees the attributes a request SUPPLIES, so it cannot police
|
|
882
|
+
// those removals. An attribute-scoped role could otherwise erase an attribute it has no `update`
|
|
883
|
+
// permission for by leaving it out. REST closes the same gap in `Table.allowUpdate` by restoring
|
|
884
|
+
// such attributes from the stored record, which needs the stored record; this runs before anything
|
|
885
|
+
// is read, so it denies instead. Roles that scope no attribute are unaffected.
|
|
886
|
+
if (attrPermissions.size > 0 && op === write.put.name) {
|
|
887
|
+
return permsResponse.handleUnauthorizedItem(HDB_ERROR_MSGS.PUT_WITH_ATTRIBUTE_PERMS(operationSchema, table));
|
|
888
|
+
}
|
|
711
889
|
checkAttributePerms(recordAttrs, attrPermissions, op, table, operationSchema, permsResponse, action);
|
|
712
890
|
|
|
713
891
|
//This result value will be null if no perms issues were found in checkAttributePerms
|
|
@@ -913,12 +1091,20 @@ export function checkAttributePerms(
|
|
|
913
1091
|
* @param json - json containing the request
|
|
914
1092
|
* @returns {Set} - all attributes affected by the request statement.
|
|
915
1093
|
*/
|
|
916
|
-
|
|
1094
|
+
/** Whether the operation checks attribute permissions per chunk rather than from this request. */
|
|
1095
|
+
function isBulkLoadOperation(operationName): boolean {
|
|
1096
|
+
return BULK_OP_NAMES.has(operationName);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function getRecordAttributes(json, operationName?) {
|
|
917
1100
|
let affectedAttributes = new Set();
|
|
918
1101
|
try {
|
|
919
|
-
//Bulk load operations
|
|
920
|
-
//
|
|
921
|
-
|
|
1102
|
+
// Bulk load operations have their attribute permissions checked per chunk instead, in
|
|
1103
|
+
// `bulkLoad.validateChunk` — the records aren't in this request. That opt-out is keyed on the
|
|
1104
|
+
// OPERATION, never on the caller-supplied `action` field: `action` is an unknown-but-accepted
|
|
1105
|
+
// key on the insert/update/upsert validator, so inferring the opt-out from its presence let any
|
|
1106
|
+
// direct request skip every attribute check by adding `action: "update"` to the body.
|
|
1107
|
+
if (isBulkLoadOperation(operationName)) {
|
|
922
1108
|
return affectedAttributes;
|
|
923
1109
|
}
|
|
924
1110
|
if (json.operation === terms.OPERATIONS_ENUM.SEARCH_BY_CONDITIONS) {
|
|
@@ -980,7 +1166,10 @@ export function getAttributePermissions(rolePerms, operationSchema, table) {
|
|
|
980
1166
|
}
|
|
981
1167
|
//Some commands do not require a table to be specified. If there is no table, there is likely not
|
|
982
1168
|
// anything attribute permissions needs to check.
|
|
983
|
-
|
|
1169
|
+
// Nullish rather than falsy for the same reason as the map guard in verifyPerms: a table named `0`
|
|
1170
|
+
// validates (`hdbTable` accepts a number), and skipping it here means its attribute permissions
|
|
1171
|
+
// go unchecked.
|
|
1172
|
+
if (operationSchema == undefined || table == undefined) {
|
|
984
1173
|
return roleAttributePermissions;
|
|
985
1174
|
}
|
|
986
1175
|
try {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
import { loggerWithTag } from './logging/harper_logger.ts';
|
|
4
|
+
|
|
5
|
+
const watchPathLogger = loggerWithTag('watcher');
|
|
6
|
+
let canonicalizationWarned = false;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Canonicalize an absolute path before it is handed to a native file watch (`fs.watch`, directly or
|
|
10
|
+
* through chokidar).
|
|
11
|
+
*
|
|
12
|
+
* libuv's Windows fs-event callback rebuilds each event's absolute path, expands it with
|
|
13
|
+
* `GetLongPathNameW`, and then asserts the expansion still starts with the directory it stored when
|
|
14
|
+
* the watch was armed. An 8.3 short directory (`C:\Users\RUNNER~1\...`) never survives that
|
|
15
|
+
* comparison, and the assertion aborts the process rather than failing the watch, so a short path
|
|
16
|
+
* reaching a native watch takes down the whole server (harper#2234).
|
|
17
|
+
*
|
|
18
|
+
* Every Windows path is resolved rather than only the ones that look short: `GetLongPathNameW`'s
|
|
19
|
+
* documentation is explicit that a short name need not contain a tilde, and NTFS allows an
|
|
20
|
+
* explicitly assigned one, so any spelling test would leave the abort reachable. `realpathSync` is
|
|
21
|
+
* not a substitute for the `.native` variant — it resolves symlinks but leaves 8.3 names intact.
|
|
22
|
+
*
|
|
23
|
+
* Returns `undefined` when the long form cannot be established, because a path we cannot prove is
|
|
24
|
+
* expanded is exactly the one that must never reach a native watch.
|
|
25
|
+
*/
|
|
26
|
+
export function canonicalizeWatchPath(watchPath: string, platform: string = process.platform): string | undefined {
|
|
27
|
+
if (platform !== 'win32') return watchPath;
|
|
28
|
+
try {
|
|
29
|
+
return realpathSync.native(watchPath);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (!error || typeof error !== 'object' || !('code' in error) || error.code !== 'ENOENT') return undefined;
|
|
32
|
+
// libuv stores only the parent directory of a file target and compares only that prefix, so a
|
|
33
|
+
// leaf that does not exist yet — a config file whose watcher is armed during the install window
|
|
34
|
+
// — still yields a watch path that cannot trip the assertion.
|
|
35
|
+
try {
|
|
36
|
+
return join(realpathSync.native(dirname(watchPath)), basename(watchPath));
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Watch target for a chokidar/`fs.watch` call: the canonical path, or the original path with
|
|
45
|
+
* `mustPoll`, which callers turn into their polling options. Polling stats the file instead of
|
|
46
|
+
* arming a native watch, so it cannot reach the abort.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveWatchTarget(watchPath: string): { path: string; mustPoll: boolean } {
|
|
49
|
+
const canonical = canonicalizeWatchPath(watchPath);
|
|
50
|
+
if (canonical !== undefined) return { path: canonical, mustPoll: false };
|
|
51
|
+
if (!canonicalizationWarned) {
|
|
52
|
+
canonicalizationWarned = true;
|
|
53
|
+
watchPathLogger.warn?.(
|
|
54
|
+
`Could not resolve the long-path form of ${watchPath}. Falling back to polling-based watching for ` +
|
|
55
|
+
'affected watchers, because handing a Windows 8.3 short path to a native watch aborts the process.'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return { path: watchPath, mustPoll: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function _resetForTests(): void {
|
|
62
|
+
canonicalizationWarned = false;
|
|
63
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
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
|
|
@@ -71,4 +72,151 @@ export function warnWatcherFallback(watchedPath: string): void {
|
|
|
71
72
|
// Test-only hook to reset the one-time warning gate between cases.
|
|
72
73
|
export function _resetForTests(): void {
|
|
73
74
|
exhaustionWarned = false;
|
|
75
|
+
lostNativeWatchCount = 0;
|
|
76
|
+
lostNativeWatchWarnThreshold = 1;
|
|
77
|
+
// Also drop the process listener, so a case that installed the guard can't leave one
|
|
78
|
+
// attached to the test runner's process for every case that follows.
|
|
79
|
+
process.removeListener('uncaughtException', handleUncaughtException);
|
|
80
|
+
lostNativeWatchGuardInstalled = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Lost native watch (predominantly Windows)
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
//
|
|
87
|
+
// Every Harper watcher runs `persistent: false`, which is the one chokidar
|
|
88
|
+
// branch that never attaches an 'error' listener to the underlying Node
|
|
89
|
+
// `fs.FSWatcher` (chokidar `handler.js`, `setFsWatchListener`; still so at
|
|
90
|
+
// 5.0.0). Its `errHandler` covers only a synchronous throw out of `fs.watch()`
|
|
91
|
+
// — the ENOSPC/EMFILE route above. An asynchronous failure is emitted on a
|
|
92
|
+
// handle with no listener, so Node makes it an uncaughtException without it
|
|
93
|
+
// ever reaching chokidar; `.on('error')` on the wrapper we hold cannot see it.
|
|
94
|
+
// The process is therefore the only place it is observable, which is what
|
|
95
|
+
// `installLostNativeWatchGuard()` uses.
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Returns `true` for the asynchronous "the watched path went away" error raised
|
|
99
|
+
* by Node's `fs.FSWatcher` — on Windows, `EPERM: operation not permitted, watch`
|
|
100
|
+
* (errno -4048) when a watched directory is removed or swapped out. Unlike
|
|
101
|
+
* {@link isWatcherExhaustionError} there is nothing to degrade to: polling a
|
|
102
|
+
* path that no longer exists only burns CPU.
|
|
103
|
+
*
|
|
104
|
+
* All three conditions are load-bearing, because whatever this claims is
|
|
105
|
+
* swallowed process-wide:
|
|
106
|
+
*
|
|
107
|
+
* - `syscall === 'watch'` is only ever set by fs watch handles.
|
|
108
|
+
* - `EPERM` only. An async `ENOENT` from a watch handle has never been
|
|
109
|
+
* observed, whereas a *synchronous* one is the ordinary "watch a path that
|
|
110
|
+
* isn't there" misconfiguration, which must stay fatal.
|
|
111
|
+
* - no `path`. Node populates `path` on the error it throws out of
|
|
112
|
+
* `fs.watch()` and leaves it absent on the one it delivers to the handle,
|
|
113
|
+
* so without this a raw `fs.watch(missingPath)` reaching the guard would be
|
|
114
|
+
* mistaken for a benign lost watch and swallowed.
|
|
115
|
+
*/
|
|
116
|
+
export function isLostNativeWatchError(error: unknown): boolean {
|
|
117
|
+
if (typeof error !== 'object' || error === null) return false;
|
|
118
|
+
const { code, syscall, path } = error as { code?: unknown; syscall?: unknown; path?: unknown };
|
|
119
|
+
return syscall === 'watch' && code === 'EPERM' && path == null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let lostNativeWatchCount = 0;
|
|
123
|
+
let lostNativeWatchWarnThreshold = 1;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* If `error` is a lost native watch error, mark it handled, log it, and report
|
|
127
|
+
* that it has been claimed. Exported so the per-watcher error routes can give it
|
|
128
|
+
* the same benign treatment on the day chokidar does deliver it to the wrapper
|
|
129
|
+
* (its polling and `persistent: true` branches already would).
|
|
130
|
+
*/
|
|
131
|
+
export function claimLostNativeWatchError(error: unknown): boolean {
|
|
132
|
+
if (!isLostNativeWatchError(error)) return false;
|
|
133
|
+
const claimed = error as { isHandled?: boolean };
|
|
134
|
+
// Counting one instance twice would trip the warn threshold early.
|
|
135
|
+
if (claimed.isHandled) return true;
|
|
136
|
+
lostNativeWatchCount++;
|
|
137
|
+
// The claim is the classification above; the bookkeeping that follows must not be able to
|
|
138
|
+
// undo it. A frozen error (`isHandled` unassignable) or a throwing logger would otherwise
|
|
139
|
+
// make a failure just classified as benign fatal — here, or in a caller's 'error' route.
|
|
140
|
+
try {
|
|
141
|
+
fallbackLogger.trace?.(`Lost native file watch handle (occurrence ${lostNativeWatchCount}):`, error);
|
|
142
|
+
// A subsystem that has silently stopped being watched is what an operator needs to see, and
|
|
143
|
+
// the default level is `warn`, so trace alone would hide every occurrence after the first.
|
|
144
|
+
// Warning at each decade keeps a delete storm bounded without ever going fully silent.
|
|
145
|
+
if (lostNativeWatchCount >= lostNativeWatchWarnThreshold) {
|
|
146
|
+
lostNativeWatchWarnThreshold *= 10;
|
|
147
|
+
fallbackLogger.warn?.(
|
|
148
|
+
`A native file watch handle failed asynchronously (EPERM, syscall=watch) and was suppressed to ` +
|
|
149
|
+
`keep the thread alive; occurrence ${lostNativeWatchCount}. Node reports no path for this error, ` +
|
|
150
|
+
`so it cannot be attributed to a specific watcher — whatever was watching that path has stopped ` +
|
|
151
|
+
`reporting changes until it is re-established. On Windows this is usually a watched directory ` +
|
|
152
|
+
`being deleted or replaced (a component redeploy, a package install, a test teardown). If file ` +
|
|
153
|
+
`changes stop being picked up somewhere, this is why. Subsequent occurrences log at trace, with ` +
|
|
154
|
+
`a warning at each tenfold increase.`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
// Last: threadServer.js and socketRouter.ts skip errors already marked handled, so failing
|
|
158
|
+
// to mark one costs only a duplicate log line.
|
|
159
|
+
claimed.isHandled = true;
|
|
160
|
+
} catch {
|
|
161
|
+
// Marked or not, logged or not, the error stays claimed.
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let lostNativeWatchGuardInstalled = false;
|
|
167
|
+
|
|
168
|
+
function handleUncaughtException(error: unknown): void {
|
|
169
|
+
let claimed = false;
|
|
170
|
+
try {
|
|
171
|
+
claimed = claimLostNativeWatchError(error);
|
|
172
|
+
} catch {
|
|
173
|
+
// Classification is all that can still throw (a property getter that does), and a throw
|
|
174
|
+
// from inside an 'uncaughtException' listener would replace Node's report with it and
|
|
175
|
+
// exit 7. An error that cannot be classified is not ours to claim.
|
|
176
|
+
claimed = false;
|
|
177
|
+
}
|
|
178
|
+
if (claimed) return;
|
|
179
|
+
// Node suppresses its default fatal handling as soon as *any* 'uncaughtException' listener
|
|
180
|
+
// exists, so being the only listener would turn unrelated crashes into silent hangs.
|
|
181
|
+
if (process.listenerCount('uncaughtException') > 1) return;
|
|
182
|
+
process.removeListener('uncaughtException', handleUncaughtException);
|
|
183
|
+
// A process that somehow survives re-arms on its next guardedWatch() rather than running
|
|
184
|
+
// unguarded thereafter.
|
|
185
|
+
lostNativeWatchGuardInstalled = false;
|
|
186
|
+
// nextTick, not a throw from inside the handler: that keeps Node's report and exit code 1.
|
|
187
|
+
process.nextTick(() => {
|
|
188
|
+
throw error;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Idempotently install the process-level listener that supplies the 'error'
|
|
194
|
+
* handler chokidar omits for non-persistent watchers. Called by
|
|
195
|
+
* {@link guardedWatch} so no watcher call site can forget it.
|
|
196
|
+
*/
|
|
197
|
+
export function installLostNativeWatchGuard(): void {
|
|
198
|
+
if (lostNativeWatchGuardInstalled) return;
|
|
199
|
+
lostNativeWatchGuardInstalled = true;
|
|
200
|
+
// prepend, not append: threadServer.js already has an 'uncaughtException'
|
|
201
|
+
// listener, and the `isHandled` mark above only suppresses its log line if
|
|
202
|
+
// this guard runs first.
|
|
203
|
+
process.prependListener('uncaughtException', handleUncaughtException);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* `chokidar.watch()` with the lost-native-watch guard installed. Every Harper
|
|
208
|
+
* chokidar watcher must go through this — a raw `chokidar.watch(..., { persistent: false })`
|
|
209
|
+
* can take down the thread the first time its path is deleted.
|
|
210
|
+
*/
|
|
211
|
+
export function guardedWatch(paths: string | string[], options?: ChokidarOptions): FSWatcher {
|
|
212
|
+
installLostNativeWatchGuard();
|
|
213
|
+
// Deliberately a property access on the default export rather than a named `watch` import:
|
|
214
|
+
// unitTests/server/threads/watchDirFallback.test.js drives the reopen-on-exhaustion chain by
|
|
215
|
+
// swapping `chokidar.default.watch`, and a named import binds past that seam.
|
|
216
|
+
return chokidar.watch(paths, options);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Test-only: number of lost native watch errors claimed so far.
|
|
220
|
+
export function _lostNativeWatchCountForTests(): number {
|
|
221
|
+
return lostNativeWatchCount;
|
|
74
222
|
}
|