@harperfast/harper 5.2.13 → 5.3.0-beta.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 (655) hide show
  1. package/agent/mcpTools.ts +1 -1
  2. package/agent/session.ts +25 -14
  3. package/bin/cliOperations.ts +58 -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 +120 -4
  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 +3436 -242
  14. package/components/ApplicationScope.ts +8 -0
  15. package/components/EntryHandler.ts +59 -39
  16. package/components/OptionsWatcher.ts +440 -98
  17. package/components/RuntimeModuleTracker.ts +38 -7
  18. package/components/Scope.ts +56 -15
  19. package/components/awaitRestart.ts +84 -0
  20. package/components/componentLoader.ts +381 -32
  21. package/components/componentPreparationLock.ts +16 -5
  22. package/components/deploymentOperations.ts +4 -1
  23. package/components/deploymentRecorder.ts +9 -2
  24. package/components/mcp/adapters/harperHttp.ts +4 -0
  25. package/components/mcp/listChanged.ts +4 -0
  26. package/components/mcp/toolRegistry.ts +2 -0
  27. package/components/mcp/tools/operations.ts +9 -0
  28. package/components/mcp/tools/schemas/operationDescriptions.ts +2 -2
  29. package/components/operations.js +537 -113
  30. package/components/operationsValidation.js +98 -3
  31. package/components/packageComponent.ts +25 -1
  32. package/components/requestRestart.ts +11 -0
  33. package/components/status/ComponentStatusRegistry.ts +59 -0
  34. package/config/RootConfigWatcher.ts +240 -40
  35. package/config/configReadRetry.ts +62 -0
  36. package/config/configUtils.ts +357 -48
  37. package/config/harperConfigEnvVars.ts +170 -27
  38. package/config/parseConfigFile.ts +34 -0
  39. package/config/readConfigFileSync.ts +44 -0
  40. package/config/watcherArming.ts +59 -0
  41. package/config-root.schema.json +33 -0
  42. package/dataLayer/blobBackup.ts +160 -50
  43. package/dataLayer/delete.ts +6 -1
  44. package/dataLayer/harperBridge/ResourceBridge.ts +80 -10
  45. package/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  46. package/dataLayer/hdbInfoController.ts +34 -1
  47. package/dataLayer/insert.ts +44 -1
  48. package/dataLayer/rocksdbBackup.ts +53 -10
  49. package/dataLayer/schema.ts +11 -1
  50. package/dataLayer/schemaDescribe.ts +8 -1
  51. package/dist/agent/mcpTools.js +1 -1
  52. package/dist/agent/mcpTools.js.map +1 -1
  53. package/dist/agent/session.d.ts +22 -0
  54. package/dist/agent/session.js +26 -15
  55. package/dist/agent/session.js.map +1 -1
  56. package/dist/bin/cliOperations.js +61 -9
  57. package/dist/bin/cliOperations.js.map +1 -1
  58. package/dist/bin/copyDb.d.ts +12 -1
  59. package/dist/bin/copyDb.js +248 -60
  60. package/dist/bin/copyDb.js.map +1 -1
  61. package/dist/bin/deploySetup.d.ts +2 -0
  62. package/dist/bin/deploySetup.js +11 -3
  63. package/dist/bin/deploySetup.js.map +1 -1
  64. package/dist/bin/harper.js +1 -1
  65. package/dist/bin/harper.js.map +1 -1
  66. package/dist/bin/help.js +4 -1
  67. package/dist/bin/help.js.map +1 -1
  68. package/dist/bin/lite.js +4 -1
  69. package/dist/bin/lite.js.map +1 -1
  70. package/dist/bin/restart.js +91 -6
  71. package/dist/bin/restart.js.map +1 -1
  72. package/dist/bin/run.js +4 -10
  73. package/dist/bin/run.js.map +1 -1
  74. package/dist/bin/upgrade.js +4 -3
  75. package/dist/bin/upgrade.js.map +1 -1
  76. package/dist/bin/workloadIdentity.d.ts +18 -0
  77. package/dist/bin/workloadIdentity.js +100 -0
  78. package/dist/bin/workloadIdentity.js.map +1 -0
  79. package/dist/components/Application.d.ts +235 -17
  80. package/dist/components/Application.js +3038 -238
  81. package/dist/components/Application.js.map +1 -1
  82. package/dist/components/ApplicationScope.d.ts +8 -0
  83. package/dist/components/ApplicationScope.js +7 -0
  84. package/dist/components/ApplicationScope.js.map +1 -1
  85. package/dist/components/EntryHandler.js +26 -10
  86. package/dist/components/EntryHandler.js.map +1 -1
  87. package/dist/components/OptionsWatcher.d.ts +4 -0
  88. package/dist/components/OptionsWatcher.js +440 -99
  89. package/dist/components/OptionsWatcher.js.map +1 -1
  90. package/dist/components/RuntimeModuleTracker.js +40 -6
  91. package/dist/components/RuntimeModuleTracker.js.map +1 -1
  92. package/dist/components/Scope.js +52 -13
  93. package/dist/components/Scope.js.map +1 -1
  94. package/dist/components/awaitRestart.d.ts +33 -0
  95. package/dist/components/awaitRestart.js +61 -0
  96. package/dist/components/awaitRestart.js.map +1 -0
  97. package/dist/components/componentLoader.d.ts +38 -1
  98. package/dist/components/componentLoader.js +313 -24
  99. package/dist/components/componentLoader.js.map +1 -1
  100. package/dist/components/componentPreparationLock.d.ts +5 -0
  101. package/dist/components/componentPreparationLock.js +14 -6
  102. package/dist/components/componentPreparationLock.js.map +1 -1
  103. package/dist/components/deploymentOperations.js +4 -1
  104. package/dist/components/deploymentOperations.js.map +1 -1
  105. package/dist/components/deploymentRecorder.d.ts +4 -2
  106. package/dist/components/deploymentRecorder.js +1 -0
  107. package/dist/components/deploymentRecorder.js.map +1 -1
  108. package/dist/components/mcp/adapters/harperHttp.js +4 -0
  109. package/dist/components/mcp/adapters/harperHttp.js.map +1 -1
  110. package/dist/components/mcp/listChanged.js +5 -0
  111. package/dist/components/mcp/listChanged.js.map +1 -1
  112. package/dist/components/mcp/toolRegistry.d.ts +1 -0
  113. package/dist/components/mcp/toolRegistry.js.map +1 -1
  114. package/dist/components/mcp/tools/operations.d.ts +5 -0
  115. package/dist/components/mcp/tools/operations.js +9 -0
  116. package/dist/components/mcp/tools/operations.js.map +1 -1
  117. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -2
  118. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  119. package/dist/components/operations.d.ts +28 -0
  120. package/dist/components/operations.js +476 -113
  121. package/dist/components/operations.js.map +1 -1
  122. package/dist/components/operationsValidation.js +97 -3
  123. package/dist/components/operationsValidation.js.map +1 -1
  124. package/dist/components/packageComponent.js +24 -0
  125. package/dist/components/packageComponent.js.map +1 -1
  126. package/dist/components/requestRestart.d.ts +1 -0
  127. package/dist/components/requestRestart.js +7 -0
  128. package/dist/components/requestRestart.js.map +1 -1
  129. package/dist/components/status/ComponentStatusRegistry.d.ts +0 -4
  130. package/dist/components/status/ComponentStatusRegistry.js +63 -0
  131. package/dist/components/status/ComponentStatusRegistry.js.map +1 -1
  132. package/dist/config/RootConfigWatcher.d.ts +9 -1
  133. package/dist/config/RootConfigWatcher.js +228 -37
  134. package/dist/config/RootConfigWatcher.js.map +1 -1
  135. package/dist/config/configReadRetry.d.ts +8 -0
  136. package/dist/config/configReadRetry.js +62 -0
  137. package/dist/config/configReadRetry.js.map +1 -0
  138. package/dist/config/configUtils.d.ts +11 -2
  139. package/dist/config/configUtils.js +304 -47
  140. package/dist/config/configUtils.js.map +1 -1
  141. package/dist/config/harperConfigEnvVars.d.ts +16 -0
  142. package/dist/config/harperConfigEnvVars.js +162 -25
  143. package/dist/config/harperConfigEnvVars.js.map +1 -1
  144. package/dist/config/parseConfigFile.d.ts +4 -0
  145. package/dist/config/parseConfigFile.js +35 -0
  146. package/dist/config/parseConfigFile.js.map +1 -0
  147. package/dist/config/readConfigFileSync.d.ts +1 -0
  148. package/dist/config/readConfigFileSync.js +47 -0
  149. package/dist/config/readConfigFileSync.js.map +1 -0
  150. package/dist/config/watcherArming.d.ts +15 -0
  151. package/dist/config/watcherArming.js +59 -0
  152. package/dist/config/watcherArming.js.map +1 -0
  153. package/dist/dataLayer/blobBackup.d.ts +49 -20
  154. package/dist/dataLayer/blobBackup.js +139 -50
  155. package/dist/dataLayer/blobBackup.js.map +1 -1
  156. package/dist/dataLayer/delete.js +1 -1
  157. package/dist/dataLayer/delete.js.map +1 -1
  158. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +14 -1
  159. package/dist/dataLayer/harperBridge/ResourceBridge.js +72 -12
  160. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  161. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.d.ts +3 -1
  162. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js +3 -1
  163. package/dist/dataLayer/harperBridge/lmdbBridge/lmdbMethods/DeleteAuditLogsBeforeResults.js.map +1 -1
  164. package/dist/dataLayer/hdbInfoController.d.ts +10 -0
  165. package/dist/dataLayer/hdbInfoController.js +30 -1
  166. package/dist/dataLayer/hdbInfoController.js.map +1 -1
  167. package/dist/dataLayer/insert.d.ts +9 -1
  168. package/dist/dataLayer/insert.js +30 -0
  169. package/dist/dataLayer/insert.js.map +1 -1
  170. package/dist/dataLayer/rocksdbBackup.d.ts +2 -2
  171. package/dist/dataLayer/rocksdbBackup.js +45 -8
  172. package/dist/dataLayer/rocksdbBackup.js.map +1 -1
  173. package/dist/dataLayer/schema.js +8 -0
  174. package/dist/dataLayer/schema.js.map +1 -1
  175. package/dist/dataLayer/schemaDescribe.js +8 -1
  176. package/dist/dataLayer/schemaDescribe.js.map +1 -1
  177. package/dist/index.d.ts +2 -1
  178. package/dist/index.js +4 -1
  179. package/dist/index.js.map +1 -1
  180. package/dist/json/systemSchema.json +55 -0
  181. package/dist/resources/DatabaseTransaction.d.ts +77 -0
  182. package/dist/resources/DatabaseTransaction.js +586 -54
  183. package/dist/resources/DatabaseTransaction.js.map +1 -1
  184. package/dist/resources/LMDBTransaction.d.ts +2 -1
  185. package/dist/resources/LMDBTransaction.js +43 -6
  186. package/dist/resources/LMDBTransaction.js.map +1 -1
  187. package/dist/resources/PrimaryRocksDatabase.d.ts +1 -0
  188. package/dist/resources/PrimaryRocksDatabase.js +49 -5
  189. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  190. package/dist/resources/RecordEncoder.d.ts +20 -0
  191. package/dist/resources/RecordEncoder.js +110 -9
  192. package/dist/resources/RecordEncoder.js.map +1 -1
  193. package/dist/resources/RequestTarget.d.ts +2 -0
  194. package/dist/resources/RequestTarget.js.map +1 -1
  195. package/dist/resources/Resource.js +117 -24
  196. package/dist/resources/Resource.js.map +1 -1
  197. package/dist/resources/ResourceInterface.d.ts +28 -1
  198. package/dist/resources/ResourceInterface.js.map +1 -1
  199. package/dist/resources/RocksIndexStore.d.ts +6 -1
  200. package/dist/resources/RocksIndexStore.js +24 -9
  201. package/dist/resources/RocksIndexStore.js.map +1 -1
  202. package/dist/resources/RocksTransactionLogStore.d.ts +24 -1
  203. package/dist/resources/RocksTransactionLogStore.js +160 -49
  204. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  205. package/dist/resources/Table.d.ts +165 -10
  206. package/dist/resources/Table.js +1805 -303
  207. package/dist/resources/Table.js.map +1 -1
  208. package/dist/resources/analytics/write.js +10 -3
  209. package/dist/resources/analytics/write.js.map +1 -1
  210. package/dist/resources/auditStore.d.ts +181 -2
  211. package/dist/resources/auditStore.js +640 -29
  212. package/dist/resources/auditStore.js.map +1 -1
  213. package/dist/resources/blob.d.ts +129 -9
  214. package/dist/resources/blob.js +992 -114
  215. package/dist/resources/blob.js.map +1 -1
  216. package/dist/resources/branchDatabase.d.ts +48 -0
  217. package/dist/resources/branchDatabase.js +892 -0
  218. package/dist/resources/branchDatabase.js.map +1 -0
  219. package/dist/resources/crdt.js +50 -12
  220. package/dist/resources/crdt.js.map +1 -1
  221. package/dist/resources/dataLoader.js +3 -4
  222. package/dist/resources/dataLoader.js.map +1 -1
  223. package/dist/resources/databases.d.ts +157 -14
  224. package/dist/resources/databases.js +1462 -325
  225. package/dist/resources/databases.js.map +1 -1
  226. package/dist/resources/defineTable.d.ts +10 -2
  227. package/dist/resources/defineTable.js +9 -1
  228. package/dist/resources/defineTable.js.map +1 -1
  229. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  230. package/dist/resources/derivedIndexRegistry.js +68 -0
  231. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  232. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  233. package/dist/resources/derivedIndexRuntime.js +2027 -0
  234. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  235. package/dist/resources/graphql.d.ts +1 -1
  236. package/dist/resources/graphql.js +52 -16
  237. package/dist/resources/graphql.js.map +1 -1
  238. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +103 -9
  239. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +854 -41
  240. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  241. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  242. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  243. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  244. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  245. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  246. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  247. package/dist/resources/longLivedTransactions.d.ts +71 -0
  248. package/dist/resources/longLivedTransactions.js +358 -0
  249. package/dist/resources/longLivedTransactions.js.map +1 -0
  250. package/dist/resources/models/backendRegistry.d.ts +26 -0
  251. package/dist/resources/models/backendRegistry.js +60 -2
  252. package/dist/resources/models/backendRegistry.js.map +1 -1
  253. package/dist/resources/models/bootstrap.d.ts +33 -1
  254. package/dist/resources/models/bootstrap.js +416 -31
  255. package/dist/resources/models/bootstrap.js.map +1 -1
  256. package/dist/resources/nodeIdMapping.d.ts +5 -0
  257. package/dist/resources/nodeIdMapping.js +49 -0
  258. package/dist/resources/nodeIdMapping.js.map +1 -1
  259. package/dist/resources/recordLock.d.ts +123 -0
  260. package/dist/resources/recordLock.js +315 -0
  261. package/dist/resources/recordLock.js.map +1 -0
  262. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  263. package/dist/resources/recordLockCoordinator.js +2565 -0
  264. package/dist/resources/recordLockCoordinator.js.map +1 -0
  265. package/dist/resources/replayLogs.d.ts +14 -1
  266. package/dist/resources/replayLogs.js +157 -24
  267. package/dist/resources/replayLogs.js.map +1 -1
  268. package/dist/resources/replayLogsGuards.d.ts +94 -7
  269. package/dist/resources/replayLogsGuards.js +111 -7
  270. package/dist/resources/replayLogsGuards.js.map +1 -1
  271. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  272. package/dist/resources/replicatedApplyFailure.js +63 -0
  273. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  274. package/dist/resources/scheduler/scheduler.js +3 -3
  275. package/dist/resources/scheduler/scheduler.js.map +1 -1
  276. package/dist/resources/search.d.ts +10 -4
  277. package/dist/resources/search.js +283 -37
  278. package/dist/resources/search.js.map +1 -1
  279. package/dist/resources/tracked.d.ts +5 -1
  280. package/dist/resources/tracked.js +74 -23
  281. package/dist/resources/tracked.js.map +1 -1
  282. package/dist/resources/transactionBroadcast.d.ts +1 -1
  283. package/dist/resources/transactionBroadcast.js +2 -2
  284. package/dist/resources/transactionBroadcast.js.map +1 -1
  285. package/dist/security/auth.js +61 -30
  286. package/dist/security/auth.js.map +1 -1
  287. package/dist/security/authn/oidc/claims.d.ts +22 -0
  288. package/dist/security/authn/oidc/claims.js +71 -0
  289. package/dist/security/authn/oidc/claims.js.map +1 -0
  290. package/dist/security/authn/oidc/identityToken.d.ts +27 -0
  291. package/dist/security/authn/oidc/identityToken.js +111 -0
  292. package/dist/security/authn/oidc/identityToken.js.map +1 -0
  293. package/dist/security/authn/oidc/jwks.d.ts +25 -0
  294. package/dist/security/authn/oidc/jwks.js +261 -0
  295. package/dist/security/authn/oidc/jwks.js.map +1 -0
  296. package/dist/security/authn/oidc/providers/generic.d.ts +13 -0
  297. package/dist/security/authn/oidc/providers/generic.js +34 -0
  298. package/dist/security/authn/oidc/providers/generic.js.map +1 -0
  299. package/dist/security/authn/oidc/providers/githubActions.d.ts +11 -0
  300. package/dist/security/authn/oidc/providers/githubActions.js +129 -0
  301. package/dist/security/authn/oidc/providers/githubActions.js.map +1 -0
  302. package/dist/security/authn/oidc/providers/index.d.ts +37 -0
  303. package/dist/security/authn/oidc/providers/index.js +24 -0
  304. package/dist/security/authn/oidc/providers/index.js.map +1 -0
  305. package/dist/security/authn/oidc/tokenExchange.d.ts +12 -0
  306. package/dist/security/authn/oidc/tokenExchange.js +306 -0
  307. package/dist/security/authn/oidc/tokenExchange.js.map +1 -0
  308. package/dist/security/authn/oidc/trustPolicyOperations.d.ts +49 -0
  309. package/dist/security/authn/oidc/trustPolicyOperations.js +358 -0
  310. package/dist/security/authn/oidc/trustPolicyOperations.js.map +1 -0
  311. package/dist/security/authn/oidc/types.d.ts +38 -0
  312. package/dist/security/authn/oidc/types.js +6 -0
  313. package/dist/security/authn/oidc/types.js.map +1 -0
  314. package/dist/security/certificateVerification/index.js +40 -11
  315. package/dist/security/certificateVerification/index.js.map +1 -1
  316. package/dist/security/certificateVerification/trustedIssuers.d.ts +24 -0
  317. package/dist/security/certificateVerification/trustedIssuers.js +79 -0
  318. package/dist/security/certificateVerification/trustedIssuers.js.map +1 -0
  319. package/dist/security/certificateVerification/types.d.ts +1 -0
  320. package/dist/security/credentialProvenance.d.ts +35 -0
  321. package/dist/security/credentialProvenance.js +51 -0
  322. package/dist/security/credentialProvenance.js.map +1 -0
  323. package/dist/security/credentialRejection.d.ts +4 -0
  324. package/dist/security/credentialRejection.js +24 -0
  325. package/dist/security/credentialRejection.js.map +1 -0
  326. package/dist/security/deferredAuthentication.d.ts +36 -0
  327. package/dist/security/deferredAuthentication.js +70 -0
  328. package/dist/security/deferredAuthentication.js.map +1 -0
  329. package/dist/security/impersonation.d.ts +21 -0
  330. package/dist/security/impersonation.js +108 -9
  331. package/dist/security/impersonation.js.map +1 -1
  332. package/dist/security/jsLoader.d.ts +6 -0
  333. package/dist/security/jsLoader.js +77 -15
  334. package/dist/security/jsLoader.js.map +1 -1
  335. package/dist/security/keys.js +301 -71
  336. package/dist/security/keys.js.map +1 -1
  337. package/dist/security/operationScope.d.ts +21 -0
  338. package/dist/security/operationScope.js +36 -0
  339. package/dist/security/operationScope.js.map +1 -0
  340. package/dist/security/permissionsTranslator.js +21 -0
  341. package/dist/security/permissionsTranslator.js.map +1 -1
  342. package/dist/security/tokenAuthentication.d.ts +19 -1
  343. package/dist/security/tokenAuthentication.js +191 -10
  344. package/dist/security/tokenAuthentication.js.map +1 -1
  345. package/dist/security/user.js +4 -3
  346. package/dist/security/user.js.map +1 -1
  347. package/dist/server/DurableSubscriptionsSession.d.ts +2 -2
  348. package/dist/server/DurableSubscriptionsSession.js +67 -10
  349. package/dist/server/DurableSubscriptionsSession.js.map +1 -1
  350. package/dist/server/REST.js +106 -2
  351. package/dist/server/REST.js.map +1 -1
  352. package/dist/server/graphqlQuerying.js +4 -0
  353. package/dist/server/graphqlQuerying.js.map +1 -1
  354. package/dist/server/http.d.ts +16 -1
  355. package/dist/server/http.js +122 -17
  356. package/dist/server/http.js.map +1 -1
  357. package/dist/server/itc/serverHandlers.js +8 -1
  358. package/dist/server/itc/serverHandlers.js.map +1 -1
  359. package/dist/server/jobs/jobProcess.js +6 -2
  360. package/dist/server/jobs/jobProcess.js.map +1 -1
  361. package/dist/server/jobs/jobs.js +4 -1
  362. package/dist/server/jobs/jobs.js.map +1 -1
  363. package/dist/server/liveSubscriptionAuth.d.ts +26 -4
  364. package/dist/server/liveSubscriptionAuth.js +105 -39
  365. package/dist/server/liveSubscriptionAuth.js.map +1 -1
  366. package/dist/server/loadRootComponents.js +49 -10
  367. package/dist/server/loadRootComponents.js.map +1 -1
  368. package/dist/server/mqtt.d.ts +2 -0
  369. package/dist/server/mqtt.js +165 -30
  370. package/dist/server/mqtt.js.map +1 -1
  371. package/dist/server/nodeName.d.ts +2 -0
  372. package/dist/server/nodeName.js +107 -23
  373. package/dist/server/nodeName.js.map +1 -1
  374. package/dist/server/serverHelpers/Headers.d.ts +27 -0
  375. package/dist/server/serverHelpers/Headers.js +145 -1
  376. package/dist/server/serverHelpers/Headers.js.map +1 -1
  377. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  378. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  379. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  380. package/dist/server/serverHelpers/Request.d.ts +5 -10
  381. package/dist/server/serverHelpers/Request.js +38 -136
  382. package/dist/server/serverHelpers/Request.js.map +1 -1
  383. package/dist/server/serverHelpers/contentTypes.d.ts +11 -0
  384. package/dist/server/serverHelpers/contentTypes.js +221 -40
  385. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  386. package/dist/server/serverHelpers/deployValidationState.d.ts +3 -0
  387. package/dist/server/serverHelpers/deployValidationState.js +9 -19
  388. package/dist/server/serverHelpers/deployValidationState.js.map +1 -1
  389. package/dist/server/serverHelpers/operationAuthorizationState.d.ts +12 -0
  390. package/dist/server/serverHelpers/operationAuthorizationState.js +24 -2
  391. package/dist/server/serverHelpers/operationAuthorizationState.js.map +1 -1
  392. package/dist/server/serverHelpers/registeredOperations.d.ts +5 -4
  393. package/dist/server/serverHelpers/registeredOperations.js +74 -21
  394. package/dist/server/serverHelpers/registeredOperations.js.map +1 -1
  395. package/dist/server/serverHelpers/requestSanitization.d.ts +11 -0
  396. package/dist/server/serverHelpers/requestSanitization.js +20 -0
  397. package/dist/server/serverHelpers/requestSanitization.js.map +1 -0
  398. package/dist/server/serverHelpers/serverHandlers.js +6 -3
  399. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  400. package/dist/server/serverHelpers/serverUtilities.d.ts +18 -0
  401. package/dist/server/serverHelpers/serverUtilities.js +178 -29
  402. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  403. package/dist/server/serverHelpers/sharedMessageEncoding.d.ts +67 -0
  404. package/dist/server/serverHelpers/sharedMessageEncoding.js +280 -0
  405. package/dist/server/serverHelpers/sharedMessageEncoding.js.map +1 -0
  406. package/dist/server/serverHelpers/uwsServer.js +19 -1
  407. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  408. package/dist/server/static.js +24 -28
  409. package/dist/server/static.js.map +1 -1
  410. package/dist/server/storageReclamation.d.ts +5 -0
  411. package/dist/server/storageReclamation.js +17 -1
  412. package/dist/server/storageReclamation.js.map +1 -1
  413. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  414. package/dist/server/threads/isolatedApplications.js +171 -0
  415. package/dist/server/threads/isolatedApplications.js.map +1 -0
  416. package/dist/server/threads/itc.d.ts +7 -2
  417. package/dist/server/threads/itc.js +5 -1
  418. package/dist/server/threads/itc.js.map +1 -1
  419. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  420. package/dist/server/threads/logRotationTransport.js +33 -0
  421. package/dist/server/threads/logRotationTransport.js.map +1 -0
  422. package/dist/server/threads/manageThreads.d.ts +83 -3
  423. package/dist/server/threads/manageThreads.js +769 -75
  424. package/dist/server/threads/manageThreads.js.map +1 -1
  425. package/dist/server/threads/socketRouter.d.ts +1 -0
  426. package/dist/server/threads/socketRouter.js +277 -31
  427. package/dist/server/threads/socketRouter.js.map +1 -1
  428. package/dist/server/threads/threadHeapMemory.d.ts +2 -0
  429. package/dist/server/threads/threadHeapMemory.js +31 -0
  430. package/dist/server/threads/threadHeapMemory.js.map +1 -0
  431. package/dist/server/threads/threadServer.js +76 -22
  432. package/dist/server/threads/threadServer.js.map +1 -1
  433. package/dist/sqlEngine/config.d.ts +1 -3
  434. package/dist/sqlEngine/config.js +19 -16
  435. package/dist/sqlEngine/config.js.map +1 -1
  436. package/dist/sqlTranslator/index.d.ts +1 -1
  437. package/dist/sqlTranslator/index.js +30 -7
  438. package/dist/sqlTranslator/index.js.map +1 -1
  439. package/dist/upgrade/directives/5-3-0.d.ts +7 -0
  440. package/dist/upgrade/directives/5-3-0.js +148 -0
  441. package/dist/upgrade/directives/5-3-0.js.map +1 -0
  442. package/dist/upgrade/directives/directivesController.js +2 -1
  443. package/dist/upgrade/directives/directivesController.js.map +1 -1
  444. package/dist/utility/OperationFunctionCaller.js +2 -1
  445. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  446. package/dist/utility/common_utils.d.ts +16 -0
  447. package/dist/utility/common_utils.js +32 -6
  448. package/dist/utility/common_utils.js.map +1 -1
  449. package/dist/utility/componentNames.d.ts +8 -0
  450. package/dist/utility/componentNames.js +12 -1
  451. package/dist/utility/componentNames.js.map +1 -1
  452. package/dist/utility/environment/environmentManager.js +3 -6
  453. package/dist/utility/environment/environmentManager.js.map +1 -1
  454. package/dist/utility/environment/systemInformation.d.ts +1 -0
  455. package/dist/utility/environment/systemInformation.js +1 -0
  456. package/dist/utility/environment/systemInformation.js.map +1 -1
  457. package/dist/utility/errors/commonErrors.d.ts +2 -0
  458. package/dist/utility/errors/commonErrors.js +2 -0
  459. package/dist/utility/errors/commonErrors.js.map +1 -1
  460. package/dist/utility/errors/hdbError.d.ts +33 -0
  461. package/dist/utility/errors/hdbError.js +58 -1
  462. package/dist/utility/errors/hdbError.js.map +1 -1
  463. package/dist/utility/globalSchema.d.ts +18 -0
  464. package/dist/utility/hdbTerms.d.ts +18 -0
  465. package/dist/utility/hdbTerms.js +20 -2
  466. package/dist/utility/hdbTerms.js.map +1 -1
  467. package/dist/utility/logging/harper_logger.d.ts +2 -0
  468. package/dist/utility/logging/harper_logger.js +286 -30
  469. package/dist/utility/logging/harper_logger.js.map +1 -1
  470. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  471. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  472. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  473. package/dist/utility/logging/logRotation.d.ts +46 -0
  474. package/dist/utility/logging/logRotation.js +365 -0
  475. package/dist/utility/logging/logRotation.js.map +1 -0
  476. package/dist/utility/logging/logRotator.d.ts +1 -1
  477. package/dist/utility/logging/logRotator.js +192 -85
  478. package/dist/utility/logging/logRotator.js.map +1 -1
  479. package/dist/utility/nodeIdentity.d.ts +9 -0
  480. package/dist/utility/nodeIdentity.js +58 -0
  481. package/dist/utility/nodeIdentity.js.map +1 -0
  482. package/dist/utility/npmUtilities.js +11 -7
  483. package/dist/utility/npmUtilities.js.map +1 -1
  484. package/dist/utility/operationPermissions.d.ts +3 -1
  485. package/dist/utility/operationPermissions.js +16 -1
  486. package/dist/utility/operationPermissions.js.map +1 -1
  487. package/dist/utility/operation_authorization.d.ts +10 -7
  488. package/dist/utility/operation_authorization.js +212 -41
  489. package/dist/utility/operation_authorization.js.map +1 -1
  490. package/dist/utility/watchPath.d.ts +29 -0
  491. package/dist/utility/watchPath.js +68 -0
  492. package/dist/utility/watchPath.js.map +1 -0
  493. package/dist/utility/watcherFallback.d.ts +41 -0
  494. package/dist/utility/watcherFallback.js +153 -0
  495. package/dist/utility/watcherFallback.js.map +1 -1
  496. package/dist/validation/configValidator.d.ts +12 -0
  497. package/dist/validation/configValidator.js +205 -75
  498. package/dist/validation/configValidator.js.map +1 -1
  499. package/dist/validation/installValidator.js +12 -0
  500. package/dist/validation/installValidator.js.map +1 -1
  501. package/dist/validation/validationWrapper.d.ts +11 -0
  502. package/dist/validation/validationWrapper.js +16 -3
  503. package/dist/validation/validationWrapper.js.map +1 -1
  504. package/index.ts +8 -0
  505. package/json/systemSchema.json +55 -0
  506. package/npm-shrinkwrap.json +286 -194
  507. package/package.json +15 -7
  508. package/resources/DESIGN.md +219 -52
  509. package/resources/DatabaseTransaction.ts +657 -52
  510. package/resources/LMDBTransaction.ts +46 -6
  511. package/resources/PrimaryRocksDatabase.ts +46 -7
  512. package/resources/RecordEncoder.ts +124 -9
  513. package/resources/RequestTarget.ts +2 -0
  514. package/resources/Resource.ts +114 -22
  515. package/resources/ResourceInterface.ts +31 -0
  516. package/resources/RocksIndexStore.ts +30 -9
  517. package/resources/RocksTransactionLogStore.ts +188 -51
  518. package/resources/Table.ts +1960 -301
  519. package/resources/analytics/write.ts +10 -3
  520. package/resources/auditStore.ts +647 -32
  521. package/resources/blob.ts +1029 -110
  522. package/resources/branchDatabase.ts +941 -0
  523. package/resources/crdt.ts +76 -12
  524. package/resources/dataLoader.ts +3 -4
  525. package/resources/databases.ts +1584 -320
  526. package/resources/defineTable.ts +18 -2
  527. package/resources/derivedIndexRegistry.ts +56 -0
  528. package/resources/derivedIndexRuntime.ts +2292 -0
  529. package/resources/graphql.ts +72 -17
  530. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +911 -48
  531. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  532. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  533. package/resources/longLivedTransactions.ts +360 -0
  534. package/resources/models/backendRegistry.ts +84 -2
  535. package/resources/models/bootstrap.ts +473 -28
  536. package/resources/nodeIdMapping.ts +50 -0
  537. package/resources/recordLock.ts +419 -0
  538. package/resources/recordLockCoordinator.ts +3043 -0
  539. package/resources/replayLogs.ts +152 -26
  540. package/resources/replayLogsGuards.ts +171 -8
  541. package/resources/replicatedApplyFailure.ts +77 -0
  542. package/resources/scheduler/scheduler.ts +4 -4
  543. package/resources/search.ts +288 -46
  544. package/resources/tracked.ts +73 -22
  545. package/resources/transactionBroadcast.ts +3 -3
  546. package/security/auth.ts +68 -29
  547. package/security/authn/oidc/claims.ts +72 -0
  548. package/security/authn/oidc/identityToken.ts +129 -0
  549. package/security/authn/oidc/jwks.ts +260 -0
  550. package/security/authn/oidc/providers/generic.ts +40 -0
  551. package/security/authn/oidc/providers/githubActions.ts +137 -0
  552. package/security/authn/oidc/providers/index.ts +52 -0
  553. package/security/authn/oidc/tokenExchange.ts +300 -0
  554. package/security/authn/oidc/trustPolicyOperations.ts +343 -0
  555. package/security/authn/oidc/types.ts +41 -0
  556. package/security/certificateVerification/index.ts +54 -13
  557. package/security/certificateVerification/trustedIssuers.ts +76 -0
  558. package/security/certificateVerification/types.ts +1 -0
  559. package/security/credentialProvenance.ts +47 -0
  560. package/security/credentialRejection.ts +22 -0
  561. package/security/deferredAuthentication.ts +71 -0
  562. package/security/impersonation.ts +117 -12
  563. package/security/jsLoader.ts +83 -18
  564. package/security/keys.ts +298 -72
  565. package/security/operationScope.ts +33 -0
  566. package/security/permissionsTranslator.js +23 -0
  567. package/security/tokenAuthentication.ts +233 -12
  568. package/security/user.ts +4 -3
  569. package/server/DESIGN.md +194 -16
  570. package/server/DurableSubscriptionsSession.ts +71 -11
  571. package/server/REST.ts +115 -4
  572. package/server/graphqlQuerying.ts +4 -0
  573. package/server/http.ts +133 -20
  574. package/server/itc/serverHandlers.js +8 -1
  575. package/server/jobs/jobProcess.ts +8 -2
  576. package/server/jobs/jobs.ts +4 -1
  577. package/server/liveSubscriptionAuth.ts +129 -46
  578. package/server/loadRootComponents.js +50 -8
  579. package/server/mqtt.ts +179 -38
  580. package/server/nodeName.ts +103 -21
  581. package/server/serverHelpers/Headers.ts +135 -0
  582. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  583. package/server/serverHelpers/Request.ts +33 -131
  584. package/server/serverHelpers/contentTypes.ts +217 -36
  585. package/server/serverHelpers/deployValidationState.ts +24 -13
  586. package/server/serverHelpers/operationAuthorizationState.ts +34 -3
  587. package/server/serverHelpers/registeredOperations.ts +79 -22
  588. package/server/serverHelpers/requestSanitization.ts +15 -0
  589. package/server/serverHelpers/serverHandlers.js +6 -3
  590. package/server/serverHelpers/serverUtilities.ts +232 -40
  591. package/server/serverHelpers/sharedMessageEncoding.ts +307 -0
  592. package/server/serverHelpers/uwsServer.ts +17 -2
  593. package/server/static.ts +23 -29
  594. package/server/storageReclamation.ts +15 -2
  595. package/server/threads/isolatedApplications.ts +157 -0
  596. package/server/threads/itc.js +11 -1
  597. package/server/threads/logRotationTransport.ts +40 -0
  598. package/server/threads/manageThreads.js +771 -66
  599. package/server/threads/socketRouter.ts +291 -30
  600. package/server/threads/threadHeapMemory.ts +26 -0
  601. package/server/threads/threadServer.js +73 -22
  602. package/sqlTranslator/index.ts +31 -8
  603. package/studio/web/assets/{Chat-4RrB5134.js → Chat-D3j-1yY1.js} +1 -1
  604. package/studio/web/assets/{FloatingChat-omlNMDcJ.js → FloatingChat-BxJGYcfB.js} +3 -3
  605. package/studio/web/assets/{apiToken-Bke3wvfZ.js → apiToken-CT55oWOe.js} +1 -1
  606. package/studio/web/assets/{applications-DvFDYJgK.js → applications-D9Ct9_vm.js} +1 -1
  607. package/studio/web/assets/{cssMode-C1vRa7zh.js → cssMode-DV8H7VwA.js} +1 -1
  608. package/studio/web/assets/{editor-3XRWEDWX.js → editor-uatc0unt.js} +1 -1
  609. package/studio/web/assets/{html-CTY5tdMr.js → html-Bm6D6paN.js} +1 -1
  610. package/studio/web/assets/{htmlMode-CG1vSD9t.js → htmlMode-CEn7tpLG.js} +1 -1
  611. package/studio/web/assets/{index-Dfpeofdu.js → index-BIXW6Pu4.js} +5 -5
  612. package/studio/web/assets/{index.lazy-B1VOIv-t.js → index.lazy-UI7L-Vrk.js} +1 -1
  613. package/studio/web/assets/{javascript-CJeJzGnI.js → javascript-CJ0G3AFZ.js} +1 -1
  614. package/studio/web/assets/{jsonMode-CYPBwM82.js → jsonMode-DQADAYEa.js} +1 -1
  615. package/studio/web/assets/{languageServices-IKH4GUHl.js → languageServices-CAQJXWcI.js} +1 -1
  616. package/studio/web/assets/{lspLanguageFeatures-BC8_gxKG.js → lspLanguageFeatures-CCQ8P5sY.js} +1 -1
  617. package/studio/web/assets/{notifications-BfKBpYcq.js → notifications-BbxTU6Aw.js} +1 -1
  618. package/studio/web/assets/{notifications-DFbArTfC.js → notifications-Cvb3P1lB.js} +1 -1
  619. package/studio/web/assets/{profile-Q4-T6c-S.js → profile-Yyb7gsvL.js} +1 -1
  620. package/studio/web/assets/{regions-CtkV0xje.js → regions-OgjGHlU5.js} +1 -1
  621. package/studio/web/assets/{register-1ZZuMsiA.js → register-6qwNEOY3.js} +2 -2
  622. package/studio/web/assets/{setComponentFile-ZWLcWv5X.js → setComponentFile-BilDMtgB.js} +1 -1
  623. package/studio/web/assets/{setup-DWprJyJy.js → setup-J6qJ7OIU.js} +2 -2
  624. package/studio/web/assets/{status--aNm8isn.js → status-0RWGcfyD.js} +1 -1
  625. package/studio/web/assets/{toggleHighContrast-Cq_lt3XD.js → toggleHighContrast-BIn-vErT.js} +1 -1
  626. package/studio/web/assets/{tsMode-pjgytARx.js → tsMode-DgUXku4d.js} +1 -1
  627. package/studio/web/assets/{typescript-Cdg0mqUh.js → typescript-C9orXcsM.js} +1 -1
  628. package/studio/web/assets/{useEntityRestURL-RZhaY8Rn.js → useEntityRestURL-BEoXXbUB.js} +1 -1
  629. package/studio/web/assets/{workers-BgoXIqQe.js → workers-JVzSDmgx.js} +1 -1
  630. package/studio/web/assets/{xml-BwSeDMiP.js → xml-Cq-S8S4X.js} +1 -1
  631. package/studio/web/assets/{yaml-DotCUG5l.js → yaml-sfoRdh1M.js} +1 -1
  632. package/studio/web/index.html +1 -1
  633. package/upgrade/directives/5-3-0.ts +132 -0
  634. package/upgrade/directives/directivesController.ts +2 -1
  635. package/utility/OperationFunctionCaller.ts +2 -1
  636. package/utility/common_utils.ts +30 -5
  637. package/utility/componentNames.ts +12 -0
  638. package/utility/environment/environmentManager.ts +3 -7
  639. package/utility/environment/systemInformation.ts +7 -0
  640. package/utility/errors/commonErrors.ts +4 -0
  641. package/utility/errors/hdbError.ts +57 -0
  642. package/utility/hdbTerms.ts +19 -0
  643. package/utility/logging/harper_logger.ts +278 -26
  644. package/utility/logging/logGenerationCoordinator.ts +196 -0
  645. package/utility/logging/logRotation.ts +367 -0
  646. package/utility/logging/logRotator.ts +213 -81
  647. package/utility/nodeIdentity.ts +45 -0
  648. package/utility/npmUtilities.ts +12 -8
  649. package/utility/operationPermissions.ts +18 -1
  650. package/utility/operation_authorization.ts +231 -42
  651. package/utility/watchPath.ts +63 -0
  652. package/utility/watcherFallback.ts +148 -0
  653. package/validation/configValidator.ts +215 -75
  654. package/validation/installValidator.ts +15 -0
  655. package/validation/validationWrapper.ts +18 -4
