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.
Files changed (395) hide show
  1. package/agent/operations.ts +13 -16
  2. package/components/Application.ts +188 -7
  3. package/components/ApplicationScope.ts +3 -0
  4. package/components/Scope.ts +12 -0
  5. package/components/anthropic/index.ts +9 -2
  6. package/components/bedrock/index.ts +25 -2
  7. package/components/componentLoader.ts +56 -7
  8. package/components/componentSecrets.ts +451 -0
  9. package/components/deploymentRecorder.ts +7 -1
  10. package/components/mcp/adapters/fastify.ts +3 -0
  11. package/components/mcp/adapters/harperHttp.ts +3 -0
  12. package/components/mcp/audit.ts +21 -6
  13. package/components/mcp/customResourceRegistry.ts +17 -38
  14. package/components/mcp/listChanged.ts +37 -20
  15. package/components/mcp/quota.ts +145 -0
  16. package/components/mcp/rateLimit.ts +152 -14
  17. package/components/mcp/resources.ts +2 -4
  18. package/components/mcp/toolRegistry.ts +73 -4
  19. package/components/mcp/tools/application.ts +77 -8
  20. package/components/mcp/tools/operations.ts +106 -36
  21. package/components/mcp/tools/schemas/operationDescriptions.ts +4 -4
  22. package/components/mcp/tools/schemas/operations.ts +5 -0
  23. package/components/mcp/transport.ts +55 -4
  24. package/components/ollama/index.ts +6 -2
  25. package/components/openai/index.ts +9 -2
  26. package/components/operations.js +204 -10
  27. package/components/operationsValidation.js +173 -0
  28. package/components/secretOperations.ts +498 -0
  29. package/config/configUtils.ts +25 -1
  30. package/config-app.schema.json +81 -0
  31. package/config-root.schema.json +8 -1
  32. package/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  33. package/dataLayer/readAuditLog.ts +14 -0
  34. package/dataLayer/schemaDescribe.ts +1 -0
  35. package/dist/agent/operations.d.ts +7 -4
  36. package/dist/agent/operations.js +13 -15
  37. package/dist/agent/operations.js.map +1 -1
  38. package/dist/components/Application.d.ts +25 -2
  39. package/dist/components/Application.js +166 -7
  40. package/dist/components/Application.js.map +1 -1
  41. package/dist/components/ApplicationScope.d.ts +2 -0
  42. package/dist/components/ApplicationScope.js +3 -0
  43. package/dist/components/ApplicationScope.js.map +1 -1
  44. package/dist/components/Scope.d.ts +8 -0
  45. package/dist/components/Scope.js +11 -0
  46. package/dist/components/Scope.js.map +1 -1
  47. package/dist/components/anthropic/index.d.ts +4 -1
  48. package/dist/components/anthropic/index.js +4 -2
  49. package/dist/components/anthropic/index.js.map +1 -1
  50. package/dist/components/bedrock/index.d.ts +4 -1
  51. package/dist/components/bedrock/index.js +22 -2
  52. package/dist/components/bedrock/index.js.map +1 -1
  53. package/dist/components/componentLoader.d.ts +2 -0
  54. package/dist/components/componentLoader.js +52 -10
  55. package/dist/components/componentLoader.js.map +1 -1
  56. package/dist/components/componentSecrets.d.ts +63 -0
  57. package/dist/components/componentSecrets.js +405 -0
  58. package/dist/components/componentSecrets.js.map +1 -0
  59. package/dist/components/deploymentRecorder.d.ts +3 -0
  60. package/dist/components/deploymentRecorder.js +4 -3
  61. package/dist/components/deploymentRecorder.js.map +1 -1
  62. package/dist/components/mcp/adapters/fastify.d.ts +2 -0
  63. package/dist/components/mcp/adapters/fastify.js +1 -0
  64. package/dist/components/mcp/adapters/fastify.js.map +1 -1
  65. package/dist/components/mcp/adapters/harperHttp.d.ts +2 -0
  66. package/dist/components/mcp/adapters/harperHttp.js +1 -0
  67. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  68. package/dist/components/mcp/audit.d.ts +2 -2
  69. package/dist/components/mcp/audit.js +20 -5
  70. package/dist/components/mcp/audit.js.map +1 -1
  71. package/dist/components/mcp/customResourceRegistry.d.ts +5 -12
  72. package/dist/components/mcp/customResourceRegistry.js +15 -30
  73. package/dist/components/mcp/customResourceRegistry.js.map +1 -1
  74. package/dist/components/mcp/listChanged.d.ts +7 -2
  75. package/dist/components/mcp/listChanged.js +28 -0
  76. package/dist/components/mcp/listChanged.js.map +1 -1
  77. package/dist/components/mcp/quota.d.ts +31 -0
  78. package/dist/components/mcp/quota.js +155 -0
  79. package/dist/components/mcp/quota.js.map +1 -0
  80. package/dist/components/mcp/rateLimit.d.ts +19 -3
  81. package/dist/components/mcp/rateLimit.js +129 -13
  82. package/dist/components/mcp/rateLimit.js.map +1 -1
  83. package/dist/components/mcp/resources.js.map +1 -1
  84. package/dist/components/mcp/toolRegistry.d.ts +39 -1
  85. package/dist/components/mcp/toolRegistry.js +60 -5
  86. package/dist/components/mcp/toolRegistry.js.map +1 -1
  87. package/dist/components/mcp/tools/application.d.ts +8 -1
  88. package/dist/components/mcp/tools/application.js +65 -5
  89. package/dist/components/mcp/tools/application.js.map +1 -1
  90. package/dist/components/mcp/tools/operations.d.ts +15 -3
  91. package/dist/components/mcp/tools/operations.js +99 -38
  92. package/dist/components/mcp/tools/operations.js.map +1 -1
  93. package/dist/components/mcp/tools/schemas/operationDescriptions.js +4 -4
  94. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  95. package/dist/components/mcp/tools/schemas/operations.js +4 -0
  96. package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
  97. package/dist/components/mcp/transport.d.ts +6 -0
  98. package/dist/components/mcp/transport.js +47 -3
  99. package/dist/components/mcp/transport.js.map +1 -1
  100. package/dist/components/ollama/index.d.ts +4 -1
  101. package/dist/components/ollama/index.js +4 -2
  102. package/dist/components/ollama/index.js.map +1 -1
  103. package/dist/components/openai/index.d.ts +4 -1
  104. package/dist/components/openai/index.js +4 -2
  105. package/dist/components/openai/index.js.map +1 -1
  106. package/dist/components/operations.d.ts +39 -0
  107. package/dist/components/operations.js +194 -2
  108. package/dist/components/operations.js.map +1 -1
  109. package/dist/components/operationsValidation.d.ts +38 -0
  110. package/dist/components/operationsValidation.js +155 -0
  111. package/dist/components/operationsValidation.js.map +1 -1
  112. package/dist/components/secretOperations.d.ts +113 -0
  113. package/dist/components/secretOperations.js +475 -0
  114. package/dist/components/secretOperations.js.map +1 -0
  115. package/dist/config/configUtils.d.ts +4 -1
  116. package/dist/config/configUtils.js +18 -1
  117. package/dist/config/configUtils.js.map +1 -1
  118. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js +4 -3
  119. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbUtility/initializePaths.js.map +1 -1
  120. package/dist/dataLayer/readAuditLog.js +11 -0
  121. package/dist/dataLayer/readAuditLog.js.map +1 -1
  122. package/dist/dataLayer/schemaDescribe.js +2 -0
  123. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  124. package/dist/globals.d.ts +1 -1
  125. package/dist/globals.js +1 -0
  126. package/dist/globals.js.map +1 -1
  127. package/dist/index.d.ts +3 -0
  128. package/dist/index.js +1 -0
  129. package/dist/index.js.map +1 -1
  130. package/dist/json/systemSchema.json +38 -0
  131. package/dist/resources/DatabaseTransaction.d.ts +31 -3
  132. package/dist/resources/DatabaseTransaction.js +190 -29
  133. package/dist/resources/DatabaseTransaction.js.map +1 -1
  134. package/dist/resources/LMDBTransaction.d.ts +1 -1
  135. package/dist/resources/LMDBTransaction.js +43 -5
  136. package/dist/resources/LMDBTransaction.js.map +1 -1
  137. package/dist/resources/PrimaryRocksDatabase.d.ts +51 -0
  138. package/dist/resources/PrimaryRocksDatabase.js +194 -0
  139. package/dist/resources/PrimaryRocksDatabase.js.map +1 -0
  140. package/dist/resources/RecordEncoder.d.ts +1 -0
  141. package/dist/resources/RecordEncoder.js +81 -75
  142. package/dist/resources/RecordEncoder.js.map +1 -1
  143. package/dist/resources/RequestTarget.d.ts +19 -0
  144. package/dist/resources/RequestTarget.js.map +1 -1
  145. package/dist/resources/Resource.d.ts +0 -1
  146. package/dist/resources/Resource.js +103 -16
  147. package/dist/resources/Resource.js.map +1 -1
  148. package/dist/resources/ResourceInterface.d.ts +6 -2
  149. package/dist/resources/ResourceInterface.js.map +1 -1
  150. package/dist/resources/Resources.js.map +1 -1
  151. package/dist/resources/Table.d.ts +6 -4
  152. package/dist/resources/Table.js +247 -24
  153. package/dist/resources/Table.js.map +1 -1
  154. package/dist/resources/analytics/read.js +25 -28
  155. package/dist/resources/analytics/read.js.map +1 -1
  156. package/dist/resources/blob.js +165 -22
  157. package/dist/resources/blob.js.map +1 -1
  158. package/dist/resources/crdt.d.ts +2 -0
  159. package/dist/resources/crdt.js +45 -36
  160. package/dist/resources/crdt.js.map +1 -1
  161. package/dist/resources/databases.d.ts +1 -0
  162. package/dist/resources/databases.js +134 -7
  163. package/dist/resources/databases.js.map +1 -1
  164. package/dist/resources/graphql.js +8 -0
  165. package/dist/resources/graphql.js.map +1 -1
  166. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +35 -5
  167. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +239 -14
  168. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  169. package/dist/resources/jsResource.js +6 -0
  170. package/dist/resources/jsResource.js.map +1 -1
  171. package/dist/resources/loadEnv.js +30 -3
  172. package/dist/resources/loadEnv.js.map +1 -1
  173. package/dist/resources/login.js +2 -2
  174. package/dist/resources/login.js.map +1 -1
  175. package/dist/resources/models/Models.js +14 -3
  176. package/dist/resources/models/Models.js.map +1 -1
  177. package/dist/resources/models/embedHook.js +10 -2
  178. package/dist/resources/models/embedHook.js.map +1 -1
  179. package/dist/resources/models/openaiStream.d.ts +56 -0
  180. package/dist/resources/models/openaiStream.js +94 -0
  181. package/dist/resources/models/openaiStream.js.map +1 -0
  182. package/dist/resources/replayLogs.js +4 -3
  183. package/dist/resources/replayLogs.js.map +1 -1
  184. package/dist/resources/search.d.ts +1 -1
  185. package/dist/resources/search.js +104 -11
  186. package/dist/resources/search.js.map +1 -1
  187. package/dist/resources/secretDecryptor.d.ts +54 -0
  188. package/dist/resources/secretDecryptor.js +131 -0
  189. package/dist/resources/secretDecryptor.js.map +1 -0
  190. package/dist/resources/tracked.js +15 -5
  191. package/dist/resources/tracked.js.map +1 -1
  192. package/dist/security/auth.js +71 -8
  193. package/dist/security/auth.js.map +1 -1
  194. package/dist/security/jsLoader.js +11 -0
  195. package/dist/security/jsLoader.js.map +1 -1
  196. package/dist/security/tokenAuthentication.d.ts +9 -1
  197. package/dist/security/tokenAuthentication.js +31 -1
  198. package/dist/security/tokenAuthentication.js.map +1 -1
  199. package/dist/server/DurableSubscriptionsSession.d.ts +1 -1
  200. package/dist/server/DurableSubscriptionsSession.js +8 -4
  201. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  202. package/dist/server/REST.js +28 -9
  203. package/dist/server/REST.js.map +1 -1
  204. package/dist/server/fastifyRoutes.js +11 -1
  205. package/dist/server/fastifyRoutes.js.map +1 -1
  206. package/dist/server/graphqlQuerying.js +4 -3
  207. package/dist/server/graphqlQuerying.js.map +1 -1
  208. package/dist/server/http.d.ts +40 -2
  209. package/dist/server/http.js +560 -77
  210. package/dist/server/http.js.map +1 -1
  211. package/dist/server/itc/serverHandlers.js +64 -0
  212. package/dist/server/itc/serverHandlers.js.map +1 -1
  213. package/dist/server/liveSubscriptionAuth.d.ts +12 -0
  214. package/dist/server/liveSubscriptionAuth.js +140 -0
  215. package/dist/server/liveSubscriptionAuth.js.map +1 -0
  216. package/dist/server/loadRootComponents.js +2 -1
  217. package/dist/server/loadRootComponents.js.map +1 -1
  218. package/dist/server/middlewareChain.d.ts +44 -1
  219. package/dist/server/middlewareChain.js +86 -10
  220. package/dist/server/middlewareChain.js.map +1 -1
  221. package/dist/server/mqtt.js +2 -1
  222. package/dist/server/mqtt.js.map +1 -1
  223. package/dist/server/operationsServer.js +1 -1
  224. package/dist/server/operationsServer.js.map +1 -1
  225. package/dist/server/serverHelpers/Headers.d.ts +9 -0
  226. package/dist/server/serverHelpers/Headers.js +22 -0
  227. package/dist/server/serverHelpers/Headers.js.map +1 -1
  228. package/dist/server/serverHelpers/JSONStream.js +3 -3
  229. package/dist/server/serverHelpers/JSONStream.js.map +1 -1
  230. package/dist/server/serverHelpers/Request.d.ts +52 -1
  231. package/dist/server/serverHelpers/Request.js +122 -1
  232. package/dist/server/serverHelpers/Request.js.map +1 -1
  233. package/dist/server/serverHelpers/contentTypes.js +3 -8
  234. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  235. package/dist/server/serverHelpers/progressEmitter.d.ts +19 -0
  236. package/dist/server/serverHelpers/progressEmitter.js +52 -4
  237. package/dist/server/serverHelpers/progressEmitter.js.map +1 -1
  238. package/dist/server/serverHelpers/registeredOperations.d.ts +40 -0
  239. package/dist/server/serverHelpers/registeredOperations.js +286 -0
  240. package/dist/server/serverHelpers/registeredOperations.js.map +1 -0
  241. package/dist/server/serverHelpers/serverHandlers.js +11 -5
  242. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  243. package/dist/server/serverHelpers/serverUtilities.d.ts +1 -0
  244. package/dist/server/serverHelpers/serverUtilities.js +67 -5
  245. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  246. package/dist/server/serverHelpers/uwsServer.d.ts +80 -0
  247. package/dist/server/serverHelpers/uwsServer.js +433 -0
  248. package/dist/server/serverHelpers/uwsServer.js.map +1 -0
  249. package/dist/server/static.d.ts +0 -15
  250. package/dist/server/static.js +161 -2
  251. package/dist/server/static.js.map +1 -1
  252. package/dist/server/status/index.d.ts +4 -1
  253. package/dist/server/status/index.js +64 -4
  254. package/dist/server/status/index.js.map +1 -1
  255. package/dist/server/threads/manageThreads.d.ts +10 -0
  256. package/dist/server/threads/manageThreads.js +107 -4
  257. package/dist/server/threads/manageThreads.js.map +1 -1
  258. package/dist/server/threads/socketRouter.js +5 -1
  259. package/dist/server/threads/socketRouter.js.map +1 -1
  260. package/dist/server/threads/threadServer.js +92 -14
  261. package/dist/server/threads/threadServer.js.map +1 -1
  262. package/dist/upgrade/directives/5-2-0.d.ts +7 -0
  263. package/dist/upgrade/directives/5-2-0.js +108 -0
  264. package/dist/upgrade/directives/5-2-0.js.map +1 -0
  265. package/dist/upgrade/directives/directivesController.js +2 -1
  266. package/dist/upgrade/directives/directivesController.js.map +1 -1
  267. package/dist/utility/common_utils.js +5 -0
  268. package/dist/utility/common_utils.js.map +1 -1
  269. package/dist/utility/envFile.d.ts +41 -0
  270. package/dist/utility/envFile.js +221 -0
  271. package/dist/utility/envFile.js.map +1 -0
  272. package/dist/utility/globalSchema.d.ts +9 -0
  273. package/dist/utility/hdbTerms.d.ts +25 -0
  274. package/dist/utility/hdbTerms.js +31 -0
  275. package/dist/utility/hdbTerms.js.map +1 -1
  276. package/dist/utility/logging/harper_logger.d.ts +21 -0
  277. package/dist/utility/logging/harper_logger.js +160 -8
  278. package/dist/utility/logging/harper_logger.js.map +1 -1
  279. package/dist/utility/logging/logRotator.js +29 -17
  280. package/dist/utility/logging/logRotator.js.map +1 -1
  281. package/dist/utility/logging/readLog.d.ts +1 -1
  282. package/dist/utility/logging/readLog.js +305 -2
  283. package/dist/utility/logging/readLog.js.map +1 -1
  284. package/dist/utility/operationPermissions.d.ts +18 -0
  285. package/dist/utility/operationPermissions.js +35 -1
  286. package/dist/utility/operationPermissions.js.map +1 -1
  287. package/dist/utility/operation_authorization.d.ts +17 -0
  288. package/dist/utility/operation_authorization.js +38 -0
  289. package/dist/utility/operation_authorization.js.map +1 -1
  290. package/dist/utility/secretEnvelope.d.ts +30 -0
  291. package/dist/utility/secretEnvelope.js +94 -0
  292. package/dist/utility/secretEnvelope.js.map +1 -0
  293. package/dist/utility/signalling.d.ts +7 -0
  294. package/dist/utility/signalling.js +16 -0
  295. package/dist/utility/signalling.js.map +1 -1
  296. package/dist/validation/configValidator.js +70 -24
  297. package/dist/validation/configValidator.js.map +1 -1
  298. package/index.ts +4 -0
  299. package/json/systemSchema.json +38 -0
  300. package/npm-shrinkwrap.json +15066 -9347
  301. package/package.json +13 -8
  302. package/resources/DESIGN.md +15 -12
  303. package/resources/DatabaseTransaction.ts +206 -37
  304. package/resources/LMDBTransaction.ts +43 -9
  305. package/resources/PrimaryRocksDatabase.ts +201 -0
  306. package/resources/RecordEncoder.ts +81 -69
  307. package/resources/RequestTarget.ts +21 -0
  308. package/resources/Resource.ts +109 -25
  309. package/resources/ResourceInterface.ts +6 -2
  310. package/resources/Resources.ts +1 -3
  311. package/resources/Table.ts +238 -29
  312. package/resources/analytics/read.ts +25 -30
  313. package/resources/blob.ts +159 -23
  314. package/resources/crdt.ts +37 -31
  315. package/resources/databases.ts +143 -6
  316. package/resources/graphql.ts +8 -0
  317. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +251 -17
  318. package/resources/jsResource.ts +6 -0
  319. package/resources/loadEnv.ts +34 -3
  320. package/resources/login.ts +2 -2
  321. package/resources/models/Models.ts +19 -3
  322. package/resources/models/embedHook.ts +13 -2
  323. package/resources/models/openaiStream.ts +133 -0
  324. package/resources/replayLogs.ts +4 -3
  325. package/resources/search.ts +113 -11
  326. package/resources/secretDecryptor.ts +159 -0
  327. package/resources/tracked.ts +24 -17
  328. package/security/auth.ts +70 -10
  329. package/security/jsLoader.ts +12 -0
  330. package/security/tokenAuthentication.ts +42 -1
  331. package/server/DESIGN.md +10 -0
  332. package/server/DurableSubscriptionsSession.ts +8 -4
  333. package/server/REST.ts +26 -9
  334. package/server/fastifyRoutes.ts +10 -1
  335. package/server/graphqlQuerying.ts +4 -3
  336. package/server/http.ts +563 -77
  337. package/server/itc/serverHandlers.js +70 -0
  338. package/server/liveSubscriptionAuth.ts +152 -0
  339. package/server/loadRootComponents.js +2 -1
  340. package/server/middlewareChain.ts +107 -17
  341. package/server/mqtt.ts +2 -1
  342. package/server/operationsServer.ts +2 -2
  343. package/server/serverHelpers/Headers.ts +24 -0
  344. package/server/serverHelpers/JSONStream.ts +3 -3
  345. package/server/serverHelpers/Request.ts +127 -0
  346. package/server/serverHelpers/contentTypes.ts +3 -8
  347. package/server/serverHelpers/progressEmitter.ts +55 -5
  348. package/server/serverHelpers/registeredOperations.ts +259 -0
  349. package/server/serverHelpers/serverHandlers.js +11 -5
  350. package/server/serverHelpers/serverUtilities.ts +88 -5
  351. package/server/serverHelpers/uwsServer.ts +488 -0
  352. package/server/static.ts +180 -2
  353. package/server/status/index.ts +72 -4
  354. package/server/threads/manageThreads.js +102 -4
  355. package/server/threads/socketRouter.ts +5 -1
  356. package/server/threads/threadServer.js +93 -10
  357. package/studio/web/assets/{Chat-SdD1EZca.js → Chat-Cv_2paZE.js} +2 -2
  358. package/studio/web/assets/{Chat-SdD1EZca.js.map → Chat-Cv_2paZE.js.map} +1 -1
  359. package/studio/web/assets/{FloatingChat-GMq6tHK9.js → FloatingChat-CPyPIcVR.js} +4 -4
  360. package/studio/web/assets/{FloatingChat-GMq6tHK9.js.map → FloatingChat-CPyPIcVR.js.map} +1 -1
  361. package/studio/web/assets/{applications-B5hhv7uA.js → applications-C0jT2vdZ.js} +2 -2
  362. package/studio/web/assets/{applications-B5hhv7uA.js.map → applications-C0jT2vdZ.js.map} +1 -1
  363. package/studio/web/assets/{index-VFcpNWgq.js → index-D_GKOkhn.js} +6 -6
  364. package/studio/web/assets/index-D_GKOkhn.js.map +1 -0
  365. package/studio/web/assets/{index.lazy-DgcJojIA.js → index.lazy-CAmCIA65.js} +4 -4
  366. package/studio/web/assets/{index.lazy-DgcJojIA.js.map → index.lazy-CAmCIA65.js.map} +1 -1
  367. package/studio/web/assets/{profile-BqN-8vzm.js → profile-CaF-4aZe.js} +2 -2
  368. package/studio/web/assets/{profile-BqN-8vzm.js.map → profile-CaF-4aZe.js.map} +1 -1
  369. package/studio/web/assets/{setComponentFile-CHFvFq9u.js → setComponentFile-DLW1DHCt.js} +2 -2
  370. package/studio/web/assets/{setComponentFile-CHFvFq9u.js.map → setComponentFile-DLW1DHCt.js.map} +1 -1
  371. package/studio/web/assets/{setup-CQ3vWJSr.js → setup-TGCErIhq.js} +2 -2
  372. package/studio/web/assets/{setup-CQ3vWJSr.js.map → setup-TGCErIhq.js.map} +1 -1
  373. package/studio/web/assets/{status-BsW5fjuh.js → status-DG0Maoao.js} +2 -2
  374. package/studio/web/assets/{status-BsW5fjuh.js.map → status-DG0Maoao.js.map} +1 -1
  375. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js → swagger-ui-react-8T4SgQ1m.js} +2 -2
  376. package/studio/web/assets/{swagger-ui-react-DITdeB9-.js.map → swagger-ui-react-8T4SgQ1m.js.map} +1 -1
  377. package/studio/web/assets/{tsMode-Dyph-OUs.js → tsMode-DJ6Sl24Q.js} +2 -2
  378. package/studio/web/assets/{tsMode-Dyph-OUs.js.map → tsMode-DJ6Sl24Q.js.map} +1 -1
  379. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js → useEntityRestURL-JO2mfWTQ.js} +2 -2
  380. package/studio/web/assets/{useEntityRestURL-D0QTUqex.js.map → useEntityRestURL-JO2mfWTQ.js.map} +1 -1
  381. package/studio/web/index.html +1 -1
  382. package/upgrade/directives/5-2-0.ts +82 -0
  383. package/upgrade/directives/directivesController.ts +2 -1
  384. package/utility/common_utils.ts +5 -0
  385. package/utility/envFile.ts +218 -0
  386. package/utility/hdbTerms.ts +31 -0
  387. package/utility/logging/harper_logger.ts +156 -8
  388. package/utility/logging/logRotator.ts +24 -14
  389. package/utility/logging/readLog.ts +323 -2
  390. package/utility/operationPermissions.ts +35 -1
  391. package/utility/operation_authorization.ts +74 -1
  392. package/utility/secretEnvelope.ts +113 -0
  393. package/utility/signalling.ts +15 -0
  394. package/validation/configValidator.ts +78 -25
  395. package/studio/web/assets/index-VFcpNWgq.js.map +0 -1
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Component-facing consumption of the hdb_secret store (#1550) — two explicit, mutually-exclusive
3
+ * delivery tiers keyed off the row itself (set_secret decides the tier; see components/secretOperations.ts):
4
+ *
5
+ * - Global tier: rows flagged `processEnv: true` are decrypted at startup (before components load)
6
+ * and materialized into the REAL `process.env`, exactly like `.env` values. Precedence: a
7
+ * pre-existing real environment variable always wins over the store. Child processes inherit
8
+ * these values (same as `.env` today). There is no isolation promise on this tier.
9
+ * - Scoped tier: rows with `grants` (and no `processEnv`) NEVER land in `process.env` — they are
10
+ * exposed only through the per-component accessor (`import { secrets } from 'harper'` /
11
+ * `scope.secrets`), so they are not inherited by child processes and are invisible to env dumps.
12
+ * The `grants` list on the row is the authority for which components see them; a row with neither
13
+ * `processEnv` nor any grant is inert (visible to no component) until it is granted.
14
+ *
15
+ * Component configs declare their environment expectations in an `env:` block:
16
+ *
17
+ * env:
18
+ * NODE_ENV: production # string = inline literal → process.env
19
+ * STRIPE_KEY: { required: true, description: Stripe } # object = declaration, satisfied from the store
20
+ * SENTRY_DSN: { required: false } # optional declaration
21
+ *
22
+ * Declarations are requests, never grants — a declaration cannot widen access to a scoped secret.
23
+ * A `required: true` declaration that is unsatisfied stops that component from loading (the
24
+ * instance keeps running); the failure reason is one of `missing` (no row, no env var),
25
+ * `ungranted` (a scoped row exists but this component is not in its grants), or
26
+ * `custody-unavailable` (a row exists but cannot be decrypted on this node).
27
+ *
28
+ * Scoping caveat (documented, deliberate): JS-level scoping is a slowdown layer, not a security
29
+ * boundary — component code shares a process, so containers/uids remain the real boundary. Under
30
+ * the vm/compartment loaders the `harper` module is per-scope, so `import { secrets } from
31
+ * 'harper'` binds exactly; under the native loader (and for bundler-loaded code) the `harper`
32
+ * package is a process-wide singleton, so `secrets` binds via the component-load AsyncLocalStorage
33
+ * context. Accessing the process-wide `secrets` outside a component-load context fails loudly
34
+ * rather than guessing. The recommended idiom is a module-top-level destructure
35
+ * (`const { MY_KEY } = secrets;`), where binding is exact in every mode.
36
+ *
37
+ * Late custody / changed secrets heal on restart or component reload (each load cycle re-reads the
38
+ * store) — there is no live re-materialization.
39
+ */
40
+ import { AsyncLocalStorage } from 'node:async_hooks';
41
+ import { databases } from '../resources/databases.ts';
42
+ import { SYSTEM_TABLE_NAMES } from '../utility/hdbTerms.ts';
43
+ import { getSecretDecryptor } from '../resources/secretDecryptor.ts';
44
+ import { isEncryptedEnvValue } from '../utility/envFile.ts';
45
+ import logger from '../utility/logging/harper_logger.ts';
46
+ import { _assignPackageExport } from '../globals.js';
47
+
48
+ const SECRET_TABLE = SYSTEM_TABLE_NAMES.SECRET_TABLE_NAME;
49
+
50
+ /** A per-component secrets view: read-only name→value map exposed as `import { secrets } from 'harper'`. */
51
+ export type SecretsView = Readonly<Record<string, string>>;
52
+
53
+ export type UnsatisfiedReason = 'missing' | 'ungranted' | 'custody-unavailable';
54
+
55
+ /** A declared-but-unsatisfied env expectation — metadata only, never values. */
56
+ export interface UnsatisfiedDeclaration {
57
+ name: string;
58
+ required: boolean;
59
+ description?: string;
60
+ reason: UnsatisfiedReason;
61
+ /** The store tier of the matching row, when one exists (`missing` has no row, so no tier). */
62
+ tier?: 'global' | 'scoped';
63
+ /** Human-readable detail (e.g. the decrypt failure) — never the value. */
64
+ detail?: string;
65
+ }
66
+
67
+ interface EnvDeclaration {
68
+ required: boolean;
69
+ description?: string;
70
+ }
71
+
72
+ interface SecretRowState {
73
+ grants: string[];
74
+ /** Whether this row is materialized into the real process.env (the global delivery tier). */
75
+ processEnv: boolean;
76
+ /** Decrypted plaintext; undefined when this node could not decrypt the envelope. */
77
+ value?: string;
78
+ /** Why decryption failed, when it did. */
79
+ failure?: string;
80
+ }
81
+
82
+ // Snapshot of the store, refreshed by materializeGlobalSecrets() at the start of each load cycle.
83
+ let secretRows = new Map<string, SecretRowState>();
84
+ let storeAvailable = false;
85
+ // Keys THIS module wrote into process.env, so a reload can update them (changed secret) or retract
86
+ // them (row deleted / grants tightened to scoped) without ever touching genuinely pre-existing env.
87
+ const ownedEnvKeys = new Set<string>();
88
+
89
+ // Per-component registries, rebuilt as each component's env block is processed.
90
+ const declaredEnvNames = new Map<string, Set<string>>();
91
+ const unsatisfiedEnv = new Map<string, UnsatisfiedDeclaration[]>();
92
+ const accessorCache = new Map<string, SecretsView>();
93
+
94
+ /**
95
+ * Read the hdb_secret store, decrypt what this node's custody allows, materialize the global tier
96
+ * (`processEnv: true` rows) into the real process.env, and snapshot the scoped tier for the accessor. Runs
97
+ * at the start of each load cycle, and again per env-declaring component load so out-of-cycle
98
+ * loads (e.g. deploy validation in a long-lived worker) gate against a fresh snapshot. Never
99
+ * throws — every degraded mode (table missing pre-upgrade, no custody registered, undecryptable
100
+ * rows) logs and leaves the node bootable; the unsatisfied state surfaces per-component when
101
+ * declarations are evaluated.
102
+ *
103
+ * The per-component declaration registries are deliberately NOT reset here: on the main thread
104
+ * `loadedPaths` is never cleared in production, so already-loaded components are not reprocessed
105
+ * on a reload cycle — a cycle-level wipe would permanently empty their status state. Registries
106
+ * are overwrite-on-reprocess instead; state for a deleted component is unreachable from
107
+ * `get_components` (which is keyed by the existing component directories).
108
+ */
109
+ export function materializeGlobalSecrets(): Promise<void> {
110
+ // Single-flight: concurrent callers (the load-cycle call plus per-env-component refreshes fanned
111
+ // out by Promise.all over component loads) join one table scan instead of issuing N scans with
112
+ // N×rows RSA decrypts, and two scans can never interleave — so an older, slower scan cannot
113
+ // overwrite newer state. A caller arriving after completion starts a fresh scan, preserving the
114
+ // deploy-validation freshness guarantee (set_secret → deploy is sequential).
115
+ return (materializeInFlight ??= doMaterializeGlobalSecrets().finally(() => {
116
+ materializeInFlight = undefined;
117
+ }));
118
+ }
119
+ let materializeInFlight: Promise<void> | undefined;
120
+
121
+ async function doMaterializeGlobalSecrets(): Promise<void> {
122
+ const table = (databases as { system?: Record<string, any> }).system?.[SECRET_TABLE];
123
+ if (!table) {
124
+ storeAvailable = false;
125
+ secretRows = new Map();
126
+ accessorCache.clear();
127
+ logger.debug?.(
128
+ `Secrets store not initialized (system.${SECRET_TABLE} missing); component env declarations can only be satisfied from process.env`
129
+ );
130
+ return;
131
+ }
132
+ const rows = new Map<string, SecretRowState>();
133
+ const decryptor = getSecretDecryptor();
134
+ try {
135
+ for await (const row of table.search([])) {
136
+ const name = row.name;
137
+ if (typeof name !== 'string' || !name) continue;
138
+ const state: SecretRowState = {
139
+ grants: Array.isArray(row.grants) ? [...new Set(row.grants as string[])] : [],
140
+ processEnv: row.processEnv === true,
141
+ };
142
+ if (typeof row.envelope === 'string') {
143
+ if (!decryptor) {
144
+ state.failure = 'no secrets custody is registered on this node';
145
+ } else {
146
+ try {
147
+ state.value = decryptor(row.envelope);
148
+ } catch (error) {
149
+ state.failure = `decrypt failed: ${(error as Error).message}`;
150
+ }
151
+ }
152
+ } else {
153
+ state.failure = 'stored row has no envelope';
154
+ }
155
+ rows.set(name, state);
156
+ }
157
+ } catch (error) {
158
+ // Leave the previous snapshot (and process.env) untouched rather than acting on a partial read.
159
+ logger.error(`Failed to read the secrets store (system.${SECRET_TABLE}): ${(error as Error).message}`);
160
+ return;
161
+ }
162
+ storeAvailable = true;
163
+
164
+ for (const [name, state] of rows) {
165
+ if (!state.processEnv) {
166
+ // Scoped tier (or an inert un-granted row): never in process.env. If a previous cycle
167
+ // materialized this name as a processEnv secret and it has since been converted to scoped,
168
+ // retract our value.
169
+ if (ownedEnvKeys.has(name)) {
170
+ delete process.env[name];
171
+ ownedEnvKeys.delete(name);
172
+ }
173
+ continue;
174
+ }
175
+ if (state.value === undefined) {
176
+ // Undecryptable global row: loud, never silent. If we own a previously-materialized value,
177
+ // keep it (last known good) — a restart with custody heals.
178
+ logger.error(`Secret '${name}' could not be materialized into process.env: ${state.failure}`);
179
+ continue;
180
+ }
181
+ if (process.env[name] !== undefined && !ownedEnvKeys.has(name)) {
182
+ logger.warn(`Secret '${name}' is already set on process.env; the pre-existing environment value wins`);
183
+ continue;
184
+ }
185
+ process.env[name] = state.value;
186
+ ownedEnvKeys.add(name);
187
+ }
188
+ // Rows deleted from the store since we materialized them: retract our value.
189
+ for (const name of [...ownedEnvKeys]) {
190
+ if (!rows.has(name)) {
191
+ delete process.env[name];
192
+ ownedEnvKeys.delete(name);
193
+ }
194
+ }
195
+ secretRows = rows;
196
+ accessorCache.clear();
197
+ }
198
+
199
+ function parseDeclaration(componentName: string, name: string, spec: unknown): EnvDeclaration {
200
+ if (spec === null) return { required: false }; // bare `NAME:` in YAML — an optional declaration
201
+ if (typeof spec !== 'object' || Array.isArray(spec)) {
202
+ throw new Error(
203
+ `env.${name} for component '${componentName}' must be a string literal or a declaration object like { required: true, description: ... }`
204
+ );
205
+ }
206
+ const { required, description } = spec as { required?: unknown; description?: unknown };
207
+ if (required !== undefined && typeof required !== 'boolean') {
208
+ throw new Error(`env.${name} for component '${componentName}': 'required' must be a boolean`);
209
+ }
210
+ if (description !== undefined && typeof description !== 'string') {
211
+ throw new Error(`env.${name} for component '${componentName}': 'description' must be a string`);
212
+ }
213
+ return { required: required === true, description: description as string | undefined };
214
+ }
215
+
216
+ // Inline string literal — same semantics as a `.env` line loaded by resources/loadEnv.ts:
217
+ // `enc:v1:` envelopes are decrypted via the registered decryptor (undecryptable → error log + skip,
218
+ // so the app fails on a missing var rather than receiving ciphertext), and a value already present
219
+ // on process.env wins. Like `.env` files today, literals mutate the SHARED process.env: another
220
+ // component's declaration of the same name can be satisfied (or its store row shadowed) depending
221
+ // on load order — an inherent property of the global env tier, not per-component isolation.
222
+ function applyEnvLiteral(componentName: string, name: string, value: string): void {
223
+ if (isEncryptedEnvValue(value)) {
224
+ const decryptor = getSecretDecryptor();
225
+ if (!decryptor) {
226
+ logger.error(
227
+ `env.${name} for component '${componentName}' is encrypted but no secret decryptor is registered; skipping`
228
+ );
229
+ return;
230
+ }
231
+ try {
232
+ value = decryptor(value);
233
+ } catch (error) {
234
+ logger.error(
235
+ `Failed to decrypt env.${name} for component '${componentName}': ${(error as Error).message}; skipping`
236
+ );
237
+ return;
238
+ }
239
+ }
240
+ if (process.env[name] !== undefined) {
241
+ logger.warn(
242
+ `Environment variable conflict: env.${name} from component '${componentName}' is already set on process.env; keeping the existing value`
243
+ );
244
+ return;
245
+ }
246
+ process.env[name] = value;
247
+ }
248
+
249
+ function evaluateDeclaration(
250
+ componentName: string,
251
+ name: string,
252
+ declaration: EnvDeclaration
253
+ ): UnsatisfiedDeclaration | undefined {
254
+ const unsatisfied = (
255
+ reason: UnsatisfiedReason,
256
+ tier?: 'global' | 'scoped',
257
+ detail?: string
258
+ ): UnsatisfiedDeclaration => ({
259
+ name,
260
+ required: declaration.required,
261
+ description: declaration.description,
262
+ reason,
263
+ tier,
264
+ detail,
265
+ });
266
+ const row = secretRows.get(name);
267
+ if (row && !row.processEnv) {
268
+ // Scoped tier: satisfied only through the accessor for a granted component. An empty-grants row
269
+ // is inert (granted to nobody), so it falls through to the `ungranted` branch.
270
+ if (row.grants.includes(componentName)) {
271
+ if (row.value !== undefined) return undefined; // satisfied via the scoped accessor
272
+ // The accessor serves a real env var for declared names when the row is undecryptable, so
273
+ // the gate must accept the same fallback — never gate out a value the runtime would have.
274
+ if (process.env[name] !== undefined) return undefined;
275
+ return unsatisfied('custody-unavailable', 'scoped', row.failure);
276
+ }
277
+ if (process.env[name] !== undefined) return undefined; // a real env var still satisfies the request
278
+ return unsatisfied('ungranted', 'scoped', `the secret exists but is not granted to component '${componentName}'`);
279
+ }
280
+ if (process.env[name] !== undefined) return undefined; // processEnv tier materialized, literal, or real env
281
+ if (row) return unsatisfied('custody-unavailable', 'global', row.failure);
282
+ return unsatisfied(
283
+ 'missing',
284
+ undefined,
285
+ storeAvailable ? 'not in the secrets store or process.env' : 'not in process.env (secrets store unavailable)'
286
+ );
287
+ }
288
+
289
+ /**
290
+ * Process a component's `env:` config block: evaluate declarations against the store snapshot +
291
+ * process.env, and inject string literals into process.env. Two passes so nothing is mutated
292
+ * before the whole block validates and the load-gate passes: an invalid shape or an unsatisfied
293
+ * `required: true` declaration throws (naming each variable and why) WITHOUT applying any of the
294
+ * block's literals. Optional unsatisfied declarations log a warning and are recorded for status
295
+ * exposure.
296
+ */
297
+ export function processComponentEnv(componentName: string, envConfig: unknown): void {
298
+ if (envConfig === null || typeof envConfig !== 'object' || Array.isArray(envConfig)) {
299
+ throw new Error(
300
+ `the 'env' config block must be a mapping of environment variable names to a string value or a declaration object`
301
+ );
302
+ }
303
+ const declared = new Set<string>();
304
+ const literals: [string, string][] = [];
305
+ const unsatisfied: UnsatisfiedDeclaration[] = [];
306
+ const requiredFailures: string[] = [];
307
+ for (const [name, spec] of Object.entries(envConfig)) {
308
+ if (typeof spec === 'string' || typeof spec === 'number' || typeof spec === 'boolean') {
309
+ literals.push([name, String(spec)]);
310
+ continue;
311
+ }
312
+ const declaration = parseDeclaration(componentName, name, spec);
313
+ declared.add(name);
314
+ const problem = evaluateDeclaration(componentName, name, declaration);
315
+ if (problem) {
316
+ unsatisfied.push(problem);
317
+ if (declaration.required) {
318
+ requiredFailures.push(`${name} (${problem.reason}${problem.detail ? ` — ${problem.detail}` : ''})`);
319
+ } else {
320
+ logger.warn(
321
+ `Optional environment variable '${name}' declared by component '${componentName}' is unsatisfied (${problem.reason}); omitting`
322
+ );
323
+ }
324
+ }
325
+ }
326
+ declaredEnvNames.set(componentName, declared);
327
+ unsatisfiedEnv.set(componentName, unsatisfied);
328
+ accessorCache.delete(componentName);
329
+ if (requiredFailures.length > 0) {
330
+ throw new Error(`unsatisfied required environment variables: ${requiredFailures.join('; ')}`);
331
+ }
332
+ for (const [name, value] of literals) applyEnvLiteral(componentName, name, value);
333
+ }
334
+
335
+ /** Declared-but-unsatisfied env expectations for a component — metadata only, for status surfaces. */
336
+ export function getUnsatisfiedEnv(componentName: string): UnsatisfiedDeclaration[] {
337
+ return unsatisfiedEnv.get(componentName) ?? [];
338
+ }
339
+
340
+ /**
341
+ * The secrets view for a component: scoped-tier rows granted to it, plus its DECLARED names
342
+ * resolved from process.env (global-tier materialized values, env literals, or real env vars).
343
+ * The superset lets app code use `secrets.FOO` uniformly, and lets ops later tighten a secret from
344
+ * global to granted without breaking the app. Values are decrypted eagerly at load; the object is
345
+ * frozen and enumerable (`Object.keys`, spread).
346
+ */
347
+ export function getSecretsForComponent(componentName: string): SecretsView {
348
+ let view = accessorCache.get(componentName);
349
+ if (view) return view;
350
+ // Null prototype so inherited Object.prototype members (toString, hasOwnProperty, constructor)
351
+ // can never masquerade as secret values under dynamic access like `secrets[key]`.
352
+ const entries: Record<string, string> = Object.create(null);
353
+ const declared = declaredEnvNames.get(componentName);
354
+ if (declared) {
355
+ for (const name of declared) {
356
+ const value = process.env[name];
357
+ if (value !== undefined) entries[name] = value;
358
+ }
359
+ }
360
+ // Scoped rows granted to this component; on a name collision the scoped value wins.
361
+ for (const [name, row] of secretRows) {
362
+ if (row.value !== undefined && !row.processEnv && row.grants.includes(componentName)) {
363
+ entries[name] = row.value;
364
+ }
365
+ }
366
+ view = Object.freeze(entries);
367
+ accessorCache.set(componentName, view);
368
+ return view;
369
+ }
370
+
371
+ // Component-load binding for the process-wide `secrets` export: the loader runs each component's
372
+ // module loading inside this context, so the singleton `harper` package (native loader, bundled
373
+ // code, natively-loaded deps) can still resolve which component is loading.
374
+ const componentBinding = new AsyncLocalStorage<string>();
375
+
376
+ /** Run `fn` with `secrets` bound to the named component (no-op when name is undefined). */
377
+ export function runWithComponentBinding<T>(componentName: string | undefined, fn: () => T): T {
378
+ return componentName === undefined ? fn() : componentBinding.run(componentName, fn);
379
+ }
380
+
381
+ function resolveBoundSecrets(): SecretsView {
382
+ const componentName = componentBinding.getStore();
383
+ if (componentName === undefined) {
384
+ throw new Error(
385
+ `The 'secrets' accessor was used outside of a component-loading context, so Harper cannot resolve which ` +
386
+ `component's secrets to expose (and will not guess — that could hand one component another component's ` +
387
+ `scoped secrets). Read secrets at module top level during component load ` +
388
+ `(e.g. \`const { MY_KEY } = secrets;\`) or use \`scope.secrets\` in an extension.`
389
+ );
390
+ }
391
+ return getSecretsForComponent(componentName);
392
+ }
393
+
394
+ function readOnly(): never {
395
+ throw new TypeError('secrets is read-only');
396
+ }
397
+
398
+ /**
399
+ * The process-wide `secrets` export (`import { secrets } from 'harper'` under the native loader /
400
+ * bundled code; under vm and compartment loaders the per-scope `harper` module carries an exactly
401
+ * bound view instead). Resolves the current component from the component-load context; access from
402
+ * an ambiguous context fails loudly. Symbols and `then` return undefined so awaiting/inspecting
403
+ * the object does not throw, and the enumeration traps (`has`/`ownKeys`/`getOwnPropertyDescriptor`)
404
+ * report an empty object outside a binding context so inspectors/serializers (`util.inspect`,
405
+ * spread) never crash the process — only direct property reads are loud.
406
+ */
407
+ export const secrets: SecretsView = new Proxy(
408
+ {},
409
+ {
410
+ get(_target, property) {
411
+ if (typeof property === 'symbol' || property === 'then') return undefined;
412
+ return resolveBoundSecrets()[property];
413
+ },
414
+ has(_target, property) {
415
+ if (typeof property === 'symbol') return false;
416
+ if (componentBinding.getStore() === undefined) return false;
417
+ return property in resolveBoundSecrets();
418
+ },
419
+ ownKeys() {
420
+ if (componentBinding.getStore() === undefined) return [];
421
+ return Reflect.ownKeys(resolveBoundSecrets());
422
+ },
423
+ getOwnPropertyDescriptor(_target, property) {
424
+ if (typeof property === 'symbol' || componentBinding.getStore() === undefined) return undefined;
425
+ const descriptor = Object.getOwnPropertyDescriptor(resolveBoundSecrets(), property);
426
+ // The views are frozen; report configurable so the proxy invariant against its (extensible,
427
+ // empty) target holds.
428
+ return descriptor && { ...descriptor, configurable: true };
429
+ },
430
+ set: readOnly,
431
+ defineProperty: readOnly,
432
+ deleteProperty: readOnly,
433
+ // Never allow the (shared, extensible) target to be made non-extensible — a successful
434
+ // Object.freeze/preventExtensions would pin the proxy's key-set invariants to the empty
435
+ // target and break enumeration for every later consumer.
436
+ preventExtensions: readOnly,
437
+ setPrototypeOf: readOnly,
438
+ }
439
+ );
440
+ _assignPackageExport('secrets', secrets);
441
+
442
+ /** Reset all module state and retract materialized env values. Intended for tests. */
443
+ export function resetComponentSecrets(): void {
444
+ for (const name of ownedEnvKeys) delete process.env[name];
445
+ ownedEnvKeys.clear();
446
+ secretRows = new Map();
447
+ storeAvailable = false;
448
+ declaredEnvNames.clear();
449
+ unsatisfiedEnv.clear();
450
+ accessorCache.clear();
451
+ }
@@ -15,6 +15,7 @@ import { Readable, Transform, pipeline } from 'node:stream';
15
15
  import { databases } from '../resources/databases.ts';
16
16
  import { createBlob, isSaving, deleteBlob } from '../resources/blob.ts';
17
17
  import * as terms from '../utility/hdbTerms.ts';
18
+ import type { RegistryAuthReference } from './secretOperations.ts';
18
19
  import { ClientError } from '../utility/errors/hdbError.ts';
19
20
  import { logger } from '../utility/logging/logger.ts';
20
21
  import { hostname } from 'node:os';
@@ -61,6 +62,10 @@ interface CreateOptions {
61
62
  user?: string;
62
63
  restart_mode?: 'immediate' | 'rolling' | null;
63
64
  rollback_of?: string | null;
65
+ // Registry-auth in reference form (`{ registry, secret, scope? }`) — never a literal token. Kept
66
+ // so a rollback can re-resolve the private-registry credential from hdb_secret without the
67
+ // operator re-supplying it. Null when the deploy used no auth or a no-custody transient token.
68
+ registry_auth?: RegistryAuthReference[] | null;
64
69
  emitter?: ProgressEmitter;
65
70
  }
66
71
 
@@ -98,6 +103,7 @@ export class DeploymentRecorder {
98
103
  completed_at: null,
99
104
  user: options.user ?? null,
100
105
  rollback_of: options.rollback_of ?? null,
106
+ registry_auth: options.registry_auth ?? null,
101
107
  error: null,
102
108
  };
103
109
  const recorder = new DeploymentRecorder(deploymentId, record);
@@ -446,7 +452,7 @@ export class DeploymentRecorder {
446
452
  // take well over the original 30s to arrive on a peer (harper-pro#402). 120s matches the
447
453
  // blob-stream receive default and gives a loaded cluster room to converge. Override per-deploy
448
454
  // via the `deployment_timeout` operation parameter.
449
- const DEFAULT_AWAIT_ROW_TIMEOUT_MS = 120_000;
455
+ export const DEFAULT_AWAIT_ROW_TIMEOUT_MS = 120_000;
450
456
 
451
457
  /**
452
458
  * Peer-side helper — wait for the hdb_deployment row to arrive via table replication,
@@ -21,6 +21,8 @@ interface FastifyLikeRequest {
21
21
  method: string;
22
22
  headers: Record<string, string | string[] | undefined>;
23
23
  body: unknown;
24
+ /** Client socket IP (Fastify's request.ip). */
25
+ ip?: string;
24
26
  /**
25
27
  * `authAndEnsureUserOnRequest` sets the full user (incl. role + permission
26
28
  * tree) on `req.hdb_user`. Used for session binding (`username`) and
@@ -53,6 +55,7 @@ export function createFastifyHandler(profile: McpProfile) {
53
55
  user: request.hdb_user?.username ?? '',
54
56
  userObject: (request.hdb_user ?? undefined) as NormRequest['userObject'],
55
57
  profile,
58
+ clientIp: request.ip,
56
59
  };
57
60
 
58
61
  const res = await handleMcpRequest(norm);
@@ -38,6 +38,8 @@ interface HarperHttpRequest {
38
38
  */
39
39
  user?: { username?: string; role?: unknown };
40
40
  isWebSocket?: boolean;
41
+ /** Client socket IP (Harper Request getter; present on Node and Bun). */
42
+ ip?: string;
41
43
  }
42
44
 
43
45
  interface HarperHttpResponse {
@@ -61,6 +63,7 @@ export function createHarperHttpHandler(profile: McpProfile) {
61
63
  user: request.user?.username ?? '',
62
64
  userObject: request.user as NormRequest['userObject'],
63
65
  profile,
66
+ clientIp: request.ip,
64
67
  };
65
68
 
66
69
  const res = await handleMcpRequest(norm);
@@ -19,12 +19,23 @@ export interface AuditEntry {
19
19
  tool: string;
20
20
  user: string;
21
21
  args: object;
22
- status: 'ok' | 'isError' | 'rate_limited' | 'protocol_error';
22
+ status: 'ok' | 'isError' | 'rate_limited' | 'quota_exceeded' | 'protocol_error';
23
23
  durationMs: number;
24
24
  errorMessage?: string;
25
25
  }
26
26
 
27
27
  const REDACTION_PATTERN = /(secret|password|token|api[-_]?key|credentials?|auth)/i;
28
+ // Exact field names that carry secret material without a credential-looking name: `value`/`values`
29
+ // (set_secret plaintext, set_env_value .env secrets) and `envelope` (set_secret ciphertext). These
30
+ // mirror the fields processLocalTransaction strips from the REST operations log — the MCP audit
31
+ // path must not become a bypass. Exact-match so e.g. `search_value` stays auditable.
32
+ const REDACTION_EXACT_FIELDS = /^(value|values|envelope)$/i;
33
+ // ...but only for the operations that actually put secrets in those generically-named fields.
34
+ // `value`/`values` are common, non-secret params on many other operations (record data, config
35
+ // values), so blanket-redacting them would gut the audit trail for unrelated tools. The global
36
+ // REDACTION_PATTERN still applies to every tool; this just narrows the exact-field masking to the
37
+ // secret-bearing ops. NOTE: extend this set if a new op ever carries a secret in a plain field.
38
+ const EXACT_FIELD_TOOLS = new Set(['set_secret', 'set_env_value']);
28
39
  const REDACTION_PLACEHOLDER = '[redacted]';
29
40
  const MAX_REDACTION_DEPTH = 10;
30
41
 
@@ -35,18 +46,18 @@ const MAX_REDACTION_DEPTH = 10;
35
46
  * a credential buried below the depth limit cannot leak. Returns a shallow
36
47
  * clone — the caller's payload is never mutated.
37
48
  */
38
- export function redactArgs(value: unknown, depth = 0): unknown {
49
+ export function redactArgs(value: unknown, depth = 0, redactExactFields = false): unknown {
39
50
  if (value === null || typeof value !== 'object') return value;
40
51
  if (depth > MAX_REDACTION_DEPTH) return REDACTION_PLACEHOLDER;
41
52
  if (Array.isArray(value)) {
42
- return value.map((v) => redactArgs(v, depth + 1));
53
+ return value.map((v) => redactArgs(v, depth + 1, redactExactFields));
43
54
  }
44
55
  const out: Record<string, unknown> = {};
45
56
  for (const [k, v] of Object.entries(value)) {
46
- if (REDACTION_PATTERN.test(k)) {
57
+ if (REDACTION_PATTERN.test(k) || (redactExactFields && REDACTION_EXACT_FIELDS.test(k))) {
47
58
  out[k] = REDACTION_PLACEHOLDER;
48
59
  } else {
49
- out[k] = redactArgs(v, depth + 1);
60
+ out[k] = redactArgs(v, depth + 1, redactExactFields);
50
61
  }
51
62
  }
52
63
  return out;
@@ -65,7 +76,11 @@ export function maskSessionId(id: string): string {
65
76
  */
66
77
  export function emitAuditEntry(entry: AuditEntry): void {
67
78
  try {
68
- const masked = { ...entry, sessionId: maskSessionId(entry.sessionId), args: redactArgs(entry.args) };
79
+ const masked = {
80
+ ...entry,
81
+ sessionId: maskSessionId(entry.sessionId),
82
+ args: redactArgs(entry.args, 0, EXACT_FIELD_TOOLS.has(entry.tool)),
83
+ };
69
84
  harperLogger.info({ category: 'mcp.audit', ...masked });
70
85
  } catch (err) {
71
86
  // Audit emission must never break a tool call. If logging itself fails