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
@@ -67,23 +67,23 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
67
67
  let lastRotationTime = Date.now();
68
68
  hdbLogger.trace('Log rotate enabled, maxSize:', maxSize, 'interval:', interval);
69
69
  const setIntervalId = setInterval(async () => {
70
+ // A missing/relocated active log file only invalidates the rotation checks below — retention cleanup
71
+ // must still run. So skip the individual check on ENOENT rather than returning from the whole tick.
70
72
  if (maxBytes) {
71
73
  let fileStats;
72
74
  try {
73
75
  fileStats = await fsProm.stat(logger.path);
74
76
  } catch (err) {
75
- // If the log file doesn't exist, skip rotation check
76
- if (err.code === 'ENOENT') return;
77
- throw err;
77
+ // If the log file doesn't exist, skip the size-based rotation check
78
+ if (err.code !== 'ENOENT') throw err;
78
79
  }
79
80
 
80
- if (fileStats.size >= maxBytes) {
81
+ if (fileStats && fileStats.size >= maxBytes) {
81
82
  try {
82
- lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir);
83
+ lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger);
83
84
  } catch (err) {
84
85
  // If the log file doesn't exist, skip rotation
85
- if (err.code === 'ENOENT') return;
86
- throw err;
86
+ if (err.code !== 'ENOENT') throw err;
87
87
  }
88
88
  }
89
89
  }
@@ -92,12 +92,11 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
92
92
  const minSinceLastRotate = Date.now() - lastRotationTime;
93
93
  if (minSinceLastRotate >= maxInterval) {
94
94
  try {
95
- lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir);
95
+ lastRotatedLogPath = await moveLogFile(logger.path, rotatedLogDir, logger);
96
96
  lastRotationTime = Date.now();
97
97
  } catch (err) {
98
98
  // If the log file doesn't exist, skip rotation
99
- if (err.code === 'ENOENT') return;
100
- throw err;
99
+ if (err.code !== 'ENOENT') throw err;
101
100
  }
102
101
  }
103
102
  }
@@ -106,7 +105,14 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
106
105
  // adjust retention time if there is a reclamation priority in place
107
106
  const retentionMs = convertToMS(retention ?? '1M') / (1 + reclamationPriority);
108
107
  reclamationPriority = 0; // reset it after use
