@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/bin/cliOperations.ts +12 -0
  2. package/bin/restart.ts +66 -6
  3. package/components/Application.ts +1134 -112
  4. package/components/DESIGN.md +421 -0
  5. package/components/OptionsWatcher.ts +368 -102
  6. package/components/Scope.ts +20 -1
  7. package/components/componentLoader.ts +43 -4
  8. package/components/deploymentOperations.ts +4 -1
  9. package/components/deploymentRecorder.ts +9 -2
  10. package/components/mcp/DESIGN.md +109 -0
  11. package/components/mcp/audit.ts +21 -17
  12. package/components/operations.js +284 -52
  13. package/components/operationsValidation.js +49 -2
  14. package/components/packageComponent.ts +25 -1
  15. package/components/requestRestart.ts +11 -0
  16. package/config/DESIGN.md +306 -0
  17. package/config/RootConfigWatcher.ts +191 -37
  18. package/config/configReadRetry.ts +62 -0
  19. package/config/configUtils.ts +78 -26
  20. package/config/parseConfigFile.ts +34 -0
  21. package/config/readConfigFileSync.ts +44 -0
  22. package/config/watcherArming.ts +59 -0
  23. package/config-root.schema.json +4 -0
  24. package/dataLayer/DESIGN.md +179 -0
  25. package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
  26. package/dataLayer/restoreMarker.ts +92 -25
  27. package/dist/bin/cliOperations.js +13 -0
  28. package/dist/bin/cliOperations.js.map +1 -1
  29. package/dist/bin/restart.js +42 -6
  30. package/dist/bin/restart.js.map +1 -1
  31. package/dist/components/Application.d.ts +104 -9
  32. package/dist/components/Application.js +954 -102
  33. package/dist/components/Application.js.map +1 -1
  34. package/dist/components/OptionsWatcher.d.ts +4 -1
  35. package/dist/components/OptionsWatcher.js +378 -104
  36. package/dist/components/OptionsWatcher.js.map +1 -1
  37. package/dist/components/Scope.js +15 -1
  38. package/dist/components/Scope.js.map +1 -1
  39. package/dist/components/componentLoader.js +35 -3
  40. package/dist/components/componentLoader.js.map +1 -1
  41. package/dist/components/deploymentOperations.js +4 -1
  42. package/dist/components/deploymentOperations.js.map +1 -1
  43. package/dist/components/deploymentRecorder.d.ts +4 -2
  44. package/dist/components/deploymentRecorder.js +1 -0
  45. package/dist/components/deploymentRecorder.js.map +1 -1
  46. package/dist/components/mcp/audit.d.ts +2 -1
  47. package/dist/components/mcp/audit.js +21 -17
  48. package/dist/components/mcp/audit.js.map +1 -1
  49. package/dist/components/operations.d.ts +28 -0
  50. package/dist/components/operations.js +263 -54
  51. package/dist/components/operations.js.map +1 -1
  52. package/dist/components/operationsValidation.js +48 -2
  53. package/dist/components/operationsValidation.js.map +1 -1
  54. package/dist/components/packageComponent.js +24 -0
  55. package/dist/components/packageComponent.js.map +1 -1
  56. package/dist/components/requestRestart.d.ts +1 -0
  57. package/dist/components/requestRestart.js +7 -0
  58. package/dist/components/requestRestart.js.map +1 -1
  59. package/dist/config/RootConfigWatcher.d.ts +2 -0
  60. package/dist/config/RootConfigWatcher.js +189 -35
  61. package/dist/config/RootConfigWatcher.js.map +1 -1
  62. package/dist/config/configReadRetry.d.ts +8 -0
  63. package/dist/config/configReadRetry.js +62 -0
  64. package/dist/config/configReadRetry.js.map +1 -0
  65. package/dist/config/configUtils.d.ts +10 -9
  66. package/dist/config/configUtils.js +63 -27
  67. package/dist/config/configUtils.js.map +1 -1
  68. package/dist/config/parseConfigFile.d.ts +4 -0
  69. package/dist/config/parseConfigFile.js +35 -0
  70. package/dist/config/parseConfigFile.js.map +1 -0
  71. package/dist/config/readConfigFileSync.d.ts +1 -0
  72. package/dist/config/readConfigFileSync.js +47 -0
  73. package/dist/config/readConfigFileSync.js.map +1 -0
  74. package/dist/config/watcherArming.d.ts +15 -0
  75. package/dist/config/watcherArming.js +59 -0
  76. package/dist/config/watcherArming.js.map +1 -0
  77. package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
  78. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  79. package/dist/dataLayer/restoreMarker.d.ts +21 -8
  80. package/dist/dataLayer/restoreMarker.js +94 -27
  81. package/dist/dataLayer/restoreMarker.js.map +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +9 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/json/systemSchema.json +3 -0
  86. package/dist/resources/DatabaseTransaction.d.ts +25 -0
  87. package/dist/resources/DatabaseTransaction.js +224 -18
  88. package/dist/resources/DatabaseTransaction.js.map +1 -1
  89. package/dist/resources/LMDBTransaction.d.ts +2 -1
  90. package/dist/resources/LMDBTransaction.js +22 -3
  91. package/dist/resources/LMDBTransaction.js.map +1 -1
  92. package/dist/resources/PrimaryRocksDatabase.js +22 -6
  93. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  94. package/dist/resources/RecordEncoder.d.ts +1 -1
  95. package/dist/resources/RecordEncoder.js +46 -10
  96. package/dist/resources/RecordEncoder.js.map +1 -1
  97. package/dist/resources/Resource.js +97 -13
  98. package/dist/resources/Resource.js.map +1 -1
  99. package/dist/resources/ResourceInterface.d.ts +8 -0
  100. package/dist/resources/RocksIndexStore.js +2 -1
  101. package/dist/resources/RocksIndexStore.js.map +1 -1
  102. package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
  103. package/dist/resources/RocksTransactionLogStore.js +104 -33
  104. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  105. package/dist/resources/Table.d.ts +58 -7
  106. package/dist/resources/Table.js +1143 -348
  107. package/dist/resources/Table.js.map +1 -1
  108. package/dist/resources/analytics/write.d.ts +3 -0
  109. package/dist/resources/analytics/write.js +49 -16
  110. package/dist/resources/analytics/write.js.map +1 -1
  111. package/dist/resources/auditStore.d.ts +170 -0
  112. package/dist/resources/auditStore.js +457 -11
  113. package/dist/resources/auditStore.js.map +1 -1
  114. package/dist/resources/crdt.d.ts +10 -0
  115. package/dist/resources/crdt.js +22 -0
  116. package/dist/resources/crdt.js.map +1 -1
  117. package/dist/resources/dataLoader.js +3 -4
  118. package/dist/resources/dataLoader.js.map +1 -1
  119. package/dist/resources/databases.d.ts +16 -13
  120. package/dist/resources/databases.js +625 -177
  121. package/dist/resources/databases.js.map +1 -1
  122. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  123. package/dist/resources/derivedIndexRegistry.js +68 -0
  124. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  125. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  126. package/dist/resources/derivedIndexRuntime.js +2027 -0
  127. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  128. package/dist/resources/graphql.js +3 -2
  129. package/dist/resources/graphql.js.map +1 -1
  130. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
  131. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
  132. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  133. package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
  134. package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
  135. package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
  136. package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
  137. package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
  138. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
  139. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  140. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  141. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  142. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  143. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  144. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  145. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
  146. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
  147. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
  148. package/dist/resources/nodeIdMapping.d.ts +5 -0
  149. package/dist/resources/nodeIdMapping.js +49 -0
  150. package/dist/resources/nodeIdMapping.js.map +1 -1
  151. package/dist/resources/recordLock.d.ts +47 -4
  152. package/dist/resources/recordLock.js +138 -7
  153. package/dist/resources/recordLock.js.map +1 -1
  154. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  155. package/dist/resources/recordLockCoordinator.js +2597 -0
  156. package/dist/resources/recordLockCoordinator.js.map +1 -0
  157. package/dist/resources/replayLogs.js +5 -0
  158. package/dist/resources/replayLogs.js.map +1 -1
  159. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  160. package/dist/resources/replicatedApplyFailure.js +63 -0
  161. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  162. package/dist/resources/scheduler/scheduler.js +3 -3
  163. package/dist/resources/scheduler/scheduler.js.map +1 -1
  164. package/dist/resources/search.d.ts +10 -4
  165. package/dist/resources/search.js +160 -40
  166. package/dist/resources/search.js.map +1 -1
  167. package/dist/resources/tracked.d.ts +5 -1
  168. package/dist/resources/tracked.js +74 -23
  169. package/dist/resources/tracked.js.map +1 -1
  170. package/dist/resources/transactionBroadcast.js +4 -6
  171. package/dist/resources/transactionBroadcast.js.map +1 -1
  172. package/dist/security/auth.js +59 -23
  173. package/dist/security/auth.js.map +1 -1
  174. package/dist/security/deferredAuthentication.d.ts +11 -0
  175. package/dist/security/deferredAuthentication.js +25 -3
  176. package/dist/security/deferredAuthentication.js.map +1 -1
  177. package/dist/security/jsLoader.js +9 -4
  178. package/dist/security/jsLoader.js.map +1 -1
  179. package/dist/server/REST.js +39 -5
  180. package/dist/server/REST.js.map +1 -1
  181. package/dist/server/http.d.ts +5 -1
  182. package/dist/server/http.js +34 -2
  183. package/dist/server/http.js.map +1 -1
  184. package/dist/server/mqtt.js +5 -1
  185. package/dist/server/mqtt.js.map +1 -1
  186. package/dist/server/serverHelpers/Headers.d.ts +2 -0
  187. package/dist/server/serverHelpers/Headers.js +6 -0
  188. package/dist/server/serverHelpers/Headers.js.map +1 -1
  189. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  190. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  191. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  192. package/dist/server/serverHelpers/Request.d.ts +5 -10
  193. package/dist/server/serverHelpers/Request.js +38 -136
  194. package/dist/server/serverHelpers/Request.js.map +1 -1
  195. package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
  196. package/dist/server/serverHelpers/contentTypes.js +189 -15
  197. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  198. package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
  199. package/dist/server/serverHelpers/serverUtilities.js +110 -20
  200. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  201. package/dist/server/serverHelpers/uwsServer.js +4 -1
  202. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  203. package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
  204. package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
  205. package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
  206. package/dist/server/storageReclamation.js +1 -1
  207. package/dist/server/storageReclamation.js.map +1 -1
  208. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  209. package/dist/server/threads/isolatedApplications.js +171 -0
  210. package/dist/server/threads/isolatedApplications.js.map +1 -0
  211. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  212. package/dist/server/threads/logRotationTransport.js +33 -0
  213. package/dist/server/threads/logRotationTransport.js.map +1 -0
  214. package/dist/server/threads/manageThreads.d.ts +64 -6
  215. package/dist/server/threads/manageThreads.js +261 -12
  216. package/dist/server/threads/manageThreads.js.map +1 -1
  217. package/dist/server/threads/socketRouter.d.ts +1 -0
  218. package/dist/server/threads/socketRouter.js +196 -13
  219. package/dist/server/threads/socketRouter.js.map +1 -1
  220. package/dist/server/threads/threadServer.js +30 -7
  221. package/dist/server/threads/threadServer.js.map +1 -1
  222. package/dist/utility/errors/hdbError.d.ts +24 -0
  223. package/dist/utility/errors/hdbError.js +58 -1
  224. package/dist/utility/errors/hdbError.js.map +1 -1
  225. package/dist/utility/hdbTerms.d.ts +2 -0
  226. package/dist/utility/hdbTerms.js +2 -0
  227. package/dist/utility/hdbTerms.js.map +1 -1
  228. package/dist/utility/logging/harper_logger.js +218 -38
  229. package/dist/utility/logging/harper_logger.js.map +1 -1
  230. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  231. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  232. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  233. package/dist/utility/logging/logRotation.d.ts +46 -0
  234. package/dist/utility/logging/logRotation.js +365 -0
  235. package/dist/utility/logging/logRotation.js.map +1 -0
  236. package/dist/utility/logging/logRotator.d.ts +1 -1
  237. package/dist/utility/logging/logRotator.js +172 -92
  238. package/dist/utility/logging/logRotator.js.map +1 -1
  239. package/dist/utility/npmUtilities.js +6 -4
  240. package/dist/utility/npmUtilities.js.map +1 -1
  241. package/dist/utility/watcherFallback.d.ts +0 -45
  242. package/dist/utility/watcherFallback.js +1 -125
  243. package/dist/utility/watcherFallback.js.map +1 -1
  244. package/dist/validation/configValidator.js +6 -3
  245. package/dist/validation/configValidator.js.map +1 -1
  246. package/index.ts +9 -0
  247. package/json/systemSchema.json +3 -0
  248. package/npm-shrinkwrap.json +177 -87
  249. package/package.json +13 -5
  250. package/resources/DESIGN.md +689 -19
  251. package/resources/DatabaseTransaction.ts +230 -17
  252. package/resources/LMDBTransaction.ts +21 -3
  253. package/resources/PrimaryRocksDatabase.ts +20 -7
  254. package/resources/RecordEncoder.ts +52 -10
  255. package/resources/Resource.ts +97 -13
  256. package/resources/ResourceInterface.ts +8 -0
  257. package/resources/RocksIndexStore.ts +2 -1
  258. package/resources/RocksTransactionLogStore.ts +111 -31
  259. package/resources/Table.ts +1273 -396
  260. package/resources/analytics/DESIGN.md +38 -0
  261. package/resources/analytics/write.ts +50 -17
  262. package/resources/auditStore.ts +460 -11
  263. package/resources/crdt.ts +22 -0
  264. package/resources/dataLoader.ts +3 -4
  265. package/resources/databases.ts +611 -147
  266. package/resources/derivedIndexRegistry.ts +56 -0
  267. package/resources/derivedIndexRuntime.ts +2292 -0
  268. package/resources/graphql.ts +3 -2
  269. package/resources/indexes/DESIGN.md +833 -0
  270. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
  271. package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
  272. package/resources/indexes/fullTextNativeBinding.ts +146 -0
  273. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  274. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  275. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
  276. package/resources/nodeIdMapping.ts +50 -0
  277. package/resources/record-locks.md +1407 -0
  278. package/resources/recordLock.ts +173 -7
  279. package/resources/recordLockCoordinator.ts +3082 -0
  280. package/resources/replayLogs.ts +5 -0
  281. package/resources/replicatedApplyFailure.ts +77 -0
  282. package/resources/scheduler/DESIGN.md +40 -0
  283. package/resources/scheduler/scheduler.ts +4 -4
  284. package/resources/search.ts +169 -49
  285. package/resources/tracked.ts +73 -22
  286. package/resources/transactionBroadcast.ts +4 -4
  287. package/security/DESIGN.md +175 -0
  288. package/security/auth.ts +53 -24
  289. package/security/deferredAuthentication.ts +24 -2
  290. package/security/jsLoader.ts +9 -4
  291. package/server/DESIGN.md +275 -0
  292. package/server/REST.ts +42 -6
  293. package/server/http.ts +34 -2
  294. package/server/mqtt.ts +6 -4
  295. package/server/serverHelpers/Headers.ts +5 -1
  296. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  297. package/server/serverHelpers/Request.ts +33 -131
  298. package/server/serverHelpers/contentTypes.ts +188 -15
  299. package/server/serverHelpers/serverUtilities.ts +157 -27
  300. package/server/serverHelpers/uwsServer.ts +4 -1
  301. package/server/serverHelpers/webSocketCloseReason.ts +25 -0
  302. package/server/storageReclamation.ts +2 -2
  303. package/server/threads/isolatedApplications.ts +157 -0
  304. package/server/threads/logRotationTransport.ts +40 -0
  305. package/server/threads/manageThreads.js +254 -12
  306. package/server/threads/socketRouter.ts +217 -11
  307. package/server/threads/threadServer.js +30 -7
  308. package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
  309. package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
  310. package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
  311. package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
  312. package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
  313. package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
  314. package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
  315. package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
  316. package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
  317. package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
  318. package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
  319. package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
  320. package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
  321. package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
  322. package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
  323. package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
  324. package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
  325. package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
  326. package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
  327. package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
  328. package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
  329. package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
  330. package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
  331. package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
  332. package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
  333. package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
  334. package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
  335. package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
  336. package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
  337. package/studio/web/index.html +1 -1
  338. package/utility/DESIGN.md +55 -0
  339. package/utility/errors/hdbError.ts +54 -0
  340. package/utility/hdbTerms.ts +2 -0
  341. package/utility/logging/harper_logger.ts +210 -30
  342. package/utility/logging/logGenerationCoordinator.ts +196 -0
  343. package/utility/logging/logRotation.ts +367 -0
  344. package/utility/logging/logRotator.ts +196 -91
  345. package/utility/npmUtilities.ts +6 -4
  346. package/utility/watcherFallback.ts +0 -122
  347. package/validation/configValidator.ts +6 -3
