@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2
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/bin/cliOperations.ts +12 -0
- package/bin/restart.ts +66 -6
- package/components/Application.ts +1134 -112
- package/components/DESIGN.md +421 -0
- package/components/OptionsWatcher.ts +368 -102
- package/components/Scope.ts +20 -1
- package/components/componentLoader.ts +43 -4
- package/components/deploymentOperations.ts +4 -1
- package/components/deploymentRecorder.ts +9 -2
- package/components/mcp/DESIGN.md +109 -0
- package/components/mcp/audit.ts +21 -17
- package/components/operations.js +284 -52
- package/components/operationsValidation.js +49 -2
- package/components/packageComponent.ts +25 -1
- package/components/requestRestart.ts +11 -0
- package/config/DESIGN.md +306 -0
- package/config/RootConfigWatcher.ts +191 -37
- package/config/configReadRetry.ts +62 -0
- package/config/configUtils.ts +78 -26
- package/config/parseConfigFile.ts +34 -0
- package/config/readConfigFileSync.ts +44 -0
- package/config/watcherArming.ts +59 -0
- package/config-root.schema.json +4 -0
- package/dataLayer/DESIGN.md +179 -0
- package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
- package/dataLayer/restoreMarker.ts +92 -25
- package/dist/bin/cliOperations.js +13 -0
- package/dist/bin/cliOperations.js.map +1 -1
- package/dist/bin/restart.js +42 -6
- package/dist/bin/restart.js.map +1 -1
- package/dist/components/Application.d.ts +104 -9
- package/dist/components/Application.js +954 -102
- package/dist/components/Application.js.map +1 -1
- package/dist/components/OptionsWatcher.d.ts +4 -1
- package/dist/components/OptionsWatcher.js +378 -104
- package/dist/components/OptionsWatcher.js.map +1 -1
- package/dist/components/Scope.js +15 -1
- package/dist/components/Scope.js.map +1 -1
- package/dist/components/componentLoader.js +35 -3
- package/dist/components/componentLoader.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/audit.d.ts +2 -1
- package/dist/components/mcp/audit.js +21 -17
- package/dist/components/mcp/audit.js.map +1 -1
- package/dist/components/operations.d.ts +28 -0
- package/dist/components/operations.js +263 -54
- package/dist/components/operations.js.map +1 -1
- package/dist/components/operationsValidation.js +48 -2
- 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/config/RootConfigWatcher.d.ts +2 -0
- package/dist/config/RootConfigWatcher.js +189 -35
- 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 +10 -9
- package/dist/config/configUtils.js +63 -27
- package/dist/config/configUtils.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/harperBridge/ResourceBridge.js +21 -2
- package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
- package/dist/dataLayer/restoreMarker.d.ts +21 -8
- package/dist/dataLayer/restoreMarker.js +94 -27
- package/dist/dataLayer/restoreMarker.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/json/systemSchema.json +3 -0
- package/dist/resources/DatabaseTransaction.d.ts +25 -0
- package/dist/resources/DatabaseTransaction.js +224 -18
- package/dist/resources/DatabaseTransaction.js.map +1 -1
- package/dist/resources/LMDBTransaction.d.ts +2 -1
- package/dist/resources/LMDBTransaction.js +22 -3
- package/dist/resources/LMDBTransaction.js.map +1 -1
- package/dist/resources/PrimaryRocksDatabase.js +22 -6
- package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
- package/dist/resources/RecordEncoder.d.ts +1 -1
- package/dist/resources/RecordEncoder.js +46 -10
- package/dist/resources/RecordEncoder.js.map +1 -1
- package/dist/resources/Resource.js +97 -13
- package/dist/resources/Resource.js.map +1 -1
- package/dist/resources/ResourceInterface.d.ts +8 -0
- package/dist/resources/RocksIndexStore.js +2 -1
- package/dist/resources/RocksIndexStore.js.map +1 -1
- package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
- package/dist/resources/RocksTransactionLogStore.js +104 -33
- package/dist/resources/RocksTransactionLogStore.js.map +1 -1
- package/dist/resources/Table.d.ts +58 -7
- package/dist/resources/Table.js +1143 -348
- package/dist/resources/Table.js.map +1 -1
- package/dist/resources/analytics/write.d.ts +3 -0
- package/dist/resources/analytics/write.js +49 -16
- package/dist/resources/analytics/write.js.map +1 -1
- package/dist/resources/auditStore.d.ts +170 -0
- package/dist/resources/auditStore.js +457 -11
- package/dist/resources/auditStore.js.map +1 -1
- package/dist/resources/crdt.d.ts +10 -0
- package/dist/resources/crdt.js +22 -0
- 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 +16 -13
- package/dist/resources/databases.js +625 -177
- package/dist/resources/databases.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.js +3 -2
- package/dist/resources/graphql.js.map +1 -1
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
- package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
- package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
- package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
- package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
- package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
- package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
- 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/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
- package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
- 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 +47 -4
- package/dist/resources/recordLock.js +138 -7
- package/dist/resources/recordLock.js.map +1 -1
- package/dist/resources/recordLockCoordinator.d.ts +557 -0
- package/dist/resources/recordLockCoordinator.js +2597 -0
- package/dist/resources/recordLockCoordinator.js.map +1 -0
- package/dist/resources/replayLogs.js +5 -0
- package/dist/resources/replayLogs.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 +160 -40
- 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.js +4 -6
- package/dist/resources/transactionBroadcast.js.map +1 -1
- package/dist/security/auth.js +59 -23
- package/dist/security/auth.js.map +1 -1
- package/dist/security/deferredAuthentication.d.ts +11 -0
- package/dist/security/deferredAuthentication.js +25 -3
- package/dist/security/deferredAuthentication.js.map +1 -1
- package/dist/security/jsLoader.js +9 -4
- package/dist/security/jsLoader.js.map +1 -1
- package/dist/server/REST.js +39 -5
- package/dist/server/REST.js.map +1 -1
- package/dist/server/http.d.ts +5 -1
- package/dist/server/http.js +34 -2
- package/dist/server/http.js.map +1 -1
- package/dist/server/mqtt.js +5 -1
- package/dist/server/mqtt.js.map +1 -1
- package/dist/server/serverHelpers/Headers.d.ts +2 -0
- package/dist/server/serverHelpers/Headers.js +6 -0
- 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 +2 -0
- package/dist/server/serverHelpers/contentTypes.js +189 -15
- package/dist/server/serverHelpers/contentTypes.js.map +1 -1
- package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
- package/dist/server/serverHelpers/serverUtilities.js +110 -20
- package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
- package/dist/server/serverHelpers/uwsServer.js +4 -1
- package/dist/server/serverHelpers/uwsServer.js.map +1 -1
- package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
- package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
- package/dist/server/storageReclamation.js +1 -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/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 +64 -6
- package/dist/server/threads/manageThreads.js +261 -12
- package/dist/server/threads/manageThreads.js.map +1 -1
- package/dist/server/threads/socketRouter.d.ts +1 -0
- package/dist/server/threads/socketRouter.js +196 -13
- package/dist/server/threads/socketRouter.js.map +1 -1
- package/dist/server/threads/threadServer.js +30 -7
- package/dist/server/threads/threadServer.js.map +1 -1
- package/dist/utility/errors/hdbError.d.ts +24 -0
- package/dist/utility/errors/hdbError.js +58 -1
- package/dist/utility/errors/hdbError.js.map +1 -1
- package/dist/utility/hdbTerms.d.ts +2 -0
- package/dist/utility/hdbTerms.js +2 -0
- package/dist/utility/hdbTerms.js.map +1 -1
- package/dist/utility/logging/harper_logger.js +218 -38
- 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 +172 -92
- package/dist/utility/logging/logRotator.js.map +1 -1
- package/dist/utility/npmUtilities.js +6 -4
- package/dist/utility/npmUtilities.js.map +1 -1
- package/dist/utility/watcherFallback.d.ts +0 -45
- package/dist/utility/watcherFallback.js +1 -125
- package/dist/utility/watcherFallback.js.map +1 -1
- package/dist/validation/configValidator.js +6 -3
- package/dist/validation/configValidator.js.map +1 -1
- package/index.ts +9 -0
- package/json/systemSchema.json +3 -0
- package/npm-shrinkwrap.json +177 -87
- package/package.json +13 -5
- package/resources/DESIGN.md +689 -19
- package/resources/DatabaseTransaction.ts +230 -17
- package/resources/LMDBTransaction.ts +21 -3
- package/resources/PrimaryRocksDatabase.ts +20 -7
- package/resources/RecordEncoder.ts +52 -10
- package/resources/Resource.ts +97 -13
- package/resources/ResourceInterface.ts +8 -0
- package/resources/RocksIndexStore.ts +2 -1
- package/resources/RocksTransactionLogStore.ts +111 -31
- package/resources/Table.ts +1273 -396
- package/resources/analytics/DESIGN.md +38 -0
- package/resources/analytics/write.ts +50 -17
- package/resources/auditStore.ts +460 -11
- package/resources/crdt.ts +22 -0
- package/resources/dataLoader.ts +3 -4
- package/resources/databases.ts +611 -147
- package/resources/derivedIndexRegistry.ts +56 -0
- package/resources/derivedIndexRuntime.ts +2292 -0
- package/resources/graphql.ts +3 -2
- package/resources/indexes/DESIGN.md +833 -0
- package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
- package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
- package/resources/indexes/fullTextNativeBinding.ts +146 -0
- package/resources/indexes/hnswDerivedIndex.ts +531 -0
- package/resources/indexes/hnswPlaneBinding.ts +174 -0
- package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
- package/resources/nodeIdMapping.ts +50 -0
- package/resources/record-locks.md +1407 -0
- package/resources/recordLock.ts +173 -7
- package/resources/recordLockCoordinator.ts +3082 -0
- package/resources/replayLogs.ts +5 -0
- package/resources/replicatedApplyFailure.ts +77 -0
- package/resources/scheduler/DESIGN.md +40 -0
- package/resources/scheduler/scheduler.ts +4 -4
- package/resources/search.ts +169 -49
- package/resources/tracked.ts +73 -22
- package/resources/transactionBroadcast.ts +4 -4
- package/security/DESIGN.md +175 -0
- package/security/auth.ts +53 -24
- package/security/deferredAuthentication.ts +24 -2
- package/security/jsLoader.ts +9 -4
- package/server/DESIGN.md +275 -0
- package/server/REST.ts +42 -6
- package/server/http.ts +34 -2
- package/server/mqtt.ts +6 -4
- package/server/serverHelpers/Headers.ts +5 -1
- package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
- package/server/serverHelpers/Request.ts +33 -131
- package/server/serverHelpers/contentTypes.ts +188 -15
- package/server/serverHelpers/serverUtilities.ts +157 -27
- package/server/serverHelpers/uwsServer.ts +4 -1
- package/server/serverHelpers/webSocketCloseReason.ts +25 -0
- package/server/storageReclamation.ts +2 -2
- package/server/threads/isolatedApplications.ts +157 -0
- package/server/threads/logRotationTransport.ts +40 -0
- package/server/threads/manageThreads.js +254 -12
- package/server/threads/socketRouter.ts +217 -11
- package/server/threads/threadServer.js +30 -7
- package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
- package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
- package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
- package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
- package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
- package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
- package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
- package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
- package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
- package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
- package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
- package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
- package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
- package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
- package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
- package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
- package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
- package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
- package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
- package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
- package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
- package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
- package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
- package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
- package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
- package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
- package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
- package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
- package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
- package/studio/web/index.html +1 -1
- package/utility/DESIGN.md +55 -0
- package/utility/errors/hdbError.ts +54 -0
- package/utility/hdbTerms.ts +2 -0
- package/utility/logging/harper_logger.ts +210 -30
- package/utility/logging/logGenerationCoordinator.ts +196 -0
- package/utility/logging/logRotation.ts +367 -0
- package/utility/logging/logRotator.ts +196 -91
- package/utility/npmUtilities.ts +6 -4
- package/utility/watcherFallback.ts +0 -122
- package/validation/configValidator.ts +6 -3
package/components/Scope.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './componentSecrets.ts';
|
|
21
21
|
import type { SecretsView } from './componentSecrets.ts';
|
|
22
22
|
import { deployLifecycle } from './deployLifecycle.ts';
|
|
23
|
+
import { thisThreadOwnsApplication } from '../server/threads/isolatedApplications.ts';
|
|
23
24
|
|
|
24
25
|
export class MissingDefaultFilesOptionError extends Error {
|
|
25
26
|
constructor() {
|
|
@@ -68,6 +69,7 @@ export class Scope extends EventEmitter<ScopeEventsMap> {
|
|
|
68
69
|
#deployEndHandler: (name: string) => void;
|
|
69
70
|
#deployInFlight: boolean = false;
|
|
70
71
|
#restartRequestedDuringDeploy: boolean = false;
|
|
72
|
+
#optionsReady: boolean = false;
|
|
71
73
|
applicationScope?: ApplicationScope;
|
|
72
74
|
|
|
73
75
|
options: OptionsWatcher;
|
|
@@ -241,7 +243,10 @@ export class Scope extends EventEmitter<ScopeEventsMap> {
|
|
|
241
243
|
|
|
242
244
|
ensureTable<TableResourceType = unknown>(options: any): TableResourceType {
|
|
243
245
|
options.origin = this.#origin;
|
|
244
|
-
return scopedTableFactory(
|
|
246
|
+
return scopedTableFactory(
|
|
247
|
+
this.applicationScope?.branches,
|
|
248
|
+
thisThreadOwnsApplication(this.applicationScope?.name)
|
|
249
|
+
)<TableResourceType>(options);
|
|
245
250
|
}
|
|
246
251
|
|
|
247
252
|
#handleOptionsWatcherReady(): void {
|
|
@@ -251,7 +256,21 @@ export class Scope extends EventEmitter<ScopeEventsMap> {
|
|
|
251
256
|
// We could make the user call `await scope.ready()` in their `handleApplication` function, but that could lead to the same issue and it'd
|
|
252
257
|
// be harder for the user to understand why.
|
|
253
258
|
|
|
259
|
+
// A second `ready` means the scope had no config of its own and now does — a config file
|
|
260
|
+
// that was unreadable when this worker booted, or one recreated after deletion. Re-emitting
|
|
261
|
+
// reaches nobody: componentLoader is long past its `await scope.ready`, so the component is
|
|
262
|
+
// running on the defaults until something restarts it. Same recovery as the `remove`
|
|
263
|
+
// listener, and the same convention — a plugin with its own `ready` handler owns it.
|
|
264
|
+
const started = this.#optionsReady;
|
|
265
|
+
this.#optionsReady = true;
|
|
266
|
+
const restartNeeded = started && this.listenerCount('ready') === 0;
|
|
267
|
+
|
|
254
268
|
this.emit('ready');
|
|
269
|
+
|
|
270
|
+
if (restartNeeded) {
|
|
271
|
+
this.#logger.debug?.('Options arrived after the scope started, requesting restart');
|
|
272
|
+
this.requestRestart();
|
|
273
|
+
}
|
|
255
274
|
}
|
|
256
275
|
|
|
257
276
|
#handleError(error: unknown): void {
|
|
@@ -32,8 +32,13 @@ import { restartWorkers, getWorkerIndex } from '../server/threads/manageThreads.
|
|
|
32
32
|
import { resetRestartNeeded, subscribeToRestartRequests } from './requestRestart.ts';
|
|
33
33
|
import { trackScopeClose } from './scopeShutdown.ts';
|
|
34
34
|
import { deployLifecycle } from './deployLifecycle.ts';
|
|
35
|
-
import { assertBranchedDatabases } from './Application.ts';
|
|
35
|
+
import { assertBranchedDatabases, assertIsolationConfig } from './Application.ts';
|
|
36
36
|
import { prepareBranches } from '../resources/branchDatabase.ts';
|
|
37
|
+
import {
|
|
38
|
+
isIsolatedApplication,
|
|
39
|
+
shouldLoadApplicationHere,
|
|
40
|
+
thisThreadOwnsApplication,
|
|
41
|
+
} from '../server/threads/isolatedApplications.ts';
|
|
37
42
|
import { toScopeMount, nestScopeMount, type ScopeMount } from './scopeMount.ts';
|
|
38
43
|
import { scopedImport } from '../security/jsLoader.ts';
|
|
39
44
|
import { server } from '../server/Server.ts';
|
|
@@ -161,6 +166,32 @@ function rootConfigBranchedDatabases(appName: string): string[] | true | undefin
|
|
|
161
166
|
return (getConfigObj()?.[appName] as any)?.branchedDatabases;
|
|
162
167
|
}
|
|
163
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Whether this thread is the one that loads `appName` (harper#642, tier 2). An isolated application
|
|
171
|
+
* is loaded by its dedicated worker and by nothing else; every other thread loads only the
|
|
172
|
+
* applications that are not isolated. Decided before any of the application's modules are imported.
|
|
173
|
+
* With no worker threads at all there is nowhere for an isolated application to run, so it fails
|
|
174
|
+
* closed here rather than silently sharing the only thread.
|
|
175
|
+
*/
|
|
176
|
+
function placedOnThisThread(appName: string): boolean {
|
|
177
|
+
if (Object.hasOwn(TRUSTED_RESOURCE_PLUGINS, appName)) return true;
|
|
178
|
+
try {
|
|
179
|
+
assertIsolationConfig(appName, (getConfigObj()?.[appName] as any)?.isolated);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
componentLifecycle.failed(appName, error, `Component '${appName}' failed to load`);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if (isIsolatedApplication(appName) && isMainThread && getWorkerIndex() === 0) {
|
|
185
|
+
componentLifecycle.failed(
|
|
186
|
+
appName,
|
|
187
|
+
new Error(`Application '${appName}' is isolated, which needs a worker thread of its own, but threads.count is 0`),
|
|
188
|
+
`Component '${appName}' failed to load`
|
|
189
|
+
);
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return shouldLoadApplicationHere(appName);
|
|
193
|
+
}
|
|
194
|
+
|
|
164
195
|
/**
|
|
165
196
|
* Resolves the mount for `appName`, or reports the failure and returns `undefined` if the
|
|
166
197
|
* configured `host`/`urlPath` is invalid. The caller must skip loading the application in that
|
|
@@ -268,6 +299,7 @@ export async function loadComponentDirectories(
|
|
|
268
299
|
}
|
|
269
300
|
return;
|
|
270
301
|
}
|
|
302
|
+
if (!placedOnThisThread(appName)) return;
|
|
271
303
|
const mountResult = tryRootConfigMount(appName);
|
|
272
304
|
if (!mountResult.ok) return;
|
|
273
305
|
const loadedModules = new Set<any>();
|
|
@@ -316,6 +348,7 @@ export async function loadComponentDirectories(
|
|
|
316
348
|
);
|
|
317
349
|
continue;
|
|
318
350
|
}
|
|
351
|
+
if (!placedOnThisThread(appName)) continue;
|
|
319
352
|
const appFolder = join(CF_ROUTES_DIR, appName);
|
|
320
353
|
const mountResult = tryRootConfigMount(appName);
|
|
321
354
|
if (!mountResult.ok) continue;
|
|
@@ -347,7 +380,7 @@ export async function loadComponentDirectories(
|
|
|
347
380
|
if (hdbAppFolder) {
|
|
348
381
|
if (getWorkerIndex() === 0) harperLogger.info?.('Loading application from ' + hdbAppFolder);
|
|
349
382
|
const mountResult = tryRootConfigMount(basename(hdbAppFolder));
|
|
350
|
-
if (mountResult.ok) {
|
|
383
|
+
if (mountResult.ok && placedOnThisThread(basename(hdbAppFolder))) {
|
|
351
384
|
cfsLoaded.push(
|
|
352
385
|
serializeComponentLoad(hdbAppFolder, () =>
|
|
353
386
|
loadComponent(hdbAppFolder, cycleResources, hdbAppFolder, {
|
|
@@ -849,6 +882,9 @@ export async function loadComponent(
|
|
|
849
882
|
if (!componentConfig) continue;
|
|
850
883
|
|
|
851
884
|
// Initialize loading status for all components (applications and extensions)
|
|
885
|
+
// A root-config application (`package:`) is placed like a directory one: an isolated application
|
|
886
|
+
// loads only in its dedicated worker, and that worker loads no other application.
|
|
887
|
+
if (isRoot && componentConfig.package && !placedOnThisThread(componentName)) continue;
|
|
852
888
|
componentLifecycle.loading(componentStatusName);
|
|
853
889
|
|
|
854
890
|
const subApplicationScope = isRoot
|
|
@@ -940,7 +976,10 @@ export async function loadComponent(
|
|
|
940
976
|
// our own trusted modules can be directly retrieved from our map, otherwise use the (configurable) secure module loader
|
|
941
977
|
const ensureTable = (options: any) => {
|
|
942
978
|
options.origin = origin;
|
|
943
|
-
return scopedTableFactory(
|
|
979
|
+
return scopedTableFactory(
|
|
980
|
+
applicationScope.branches,
|
|
981
|
+
thisThreadOwnsApplication(applicationScope.name)
|
|
982
|
+
)(options);
|
|
944
983
|
};
|
|
945
984
|
// call the main start hook
|
|
946
985
|
const network =
|
|
@@ -1160,7 +1199,7 @@ export async function loadComponent(
|
|
|
1160
1199
|
// — not abort the loop and discard a pending follow-up, like the save that fixes it.
|
|
1161
1200
|
try {
|
|
1162
1201
|
await loadComponentDirectories();
|
|
1163
|
-
await restartWorkers();
|
|
1202
|
+
await restartWorkers(undefined, undefined, true, null, '*');
|
|
1164
1203
|
} catch (error) {
|
|
1165
1204
|
harperLogger.error('Error during component reload', error);
|
|
1166
1205
|
}
|
|
@@ -13,7 +13,10 @@ import type { ProgressEmitter } from '../server/serverHelpers/progressEmitter.ts
|
|
|
13
13
|
const { HTTP_STATUS_CODES } = hdbErrors;
|
|
14
14
|
|
|
15
15
|
const DEPLOYMENT_TABLE = terms.SYSTEM_TABLE_NAMES.DEPLOYMENT_TABLE_NAME;
|
|
16
|
-
|
|
16
|
+
// `staged` belongs here: the build is complete on disk and nothing further happens to this deployment, so
|
|
17
|
+
// an SSE tail must finish and `delete_deployment_payload` must not refuse a tarball nobody is installing
|
|
18
|
+
// from any more. Activating the artifact is a separate deployment with a row of its own.
|
|
19
|
+
const TERMINAL_STATUSES = new Set(['success', 'failed', 'rolled_back', 'staged']);
|
|
17
20
|
|
|
18
21
|
interface ListRequest {
|
|
19
22
|
project?: string;
|
|
@@ -55,7 +55,11 @@ type DeploymentStatus =
|
|
|
55
55
|
| 'restarting'
|
|
56
56
|
| 'success'
|
|
57
57
|
| 'failed'
|
|
58
|
-
| 'rolled_back'
|
|
58
|
+
| 'rolled_back'
|
|
59
|
+
// Terminal: the build is complete and certified on disk but was not activated. Terminal rather than
|
|
60
|
+
// in-flight because nothing further happens to THIS deployment — activating its artifact is a separate
|
|
61
|
+
// operator action with a row of its own.
|
|
62
|
+
| 'staged';
|
|
59
63
|
|
|
60
64
|
interface CreateOptions {
|
|
61
65
|
project?: string;
|
|
@@ -63,6 +67,8 @@ interface CreateOptions {
|
|
|
63
67
|
user?: string;
|
|
64
68
|
restart_mode?: 'immediate' | 'rolling' | null;
|
|
65
69
|
rollback_of?: string | null;
|
|
70
|
+
/** The staged deployment whose artifact this deployment activated. */
|
|
71
|
+
activated_from?: string | null;
|
|
66
72
|
// Deploy credentials in reference form (`{ registry, secret, scope? }` / `{ host, secret,
|
|
67
73
|
// username? }`) — never a literal token. Kept so a rollback can re-resolve the credential from
|
|
68
74
|
// hdb_secret without the operator re-supplying it. Null when the deploy used no credentials or a
|
|
@@ -141,6 +147,7 @@ export class DeploymentRecorder {
|
|
|
141
147
|
completed_at: null,
|
|
142
148
|
user: options.user ?? null,
|
|
143
149
|
rollback_of: options.rollback_of ?? null,
|
|
150
|
+
activated_from: options.activated_from ?? null,
|
|
144
151
|
credentials: options.credentials ?? null,
|
|
145
152
|
error: null,
|
|
146
153
|
};
|
|
@@ -465,7 +472,7 @@ export class DeploymentRecorder {
|
|
|
465
472
|
this.sealed = true;
|
|
466
473
|
}
|
|
467
474
|
|
|
468
|
-
async finish(status: 'success' | 'failed' | 'rolled_back', error?: unknown): Promise<void> {
|
|
475
|
+
async finish(status: 'success' | 'failed' | 'rolled_back' | 'staged', error?: unknown): Promise<void> {
|
|
469
476
|
if (this.finished) return;
|
|
470
477
|
// Send a terminal sentinel through the emitter (if any) BEFORE we unsubscribe and
|
|
471
478
|
// remove it from the registry, so any SSE tail subscribers can resolve their wait
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# components/mcp/ — Design notes
|
|
2
|
+
|
|
3
|
+
The MCP Streamable-HTTP surface.
|
|
4
|
+
|
|
5
|
+
**Read this when:** adding or changing an MCP method, tool, prompt or profile.
|
|
6
|
+
|
|
7
|
+
Index of every design note: [DESIGN.md](../../DESIGN.md).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## MCP protocol surface (`components/mcp/`)
|
|
12
|
+
|
|
13
|
+
The MCP Streamable-HTTP transport (spec `2025-06-18`) is served at `/mcp` under **two profiles**: an
|
|
14
|
+
_operations_ profile (mounted on the Fastify operations server) and an _application_ profile (mounted on
|
|
15
|
+
the Harper application HTTP server). Both share `transport.ts` (the JSON-RPC dispatcher) but differ in what
|
|
16
|
+
they expose — operations surfaces management operations as tools; application surfaces exported
|
|
17
|
+
Resources/tables. Profile gating runs throughout (`completeResourceArgument`, prompt visibility, tool
|
|
18
|
+
`visibleTo`), so when adding a method, decide which profile(s) it belongs to rather than assuming both.
|
|
19
|
+
|
|
20
|
+
A handful of design points are non-obvious and easy to break:
|
|
21
|
+
|
|
22
|
+
- **Per-call POST SSE streaming has a close-before-subscribe race.** A `tools/call` that opts into streaming
|
|
23
|
+
(`Accept: text/event-stream`) gets an `IterableEventQueue` whose frames the adapter consumes via the event
|
|
24
|
+
API (`on('data')` / `once('close')`), **not** `for await` — the async iterator does not terminate on
|
|
25
|
+
`'close'`. The streaming tool handler is therefore dispatched inside a `setImmediate` (a _detached_,
|
|
26
|
+
deferred task) so the adapter's consumer attaches **before** any frame is produced. Without the defer, a
|
|
27
|
+
fast handler emits its final frame + `close` synchronously; the queue buffers `'data'` but not `'close'`,
|
|
28
|
+
and the stream hangs. Any handler on this path must check `signal.aborted` first (cancellation can land
|
|
29
|
+
before the deferred task runs).
|
|
30
|
+
|
|
31
|
+
- **Server→client requests are correlated across workers.** `serverRequests.ts` lets a streaming `tools/call`
|
|
32
|
+
call _back_ into the client (`sampling/createMessage`, `elicitation/create`, `roots/list`) and await the
|
|
33
|
+
reply. The request frame rides **the call's POST SSE stream**; the client's response is a _fresh POST_ that
|
|
34
|
+
can land on **any worker**. The pending-promise registry is per-worker, so a response with no local match
|
|
35
|
+
is fanned out over ITC (`MCP_CLIENT_RESPONSE`) and the worker holding the promise resolves it (mirrors
|
|
36
|
+
`components/status/crossThread.ts`). Request ids are `srv-${randomUUID()}` — **not** a per-worker counter,
|
|
37
|
+
which would collide on `(sessionId, id)` across workers and misroute responses. Methods are capability-gated
|
|
38
|
+
(`METHOD_CAPABILITY`) against the client capabilities captured at `initialize`; the registry is bounded
|
|
39
|
+
(timeout + high-water-mark) so a non-responding client can't leak promises.
|
|
40
|
+
|
|
41
|
+
- **Application tools must be rebuilt after JS resources register, not just on schema changes.** The
|
|
42
|
+
application-profile tool scan (`registerApplicationTools`) runs at MCP component boot and on schema-change
|
|
43
|
+
ITC events — both of which fire while the `@table` classes register, **before** the `jsResource` plugin
|
|
44
|
+
registers the component's exported `class X extends tables.X` subclass. That subclass is the object the
|
|
45
|
+
registry ends up holding (REST routes to it) and the only place author opt-ins (`static mcpTools`/
|
|
46
|
+
`mcpPrompts`) live, so a scan that ran earlier sees only the base table class and misses them (#1448). The
|
|
47
|
+
fix: `jsResource` fires `signalResourcesRegistered()` (a deliberately **local-only**, non-ITC signal in
|
|
48
|
+
`utility/signalling.ts`, backed by `resourceHandler` in `server/itc/serverHandlers.js` — each worker
|
|
49
|
+
registers its own JS resources, so the rebuild belongs in that worker) after registration; `listChanged`
|
|
50
|
+
subscribes and re-runs the scan. Consequence: the verb tools (`create_*` etc.) now bind to the subclass and
|
|
51
|
+
honor its `post`/`patch` overrides, matching REST — previously they bound to the base table class and
|
|
52
|
+
silently bypassed those overrides. Advertised CRUD output schemas are still table-derived, so an overridden
|
|
53
|
+
write verb whose return diverges from `{ id }`/`{ ok }`/`{ deleted }` advertises a subset shape (the in-use
|
|
54
|
+
SDK tolerates supersets; tightening per-override envelopes is sibling-issue work — see the `derive.ts`
|
|
55
|
+
envelope note).
|
|
56
|
+
|
|
57
|
+
- **Resource subscriptions are row-backed via the audit log.** `resources/subscribe` resolves the URI to a
|
|
58
|
+
Resource and drives `Table.subscribe` off the audit-store `'committed'` path (same machinery as the
|
|
59
|
+
"Audit-store `'committed'` notification batching" section above). The targeting is the subtle part:
|
|
60
|
+
`getMatch` returns the matched Resource plus the remaining path on `relativeURL`, and `subscribeToResource`
|
|
61
|
+
sets **both** `request.id` (the record key, or `undefined`) **and** `request.isCollection` from it. A record
|
|
62
|
+
URI (`…/WorkItem/42`) watches that record; a collection URI (`…/WorkItem`, what `resources/list` advertises)
|
|
63
|
+
watches the whole table. `new RequestTarget(path)` parses an id out of the path on its own, so _both_ fields
|
|
64
|
+
must be overridden — otherwise a collection URI silently watches a phantom record named after the resource
|
|
65
|
+
and receives nothing. `harper://*` pseudo-resources are **list-changed-only** (not row-backed). Subscriptions
|
|
66
|
+
use `omitCurrent` (notify on change, not a retained snapshot — the notification just says "re-read this").
|
|
67
|
+
|
|
68
|
+
- **Subscribe requires a live GET stream; teardown is asymmetric.** `resources/subscribe` rejects (`-32602`)
|
|
69
|
+
if no GET SSE stream has registered the session — the audit-log iterator has nowhere to deliver, and there'd
|
|
70
|
+
be no `RegisteredSession` close hook to stop it. The GET `'close'` handler drops **subscriptions only**
|
|
71
|
+
(`dropSessionSubscriptions`), _not_ pending server requests: those ride the per-call POST stream, so a normal
|
|
72
|
+
GET reconnect must not reject an in-flight `ctx.serverRequest`. A `DELETE` (explicit session teardown) drops
|
|
73
|
+
**both**, because it may arrive with no open GET stream.
|
|
74
|
+
|
|
75
|
+
- **SSE resumability (`Last-Event-ID`).** Every GET-channel frame goes through `pushSessionFrame`, which
|
|
76
|
+
assigns a monotonic event id and appends to a bounded per-session `replayBuffer`. On reconnect with a
|
|
77
|
+
`Last-Event-ID` header, `replaySince` re-sends only the frames after that id. The event-id sequence **and**
|
|
78
|
+
the buffer carry across a supersede (a fresh GET replacing the old one for the same session id), so ids stay
|
|
79
|
+
monotonic and no frame is lost across a reconnect.
|
|
80
|
+
|
|
81
|
+
- **Test seams avoid loading thread/audit machinery in unit tests.** `_setSubscribeImplForTest`
|
|
82
|
+
(`resources.ts`) and `_setItcForTest` (`serverRequests.ts`) inject fakes so the unit suite needn't spin up
|
|
83
|
+
the audit log or ITC. Consequence: the subscribe **targeting** logic (`id`/`isCollection` derivation) is
|
|
84
|
+
_bypassed_ by the seam and is therefore covered at the **integration** level (`sse-listchanged.test.ts` N3
|
|
85
|
+
record / N4 collection), not in unit tests.
|
|
86
|
+
|
|
87
|
+
Two related traps: the create/schema-update path's exclusive `update-attributes` lock is a
|
|
88
|
+
synchronous bounded wait (`acquireUpdateAttributesLock` in `Table.ts`: brief hot spin, then
|
|
89
|
+
`Atomics.wait` backoff, retryable `ServerError` after the 10s `UPDATE_ATTRIBUTES_LOCK_TIMEOUT` — harper#2251; it used to be
|
|
90
|
+
an unbounded `while (!tryLock()) {}` spin that pinned a worker core forever if the holder never
|
|
91
|
+
released). Release is structural — `table()` releases in a single `finally` and `dropTable` uses
|
|
92
|
+
`withUpdateAttributesLock` — so a throw inside the locked window cannot leak the lock (regression
|
|
93
|
+
suite: `unitTests/resources/updateAttributesLock.test.js`). Because the acquire can now throw,
|
|
94
|
+
`table()` takes the RocksDB lock _before_ it mutates the live `Table` (attributes, class metadata,
|
|
95
|
+
index handles): losing the race then leaves this worker's in-memory schema exactly as it found it,
|
|
96
|
+
and moving any mutation above that acquire reintroduces schema drift the catalog never saw. LMDB
|
|
97
|
+
keeps the lazy acquire — its `exclusiveLock()` is an environment-wide write transaction that cannot
|
|
98
|
+
time out, so taking it eagerly would stall every write to the database on an unchanged reload. A
|
|
99
|
+
successful acquire that waited past `UPDATE_ATTRIBUTES_LOCK_SLOW_WAIT` (1s) warns once, since
|
|
100
|
+
contention is otherwise invisible until it becomes a timeout. The locked
|
|
101
|
+
sections MUST stay synchronous: the wait blocks the event loop, so an awaited operation inside
|
|
102
|
+
one would stall a concurrent acquirer to its deadline. And dropping then recreating a
|
|
103
|
+
same-named table within one process requires @harperfast/rocksdb-js >= the column-family
|
|
104
|
+
eviction fix (1.4.3 / rocksdb-js#<main PR>): older bindings keep the dropped column family's
|
|
105
|
+
by-name registry entry alive whenever other worker threads hold handles, so the recreate
|
|
106
|
+
silently reuses a dangling handle and every write fails with "Invalid column family specified
|
|
107
|
+
in write batch", poisoning the whole database env until restart. The regression suite for all
|
|
108
|
+
of this is `unitTests/resources/dropTableGhost.test.js` (it fails by design on pre-fix
|
|
109
|
+
bindings).
|
package/components/mcp/audit.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* (session id, profile, tool name).
|
|
7
7
|
*
|
|
8
8
|
* Argument summarization runs through a redaction step that drops anything
|
|
9
|
-
* that looks like a credential (
|
|
9
|
+
* that looks like a credential (secret/password/token). Operators who need
|
|
10
10
|
* stricter PII handling configure `mcp.audit.argumentRedactor` to a custom
|
|
11
11
|
* function via a future component-author hook (v1.1).
|
|
12
12
|
*/
|
|
@@ -25,17 +25,17 @@ export interface AuditEntry {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const REDACTION_PATTERN = /(secret|password|token|api[-_]?key|credentials?|auth)/i;
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
// Both secret tools carry all three fields, not just the ones each declares: MCP forwards arguments
|
|
29
|
+
// as-is and audits them after the handler, so a field the tool rejects is still logged. Mirrors what
|
|
30
|
+
// the REST operations log strips unconditionally (UNLOGGABLE_OPERATION_FIELDS).
|
|
31
|
+
const SECRET_VALUE_FIELDS: ReadonlySet<string> = new Set(['value', 'values', 'envelope']);
|
|
32
|
+
// Per tool, not global: `value`/`values` are ordinary params on many other operations.
|
|
33
|
+
const REDACTION_FIELDS_BY_TOOL = new Map<string, ReadonlySet<string>>([
|
|
34
|
+
['set_secret', SECRET_VALUE_FIELDS],
|
|
35
|
+
['set_env_value', SECRET_VALUE_FIELDS],
|
|
36
|
+
['add_ssh_key', new Set(['key'])],
|
|
37
|
+
['update_ssh_key', new Set(['key'])],
|
|
38
|
+
]);
|
|
39
39
|
const REDACTION_PLACEHOLDER = '[redacted]';
|
|
40
40
|
const MAX_REDACTION_DEPTH = 10;
|
|
41
41
|
|
|
@@ -46,23 +46,27 @@ const MAX_REDACTION_DEPTH = 10;
|
|
|
46
46
|
* a credential buried below the depth limit cannot leak. Returns a shallow
|
|
47
47
|
* clone — the caller's payload is never mutated.
|
|
48
48
|
*/
|
|
49
|
-
export function redactArgs(value: unknown, depth = 0,
|
|
49
|
+
export function redactArgs(value: unknown, depth = 0, redactionFields?: ReadonlySet<string>): unknown {
|
|
50
50
|
if (value === null || typeof value !== 'object') return value;
|
|
51
51
|
if (depth > MAX_REDACTION_DEPTH) return REDACTION_PLACEHOLDER;
|
|
52
52
|
if (Array.isArray(value)) {
|
|
53
|
-
return value.map((v) => redactArgs(v, depth + 1,
|
|
53
|
+
return value.map((v) => redactArgs(v, depth + 1, redactionFields));
|
|
54
54
|
}
|
|
55
55
|
const out: Record<string, unknown> = {};
|
|
56
56
|
for (const [k, v] of Object.entries(value)) {
|
|
57
|
-
if (REDACTION_PATTERN.test(k) || (
|
|
57
|
+
if (REDACTION_PATTERN.test(k) || redactionFields?.has(k.toLowerCase())) {
|
|
58
58
|
out[k] = REDACTION_PLACEHOLDER;
|
|
59
59
|
} else {
|
|
60
|
-
out[k] = redactArgs(v, depth + 1,
|
|
60
|
+
out[k] = redactArgs(v, depth + 1, redactionFields);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
return out;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export function redactArgsForTool(value: unknown, tool: string): unknown {
|
|
67
|
+
return redactArgs(value, 0, REDACTION_FIELDS_BY_TOOL.get(tool));
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
/** Mask a session id for logging — first 8 chars, suffix elided. */
|
|
67
71
|
export function maskSessionId(id: string): string {
|
|
68
72
|
if (typeof id !== 'string' || id.length <= 8) return id;
|
|
@@ -79,7 +83,7 @@ export function emitAuditEntry(entry: AuditEntry): void {
|
|
|
79
83
|
const masked = {
|
|
80
84
|
...entry,
|
|
81
85
|
sessionId: maskSessionId(entry.sessionId),
|
|
82
|
-
args:
|
|
86
|
+
args: redactArgsForTool(entry.args, entry.tool),
|
|
83
87
|
};
|
|
84
88
|
harperLogger.info({ category: 'mcp.audit', ...masked });
|
|
85
89
|
} catch (err) {
|