@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
@@ -13,7 +13,12 @@ const { ENV_ENCRYPTED_PREFIX } = require('../utility/envFile.ts');
13
13
  // File and project names can only be alphanumeric, dash and underscores. Both patterns are shared
14
14
  // with the CLI (utility/componentNames.ts): `harper deploy setup=true` resolves a project name and a
15
15
  // credential host client-side, and has to reject exactly what these schemas would.
16
- const { PROJECT_NAME_PATTERN: PROJECT_FILE_NAME_REGEX, GIT_HOST_PATTERN } = require('../utility/componentNames.ts');
16
+ const {
17
+ PROJECT_NAME_PATTERN: PROJECT_FILE_NAME_REGEX,
18
+ GIT_HOST_PATTERN,
19
+ isReservedComponentName,
20
+ } = require('../utility/componentNames.ts');
21
+ const { assertBranchedDatabases } = require('./Application.ts');
17
22
 
18
23
  // dotenv's accepted key character set. Restricting keys to this prevents a crafted key (e.g. one
19
24
  // containing `=` or a newline) from injecting extra assignments into a .env file.
@@ -24,6 +29,12 @@ const ENV_KEY_REGEX = /^[\w.-]+$/;
24
29
  const HOSTNAME_SCHEMA = Joi.string().hostname();
25
30
  const IPV6_SCHEMA = Joi.string().ip({ version: 'ipv6' });
26
31
 
32
+ // A deployment id names both an hdb_deployment row and a staging directory, so it is held to the shape
33
+ // the recorder mints — `randomUUID()`, which is lowercase. LOWERCASE ONLY, because a case-insensitive
34
+ // filesystem resolves an uppercase id to the real directory while every string compare against it fails:
35
+ // the retention pin would not recognise the artifact the request is about to activate, and would evict it.
36
+ const DEPLOYMENT_ID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
37
+
27
38
  module.exports = {
28
39
  getDropCustomFunctionValidator,
29
40
  setCustomFunctionValidator,
@@ -73,6 +84,31 @@ function checkProjectExists(checkExists, project, helpers) {
73
84
  }
74
85
  }
75
86
 
