@harperfast/harper 5.1.20 → 5.2.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/operations.ts +13 -16
- package/components/Application.ts +188 -7
- package/components/ApplicationScope.ts +3 -0
- package/components/Scope.ts +12 -0
- package/components/anthropic/index.ts +9 -2
- package/components/bedrock/index.ts +25 -2
- package/components/componentLoader.ts +56 -7
- package/components/componentSecrets.ts +451 -0
- package/components/deploymentRecorder.ts +7 -1
- package/components/mcp/adapters/fastify.ts +3 -0
- package/components/mcp/adapters/harperHttp.ts +3 -0
- package/components/mcp/audit.ts +21 -6
- package/components/mcp/customResourceRegistry.ts +17 -38
- package/components/mcp/listChanged.ts +37 -20
- package/components/mcp/quota.ts +145 -0
- package/components/mcp/rateLimit.ts +152 -14
- package/components/mcp/resources.ts +2 -4
- package/components/mcp/toolRegistry.ts +73 -4
- package/components/mcp/tools/application.ts +77 -8
- package/components/mcp/tools/operations.ts +106 -36
- package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
- package/components/mcp/tools/schemas/operations.ts +5 -0
- package/components/mcp/transport.ts +55 -4
- package/components/ollama/index.ts +6 -2
- package/components/openai/index.ts +9 -2
- package/components/operations.js +204 -10
- package/components/operationsValidation.js +173 -0
- package/components/secretOperations.ts +498 -0
- package/config/configUtils.ts +25 -1
- package/config-app.schema.json +81 -0
- package/config-root.schema.json +8 -1
- package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dataLayer/readAuditLog.ts +14 -0
- package/dataLayer/schemaDescribe.ts +1 -0
- package/dist/agent/operations.d.ts +7 -4
- package/dist/agent/operations.js +13 -15
- package/dist/agent/operations.js.map +1 -1
- package/dist/components/Application.d.ts +25 -2
- package/dist/components/Application.js +166 -7
- package/dist/components/Application.js.map +1 -1
- package/dist/components/ApplicationScope.d.ts +2 -0
- package/dist/components/ApplicationScope.js +3 -0
- package/dist/components/ApplicationScope.js.map +1 -1
- package/dist/components/Scope.d.ts +8 -0
- package/dist/components/Scope.js +11 -0
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/anthropic/index.d.ts +4 -1
- package/dist/components/anthropic/index.js +4 -2
- package/dist/components/anthropic/index.js.map +1 -1
- package/dist/components/bedrock/index.d.ts +4 -1
- package/dist/components/bedrock/index.js +22 -2
- package/dist/components/bedrock/index.js.map +1 -1
- package/dist/components/componentLoader.d.ts +2 -0
- package/dist/components/componentLoader.js +52 -10
- package/dist/components/componentLoader.js.map +1 -1
- package/dist/components/componentSecrets.d.ts +63 -0
- package/dist/components/componentSecrets.js +405 -0
- package/dist/components/componentSecrets.js.map +1 -0
- package/dist/components/deploymentRecorder.d.ts +3 -0
- package/dist/components/deploymentRecorder.js +4 -3
- package/dist/components/deploymentRecorder.js.map +1 -1
- package/dist/components/mcp/adapters/fastify.d.ts +2 -0
- package/dist/components/mcp/adapters/fastify.js +1 -0
- package/dist/components/mcp/adapters/fastify.js.map +1 -1
- package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
- package/dist/components/mcp/adapters/harperHttp.js +1 -0
- package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
- package/dist/components/mcp/audit.d.ts +2 -2
- package/dist/components/mcp/audit.js +20 -5
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
- package/dist/components/mcp/customResourceRegistry.js +15 -30
- package/dist/components/mcp/customResourceRegistry.js.map +1 -1
- package/dist/components/mcp/listChanged.d.ts +7 -2
- package/dist/components/mcp/listChanged.js +28 -0
- package/dist/components/mcp/listChanged.js.map +1 -1
- package/dist/components/mcp/quota.d.ts +31 -0
- package/dist/components/mcp/quota.js +155 -0
- package/dist/components/mcp/quota.js.map +1 -0
- package/dist/components/mcp/rateLimit.d.ts +19 -3
- package/dist/components/mcp/rateLimit.js +129 -13
- package/dist/components/mcp/rateLimit.js.map +1 -1
- package/dist/components/mcp/resources.js.map +1 -1
- package/dist/components/mcp/toolRegistry.d.ts +39 -1
- package/dist/components/mcp/toolRegistry.js +60 -5
- package/dist/components/mcp/toolRegistry.js.map +1 -1
- package/dist/components/mcp/tools/application.d.ts +8 -1
- package/dist/components/mcp/tools/application.js +65 -5
- package/dist/components/mcp/tools/application.js.map +1 -1
- package/dist/components/mcp/tools/operations.d.ts +15 -3
- package/dist/components/mcp/tools/operations.js +99 -38
- package/dist/components/mcp/tools/operations.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
- package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
- package/dist/components/mcp/tools/schemas/operations.js +4 -0
- package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
- package/dist/components/mcp/transport.d.ts +6 -0
- package/dist/components/mcp/transport.js +47 -3
- package/dist/components/mcp/transport.js.map +1 -1
- package/dist/components/ollama/index.d.ts +4 -1
- package/dist/components/ollama/index.js +4 -2
- package/dist/components/ollama/index.js.map +1 -1
- package/dist/components/openai/index.d.ts +4 -1
- package/dist/components/openai/index.js +4 -2
- package/dist/components/openai/index.js.map +1 -1
- package/dist/components/operations.d.ts +39 -0
- package/dist/components/operations.js +194 -2
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.d.ts +38 -0
- package/dist/components/operationsValidation.js +155 -0
- package/dist/components/operationsValidation.js.map +1 -1
- package/dist/components/secretOperations.d.ts +113 -0
- package/dist/components/secretOperations.js +475 -0
- package/dist/components/secretOperations.js.map +1 -0
- package/dist/config/configUtils.d.ts +4 -1
- package/dist/config/configUtils.js +18 -1
- package/dist/config/configUtils.js.map +1 -1
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
- package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
- package/dist/dataLayer/readAuditLog.js +11 -0
- package/dist/dataLayer/readAuditLog.js.map +1 -1
- package/dist/dataLayer/schemaDescribe.js +2 -0
- package/dist/dataLayer/schemaDescribe.js.map +1 -1
- package/dist/globals.d.ts +1 -1
- package/dist/globals.js +1 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +38 -0
- package/dist/resources/DatabaseTransaction.d.ts +31 -3
- package/dist/resources/DatabaseTransaction.js +190 -29
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +1 -1
- package/dist/resources/LMDBTransaction.js +43 -5
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
- package/dist/resources/PrimaryRocksDatabase.js +194 -0
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
- package/dist/resources/RecordEncoder.d.ts +1 -0
- package/dist/resources/RecordEncoder.js +81 -75
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/RequestTarget.d.ts +19 -0
- package/dist/resources/RequestTarget.js.map +1 -1
- package/dist/resources/Resource.d.ts +0 -1
- package/dist/resources/Resource.js +103 -16
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +6 -2
- package/dist/resources/ResourceInterface.js.map +1 -1
- package/dist/resources/Resources.js.map +1 -1
- package/dist/resources/Table.d.ts +6 -4
- package/dist/resources/Table.js +247 -24
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/read.js +25 -28
- package/dist/resources/analytics/read.js.map +1 -1
- package/dist/resources/blob.js +165 -22
- package/dist/resources/blob.js.map +1 -1
- package/dist/resources/crdt.d.ts +2 -0
- package/dist/resources/crdt.js +45 -36
- package/dist/resources/crdt.js.map +1 -1
- package/dist/resources/databases.d.ts +1 -0
- package/dist/resources/databases.js +134 -7
- package/dist/resources/databases.js.map +1 -1
- package/dist/resources/graphql.js +8 -0
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/jsResource.js +6 -0
- package/dist/resources/jsResource.js.map +1 -1
- package/dist/resources/loadEnv.js +30 -3
- package/dist/resources/loadEnv.js.map +1 -1
- package/dist/resources/login.js +2 -2
- package/dist/resources/login.js.map +1 -1
- package/dist/resources/models/Models.js +14 -3
- package/dist/resources/models/Models.js.map +1 -1
- package/dist/resources/models/embedHook.js +10 -2
- package/dist/resources/models/embedHook.js.map +1 -1
- package/dist/resources/models/openaiStream.d.ts +56 -0
- package/dist/resources/models/openaiStream.js +94 -0
- package/dist/resources/models/openaiStream.js.map +1 -0
- package/dist/resources/replayLogs.js +4 -3
- package/dist/resources/replayLogs.js.map +1 -1
- package/dist/resources/search.d.ts +1 -1
- package/dist/resources/search.js +104 -11
- package/dist/resources/search.js.map +1 -1
- package/dist/resources/secretDecryptor.d.ts +54 -0
- package/dist/resources/secretDecryptor.js +131 -0
- package/dist/resources/secretDecryptor.js.map +1 -0
- package/dist/resources/tracked.js +15 -5
- package/dist/resources/tracked.js.map +1 -1
- package/dist/security/auth.js +71 -8
- package/dist/security/auth.js.map +1 -1
- package/dist/security/jsLoader.js +11 -0
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/security/tokenAuthentication.d.ts +9 -1
- package/dist/security/tokenAuthentication.js +31 -1
- package/dist/security/tokenAuthentication.js.map +1 -1
- package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
- package/dist/server/DurableSubscriptionsSession.js +8 -4
- package/dist/server/DurableSubscriptionsSession.js.map +1 -1
- package/dist/server/REST.js +28 -9
- package/dist/server/REST.js.map +1 -1
- package/dist/server/fastifyRoutes.js +11 -1
- package/dist/server/fastifyRoutes.js.map +1 -1
- package/dist/server/graphqlQuerying.js +4 -3
- package/dist/server/graphqlQuerying.js.map +1 -1
- package/dist/server/http.d.ts +40 -2
- package/dist/server/http.js +560 -77
- package/dist/server/http.js.map +1 -1
- package/dist/server/itc/serverHandlers.js +64 -0
- package/dist/server/itc/serverHandlers.js.map +1 -1
- package/dist/server/liveSubscriptionAuth.d.ts +12 -0
- package/dist/server/liveSubscriptionAuth.js +140 -0
- package/dist/server/liveSubscriptionAuth.js.map +1 -0
- package/dist/server/loadRootComponents.js +2 -1
- package/dist/server/loadRootComponents.js.map +1 -1
- package/dist/server/middlewareChain.d.ts +44 -1
- package/dist/server/middlewareChain.js +86 -10
- package/dist/server/middlewareChain.js.map +1 -1
- package/dist/server/mqtt.js +2 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/operationsServer.js +1 -1
- package/dist/server/operationsServer.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +9 -0
- package/dist/server/serverHelpers/Headers.js +22 -0
- package/dist/server/serverHelpers/Headers.js.map +1 -1
- package/dist/server/serverHelpers/JSONStream.js +3 -3
- package/dist/server/serverHelpers/JSONStream.js.map +1 -1
- package/dist/server/serverHelpers/Request.d.ts +52 -1
- package/dist/server/serverHelpers/Request.js +122 -1
- package/dist/server/serverHelpers/Request.js.map +1 -1
- package/dist/server/serverHelpers/contentTypes.js +3 -8
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
- package/dist/server/serverHelpers/progressEmitter.js +52 -4
- package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
- package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
- package/dist/server/serverHelpers/registeredOperations.js +286 -0
- package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
- package/dist/server/serverHelpers/serverHandlers.js +11 -5
- package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
- package/dist/server/serverHelpers/serverUtilities.js +67 -5
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
- package/dist/server/serverHelpers/uwsServer.js +433 -0
- package/dist/server/serverHelpers/uwsServer.js.map +1 -0
- package/dist/server/static.d.ts +0 -15
- package/dist/server/static.js +161 -2
- package/dist/server/static.js.map +1 -1
- package/dist/server/status/index.d.ts +4 -1
- package/dist/server/status/index.js +64 -4
- package/dist/server/status/index.js.map +1 -1
- package/dist/server/threads/manageThreads.d.ts +10 -0
- package/dist/server/threads/manageThreads.js +107 -4
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.js +5 -1
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +92 -14
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/upgrade/directives/5-2-0.d.ts +7 -0
- package/dist/upgrade/directives/5-2-0.js +108 -0
- package/dist/upgrade/directives/5-2-0.js.map +1 -0
- package/dist/upgrade/directives/directivesController.js +2 -1
- package/dist/upgrade/directives/directivesController.js.map +1 -1
- package/dist/utility/common_utils.js +5 -0
- package/dist/utility/common_utils.js.map +1 -1
- package/dist/utility/envFile.d.ts +41 -0
- package/dist/utility/envFile.js +221 -0
- package/dist/utility/envFile.js.map +1 -0
- package/dist/utility/globalSchema.d.ts +9 -0
- package/dist/utility/hdbTerms.d.ts +25 -0
- package/dist/utility/hdbTerms.js +31 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.d.ts +21 -0
- package/dist/utility/logging/harper_logger.js +160 -8
- package/dist/utility/logging/harper_logger.js.map +1 -1
- package/dist/utility/logging/logRotator.js +29 -17
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/logging/readLog.d.ts +1 -1
- package/dist/utility/logging/readLog.js +305 -2
- package/dist/utility/logging/readLog.js.map +1 -1
- package/dist/utility/operationPermissions.d.ts +18 -0
- package/dist/utility/operationPermissions.js +35 -1
- package/dist/utility/operationPermissions.js.map +1 -1
- package/dist/utility/operation_authorization.d.ts +17 -0
- package/dist/utility/operation_authorization.js +38 -0
- package/dist/utility/operation_authorization.js.map +1 -1
- package/dist/utility/secretEnvelope.d.ts +30 -0
- package/dist/utility/secretEnvelope.js +94 -0
- package/dist/utility/secretEnvelope.js.map +1 -0
- package/dist/utility/signalling.d.ts +7 -0
- package/dist/utility/signalling.js +16 -0
- package/dist/utility/signalling.js.map +1 -1
- package/dist/validation/configValidator.js +70 -24
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +4 -0
- package/json/systemSchema.json +38 -0
- package/npm-shrinkwrap.json +15066 -9347
- package/package.json +14 -9
- package/resources/DESIGN.md +15 -12
- package/resources/DatabaseTransaction.ts +206 -37
- package/resources/LMDBTransaction.ts +43 -9
- package/resources/PrimaryRocksDatabase.ts +201 -0
- package/resources/RecordEncoder.ts +81 -69
- package/resources/RequestTarget.ts +21 -0
- package/resources/Resource.ts +109 -25
- package/resources/ResourceInterface.ts +6 -2
- package/resources/Resources.ts +1 -3
- package/resources/Table.ts +238 -29
- package/resources/analytics/read.ts +25 -30
- package/resources/blob.ts +159 -23
- package/resources/crdt.ts +37 -31
- package/resources/databases.ts +143 -6
- package/resources/graphql.ts +8 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
- package/resources/jsResource.ts +6 -0
- package/resources/loadEnv.ts +34 -3
- package/resources/login.ts +2 -2
- package/resources/models/Models.ts +19 -3
- package/resources/models/embedHook.ts +13 -2
- package/resources/models/openaiStream.ts +133 -0
- package/resources/replayLogs.ts +4 -3
- package/resources/search.ts +113 -11
- package/resources/secretDecryptor.ts +159 -0
- package/resources/tracked.ts +24 -17
- package/security/auth.ts +70 -10
- package/security/jsLoader.ts +12 -0
- package/security/tokenAuthentication.ts +42 -1
- package/server/DESIGN.md +10 -0
- package/server/DurableSubscriptionsSession.ts +8 -4
- package/server/REST.ts +26 -9
- package/server/fastifyRoutes.ts +10 -1
- package/server/graphqlQuerying.ts +4 -3
- package/server/http.ts +563 -77
- package/server/itc/serverHandlers.js +70 -0
- package/server/liveSubscriptionAuth.ts +152 -0
- package/server/loadRootComponents.js +2 -1
- package/server/middlewareChain.ts +107 -17
- package/server/mqtt.ts +2 -1
- package/server/operationsServer.ts +2 -2
- package/server/serverHelpers/Headers.ts +24 -0
- package/server/serverHelpers/JSONStream.ts +3 -3
- package/server/serverHelpers/Request.ts +127 -0
- package/server/serverHelpers/contentTypes.ts +3 -8
- package/server/serverHelpers/progressEmitter.ts +55 -5
- package/server/serverHelpers/registeredOperations.ts +259 -0
- package/server/serverHelpers/serverHandlers.js +11 -5
- package/server/serverHelpers/serverUtilities.ts +88 -5
- package/server/serverHelpers/uwsServer.ts +488 -0
- package/server/static.ts +180 -2
- package/server/status/index.ts +72 -4
- package/server/threads/manageThreads.js +102 -4
- package/server/threads/socketRouter.ts +5 -1
- package/server/threads/threadServer.js +93 -10
- package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
- package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
- package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
- package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
- package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
- package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
- package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
- package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
- package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
- package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
- package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
- package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
- package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
- package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
- package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
- package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
- package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
- package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
- package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
- package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
- package/studio/web/index.html +1 -1
- package/upgrade/directives/5-2-0.ts +82 -0
- package/upgrade/directives/directivesController.ts +2 -1
- package/utility/common_utils.ts +5 -0
- package/utility/envFile.ts +218 -0
- package/utility/hdbTerms.ts +31 -0
- package/utility/logging/harper_logger.ts +156 -8
- package/utility/logging/logRotator.ts +24 -14
- package/utility/logging/readLog.ts +323 -2
- package/utility/operationPermissions.ts +35 -1
- package/utility/operation_authorization.ts +74 -1
- package/utility/secretEnvelope.ts +113 -0
- package/utility/signalling.ts +15 -0
- package/validation/configValidator.ts +78 -25
- package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
|
@@ -115,6 +115,10 @@ function bisectInsert(arr: Candidate[], distance: number): number {
|
|
|
115
115
|
const ENTRY_POINT = Symbol.for('entryPoint');
|
|
116
116
|
const KEY_PREFIX = Symbol.for('key');
|
|
117
117
|
const MAX_LEVEL = 10; // should give good high-level skip list performance up to trillions of nodes
|
|
118
|
+
// Visit budget for the post-delete connectivity probe (repairSeveredNeighbors, #1712). Severed
|
|
119
|
+
// islands are small (bounded by the deleted node's neighborhood), so a probe that visits this many
|
|
120
|
+
// nodes without reaching the entry point is treated as connected-but-far and repaired conservatively.
|
|
121
|
+
const PROBE_VISIT_LIMIT = 256;
|
|
118
122
|
type Connection = {
|
|
119
123
|
id: number;
|
|
120
124
|
distance: number;
|
|
@@ -142,7 +146,12 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
142
146
|
// Index options that only affect search, not the stored graph — changing them must not trigger a
|
|
143
147
|
// reindex (databases.ts persists the new value but skips rebuilding). efConstructionSearch is the
|
|
144
148
|
// search-time candidate-list size; the build uses efConstruction/M/distance, which are structural.
|
|
145
|
-
|
|
149
|
+
// filterExpansion is the visit-budget multiplier for predicate-aware (filtered) traversal.
|
|
150
|
+
static searchOnlyOptions = ['efConstructionSearch', 'filterExpansion'];
|
|
151
|
+
// Signals to search.ts that this index accepts a per-record predicate in search() and applies it
|
|
152
|
+
// during traversal (predicate-aware / ACORN-style filtering), so companion conditions and RBAC can
|
|
153
|
+
// be pushed down instead of post-filtering an under-filled candidate set (#1241).
|
|
154
|
+
filteredSearch = true;
|
|
146
155
|
indexStore: any;
|
|
147
156
|
M: number = 16; // max number of connections per layer
|
|
148
157
|
efConstruction: number = 100; // size of dynamic candidate list
|
|
@@ -152,6 +161,17 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
152
161
|
// a value of 1 is extremely aggressive.
|
|
153
162
|
optimizeRouting = 0.5;
|
|
154
163
|
nodesVisitedCount = 0;
|
|
164
|
+
// Visit-budget multiplier for predicate-aware traversal (#1241). When a filter is selective enough
|
|
165
|
+
// that layer-0 results never fill `ef`, the "closest candidate worse than worst result" stop rule
|
|
166
|
+
// never triggers, so maxVisits = ef * filterExpansion caps how many nodes an under-filled filtered
|
|
167
|
+
// search visits before returning what it has (approximate, like all ANN). It does NOT bound a filter
|
|
168
|
+
// that fills `ef` — that terminates naturally. Default 24 (not the 8 the design sketch assumed):
|
|
169
|
+
// this HNSW visits a large fraction of the graph per query, so filling `ef` at selectivity `s` needs
|
|
170
|
+
// ~ef/s visits; a multiplier of 24 fills down to ~4% selectivity before the budget bites, keeping
|
|
171
|
+
// recall at or above post-filtering across the range the query planner routes to traversal. Raising
|
|
172
|
+
// it is nearly free for condition-derived filters (they fill and self-terminate); it mainly trades
|
|
173
|
+
// latency for recall on selective *function* predicates. Per-query override via the search options.
|
|
174
|
+
filterExpansion = 24;
|
|
155
175
|
|
|
156
176
|
idIncrementer: BigInt64Array | undefined;
|
|
157
177
|
distance: (a: number[], b: number[]) => number;
|
|
@@ -188,6 +208,7 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
188
208
|
if (options.efConstructionSearch !== undefined) this.efConstructionSearch = options.efConstructionSearch;
|
|
189
209
|
if (options.mL !== undefined) this.mL = options.mL;
|
|
190
210
|
if (options.optimizeRouting !== undefined) this.optimizeRouting = options.optimizeRouting;
|
|
211
|
+
if (options.filterExpansion !== undefined) this.filterExpansion = options.filterExpansion;
|
|
191
212
|
}
|
|
192
213
|
}
|
|
193
214
|
index(primaryKey: Id, vector: number[], existingVector?: number[], options: any = {}) {
|
|
@@ -497,6 +518,10 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
497
518
|
this.indexStore.remove(safeKey, options);
|
|
498
519
|
}
|
|
499
520
|
const needsReindexing = new Map();
|
|
521
|
+
// On DELETE, track every neighbor that loses an edge: the empty-list check below only
|
|
522
|
+
// catches fully-orphaned nodes, but a cluster can stay internally connected while losing
|
|
523
|
+
// its only bridges to the entry point — see repairSeveredNeighbors (#1712).
|
|
524
|
+
const edgeLosingNeighborIds = vector ? undefined : new Set<number>();
|
|
500
525
|
// remove connections to this node that are no longer valid
|
|
501
526
|
if (oldNode.level !== undefined) {
|
|
502
527
|
for (let l = 0; l <= oldNode.level; l++) {
|
|
@@ -505,6 +530,7 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
505
530
|
// get and copy the neighbor node so we can modify it
|
|
506
531
|
const neighborNode = updateNode(neighborId, this.safeGetSync(neighborId, options));
|
|
507
532
|
if (!neighborNode) continue;
|
|
533
|
+
edgeLosingNeighborIds?.add(neighborId);
|
|
508
534
|
// On an UPDATE (vector != null), only remove the reverse edge at
|
|
509
535
|
// the exact level l where the old connection existed. Sweeping 0..l would destroy reverse
|
|
510
536
|
// edges at lower levels that were just re-added by addConnection or preserved by the
|
|
@@ -580,9 +606,92 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
580
606
|
}
|
|
581
607
|
this.index(key, orphanVector, orphanVector, options);
|
|
582
608
|
}
|
|
609
|
+
if (edgeLosingNeighborIds?.size) this.repairSeveredNeighbors(edgeLosingNeighborIds, options);
|
|
583
610
|
this.checkSymmetry(nodeId, this.safeGetSync(nodeId, options), options);
|
|
584
611
|
}
|
|
585
612
|
|
|
613
|
+
/**
|
|
614
|
+
* After a delete, a neighbor that lost an edge can be severed from the entry point even with
|
|
615
|
+
* non-empty connection lists: a locally-connected cluster whose only bridges ran through the
|
|
616
|
+
* deleted node becomes a disconnected island, unreachable by search at any ef (#1712). The
|
|
617
|
+
* empty-list orphan check in index() can't see this, so probe each edge-losing neighbor's
|
|
618
|
+
* connectivity and reindex the ones that are cut off.
|
|
619
|
+
*
|
|
620
|
+
* The probe is a level-prioritized best-first traversal (hubs first, since the entry point is
|
|
621
|
+
* a high-level node), treating edges as undirected — the same assumption validateConnectivity
|
|
622
|
+
* makes. Note the direction difference: the probe walks source→entry-point over stored
|
|
623
|
+
* (outgoing) edges while search walks entry-point→outward, so an asymmetric edge could make
|
|
624
|
+
* the probe claim connectivity that search doesn't have. Edges are bidirectional by
|
|
625
|
+
* construction (checkSymmetry logs violations), and reverse-reachability from the entry point
|
|
626
|
+
* would be O(N) per delete, so this is a deliberate residual. In a connected component the
|
|
627
|
+
* probe reaches the known-connected set within a few hops; in an island it exhausts the
|
|
628
|
+
* island quickly. If it exceeds PROBE_VISIT_LIMIT without an answer (pathologically distant
|
|
629
|
+
* entry point — or an island larger than the budget), reinsert just the probe source: a
|
|
630
|
+
* wasted reinsert in the connected case, and in the oversized-island case a partial repair
|
|
631
|
+
* (the source relinks to the live graph, the rest of the island stays severed — reindexing
|
|
632
|
+
* replaces the source's edges rather than bridging through them, and reindexing all 256+
|
|
633
|
+
* probed nodes on what is usually just a distant entry point would be far too expensive).
|
|
634
|
+
*/
|
|
635
|
+
private repairSeveredNeighbors(neighborIds: Set<number>, options: any) {
|
|
636
|
+
const entryPointId = this.indexStore.getSync(ENTRY_POINT, options);
|
|
637
|
+
if (entryPointId === undefined) return;
|
|
638
|
+
const knownConnected = new Set<number>([entryPointId]);
|
|
639
|
+
for (const sourceId of neighborIds) {
|
|
640
|
+
if (knownConnected.has(sourceId)) continue;
|
|
641
|
+
const sourceNode = this.safeGetSync(sourceId, options);
|
|
642
|
+
if (!sourceNode || sourceNode.level === undefined) continue; // already removed or stale reference
|
|
643
|
+
const visited = new Map<number, Node>([[sourceId, sourceNode]]);
|
|
644
|
+
const frontier = new MinHeap();
|
|
645
|
+
frontier.push({ id: sourceId, distance: -sourceNode.level, node: sourceNode });
|
|
646
|
+
let verdict: 'connected' | 'island' | 'inconclusive' = 'island';
|
|
647
|
+
probe: for (;;) {
|
|
648
|
+
const current = frontier.pop();
|
|
649
|
+
if (!current) break; // exhausted the reachable set without touching the connected component
|
|
650
|
+
for (let l = 0; l <= current.node.level; l++) {
|
|
651
|
+
for (const { id: neighborId } of current.node[l] || []) {
|
|
652
|
+
if (neighborId === undefined) continue;
|
|
653
|
+
if (knownConnected.has(neighborId)) {
|
|
654
|
+
verdict = 'connected';
|
|
655
|
+
break probe;
|
|
656
|
+
}
|
|
657
|
+
if (visited.has(neighborId)) continue;
|
|
658
|
+
const node = this.safeGetSync(neighborId, options);
|
|
659
|
+
if (!node || node.level === undefined) continue; // stale reference
|
|
660
|
+
visited.set(neighborId, node);
|
|
661
|
+
frontier.push({ id: neighborId, distance: -node.level, node });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (visited.size > PROBE_VISIT_LIMIT) {
|
|
665
|
+
verdict = 'inconclusive';
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (verdict === 'connected') {
|
|
670
|
+
// every visited node connects to the source, which reaches the connected component
|
|
671
|
+
for (const id of visited.keys()) knownConnected.add(id);
|
|
672
|
+
} else {
|
|
673
|
+
// island: reinsert every member so the whole cluster relinks to the live graph;
|
|
674
|
+
// inconclusive: reinsert only the source (partial repair — see docstring)
|
|
675
|
+
if (verdict === 'inconclusive') {
|
|
676
|
+
logger.warn?.('connectivity probe exceeded visit budget after a delete, reinserting only', sourceId);
|
|
677
|
+
}
|
|
678
|
+
const toReindex = verdict === 'island' ? visited.values() : [sourceNode];
|
|
679
|
+
logger.debug?.('reindexing nodes severed from the entry point by a delete', sourceId);
|
|
680
|
+
for (const node of toReindex) {
|
|
681
|
+
const nodeVector =
|
|
682
|
+
node.scale !== undefined ? dequantizeInt8(node.vector as Int8Array, node.scale) : (node.vector as number[]);
|
|
683
|
+
this.index(node.primaryKey, nodeVector, nodeVector, options);
|
|
684
|
+
}
|
|
685
|
+
if (verdict === 'island') {
|
|
686
|
+
// the whole reindexed island is now relinked; skip re-probing its members
|
|
687
|
+
for (const id of visited.keys()) knownConnected.add(id);
|
|
688
|
+
} else {
|
|
689
|
+
knownConnected.add(sourceId);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
586
695
|
private safeGetSync(key: any, options?: any): any {
|
|
587
696
|
try {
|
|
588
697
|
let node = this.indexStore.getSync(key, options);
|
|
@@ -643,7 +752,13 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
643
752
|
ef: number,
|
|
644
753
|
level: number,
|
|
645
754
|
options: { transaction?: any } = {},
|
|
646
|
-
distanceFunction = this.distance
|
|
755
|
+
distanceFunction = this.distance,
|
|
756
|
+
// Predicate-aware traversal (#1241, layer 0 only). `filter` decides result admission by primary
|
|
757
|
+
// key; non-matching nodes still route (they stay in the candidate/visited sets) so the graph
|
|
758
|
+
// remains navigable under selective filters (ACORN). `filterState` carries the visit budget and
|
|
759
|
+
// per-query counters. Both are undefined for routing layers and for unfiltered searches.
|
|
760
|
+
filter?: (primaryKey: Id) => boolean,
|
|
761
|
+
filterState?: FilterState
|
|
647
762
|
): SearchResults {
|
|
648
763
|
// Pre-compute query magnitude for cosine; use cached invMag on stored nodes to skip sqrt per neighbor.
|
|
649
764
|
// Asymmetric distance: the query stays full-precision float; a stored neighbor may be int8
|
|
@@ -697,13 +812,62 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
697
812
|
|
|
698
813
|
const candidates = new MinHeap();
|
|
699
814
|
candidates.push(initialCandidate);
|
|
700
|
-
const results = [initialCandidate] as SearchResults;
|
|
701
815
|
|
|
816
|
+
if (!filter) {
|
|
817
|
+
// Unfiltered path (unchanged): results are seeded with the entry point and every visited
|
|
818
|
+
// node is admitted; the stop rule is "closest remaining candidate worse than worst result".
|
|
819
|
+
const results = [initialCandidate] as SearchResults;
|
|
820
|
+
while (candidates.size > 0) {
|
|
821
|
+
const current = candidates.pop()!;
|
|
822
|
+
const furthestDistance = results[results.length - 1].distance;
|
|
823
|
+
|
|
824
|
+
if (current.distance > furthestDistance) break;
|
|
825
|
+
|
|
826
|
+
for (const { id: neighborId } of current.node[level] || []) {
|
|
827
|
+
if (visited.has(neighborId) || neighborId === undefined) continue;
|
|
828
|
+
visited.add(neighborId);
|
|
829
|
+
|
|
830
|
+
const neighbor = this.safeGetSync(neighborId, options);
|
|
831
|
+
if (!neighbor) continue;
|
|
832
|
+
this.nodesVisitedCount++;
|
|
833
|
+
const distance = computeDistance(neighbor.vector, neighbor.invMag, neighbor.scale);
|
|
834
|
+
|
|
835
|
+
if (distance < furthestDistance || results.length < ef) {
|
|
836
|
+
const candidate: Candidate = { id: neighborId, distance, node: neighbor };
|
|
837
|
+
candidates.push(candidate);
|
|
838
|
+
results.splice(bisectInsert(results, distance), 0, candidate);
|
|
839
|
+
if (results.length > ef) results.pop();
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
results.visited = visited.size;
|
|
844
|
+
return results;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Predicate-aware path (#1241). `results` holds only nodes the filter admits, but every visited
|
|
848
|
+
// node still routes: it enters the candidate heap and can be expanded, preserving connectivity
|
|
849
|
+
// through non-matching regions. Because matches accrue slower than visits, the distance stop rule
|
|
850
|
+
// only applies once `ef` matches are in hand; until then we keep expanding.
|
|
851
|
+
//
|
|
852
|
+
// maxVisits = ef * filterExpansion is a hard ceiling on layer-0 node visits. It has to be
|
|
853
|
+
// generous enough NOT to truncate a non-selective filter before it fills `ef` (filling at
|
|
854
|
+
// selectivity `s` costs ~ef/s visits) — that is why the default filterExpansion is larger than a
|
|
855
|
+
// standard-HNSW rule of thumb, since this index visits a large fraction of the graph per query.
|
|
856
|
+
// It matters as a genuine bound in two cases: a selective filter that can never fill `ef` (where
|
|
857
|
+
// the alternative is crawling the whole graph — the same regime the planner diverts to the exact
|
|
858
|
+
// brute-force path for condition filters, leaving function predicates the real beneficiary), and
|
|
859
|
+
// a filter that fills `ef` with distant matches (a loose worst-match bound would otherwise let
|
|
860
|
+
// the distance rule explore almost everything).
|
|
861
|
+
// search() always supplies filterState alongside filter; default one for any direct caller that doesn't.
|
|
862
|
+
if (!filterState) filterState = { maxVisits: Infinity, nodesVisited: 0, filterEvaluations: 0 };
|
|
863
|
+
const results = [] as unknown as SearchResults;
|
|
864
|
+
if (this.admit(filter, filterState, entryPoint.primaryKey)) results.push(initialCandidate);
|
|
865
|
+
let budgetExhausted = false;
|
|
702
866
|
while (candidates.size > 0) {
|
|
703
867
|
const current = candidates.pop()!;
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
if (current.distance > furthestDistance) break;
|
|
868
|
+
// Once we have ef matches, the worst of them bounds useful exploration; before that, keep going.
|
|
869
|
+
const furthestDistance = results.length >= ef ? results[results.length - 1].distance : Infinity;
|
|
870
|
+
if (results.length >= ef && current.distance > furthestDistance) break;
|
|
707
871
|
|
|
708
872
|
for (const { id: neighborId } of current.node[level] || []) {
|
|
709
873
|
if (visited.has(neighborId) || neighborId === undefined) continue;
|
|
@@ -712,20 +876,41 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
712
876
|
const neighbor = this.safeGetSync(neighborId, options);
|
|
713
877
|
if (!neighbor) continue;
|
|
714
878
|
this.nodesVisitedCount++;
|
|
879
|
+
filterState.nodesVisited++;
|
|
715
880
|
const distance = computeDistance(neighbor.vector, neighbor.invMag, neighbor.scale);
|
|
716
881
|
|
|
882
|
+
// Route through any node that could still improve the result set (under-filled or nearer
|
|
883
|
+
// than the current worst match) — filtering does not prune the graph, only admission.
|
|
717
884
|
if (distance < furthestDistance || results.length < ef) {
|
|
718
885
|
const candidate: Candidate = { id: neighborId, distance, node: neighbor };
|
|
719
886
|
candidates.push(candidate);
|
|
720
|
-
|
|
721
|
-
|
|
887
|
+
if (this.admit(filter, filterState, neighbor.primaryKey)) {
|
|
888
|
+
results.splice(bisectInsert(results, distance), 0, candidate);
|
|
889
|
+
if (results.length > ef) results.pop();
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (filterState.nodesVisited >= filterState.maxVisits) {
|
|
893
|
+
budgetExhausted = true;
|
|
894
|
+
break;
|
|
722
895
|
}
|
|
723
896
|
}
|
|
897
|
+
if (budgetExhausted) break;
|
|
724
898
|
}
|
|
725
899
|
results.visited = visited.size;
|
|
726
900
|
return results;
|
|
727
901
|
}
|
|
728
902
|
|
|
903
|
+
/**
|
|
904
|
+
* Evaluate the traversal predicate for one node, counting the evaluation for `explain`. Kept as a
|
|
905
|
+
* tiny helper so both the entry-point seed and the neighbor loop share the counting path. The filter
|
|
906
|
+
* itself memoizes verdicts per query (a node can be reached from multiple neighbors), so this stays
|
|
907
|
+
* cheap even when the predicate loads a record.
|
|
908
|
+
*/
|
|
909
|
+
private admit(filter: (primaryKey: Id) => boolean, filterState: FilterState | undefined, primaryKey: Id): boolean {
|
|
910
|
+
if (filterState) filterState.filterEvaluations++;
|
|
911
|
+
return filter(primaryKey);
|
|
912
|
+
}
|
|
913
|
+
|
|
729
914
|
/**
|
|
730
915
|
* This the main entry from Harper's query functionality, where we actually search for an ordered list of nearest
|
|
731
916
|
* neighbors, using the provided sort/order definition object and performing the multi-layer skip-list search.
|
|
@@ -745,6 +930,7 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
745
930
|
distance,
|
|
746
931
|
comparator,
|
|
747
932
|
ef,
|
|
933
|
+
filterExpansion,
|
|
748
934
|
}: {
|
|
749
935
|
target: number[];
|
|
750
936
|
value: number;
|
|
@@ -752,8 +938,14 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
752
938
|
distance: string;
|
|
753
939
|
comparator: string;
|
|
754
940
|
ef?: number;
|
|
941
|
+
filterExpansion?: number;
|
|
755
942
|
},
|
|
756
|
-
context: any
|
|
943
|
+
context: any,
|
|
944
|
+
// Predicate-aware traversal (#1241). When provided, only nodes for which `filter(primaryKey)`
|
|
945
|
+
// returns true are admitted to the result list at layer 0; routing is unaffected. Composed by
|
|
946
|
+
// search.ts from companion AND conditions, a caller-supplied `vectorFilter`, and record-level
|
|
947
|
+
// RBAC. Must be synchronous and side-effect free. JS-API only (never from a REST query string).
|
|
948
|
+
filter?: (primaryKey: Id) => boolean
|
|
757
949
|
) {
|
|
758
950
|
let limit: number | undefined; // only set for threshold comparators; 0 is a valid threshold (e.g. dotProduct)
|
|
759
951
|
let limitInclusive = false; // true for `le`, false for `lt`
|
|
@@ -793,14 +985,34 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
793
985
|
: (this.indexStore.getStats?.()?.entryCount ?? 0);
|
|
794
986
|
effectiveEf = autoScaleEf(nodeCount);
|
|
795
987
|
}
|
|
988
|
+
// Predicate-aware traversal budget (#1241): matches accrue slower than visits under a selective
|
|
989
|
+
// filter, so bound layer-0 work at ef * filterExpansion nodes. Only built when a filter is active.
|
|
990
|
+
const filterState: FilterState | undefined = filter
|
|
991
|
+
? {
|
|
992
|
+
maxVisits: effectiveEf * (filterExpansion && filterExpansion > 0 ? filterExpansion : this.filterExpansion),
|
|
993
|
+
nodesVisited: 0,
|
|
994
|
+
filterEvaluations: 0,
|
|
995
|
+
}
|
|
996
|
+
: undefined;
|
|
796
997
|
let entryPoint = this.getEntryPoint(options);
|
|
797
|
-
if (!entryPoint) return [];
|
|
998
|
+
if (!entryPoint) return withStats([], filterState);
|
|
798
999
|
let entryPointId = entryPoint.id;
|
|
799
1000
|
let results: Candidate[] = [];
|
|
800
|
-
// For each level from top to bottom
|
|
1001
|
+
// For each level from top to bottom. The filter applies only at layer 0 (result admission);
|
|
1002
|
+
// upper layers route unfiltered so non-matching hubs still guide the descent.
|
|
801
1003
|
for (let l = entryPoint.level; l >= 0; l--) {
|
|
802
1004
|
// Search for closest neighbors at current level
|
|
803
|
-
results = this.searchLayer(
|
|
1005
|
+
results = this.searchLayer(
|
|
1006
|
+
target,
|
|
1007
|
+
entryPointId,
|
|
1008
|
+
entryPoint,
|
|
1009
|
+
effectiveEf,
|
|
1010
|
+
l,
|
|
1011
|
+
options,
|
|
1012
|
+
distanceFunction,
|
|
1013
|
+
l === 0 ? filter : undefined,
|
|
1014
|
+
l === 0 ? filterState : undefined
|
|
1015
|
+
);
|
|
804
1016
|
|
|
805
1017
|
if (results.length > 0) {
|
|
806
1018
|
const neighbor = results[0]; // closest neighbor becomes new entry point
|
|
@@ -812,11 +1024,14 @@ export class HierarchicalNavigableSmallWorld {
|
|
|
812
1024
|
results = results.filter((candidate) =>
|
|
813
1025
|
limitInclusive ? candidate.distance <= limit : candidate.distance < limit
|
|
814
1026
|
);
|
|
815
|
-
return
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1027
|
+
return withStats(
|
|
1028
|
+
results.map((candidate) => ({
|
|
1029
|
+
// we return the result as an entry so we can provide distance as metadata
|
|
1030
|
+
key: candidate.node.primaryKey, // return value
|
|
1031
|
+
distance: candidate.distance,
|
|
1032
|
+
})),
|
|
1033
|
+
filterState
|
|
1034
|
+
);
|
|
820
1035
|
}
|
|
821
1036
|
/**
|
|
822
1037
|
* Exact distance between a query and a record's FULL-precision vector, mirroring search()'s metric
|
|
@@ -1052,3 +1267,22 @@ type Candidate = {
|
|
|
1052
1267
|
node: Node;
|
|
1053
1268
|
};
|
|
1054
1269
|
type SearchResults = Candidate[] & { visited: number };
|
|
1270
|
+
// Per-query state for predicate-aware traversal (#1241): the visit budget plus counters surfaced for
|
|
1271
|
+
// tuning (nodesVisited / filterEvaluations). One instance per search(), threaded to the layer-0 searchLayer.
|
|
1272
|
+
type FilterState = {
|
|
1273
|
+
maxVisits: number;
|
|
1274
|
+
nodesVisited: number;
|
|
1275
|
+
filterEvaluations: number;
|
|
1276
|
+
};
|
|
1277
|
+
/**
|
|
1278
|
+
* Attach filtered-traversal counters to the returned entries array so callers (search.ts / explain) can
|
|
1279
|
+
* report how much of the graph a filtered query touched. No-op for unfiltered searches. Non-enumerable so
|
|
1280
|
+
* the stats don't leak into iteration/serialization of the result entries.
|
|
1281
|
+
*/
|
|
1282
|
+
function withStats<T extends any[]>(entries: T, filterState: FilterState | undefined): T {
|
|
1283
|
+
if (filterState) {
|
|
1284
|
+
Object.defineProperty(entries, 'nodesVisited', { value: filterState.nodesVisited, enumerable: false });
|
|
1285
|
+
Object.defineProperty(entries, 'filterEvaluations', { value: filterState.filterEvaluations, enumerable: false });
|
|
1286
|
+
}
|
|
1287
|
+
return entries;
|
|
1288
|
+
}
|
package/resources/jsResource.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Scope } from '../components/Scope.ts';
|
|
2
2
|
import { dirname } from 'path';
|
|
3
|
+
import { signalResourcesRegistered } from '../utility/signalling.ts';
|
|
3
4
|
|
|
4
5
|
function isResource(value: any) {
|
|
5
6
|
return (
|
|
@@ -96,6 +97,11 @@ export async function handleApplication(scope: Scope) {
|
|
|
96
97
|
scope.logger.debug?.(`Registered root resource: ${path}`);
|
|
97
98
|
}
|
|
98
99
|
recurseForResources(scope, resourceModule, root);
|
|
100
|
+
// A JS resource that extends an exported @table is the one carrying author opt-ins
|
|
101
|
+
// (`static mcpTools`/`mcpPrompts`), and it registers here — after the schema-derived
|
|
102
|
+
// table class and after the MCP component's boot scan. Signal so listing surfaces
|
|
103
|
+
// (MCP application tools) rebuild against the now-settled registry (#1448).
|
|
104
|
+
signalResourcesRegistered();
|
|
99
105
|
} catch (error) {
|
|
100
106
|
// Rethrow with more context
|
|
101
107
|
throw new ResourceLoadError(entryEvent.absolutePath, error);
|
package/resources/loadEnv.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { parse } from 'dotenv';
|
|
2
2
|
import logger from '../utility/logging/harper_logger.ts';
|
|
3
3
|
import { Scope } from '../components/Scope.ts';
|
|
4
|
+
import { isEncryptedEnvValue } from '../utility/envFile.ts';
|
|
5
|
+
import { deferEncryptedEnvValue, getSecretDecryptor } from './secretDecryptor.ts';
|
|
4
6
|
import { CONFIG_SHAPING_ENV_VARS } from '../config/componentEnvPrepass.ts';
|
|
5
7
|
|
|
6
8
|
export function handleApplication(scope: Scope) {
|
|
@@ -11,7 +13,36 @@ export function handleApplication(scope: Scope) {
|
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
13
15
|
logger.debug(`Loading env file: ${entry.absolutePath}`);
|
|
14
|
-
for (const [key,
|
|
16
|
+
for (const [key, rawValue] of Object.entries(parse(entry.contents))) {
|
|
17
|
+
let value = rawValue;
|
|
18
|
+
// Encrypted (`enc:v1:`) values are decrypted via the Pro env-secrets component if it has
|
|
19
|
+
// registered a decryptor; otherwise the secret stays opaque and is skipped (so the app
|
|
20
|
+
// fails loudly on a missing var rather than receiving ciphertext as the value).
|
|
21
|
+
if (isEncryptedEnvValue(rawValue)) {
|
|
22
|
+
const decryptor = getSecretDecryptor();
|
|
23
|
+
// Logged at error level (not warn) so an undecryptable secret is never silent — but
|
|
24
|
+
// skipped rather than fatal, so the node still boots and the bad value can be fixed via
|
|
25
|
+
// set_env_value, and a non-Pro node isn't crashed by a replicated encrypted value. The
|
|
26
|
+
// entry is also queued so a decryptor that registers later (custody can come up after
|
|
27
|
+
// component `.env` loading) replays it into process.env; until then the app sees a
|
|
28
|
+
// missing var (and should fail on it) rather than receiving ciphertext.
|
|
29
|
+
if (!decryptor) {
|
|
30
|
+
logger.error(
|
|
31
|
+
`Environment variable ${key} from ${entry.absolutePath} is encrypted but no env-secret decryptor is registered yet (the Harper Pro env-secrets component is required); deferring`
|
|
32
|
+
);
|
|
33
|
+
deferEncryptedEnvValue({ key, rawValue, sourcePath: entry.absolutePath, override });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
value = decryptor(rawValue);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error(
|
|
40
|
+
`Failed to decrypt environment variable ${key} from ${entry.absolutePath}: ${(error as Error).message}; skipping`
|
|
41
|
+
);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
15
46
|
if (CONFIG_SHAPING_ENV_VARS.includes(key)) {
|
|
16
47
|
// covers components deployed after boot too — the boot-time pre-pass only sees
|
|
17
48
|
// components present when the config is composed (#1513)
|
|
@@ -20,8 +51,8 @@ export function handleApplication(scope: Scope) {
|
|
|
20
51
|
);
|
|
21
52
|
// Enforce at the injection point: the trio must never reach process.env
|
|
22
53
|
// from a component .env — anything downstream that (re)composes config
|
|
23
|
-
// from process.env
|
|
24
|
-
// top-down config relationship.
|
|
54
|
+
// from process.env (e.g. #1618/#1726's OptionsWatcher) would otherwise
|
|
55
|
+
// silently honor it, re-inverting the top-down config relationship.
|
|
25
56
|
continue;
|
|
26
57
|
}
|
|
27
58
|
if (process.env[key] !== undefined) {
|
package/resources/login.ts
CHANGED
|
@@ -12,9 +12,9 @@ class Login extends Resource {
|
|
|
12
12
|
// TODO: Return a login page
|
|
13
13
|
}
|
|
14
14
|
static async post(_id, body, request) {
|
|
15
|
-
const { username, password } = body;
|
|
15
|
+
const { username, password, token } = body;
|
|
16
16
|
return {
|
|
17
|
-
data: await request.login(username, password),
|
|
17
|
+
data: await request.login(username, password, token),
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -112,7 +112,7 @@ export class Models implements ModelsContract {
|
|
|
112
112
|
signal?.throwIfAborted();
|
|
113
113
|
const attemptStart = performance.now();
|
|
114
114
|
try {
|
|
115
|
-
const backendOpts
|
|
115
|
+
const backendOpts = toBackendOpts(opts, signal, accounting);
|
|
116
116
|
const result = await backend.embed!(input, backendOpts);
|
|
117
117
|
// Throw on `pending` BEFORE recording success — otherwise we'd write a
|
|
118
118
|
// success row followed by a failure row from the catch (duplicate).
|
|
@@ -166,7 +166,7 @@ export class Models implements ModelsContract {
|
|
|
166
166
|
signal?.throwIfAborted();
|
|
167
167
|
const attemptStart = performance.now();
|
|
168
168
|
try {
|
|
169
|
-
const backendOpts
|
|
169
|
+
const backendOpts = toBackendOpts(opts, signal, accounting);
|
|
170
170
|
const result = await backend.generate!(input, backendOpts);
|
|
171
171
|
if (result.status !== 'completed') throw new ModelPendingNotSupportedError(backend.name);
|
|
172
172
|
this.#record(backend, 'generate', opts.model, accounting, opts, result, attemptStart);
|
|
@@ -210,7 +210,7 @@ export class Models implements ModelsContract {
|
|
|
210
210
|
}
|
|
211
211
|
// First candidate only — mid-stream fallback would mean replaying already-yielded chunks.
|
|
212
212
|
const backend = resolved.candidates[0];
|
|
213
|
-
const backendOpts
|
|
213
|
+
const backendOpts = toBackendOpts(opts, signal, accounting);
|
|
214
214
|
return this.#wrapStream(backend, input, backendOpts, opts, accounting, startedAt);
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -352,6 +352,22 @@ type Resolution = { candidates: ModelBackend[] } | { error: Error; backend: Mode
|
|
|
352
352
|
* against a backend that actually supports the call (the default router never returns
|
|
353
353
|
* empty for a satisfying primary; it only drops on name or capability miss).
|
|
354
354
|
*/
|
|
355
|
+
/**
|
|
356
|
+
* Builds the options passed to a backend call. `opts.model` is the LOGICAL name — it selects
|
|
357
|
+
* the configured entry via resolveCandidates and must not reach the backend, where it would
|
|
358
|
+
* override the entry's configured wire model id (#1593: `@embed(model: "default")` sent the
|
|
359
|
+
* literal string "default" to the provider). Backends always use their own configured model.
|
|
360
|
+
*/
|
|
361
|
+
function toBackendOpts<TOpts extends { model?: string; signal?: AbortSignal }>(
|
|
362
|
+
opts: TOpts,
|
|
363
|
+
signal: AbortSignal | undefined,
|
|
364
|
+
accounting: AccountingContext
|
|
365
|
+
): BackendOpts<TOpts> {
|
|
366
|
+
const backendOpts = { ...opts, signal, accounting };
|
|
367
|
+
delete backendOpts.model;
|
|
368
|
+
return backendOpts;
|
|
369
|
+
}
|
|
370
|
+
|
|
355
371
|
function resolveCandidates(kind: ResolveKind, model: string | undefined, requires: Capability[]): Resolution {
|
|
356
372
|
const logicalName = model ?? 'default';
|
|
357
373
|
const candidates = getRouter().route({ kind, logicalName, requires });
|
|
@@ -118,9 +118,20 @@ export function buildEmbedBefore(
|
|
|
118
118
|
try {
|
|
119
119
|
vector = await embedder(record);
|
|
120
120
|
} catch (err) {
|
|
121
|
-
// Backend errors can carry URLs / key tails; log raw, rethrow sanitized.
|
|
121
|
+
// Backend errors can carry URLs / key tails; log raw, rethrow sanitized. Include
|
|
122
|
+
// only safe identifiers (error class name, upstream HTTP status) so the failure is
|
|
123
|
+
// diagnosable without hunting through server logs (#1593). upstreamStatus is the
|
|
124
|
+
// provider's status — NOT ServerError's statusCode, which is Harper's own response
|
|
125
|
+
// status and would misleadingly read 500 here.
|
|
122
126
|
getLogger().error?.(`Embedder for attribute "${attr.name}" failed:`, err);
|
|
123
|
-
|
|
127
|
+
const status = (err as any)?.upstreamStatus;
|
|
128
|
+
const errName = (err as any)?.name;
|
|
129
|
+
const detail =
|
|
130
|
+
(errName && errName !== 'Error' ? ` [${errName}]` : '') +
|
|
131
|
+
(typeof status === 'number' ? ` (backend HTTP ${status})` : '');
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Failed to compute embedding for attribute "${attr.name}"${detail} — see server log for details`
|
|
134
|
+
);
|
|
124
135
|
}
|
|
125
136
|
record[attr.name] = normalizeVector(vector);
|
|
126
137
|
})
|