@harperfast/harper 5.2.10 → 5.3.0-alpha.1

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 (554) hide show
  1. package/agent/mcpTools.ts +1 -1
  2. package/agent/session.ts +25 -14
  3. package/bin/cliOperations.ts +46 -9
  4. package/bin/copyDb.ts +282 -59
  5. package/bin/deploySetup.ts +16 -5
  6. package/bin/harper.ts +1 -1
  7. package/bin/help.ts +4 -1
  8. package/bin/lite.ts +4 -1
  9. package/bin/restart.ts +59 -3
  10. package/bin/run.ts +6 -11
  11. package/bin/upgrade.js +7 -3
  12. package/bin/workloadIdentity.ts +119 -0
  13. package/components/Application.ts +2414 -242
  14. package/components/ApplicationScope.ts +8 -0
  15. package/components/EntryHandler.ts +59 -39
  16. package/components/OptionsWatcher.ts +150 -74
  17. package/components/RuntimeModuleTracker.ts +38 -7
  18. package/components/Scope.ts +37 -15
  19. package/components/awaitRestart.ts +84 -0
  20. package/components/componentLoader.ts +340 -30
  21. package/components/componentPreparationLock.ts +16 -5
  22. package/components/mcp/adapters/harperHttp.ts +4 -0
  23. package/components/mcp/listChanged.ts +4 -0
  24. package/components/mcp/toolRegistry.ts +2 -0
  25. package/components/mcp/tools/operations.ts +9 -0
  26. package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
  27. package/components/operations.js +270 -78
  28. package/components/operationsValidation.js +49 -1
  29. package/components/status/ComponentStatusRegistry.ts +59 -0
  30. package/config/RootConfigWatcher.ts +80 -34
  31. package/config/configUtils.ts +291 -34
  32. package/config/harperConfigEnvVars.ts +170 -27
  33. package/config-root.schema.json +29 -0
  34. package/dataLayer/blobBackup.ts +160 -50
  35. package/dataLayer/delete.ts +6 -1
  36. package/dataLayer/harperBridge/ResourceBridge.ts +52 -8
  37. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  38. package/dataLayer/hdbInfoController.ts +34 -1
  39. package/dataLayer/insert.ts +44 -1
  40. package/dataLayer/rocksdbBackup.ts +53 -10
  41. package/dataLayer/schema.ts +11 -1
  42. package/dataLayer/schemaDescribe.ts +8 -1
  43. package/dist/agent/mcpTools.js +1 -1
  44. package/dist/agent/mcpTools.js.map +1 -1
  45. package/dist/agent/session.d.ts +22 -0
  46. package/dist/agent/session.js +26 -15
  47. package/dist/agent/session.js.map +1 -1
  48. package/dist/bin/cliOperations.js +48 -9
  49. package/dist/bin/cliOperations.js.map +1 -1
  50. package/dist/bin/copyDb.d.ts +12 -1
  51. package/dist/bin/copyDb.js +248 -60
  52. package/dist/bin/copyDb.js.map +1 -1
  53. package/dist/bin/deploySetup.d.ts +2 -0
  54. package/dist/bin/deploySetup.js +11 -3
  55. package/dist/bin/deploySetup.js.map +1 -1
  56. package/dist/bin/harper.js +1 -1
  57. package/dist/bin/harper.js.map +1 -1
  58. package/dist/bin/help.js +4 -1
  59. package/dist/bin/help.js.map +1 -1
  60. package/dist/bin/lite.js +4 -1
  61. package/dist/bin/lite.js.map +1 -1
  62. package/dist/bin/restart.js +54 -5
  63. package/dist/bin/restart.js.map +1 -1
  64. package/dist/bin/run.js +4 -10
  65. package/dist/bin/run.js.map +1 -1
  66. package/dist/bin/upgrade.js +4 -3
  67. package/dist/bin/upgrade.js.map +1 -1
  68. package/dist/bin/workloadIdentity.d.ts +18 -0
  69. package/dist/bin/workloadIdentity.js +100 -0
  70. package/dist/bin/workloadIdentity.js.map +1 -0
  71. package/dist/components/Application.d.ts +139 -16
  72. package/dist/components/Application.js +2215 -267
  73. package/dist/components/Application.js.map +1 -1
  74. package/dist/components/ApplicationScope.d.ts +8 -0
  75. package/dist/components/ApplicationScope.js +7 -0
  76. package/dist/components/ApplicationScope.js.map +1 -1
  77. package/dist/components/EntryHandler.js +26 -10
  78. package/dist/components/EntryHandler.js.map +1 -1
  79. package/dist/components/OptionsWatcher.d.ts +1 -0
  80. package/dist/components/OptionsWatcher.js +141 -74
  81. package/dist/components/OptionsWatcher.js.map +1 -1
  82. package/dist/components/RuntimeModuleTracker.js +40 -6
  83. package/dist/components/RuntimeModuleTracker.js.map +1 -1
  84. package/dist/components/Scope.js +38 -13
  85. package/dist/components/Scope.js.map +1 -1
  86. package/dist/components/awaitRestart.d.ts +33 -0
  87. package/dist/components/awaitRestart.js +61 -0
  88. package/dist/components/awaitRestart.js.map +1 -0
  89. package/dist/components/componentLoader.d.ts +38 -1
  90. package/dist/components/componentLoader.js +279 -22
  91. package/dist/components/componentLoader.js.map +1 -1
  92. package/dist/components/componentPreparationLock.d.ts +5 -0
  93. package/dist/components/componentPreparationLock.js +14 -6
  94. package/dist/components/componentPreparationLock.js.map +1 -1
  95. package/dist/components/mcp/adapters/harperHttp.js +4 -0
  96. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  97. package/dist/components/mcp/listChanged.js +5 -0
  98. package/dist/components/mcp/listChanged.js.map +1 -1
  99. package/dist/components/mcp/toolRegistry.d.ts +1 -0
  100. package/dist/components/mcp/toolRegistry.js.map +1 -1
  101. package/dist/components/mcp/tools/operations.d.ts +5 -0
  102. package/dist/components/mcp/tools/operations.js +9 -0
  103. package/dist/components/mcp/tools/operations.js.map +1 -1
  104. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
  105. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  106. package/dist/components/operations.js +231 -77
  107. package/dist/components/operations.js.map +1 -1
  108. package/dist/components/operationsValidation.js +49 -1
  109. package/dist/components/operationsValidation.js.map +1 -1
  110. package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
  111. package/dist/components/status/ComponentStatusRegistry.js +63 -0
  112. package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
  113. package/dist/config/RootConfigWatcher.d.ts +7 -1
  114. package/dist/config/RootConfigWatcher.js +64 -27
  115. package/dist/config/RootConfigWatcher.js.map +1 -1
  116. package/dist/config/configUtils.d.ts +9 -1
  117. package/dist/config/configUtils.js +254 -33
  118. package/dist/config/configUtils.js.map +1 -1
  119. package/dist/config/harperConfigEnvVars.d.ts +16 -0
  120. package/dist/config/harperConfigEnvVars.js +162 -25
  121. package/dist/config/harperConfigEnvVars.js.map +1 -1
  122. package/dist/dataLayer/blobBackup.d.ts +49 -20
  123. package/dist/dataLayer/blobBackup.js +139 -50
  124. package/dist/dataLayer/blobBackup.js.map +1 -1
  125. package/dist/dataLayer/delete.js +1 -1
  126. package/dist/dataLayer/delete.js.map +1 -1
  127. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
  128. package/dist/dataLayer/harperBridge/ResourceBridge.js +51 -10
  129. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  130. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
  131. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  132. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
  133. package/dist/dataLayer/hdbInfoController.d.ts +10 -0
  134. package/dist/dataLayer/hdbInfoController.js +30 -1
  135. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  136. package/dist/dataLayer/insert.d.ts +9 -1
  137. package/dist/dataLayer/insert.js +30 -0
  138. package/dist/dataLayer/insert.js.map +1 -1
  139. package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
  140. package/dist/dataLayer/rocksdbBackup.js +45 -8
  141. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  142. package/dist/dataLayer/schema.js +8 -0
  143. package/dist/dataLayer/schema.js.map +1 -1
  144. package/dist/dataLayer/schemaDescribe.js +8 -1
  145. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  146. package/dist/index.d.ts +1 -1
  147. package/dist/index.js.map +1 -1
  148. package/dist/json/systemSchema.json +52 -0
  149. package/dist/resources/DatabaseTransaction.d.ts +55 -3
  150. package/dist/resources/DatabaseTransaction.js +460 -134
  151. package/dist/resources/DatabaseTransaction.js.map +1 -1
  152. package/dist/resources/LMDBTransaction.js +22 -4
  153. package/dist/resources/LMDBTransaction.js.map +1 -1
  154. package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
  155. package/dist/resources/PrimaryRocksDatabase.js +30 -2
  156. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  157. package/dist/resources/RecordEncoder.d.ts +20 -0
  158. package/dist/resources/RecordEncoder.js +94 -9
  159. package/dist/resources/RecordEncoder.js.map +1 -1
  160. package/dist/resources/RequestTarget.d.ts +2 -0
  161. package/dist/resources/RequestTarget.js.map +1 -1
  162. package/dist/resources/Resource.js +20 -11
  163. package/dist/resources/Resource.js.map +1 -1
  164. package/dist/resources/ResourceInterface.d.ts +20 -1
  165. package/dist/resources/ResourceInterface.js.map +1 -1
  166. package/dist/resources/RocksIndexStore.d.ts +6 -1
  167. package/dist/resources/RocksIndexStore.js +24 -10
  168. package/dist/resources/RocksIndexStore.js.map +1 -1
  169. package/dist/resources/RocksTransactionLogStore.d.ts +14 -1
  170. package/dist/resources/RocksTransactionLogStore.js +57 -17
  171. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  172. package/dist/resources/Table.d.ts +112 -8
  173. package/dist/resources/Table.js +1071 -197
  174. package/dist/resources/Table.js.map +1 -1
  175. package/dist/resources/auditStore.d.ts +11 -2
  176. package/dist/resources/auditStore.js +183 -18
  177. package/dist/resources/auditStore.js.map +1 -1
  178. package/dist/resources/blob.d.ts +129 -9
  179. package/dist/resources/blob.js +992 -114
  180. package/dist/resources/blob.js.map +1 -1
  181. package/dist/resources/branchDatabase.d.ts +48 -0
  182. package/dist/resources/branchDatabase.js +892 -0
  183. package/dist/resources/branchDatabase.js.map +1 -0
  184. package/dist/resources/crdt.js +50 -12
  185. package/dist/resources/crdt.js.map +1 -1
  186. package/dist/resources/databases.d.ts +150 -9
  187. package/dist/resources/databases.js +1247 -525
  188. package/dist/resources/databases.js.map +1 -1
  189. package/dist/resources/defineTable.d.ts +10 -2
  190. package/dist/resources/defineTable.js +9 -1
  191. package/dist/resources/defineTable.js.map +1 -1
  192. package/dist/resources/graphql.d.ts +1 -1
  193. package/dist/resources/graphql.js +50 -15
  194. package/dist/resources/graphql.js.map +1 -1
  195. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +1 -0
  196. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +6 -2
  197. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  198. package/dist/resources/longLivedTransactions.d.ts +71 -0
  199. package/dist/resources/longLivedTransactions.js +358 -0
  200. package/dist/resources/longLivedTransactions.js.map +1 -0
  201. package/dist/resources/models/backendRegistry.d.ts +26 -0
  202. package/dist/resources/models/backendRegistry.js +60 -2
  203. package/dist/resources/models/backendRegistry.js.map +1 -1
  204. package/dist/resources/models/bootstrap.d.ts +33 -1
  205. package/dist/resources/models/bootstrap.js +416 -31
  206. package/dist/resources/models/bootstrap.js.map +1 -1
  207. package/dist/resources/recordLock.d.ts +80 -0
  208. package/dist/resources/recordLock.js +184 -0
  209. package/dist/resources/recordLock.js.map +1 -0
  210. package/dist/resources/replayLogs.d.ts +14 -1
  211. package/dist/resources/replayLogs.js +152 -24
  212. package/dist/resources/replayLogs.js.map +1 -1
  213. package/dist/resources/replayLogsGuards.d.ts +94 -7
  214. package/dist/resources/replayLogsGuards.js +111 -7
  215. package/dist/resources/replayLogsGuards.js.map +1 -1
  216. package/dist/resources/search.js +158 -32
  217. package/dist/resources/search.js.map +1 -1
  218. package/dist/resources/transactionBroadcast.d.ts +1 -1
  219. package/dist/resources/transactionBroadcast.js +2 -2
  220. package/dist/resources/transactionBroadcast.js.map +1 -1
  221. package/dist/security/auth.js +61 -30
  222. package/dist/security/auth.js.map +1 -1
  223. package/dist/security/authn/oidc/claims.d.ts +22 -0
  224. package/dist/security/authn/oidc/claims.js +71 -0
  225. package/dist/security/authn/oidc/claims.js.map +1 -0
  226. package/dist/security/authn/oidc/identityToken.d.ts +27 -0
  227. package/dist/security/authn/oidc/identityToken.js +111 -0
  228. package/dist/security/authn/oidc/identityToken.js.map +1 -0
  229. package/dist/security/authn/oidc/jwks.d.ts +25 -0
  230. package/dist/security/authn/oidc/jwks.js +261 -0
  231. package/dist/security/authn/oidc/jwks.js.map +1 -0
  232. package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
  233. package/dist/security/authn/oidc/providers/generic.js +34 -0
  234. package/dist/security/authn/oidc/providers/generic.js.map +1 -0
  235. package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
  236. package/dist/security/authn/oidc/providers/githubActions.js +129 -0
  237. package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
  238. package/dist/security/authn/oidc/providers/index.d.ts +37 -0
  239. package/dist/security/authn/oidc/providers/index.js +24 -0
  240. package/dist/security/authn/oidc/providers/index.js.map +1 -0
  241. package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
  242. package/dist/security/authn/oidc/tokenExchange.js +306 -0
  243. package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
  244. package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
  245. package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
  246. package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
  247. package/dist/security/authn/oidc/types.d.ts +38 -0
  248. package/dist/security/authn/oidc/types.js +6 -0
  249. package/dist/security/authn/oidc/types.js.map +1 -0
  250. package/dist/security/certificateVerification/index.js +40 -11
  251. package/dist/security/certificateVerification/index.js.map +1 -1
  252. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  253. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  254. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  255. package/dist/security/certificateVerification/types.d.ts +1 -0
  256. package/dist/security/credentialProvenance.d.ts +35 -0
  257. package/dist/security/credentialProvenance.js +51 -0
  258. package/dist/security/credentialProvenance.js.map +1 -0
  259. package/dist/security/credentialRejection.d.ts +4 -0
  260. package/dist/security/credentialRejection.js +24 -0
  261. package/dist/security/credentialRejection.js.map +1 -0
  262. package/dist/security/deferredAuthentication.d.ts +36 -0
  263. package/dist/security/deferredAuthentication.js +70 -0
  264. package/dist/security/deferredAuthentication.js.map +1 -0
  265. package/dist/security/impersonation.d.ts +21 -0
  266. package/dist/security/impersonation.js +108 -9
  267. package/dist/security/impersonation.js.map +1 -1
  268. package/dist/security/jsLoader.d.ts +6 -0
  269. package/dist/security/jsLoader.js +75 -15
  270. package/dist/security/jsLoader.js.map +1 -1
  271. package/dist/security/keys.js +301 -71
  272. package/dist/security/keys.js.map +1 -1
  273. package/dist/security/operationScope.d.ts +21 -0
  274. package/dist/security/operationScope.js +36 -0
  275. package/dist/security/operationScope.js.map +1 -0
  276. package/dist/security/permissionsTranslator.js +21 -0
  277. package/dist/security/permissionsTranslator.js.map +1 -1
  278. package/dist/security/tokenAuthentication.d.ts +19 -1
  279. package/dist/security/tokenAuthentication.js +191 -10
  280. package/dist/security/tokenAuthentication.js.map +1 -1
  281. package/dist/security/user.js +4 -3
  282. package/dist/security/user.js.map +1 -1
  283. package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
  284. package/dist/server/DurableSubscriptionsSession.js +67 -10
  285. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  286. package/dist/server/REST.js +73 -0
  287. package/dist/server/REST.js.map +1 -1
  288. package/dist/server/graphqlQuerying.js +4 -0
  289. package/dist/server/graphqlQuerying.js.map +1 -1
  290. package/dist/server/http.d.ts +11 -0
  291. package/dist/server/http.js +88 -15
  292. package/dist/server/http.js.map +1 -1
  293. package/dist/server/itc/serverHandlers.js +8 -1
  294. package/dist/server/itc/serverHandlers.js.map +1 -1
  295. package/dist/server/jobs/jobProcess.js +6 -2
  296. package/dist/server/jobs/jobProcess.js.map +1 -1
  297. package/dist/server/jobs/jobs.js +4 -1
  298. package/dist/server/jobs/jobs.js.map +1 -1
  299. package/dist/server/liveSubscriptionAuth.d.ts +26 -4
  300. package/dist/server/liveSubscriptionAuth.js +105 -39
  301. package/dist/server/liveSubscriptionAuth.js.map +1 -1
  302. package/dist/server/loadRootComponents.js +49 -10
  303. package/dist/server/loadRootComponents.js.map +1 -1
  304. package/dist/server/mqtt.d.ts +2 -0
  305. package/dist/server/mqtt.js +165 -30
  306. package/dist/server/mqtt.js.map +1 -1
  307. package/dist/server/nodeName.d.ts +2 -0
  308. package/dist/server/nodeName.js +107 -23
  309. package/dist/server/nodeName.js.map +1 -1
  310. package/dist/server/serverHelpers/Headers.d.ts +25 -0
  311. package/dist/server/serverHelpers/Headers.js +139 -1
  312. package/dist/server/serverHelpers/Headers.js.map +1 -1
  313. package/dist/server/serverHelpers/contentTypes.d.ts +9 -0
  314. package/dist/server/serverHelpers/contentTypes.js +32 -25
  315. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  316. package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
  317. package/dist/server/serverHelpers/deployValidationState.js +9 -19
  318. package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
  319. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
  320. package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
  321. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
  322. package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
  323. package/dist/server/serverHelpers/registeredOperations.js +74 -21
  324. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  325. package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
  326. package/dist/server/serverHelpers/requestSanitization.js +20 -0
  327. package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
  328. package/dist/server/serverHelpers/serverHandlers.js +6 -3
  329. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  330. package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
  331. package/dist/server/serverHelpers/serverUtilities.js +90 -20
  332. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  333. package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
  334. package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
  335. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
  336. package/dist/server/serverHelpers/uwsServer.js +19 -1
  337. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  338. package/dist/server/static.js +24 -28
  339. package/dist/server/static.js.map +1 -1
  340. package/dist/server/storageReclamation.d.ts +5 -0
  341. package/dist/server/storageReclamation.js +16 -0
  342. package/dist/server/storageReclamation.js.map +1 -1
  343. package/dist/server/threads/itc.d.ts +7 -2
  344. package/dist/server/threads/itc.js +5 -1
  345. package/dist/server/threads/itc.js.map +1 -1
  346. package/dist/server/threads/manageThreads.d.ts +26 -4
  347. package/dist/server/threads/manageThreads.js +517 -72
  348. package/dist/server/threads/manageThreads.js.map +1 -1
  349. package/dist/server/threads/socketRouter.js +89 -26
  350. package/dist/server/threads/socketRouter.js.map +1 -1
  351. package/dist/server/threads/threadHeapMemory.d.ts +2 -0
  352. package/dist/server/threads/threadHeapMemory.js +31 -0
  353. package/dist/server/threads/threadHeapMemory.js.map +1 -0
  354. package/dist/server/threads/threadServer.js +46 -15
  355. package/dist/server/threads/threadServer.js.map +1 -1
  356. package/dist/sqlEngine/config.d.ts +1 -3
  357. package/dist/sqlEngine/config.js +19 -16
  358. package/dist/sqlEngine/config.js.map +1 -1
  359. package/dist/sqlTranslator/index.d.ts +1 -1
  360. package/dist/sqlTranslator/index.js +30 -7
  361. package/dist/sqlTranslator/index.js.map +1 -1
  362. package/dist/upgrade/directives/5-3-0.d.ts +7 -0
  363. package/dist/upgrade/directives/5-3-0.js +148 -0
  364. package/dist/upgrade/directives/5-3-0.js.map +1 -0
  365. package/dist/upgrade/directives/directivesController.js +2 -1
  366. package/dist/upgrade/directives/directivesController.js.map +1 -1
  367. package/dist/utility/OperationFunctionCaller.js +2 -1
  368. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  369. package/dist/utility/common_utils.d.ts +16 -0
  370. package/dist/utility/common_utils.js +32 -6
  371. package/dist/utility/common_utils.js.map +1 -1
  372. package/dist/utility/componentNames.d.ts +8 -0
  373. package/dist/utility/componentNames.js +12 -1
  374. package/dist/utility/componentNames.js.map +1 -1
  375. package/dist/utility/environment/environmentManager.js +3 -6
  376. package/dist/utility/environment/environmentManager.js.map +1 -1
  377. package/dist/utility/environment/systemInformation.d.ts +1 -0
  378. package/dist/utility/environment/systemInformation.js +1 -0
  379. package/dist/utility/environment/systemInformation.js.map +1 -1
  380. package/dist/utility/errors/commonErrors.d.ts +2 -0
  381. package/dist/utility/errors/commonErrors.js +2 -0
  382. package/dist/utility/errors/commonErrors.js.map +1 -1
  383. package/dist/utility/errors/hdbError.d.ts +17 -0
  384. package/dist/utility/errors/hdbError.js +30 -1
  385. package/dist/utility/errors/hdbError.js.map +1 -1
  386. package/dist/utility/globalSchema.d.ts +18 -0
  387. package/dist/utility/hdbTerms.d.ts +16 -0
  388. package/dist/utility/hdbTerms.js +18 -2
  389. package/dist/utility/hdbTerms.js.map +1 -1
  390. package/dist/utility/logging/harper_logger.d.ts +2 -0
  391. package/dist/utility/logging/harper_logger.js +91 -14
  392. package/dist/utility/logging/harper_logger.js.map +1 -1
  393. package/dist/utility/logging/logRotator.js +76 -49
  394. package/dist/utility/logging/logRotator.js.map +1 -1
  395. package/dist/utility/nodeIdentity.d.ts +9 -0
  396. package/dist/utility/nodeIdentity.js +58 -0
  397. package/dist/utility/nodeIdentity.js.map +1 -0
  398. package/dist/utility/npmUtilities.js +9 -7
  399. package/dist/utility/npmUtilities.js.map +1 -1
  400. package/dist/utility/operationPermissions.d.ts +3 -1
  401. package/dist/utility/operationPermissions.js +16 -1
  402. package/dist/utility/operationPermissions.js.map +1 -1
  403. package/dist/utility/operation_authorization.d.ts +10 -7
  404. package/dist/utility/operation_authorization.js +212 -41
  405. package/dist/utility/operation_authorization.js.map +1 -1
  406. package/dist/utility/watchPath.d.ts +29 -0
  407. package/dist/utility/watchPath.js +68 -0
  408. package/dist/utility/watchPath.js.map +1 -0
  409. package/dist/utility/watcherFallback.d.ts +86 -0
  410. package/dist/utility/watcherFallback.js +278 -1
  411. package/dist/utility/watcherFallback.js.map +1 -1
  412. package/dist/validation/configValidator.d.ts +12 -0
  413. package/dist/validation/configValidator.js +199 -72
  414. package/dist/validation/configValidator.js.map +1 -1
  415. package/dist/validation/installValidator.js +12 -0
  416. package/dist/validation/installValidator.js.map +1 -1
  417. package/dist/validation/validationWrapper.d.ts +11 -0
  418. package/dist/validation/validationWrapper.js +16 -3
  419. package/dist/validation/validationWrapper.js.map +1 -1
  420. package/index.ts +2 -0
  421. package/json/systemSchema.json +52 -0
  422. package/npm-shrinkwrap.json +235 -233
  423. package/package.json +8 -7
  424. package/resources/DESIGN.md +114 -52
  425. package/resources/DatabaseTransaction.ts +522 -130
  426. package/resources/LMDBTransaction.ts +26 -4
  427. package/resources/PrimaryRocksDatabase.ts +29 -3
  428. package/resources/RecordEncoder.ts +103 -8
  429. package/resources/RequestTarget.ts +2 -0
  430. package/resources/Resource.ts +17 -9
  431. package/resources/ResourceInterface.ts +23 -0
  432. package/resources/RocksIndexStore.ts +30 -10
  433. package/resources/RocksTransactionLogStore.ts +78 -21
  434. package/resources/Table.ts +1107 -164
  435. package/resources/auditStore.ts +187 -21
  436. package/resources/blob.ts +1029 -110
  437. package/resources/branchDatabase.ts +941 -0
  438. package/resources/crdt.ts +76 -12
  439. package/resources/databases.ts +1354 -522
  440. package/resources/defineTable.ts +18 -2
  441. package/resources/graphql.ts +70 -16
  442. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +6 -2
  443. package/resources/longLivedTransactions.ts +360 -0
  444. package/resources/models/backendRegistry.ts +84 -2
  445. package/resources/models/bootstrap.ts +473 -28
  446. package/resources/recordLock.ts +253 -0
  447. package/resources/replayLogs.ts +147 -26
  448. package/resources/replayLogsGuards.ts +171 -8
  449. package/resources/search.ts +154 -32
  450. package/resources/transactionBroadcast.ts +3 -3
  451. package/security/auth.ts +68 -29
  452. package/security/authn/oidc/claims.ts +72 -0
  453. package/security/authn/oidc/identityToken.ts +129 -0
  454. package/security/authn/oidc/jwks.ts +260 -0
  455. package/security/authn/oidc/providers/generic.ts +40 -0
  456. package/security/authn/oidc/providers/githubActions.ts +137 -0
  457. package/security/authn/oidc/providers/index.ts +52 -0
  458. package/security/authn/oidc/tokenExchange.ts +300 -0
  459. package/security/authn/oidc/trustPolicyOperations.ts +343 -0
  460. package/security/authn/oidc/types.ts +41 -0
  461. package/security/certificateVerification/index.ts +54 -13
  462. package/security/certificateVerification/trustedIssuers.ts +76 -0
  463. package/security/certificateVerification/types.ts +1 -0
  464. package/security/credentialProvenance.ts +47 -0
  465. package/security/credentialRejection.ts +22 -0
  466. package/security/deferredAuthentication.ts +71 -0
  467. package/security/impersonation.ts +117 -12
  468. package/security/jsLoader.ts +81 -18
  469. package/security/keys.ts +298 -72
  470. package/security/operationScope.ts +33 -0
  471. package/security/permissionsTranslator.js +23 -0
  472. package/security/tokenAuthentication.ts +233 -12
  473. package/security/user.ts +4 -3
  474. package/server/DESIGN.md +183 -16
  475. package/server/DurableSubscriptionsSession.ts +71 -11
  476. package/server/REST.ts +79 -1
  477. package/server/graphqlQuerying.ts +4 -0
  478. package/server/http.ts +99 -18
  479. package/server/itc/serverHandlers.js +8 -1
  480. package/server/jobs/jobProcess.ts +8 -2
  481. package/server/jobs/jobs.ts +4 -1
  482. package/server/liveSubscriptionAuth.ts +129 -46
  483. package/server/loadRootComponents.js +50 -8
  484. package/server/mqtt.ts +179 -38
  485. package/server/nodeName.ts +103 -21
  486. package/server/serverHelpers/Headers.ts +131 -0
  487. package/server/serverHelpers/contentTypes.ts +29 -21
  488. package/server/serverHelpers/deployValidationState.ts +24 -13
  489. package/server/serverHelpers/operationAuthorizationState.ts +34 -3
  490. package/server/serverHelpers/registeredOperations.ts +79 -22
  491. package/server/serverHelpers/requestSanitization.ts +15 -0
  492. package/server/serverHelpers/serverHandlers.js +6 -3
  493. package/server/serverHelpers/serverUtilities.ts +104 -31
  494. package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
  495. package/server/serverHelpers/uwsServer.ts +17 -2
  496. package/server/static.ts +23 -29
  497. package/server/storageReclamation.ts +13 -0
  498. package/server/threads/itc.js +11 -1
  499. package/server/threads/manageThreads.js +526 -63
  500. package/server/threads/socketRouter.ts +81 -26
  501. package/server/threads/threadHeapMemory.ts +26 -0
  502. package/server/threads/threadServer.js +43 -15
  503. package/sqlTranslator/index.ts +31 -8
  504. package/studio/web/assets/{Chat-Br06zdMA.js → Chat-BnCBegQz.js} +1 -1
  505. package/studio/web/assets/{FloatingChat-BWImX5fA.js → FloatingChat-CoDW1ySS.js} +3 -3
  506. package/studio/web/assets/{apiToken-DN0nmDsq.js → apiToken-Bwk5BLXW.js} +1 -1
  507. package/studio/web/assets/{applications-kSxVoyeU.js → applications-DHxGi7JH.js} +1 -1
  508. package/studio/web/assets/{cssMode-C1JeufH5.js → cssMode-s0cWI-_M.js} +1 -1
  509. package/studio/web/assets/{editor-qoo9CrGO.js → editor-DNcRHK54.js} +1 -1
  510. package/studio/web/assets/{html-Dt4IIy04.js → html-Bdssedlg.js} +1 -1
  511. package/studio/web/assets/{htmlMode-DXgKKr4C.js → htmlMode-CoDlJ3fw.js} +1 -1
  512. package/studio/web/assets/{index-6onkYFOG.js → index-D6sxmFLR.js} +5 -5
  513. package/studio/web/assets/{index.lazy-BrCFnpNJ.js → index.lazy-tmU5BS8s.js} +1 -1
  514. package/studio/web/assets/{javascript-DNCQGUBc.js → javascript-B8meVSTH.js} +1 -1
  515. package/studio/web/assets/{jsonMode-CR6HWruP.js → jsonMode-DpIPd35T.js} +1 -1
  516. package/studio/web/assets/{languageServices-BM4fI4rS.js → languageServices-C_5FMJzQ.js} +1 -1
  517. package/studio/web/assets/{lspLanguageFeatures-DSa1ttcD.js → lspLanguageFeatures-BIzNBzPK.js} +1 -1
  518. package/studio/web/assets/{notifications-BHXLnh6x.js → notifications-CQf18QKb.js} +1 -1
  519. package/studio/web/assets/{notifications-CUmtIA6z.js → notifications-CvZivSbh.js} +1 -1
  520. package/studio/web/assets/{profile-8BeFSF3j.js → profile-DdOwtntb.js} +1 -1
  521. package/studio/web/assets/{regions-C8qR0HhD.js → regions-n69fwagr.js} +1 -1
  522. package/studio/web/assets/{register-B4n5i0SD.js → register-PfWTCXWB.js} +2 -2
  523. package/studio/web/assets/{setComponentFile-g0_B0lgX.js → setComponentFile-Bg6O7X0S.js} +1 -1
  524. package/studio/web/assets/{setup-B0CTj_Q5.js → setup-CUx_aUDl.js} +2 -2
  525. package/studio/web/assets/{status-C6Yib7-K.js → status-D7BVKqX9.js} +1 -1
  526. package/studio/web/assets/{toggleHighContrast-Dgta7bVi.js → toggleHighContrast-DBSyXzMr.js} +1 -1
  527. package/studio/web/assets/{tsMode-CH_jHvU-.js → tsMode-BByKCjBS.js} +1 -1
  528. package/studio/web/assets/{typescript-Co9LCXd5.js → typescript-DDLnLpw9.js} +1 -1
  529. package/studio/web/assets/{useEntityRestURL-wKC8NsC_.js → useEntityRestURL-31CHGaHk.js} +1 -1
  530. package/studio/web/assets/{workers-CWeLxCXA.js → workers-pR3jRY9D.js} +1 -1
  531. package/studio/web/assets/{xml-BadC-0Rk.js → xml-2iRnMhQO.js} +1 -1
  532. package/studio/web/assets/{yaml-BiUfxPbC.js → yaml-Bf92gJpd.js} +1 -1
  533. package/studio/web/index.html +1 -1
  534. package/upgrade/directives/5-3-0.ts +132 -0
  535. package/upgrade/directives/directivesController.ts +2 -1
  536. package/utility/OperationFunctionCaller.ts +2 -1
  537. package/utility/common_utils.ts +30 -5
  538. package/utility/componentNames.ts +12 -0
  539. package/utility/environment/environmentManager.ts +3 -7
  540. package/utility/environment/systemInformation.ts +7 -0
  541. package/utility/errors/commonErrors.ts +4 -0
  542. package/utility/errors/hdbError.ts +29 -0
  543. package/utility/hdbTerms.ts +17 -0
  544. package/utility/logging/harper_logger.ts +87 -14
  545. package/utility/logging/logRotator.ts +72 -45
  546. package/utility/nodeIdentity.ts +45 -0
  547. package/utility/npmUtilities.ts +10 -8
  548. package/utility/operationPermissions.ts +18 -1
  549. package/utility/operation_authorization.ts +231 -42
  550. package/utility/watchPath.ts +63 -0
  551. package/utility/watcherFallback.ts +270 -0
  552. package/validation/configValidator.ts +209 -72
  553. package/validation/installValidator.ts +15 -0
  554. package/validation/validationWrapper.ts +18 -4