87
+ // Drop/package/file operations deliberately don't call this: an application deployed under the
88
+ // name before it was reserved has to stay removable.
89
+ function checkReservedProjectName(project, helpers) {
90
+ if (isReservedComponentName(project)) return helpers.message(HDB_ERROR_MSGS.RESERVED_PROJECT_NAME(project));
91
+ return project;
92
+ }
93
+
94
+ /**
95
+ * The file writers create the project directory as a side effect of writing into it, so they refuse
96
+ * only the creation — an application that already holds the name stays editable until it is
97
+ * migrated off it.
98
+ */
99
+ function checkReservedProjectCreation(project, helpers) {
100
+ if (!isReservedComponentName(project)) return project;
101
+ let projectDir;
102
+ try {
103
+ const componentsRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
104
+ if (componentsRoot) projectDir = path.join(componentsRoot, project);
105
+ } catch (err) {
106
+ hdbLogger.error(err);
107
+ }
108
+ if (projectDir && fs.existsSync(projectDir)) return project;
109
+ return helpers.message(HDB_ERROR_MSGS.RESERVED_PROJECT_NAME(project));
110
+ }
111
+
76
112
  function checkFilePath(path, helpers) {
77
113
  if (path.includes('..')) return helpers.message('Invalid file path');
78
114
  return path;
@@ -154,6 +190,7 @@ function setComponentFileValidator(req) {
154
190
  const setCompSchema = Joi.object({
155
191
  project: Joi.string()
156
192
  .pattern(PROJECT_FILE_NAME_REGEX)
193
+ .custom(checkReservedProjectCreation)
157
194
  .required()
158
195
  .messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
159
196
  file: Joi.string().custom(checkFilePath).required(),
@@ -214,6 +251,7 @@ function setEnvValueValidator(req) {
214
251
  const schema = Joi.object({
215
252
  project: Joi.string()
216
253
  .pattern(PROJECT_FILE_NAME_REGEX)
254
+ .custom(checkReservedProjectCreation)
217
255
  .required()
218
256
  .messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
219
257
  file: Joi.string().custom(checkFilePath).optional(),
@@ -330,6 +368,7 @@ function addComponentValidator(req) {
330
368
  const addFuncSchema = Joi.object({
331
369
  project: Joi.string()
332
370
  .pattern(PROJECT_FILE_NAME_REGEX)
371
+ .custom(checkReservedProjectName)
333
372
  .custom(checkProjectExists.bind(null, false))
334
373
  .required()
335
374
  .messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
@@ -455,10 +494,36 @@ function deployComponentValidator(req) {
455
494
  const deployProjSchema = Joi.object({
456
495
  project: Joi.string()
457
496
  .pattern(PROJECT_FILE_NAME_REGEX)
497
+ .custom(checkReservedProjectName)
458
498
  .required()
459
499
  .messages({ 'string.pattern.base': HDB_ERROR_MSGS.BAD_PROJECT_NAME }),
460
500
  package: Joi.string().optional(),
461
- restart: Joi.alternatives().try(Joi.boolean(), Joi.string().valid('rolling')).optional(),
501
+ // `activate: false` builds and certifies the component, then stops, leaving an artifact a later
502
+ // request activates by `deployment_id`. STRICT: `validateBySchema` throws away Joi's converted
503
+ // value, so a coercing boolean would let `activate: "false"` validate here and then read as truthy
504
+ // in the handler — deploying the very release the caller asked not to make live.
505
+ activate: Joi.boolean().strict().optional(),
506
+ // Activate a build staged earlier by `activate: false`. It becomes a path segment
507
+ // (`.deploy-staging/<id>/<component>`), so the charset is pinned as tightly as the value it names.
508
+ deployment_id: Joi.string().pattern(DEPLOYMENT_ID_REGEX).optional().messages({
509
+ 'string.pattern.base': `'deployment_id' must be a UUID`,
510
+ }),
511
+ // Set by deployComponent from the origin's deployment row and carried to peers by replication.
512
+ // Validated because it is a path segment on EVERY deploy and unknown keys are admitted, so an
513
+ // operator-supplied one would otherwise reach the filesystem unchecked.
514
+ _deploymentId: Joi.string().pattern(DEPLOYMENT_ID_REGEX).optional().messages({
515
+ 'string.pattern.base': `'_deploymentId' must be a UUID`,
516
+ }),
517
+ restart: Joi.alternatives()
518
+ .try(Joi.boolean(), Joi.string().valid('rolling'))
519
+ .optional()
520
+ // Nothing goes live, so there is nothing to restart into.
521
+ .when('activate', {
522
+ is: false,
523
+ then: Joi.forbidden().messages({
524
+ 'any.unknown': `'restart' cannot be combined with 'activate: false': staging does not change the running component`,
525
+ }),
526
+ }),
462
527
  install_command: Joi.string().optional(),
463
528
  install_timeout: Joi.number().optional(),
464
529
  install_allow_scripts: Joi.boolean().optional(),
@@ -496,6 +561,21 @@ function deployComponentValidator(req) {
496
561
  })
497
562
  .optional()
498
563
  .messages({ 'string.bracketedHost': '{#label} must not be bracketed; use the bare IPv6 literal' }),
564
+ // Databases this application forks a private copy of (harper#642/#643). Deployment routing
565
+ // like urlPath/host above, not component config: reusing assertBranchedDatabases here (rather
566
+ // than re-deriving its rules in Joi) gives a synchronous 400 at deploy time instead of a load
567
+ // failure discovered much later and disconnected from the request that caused it.
568
+ isolated: Joi.boolean().optional(),
569
+ branchedDatabases: Joi.custom((value, helpers) => {
570
+ try {
571
+ assertBranchedDatabases(req.project, value);
572
+ } catch (error) {
573
+ return helpers.error('any.invalid', { reason: error.message });
574
+ }
575
+ return value;
576
+ }, 'branchedDatabases')
577
+ .optional()
578
+ .messages({ 'any.invalid': '{{#reason}}' }),
499
579
  // Deploy credentials. The array is kind-heterogeneous: an entry's kind is implied by its
500
580
  // identifying key rather than a separate discriminator field, so a new kind is added as
501
581
  // another item alternative here without reshaping the field. Today: npm registry auth
@@ -527,7 +607,22 @@ function deployComponentValidator(req) {
527
607
  }),
528
608
  })
529
609
  .with('urlPath', 'package')
530
- .with('host', 'package');
610
+ .with('host', 'package')
611
+ // An activation has no build inputs and takes no routing or packaging opinions: the artifact's own
612
+ // descriptor is the authority for what it publishes, and silently ignoring these would hide that.
613
+ .without('deployment_id', [
614
+ 'package',
615
+ 'payload',
616
+ 'activate',
617
+ 'install_command',
618
+ 'install_timeout',
619
+ 'install_allow_scripts',
620
+ 'credentials',
621
+ 'urlPath',
622
+ 'host',
623
+ 'branchedDatabases',
624
+ 'isolated',
625
+ ]);
531
626
 
532
627
  return validator.validateBySchema(req, deployProjSchema);
533
628
  }
@@ -1,4 +1,4 @@
1
- import { join, relative, sep } from 'node:path';
1
+ import { basename, join, relative, sep } from 'node:path';
2
2
  import { stat, readdir } from 'node:fs/promises';
3
3
  import { Readable, pipeline } from 'node:stream';
4
4
  import tar from 'tar-fs';
@@ -13,6 +13,27 @@ const DEFAULT_OPTIONS: PackageOptions = { skip_node_modules: false, skip_symlink
13
13
 
14
14
  const WEBPACK_CACHE_SEGMENT = join('cache', 'webpack');
15
15
 
16
+ /**
17
+ * `node_modules/harper` is Harper's, whatever is on disk there.
18
+ *
19
+ * Every non-root component load runs `symlinkHarperModule`, which does `rmSync(harperModule, {
20
+ * recursive: true, force: true })` and then links the running install (`components/componentLoader.ts`).
21
+ * It does that whether or not the path already existed, so component content there survives no load and
22
+ * packaging it cannot be preserving component source — while packaging it walks the whole install, since
23
+ * the packer dereferences symlinks and recurses into linked directories.
24
+ *
25
+ * `harperdb` is deliberately NOT here, and the asymmetry is the reason: the loader only *repairs*
26
+ * `node_modules/harperdb` when that path already exists. A legacy component shipping a real
27
+ * npm-installed `harperdb` that has never been loaded on this host therefore owns it — and excluding it
28
+ * would strip it from the tarball, after which `installApplication` skips installing (it returns early
29
+ * when `node_modules` exists), the loader's present-gate never fires, and `import from 'harperdb'`
30
+ * fails on the target — and that holds whether the path is skipped by name or by resolved identity,
31
+ * because an archive that simply OMITS it cannot restore it. So a `harperdb` link to the install is
32
+ * still followed. Closing that means packing a placeholder at the path so the loader's present-gate can
33
+ * fire on the target, not skipping it; tracked in harper#2577.
34
+ */
35
+ const INSTALL_OWNED_PATH = join('node_modules', 'harper');
36
+
16
37
  /**
17
38
  * Whether `fullPath` (an absolute path under `directory`) should be excluded from the package when
18
39
  * `skip_node_modules` is set. The path is first made relative to `directory`, so packaging a component
@@ -21,6 +42,9 @@ const WEBPACK_CACHE_SEGMENT = join('cache', 'webpack');
21
42
  * excluded the whole tree. Shared by the stream packer and the directory walk so they cannot diverge.
22
43
  */
23
44
  function isExcluded(directory: string, fullPath: string, options: PackageOptions): boolean {
45
+ // Unconditional, including under `skip_symlinks`: packed literally, the link ships an absolute path to
46
+ // the packaging host's install root, which arrives dangling.
47
+ if (basename(fullPath) === 'harper' && relative(directory, fullPath) === INSTALL_OWNED_PATH) return true;
24
48
  if (!options.skip_node_modules) return false;
25
49
  const rel = relative(directory, fullPath);
26
50
  return rel.split(sep).includes('node_modules') || rel.includes(WEBPACK_CACHE_SEGMENT);
@@ -24,6 +24,17 @@ export function requestRestart() {
24
24
  restartArrayBuffer.notify();
25
25
  }
26
26
 
27
+ export function requestRestartAfterDeploy(
28
+ isNewComponent: boolean,
29
+ packageMetadataChanged: boolean,
30
+ wasIsolated: boolean,
31
+ isIsolated: boolean
32
+ ): boolean {
33
+ if (!isNewComponent && !packageMetadataChanged && wasIsolated === isIsolated) return false;
34
+ requestRestart();
35
+ return true;
36
+ }
37
+
27
38
  export function restartNeeded() {
28
39
  ensureInitialized();
29
40
  return restartNeededArray[0] === 1;
@@ -5,6 +5,7 @@
5
5
  * centralized management of component health status.
6
6
  */
7
7
 
8
+ import { deployValidationStatusSink } from '../../server/serverHelpers/deployValidationState.ts';
8
9
  import { ComponentStatus } from './ComponentStatus.ts';
9
10
  import {
10
11
  type ComponentStatusLevel,
@@ -24,6 +25,37 @@ export type ComponentStatusMap = Map<string, ComponentStatus>;
24
25
  * Component Status Registry Class
25
26
  * Provides a centralized registry for managing component health status
26
27
  */
28
+ /**
29
+ * A status copy handed to a validation must share nothing mutable with the live entry. `Error` is reachable
30
+ * through the public `statusForComponent(...).get()` path, so candidate code could otherwise edit
31
+ * `message`, `cause` or a custom field on the serving component's own error object. Own enumerable
32
+ * properties are carried across; `cause` is flattened to a message, because an error graph of unknown depth
33
+ * is not worth cloning to hand a throwaway load its own read.
34
+ */
35
+ function detachError(error: Error | string | undefined): Error | string | undefined {
36
+ if (!(error instanceof Error)) return error;
37
+ const detached = new Error(error.message);
38
+ detached.name = error.name;
39
+ detached.stack = error.stack;
40
+ for (const [key, value] of Object.entries(error)) {
41
+ if (key === 'cause') continue;
42
+ // Detached too where it can be: copying an object property by reference would leave the candidate a
43
+ // mutable handle into the live error after all. Anything unclonable (a function, a class instance) is
44
+ // replaced by its string form rather than shared.
45
+ let detachedValue: unknown;
46
+ try {
47
+ detachedValue = structuredClone(value);
48
+ } catch {
49
+ detachedValue = typeof value === 'object' && value !== null ? String(value) : value;
50
+ }
51
+ (detached as unknown as Record<string, unknown>)[key] = detachedValue;
52
+ }
53
+ if (error.cause !== undefined) {
54
+ detached.cause = error.cause instanceof Error ? `${error.cause.name}: ${error.cause.message}` : error.cause;
55
+ }
56
+ return detached;
57
+ }
58
+
27
59
  export class ComponentStatusRegistry {
28
60
  private statusMap: ComponentStatusMap = new Map();
29
61
 
@@ -61,6 +93,16 @@ export class ComponentStatusRegistry {
61
93
  );
62
94
  }
63
95
 
96
+ // A deploy's pre-flight validation loads the CANDIDATE's code under the real component's name, so its
97
+ // writes would land on the live component. They go to the validation's own throwaway map instead —
98
+ // which is why this cannot simply be reverted afterwards: the live component keeps serving through
99
+ // that window, and `statusForComponent()` is a public API its own code may call at any time, so
100
+ // reverting would silently discard a genuine report from it.
101
+ const sink = deployValidationStatusSink();
102
+ if (sink) {
103
+ sink.set(componentName, new ComponentStatus(status, message, error));
104
+ return;
105
+ }
64
106
  this.statusMap.set(componentName, new ComponentStatus(status, message, error));
65
107
  }
66
108
 
@@ -68,6 +110,23 @@ export class ComponentStatusRegistry {
68
110
  * Get the current status of a component
69
111
  */
70
112
  public getStatus(componentName: string): ComponentStatus | undefined {
113
+ // Inside a validation the candidate reads its OWN writes, and never the live object: the returned
114
+ // `ComponentStatus` is mutable, so handing back the live one would let candidate code edit the
115
+ // serving component's status in place and bypass the diversion entirely. A component the candidate
116
+ // has not written yet is copied into the sink on first read for the same reason.
117
+ const sink = deployValidationStatusSink();
118
+ if (sink) {
119
+ const diverted = sink.get(componentName);
120
+ if (diverted) return diverted;
121
+ const live = this.statusMap.get(componentName);
122
+ if (!live) return undefined;
123
+ const copy = new ComponentStatus(live.status, live.message, detachError(live.error));
124
+ // A new Date, not the live one: `Date` is mutable, so sharing it would let candidate code reach
125
+ // back into the serving component's status through the copy.
126
+ copy.lastChecked = new Date(live.lastChecked);
127
+ sink.set(componentName, copy);
128
+ return copy;
129
+ }
71
130
  return this.statusMap.get(componentName);
72
131
  }
73
132
 
@@ -1,38 +1,124 @@
1
- import chokidar, { FSWatcher } from 'chokidar';
2
- import { readFile } from 'node:fs/promises';
1
+ import { FSWatcher } from 'chokidar';
3
2
  import { getConfigFilePath } from './configUtils.ts';
3
+ import { readConfigFileSync } from './readConfigFileSync.ts';
4
4
  import { EventEmitter, once } from 'node:events';
5
- import { parse } from 'yaml';
6
- import { POLLING_FALLBACK_OPTIONS, isWatcherExhaustionError, warnWatcherFallback } from '../utility/watcherFallback.ts';
5
+ import { parseConfigFile } from './parseConfigFile.ts';
6
+ import {
7
+ POLLING_FALLBACK_OPTIONS,
8
+ claimLostNativeWatchError,
9
+ guardedWatch,
10
+ isWatcherExhaustionError,
11
+ warnWatcherFallback,
12
+ } from '../utility/watcherFallback.ts';
13
+ import { resolveWatchTarget } from '../utility/watchPath.ts';
14
+ import { errorForLog, loggerWithTag } from '../utility/logging/harper_logger.ts';
15
+ import { ConfigReadRetry } from './configReadRetry.ts';
16
+ import { ArmGate } from './watcherArming.ts';
17
+
18
+ function isMissingFile(error: unknown): boolean {
19
+ return !!error && typeof error === 'object' && (error as NodeJS.ErrnoException).code === 'ENOENT';
20
+ }
21
+
22
+ // `harper_logger` imports this module at its own bottom to break their cycle, so a tagged logger
23
+ // built at module scope would run `loggerWithTag()` before `mainLogger` is initialized.
24
+ let taggedLogger: ReturnType<typeof loggerWithTag> | undefined;
25
+ function logger() {
26
+ return (taggedLogger ??= loggerWithTag('config-watcher'));
27
+ }
28
+
29
+ let sharedWatcher: RootConfigWatcher | undefined;
30
+
31
+ /**
32
+ * The isolate-wide watcher for the root config file. Consumers (logging, models) subscribe to this
33
+ * one instance instead of each opening their own native watcher — per-worker duplicates double the
34
+ * inotify/FD footprint and the synchronous read+parse work on every config write.
35
+ */
36
+ export function getSharedRootConfigWatcher(): RootConfigWatcher {
37
+ return (sharedWatcher ??= new RootConfigWatcher());
38
+ }
7
39
 
8
40
  export class RootConfigWatcher extends EventEmitter {
9
41
  #configFilePath: string;
42
+ #watchPath: string;
10
43
  #watcher!: FSWatcher;
11
44
  #config: any;
12
45
  #usingPolling: boolean;
13
46
  #closed: boolean;
14
47
  #openCount: number = 0;
48
+ #readCount: number = 0;
49
+ #readRetry: ConfigReadRetry = new ConfigReadRetry();
50
+ #armGate: ArmGate = new ArmGate();
51
+ // The gate above is chokidar's scan finishing; this is the barrier's gate. A terminal outcome
52
+ // opens it without claiming the watch is armed, so the arming re-read still runs afterwards.
53
+ #barrierOpen: boolean = false;
54
+ #configLoaded: boolean = false;
55
+ #readyStaged: boolean = false;
56
+ #readyEmitted: boolean = false;
15
57
  ready: Promise<any[]>;
16
58
 
17
- constructor() {
59
+ constructor(configFilePath: string = getConfigFilePath()) {
18
60
  super();
19
- this.#configFilePath = getConfigFilePath();
20
- this.#usingPolling = false;
61
+ this.#configFilePath = configFilePath;
62
+ const watchTarget = resolveWatchTarget(this.#configFilePath);
63
+ this.#watchPath = watchTarget.path;
64
+ this.#usingPolling = watchTarget.mustPoll;
21
65
  this.#closed = false;
22
66
  this.ready = once(this, 'ready');
23
67
  this.#openWatcher();
24
68
  }
25
69
 
26
70
  #openWatcher() {
27
- this.#openCount++;
28
- this.#watcher = chokidar
29
- .watch(this.#configFilePath, {
30
- persistent: false,
31
- ...(this.#usingPolling ? POLLING_FALLBACK_OPTIONS : {}),
32
- })
71
+ const generation = ++this.#openCount;
72
+ this.#watcher = guardedWatch(this.#watchPath, {
73
+ persistent: false,
74
+ ...(this.#usingPolling ? POLLING_FALLBACK_OPTIONS : {}),
75
+ })
33
76
  .on('add', this.handleChange.bind(this))
34
77
  .on('change', this.handleChange.bind(this))
35
- .on('error', this.handleError.bind(this));
78
+ .on('error', this.handleError.bind(this))
79
+ // Generation-bound: `#armGate.reset()` runs before the failed watcher is closed, so a
80
+ // `ready` still queued on it would arm the gate on the replacement's behalf and the
81
+ // replacement's own `ready` would then be a no-op — leaving its scan window unre-read.
82
+ .on('ready', () => this.#handleArmed(generation));
83
+ }
84
+
85
+ #handleArmed(generation: number) {
86
+ if (this.#closed || generation !== this.#openCount) return;
87
+ this.#armGate.arm(() => this.#markArmed());
88
+ }
89
+
90
+ #markArmed() {
91
+ this.#barrierOpen = true;
92
+ // A write that landed while the watch was unarmed was never reported, and a scan that found
93
+ // no file at all reported nothing either, so arming always re-reads rather than publishing
94
+ // what an earlier read staged — or, with no file, staying pending forever.
95
+ this.#read(true);
96
+ // A read that armed the ladder settles `ready` itself, with the newer config.
97
+ if (!this.#readRetry.pending) this.#emitReady();
98
+ }
99
+
100
+ #emitReady() {
101
+ if (this.#readyEmitted || !this.#barrierOpen || !this.#readyStaged || this.#closed) return;
102
+ this.#readyEmitted = true;
103
+ try {
104
+ this.emit('ready', this.#config);
105
+ } catch (error) {
106
+ logger().warn('A Harper configuration listener failed', errorForLog(error));
107
+ }
108
+ }
109
+
110
+ // `harper_logger.start()` awaits `ready` with no timeout, so every terminal outcome has to
111
+ // settle the barrier. A failed read discards an earlier staged value, but a watcher error keeps
112
+ // it because no later read superseded it. No config is represented by `undefined`, not `{}`:
113
+ // an empty object is a configuration that turns logging off.
114
+ #stageBootFallback(discardStaged = true) {
115
+ if (this.#readyEmitted) return;
116
+ if (discardStaged) {
117
+ this.#config = undefined;
118
+ this.#configLoaded = false;
119
+ }
120
+ this.#readyStaged = true;
121
+ this.#emitReady();
36
122
  }
37
123
 
38
124
  // Test-only: simulate the underlying chokidar watcher emitting an error.
@@ -42,17 +128,29 @@ export class RootConfigWatcher extends EventEmitter {
42
128
  this.handleError(error);
43
129
  }
44
130
 
45
- // Test-only: whether the watcher has fallen back to polling.
46
131
  get _usingPollingForTests(): boolean {
47
132
  return this.#usingPolling;
48
133
  }
49
134
 
50
- // Test-only: number of times the underlying watcher has been (re)opened.
51
135
  get _openCountForTests(): number {
52
136
  return this.#openCount;
53
137
  }
54
138
 
139
+ // Distinguishes a ladder rung from a watcher event.
140
+ get _readCountForTests(): number {
141
+ return this.#readCount;
142
+ }
143
+
144
+ get _armedForTests(): boolean {
145
+ return this.#armGate.armed;
146
+ }
147
+
55
148
  handleError(error: unknown) {
149
+ // A queued chokidar error can land after close(), which has dropped every listener.
150
+ if (this.#closed) return;
151
+ // See EntryHandler.#handleWatcherError: a lost native watch handle is benign
152
+ // and must not be surfaced to consumers as a config-watch failure.
153
+ if (claimLostNativeWatchError(error)) return;
56
154
  if (isWatcherExhaustionError(error)) {
57
155
  // Swallow every exhaustion error — chokidar can emit several before the
58
156
  // failed native watcher closes, and we don't want a flurry of ENOSPC to
@@ -60,47 +158,149 @@ export class RootConfigWatcher extends EventEmitter {
60
158
  if (!this.#usingPolling) {
61
159
  warnWatcherFallback(this.#configFilePath);
62
160
  this.#usingPolling = true;
63
- // Guard against reopen-after-close: the caller may have invoked
64
- // close() while this teardown was in flight. The .catch is required
65
- // because `finally` would re-raise a teardown rejection as an
66
- // unhandled one.
67
- this.#watcher
68
- .close()
161
+ // The generation that just failed no longer speaks for the watch; the replacement
162
+ // arms on its own scan, and re-reads then as the first one did.
163
+ this.#armGate.reset();
164
+ // Start close() from a microtask, not directly here, so a synchronous throw
165
+ // can't escape this 'error' listener as an uncaught exception.
166
+ Promise.resolve()
167
+ .then(() => this.#watcher.close())
69
168
  .catch(() => {
70
169
  // Teardown errors on an already-failed watcher are not actionable.
71
170
  })
72
- .finally(() => {
171
+ .then(() => {
73
172
  if (!this.#closed) this.#openWatcher();
74
- });
173
+ })
174
+ .catch((error) =>
175
+ logger().warn(`Could not reopen the ${this.#configFilePath} watch on polling`, errorForLog(error))
176
+ );
177
+ } else {
178
+ // Already polling — the replacement failed too, or the watch was polling from
179
+ // construction (`mustPoll`) and never had a fallback to take. Either way the branch
180
+ // above reopens only once, so this is the watch's terminal outcome and the barrier
181
+ // has to settle or `harper_logger.start()` awaits it forever.
182
+ this.#barrierOpen = true;
183
+ this.#stageBootFallback(false);
75
184
  }
76
185
  return;
77
186
  }
78
- this.emit('error', error);
187
+ // chokidar may never reach its own `ready` after a scan error, and nothing else would
188
+ // settle the barrier: the error is this read's terminal outcome. The scan is not over
189
+ // though, so the arm gate stays closed and a later `ready` still takes the arming re-read.
190
+ this.#barrierOpen = true;
191
+ this.#stageBootFallback(false);
192
+ // Settling the barrier removed the `error` listener `once(this, 'ready')` attached, and an
193
+ // emit with none left throws the error back into chokidar's dispatch — as does a consumer
194
+ // that throws from its own handler.
195
+ if (this.listenerCount('error') === 0) {
196
+ logger().warn(`The Harper configuration watcher at ${this.#configFilePath} failed`, errorForLog(error));
197
+ return;
198
+ }
199
+ try {
200
+ this.emit('error', error);
201
+ } catch (listenerError) {
202
+ logger().warn('A Harper configuration error listener failed', errorForLog(listenerError));
203
+ }
79
204
  }
80
205
 
81
206
  handleChange() {
82
- readFile(this.#configFilePath, 'utf-8')
83
- .then((data) => {
84
- if (!data) return;
207
+ this.#read(true);
208
+ }
85
209
 
86
- const config = parse(data);
210
+ // `harper_logger.start()` awaits `ready` with no timeout and the ladder may be the only thing
211
+ // left to settle it, so until then its timer keeps the thread alive rather than letting it
212
+ // drain and exit mid-boot.
213
+ #schedule(): boolean {
214
+ return this.#readRetry.schedule(() => this.#read(false), !this.#readyEmitted);
215
+ }
87
216
 
88
- if (!this.#config) {
89
- this.#config = config;
90
- this.emit('ready', this.#config);
91
- return;
92
- }
217
+ #read(waitForLock: boolean) {
218
+ // A queued chokidar callback can still land after close(), which has already discarded the
219
+ // config and dropped every listener.
220
+ if (this.#closed) return;
221
+ this.#readCount++;
222
+ let data: string;
223
+ try {
224
+ data = readConfigFileSync(this.#configFilePath, waitForLock);
225
+ } catch (error) {
226
+ // A missing file is not a lock — `readConfigFileSync` does not retry it either, and
227
+ // `OptionsWatcher` settles it immediately as the install window. Taking the ladder here
228
+ // would disagree with that and cost `harper_logger.start()` the whole budget on every
229
+ // boot that has no config file (an env-var-only deployment, an empty mounted rootPath).
230
+ if (!isMissingFile(error) && this.#schedule()) return;
231
+ // A ladder armed by an earlier empty read is spent by the time a rung lands on ENOENT,
232
+ // and every other terminal path clears its deadline.
233
+ this.#readRetry.reset();
234
+ logger().warn(
235
+ `Unable to read the Harper configuration file at ${this.#configFilePath}` +
236
+ (this.#configLoaded ? ', continuing with the previously loaded configuration' : '; none has been loaded yet'),
237
+ errorForLog(error)
238
+ );
239
+ this.#stageBootFallback();
240
+ return;
241
+ }
242
+ // See DESIGN.md, "An empty read is a writer mid-write, not an empty config".
243
+ if (!data) {
244
+ if (this.#schedule()) return;
245
+ logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
246
+ this.#stageBootFallback();
247
+ return;
248
+ }
249
+ let config;
250
+ try {
251
+ config = parseConfigFile(data, this.#configFilePath);
252
+ } catch (error) {
253
+ // A read taken mid-write is untrustworthy, not only an empty one: the writer's first
254
+ // `write(2)` can land a prefix of the document, and the event carrying the rest is the one
255
+ // chokidar throttles away. So an unparseable read rides out the same ladder as an empty
256
+ // one, and only a read that parses releases it.
257
+ if (this.#schedule()) return;
258
+ logger().warn((error as Error).message);
259
+ this.#stageBootFallback();
260
+ return;
261
+ }
262
+ // The third shape a mid-write read takes, and the only one that parses: a truncated
263
+ // document, a lone `\n`, a file that is nothing but comments all yield `null` rather than
264
+ // throwing, and adopting one hands every consumer a config with nothing in it. Same ladder
265
+ // as the two above, and past it the file is empty rather than mid-write.
266
+ if (!config || typeof config !== 'object') {
267
+ if (this.#schedule()) return;
268
+ logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
269
+ this.#stageBootFallback();
270
+ return;
271
+ }
272
+ this.#readRetry.reset();
93
273
 
94
- this.emit('change', (this.#config = config));
95
- })
96
- .catch((_error) => {
97
- // if yaml parse error ignore?
98
- });
274
+ // Before `ready` goes out there is no prior state to have changed *since*.
275
+ this.#configLoaded = true;
276
+ this.#readyStaged = true;
277
+ if (!this.#readyEmitted) {
278
+ this.#config = config;
279
+ this.#emitReady();
280
+ return;
281
+ }
282
+
283
+ try {
284
+ this.emit('change', (this.#config = config));
285
+ } catch (error) {
286
+ logger().warn('A Harper configuration change listener failed', errorForLog(error));
287
+ }
99
288
  }
100
289
 
101
290
  close() {
291
+ // Closing is a terminal outcome too: leaving `ready` pending would hang anything still
292
+ // awaiting the barrier. Through `#emitReady`, so a listener that throws cannot skip the
293
+ // teardown below it and leave the watcher and its arm timer running.
294
+ this.#barrierOpen = true;
295
+ this.#readyStaged = true;
296
+ this.#emitReady();
102
297
  this.#closed = true;
103
- this.#watcher.close();
298
+ this.#readRetry.cancel();
299
+ this.#armGate.cancel();
300
+ // chokidar's close() is a promise; an unhandled teardown rejection would reach Node as one,
301
+ // on the path whose whole job is to stop caring about this watcher. Same shape as the
302
+ // exhaustion-recovery close above, and as `OptionsWatcher.close`.
303
+ Promise.resolve(this.#watcher.close()).catch(() => {});
104
304
  this.#config = undefined;
105
305
  this.emit('close');
106
306
  this.removeAllListeners();