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
@@ -11,6 +11,7 @@ const harperBridge =
11
11
  require('../../dataLayer/harperBridge/harperBridge.ts').default ||
12
12
  require('../../dataLayer/harperBridge/harperBridge.ts');
13
13
  const process = require('process');
14
+ const { isMainThread, workerData } = require('worker_threads');
14
15
  const { resetDatabases } = require('../../resources/databases.ts');
15
16
 
16
17
  /**
@@ -22,6 +23,13 @@ const serverItcHandlers = {
22
23
  [hdbTerms.ITC_EVENT_TYPES.USER]: userHandler,
23
24
  [hdbTerms.ITC_EVENT_TYPES.COMPONENT_STATUS_REQUEST]: componentStatusRequestHandler,
24
25
  [hdbTerms.ITC_EVENT_TYPES.RESOURCE_OPENAPI_REQUEST]: resourceOpenApiRequestHandler,
26
+ [hdbTerms.ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_REQUEST]: middlewareChainsRequestHandler,
27
+ // #1736 cross-thread registered-operation bridge. Lazy require: this module loads on every
28
+ // thread via itc.js, and registeredOperations pulls in the serverUtilities module graph.
29
+ [hdbTerms.ITC_EVENT_TYPES.OPERATION_REGISTERED]: (event) =>
30
+ require('../serverHelpers/registeredOperations.ts').operationRegisteredHandler(event),
31
+ [hdbTerms.ITC_EVENT_TYPES.OPERATION_EXECUTE_REQUEST]: (event) =>
32
+ require('../serverHelpers/registeredOperations.ts').operationExecuteRequestHandler(event),
25
33
  };
26
34
 
27
35
  /**
@@ -105,6 +113,31 @@ userHandler.addListener = function (listener) {
105
113
  userListeners.push(listener);
106
114
  };
107
115
 
116
+ const resourceListeners = [];
117
+ /**
118
+ * Local-only fan-out for "JS resources just registered" (resources.js loaded via the jsResource
119
+ * plugin). Unlike schema/user changes this is NOT an ITC event: each worker loads and registers
120
+ * its own JS resources, so a listener (e.g. the MCP application-tool rebuild) only needs to run in
121
+ * the worker where the registration happened. Surfaces author opt-ins (`static mcpTools`/
122
+ * `mcpPrompts`) that land on the resource registry after the MCP component's boot scan (#1448).
123
+ */
124
+ function resourceHandler() {
125
+ for (const listener of resourceListeners) {
126
+ try {
127
+ listener();
128
+ } catch (err) {
129
+ hdbLogger.error(err);
130
+ }
131
+ }
132
+ }
133
+ resourceHandler.addListener = function (listener) {
134
+ resourceListeners.push(listener);
135
+ };
136
+ // Test seam: drop registered listeners so a unit suite doesn't leak fakes into later suites.
137
+ resourceHandler._resetListenersForTest = function () {
138
+ resourceListeners.length = 0;
139
+ };
140
+
108
141
  /**
109
142
  * Handles incoming requests for component status from inter-thread communication (ITC).
110
143
  * Validates the event, retrieves the current thread's component statuses, and sends a response
@@ -211,8 +244,45 @@ async function resourceOpenApiRequestHandler(event) {
211
244
  }
212
245
  }
213
246
 
247
+ /**
248
+ * Handles the main thread's request for the resolved HTTP/upgrade/WebSocket middleware chains (#1573).
249
+ * Only HTTP workers reply: the main thread carries just the operations-API middleware (so it stays
250
+ * silent even though it has responders), and job workers have none. HTTP workers answer even when
251
+ * they have no middleware (empty chains) so get_status doesn't wait out the request timeout. First
252
+ * worker to answer wins — all HTTP workers register identically, so any one is representative.
253
+ */
254
+ async function middlewareChainsRequestHandler(event) {
255
+ try {
256
+ const validate = validateEvent(event);
257
+ if (validate) {
258
+ hdbLogger.error(validate);
259
+ return;
260
+ }
261
+
262
+ if (isMainThread || workerData?.name !== hdbTerms.THREAD_TYPES.HTTP) return;
263
+ const { describeMiddlewareChains } = require('../http.ts');
264
+
265
+ const responseMessage = {
266
+ type: hdbTerms.ITC_EVENT_TYPES.MIDDLEWARE_CHAINS_RESPONSE,
267
+ message: {
268
+ requestId: event.message.requestId,
269
+ chains: describeMiddlewareChains(),
270
+ },
271
+ };
272
+
273
+ if (!threads.sendToThread(event.message.originator, responseMessage)) {
274
+ hdbLogger.trace(
275
+ `Dropping middleware chains response for request ${event.message.requestId}: originator thread ${event.message.originator} is unreachable`
276
+ );
277
+ }
278
+ } catch (error) {
279
+ hdbLogger.error('Error handling middleware chains request:', error);
280
+ }
281
+ }
282
+
214
283
  module.exports = serverItcHandlers;