109
- const files = await fsProm.readdir(rotatedLogDir);
108
+ let files;
109
+ try {
110
+ files = await fsProm.readdir(rotatedLogDir);
111
+ } catch (err) {
112
+ // The rotated log dir may not exist yet (nothing rotated so far); nothing to clean up
113
+ if (err.code !== 'ENOENT') hdbLogger.error('Error reading rotated log directory', rotatedLogDir, err);
114
+ files = [];
115
+ }
110
116
  for (const file of files) {
111
117
  try {
112
118
  const fileStats = await fsProm.stat(path.join(rotatedLogDir, file));
@@ -129,7 +135,7 @@ function logRotator({ logger, maxSize, interval, retention, enabled, path: rotat
129
135
  };
130
136
  }
131
137
 
132
- async function moveLogFile(logPath: string, rotatedLogPath: string) {
138
+ async function moveLogFile(logPath: string, rotatedLogPath: string, logger?: any) {
133
139
  const compress = envMgr.get(CONFIG_PARAMS.LOGGING_ROTATION_COMPRESS);
134
140
  let fullRotateLogPath = path.join(
135
141
  rotatedLogPath,
@@ -138,6 +144,12 @@ async function moveLogFile(logPath: string, rotatedLogPath: string) {
138
144
  // Move log file to rotated log path first (if we crash
139
145
  // during compression, we don't want to restart the compression with a new file)
140
146
  await fsProm.rename(logPath, fullRotateLogPath);
147
+ // Close the rotating logger's own file descriptor now that the file has moved. This must be the
148
+ // logger's own closeLogFile (which resets its internal logFD), not the module-global one — otherwise
149
+ // the descriptor stays open on the moved (and, when compressing, subsequently unlinked) inode until the
150
+ // logger's safety timeout fires, pinning disk space and sending any writes in that window into the
151
+ // rotated/deleted file. Closing it here makes the next write reopen a fresh log file immediately.
152
+ (logger?.closeLogFile ?? hdbLogger.closeLogFile)();
141
153
  if (compress) {
142
154
  logPath = fullRotateLogPath;
143
155
  fullRotateLogPath += '.gz';
@@ -145,8 +157,6 @@ async function moveLogFile(logPath: string, rotatedLogPath: string) {
145
157
  await fsProm.unlink(logPath);
146
158
  }
147
159
 
148
- // Close old log file.
149
- hdbLogger.closeLogFile();
150
160
  // This notify log will create a new log file after the previous one has been rotated. It's important to keep this log as notify
151
161
  hdbLogger.notify(`hdb.log rotated, old log moved to ${fullRotateLogPath}`);
152
162
  return fullRotateLogPath;
@@ -9,6 +9,7 @@ import { once } from 'events';
9
9
  import { getConfigPath } from '../../config/configUtils.ts';
10
10
  import { handleHDBError, hdbErrors } from '../errors/hdbError.ts';
11
11
  import { server } from '../../server/Server.ts';
12
+ import { StringDecoder } from 'string_decoder';
12
13
 
13
14
  const DEFAULT_READ_LOG_LIMIT = 1000;
14
15
  const ESTIMATED_AVERAGE_ENTRY_SIZE = 200;
@@ -33,8 +34,10 @@ async function readLog(request: any) {
33
34
  true
34
35
  );
35
36
  }
36
- // start pulling logs from the other nodes now so it can be done in parallel
37
- let whenReplicatedResponse = server.replication.replicateOperation(request);
37
+ // Start pulling logs from the other nodes now so it can be done in parallel. A live SSE
38
+ // tail (below) is local-only and never fans out, so skip replication on that path — the
39
+ // buffered read still aggregates the cluster for point-in-time queries.
40
+ let whenReplicatedResponse = isStreamingRequest(request) ? undefined : server.replication.replicateOperation(request);
38
41
 
39
42
  const logPath = getConfigPath(hdbTerms.HDB_SETTINGS_NAMES.LOG_PATH_KEY);
40
43
  const rawLogName = request.log_name === undefined ? hdbTerms.LOG_NAMES.HDB : request.log_name;
@@ -57,6 +60,26 @@ async function readLog(request: any) {
57
60
  const start = request.start === undefined ? 0 : request.start;
58
61
  const max = start + limit;
59
62
  const filter = request.filter;
63
+
64
+ // SSE mode: the server attached a ProgressEmitter as `request.progress` (see
65
+ // serverHandlers.js) because the client sent `Accept: text/event-stream`. Instead of a
66
+ // one-shot array, stream the recent backlog and then tail new lines live until the client
67
+ // disconnects. Scoped to this node's log file; the buffered path above is what aggregates
68
+ // the cluster for point-in-time reads.
69
+ if (isStreamingRequest(request)) {
70
+ return streamLogTail(request.progress, {
71
+ readLogPath,
72
+ level,
73
+ levelDefined,
74
+ from,
75
+ fromDefined,
76
+ to,
77
+ toDefined,
78
+ limit,
79
+ filter,
80
+ });
81
+ }
82
+
60
83
  let fileStart = 0;
61
84
  if (order === 'desc' && !from && !to) {
62
85
  fileStart = Math.max(fs.statSync(readLogPath).size - (max + 5) * ESTIMATED_AVERAGE_ENTRY_SIZE, 0);
@@ -348,3 +371,301 @@ function insertAscending(value: any, result: any[]) {
348
371
 
349
372
  result.splice(low, 0, value);
350
373
  }
374
+
375
+ interface LogEntry {
376
+ timestamp: string;
377
+ thread: string;
378
+ level: string;
379
+ tags: string[];
380
+ message: string;
381
+ }
382
+
383
+ interface TailFilterParams {
384
+ readLogPath: string;
385
+ level: string | undefined;
386
+ levelDefined: boolean;
387
+ from: Date | undefined;
388
+ fromDefined: boolean;
389
+ to: Date | undefined;
390
+ toDefined: boolean;
391
+ limit: number;
392
+ filter: string | undefined;
393
+ }
394
+
395
+ // The marker that begins every log line — `TIMESTAMP [thread] [level]...: ` — matching the
396
+ // buffered reader's regex so the two paths parse identically.
397
+ const LOG_ENTRY_MARKER = /(?:^|\n)(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:[\d.]+Z) \[(.+?)]: /g;
398
+ // How often a live tail polls the log file for appended bytes. fs.watchFile is stat-poll
399
+ // based, which is far more robust across platforms and log rotation than fs.watch's rename
400
+ // events; sub-second latency is plenty for a human watching logs.
401
+ const TAIL_POLL_INTERVAL_MS = 250;
402
+ // Cap the live-tail backlog regardless of a caller-supplied `limit`, so an unbounded `limit`
403
+ // can neither seek-read a huge slice into memory nor drive an O(n·limit) eviction on connect.
404
+ // Deeper history is the buffered read_log's job; the tail is about what happens next.
405
+ const MAX_SSE_BACKLOG_ENTRIES = 1000;
406
+ // A transient delta-read failure is retried this many times (with this delay) before the tail
407
+ // gives up and emits a terminal error so the client can fall back to polling.
408
+ const TAIL_READ_MAX_RETRIES = 3;
409
+ const TAIL_READ_RETRY_MS = 250;
410
+
411
+ function isStreamingRequest(request: any): boolean {
412
+ return !!request.progress && typeof request.progress.emit === 'function';
413
+ }
414
+
415
+ function makeLogEntry(timestamp: string, tagsString: string, message: string): LogEntry {
416
+ const tags = tagsString.split('] [');
417
+ const thread = tags[0];
418
+ const level = tags[1];
419
+ tags.splice(0, 2);
420
+ return { timestamp, thread, level, tags, message };
421
+ }
422
+
423
+ function matchesLogFilters(entry: LogEntry, params: TailFilterParams): boolean {
424
+ const { level, levelDefined, from, fromDefined, to, toDefined, filter } = params;
425
+ if (filter !== undefined) {
426
+ const hit = (['timestamp', 'thread', 'level', 'tags', 'message'] as const).some((attr) => {
427
+ const value = entry[attr];
428
+ if (Array.isArray(value)) return value.some((v) => v.includes(filter));
429
+ return typeof value === 'string' && value.includes(filter);
430
+ });
431
+ if (!hit) return false;
432
+ }
433
+ if (levelDefined && entry.level !== level) return false;
434
+ if (fromDefined || toDefined) {
435
+ const when = new Date(entry.timestamp);
436
+ if (fromDefined && when < (from as Date)) return false;
437
+ if (toDefined && when > (to as Date)) return false;
438
+ }
439
+ return true;
440
+ }
441
+
442
+ // Stateful incremental parser: fed appended chunks over time, it emits an entry only once the
443
+ // next marker delimits its (possibly multi-line) message. It never force-emits a pending
444
+ // entry — the same instance spans the backlog read and the live tail (see streamLogTail), so a
445
+ // line torn across the snapshot boundary stays pending and is completed by the tail's next
446
+ // read rather than emitted truncated with its continuation dropped. `reset()` clears state on
447
+ // log rotation.
448
+ function createIncrementalLogParser(onEntry: (entry: LogEntry) => void) {
449
+ let remaining = '';
450
+ let pending: { timestamp: string; tagsString: string } | undefined;
451
+ // One RegExp per parser instance. Each push runs `exec` to completion (until null), which
452
+ // resets lastIndex to 0; we also reset it explicitly at the top of push for robustness.
453
+ const reader = new RegExp(LOG_ENTRY_MARKER);
454
+ return {
455
+ push(text: string) {
456
+ const data = remaining + text;
457
+ reader.lastIndex = 0;
458
+ let lastPosition = 0;
459
+ let parsed;
460
+ while ((parsed = reader.exec(data))) {
461
+ if (pending) {
462
+ onEntry(makeLogEntry(pending.timestamp, pending.tagsString, data.slice(lastPosition, parsed.index)));
463
+ }
464
+ const [intro, timestamp, tagsString] = parsed;
465
+ pending = { timestamp, tagsString };
466
+ lastPosition = parsed.index + intro.length;
467
+ }
468
+ remaining = data.slice(lastPosition);
469
+ },
470
+ reset() {
471
+ pending = undefined;
472
+ remaining = '';
473
+ },
474
+ };
475
+ }
476
+
477
+ /**
478
+ * Stream the local log over SSE: replay the recent (bounded) backlog, then tail newly-appended
479
+ * lines until the client disconnects (the emitter's `signal` aborts) — or, if reads fail past
480
+ * retry, emit a terminal `error` so the client falls back to polling instead of watching a
481
+ * silently-stalled "live" stream. Resolves when the tail ends so the SSE wrapper can close.
482
+ *
483
+ * A single parser and decoder span both the backlog read and the live tail with no flush at
484
+ * the seam, so a line (or multi-byte character) torn across the snapshot boundary stays pending
485
+ * and is completed by the tail's next read — never emitted truncated with its continuation
486
+ * dropped. (A consequence: the newest backlog line is delivered once the following line
487
+ * delimits it, matching how the tail delimits every entry.)
488
+ */
489
+ function streamLogTail(progress: any, params: TailFilterParams): Promise<void> {
490
+ const { readLogPath, limit } = params;
491
+ const signal: AbortSignal | undefined = progress.signal;
492
+ const emit = (entry: LogEntry) => progress.emit('log', entry);
493
+ // The tail never shows more backlog than this, whatever `limit` asks for (see the constant).
494
+ const backlogLimit = Math.min(limit, MAX_SSE_BACKLOG_ENTRIES);
495
+
496
+ return new Promise<void>((resolve) => {
497
+ if (signal?.aborted) return resolve();
498
+
499
+ let offset = 0;
500
+ let pumping = false;
501
+ let pumpQueued = false;
502
+ let watching = false;
503
+ let finished = false;
504
+ let readFailures = 0;
505
+ // While replaying the backlog, matching entries are collected here (capped) rather than
506
+ // emitted; once the boundary is passed `backlogPhase` flips and entries emit live.
507
+ let backlogPhase = true;
508
+ const backlogEntries: LogEntry[] = [];
509
+ // One decoder + one parser for the whole session (backlog + tail): a multi-byte char (or a
510
+ // line) split across the boundary is held/pending and finished by the next read, not
511
+ // flushed as U+FFFD or truncated. Reset together only on rotation.
512
+ let decoder = new StringDecoder('utf8');
513
+ const parser = createIncrementalLogParser((entry) => {
514
+ if (!matchesLogFilters(entry, params)) return;
515
+ if (backlogPhase) {
516
+ backlogEntries.push(entry);
517
+ if (backlogEntries.length > backlogLimit) backlogEntries.shift();
518
+ } else {
519
+ emit(entry);
520
+ }
521
+ });
522
+
523
+ function finish() {
524
+ if (finished) return;
525
+ finished = true;
526
+ if (watching) fs.unwatchFile(readLogPath, onChange);
527
+ resolve();
528
+ }
529
+
530
+ // Wait `ms`, or resolve early on disconnect so a retry delay never outlives the connection.
531
+ const abortableDelay = (ms: number) =>
532
+ new Promise<void>((res) => {
533
+ if (signal?.aborted) return res();
534
+ const timer = setTimeout(() => {
535
+ signal?.removeEventListener('abort', onAbort);
536
+ res();
537
+ }, ms);
538
+ timer.unref?.();
539
+ const onAbort = () => {
540
+ clearTimeout(timer);
541
+ res();
542
+ };
543
+ signal?.addEventListener('abort', onAbort, { once: true });
544
+ });
545
+
546
+ // Stream [offset, end] inclusive through the session decoder + shared parser, decoding and
547
+ // pushing each fs chunk as it arrives (never materializing the whole delta) and advancing
548
+ // `offset` per chunk, so peak memory is one chunk and a mid-range error simply resumes from
549
+ // where it stopped. Yields to backpressure between chunks. Returns false if the read errored.
550
+ async function drainTo(end: number): Promise<boolean> {
551
+ const rs = fs.createReadStream(readLogPath, { start: offset, end });
552
+ try {
553
+ for await (const chunk of rs) {
554
+ if (signal?.aborted) {
555
+ rs.destroy();
556
+ return true;
557
+ }
558
+ parser.push(decoder.write(chunk as Buffer));
559
+ offset += (chunk as Buffer).length;
560
+ if (progress.paused) await progress.whenWritable();
561
+ }
562
+ return true;
563
+ } catch (err: any) {
564
+ hdbLogger.warn(
565
+ `read_log SSE tail: failed reading ${readLogPath} near offset ${offset}: ${err?.message ?? err}`
566
+ );
567
+ return false;
568
+ }
569
+ }
570
+
571
+ // Single-flight pump: drain all appended bytes into the parser. A change arriving while a
572
+ // pump runs sets `pumpQueued` so the loop runs once more, rather than being missed in the
573
+ // window between "caught up" and clearing `pumping`.
574
+ async function pump() {
575
+ if (pumping) {
576
+ pumpQueued = true;
577
+ return;
578
+ }
579
+ pumping = true;
580
+ try {
581
+ do {
582
+ pumpQueued = false;
583
+ while (!signal?.aborted) {
584
+ let size: number;
585
+ try {
586
+ size = (await fs.promises.stat(readLogPath)).size;
587
+ } catch {
588
+ break;
589
+ }
590
+ if (size < offset) {
591
+ // Truncation or rotation: restart from the new file and reset decode/parse state.
592
+ offset = 0;
593
+ decoder = new StringDecoder('utf8');
594
+ parser.reset();
595
+ }
596
+ if (size <= offset) break;
597
+ if (progress.paused) {
598
+ await progress.whenWritable();
599
+ continue;
600
+ }
601
+ const ok = await drainTo(size - 1);
602
+ if (signal?.aborted) break;
603
+ if (!ok) {
604
+ if (++readFailures > TAIL_READ_MAX_RETRIES) {
605
+ // Don't leave the client staring at a silently-stalled "live" stream: a
606
+ // terminal error is its cue to fall back to polling.
607
+ progress.emit('error', {
608
+ message: `read_log live tail could not read ${readLogPath} after ${TAIL_READ_MAX_RETRIES} retries`,
609
+ code: 'READ_LOG_TAIL_READ_ERROR',
610
+ });
611
+ finish();
612
+ return;
613
+ }
614
+ await abortableDelay(TAIL_READ_RETRY_MS);
615
+ continue; // resume from the advanced offset
616
+ }
617
+ readFailures = 0;
618
+ }
619
+ } while (pumpQueued && !signal?.aborted);
620
+ } finally {
621
+ pumping = false;
622
+ }
623
+ }
624
+
625
+ function onChange() {
626
+ void pump();
627
+ }
628
+
629
+ const startTail = () => {
630
+ // `offset` is already the backlog boundary, so the tail picks up strictly from there.
631
+ // Without a disconnect signal we can't safely tail forever; degrade to backlog-only so
632
+ // the client polls instead.
633
+ if (!signal || signal.aborted) return finish();
634
+ fs.watchFile(readLogPath, { interval: TAIL_POLL_INTERVAL_MS }, onChange);
635
+ watching = true;
636
+ signal.addEventListener('abort', finish, { once: true });
637
+ // Catch anything appended between the backlog snapshot and arming the watcher.
638
+ void pump();
639
+ };
640
+
641
+ // Replay the backlog through the shared parser/decoder, then tail from the same boundary.
642
+ (async () => {
643
+ let startSize = 0;
644
+ try {
645
+ startSize = fs.statSync(readLogPath).size;
646
+ } catch {
647
+ startSize = 0;
648
+ }
649
+ if (startSize > 0) {
650
+ // Bound the backlog read to roughly the last `backlogLimit` entries so a large log
651
+ // file can't be seek-read into memory all at once (mirrors the buffered path). A
652
+ // partial first line the seek lands in is dropped by the parser, as in that path.
653
+ offset = Math.max(startSize - (backlogLimit + 5) * ESTIMATED_AVERAGE_ENTRY_SIZE, 0);
654
+ await drainTo(startSize - 1); // collects into backlogEntries; read errors are logged, best-effort
655
+ }
656
+ // Hand off to the live tail: flip to emit mode and deliver the collected backlog. The
657
+ // parser's pending entry (the newest, possibly boundary-torn line) is deliberately left
658
+ // pending — the tail finishes it — so nothing is emitted truncated.
659
+ backlogPhase = false;
660
+ for (const entry of backlogEntries) {
661
+ if (signal?.aborted) break;
662
+ emit(entry);
663
+ if (progress.paused) await progress.whenWritable();
664
+ }
665
+ startTail();
666
+ })().catch((err) => {
667
+ hdbLogger.warn(`read_log SSE tail: backlog replay failed for ${readLogPath}: ${err?.message ?? err}`);
668
+ startTail();
669
+ });
670
+ });
671
+ }
@@ -76,10 +76,44 @@ export const OPERATION_PERMISSION_GROUPS = {
76
76
  OPERATIONS_ENUM.CSV_URL_LOAD,
77
77
  OPERATIONS_ENUM.IMPORT_FROM_S3,
78
78
  ],
79
+ /**
80
+ * Drive the built-in agent (#626): send prompts, read/list sessions, and approve/cancel
81
+ * runs. Grants a scoped, non-super_user "delegation" role access to the agent without full
82
+ * super_user (all agent ops are super_user by default). Deliberately EXCLUDES set_agent_config
83
+ * — changing the agent's model/approval policy stays an operator (super_user) action.
84
+ */
85
+ agent: [
86
+ OPERATIONS_ENUM.AGENT_PROMPT,
87
+ OPERATIONS_ENUM.GET_AGENT_SESSION,
88
+ OPERATIONS_ENUM.LIST_AGENT_SESSIONS,
89
+ OPERATIONS_ENUM.CANCEL_AGENT_RUN,
90
+ OPERATIONS_ENUM.APPROVE_AGENT_ACTION,
91
+ ],
79
92
  } as const;
80
93
 
81
94
  const validOps: Set<string> = new Set(Object.values(OPERATIONS_ENUM));
82
95
  const validGroups: Set<string> = new Set(Object.keys(OPERATION_PERMISSION_GROUPS));
96
+ // Operations registered dynamically at runtime (server.registerOperation with a declared
97
+ // permission) whose names may fall outside OPERATIONS_ENUM. Tracked so they're grantable in a
98
+ // role's `operations` allowlist (add_role/alter_role/impersonation validate against this set too).
99
+ const dynamicallyRegisteredOps: Set<string> = new Set();
100
+
101
+ /**
102
+ * Mark a dynamically-registered operation name as a valid target for role `operations` grants.
103
+ * Called by registerOperationPermission so a component op declaring a permission can actually be
104
+ * granted (not just enforced). Enum-backed op names are already valid; this covers custom names.
105
+ */
106
+ export function registerGrantableOperation(name: string): void {
107
+ dynamicallyRegisteredOps.add(name);
108
+ }
109
+
110
+ /**
111
+ * Remove a dynamically-registered grantable operation name. Mirrors registerGrantableOperation;
112
+ * primarily for tests that register throwaway ops and must not leak them into the global set.
113
+ */
114
+ export function unregisterGrantableOperation(name: string): void {
115
+ dynamicallyRegisteredOps.delete(name);
116
+ }
83
117
 
84
118
  /**
85
119
  * Validates that every entry in an operations array is a known operation name or group name.
@@ -87,7 +121,7 @@ const validGroups: Set<string> = new Set(Object.keys(OPERATION_PERMISSION_GROUPS
87
121
  */
88
122
  export function validateOperations(operations: readonly unknown[]): string | null {
89
123
  for (const op of operations) {
90
- if (typeof op !== 'string' || (!validOps.has(op) && !validGroups.has(op))) {
124
+ if (typeof op !== 'string' || (!validOps.has(op) && !validGroups.has(op) && !dynamicallyRegisteredOps.has(op))) {
91
125
  return String(op);
92
126
  }
93
127
  }
@@ -25,7 +25,11 @@ import readLog from '../utility/logging/readLog.ts';
25
25
  import * as commonUtils from './common_utils.ts';
26
26
  import * as restart from '../bin/restart.ts';
27
27
  import * as terms from './hdbTerms.ts';
28
- import { expandOperationsPerms } from './operationPermissions.ts';
28
+ import {
29
+ expandOperationsPerms,
30
+ registerGrantableOperation,
31
+ unregisterGrantableOperation,
32
+ } from './operationPermissions.ts';
29
33
  import * as permsTranslator from '../security/permissionsTranslator.js';
30
34
  import { systemInformation } from '../utility/environment/systemInformation.ts';
31
35
  import * as tokenAuthentication from '../security/tokenAuthentication.ts';
@@ -41,6 +45,7 @@ import { handleHDBError, hdbErrors } from '../utility/errors/hdbError.ts';
41
45
 
42
46
  import * as regDeprecated from '../resources/registrationDeprecated.ts';
43
47
  import * as deploymentOperations from '../components/deploymentOperations.ts';
48
+ import * as secretOperations from '../components/secretOperations.ts';
44
49
 
45
50
  const requiredPermissions = new Map();
46
51
  const DELETE_PERM = 'delete';
@@ -108,6 +113,33 @@ class permission {
108
113
  }
109
114
  }
110
115
 
116
+ /**
117
+ * Register an authorization entry for a dynamically-registered operation (via
118
+ * server.registerOperation), so it flows through verifyPerms and the role `operations`
119
+ * allowlist exactly like a built-in op — instead of hand-rolling an inline super_user check.
120
+ * Keyed by the snake_case API operation name (which registerOperation also forces onto the
121
+ * handler's `.name`, the key verifyPerms looks up). SU-only by default.
122
+ */
123
+ export function registerOperationPermission(
124
+ apiName: string,
125
+ { requiresSu = false, perms = [] }: { requiresSu?: boolean; perms?: string[] } = {}
126
+ ) {
127
+ requiredPermissions.set(apiName, new (permission as any)(requiresSu, perms, apiName));
128
+ // Also make the op grantable in a role's `operations` allowlist (validateOperations), so a
129
+ // declared permission can be both enforced AND granted — not just enforced.
130
+ registerGrantableOperation(apiName);
131
+ }
132
+
133
+ /**
134
+ * Remove an authorization entry registered via registerOperationPermission (both the verifyPerms
135
+ * entry and the grantable-op mark). Mirrors registerOperationPermission; primarily for tests that
136
+ * register throwaway ops and must not leak them into the process-global registries.
137
+ */
138
+ export function unregisterOperationPermission(apiName: string) {
139
+ requiredPermissions.delete(apiName);
140
+ unregisterGrantableOperation(apiName);
141
+ }
142
+
111
143
  requiredPermissions.set(write.insert.name, new (permission as any)(false, [INSERT_PERM], terms.OPERATIONS_ENUM.INSERT));
112
144
  requiredPermissions.set(write.update.name, new (permission as any)(false, [UPDATE_PERM], terms.OPERATIONS_ENUM.UPDATE));
113
145
  requiredPermissions.set(
@@ -231,6 +263,18 @@ requiredPermissions.set(
231
263
  new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_COMPONENT_FILE)
232
264
  );
233
265
  requiredPermissions.set(functionsOperations.setComponentFile.name, new (permission as any)(true, []));
266
+ requiredPermissions.set(
267
+ functionsOperations.getEnvKeys.name,
268
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_ENV_KEYS)
269
+ );
270
+ requiredPermissions.set(
271
+ functionsOperations.setEnvValue.name,
272
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.SET_ENV_VALUE)
273
+ );
274
+ requiredPermissions.set(
275
+ functionsOperations.deleteEnvValue.name,
276
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.DELETE_ENV_VALUE)
277
+ );
234
278
  requiredPermissions.set(functionsOperations.dropComponent.name, new (permission as any)(true, []));
235
279
  requiredPermissions.set(
236
280
  functionsOperations.getCustomFunction.name,
@@ -251,6 +295,33 @@ requiredPermissions.set(
251
295
  new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_DEPLOYMENT)
252
296
  );
253
297
 
298
+ // Secrets-store operations. All SU-only; the handlers ALSO enforce super_user directly, so these
299
+ // cannot be delegated through a role's `operations` allowlist (gate-2 bypass below).
300
+ requiredPermissions.set(
301
+ secretOperations.setSecret.name,
302
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.SET_SECRET)
303
+ );
304
+ requiredPermissions.set(
305
+ secretOperations.grantSecret.name,
306
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.GRANT_SECRET)
307
+ );
308
+ requiredPermissions.set(
309
+ secretOperations.revokeSecret.name,
310
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.REVOKE_SECRET)
311
+ );
312
+ requiredPermissions.set(
313
+ secretOperations.listSecrets.name,
314
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.LIST_SECRETS)
315
+ );
316
+ requiredPermissions.set(
317
+ secretOperations.deleteSecret.name,
318
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.DELETE_SECRET)
319
+ );
320
+ requiredPermissions.set(
321
+ secretOperations.getSecretsPublicKey.name,
322
+ new (permission as any)(true, [], terms.OPERATIONS_ENUM.GET_SECRETS_PUBLIC_KEY)
323
+ );
324
+
254
325
  //Below are functions that are currently open to all roles
255
326
  requiredPermissions.set(regDeprecated.getRegistrationInfo.name, new (permission as any)(false, []));
256
327
  requiredPermissions.set(user.userInfo.name, new (permission as any)(false, [], terms.OPERATIONS_ENUM.USER_INFO));
@@ -299,6 +370,8 @@ module.exports = {
299
370
  verifyPerms,
300
371
  verifyPermsAST,
301
372
  verifyBulkLoadAttributePerms,
373
+ registerOperationPermission,
374
+ unregisterOperationPermission,
302
375
  };
303
376
 
304
377
  /**