@@ -89,9 +89,9 @@ export type OperationFunctionName = ValueOf<typeof terms.OPERATIONS_ENUM>;
89
89
  * name — still stripped, since this runs ahead of the validation that now rejects it). `value` /
90
90
  * `values` carry .env secrets from set_env_value; `value` / `envelope` carry secrets from
91
91
  * set_secret. `token` is the login-purpose token (login) and the CI identity token
92
- * (exchange_oidc_token), and is also stripped defensively — no operation declares a top-level `token`,
93
- * but validation allows unknown keys, so a mistyped `harper deploy setup token=…` must not log a live
94
- * credential. `refresh_token` is the 30-day credential (refresh_operation_token).
92
+ * (exchange_oidc_token), and is also stripped defensively — no operation declares a top-level
93
+ * `token`, but validation allows unknown keys, so a mistyped `harper deploy setup token=…` must not
94
+ * log a live credential. `refresh_token` is the 30-day credential (refresh_operation_token).
95
95
  *
96
96
  * Redaction runs *before* the handler, so a rejected request logs a still-spendable credential —
97
97
  * which is why a new secret-bearing field belongs here rather than left to the default (harper#1527
@@ -111,10 +111,21 @@ export const UNLOGGABLE_OPERATION_FIELDS = [
111
111
  'refresh_token',
112
112
  ];
113
113
 
114
+ const UNLOGGABLE_FIELDS_BY_OPERATION = new Map<string, ReadonlySet<string>>([
115
+ ['add_ssh_key', new Set(['key'])],
116
+ ['update_ssh_key', new Set(['key'])],
117
+ ]);
118
+
114
119
  /** Callers gate this on log level: it allocates, and the operations log is often off. */
115
120
  export function redactForOperationLog(body: Record<string, any>): Record<string, any> {
116
121
  const clean = { ...body };
117
122
  for (const field of UNLOGGABLE_OPERATION_FIELDS) delete clean[field];
123
+ const operationFields = UNLOGGABLE_FIELDS_BY_OPERATION.get(body.operation);
124
+ if (operationFields) {
125
+ for (const field of Object.keys(clean)) {
126
+ if (operationFields.has(field.toLowerCase())) delete clean[field];
127
+ }
128
+ }
118
129
  return clean;
119
130
  }
120
131
 
@@ -235,6 +246,17 @@ server.setMcpQuotaHandler = (handler) => {
235
246
  setMcpQuotaHandler(handler);
236
247
  };
237
248
 
249
+ // The nested `search_operation` operations dataLayer/export.ts's getRecords can actually run (its
250
+ // VALID_SEARCH_OPERATIONS). Validated at dispatch so an unsupported or missing nested operation is a
251
+ // request-time 400 rather than an async job failure, and so the nested authorization below always
252
+ // resolves a real handler.
253
+ const EXPORT_SEARCH_OPERATIONS = new Set<string>([
254
+ terms.OPERATIONS_ENUM.SEARCH_BY_VALUE,
255
+ terms.OPERATIONS_ENUM.SEARCH_BY_HASH,
256
+ terms.OPERATIONS_ENUM.SEARCH_BY_CONDITIONS,
257
+ terms.OPERATIONS_ENUM.SQL,
258
+ ]);
259
+
238
260
  export function chooseOperation(json: OperationRequestBody, bypassAuth = false) {
239
261
  let getOpResult: OperationFunctionObject;
240
262
  try {
@@ -255,32 +277,112 @@ export function chooseOperation(json: OperationRequestBody, bypassAuth = false)
255
277
 
256
278
  const { operation_function, job_operation_function } = getOpResult;
257
279
 
258
- // Here there is a SQL statement in either the operation or the searchOperation (from jobs like export_local). Need to check the perms
259
- // on all affected tables/attributes.
280
+ const isSqlOperation = json.operation === terms.OPERATIONS_ENUM.SQL;
281
+ // `verifyPerms` derives the tables it checks from the object it is handed, so the caller-supplied
282
+ // `search_operation` may only stand in as the permission subject for the operations that actually
283
+ // run it (dataLayer/export.ts is its only consumer). For anything else it would check the nested
284
+ // tables while the handler runs against the top-level ones.
285
+ const nestedSearch =
286
+ json.operation === terms.OPERATIONS_ENUM.EXPORT_LOCAL || json.operation === terms.OPERATIONS_ENUM.EXPORT_TO_S3
287
+ ? json.search_operation
288
+ : undefined;
289
+ if (nestedSearch !== undefined) {
290
+ // It becomes the permission subject below, and a primitive would throw on the principal
291
+ // assignment — a 400 rather than a wrapped 500.
292
+ if (nestedSearch === null || typeof nestedSearch !== 'object' || Array.isArray(nestedSearch)) {
293
+ throw handleHDBError(
294
+ new Error(),
295
+ `'search_operation' must be an object`,
296
+ hdbErrors.HTTP_STATUS_CODES.BAD_REQUEST,
297
+ undefined,
298
+ undefined,
299
+ true
300
+ );
301
+ }
302
+ // The object check alone lets `{}` and unsupported operations through — they would then fail
303
+ // asynchronously in the export worker (or dereference `search_operation.operation`). Require a
304
+ // supported operation at request time; the nested authorization below relies on it resolving.
305
+ if (typeof nestedSearch.operation !== 'string' || !EXPORT_SEARCH_OPERATIONS.has(nestedSearch.operation)) {
306
+ throw handleHDBError(
307
+ new Error(),
308
+ `'search_operation.operation' must be one of: ${[...EXPORT_SEARCH_OPERATIONS].join(', ')}`,
309
+ hdbErrors.HTTP_STATUS_CODES.BAD_REQUEST,
310
+ undefined,
311
+ undefined,
312
+ true
313
+ );
314
+ }
315
+ // Dispatch state, never client input: the export worker re-reads `parsed_sql_object` off this
316
+ // same object (evaluateSQL), and it carries `permissions_checked`, so a body-supplied one would
317
+ // execute an AST no check ever saw. Deleting it rather than replacing it with the authorized
318
+ // parse is deliberate — the worker then re-parses and re-runs the check itself.
319
+ delete nestedSearch.parsed_sql_object;
320
+ }
321
+ // The AST check below covers only the statement's tables, never the caller's right to invoke the
322
+ // job operation, so it is additive to verifyPerms rather than an alternative to it.
323
+ const hasNestedSqlSearch = nestedSearch?.operation === terms.OPERATIONS_ENUM.SQL;
324
+
260
325
  try {
261
- if (json.operation === 'sql' || (json.search_operation && json.search_operation.operation === 'sql')) {
326
+ if (isSqlOperation || hasNestedSqlSearch) {
262
327
  const sql = require('../../sqlTranslator/index');
263
- const sqlStatement = json.operation === 'sql' ? json.sql : json.search_operation.sql;
328
+ const sqlStatement = isSqlOperation ? json.sql : nestedSearch.sql;
264
329
  // Before this dispatch's own parse is assigned, so a body-supplied object cannot survive it.
265
330
  stripSuppliedParsedSqlObject(json);
266
331
  const parsedSqlObject = sql.convertSQLToAST(sqlStatement);
267
- json.parsed_sql_object = parsedSqlObject;
332
+ if (hasNestedSqlSearch && parsedSqlObject.variant !== terms.VALID_SQL_OPS_ENUM.SELECT) {
333
+ throw handleHDBError(
334
+ new Error(),
335
+ `'search_operation.sql' must be a SELECT statement`,
336
+ hdbErrors.HTTP_STATUS_CODES.BAD_REQUEST,
337
+ undefined,
338
+ undefined,
339
+ true
340
+ );
341
+ }
342
+ // Only the direct-SQL path consumes `json.parsed_sql_object` (evaluateSQL). A nested export
343
+ // re-parses off `search_operation` — whose `parsed_sql_object` was deleted above — so setting
344
+ // it here for a job would be inert.
345
+ if (isSqlOperation) {
346
+ json.parsed_sql_object = parsedSqlObject;
347
+ }
268
348
  if (!bypassAuth) {
269
- // The SQL path never reaches verifyPerms, so the role `operations` allowlist must be
270
- // enforced here — otherwise an allowlisted role reaches unlisted operations via `sql`.
271
- // json.operation is already the API name ('sql', or the outer job op like 'export_local').
272
- const allowlistDenial = opAuth.verifyOperationsAllowlist(json, json.operation);
273
- if (allowlistDenial) {
274
- operationLog.error(`${HTTP_STATUS_CODES.FORBIDDEN} from operation ${json.operation}`);
275
- operationLog.warn(`User '${json.hdb_user?.username}' is not permitted to ${json.operation}`);
276
- throw handleHDBError(
277
- new Error(),
278
- allowlistDenial,
279
- hdbErrors.HTTP_STATUS_CODES.FORBIDDEN,
280
- undefined,
281
- undefined,
282
- true
283
- );
349
+ if (hasNestedSqlSearch) {
350
+ // A nested-SQL export's outer op (export_local/export_to_s3) is `requires_su`, which the
351
+ // AST table check below does not enforce. Authorize invoking it through verifyPerms
352
+ // exactly as the non-SQL job path does — otherwise SQL is a way around the requires_su
353
+ // gate that path enforces. The nested table reads are then covered additively by the AST
354
+ // check. verifyPerms also runs the allowlist (gate 1) and the export token scope.
355
+ const functionToCheck = job_operation_function === undefined ? operation_function : job_operation_function;
356
+ const outerDenial = opAuth.verifyPerms(json, functionToCheck, { apiOperation: json.operation });
357
+ if (outerDenial) {
358
+ operationLog.error(`${HTTP_STATUS_CODES.FORBIDDEN} from operation ${json.operation}`);
359
+ operationLog.warn(`User '${json.hdb_user?.username}' is not permitted to ${json.operation}`);
360
+ throw handleHDBError(
361
+ new Error(),
362
+ outerDenial,
363
+ hdbErrors.HTTP_STATUS_CODES.FORBIDDEN,
364
+ undefined,
365
+ false,
366
+ true
367
+ );
368
+ }
369
+ } else {
370
+ // Direct SQL: the invoked operation IS `sql`, so the role `operations` allowlist is the
371
+ // operation-invocation check — otherwise an allowlisted role reaches unlisted operations
372
+ // via `sql`. json.operation is already the API name.
373
+ const allowlistDenial = opAuth.verifyOperationsAllowlist(json, json.operation);
374
+ if (allowlistDenial) {
375
+ operationLog.error(`${HTTP_STATUS_CODES.FORBIDDEN} from operation ${json.operation}`);
376
+ operationLog.warn(`User '${json.hdb_user?.username}' is not permitted to ${json.operation}`);
377
+ throw handleHDBError(
378
+ new Error(),
379
+ allowlistDenial,
380
+ hdbErrors.HTTP_STATUS_CODES.FORBIDDEN,
381
+ undefined,
382
+ undefined,
383
+ true
384
+ );
385
+ }
284
386
  }
285
387
  // `json.operation` explicitly — the operation this dispatch already resolved, not a
286
388
  // field read back off the request body.
@@ -309,10 +411,9 @@ export function chooseOperation(json: OperationRequestBody, bypassAuth = false)
309
411
  json.operation !== terms.OPERATIONS_ENUM.EXCHANGE_OIDC_TOKEN
310
412
  ) {
311
413
  const functionToCheck = job_operation_function === undefined ? operation_function : job_operation_function;
312
- const operation_json = json.search_operation ? json.search_operation : json;
313
- if (!operation_json.hdb_user) {
314
- operation_json.hdb_user = json.hdb_user;
315
- }
414
+ const operation_json = nestedSearch ?? json;
415
+ // Authentication is the only source of the principal; a nested one is overwritten, not honored.
416
+ operation_json.hdb_user = json.hdb_user;
316
417
 
317
418
  // Pass the top-level operation for the token-scope check: for an export job, operation_json
318
419
  // is the nested search_operation, so json.operation (export_local/export_to_s3) is the op the
@@ -335,6 +436,35 @@ export function chooseOperation(json: OperationRequestBody, bypassAuth = false)
335
436
  true
336
437
  );
337
438
  }
439
+
440
+ // The check above authorizes invoking the operation, but for a non-SQL export it returns
441
+ // before any table check — a requires_su export is granted the moment its `operations`
442
+ // allowlist lists it (verifyPerms gate 2) — and the job worker then runs `search_operation`
443
+ // through searchByValue/Hash/Conditions, none of which check permissions. So the nested read
444
+ // is only enforceable here: authorize it additively against its real search handler and the
445
+ // authenticated principal (assigned above via operation_json), so a role that may invoke the
446
+ // export but lacks read on the table is denied. A nested SQL search is covered by the AST
447
+ // branch above instead.
448
+ if (nestedSearch) {
449
+ const nestedFunction = getOperationFunction(nestedSearch).operation_function;
450
+ const nestedPermsResult = opAuth.verifyPerms(nestedSearch, nestedFunction, {
451
+ apiOperation: json.operation,
452
+ });
453
+ if (nestedPermsResult) {
454
+ operationLog.error(`${HTTP_STATUS_CODES.FORBIDDEN} from operation ${json.operation}`);
455
+ operationLog.warn(
456
+ `User '${operation_json.hdb_user?.username}' is not permitted to ${nestedSearch.operation} within ${json.operation}`
457
+ );
458
+ throw handleHDBError(
459
+ new Error(),
460
+ nestedPermsResult,
461
+ hdbErrors.HTTP_STATUS_CODES.FORBIDDEN,
462
+ undefined,
463
+ false,
464
+ true
465
+ );
466
+ }
467
+ }
338
468
  }
339
469
  } catch (err) {
340
470
  throw handleHDBError(err, `There was an error when trying to choose an operation path`, 500);
@@ -24,6 +24,7 @@ import { UwsRequest, UwsRequestBody } from './Request.ts';
24
24
  import { Headers } from './Headers.ts';
25
25
  import { when } from '../../utility/when.ts';
26
26
  import { ClientError } from '../../utility/errors/hdbError.ts';
27
+ import { errorToString } from '../../utility/logging/harper_logger.ts';
27
28
 
28
29
  // uWS has no npm package; it's installed from a GitHub tag and is platform/ABI-specific.
29
30
  // Imported lazily so harper builds/loads on platforms without a uWS binary.
@@ -160,7 +161,9 @@ export async function createUwsServer(options: UwsServerOptions): Promise<{ app:
160
161
  res.cork(() => {
161
162
  res.writeStatus(statusText(status));
162
163
  res.writeHeader('content-type', 'text/plain');
163
- res.end(String((error && error.message) || error));
164
+ // the same renderer as the Node and Bun terminal handlers: the class name is the
165
+ // error code a client branches on, and it must not depend on the runtime
166
+ res.end(errorToString(error));
164
167
  });
165
168
  }
166
169
  );
@@ -0,0 +1,25 @@
1
+ // A close frame's payload is at most 125 bytes, two of which are the status code, and `ws` throws a
2
+ // RangeError past that (node_modules/ws/lib/sender.js). Both close sites below are reached from a
3
+ // rejection handler, where such a throw would surface as an unhandled rejection rather than a failed
4
+ // close, so a message Harper does not control — a `server.getUser` override's, for instance — must be
5
+ // bounded before it gets there.
6
+ const MAX_CLOSE_REASON_BYTES = 123;
7
+
8
+ /** Bounds `text` to what a close frame accepts, truncating on a code-point boundary. */
9
+ export function toCloseReason(text: string | undefined): string {
10
+ const reason = text ?? '';
11
+ // a UTF-16 code unit never encodes to fewer than one byte, so an over-long string is over the byte
12
+ // limit too — testing that first keeps an unbounded message off byteLength's whole-string scan
13
+ if (reason.length <= MAX_CLOSE_REASON_BYTES && Buffer.byteLength(reason, 'utf8') <= MAX_CLOSE_REASON_BYTES)
14
+ return reason;
15
+ let bytes = 0;
16
+ let end = 0;
17
+ // iterating the string yields whole code points, so a surrogate pair is never split
18
+ for (const character of reason) {
19
+ const size = Buffer.byteLength(character, 'utf8');
20
+ if (bytes + size > MAX_CLOSE_REASON_BYTES) break;
21
+ bytes += size;
22
+ end += character.length;
23
+ }
24
+ return reason.slice(0, end);
25
+ }
@@ -1,7 +1,7 @@
1
1
  import { readFile, realpath } from 'node:fs/promises';
2
2
  import { statfs } from 'node:fs/promises';
3
3
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
- import { getWorkerIndex, getWorkerCount } from '../server/threads/manageThreads.js';
4
+ import { ownsStoreMaintenance } from '../server/threads/manageThreads.js';
5
5
  import { logger } from '../utility/logging/logger.ts';
6
6
  import { CONFIG_PARAMS } from '../utility/hdbTerms.ts';
7
7
  import * as envMgr from '../utility/environment/environmentManager.ts';
@@ -106,7 +106,7 @@ export function onStorageReclamation(
106
106
  handler: (priority: number) => Promise<void> | void,
107
107
  skipThreadCheck?: boolean
108
108
  ) {
109
- if (skipThreadCheck || getWorkerIndex() === getWorkerCount() - 1) {
109
+ if (skipThreadCheck || ownsStoreMaintenance(path)) {
110
110
  // only run on one thread (last one)
111
111
  if (!path) {
112
112
  throw new Error('Storage reclamation path cannot be empty');
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Isolated applications: an application whose root-config entry carries `isolated: true` runs in a
3
+ * worker thread of its own that loads no other application (harper#642, tier 2).
4
+ *
5
+ * The invariant every helper here serves: an isolated application is loaded by exactly one thread,
6
+ * and that thread loads nothing else. The dedicated worker is an ordinary `http` worker by type --
7
+ * so restarts, overlap rules and shutdown treat it as one -- distinguished only by
8
+ * `workerData.isolatedApplication`. It binds none of the public ports (the kernel would hand it
9
+ * connections for every other application) and is reachable only through its own UDS mirror, whose
10
+ * metadata names the application and its hosts for the fronting proxy to route by.
11
+ */
12
+ import { isMainThread, workerData } from 'node:worker_threads';
13
+ import { getWorkerIndex } from './manageThreads.js';
14
+ import { getConfigObj, getConfigPath } from '../../config/configUtils.ts';
15
+ import * as env from '../../utility/environment/environmentManager.ts';
16
+ import { CONFIG_PARAMS } from '../../utility/hdbTerms.ts';
17
+ import { isDomainSocketPathTooLong } from '../../utility/domainSocket.ts';
18
+ import { existsSync } from 'node:fs';
19
+ import { basename, join } from 'node:path';
20
+
21
+ export const DEFAULT_MAX_ISOLATED_APPLICATIONS = 8;
22
+
23
+ /** The application this thread is dedicated to, if it is a dedicated worker. */
24
+ export function thisThreadsIsolatedApplication(): string | undefined {
25
+ return (workerData as any)?.isolatedApplication;
26
+ }
27
+
28
+ export function thisThreadOwnsApplication(
29
+ appName: string | undefined,
30
+ owner: string | undefined = thisThreadsIsolatedApplication()
31
+ ): boolean {
32
+ return owner !== undefined && appName === owner;
33
+ }
34
+
35
+ export function isIsolatedApplication(
36
+ appName: string,
37
+ config: Record<string, any> | undefined = getConfigObj()
38
+ ): boolean {
39
+ const entry = config?.[appName];
40
+ return typeof entry === 'object' && entry !== null && entry.isolated === true;
41
+ }
42
+
43
+ /** Every application the root config marks isolated, in config order. */
44
+ export function isolatedApplicationNames(config: Record<string, any> | undefined = getConfigObj()): string[] {
45
+ if (!config) return [];
46
+ return Object.keys(config).filter((name) => isIsolatedApplication(name, config));
47
+ }
48
+
49
+ export function presentIsolatedApplicationNames(
50
+ config: Record<string, any> | undefined = getConfigObj(),
51
+ componentsRoot: string = getConfigPath(CONFIG_PARAMS.COMPONENTSROOT) as string,
52
+ installedRoot: string = join(env.get(CONFIG_PARAMS.ROOTPATH), 'components'),
53
+ runApplicationPath: string | undefined = process.env.RUN_HDB_APP
54
+ ): string[] {
55
+ return isolatedApplicationNames(config).filter(
56
+ (application) =>
57
+ existsSync(join(componentsRoot, application)) ||
58
+ existsSync(join(installedRoot, application)) ||
59
+ (runApplicationPath && basename(runApplicationPath) === application)
60
+ );
61
+ }
62
+
63
+ export function maxIsolatedApplications(): number {
64
+ const configured = Number(env.get(CONFIG_PARAMS.THREADS_MAXISOLATED));
65
+ return Number.isInteger(configured) && configured >= 0 ? configured : DEFAULT_MAX_ISOLATED_APPLICATIONS;
66
+ }
67
+
68
+ /** Why `appName` cannot claim a place in the dedicated-worker budget, if the budget is full. */
69
+ export function isolatedApplicationCapacityRefusal(
70
+ appName: string,
71
+ runningApplications: Iterable<string>,
72
+ max = maxIsolatedApplications()
73
+ ): string | undefined {
74
+ const running = new Set(runningApplications);
75
+ if (running.has(appName) || running.size < max) return undefined;
76
+ return `the instance already runs ${max} isolated application(s) (threads.maxIsolated)`;
77
+ }
78
+
79
+ /**
80
+ * Whether the calling thread is the one that loads the application `appName` (an application: a
81
+ * directory under componentsRoot, or a root-config entry with `package`). A dedicated worker loads
82
+ * only its own application; every other thread -- pool workers and the main thread alike -- loads only
83
+ * the applications that are not isolated. Decided before any of the application's modules are
84
+ * imported, so a skipped application has no side effects on the thread that skipped it.
85
+ */
86
+ export function shouldLoadApplicationHere(
87
+ appName: string,
88
+ owner: string | undefined = thisThreadsIsolatedApplication(),
89
+ config: Record<string, any> | undefined = getConfigObj()
90
+ ): boolean {
91
+ if (owner !== undefined) return appName === owner;
92
+ return !isIsolatedApplication(appName, config);
93
+ }
94
+
95
+ /**
96
+ * Whether this thread starts the uWS listener for one `uwsServeConfigs` entry (see listenOnPorts()).
97
+ * A dedicated worker starts only its own per-application UDS mirrors -- the entries that carry a
98
+ * `socketPath` -- and never a global port listener, which the kernel would hand connections for every
99
+ * other application.
100
+ */
101
+ export function shouldStartUwsListenerHere(
102
+ serveConfig: { socketPath?: string },
103
+ owner: string | undefined = thisThreadsIsolatedApplication()
104
+ ): boolean {
105
+ return owner === undefined || Boolean(serveConfig.socketPath);
106
+ }
107
+
108
+ /**
109
+ * Whether a dedicated worker can be reached at all: it binds only UDS mirrors of the secure port, so
110
+ * without `tls.unixDomainSockets` and a secure port an isolated application would load and answer
111
+ * nothing, silently. Refused at admission instead. Returns the reason, or undefined when reachable.
112
+ */
113
+ export function isolatedApplicationsUnreachableReason(platform = process.platform): string | undefined {
114
+ if (platform === 'win32') return 'Windows does not support per-application UDS mirrors';
115
+ // the main thread standing in as the only worker (threads.count: 0) has no thread to give
116
+ if (isMainThread && getWorkerIndex() === 0) return 'threads.count is 0, so there is no worker thread to dedicate';
117
+ if (!env.get(CONFIG_PARAMS.HTTP_SECUREPORT)) return 'no http.securePort is configured';
118
+ if (!env.get(CONFIG_PARAMS.TLS_UNIXDOMAINSOCKETS)) return 'tls.unixDomainSockets is not enabled';
119
+ return undefined;
120
+ }
121
+
122
+ /** Why `appName` cannot get a dedicated worker on this instance, or undefined when it can. */
123
+ export function isolatedApplicationRefusal(appName: string): string | undefined {
124
+ const unreachable = isolatedApplicationsUnreachableReason();
125
+ if (unreachable) return `its worker would be unreachable: ${unreachable}`;
126
+ const socketPath = join(
127
+ env.getHdbBasePath(),
128
+ 'sockets',
129
+ `${applicationSocketName(appName, env.get(CONFIG_PARAMS.HTTP_SECUREPORT))}.sock`
130
+ );
131
+ if (isDomainSocketPathTooLong(socketPath)) return `its UDS mirror path ${socketPath} exceeds the platform limit`;
132
+ return undefined;
133
+ }
134
+
135
+ /**
136
+ * The filesystem name of an isolated worker's UDS mirror for `port`: every UTF-8 byte outside
137
+ * `[A-Za-z0-9._]` becomes a fixed-width `%XX`, which is injective (two application names never share
138
+ * a socket), and the `app-` prefix keeps it apart from the pool's `<workerIndex>-<port>` names.
139
+ */
140
+ export function applicationSocketName(appName: string, port: number | string): string {
141
+ let encoded = '';
142
+ for (const byte of Buffer.from(appName, 'utf8')) {
143
+ const c = String.fromCharCode(byte);
144
+ encoded += byte < 0x80 && /[A-Za-z0-9._]/.test(c) ? c : `%${byte.toString(16).toUpperCase().padStart(2, '0')}`;
145
+ }
146
+ return `app-${encoded}-${port}`;
147
+ }
148
+
149
+ /** What a dedicated worker publishes in its mirror metadata for the proxy to route by. */
150
+ export function isolatedApplicationRoute(
151
+ appName: string | undefined = thisThreadsIsolatedApplication(),
152
+ config: Record<string, any> | undefined = getConfigObj()
153
+ ): { application: string; hosts: string[] } | undefined {
154
+ if (!appName) return undefined;
155
+ const host = config?.[appName]?.host; // a string, as the mount parser requires
156
+ return { application: appName, hosts: typeof host === 'string' && host ? [host] : [] };
157
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ // Hands the log-rotation coordinator the thread mesh it needs, from this side of the dependency
4
+ // edge: manageThreads already imports harper_logger, so logging can never import manageThreads.
5
+
6
+ import { threadId } from 'node:worker_threads';
7
+ import { setRotationTransport } from '../../utility/logging/logGenerationCoordinator.ts';
8
+ import { broadcast, onMessageByType, onThreadExit } from './manageThreads.js';
9
+
10
+ // Assigned to the `threads` global by manageThreads.
11
+ declare const threads: {
12
+ sendToThread(threadId: number, message: any): boolean;
13
+ [index: number]: { threadId?: number };
14
+ length: number;
15
+ };
16
+
17
+ setRotationTransport({
18
+ threadId,
19
+ broadcast(message) {
20
+ broadcast(message);
21
+ },
22
+ sendToThread(target, message) {
23
+ threads.sendToThread(target, message);
24
+ },
25
+ onMessage(type, handler) {
26
+ onMessageByType(type, handler);
27
+ },
28
+ onThreadExit,
29
+ peerThreadIds() {
30
+ // Job workers included: they hold log descriptors whether or not they take part in ITC gossip,
31
+ // and the deadlock that excludes them from the schema broadcast cannot happen here — the
32
+ // handler only closes a descriptor, and no caller blocks its event loop on the answer.
33
+ const ids = new Set<number>();
34
+ for (let i = 0; i < threads.length; i++) {
35
+ const peer = threads[i].threadId;
36
+ if (peer !== undefined && peer !== threadId) ids.add(peer);
37
+ }
38
+ return ids;
39
+ },
40
+ });