215
284
  // Named exports so consumers (e.g., MCP listChanged) can subscribe via
216
285
  // `userHandler.addListener(fn)` / `schemaHandler.addListener(fn)`.
217
286
  module.exports.userHandler = userHandler;
218
287
  module.exports.schemaHandler = schemaHandler;
288
+ module.exports.resourceHandler = resourceHandler;
@@ -0,0 +1,152 @@
1
+ import hdbLogger from '../utility/logging/harper_logger.ts';
2
+
3
+ /**
4
+ * Continuous re-authorization for live subscriptions (#1414).
5
+ *
6
+ * Subscribe-time authorization is a point-in-time check: once a stream (SSE / WebSocket / MQTT)
7
+ * is open, it keeps delivering even if the principal later loses access (drop_user, role/permission
8
+ * change) or its bearer token expires. This registry re-evaluates each live subscription's
9
+ * authorization — at the TABLE/RBAC level, matching how the subscription was granted; there is no
10
+ * per-record evaluation — and terminates any that no longer authorize.
11
+ *
12
+ * Triggers: (1) immediately on the ITC user-change broadcast (the same signal that rebuilds the
13
+ * user/role cache), and (2) on a fixed interval as a backstop and to catch token expiry, which is
14
+ * not event-signaled.
15
+ */
16
+
17
+ // Backstop interval; also catches token expiry, which is not event-signaled. Overridable for tests.
18
+ const RECHECK_INTERVAL_MS = Number(process.env.HARPER_SUBSCRIPTION_REAUTH_INTERVAL_MS) || 30_000;
19
+
20
+ interface LiveSubscription {
21
+ username: string;
22
+ /** JWT `exp` (seconds since epoch) of the credential the subscription was opened with, if any. */
23
+ authExpiresAt?: number;
24
+ /** Returns true if the principal is still authorized for this subscription. */
25
+ recheck: () => Promise<boolean>;
26
+ /** Stop delivery and tear down the subscription. */
27
+ terminate: () => void;
28
+ }
29
+
30
+ const registry = new Set<LiveSubscription>();
31
+ let sweepTimer: any = null;
32
+ let itcListenerInstalled = false;
33
+ let sweeping = false;
34
+
35
+ function ensureStarted(): void {
36
+ if (!sweepTimer) {
37
+ sweepTimer = setInterval(() => void sweep(), RECHECK_INTERVAL_MS);
38
+ // don't keep the worker alive solely for the recheck timer
39
+ sweepTimer.unref?.();
40
+ }
41
+ if (!itcListenerInstalled) {
42
+ try {
43
+ // Fire an immediate sweep when a user/role mutation propagates. serverHandlers rebuilds the
44
+ // user/role cache before invoking listeners, so recheck() observes the new permissions.
45
+ const handlers = require('./itc/serverHandlers.js');
46
+ if (handlers?.userHandler?.addListener) {
47
+ handlers.userHandler.addListener(() => void sweep());
48
+ itcListenerInstalled = true;
49
+ }
50
+ } catch (error) {
51
+ hdbLogger.trace?.(`liveSubscriptionAuth: ITC userHandler unavailable: ${(error as Error).message}`);
52
+ }
53
+ }
54
+ }
55
+
56
+ function stopIfIdle(): void {
57
+ if (registry.size === 0 && sweepTimer) {
58
+ clearInterval(sweepTimer);
59
+ sweepTimer = null;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Register a live subscription for continuous re-authorization. The returned subscription's normal
65
+ * teardown (end()/close) automatically unregisters it, so callers don't need to.
66
+ */
67
+ export function registerLiveSubscription(opts: {
68
+ subscription: any;
69
+ username: string;
70
+ authExpiresAt?: number;
71
+ recheck: () => Promise<boolean>;
72
+ }): void {
73
+ const { subscription, username, authExpiresAt, recheck } = opts;
74
+ if (!subscription || typeof subscription !== 'object') return;
75
+
76
+ const entry: LiveSubscription = {
77
+ username,
78
+ authExpiresAt,
79
+ recheck,
80
+ terminate: () => {
81
+ // emit('close') runs the subscription's own teardown wiring; end() removes it from the
82
+ // broadcast notify loop so no further events are delivered. Both are idempotent.
83
+ try {
84
+ subscription.emit?.('close');
85
+ } catch {
86
+ /* ignore */
87
+ }
88
+ try {
89
+ subscription.end?.();
90
+ } catch {
91
+ /* ignore */
92
+ }
93
+ },
94
+ };
95
+ registry.add(entry);
96
+
97
+ const unregister = () => {
98
+ registry.delete(entry);
99
+ stopIfIdle();
100
+ };
101
+ // Both transports ultimately call end() on normal teardown (MQTT unsubscribe/disconnect; SSE close
102
+ // is wired to end()); wrap it so a closed stream never leaks a registry entry. Also listen for
103
+ // 'close' to cover any iterable that closes without an end().
104
+ const originalEnd = typeof subscription.end === 'function' ? subscription.end.bind(subscription) : null;
105
+ if (originalEnd) {
106
+ subscription.end = function (...args: any[]) {
107
+ unregister();
108
+ return originalEnd(...args);
109
+ };
110
+ }
111
+ subscription.on?.('close', unregister);
112
+
113
+ ensureStarted();
114
+ }
115
+
116
+ async function sweep(): Promise<void> {
117
+ if (sweeping) return; // a slow recheck must not overlap with the next tick/event
118
+ sweeping = true;
119
+ try {
120
+ const now = Date.now();
121
+ for (const entry of registry) {
122
+ try {
123
+ const expired = entry.authExpiresAt != null && now >= entry.authExpiresAt * 1000;
124
+ const stillAuthorized = expired ? false : await entry.recheck();
125
+ const revoke = expired || !stillAuthorized;
126
+ if (revoke) {
127
+ registry.delete(entry);
128
+ entry.terminate();
129
+ }
130
+ } catch (error) {
131
+ // fail closed: if authorization can't be confirmed, revoke
132
+ registry.delete(entry);
133
+ try {
134
+ entry.terminate();
135
+ } catch {
136
+ /* ignore */
137
+ }
138
+ hdbLogger.warn?.(
139
+ `liveSubscriptionAuth: revoked subscription for ${entry.username} after recheck error: ${(error as Error).message}`
140
+ );
141
+ }
142
+ }
143
+ } finally {
144
+ sweeping = false;
145
+ stopIfIdle();
146
+ }
147
+ }
148
+
149
+ /** Test-only: current number of tracked subscriptions. */
150
+ export function _liveSubscriptionCount(): number {
151
+ return registry.size;
152
+ }
@@ -6,6 +6,7 @@ const configUtils = require('../config/configUtils.ts');
6
6
  const { dirname } = require('path');
7
7
  const { loadCertificates } = require('../security/keys.ts');
8
8
  const { installApplications } = require('../components/Application.ts');
9
+ const { errorForLog } = require('../utility/logging/harper_logger.ts');
9
10
 
10
11
  let loadedComponents = new Map();
11
12
  /**
@@ -16,7 +17,7 @@ async function loadRootComponents(isWorkerThread = false) {
16
17
  try {
17
18
  if (isMainThread && !process.env.HARPER_SAFE_MODE) await installApplications();
18
19
  } catch (error) {
19
- console.error(error);
20
+ console.error(errorForLog(error));
20
21
  }
21
22
 
22
23
  let resources = resetResources();
@@ -8,6 +8,18 @@ export type HttpEntry = {
8
8
  host?: string;
9
9
  };
10
10
 
11
+ /**
12
+ * A single route's resolved middleware order. `order` is the topologically sorted
13
+ * list of entries the chain will invoke, outermost first. `host`/`urlPath` are the
14
+ * route scope (both undefined for the default route). This is the observable form of
15
+ * what `buildRoutedChain`/`buildLinearChain` actually execute — see `describeChains`.
16
+ */
17
+ export type ResolvedChain = {
18
+ host?: string;
19
+ urlPath?: string;
20
+ order: HttpEntry[];
21
+ };
22
+
11
23
  /**
12
24
  * Topological sort of middleware entries respecting `before`/`after` constraints.
13
25
  * Uses the original registration index as a tiebreaker so config order is preserved
@@ -92,7 +104,7 @@ export function topoSort(entries: HttpEntry[], onCycle?: () => void): HttpEntry[
92
104
  */
93
105
  export function buildLinearChain(sorted: HttpEntry[], fallback: Function): Function {
94
106
  let next = fallback;
95
- for (let i = sorted.length; i > 0; ) {
107
+ for (let i = sorted.length; i > 0;) {
96
108
  const { listener } = sorted[--i];
97
109
  const callback = next;
98
110
  next = (...args: any[]) => listener(...args, callback);
@@ -208,12 +220,16 @@ export function stripPrefix(request: any, prefix: string): any {
208
220
  * The matched (and prefix-stripped) request is substituted back into the same
209
221
  * position before forwarding to the inner chain.
210
222
  */
211
- export function buildRoutedChain(
212
- portEntries: HttpEntry[],
213
- fallback: Function,
214
- onCycle?: () => void,
215
- requestArgIndex: number = 0
216
- ): Function {
223
+ /**
224
+ * Resolves the per-route middleware order for a port that has sub-routes.
225
+ * Returns each sub-route in dispatch priority order (host+path > host-only > path-only;
226
+ * longer paths win ties), followed by the default route last. Each route's `order` is the
227
+ * `after`-dependency-resolved, topologically sorted entry list the chain will invoke.
228
+ *
229
+ * This is the single source of ordering truth: `buildRoutedChain` builds callbacks from it,
230
+ * and `describeChains` reports it, so the observed order can never drift from the served one.
231
+ */
232
+ export function resolveRoutedChains(portEntries: HttpEntry[], onCycle?: () => void): ResolvedChain[] {
217
233
  // Global name registry across all routes (first registration wins)
218
234
  const nameToEntry = new Map<string, HttpEntry>();
219
235
  for (const entry of portEntries) {
@@ -233,19 +249,36 @@ export function buildRoutedChain(
233
249
  const defaultGroup = routeGroups.find((g) => !g.host && !g.urlPath);
234
250
  const subRouteGroups = routeGroups.filter((g) => g.host || g.urlPath);
235
251
 
236
- const subRouteChains = subRouteGroups.map((group) => {
237
- const resolved = resolveDeps(group.entries, nameToEntry);
238
- return { host: group.host, urlPath: group.urlPath, chain: buildLinearChain(topoSort(resolved, onCycle), fallback) };
239
- });
252
+ const subRoutes: ResolvedChain[] = subRouteGroups.map((group) => ({
253
+ host: group.host,
254
+ urlPath: group.urlPath,
255
+ order: topoSort(resolveDeps(group.entries, nameToEntry), onCycle),
256
+ }));
240
257
 
241
- subRouteChains.sort((a, b) => {
258
+ subRoutes.sort((a, b) => {
242
259
  const aSpec = (a.host ? 2 : 0) + (a.urlPath ? 1 : 0);
243
260
  const bSpec = (b.host ? 2 : 0) + (b.urlPath ? 1 : 0);
244
261
  if (aSpec !== bSpec) return bSpec - aSpec;
245
262
  return (b.urlPath?.length ?? 0) - (a.urlPath?.length ?? 0);
246
263
  });
247
264
 
248
- const defaultChain = buildLinearChain(topoSort(defaultGroup?.entries ?? [], onCycle), fallback);
265
+ return [...subRoutes, { order: topoSort(defaultGroup?.entries ?? [], onCycle) }];
266
+ }
267
+
268
+ export function buildRoutedChain(
269
+ portEntries: HttpEntry[],
270
+ fallback: Function,
271
+ onCycle?: () => void,
272
+ requestArgIndex: number = 0
273
+ ): Function {
274
+ const resolved = resolveRoutedChains(portEntries, onCycle);
275
+ // resolveRoutedChains returns sub-routes (dispatch order) followed by the default route last.
276
+ const defaultChain = buildLinearChain(resolved[resolved.length - 1].order, fallback);
277
+ const subRouteChains = resolved.slice(0, -1).map((route) => ({
278
+ host: route.host,
279
+ urlPath: route.urlPath,
280
+ chain: buildLinearChain(route.order, fallback),
281
+ }));
249
282
 
250
283
  return function dispatch(...args: any[]) {
251
284
  const request = args[requestArgIndex];
@@ -263,20 +296,77 @@ export function buildRoutedChain(
263
296
  };
264
297
  }
265
298
 
299
+ export type UnresolvedOrderingRef = { entryName?: string; kind: 'before' | 'after'; target: string };
300
+
301
+ /**
302
+ * Returns the `before`/`after` references among `entries` that name no registered entry.
303
+ * topoSort silently ignores these, so they deserve a diagnostic (see makeCallbackChain).
304
+ */
305
+ export function findUnresolvedOrderingRefs(entries: HttpEntry[]): UnresolvedOrderingRef[] {
306
+ const names = new Set<string>();
307
+ for (const { name } of entries) {
308
+ if (name) names.add(name);
309
+ }
310
+ const unresolved: UnresolvedOrderingRef[] = [];
311
+ for (const { name, before, after } of entries) {
312
+ if (before && !names.has(before)) unresolved.push({ entryName: name, kind: 'before', target: before });
313
+ if (after && !names.has(after)) unresolved.push({ entryName: name, kind: 'after', target: after });
314
+ }
315
+ return unresolved;
316
+ }
317
+
266
318
  /**
267
319
  * Builds the complete middleware chain for a given port from the full responders list.
268
320
  * Uses a flat linear chain when no sub-routes are present (fast path),
269
321
  * or a route-dispatching chain when any entry has urlPath or host.
322
+ *
323
+ * @param onUnresolved - called (once per unresolved reference) when a `before`/`after` names no
324
+ * registered entry, so a typo or legacy config key doesn't silently drop the ordering constraint.
325
+ * Reported on the chain's first dispatch, not at build time: the chain is rebuilt on every
326
+ * registration, so an early build may reference an entry that a later registration resolves.
270
327
  */
271
328
  export function makeCallbackChain(
272
329
  responders: HttpEntry[],
273
330
  portNum: number | string,
274
331
  fallback: Function,
275
332
  onCycle?: () => void,
276
- requestArgIndex: number = 0
333
+ requestArgIndex: number = 0,
334
+ onUnresolved?: (ref: UnresolvedOrderingRef) => void
277
335
  ): Function {
278
336
  const portEntries = responders.filter(({ port }) => port === portNum || port === 'all');
279
- if (portEntries.some((e) => e.urlPath || e.host))
280
- return buildRoutedChain(portEntries, fallback, onCycle, requestArgIndex);
281
- return buildLinearChain(topoSort(portEntries, onCycle), fallback);
337
+ const chain = portEntries.some((e) => e.urlPath || e.host)
338
+ ? buildRoutedChain(portEntries, fallback, onCycle, requestArgIndex)
339
+ : buildLinearChain(topoSort(portEntries, onCycle), fallback);
340
+ if (onUnresolved) {
341
+ const unresolved = findUnresolvedOrderingRefs(portEntries);
342
+ if (unresolved.length > 0) {
343
+ let reported = false;
344
+ return (...args: any[]) => {
345
+ if (!reported) {
346
+ reported = true;
347
+ for (const ref of unresolved) onUnresolved(ref);
348
+ }
349
+ return chain(...args);
350
+ };
351
+ }
352
+ }
353
+ return chain;
354
+ }
355
+
356
+ /**
357
+ * Describes the resolved middleware order for a port without building callbacks.
358
+ * Mirrors `makeCallbackChain`'s branch selection and reuses the same resolvers, so the
359
+ * returned order is exactly what a request on that port would traverse. Used for the
360
+ * chain-build debug log and for `get_status` introspection (issue #1573).
361
+ */
362
+ export function describeChains(
363
+ responders: HttpEntry[],
364
+ portNum: number | string,
365
+ onCycle?: () => void
366
+ ): ResolvedChain[] {
367
+ // Must use the exact same port selection and routing branch as makeCallbackChain so that, for a
368
+ // given portNum, the described order equals the order the built chain actually serves.
369
+ const portEntries = responders.filter(({ port }) => port === portNum || port === 'all');
370
+ if (portEntries.some((e) => e.urlPath || e.host)) return resolveRoutedChains(portEntries, onCycle);
371
+ return [{ order: topoSort(portEntries, onCycle) }];
282
372
  }
package/server/mqtt.ts CHANGED
@@ -463,7 +463,8 @@ function onSocket(socket, send, request, user, mqttSettings) {
463
463
  break;
464
464
  case 'pubcomp':
465
465
  case 'puback':
466
- session.acknowledge(packet.messageId);
466
+ await session.acknowledge(packet.messageId);
467
+ emitEvent('acknowledged', session, packet);
467
468
  break;
468
469
  case 'pingreq':
469
470
  sendPacket({ cmd: 'pingresp' });
@@ -25,7 +25,7 @@ import {
25
25
  serverErrorHandler,
26
26
  reqBodyValidationHandler,
27
27
  } from './serverHelpers/serverHandlers.js';
28
- import { registerBunFastifyInstance } from './http.ts';
28
+ import { registerFastifyInstance } from './http.ts';
29
29
  import { registerContentHandlers } from './serverHelpers/contentTypes.ts';
30
30
  import { getConfigObj } from '../config/configUtils.ts';
31
31
  import { registerMcpProfile } from '../components/mcp/index.ts';
@@ -94,7 +94,7 @@ async function operationsServer(options: ServerOptions & { resources?: Resources
94
94
  // On Bun, register the Fastify instance so requests can be delegated via inject()
95
95
  if (typeof globalThis.Bun !== 'undefined') {
96
96
  const port = options.port || options.securePort || env.get(CONFIG_PARAMS.OPERATIONSAPI_NETWORK_PORT);
97
- if (port) registerBunFastifyInstance(port, server);
97
+ if (port) registerFastifyInstance(port, server);
98
98
  }
99
99
  if (!server.server.closeIdleConnections) {
100
100
  // before Node v18, closeIdleConnections is not available, and we have to setup a listener for fastify
@@ -60,6 +60,30 @@ export class Headers extends Map<string, [string, string | string[]]> {
60
60
  }
61
61
  }
62
62
 
63
+ /**
64
+ * Add a field-name token to the `Vary` response header, appending to (and de-duplicating against) any
65
+ * existing value rather than overwriting it. Used to declare cache-partitioning dimensions (Origin,
66
+ * Authorization, Cookie) so a shared cache/CDN keys the response correctly (#1518, #1565).
67
+ */
68
+ export function addVaryHeader(
69
+ headers: { get(name: string): any; set(name: string, value: string): any },
70
+ token: string
71
+ ) {
72
+ const existing = headers.get('Vary');
73
+ if (!existing) {
74
+ headers.set('Vary', token);
75
+ return;
76
+ }
77
+ const existingString = Array.isArray(existing) ? existing.join(', ') : existing;
78
+ const lowerToken = token.toLowerCase();
79
+ for (const part of existingString.split(',')) {
80
+ const trimmed = part.trim().toLowerCase();
81
+ // a `Vary: *` already covers every dimension, and an exact token match is a no-op
82
+ if (trimmed === lowerToken || trimmed === '*') return;
83
+ }
84
+ headers.set('Vary', existingString + ', ' + token);
85
+ }
86
+
63
87
  export function appendHeader(headers, name, value, commaDelimited) {
64
88
  if (headers.append) {
65
89
  headers.append(name, value, commaDelimited);
@@ -129,7 +129,7 @@ class JSONStream extends Readable {
129
129
  }
130
130
  },
131
131
  (error) => {
132
- console.error(error);
132
+ console.error(harperLogger.errorForLog(error));
133
133
  this.done = true;
134
134
  this.push(errorToString(error));
135
135
  this.push(null);
@@ -201,7 +201,7 @@ class JSONStream extends Readable {
201
201
  }
202
202
  } while (this.push(nextString));
203
203
  } catch (error) {
204
- console.error(error);
204
+ console.error(harperLogger.errorForLog(error));
205
205
  this.push(errorToString(error));
206
206
  this.push(null);
207
207
  return true;
@@ -218,7 +218,7 @@ class JSONStream extends Readable {
218
218
  }
219
219
 
220
220
  function handleError(error) {
221
- console.error(error);
221
+ console.error(harperLogger.errorForLog(error));
222
222
  return JSON.stringify(errorToString(error));
223
223
  }
224
224
 
@@ -436,6 +436,133 @@ export class BunRequest {
436
436
  }
437
437
  }
438
438
 
439
+ /**
440
+ * uWebSockets.js Request adapter. Wraps the data extracted from a uWS HttpRequest/HttpResponse
441
+ * (which is only valid synchronously inside the route handler) into Harper's request interface,
442
+ * matching BunRequest. Intended for the plaintext-UDS-behind-symphony path: TLS/mTLS are
443
+ * terminated upstream, so peerCertificate/authorized are not available here and the real client
444
+ * IP arrives via the X-Forwarded-For header.
445
+ */
446
+ export class UwsRequest {
447
+ #body: UwsRequestBody | undefined;
448
+ #isSecure: boolean;
449
+ #ip: string | undefined;
450
+ #signal: AbortSignal | undefined;
451
+ public _nodeRequest: any = null;
452
+ public _nodeResponse: any = null;
453
+ public method: string;
454
+ public url: string;
455
+ public headers: RequestHeaders;
456
+ public isWebSocket?: boolean;
457
+ public user?: any;
458
+ public response: {
459
+ status?: number;
460
+ headers: ResponseHeaders;
461
+ };
462
+ public __harperRequestUpgraded: boolean;
463
+
464
+ constructor(source: {
465
+ method: string;
466
+ url: string;
467
+ headers: Record<string, string | string[]>;
468
+ ip?: string;
469
+ secure?: boolean;
470
+ body?: UwsRequestBody;
471
+ signal?: AbortSignal;
472
+ }) {
473
+ this.method = source.method;
474
+ this.url = source.url;
475
+ this.headers = new RequestHeaders(source.headers);
476
+ this.#isSecure = !!source.secure;
477
+ this.#ip = source.ip;
478
+ this.#body = source.body;
479
+ this.#signal = source.signal;
480
+ this.__harperRequestUpgraded = false;
481
+ }
482
+ get absoluteURL() {
483
+ return this.protocol + '://' + this.host + this.url;
484
+ }
485
+ get pathname() {
486
+ const queryStart = this.url.indexOf('?');
487
+ if (queryStart > -1) return this.url.slice(0, queryStart);
488
+ return this.url;
489
+ }
490
+ set pathname(pathname) {
491
+ const queryStart = this.url.indexOf('?');
492
+ if (queryStart > -1) this.url = pathname + this.url.slice(queryStart);
493
+ else this.url = pathname;
494
+ }
495
+ get protocol() {
496
+ // Behind symphony the on-the-wire hop is plaintext; the original scheme is conveyed
497
+ // by the proxy (X-Forwarded-Proto) or assumed from the listener's secure flag.
498
+ const xfp = this.headers.get('x-forwarded-proto');
499
+ if (xfp) return Array.isArray(xfp) ? xfp[0] : xfp;
500
+ return this.#isSecure ? 'https' : 'http';
501
+ }
502
+ get ip() {
503
+ // A real peer address (direct-TCP path) is authoritative and unspoofable, so it wins. Only the
504
+ // symphony-UDS path — where the socket has no client address (#ip is left unset) and symphony is
505
+ // the trusted setter/stripper of X-Forwarded-For — falls back to XFF. This prevents a direct
506
+ // client from spoofing `X-Forwarded-For: 127.0.0.1` to satisfy local auth (security/auth.ts).
507
+ if (this.#ip) return this.#ip;
508
+ const xff = this.headers.get('x-forwarded-for');
509
+ if (xff) return (Array.isArray(xff) ? xff[0] : xff).split(',')[0].trim();
510
+ return '';
511
+ }
512
+ get authorized() {
513
+ // TLS terminated upstream — client authorization not available at this hop.
514
+ return undefined;
515
+ }
516
+ get peerCertificate() {
517
+ return null;
518
+ }
519
+ get mtlsConfig() {
520
+ return undefined;
521
+ }
522
+ get body() {
523
+ // Bodyless requests get an already-ended empty stream so consumers can still read it uniformly.
524
+ if (!this.#body) {
525
+ this.#body = new UwsRequestBody();
526
+ this.#body.push(null);
527
+ }
528
+ return this.#body;
529
+ }
530
+ get host() {
531
+ return this.headers.get('host') as string;
532
+ }
533
+ get hostname() {
534
+ return this.headers.get('host') as string;
535
+ }
536
+ get httpVersion() {
537
+ return '1.1';
538
+ }
539
+ get isAborted() {
540
+ return this.#signal?.aborted ?? false;
541
+ }
542
+ get signal(): AbortSignal {
543
+ return this.#signal ?? new AbortController().signal;
544
+ }
545
+ _abort(): void {
546
+ // Abort is driven by the uWS res.onAborted handler wired into the provided signal.
547
+ }
548
+ get nodeRequest() {
549
+ return null;
550
+ }
551
+ sendEarlyHints(_link: string, _headers: Record<string, any> = {}) {
552
+ // Early hints not wired for the uWS path
553
+ }
554
+ }
555
+
556
+ // A push-based Readable: the uWS server pushes each request-body chunk (and the terminating null) in
557
+ // as it arrives off the socket, so consumers (streamToBuffer, future streaming deserializers) see a
558
+ // real stream instead of a pre-buffered blob. Extends Readable so it matches the RequestBody/
559
+ // BunRequestBody contract — on/pipe, `for await` async iteration, destroy() for abort handling.
560
+ // _read() is a no-op because flow is driven by push() from the uWS onData callback; uWS has no inbound
561
+ // backpressure, so pushed-but-unread chunks queue here (bounded by the server's maxBodyBytes ceiling).
562
+ export class UwsRequestBody extends Readable {
563
+ _read() {}
564
+ }
565
+
439
566
  class RequestBody {
440
567
  #nodeRequest: IncomingMessage;
441
568
  constructor(nodeRequest: IncomingMessage) {
@@ -623,15 +623,10 @@ function transformIterable(iterable, transform) {
623
623
  next() {
624
624
  const step = iterator.next();
625
625
  if (step.then) {
626
- return step.then((step) => ({
627
- value: transform(step.value),
628
- done: step.done,
629
- }));
626
+ // don't transform the terminal sentinel step, whose value is undefined
627
+ return step.then((result) => (result.done ? result : { value: transform(result.value), done: false }));
630
628
  }
631
- return {
632
- value: transform(step.value),
633
- done: step.done,
634
- };
629
+ return step.done ? step : { value: transform(step.value), done: false };
635
630
  },
636
631
  return(value) {
637
632
  return iterator.return(value);