@@ -0,0 +1,62 @@
1
+ // Why a lock that outlives the reader's budget is retried from a timer, and why both the bound and
2
+ // the backoff are wall clock rather than an attempt count: see "Root config watchers must read
3
+ // synchronously" in DESIGN.md (harper#2191).
4
+ const RETRY_BUDGET_MS = 3_100;
5
+ const INITIAL_DELAY_MS = 100;
6
+ const MAX_DELAY_MS = 1_600;
7
+
8
+ let retryBudgetMs = RETRY_BUDGET_MS;
9
+
10
+ // Test-only, the read-side twin of `atomicWriteFile`'s `retryBudgetMs` option. A case that only
11
+ // needs the ladder *spent* has no way to get there but wall clock, so without this each one sits
12
+ // out the shipped 3.1 s; a dozen of them cost the unit job 40 s. Called with no argument it
13
+ // restores the shipped value, and `configReadRetry.test.js` still spends that value in full, so
14
+ // shortening it elsewhere cannot hide a change to what ships.
15
+ export function _setRetryBudgetForTests(ms: number = RETRY_BUDGET_MS): void {
16
+ retryBudgetMs = ms;
17
+ }
18
+
19
+ export class ConfigReadRetry {
20
+ #timer?: NodeJS.Timeout;
21
+ #deadline?: number;
22
+ // Captured with the deadline: the backoff is a fraction of the budget the deadline came from,
23
+ // so reading the module value again mid-ladder would mis-scale it if a test had changed it.
24
+ #budgetMs: number = retryBudgetMs;
25
+
26
+ // `holdEventLoop` is for a caller whose boot barrier this ladder is the only thing left to
27
+ // settle: an unref'd timer would let the thread drain and exit mid-boot instead.
28
+ schedule(retry: () => void, holdEventLoop: boolean = false): boolean {
29
+ this.cancel();
30
+ const now = performance.now();
31
+ if (this.#deadline === undefined) {
32
+ this.#budgetMs = retryBudgetMs;
33
+ this.#deadline = now + this.#budgetMs;
34
+ }
35
+ const remainingMs = this.#deadline - now;
36
+ if (remainingMs <= 0) {
37
+ this.reset();
38
+ return false;
39
+ }
40
+ const elapsedMs = this.#budgetMs - remainingMs;
41
+ const delayMs = Math.min(Math.max(elapsedMs, INITIAL_DELAY_MS), MAX_DELAY_MS, remainingMs);
42
+ this.#timer = setTimeout(retry, delayMs);
43
+ if (!holdEventLoop) this.#timer.unref();
44
+ return true;
45
+ }
46
+
47
+ get pending(): boolean {
48
+ return this.#timer !== undefined;
49
+ }
50
+
51
+ reset(): void {
52
+ this.cancel();
53
+ this.#deadline = undefined;
54
+ }
55
+
56
+ cancel(): void {
57
+ if (this.#timer) {
58
+ clearTimeout(this.#timer);
59
+ this.#timer = undefined;
60
+ }
61
+ }
62
+ }
@@ -1,11 +1,17 @@
1
1
  import * as hdbTerms from '../utility/hdbTerms.ts';
2
2
  import * as hdbUtils from '../utility/common_utils.ts';
3
3
  import logger from '../utility/logging/harper_logger.ts';
4
- import { configValidator, getDomainSocketPathLengthWarning } from '../validation/configValidator.ts';
4
+ import {
5
+ configValidator,
6
+ getDomainSocketPathLengthWarning,
7
+ isLegacySqlApplicationEntry,
8
+ } from '../validation/configValidator.ts';
9
+ import { isReservedComponentName } from '../utility/componentNames.ts';
5
10
  import fs from 'fs-extra';
6
11
  import YAML from 'yaml';
7
12
  import path from 'path';
8
- import { threadId } from 'node:worker_threads';
13
+ import { constants as osConstants } from 'node:os';
14
+ import { isMainThread, threadId } from 'node:worker_threads';
9
15
  import { randomBytes } from 'node:crypto';
10
16
  import isNumber from 'is-number';
11
17
  import propertiesReaderModule from 'properties-reader';
@@ -23,8 +29,9 @@ import { server } from '../server/Server.ts';
23
29
  import { getBackupDirPath } from './configHelpers.ts';
24
30
  import { PACKAGE_ROOT } from '../utility/packageUtils.js';
25
31
  import * as env from '../utility/environment/environmentManager.ts';
26
- import { applyRuntimeEnvConfig, hasPersistedEnvConfigState } from './harperConfigEnvVars.ts';
32
+ import { prepareRuntimeEnvConfig, hasPersistedEnvConfigState, discardConfigState } from './harperConfigEnvVars.ts';
27
33
  import { warnComponentEnvConfigVars, resolveConfiguredPath } from './componentEnvPrepass.ts';
34
+ import { isStartableThreadHeapMemory } from '../server/threads/threadHeapMemory.ts';
28
35
 
29
36
  const { DATABASES_PARAM_CONFIG, CONFIG_PARAMS, CONFIG_PARAM_MAP } = hdbTerms;
30
37
  const UNINIT_GET_CONFIG_ERR = 'Unable to get config value because config is uninitialized';
@@ -85,63 +92,191 @@ export function getConfigPath(param: string) {
85
92
  return path.resolve(rootPath, value);
86
93
  }
87
94
 
88
- // Write atomically via temp file + rename so readers don't observe a truncated/empty file.
89
- // Temp path includes randomness so two worker threads in the same process (same pid) writing
90
- // in the same millisecond can't collide on the temp name and then race the rename.
91
- //
95
+ // Write atomically via a randomized temp file + rename so readers do not observe partial content
96
+ // and concurrent workers, which share process.pid, do not collide on a temp path.
92
97
  // Windows has no POSIX-style "replace an open file" semantics: rename() fails with
93
- // EPERM/EACCES if another thread/process has the destination momentarily open for read.
94
- // Every worker thread runs its own RootConfigWatcher (chokidar), so a write on one thread
95
- // routinely races a hot-reload read on another; Windows Defender / AV real-time scanning can
96
- // hold a similar transient handle. Retry with exponential backoff to ride out the race -
97
- // callers are synchronous, so the wait is a synchronous sleep rather than an async one.
98
- // The budget must outlast a single AV real-time scan pass (seconds, not hundreds of ms):
99
- // the previous ~910ms budget was exhausted twice in a row by the same test on a CI runner
100
- // (harper#2036), so the worst case is now ~3.6s.
101
- const RENAME_RETRY_MAX_ATTEMPTS = 12;
98
+ // EPERM/EACCES/EBUSY while another worker or AV holds the destination open. Root config watchers use
99
+ // readConfigFileSync so this blocking retry cannot wait on a read owned by its own worker.
100
+ // The budget is the wall-clock window of the 12-attempt schedule it replaced
101
+ // (10+20+40+80+160+320+500*6), so it stays a deadline rather than an attempt count without
102
+ // widening the stall: this loop blocks the calling worker's event loop, and `set_configuration`
103
+ // reaches it from a live request thread.
104
+ const RENAME_RETRY_BUDGET_MS = 3_630;
105
+ // Secondary guard only: stops a degenerate zero-delay option set from spinning the whole budget.
106
+ const RENAME_RETRY_MAX_ATTEMPTS = 25;
102
107
  const RENAME_RETRY_INITIAL_DELAY_MS = 10;
103
108
  const RENAME_RETRY_MAX_DELAY_MS = 500;
104
- // Never notified; exists only so Atomics.wait can time out (a synchronous, CPU-idle sleep).
109
+ // Never notified; Atomics.wait uses this only as a CPU-idle synchronous sleep.
105
110
  const renameRetrySleepBuffer = new Int32Array(new SharedArrayBuffer(4));
106
111
 
112
+ // Classified by code alone rather than gated to win32 like the read side: `process.platform` does
113
+ // not answer whether this filesystem can replace an open file — WSL drvfs, CIFS/SMB and Docker
114
+ // Desktop bind mounts all report `linux` and return these codes transiently.
115
+ function isRetryableRenameError(code: string): boolean {
116
+ return code === 'EPERM' || code === 'EACCES' || code === 'EBUSY';
117
+ }
118
+
119
+ type RenameRetryOptions = {
120
+ retryBudgetMs?: number;
121
+ maxRetries?: number;
122
+ initialDelayMs?: number;
123
+ maxDelayMs?: number;
124
+ };
125
+
126
+ type AtomicWriteOptions = RenameRetryOptions & {
127
+ skipIfUnchanged?: boolean;
128
+ };
129
+
130
+ function validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs }: RenameRetryOptions) {
131
+ const invalidOption =
132
+ !Number.isFinite(retryBudgetMs) ||
133
+ retryBudgetMs < 0 ||
134
+ (!Number.isFinite(maxRetries) && maxRetries !== Infinity) ||
135
+ maxRetries < 0 ||
136
+ !Number.isFinite(initialDelayMs) ||
137
+ initialDelayMs < 0 ||
138
+ !Number.isFinite(maxDelayMs) ||
139
+ maxDelayMs < 0;
140
+ if (invalidOption) {
141
+ throw new RangeError('rename retry options must be non-negative numbers');
142
+ }
143
+ }
144
+
145
+ // Linux has no libuv mapping for EDQUOT, so a quota-exhausted write surfaces as
146
+ // `Unknown system error -122` with an unusable `code`; the numeric errno is the portable signal
147
+ // (EDQUOT is 122 on Linux, 69 on macOS).
148
+ const STORAGE_EXHAUSTED_CODES = new Set(['ENOSPC', 'EDQUOT']);
149
+ // EDQUOT is absent from os.constants.errno on platforms without quotas, so filter before negating:
150
+ // -undefined is NaN, which would sit in the set matching nothing and reading as a bug.
151
+ const STORAGE_EXHAUSTED_ERRNOS = new Set(
152
+ [osConstants.errno.ENOSPC, osConstants.errno.EDQUOT].filter((errno) => errno !== undefined).map((errno) => -errno)
153
+ );
154
+
155
+ export function isStorageExhausted(error): boolean {
156
+ return STORAGE_EXHAUSTED_CODES.has(error?.code) || STORAGE_EXHAUSTED_ERRNOS.has(error?.errno);
157
+ }
158
+
159
+ // Boot-path persistence of derived config is best-effort: on an exhausted volume a fatal write here
160
+ // is an un-breakable restart loop, because freeing space needs a started process (#847).
161
+ export function persistConfigDuringBoot(artifactPath: string, write: () => void): boolean {
162
+ try {
163
+ write();
164
+ return true;
165
+ } catch (error) {
166
+ if (!isStorageExhausted(error)) throw error;
167
+ logger.error(
168
+ `Storage exhausted (${error.code ?? error.errno}) writing ${artifactPath}; continuing startup with the in-memory configuration. The file on disk is unchanged - free space on the Harper volume to restore config persistence.`
169
+ );
170
+ return false;
171
+ }
172
+ }
173
+
174
+ // Returns true when the file was written, false when an unchanged write was skipped.
107
175
  export function atomicWriteFile(
108
176
  filePath,
109
177
  content,
110
178
  {
179
+ retryBudgetMs = RENAME_RETRY_BUDGET_MS,
111
180
  maxRetries = RENAME_RETRY_MAX_ATTEMPTS,
112
181
  initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS,
113
182
  maxDelayMs = RENAME_RETRY_MAX_DELAY_MS,
114
- } = {}
183
+ skipIfUnchanged = false,
184
+ }: AtomicWriteOptions = {}
115
185
  ) {
186
+ // Before the temp write, so an option set that can never rename leaves no file behind.
187
+ validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
188
+ // Opt-in: skipping means no mtime bump, so no watcher event. Only callers that re-derive the
189
+ // same file every boot want that.
190
+ if (skipIfUnchanged && matchesFileContent(filePath, content)) return false;
116
191
  const tempPath = `${filePath}.${process.pid}.${threadId}.${randomBytes(4).toString('hex')}.tmp`;
117
- fs.writeFileSync(tempPath, content);
192
+ try {
193
+ fs.writeFileSync(tempPath, content);
194
+ } catch (err) {
195
+ // The open succeeds before the write runs out of room, leaving the temp file behind.
196
+ removeTempFile(tempPath);
197
+ throw err;
198
+ }
199
+ try {
200
+ renameWithRetry(tempPath, filePath, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
201
+ } catch (err) {
202
+ // The temp name carries fresh randomness on every call, so a spent budget would otherwise
203
+ // leave a file nothing else will ever collect.
204
+ removeTempFile(tempPath);
205
+ throw err;
206
+ }
207
+ return true;
208
+ }
209
+
210
+ export function renameWithRetry(
211
+ fromPath,
212
+ toPath,
213
+ {
214
+ retryBudgetMs = RENAME_RETRY_BUDGET_MS,
215
+ maxRetries = RENAME_RETRY_MAX_ATTEMPTS,
216
+ initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS,
217
+ maxDelayMs = RENAME_RETRY_MAX_DELAY_MS,
218
+ }: RenameRetryOptions = {}
219
+ ) {
220
+ validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
118
221
  let retries = maxRetries;
119
222
  let delayMs = initialDelayMs;
223
+ let retryDeadline;
224
+ let finalAttempt = false;
225
+ let attempts = 0;
226
+ const startedAt = performance.now();
120
227
  while (true) {
121
228
  try {
122
- fs.renameSync(tempPath, filePath);
123
- break;
229
+ attempts++;
230
+ fs.renameSync(fromPath, toPath);
231
+ return;
124
232
  } catch (err) {
125
- if (retries > 0 && (err.code === 'EPERM' || err.code === 'EACCES')) {
233
+ if (!finalAttempt && retries > 0 && isRetryableRenameError(err.code)) {
126
234
  retries--;
127
- // Sleep synchronously (all call sites are sync) to allow the holder to close the
128
- // file. Atomics.wait yields the thread to the OS instead of spinning the CPU,
129
- // which is what makes a multi-second worst-case budget affordable.
130
- if (delayMs > 0) Atomics.wait(renameRetrySleepBuffer, 0, 0, delayMs);
131
- delayMs = Math.min(delayMs * 2, maxDelayMs);
132
- continue;
235
+ if (retryDeadline === undefined) {
236
+ retryDeadline = performance.now() + retryBudgetMs;
237
+ }
238
+ const remainingBudgetMs = retryDeadline - performance.now();
239
+ if (remainingBudgetMs > 0) {
240
+ // Sleep synchronously (all call sites are sync) to allow the holder to close the
241
+ // file. Atomics.wait yields the thread to the OS instead of spinning the CPU,
242
+ // which is what makes a multi-second worst-case budget affordable.
243
+ const sleepMs = Math.min(delayMs, remainingBudgetMs);
244
+ finalAttempt = sleepMs === remainingBudgetMs;
245
+ if (sleepMs > 0) Atomics.wait(renameRetrySleepBuffer, 0, 0, sleepMs);
246
+ delayMs = Math.min(Math.max(delayMs * 2, RENAME_RETRY_INITIAL_DELAY_MS), maxDelayMs);
247
+ continue;
248
+ }
133
249
  }
134
- // if it fails we should clean up the tmp file
135
- try {
136
- fs.unlinkSync(tempPath);
137
- } catch {
138
- // ignore cleanup errors
250
+ // Whether the budget was spent or the code was never retryable is the difference
251
+ // between a holder that never released and a one-off failure, and neither survives on
252
+ // the rethrown error.
253
+ if (isRetryableRenameError(err.code)) {
254
+ logger.warn(
255
+ `Could not replace ${toPath}: ${err.code} after ${attempts} attempts over ${Math.round(performance.now() - startedAt)}ms`
256
+ );
139
257
  }
140
258
  throw err;
141
259
  }
142
260
  }
143
261
  }
144
262
 
263
+ function removeTempFile(tempPath) {
264
+ try {
265
+ fs.unlinkSync(tempPath);
266
+ } catch {
267
+ // ignore cleanup errors
268
+ }
269
+ }
270
+
271
+ function matchesFileContent(filePath, content): boolean {
272
+ if (typeof content !== 'string') return false;
273
+ try {
274
+ return fs.readFileSync(filePath, 'utf8') === content;
275
+ } catch {
276
+ return false;
277
+ }
278
+ }
279
+
145
280
  /**
146
281
  * Builds the Harper config file using user inputs and default values from defaultConfig.yaml
147
282
  * @param args - any args that the user provided.
@@ -154,7 +289,7 @@ export function createConfigFile(args, skipFsValidation = false) {
154
289
  // Loop through the user inputted args. Match them to a parameter in the default config file and update value.
155
290
  let schemasArgs;
156
291
  for (const arg in args) {
157
- let configParam = CONFIG_PARAM_MAP[arg.toLowerCase()];
292
+ let configParam = lookupConfigParam(arg);
158
293
 
159
294
  // Schemas config args are handled differently, so if they exist set them to var that will be used by setSchemasConfig
160
295
  if (configParam === CONFIG_PARAMS.DATABASES) {
@@ -169,7 +304,7 @@ export function createConfigFile(args, skipFsValidation = false) {
169
304
  continue;
170
305
  }
171
306
 
172
- if (!configParam && (arg.endsWith('_package') || arg.endsWith('_port'))) {
307
+ if (!configParam && isSuffixEscapedParam(arg)) {
173
308
  configParam = arg;
174
309
  }
175
310
 
@@ -273,7 +408,7 @@ export function getDefaultConfig(param: string) {
273
408
  flatDefaultConfigObj = flattenConfig(configDoc.toJSON());
274
409
  }
275
410
 
276
- const paramMap = CONFIG_PARAM_MAP[param.toLowerCase()];
411
+ const paramMap = lookupConfigParam(param);
277
412
  if (paramMap === undefined) return undefined;
278
413
 
279
414
  return flatDefaultConfigObj[paramMap.toLowerCase()];
@@ -297,7 +432,7 @@ export function getConfigValue(param: string | null | undefined) {
297
432
  return undefined;
298
433
  }
299
434
 
300
- const paramMap = CONFIG_PARAM_MAP[param.toLowerCase()];
435
+ const paramMap = lookupConfigParam(param);
301
436
  if (paramMap === undefined) return undefined;
302
437
 
303
438
  return flatConfigObj[paramMap.toLowerCase()];
@@ -352,7 +487,10 @@ export function ensureConfigKeysPresent(keys: string[]): string[] {
352
487
  }
353
488
  if (added.length === 0) return [];
354
489
 
355
- atomicWriteFile(configFilePath, String(configDoc));
490
+ // Worker threads re-read the config from disk and never run this backfill, so a key that only
491
+ // exists in this thread's memory activates nowhere that serves requests: report nothing when the
492
+ // write was refused rather than logging an activation the request path did not get.
493
+ if (!persistConfigDuringBoot(configFilePath, () => atomicWriteFile(configFilePath, String(configDoc)))) return [];
356
494
 
357
495
  // Mirror the additions into the already-memoized config so a built-in gated on the new key
358
496
  // activates on the CURRENT boot: componentLoader reads the root config from getConfigObj(),
@@ -572,7 +710,7 @@ function checkForUpdatedConfig(configDoc, configFilePath) {
572
710
  HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR
573
711
  );
574
712
  }
575
- atomicWriteFile(configFilePath, String(configDoc));
713
+ persistConfigDuringBoot(configFilePath, () => atomicWriteFile(configFilePath, String(configDoc)));
576
714
  }
577
715
  }
578
716
 
@@ -676,6 +814,10 @@ function validateConfig(configDoc, skipFsValidation = false) {
676
814
  configDoc.setIn(['operationsApi', 'network', 'domainSocket'], domainSocket);
677
815
  const domainSocketWarning = getDomainSocketPathLengthWarning(validation.value.rootPath, domainSocket);
678
816
  if (domainSocketWarning) logger.warn(domainSocketWarning);
817
+ if (isLegacySqlApplicationEntry(configJson.sql))
818
+ logger.warn(
819
+ "The root config entry 'sql' is an application, but 'sql' now configures Harper's SQL engine. Redeploy that application under a different name and remove the 'sql' entry; until then the SQL engine settings cannot be configured."
820
+ );
679
821
  }
680
822
 
681
823
  /**
@@ -690,7 +832,7 @@ export function updateConfigObject(param: string, value: any) {
690
832
  flatConfigObj = {};
691
833
  }
692
834
 
693
- const configObjKey = CONFIG_PARAM_MAP[param.toLowerCase()];
835
+ const configObjKey = lookupConfigParam(param);
694
836
  if (configObjKey === undefined) {
695
837
  logger.trace(`Unable to update config object because config param '${param}' does not exist`);
696
838
  return;
@@ -734,6 +876,71 @@ export function updateConfigObject(param: string, value: any) {
734
876
  }
735
877
  }
736
878
 
879
+ /**
880
+ * Canonical config param for an arg name, or `undefined` when the name is not a config param.
881
+ * `Object.hasOwn` because a bare lookup resolves inherited names: `constructor` yields an
882
+ * `Object.prototype` member that then fails `.split('_')` or `.toLowerCase()`.
883
+ */
884
+ function lookupConfigParam(arg: string): string | undefined {
885
+ if (typeof arg !== 'string') return undefined;
886
+ const name = arg.toLowerCase();
887
+ return Object.hasOwn(CONFIG_PARAM_MAP, name) ? CONFIG_PARAM_MAP[name] : undefined;
888
+ }
889
+
890
+ const COMPONENT_PARAM_SUFFIXES = ['_package', '_port'];
891
+
892
+ function suffixEscapedComponentName(arg: string): string | undefined {
893
+ if (typeof arg !== 'string') return undefined;
894
+ const suffix = COMPONENT_PARAM_SUFFIXES.find((candidate) => arg.endsWith(candidate));
895
+ if (suffix === undefined) return undefined;
896
+ const component = arg.slice(0, -suffix.length);
897
+ return component === '' ? undefined : component;
898
+ }
899
+
900
+ /**
901
+ * Component entries (`my-component_package`, `my-component_port`) are operator-named, so they
902
+ * cannot be enumerated in CONFIG_PARAM_MAP and bypass it. This escape is the one way to write a
903
+ * root component entry without going through deploy_component, so a reserved name is excluded.
904
+ */
905
+ function isSuffixEscapedParam(arg: string): boolean {
906
+ const component = suffixEscapedComponentName(arg);
907
+ return component !== undefined && !isReservedComponentName(component);
908
+ }
909
+
910
+ function findReservedComponentParams(args: object): string[] {
911
+ const reserved = [];
912
+ for (const arg in args) {
913
+ if (!Object.hasOwn(args, arg)) continue;
914
+ const component = suffixEscapedComponentName(arg);
915
+ if (component !== undefined && isReservedComponentName(component)) reserved.push(arg);
916
+ }
917
+ return reserved;
918
+ }
919
+
920
+ const MAX_REPORTED_UNRECOGNIZED = 10;
921
+
922
+ /**
923
+ * Render unrecognized names for an error that also reaches the operations log: control characters
924
+ * are stripped so a name containing a newline cannot forge a log line, and the list is capped so a
925
+ * body carrying thousands of unknown keys cannot produce an unbounded message.
926
+ */
927
+ function describeUnrecognized(names: string[]): string {
928
+ const shown = names
929
+ .slice(0, MAX_REPORTED_UNRECOGNIZED)
930
+ // eslint-disable-next-line no-control-regex
931
+ .map((name) => name.replace(/[\u0000-\u001f\u007f]/g, '?'));
932
+ const remaining = names.length - shown.length;
933
+ return remaining > 0 ? `${shown.join(', ')} (and ${remaining} more)` : shown.join(', ');
934
+ }
935
+
936
+ function findUnrecognizedParams(args: object): string[] {
937
+ let unrecognized;
938
+ for (const arg in args) {
939
+ if (lookupConfigParam(arg) === undefined && !isSuffixEscapedParam(arg)) (unrecognized ??= []).push(arg);
940
+ }
941
+ return unrecognized ?? [];
942
+ }
943
+
737
944
  /**
738
945
  * Updates and validates a config value in config file. Can also create a backup of config before updating.
739
946
  * @param param - the config value to update
@@ -794,7 +1001,7 @@ export function updateConfigValue(
794
1001
  if (skipParamMap) {
795
1002
  configParam = param;
796
1003
  } else {
797
- configParam = CONFIG_PARAM_MAP[param.toLowerCase()];
1004
+ configParam = lookupConfigParam(param);
798
1005
  if (configParam === undefined) {
799
1006
  throw handleHDBError(
800
1007
  new Error(),
@@ -813,7 +1020,7 @@ export function updateConfigValue(
813
1020
  } else {
814
1021
  // Loop through the user inputted args. Match them to a parameter in the default config file and update value.
815
1022
  for (const arg in parsedArgs) {
816
- let configParam = CONFIG_PARAM_MAP[arg.toLowerCase()];
1023
+ let configParam = lookupConfigParam(arg);
817
1024
 
818
1025
  // If setting http.securePort to the same value as http.port, set http.port to null to avoid clashing ports
819
1026
  if (
@@ -845,7 +1052,7 @@ export function updateConfigValue(
845
1052
  }
846
1053
  }
847
1054
 
848
- if (!configParam && (arg.endsWith('_package') || arg.endsWith('_port'))) {
1055
+ if (!configParam && isSuffixEscapedParam(arg)) {
849
1056
  configParam = arg;
850
1057
  }
851
1058
 
@@ -1039,14 +1246,54 @@ export function getConfiguration() {
1039
1246
  return configDoc.toJSON();
1040
1247
  }
1041
1248
 
1249
+ // `set_configuration` is the one config writer that also fans out (`replicated: true`), so a value
1250
+ // accepted here lands on every peer at once and the next rolling restart takes the whole cluster
1251
+ // down together (harper-pro#558). Boot-time writers are deliberately not gated the same way: config
1252
+ // that already exists has to stay bootable, so it is recovered at the point of use instead
1253
+ // (server/threads/threadHeapMemory.ts).
1254
+ function assertThreadHeapMemoryStartable(configFields) {
1255
+ for (const field in configFields) {
1256
+ const configParam = CONFIG_PARAM_MAP[field.toLowerCase()];
1257
+ let configured;
1258
+ if (configParam === CONFIG_PARAMS.THREADS_MAXHEAPMEMORY) configured = configFields[field];
1259
+ else if (configParam === CONFIG_PARAMS.THREADS) configured = readSectionHeapMemory(configFields[field]);
1260
+ else continue;
1261
+ const value = castConfigValue(CONFIG_PARAMS.THREADS_MAXHEAPMEMORY, configured);
1262
+ if (typeof value !== 'number' || isStartableThreadHeapMemory(value)) continue;
1263
+ throw handleHDBError(
1264
+ new Error(),
1265
+ HDB_ERROR_MSGS.CONFIG_VALIDATION(
1266
+ `'threads.maxHeapMemory' must be greater than or equal to ${hdbTerms.MIN_THREAD_HEAP_MEMORY_MB}`
1267
+ ),
1268
+ HTTP_STATUS_CODES.BAD_REQUEST,
1269
+ undefined,
1270
+ undefined,
1271
+ true
1272
+ );
1273
+ }
1274
+ }
1275
+
1276
+ // A whole `threads` section reaches the same config key, and `threads` canonicalizes to itself
1277
+ // rather than to `threads_count`. It arrives either as an object or as the JSON string
1278
+ // castConfigValue parses, and flattenConfig lowercases every key on the way back out, so the nested
1279
+ // name has to be matched the same way the top-level one is.
1280
+ function readSectionHeapMemory(section) {
1281
+ const parsed = castConfigValue(CONFIG_PARAMS.THREADS, section);
1282
+ if (!hdbUtils.isObject(parsed)) return undefined;
1283
+ for (const key in parsed) if (key.toLowerCase() === 'maxheapmemory') return parsed[key];
1284
+ }
1285
+
1042
1286
  /**
1043
1287
  * Set Configuration - this function sets new configuration
1044
1288
  * @param setConfigJson
1045
1289
 
1046
1290
  */
1047
1291
  export async function setConfiguration(setConfigJson) {
1292
+ // `hdb_auth_header` is the 4.x spelling of `hdbAuthHeader`, and `impersonate` is a generic
1293
+ // operation-body field (server/operationsServer.ts): control fields, never config params.
1048
1294
  // eslint-disable-next-line no-unused-vars
1049
- const { operation, hdb_user, hdbAuthHeader, replicated, ...configFields } = setConfigJson;
1295
+ const { operation, hdb_user, hdbAuthHeader, hdb_auth_header, impersonate, replicated, ...configFields } =
1296
+ setConfigJson;
1050
1297
  // Operation-control field, not a config param: enforce boolean (matching other
1051
1298
  // `replicated` surfaces, e.g. analyticsValidator) before any local write so a
1052
1299
  // malformed value like the string "false" — which is truthy — can't apply config
@@ -1061,6 +1308,32 @@ export async function setConfiguration(setConfigJson) {
1061
1308
  true
1062
1309
  );
1063
1310
  }
1311
+ const reservedComponentParams = findReservedComponentParams(configFields);
1312
+ if (reservedComponentParams.length > 0) {
1313
+ throw handleHDBError(
1314
+ new Error(),
1315
+ `Unable to update config, cannot configure a component whose name is reserved for Harper's own configuration section: ${describeUnrecognized(reservedComponentParams)}`,
1316
+ HTTP_STATUS_CODES.BAD_REQUEST,
1317
+ undefined,
1318
+ undefined,
1319
+ true
1320
+ );
1321
+ }
1322
+ // Before any local write: the writer skips names it cannot resolve, so a request mixing
1323
+ // recognized and unrecognized names would otherwise apply the recognized half and still report
1324
+ // success.
1325
+ const unrecognized = findUnrecognizedParams(configFields);
1326
+ if (unrecognized.length > 0) {
1327
+ throw handleHDBError(
1328
+ new Error(),
1329
+ `Unable to update config, unrecognized config parameter${unrecognized.length > 1 ? 's' : ''}: ${describeUnrecognized(unrecognized)}`,
1330
+ HTTP_STATUS_CODES.BAD_REQUEST,
1331
+ undefined,
1332
+ undefined,
1333
+ true
1334
+ );
1335
+ }
1336
+ assertThreadHeapMemoryStartable(configFields);
1064
1337
  try {
1065
1338
  updateConfigValue(undefined, undefined, configFields, true);
1066
1339
  if (replicated) {
@@ -1151,9 +1424,16 @@ function applyRuntimeEnvVarConfig(configDoc, configFilePath, options = {}) {
1151
1424
  // Convert to JSON for processing
1152
1425
  const configObj = configDoc.toJSON();
1153
1426
 
1427
+ let saveEnvConfigState;
1428
+ let confirmEnvConfigState;
1429
+ let commitEnvConfigState;
1154
1430
  try {
1155
1431
  // Apply env vars with source tracking and drift detection
1156
- applyRuntimeEnvConfig(configObj, rootPath, options);
1432
+ ({
1433
+ saveState: saveEnvConfigState,
1434
+ confirmConfigWritten: confirmEnvConfigState,
1435
+ commitState: commitEnvConfigState,
1436
+ } = prepareRuntimeEnvConfig(configObj, rootPath, options));
1157
1437
 
1158
1438
  // If securePort was set to the same value as port, auto-null port to avoid clashing
1159
1439
  if (configObj.http?.port && configObj.http?.port === configObj.http?.securePort) {
@@ -1185,11 +1465,18 @@ function applyRuntimeEnvVarConfig(configDoc, configFilePath, options = {}) {
1185
1465
  throw error;
1186
1466
  }
1187
1467
 
1188
- // We're done here if no config file to write to
1468
+ // Install has no config file yet and no process to keep alive, so its snapshot write stays
1469
+ // mandatory - an install that never recorded originals should fail, not proceed.
1189
1470
  if (!configFilePath) {
1471
+ commitEnvConfigState();
1190
1472
  return;
1191
1473
  }
1192
1474
 
1475
+ // Every worker thread runs initConfig and derives the same merged config, so letting them all
1476
+ // persist it means N threads racing over one pair of files for a result they already agree on.
1477
+ // The main thread owns the on-disk copy; a worker runs on the in-memory one.
1478
+ if (!isMainThread) return;
1479
+
1193
1480
  // Persist changes to file
1194
1481
  try {
1195
1482
  if (configDoc.errors?.length > 0) {
@@ -1199,8 +1486,30 @@ function applyRuntimeEnvVarConfig(configDoc, configFilePath, options = {}) {
1199
1486
  HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR
1200
1487
  );
1201
1488
  }
1202
- atomicWriteFile(configFilePath, String(configDoc));
1203
- logger.debug('Config file updated with runtime env var values');
1489
+ // Stage, write the file, promote by rename: the confirmed state is the only record of the
1490
+ // file's pre-env values, so no write an exhausted volume can refuse may stand between it and
1491
+ // disk. See DESIGN.md, boot-path config persistence.
1492
+ let stateStaged = false;
1493
+ if (persistConfigDuringBoot(`${rootPath} env config state`, () => (stateStaged = saveEnvConfigState()))) {
1494
+ let configPersisted = false;
1495
+ let configRewritten = false;
1496
+ try {
1497
+ configPersisted = persistConfigDuringBoot(configFilePath, () => {
1498
+ configRewritten = atomicWriteFile(configFilePath, String(configDoc), { skipIfUnchanged: true });
1499
+ });
1500
+ } finally {
1501
+ if (!configPersisted && stateStaged) discardConfigState(rootPath as string);
1502
+ }
1503
+ if (configPersisted) {
1504
+ if (stateStaged) confirmEnvConfigState();
1505
+ // Distinguished, because this is the line that answers "did something rewrite my config?"
1506
+ logger.debug(
1507
+ configRewritten
1508
+ ? 'Config file updated with runtime env var values'
1509
+ : 'Config file already matched the runtime env var values'
1510
+ );
1511
+ }
1512
+ }
1204
1513
  } catch (error) {
1205
1514
  logger.error(`Failed to write config file after applying runtime env vars: ${error.message}`);
1206
1515
  throw error;