@@ -5,11 +5,13 @@
5
5
  // events. Polling-based watching doesn't consume inotify handles or per-watcher
6
6
  // file descriptors, so we fall back to it once and warn — see harper#488.
7
7
 
8
+ import chokidar, { type ChokidarOptions, type FSWatcher } from 'chokidar';
8
9
  import { loggerWithTag } from './logging/harper_logger.ts';
9
10
 
10
11
  // One-time process-wide warning so a thundering herd of failing watchers doesn't
11
12
  // produce hundreds of identical log lines.
12
13
  let exhaustionWarned = false;
14
+ const partialReadWarned = new Set<string>();
13
15
 
14
16
  const fallbackLogger = loggerWithTag('watcher');
15
17
 
@@ -71,4 +73,272 @@ export function warnWatcherFallback(watchedPath: string): void {
71
73
  // Test-only hook to reset the one-time warning gate between cases.
72
74
  export function _resetForTests(): void {
73
75
  exhaustionWarned = false;
76
+ lostNativeWatchCount = 0;
77
+ lostNativeWatchWarnThreshold = 1;
78
+ // Also drop the process listener, so a case that installed the guard can't leave one
79
+ // attached to the test runner's process for every case that follows.
80
+ process.removeListener('uncaughtException', handleUncaughtException);
81
+ lostNativeWatchGuardInstalled = false;
82
+ }
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Lost native watch (predominantly Windows)
86
+ // ---------------------------------------------------------------------------
87
+ //
88
+ // Every Harper watcher runs `persistent: false`, which is the one chokidar
89
+ // branch that never attaches an 'error' listener to the underlying Node
90
+ // `fs.FSWatcher` (chokidar `handler.js`, `setFsWatchListener`; still so at
91
+ // 5.0.0). Its `errHandler` covers only a synchronous throw out of `fs.watch()`
92
+ // — the ENOSPC/EMFILE route above. An asynchronous failure is emitted on a
93
+ // handle with no listener, so Node makes it an uncaughtException without it
94
+ // ever reaching chokidar; `.on('error')` on the wrapper we hold cannot see it.
95
+ // The process is therefore the only place it is observable, which is what
96
+ // `installLostNativeWatchGuard()` uses.
97
+
98
+ /**
99
+ * Returns `true` for the asynchronous "the watched path went away" error raised
100
+ * by Node's `fs.FSWatcher` — on Windows, `EPERM: operation not permitted, watch`
101
+ * (errno -4048) when a watched directory is removed or swapped out. Unlike
102
+ * {@link isWatcherExhaustionError} there is nothing to degrade to: polling a
103
+ * path that no longer exists only burns CPU.
104
+ *
105
+ * All three conditions are load-bearing, because whatever this claims is
106
+ * swallowed process-wide:
107
+ *
108
+ * - `syscall === 'watch'` is only ever set by fs watch handles.
109
+ * - `EPERM` only. An async `ENOENT` from a watch handle has never been
110
+ * observed, whereas a *synchronous* one is the ordinary "watch a path that
111
+ * isn't there" misconfiguration, which must stay fatal.
112
+ * - no `path`. Node populates `path` on the error it throws out of
113
+ * `fs.watch()` and leaves it absent on the one it delivers to the handle,
114
+ * so without this a raw `fs.watch(missingPath)` reaching the guard would be
115
+ * mistaken for a benign lost watch and swallowed.
116
+ */
117
+ export function isLostNativeWatchError(error: unknown): boolean {
118
+ if (typeof error !== 'object' || error === null) return false;
119
+ const { code, syscall, path } = error as { code?: unknown; syscall?: unknown; path?: unknown };
120
+ return syscall === 'watch' && code === 'EPERM' && path == null;
121
+ }
122
+
123
+ let lostNativeWatchCount = 0;
124
+ let lostNativeWatchWarnThreshold = 1;
125
+
126
+ /**
127
+ * If `error` is a lost native watch error, mark it handled, log it, and report
128
+ * that it has been claimed. Exported so the per-watcher error routes can give it
129
+ * the same benign treatment on the day chokidar does deliver it to the wrapper
130
+ * (its polling and `persistent: true` branches already would).
131
+ */
132
+ export function claimLostNativeWatchError(error: unknown): boolean {
133
+ if (!isLostNativeWatchError(error)) return false;
134
+ const claimed = error as { isHandled?: boolean };
135
+ // Counting one instance twice would trip the warn threshold early.
136
+ if (claimed.isHandled) return true;
137
+ lostNativeWatchCount++;
138
+ // The claim is the classification above; the bookkeeping that follows must not be able to
139
+ // undo it. A frozen error (`isHandled` unassignable) or a throwing logger would otherwise
140
+ // make a failure just classified as benign fatal — here, or in a caller's 'error' route.
141
+ try {
142
+ fallbackLogger.trace?.(`Lost native file watch handle (occurrence ${lostNativeWatchCount}):`, error);
143
+ // A subsystem that has silently stopped being watched is what an operator needs to see, and
144
+ // the default level is `warn`, so trace alone would hide every occurrence after the first.
145
+ // Warning at each decade keeps a delete storm bounded without ever going fully silent.
146
+ if (lostNativeWatchCount >= lostNativeWatchWarnThreshold) {
147
+ lostNativeWatchWarnThreshold *= 10;
148
+ fallbackLogger.warn?.(
149
+ `A native file watch handle failed asynchronously (EPERM, syscall=watch) and was suppressed to ` +
150
+ `keep the thread alive; occurrence ${lostNativeWatchCount}. Node reports no path for this error, ` +
151
+ `so it cannot be attributed to a specific watcher — whatever was watching that path has stopped ` +
152
+ `reporting changes until it is re-established. On Windows this is usually a watched directory ` +
153
+ `being deleted or replaced (a component redeploy, a package install, a test teardown). If file ` +
154
+ `changes stop being picked up somewhere, this is why. Subsequent occurrences log at trace, with ` +
155
+ `a warning at each tenfold increase.`
156
+ );
157
+ }
158
+ // Last: threadServer.js and socketRouter.ts skip errors already marked handled, so failing
159
+ // to mark one costs only a duplicate log line.
160
+ claimed.isHandled = true;
161
+ } catch {
162
+ // Marked or not, logged or not, the error stays claimed.
163
+ }
164
+ return true;
165
+ }
166
+
167
+ let lostNativeWatchGuardInstalled = false;
168
+
169
+ function handleUncaughtException(error: unknown): void {
170
+ let claimed = false;
171
+ try {
172
+ claimed = claimLostNativeWatchError(error);
173
+ } catch {
174
+ // Classification is all that can still throw (a property getter that does), and a throw
175
+ // from inside an 'uncaughtException' listener would replace Node's report with it and
176
+ // exit 7. An error that cannot be classified is not ours to claim.
177
+ claimed = false;
178
+ }
179
+ if (claimed) return;
180
+ // Node suppresses its default fatal handling as soon as *any* 'uncaughtException' listener
181
+ // exists, so being the only listener would turn unrelated crashes into silent hangs.
182
+ if (process.listenerCount('uncaughtException') > 1) return;
183
+ process.removeListener('uncaughtException', handleUncaughtException);
184
+ // A process that somehow survives re-arms on its next guardedWatch() rather than running
185
+ // unguarded thereafter.
186
+ lostNativeWatchGuardInstalled = false;
187
+ // nextTick, not a throw from inside the handler: that keeps Node's report and exit code 1.
188
+ process.nextTick(() => {
189
+ throw error;
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Idempotently install the process-level listener that supplies the 'error'
195
+ * handler chokidar omits for non-persistent watchers. Called by
196
+ * {@link guardedWatch} so no watcher call site can forget it.
197
+ */
198
+ export function installLostNativeWatchGuard(): void {
199
+ if (lostNativeWatchGuardInstalled) return;
200
+ lostNativeWatchGuardInstalled = true;
201
+ // prepend, not append: threadServer.js already has an 'uncaughtException'
202
+ // listener, and the `isHandled` mark above only suppresses its log line if
203
+ // this guard runs first.
204
+ process.prependListener('uncaughtException', handleUncaughtException);
205
+ }
206
+
207
+ /**
208
+ * `chokidar.watch()` with the lost-native-watch guard installed. Every Harper
209
+ * chokidar watcher must go through this — a raw `chokidar.watch(..., { persistent: false })`
210
+ * can take down the thread the first time its path is deleted.
211
+ */
212
+ export function guardedWatch(paths: string | string[], options?: ChokidarOptions): FSWatcher {
213
+ installLostNativeWatchGuard();
214
+ // Deliberately a property access on the default export rather than a named `watch` import:
215
+ // unitTests/server/threads/watchDirFallback.test.js drives the reopen-on-exhaustion chain by
216
+ // swapping `chokidar.default.watch`, and a named import binds past that seam.
217
+ return chokidar.watch(paths, options);
218
+ }
219
+
220
+ // Test-only: number of lost native watch errors claimed so far.
221
+ export function _lostNativeWatchCountForTests(): number {
222
+ return lostNativeWatchCount;
223
+ }
224
+
225
+ /**
226
+ * A config file replaced in place (truncate, then write) can be read back empty or
227
+ * half-written, and chokidar may emit nothing further for that write — so a watcher that
228
+ * simply drops the unusable read would serve stale config until something else touched the
229
+ * file. Re-read on a later turn instead, bounded so a genuinely empty or corrupt file cannot
230
+ * spin. Callers read synchronously, so the descriptor still never outlives a single turn.
231
+ */
232
+ const PARTIAL_READ_REREAD_DELAY_MS = 20;
233
+ const PARTIAL_READ_MAX_REREADS = 10;
234
+
235
+ export class PartialReadRetry {
236
+ #filePath: string;
237
+ #timer?: ReturnType<typeof setTimeout>;
238
+ #remaining: number = PARTIAL_READ_MAX_REREADS;
239
+ #closed = false;
240
+
241
+ constructor(filePath: string) {
242
+ this.#filePath = filePath;
243
+ }
244
+
245
+ /** False once the budget is spent, so the caller can fall back to its own error handling. */
246
+ schedule(reread: () => void): boolean {
247
+ if (this.#closed) return false;
248
+ if (this.#timer) return true;
249
+ if (this.#remaining <= 0) return false;
250
+ this.#remaining--;
251
+ this.#timer = setTimeout(() => {
252
+ this.#timer = undefined;
253
+ reread();
254
+ }, PARTIAL_READ_REREAD_DELAY_MS);
255
+ this.#timer.unref?.();
256
+ return true;
257
+ }
258
+
259
+ /** A usable read arrived, so any re-read still armed for the previous one would duplicate it. */
260
+ settled() {
261
+ if (this.#timer) clearTimeout(this.#timer);
262
+ this.#timer = undefined;
263
+ this.#remaining = PARTIAL_READ_MAX_REREADS;
264
+ // The file recovered, so the next time it breaks is a new incident and has to be reported
265
+ // again rather than silenced by the warning it emitted weeks ago.
266
+ partialReadWarned.delete(this.#filePath);
267
+ }
268
+
269
+ /**
270
+ * The budget is spent. Distinct from `settled()` in that the report stands — the file has not
271
+ * recovered, and it is shared with every other watcher of it. The budget itself is restored,
272
+ * because the next event may be the repair, and that repair can be observed mid-write too.
273
+ * Returns whether this give-up was the one reported.
274
+ */
275
+ gaveUp(error?: unknown): boolean {
276
+ if (this.#closed) return false;
277
+ this.#remaining = PARTIAL_READ_MAX_REREADS;
278
+ return warnPartialReadGaveUp(this.#filePath, error);
279
+ }
280
+
281
+ /** Terminal: the watcher is closing, so nothing may re-arm the re-read or report on it. */
282
+ cancel() {
283
+ if (this.#timer) clearTimeout(this.#timer);
284
+ this.#timer = undefined;
285
+ this.#closed = true;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * ENOENT is excluded not because it cannot be transient, but because it already has an answer:
291
+ * `OptionsWatcher` routes it to `remove` (env-only fallback at boot, then removal), and
292
+ * `RootConfigWatcher` keeps its last config rather than tearing down core features on a file
293
+ * that may just be mid-replace. Re-reading would only delay a decision already made.
294
+ */
295
+ export function isPartialReadError(error: unknown): boolean {
296
+ return !(typeof error === 'object' && error !== null && (error as { code?: string }).code === 'ENOENT');
297
+ }
298
+
299
+ /**
300
+ * A watcher that exhausts its re-read budget serves stale config from then on, so the give-up
301
+ * has to be visible — otherwise the only symptom is a config change that silently did nothing.
302
+ * Warned once per file: every root-config scope watches the same one and would otherwise report
303
+ * a single bad file once each, on every event.
304
+ */
305
+ export function warnPartialReadGaveUp(filePath: string, error?: unknown): boolean {
306
+ if (partialReadWarned.has(filePath)) return false;
307
+ partialReadWarned.add(filePath);
308
+ // The cause matters to whoever has to fix it: a file that never parses is a typo to correct,
309
+ // while one that reads empty is a writer that never finished. Report the kind and position
310
+ // only — a YAML parse error's message quotes the offending source, and this file holds
311
+ // credentials.
312
+ const cause = error ? `: ${describeReadFailure(error)}` : ' that were empty or incomplete';
313
+ fallbackLogger.warn(`Gave up re-reading ${filePath} after ${PARTIAL_READ_MAX_REREADS} unusable reads${cause}`);
314
+ return true;
315
+ }
316
+
317
+ /**
318
+ * The kind and position of a failed config read, never its content: a YAML parse error's message
319
+ * quotes the offending source lines, and these files hold credentials.
320
+ */
321
+ export function describeReadFailure(error: unknown): string {
322
+ if (typeof error !== 'object' || error === null) return 'unusable';
323
+ const { name, code, linePos } = error as { name?: string; code?: string; linePos?: { line: number; col: number }[] };
324
+ const at = linePos?.[0] ? ` at line ${linePos[0].line}, column ${linePos[0].col}` : '';
325
+ return `${code ?? name ?? 'unusable'}${at}`;
326
+ }
327
+
328
+ /** Test-only: whether a give-up warning for this file is currently suppressed as a duplicate. */
329
+ export function isPartialReadWarned(filePath: string): boolean {
330
+ return partialReadWarned.has(filePath);
331
+ }
332
+
333
+ /** Test-only: forget that this file was reported, so a suite can start from a known state. */
334
+ export function clearPartialReadWarning(filePath: string) {
335
+ partialReadWarned.delete(filePath);
336
+ }
337
+
338
+ /**
339
+ * A listener that throws while applying new config is a bug in that listener, not evidence the
340
+ * file was half-written — the watcher's own state is already updated, so it keeps going.
341
+ */
342
+ export function warnWatcherListenerError(filePath: string, error: unknown) {
343
+ fallbackLogger.warn(`Error applying a configuration change from ${filePath}`, error);
74
344
  }
@@ -10,6 +10,7 @@ import * as hdbLogger from '../utility/logging/harper_logger.ts';
10
10
  import * as hdbUtils from '../utility/common_utils.ts';
11
11
  import * as hdbTerms from '../utility/hdbTerms.ts';
12
12
  import { getDomainSocketPathMaxBytes } from '../utility/domainSocket.ts';
13
+ import { bareHostViolation } from '../utility/nodeIdentity.ts';
13
14
  import * as validator from './validationWrapper.ts';
14
15
 
15
16
  const DEFAULT_LOG_FOLDER = 'log';
@@ -29,6 +30,31 @@ const INVALID_RETENTION_VALUE_MSG =
29
30
  const VALID_ROTATION_DURATION_UNITS = ['D', 'd', 'H', 'h', 'M', 'm'];
30
31
  const UNDEFINED_OPS_API = 'rootPath config parameter is undefined';
31
32
 
33
+ /**
34
+ * What `deploy_component` writes into a root-config entry, plus the deployment-level keys
35
+ * componentLoader reads off it — the shape of a `sql` application deployed before the name was
36
+ * reserved. Closed to new keys: nothing new may take this shape.
37
+ */
38
+ export const LEGACY_SQL_APPLICATION_KEYS = [
39
+ 'package',
40
+ 'files',
41
+ 'path',
42
+ 'install',
43
+ 'credentials',
44
+ 'loadComponent',
45
+ 'urlPath',
46
+ 'host',
47
+ 'branchedDatabases',
48
+ 'network',
49
+ 'port',
50
+ 'securePort',
51
+ ];
52
+
53
+ export function isLegacySqlApplicationEntry(value: unknown): boolean {
54
+ if (typeof value !== 'object' || value === null) return false;
55
+ return LEGACY_SQL_APPLICATION_KEYS.some((key) => Object.hasOwn(value, key));
56
+ }
57
+
32
58
  // Directory-path validation. The previous `([...]+)+$` nested quantifier
33
59
  // backtracked catastrophically (ReDoS), hanging the CLI at 100% CPU on any
34
60
  // value with a character outside its allow-list after a run of valid ones — a
@@ -52,6 +78,10 @@ const DIRECTORY_PATH_PATTERN = /^(?!\s*$)[^\x00-\x1f\x7f\x80-\x9f\u2028\u2029]+$
52
78
  const portConstraints = Joi.alternatives([number.min(0), string])
53
79
  .optional()
54
80
  .empty(null);
81
+ // node.hostname / replication.hostname must be a bare host, not a URL (utility/nodeIdentity.ts).
82
+ // `Joi.any` (not `string`) so validateBareHost reports a non-string with its own reason.
83
+ const bareHostConstraints = Joi.any().custom(validateBareHost).optional().empty(null);
84
+ const nodeUrlConstraints = string.custom(validateNodeUrl).optional().empty(null);
55
85
  // Controlled-flow ("directional") replication fields. A route's `replicates` is either a boolean
56
86
  // (full replication on/off) or an object describing per-direction flow; `sends`/`receives` and
57
87
  // `sendsTo`/`receivesFrom` are also accepted as top-level route keys (iterateRoutes normalizes both
@@ -102,6 +132,88 @@ export const routeConstraints = Joi.alternatives([
102
132
  array.items(string),
103
133
  ]);
104
134
 
135
+ // Models — `models:` block opts a deployment into the per-backend registry.
136
+ // Per-backend shape is validated by a discriminated alternative on the
137
+ // `backend` field. Phase 2 (#629) lands ollama; Phase 3 (#630) lands openai.
138
+ //
139
+ // `.unknown(false)` on each known backend's schema turns field-name typos
140
+ // (`bakend: ollama`, `hsot: ...`) into boot-blocking validation errors.
141
+ // Without it, Joi's top-level `allowUnknown: true` propagates and typos
142
+ // silently survive into bootstrap. Unknown backend types (anything not in
143
+ // the `switch` list) fall through to a permissive schema so future Harper
144
+ // versions or third-party components can register their own backends
145
+ // without core schema edits — `bootstrapModels` logs+skips at runtime.
146
+ //
147
+ // `requestTimeoutMs: min(1)` (not `min(0)`) so the meaning is unambiguous:
148
+ // omit the field for "no timeout". `0` would validate but `composeSignal`
149
+ // treats it as "no timeout" via `if (!timeoutMs)`, surprising a test that
150
+ // sets 0 to mean "fail immediately".
151
+ const commonEntryFields = {
152
+ model: string.optional(),
153
+ requestTimeoutMs: number.min(1).optional(),
154
+ // Ordered fallback group — other logical names tried, in order, after this one (#1326).
155
+ fallback: Joi.array().items(string).optional(),
156
+ };
157
+ const ollamaEntrySchema = Joi.object({
158
+ backend: string.valid('ollama').required(),
159
+ host: string.optional(),
160
+ ...commonEntryFields,
161
+ }).unknown(false);
162
+ const openaiEntrySchema = Joi.object({
163
+ backend: string.valid('openai').required(),
164
+ // `apiKey` may be a literal secret or a `${ENV_VAR}` placeholder; both
165
+ // are syntactically strings. `bootstrap.ts` runs `expandEnvVarsDeep`
166
+ // before construction; the backend rejects unresolved placeholders
167
+ // with an explicit error pointing at the env-var name.
168
+ apiKey: string.required(),
169
+ baseUrl: string.optional(),
170
+ organization: string.optional(),
171
+ ...commonEntryFields,
172
+ }).unknown(false);
173
+ const anthropicEntrySchema = Joi.object({
174
+ backend: string.valid('anthropic').required(),
175
+ // Same secret-handling posture as openai's `apiKey`.
176
+ apiKey: string.required(),
177
+ baseUrl: string.optional(),
178
+ ...commonEntryFields,
179
+ }).unknown(false);
180
+ const bedrockEntrySchema = Joi.object({
181
+ backend: string.valid('bedrock').required(),
182
+ // AWS credentials resolve via the SDK chain (env / shared file / IAM
183
+ // roles for service accounts) — no apiKey field. `region` is
184
+ // effectively required (Bedrock is regional) but the backend can
185
+ // fall back to AWS_REGION env, so we leave it optional here.
186
+ region: string.optional(),
187
+ ...commonEntryFields,
188
+ }).unknown(false);
189
+ const unknownBackendEntrySchema = Joi.object({
190
+ backend: string.required(),
191
+ }).unknown(true);
192
+ const modelEntrySchema = Joi.alternatives().conditional('.backend', {
193
+ switch: [
194
+ { is: 'ollama', then: ollamaEntrySchema },
195
+ { is: 'openai', then: openaiEntrySchema },
196
+ { is: 'anthropic', then: anthropicEntrySchema },
197
+ { is: 'bedrock', then: bedrockEntrySchema },
198
+ ],
199
+ otherwise: unknownBackendEntrySchema,
200
+ });
201
+ const modelsSchema = Joi.object({
202
+ embedding: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
203
+ generative: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
204
+ });
205
+
206
+ /**
207
+ * Validate a `models:` block on its own — the hot-reload path applies a watched file's block
208
+ * without the full boot validation, and must enforce the same schema boot does.
209
+ */
210
+ export function validateModelsBlock(models) {
211
+ // `allowUnknown: true` mirrors the boot path, whose top-level option propagates into this
212
+ // subtree: a sibling of embedding/generative that boots must not block a reload. Entry-level
213
+ // strictness is preserved by each backend schema's `.unknown(false)`.
214
+ return modelsSchema.validate(models, { abortEarly: false, allowUnknown: true, errors: { wrap: { label: "'" } } });
215
+ }
216
+
105
217
  let hdbRoot;
106
218
  let skipFsVal = false;
107
219
 
@@ -157,76 +269,42 @@ export function configValidator(configJson, skipFsValidation = false) {
157
269
  session: mcpSessionSchema.optional(),
158
270
  });
159
271
 
160
- // Models — `models:` block opts a deployment into the per-backend registry.
161
- // Per-backend shape is validated by a discriminated alternative on the
162
- // `backend` field. Phase 2 (#629) lands ollama; Phase 3 (#630) lands openai.
163
- //
164
- // `.unknown(false)` on each known backend's schema turns field-name typos
165
- // (`bakend: ollama`, `hsot: ...`) into boot-blocking validation errors.
166
- // Without it, Joi's top-level `allowUnknown: true` propagates and typos
167
- // silently survive into bootstrap. Unknown backend types (anything not in
168
- // the `switch` list) fall through to a permissive schema so future Harper
169
- // versions or third-party components can register their own backends
170
- // without core schema edits — `bootstrapModels` logs+skips at runtime.
171
- //
172
- // `requestTimeoutMs: min(1)` (not `min(0)`) so the meaning is unambiguous:
173
- // omit the field for "no timeout". `0` would validate but `composeSignal`
174
- // treats it as "no timeout" via `if (!timeoutMs)`, surprising a test that
175
- // sets 0 to mean "fail immediately".
176
- const commonEntryFields = {
177
- model: string.optional(),
178
- requestTimeoutMs: number.min(1).optional(),
179
- // Ordered fallback group — other logical names tried, in order, after this one (#1326).
180
- fallback: Joi.array().items(string).optional(),
181
- };
182
- const ollamaEntrySchema = Joi.object({
183
- backend: string.valid('ollama').required(),
184
- host: string.optional(),
185
- ...commonEntryFields,
186
- }).unknown(false);
187
- const openaiEntrySchema = Joi.object({
188
- backend: string.valid('openai').required(),
189
- // `apiKey` may be a literal secret or a `${ENV_VAR}` placeholder; both
190
- // are syntactically strings. `bootstrap.ts` runs `expandEnvVarsDeep`
191
- // before construction; the backend rejects unresolved placeholders
192
- // with an explicit error pointing at the env-var name.
193
- apiKey: string.required(),
194
- baseUrl: string.optional(),
195
- organization: string.optional(),
196
- ...commonEntryFields,
197
- }).unknown(false);
198
- const anthropicEntrySchema = Joi.object({
199
- backend: string.valid('anthropic').required(),
200
- // Same secret-handling posture as openai's `apiKey`.
201
- apiKey: string.required(),
202
- baseUrl: string.optional(),
203
- ...commonEntryFields,
204
- }).unknown(false);
205
- const bedrockEntrySchema = Joi.object({
206
- backend: string.valid('bedrock').required(),
207
- // AWS credentials resolve via the SDK chain (env / shared file / IAM
208
- // roles for service accounts) — no apiKey field. `region` is
209
- // effectively required (Bedrock is regional) but the backend can
210
- // fall back to AWS_REGION env, so we leave it optional here.
211
- region: string.optional(),
212
- ...commonEntryFields,
213
- }).unknown(false);
214
- const unknownBackendEntrySchema = Joi.object({
215
- backend: string.required(),
216
- }).unknown(true);
217
- const modelEntrySchema = Joi.alternatives().conditional('.backend', {
218
- switch: [
219
- { is: 'ollama', then: ollamaEntrySchema },
220
- { is: 'openai', then: openaiEntrySchema },
221
- { is: 'anthropic', then: anthropicEntrySchema },
222
- { is: 'bedrock', then: bedrockEntrySchema },
223
- ],
224
- otherwise: unknownBackendEntrySchema,
225
- });
226
- const modelsSchema = Joi.object({
227
- embedding: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
228
- generative: Joi.object().pattern(Joi.string(), modelEntrySchema).optional(),
229
- });
272
+ // `convert: false` — validateConfig() only writes the coerced value back into configDoc for
273
+ // threads/componentsRoot/logging/storage/operationsApi, not sql, so leaving Joi's default
274
+ // convert:true on here would accept a quoted `allowFullScan: "true"` and then silently drop
275
+ // it (getSqlEngineConfig()'s typeof guard rejects the still-unconverted string).
276
+ const sqlSettingsSchema = Joi.object({
277
+ engine: string.valid('legacy', 'new', 'auto').optional(),
278
+ allowFullScan: boolean.optional(),
279
+ maxSortRows: number.integer().min(1).optional(),
280
+ maxHashRows: number.integer().min(1).optional(),
281
+ })
282
+ .unknown(false)
283
+ .prefs({ convert: false });
284
+
285
+ // An application deployed under the name before it was reserved still boots; validateConfig()
286
+ // warns to rename it. Selected positively so a typo'd setting is still held to the settings
287
+ // schema instead of passing as an application.
288
+ const legacySqlApplicationSchema = Joi.object({
289
+ engine: Joi.any().forbidden(),
290
+ allowFullScan: Joi.any().forbidden(),
291
+ maxSortRows: Joi.any().forbidden(),
292
+ maxHashRows: Joi.any().forbidden(),
293
+ })
294
+ .unknown(true)
295
+ .messages({
296
+ 'any.unknown': `{#label} cannot be set while 'sql' names a deployed application; redeploy that application under a different name`,
297
+ });
298
+ const sqlSchema = Joi.alternatives()
299
+ .conditional(
300
+ Joi.object()
301
+ .or(...LEGACY_SQL_APPLICATION_KEYS)
302
+ .unknown(true),
303
+ { then: legacySqlApplicationSchema, otherwise: sqlSettingsSchema }
304
+ )
305
+ // `false`/null is how componentLoader spells a disabled entry, so an operator who had already
306
+ // turned a pre-reservation `sql` application off keeps booting.
307
+ .allow(false, null);
230
308
 
231
309
  const configSchema = Joi.object({
232
310
  authentication: Joi.alternatives(
@@ -247,8 +325,8 @@ export function configValidator(configJson, skipFsValidation = false) {
247
325
  replicate: boolean.optional(),
248
326
  }),
249
327
  replication: Joi.object({
250
- hostname: Joi.alternatives(string, number).optional().empty(null),
251
- url: string.optional().empty(null),
328
+ hostname: bareHostConstraints,
329
+ url: nodeUrlConstraints,
252
330
  port: portConstraints,
253
331
  securePort: portConstraints,
254
332
  routes: array.optional().empty(null),
@@ -265,6 +343,10 @@ export function configValidator(configJson, skipFsValidation = false) {
265
343
  copyCursorFlushIntervalMs: number.min(1).optional().empty(null),
266
344
  replayTimeout: number.min(1).optional().empty(null),
267
345
  }).optional(),
346
+ node: Joi.object({
347
+ hostname: bareHostConstraints,
348
+ url: string.optional().empty(null),
349
+ }).optional(),
268
350
  componentsRoot: rootConstraints.optional(),
269
351
  localStudio: Joi.object({
270
352
  enabled: enabledConstraints,
@@ -364,6 +446,30 @@ export function configValidator(configJson, skipFsValidation = false) {
364
446
  writeAsync: boolean.required(),
365
447
  overlappingSync: boolean.optional(),
366
448
  caching: boolean.optional(),
449
+ blobs: Joi.object({
450
+ compression: Joi.object()
451
+ .pattern(
452
+ // exact content type, 'type/*' wildcard, or the 'default' entry
453
+ /^([\w.+-]+\/(\*|[\w.+-]+)|default)$/,
454
+ Joi.alternatives([
455
+ Joi.valid(false),
456
+ // .unknown(false) so a typo'd nested field (e.g. `treshold`) is rejected instead of
457
+ // silently inheriting the top-level validate() allowUnknown:true and falling back to
458
+ // the default threshold — matching config-root.schema.json.
459
+ Joi.object({ codec: Joi.valid('deflate').optional(), threshold: number.min(0).optional() }).unknown(
460
+ false
461
+ ),
462
+ ])
463
+ )
464
+ // fail on keys that are not a content type, a 'type/*' wildcard, or 'default' —
465
+ // a typo'd key would otherwise silently never match anything
466
+ .unknown(false)
467
+ .optional(),
468
+ })
469
+ // reject a misspelled property directly under storage.blobs (e.g. `compresion:`), which the
470
+ // top-level validate()'s allowUnknown:true would otherwise accept and silently leave off
471
+ .unknown(false)
472
+ .optional(),
367
473
  compression: Joi.alternatives([
368
474
  boolean.optional(),
369
475
  Joi.object({ dictionary: string.optional(), threshold: number.optional() }),
@@ -378,6 +484,7 @@ export function configValidator(configJson, skipFsValidation = false) {
378
484
  }).required(),
379
485
  mcp: mcpSchema.optional(),
380
486
  models: modelsSchema.optional(),
487
+ sql: sqlSchema.optional(),
381
488
  ignoreScripts: boolean.optional(),
382
489
  tls: Joi.alternatives([Joi.array().items(tlsConstraints), tlsConstraints]),
383
490
  });
@@ -402,6 +509,36 @@ function doesPathExist(pathToCheck) {
402
509
  return `Specified path ${pathToCheck} does not exist.`;
403
510
  }
404
511
 
512
+ function validateBareHost(value, helpers) {
513
+ const violation = bareHostViolation(value);
514
+ if (violation) {
515
+ return helpers.message(
516
+ `{{#label}} ${violation}; it must be a bare hostname or IP literal (no scheme, port, or path) because it is this node's identity`
517
+ );
518
+ }
519
+ return value;
520
+ }
521
+
522
+ // A node URL supplies this node's identity via its host (server/nodeName.ts urlToNodeName), so it
523
+ // must have an authority. A hostless scheme ("mailto:", "data:", "file:") parses but has no host.
524
+ // A node URL supplies this node's identity via its host (server/nodeName.ts urlToNodeName). Only
525
+ // reject the unambiguous mistake: a value written as a real URL (with a "//" authority) that still
526
+ // parses to no host. Everything else is deliberately tolerated, because urlToNodeName skips a value
527
+ // it cannot take a host from and identity falls through to another source — a scheme-less
528
+ // "node1.example.com:9933" even parses as a *scheme*, so it is indistinguishable from "mailto:" here,
529
+ // and rejecting that shape would turn a previously harmless config into a boot failure.
530
+ function validateNodeUrl(value, helpers) {
531
+ if (!value.includes('//')) return value;
532
+ let host;
533
+ try {
534
+ host = new URL(value).hostname;
535
+ } catch {
536
+ return value; // unparseable — skipped at runtime, not worth failing the boot over
537
+ }
538
+ if (!host) return helpers.message('{{#label}} must be a URL with a host (e.g. "wss://node1.example.com:9933")');
539
+ return value;
540
+ }
541
+
405
542
  function validatePath(value, helpers) {
406
543
  Joi.assert(value, string.pattern(DIRECTORY_PATH_PATTERN, 'directory path'));
407
544
 
@@ -5,6 +5,7 @@ const { string, number } = Joi.types();
5
5
  import * as fs from 'fs-extra';
6
6
  import * as hdbTerms from '../utility/hdbTerms.ts';
7
7
  import * as path from 'path';
8
+ import { bareHostViolation } from '../utility/nodeIdentity.ts';
8
9
  import * as validator from './validationWrapper.ts';
9
10
 
10
11
  export default installValidator;
@@ -22,11 +23,25 @@ function installValidator(param) {
22
23
  null
23
24
  ),
24
25
  [(hdbTerms.INSTALL_PROMPTS as any).TC_AGREEMENT]: string.valid('yes', 'YES', 'Yes'),
26
+ // Catch a bad node identity here rather than at first boot. This runs after the v5 migration
27
+ // copies REPLICATION_HOSTNAME into NODE_HOSTNAME, so an upgrade carrying a URL-ish value is
28
+ // reported during install with the same reason the config boundary would give (harper#2218).
29
+ [hdbTerms.INSTALL_PROMPTS.NODE_HOSTNAME]: Joi.custom(validateNodeHostname).allow('null', null),
25
30
  });
26
31
 
27
32
  return validator.validateBySchema(param, installSchema);
28
33
  }
29
34
 
35
+ function validateNodeHostname(value, helpers) {
36
+ const violation = bareHostViolation(value);
37
+ if (violation) {
38
+ return helpers.message(
39
+ `{{#label}} ${violation}; it must be a bare hostname or IP literal (no scheme, port, or path) because it is this node's identity`
40
+ );
41
+ }
42
+ return value;
43
+ }
44
+
30
45
  function validateRootAvailable(value, helpers) {
31
46
  if (
32
47
  fs.existsSync(path.join(value, 'system/hdb_user/data.mdb')) ||