@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
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
  const path = require('node:path');
3
- const { isMainThread } = require('node:worker_threads');
3
+ const { isMainThread, parentPort } = require('node:worker_threads');
4
+ const { isDeepStrictEqual } = require('node:util');
4
5
  const fs = require('fs-extra');
5
6
  const fg = require('fast-glob');
6
7
  const normalize = require('normalize-path');
@@ -17,11 +18,22 @@ const { HDB_ERROR_MSGS, HTTP_STATUS_CODES } = hdbErrors;
17
18
  const manageThreads = require('../server/threads/manageThreads.js');
18
19
  const { packageDirectory, scanPackageDirectory, streamPackagedDirectory, } = require("../components/packageComponent.js");
19
20
  const { Resources } = require("../resources/Resources.js");
20
- const { Application, prepareApplication, ASIDE_STAGING_DIR } = require("./Application.js");
21
- const { COMPONENT_PREPARATION_LOCK_DIR } = require("./componentPreparationLock.js");
21
+ const { Application, prepareApplication, ASIDE_STAGING_DIR, DEPLOY_STAGING_DIR, dropComponentDirectory, } = require("./Application.js");
22
+ const { COMPONENT_PREPARATION_LOCK_DIR, withComponentPreparationLock } = require("./componentPreparationLock.js");
22
23
  const { server } = require("../server/Server.js");
23
24
  const { DeploymentRecorder, awaitDeploymentRow, readPayloadBlobWithRetry, coerceTimeoutMs, DEFAULT_AWAIT_ROW_TIMEOUT_MS, } = require("./deploymentRecorder.js");
24
25
  const { ProgressEmitter } = require("../server/serverHelpers/progressEmitter.js");
26
+ const DROP_COMPONENT_LOCK_TIMEOUT_MS = 5 * 60 * 1000;
27
+ const ISOLATED_TOPOLOGY_REQUEST_TIMEOUT_MS = 5000;
28
+ function componentDropLockOptions(project) {
29
+ return {
30
+ timeoutMs: DROP_COMPONENT_LOCK_TIMEOUT_MS,
31
+ onWait: (owner) => log.info(`Waiting to drop ${project} while component preparation is in progress` +
32
+ (owner ? ` in process ${owner.pid}, thread ${owner.threadId}` : '')),
33
+ onReleaseError: (error) => log.error(`Failed to release the component preparation lock for ${project}:`, error),
34
+ isOwnerAlive: (owner) => owner.pid !== process.pid || manageThreads.isThreadRunning(owner.threadId),
35
+ };
36
+ }
25
37
  /**
26
38
  * Read the settings.js file and return the
27
39
  *
@@ -172,12 +184,10 @@ async function addComponent(req) {
172
184
  throw handleHDBError(validation, validation.message, HTTP_STATUS_CODES.BAD_REQUEST);
173
185
  }
174
186
  log.trace(`adding component`);
175
- const cfDir = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
176
187
  const { project, install_command, install_timeout, install_allow_scripts } = req;
177
188
  const template = req.template || 'https://github.com/harperdb/application-template';
178
189
  try {
179
- const projectDir = path.join(cfDir, project);
180
- fs.mkdirSync(projectDir, { recursive: true });
190
+ await fs.mkdir(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), { recursive: true });
181
191
  const application = new Application({
182
192
  name: project,
183
193
  packageIdentifier: template,
@@ -230,8 +240,13 @@ async function dropCustomFunctionProject(req) {
230
240
  }
231
241
  try {
232
242
  const projectDir = path.join(cfDir, project);
233
- fs.rmSync(projectDir, { recursive: true });
234
- let response = await server.replication.replicateOperation(req);
243
+ const stagingDir = path.join(cfDir, ASIDE_STAGING_DIR, project);
244
+ if (!(await fs.pathExists(projectDir)) && !(await fs.pathExists(stagingDir)))
245
+ await fs.stat(projectDir);
246
+ await withComponentPreparationLock(projectDir, async () => {
247
+ await dropComponentDirectory(projectDir, project, log);
248
+ }, componentDropLockOptions(project));
249
+ const response = await server.replication.replicateOperation(req);
235
250
  response.message = `Successfully deleted project: ${project}`;
236
251
  return response;
237
252
  }
@@ -320,6 +335,115 @@ async function packageComponent(req) {
320
335
  // return the package payload as base64-encoded string
321
336
  return { project, payload };
322
337
  }
338
+ /**
339
+ * Load the built candidate to surface load-time errors. A load-ERROR PROBE, not a safety guarantee: it runs
340
+ * the component's own top-level code with incomplete side-effect isolation.
341
+ *
342
+ * A no-op on the main thread, and the operations API deploys there — so operator deploys are unvalidated
343
+ * (#2315 step 2). What this guarantees is ORDER: where validation runs, a rejected candidate never goes live.
344
+ */
345
+ // `componentLoader.setErrorReporter` is ONE process-global callback, so two components validating
346
+ // concurrently on the same worker cross-attribute their failures: B installs its reporter while A is
347
+ // loading, A's load error lands in B, and A then activates broken code while B rejects a good candidate.
348
+ // Validation is serialized (it is already the slow path) and the previous reporter is restored, so the
349
+ // global is only ever owned by one in-flight validation.
350
+ let validationChain = Promise.resolve();
351
+ async function validateComponentLoads(candidateDirPath, emit) {
352
+ const run = validationChain.then(() => validateComponentLoadsExclusive(candidateDirPath, emit), () => validateComponentLoadsExclusive(candidateDirPath, emit));
353
+ validationChain = run.then(() => { }, () => { });
354
+ return run;
355
+ }
356
+ async function validateComponentLoadsExclusive(candidateDirPath, emit) {
357
+ // now we attempt to actually load the component in case there is
358
+ // an error we can immediately detect and report, but app code should not run on the main thread
359
+ if (!isMainThread && !process.env.HARPER_SAFE_MODE) {
360
+ const pseudoResources = new Resources();
361
+ pseudoResources.isWorker = true;
362
+ const componentLoader = require("./componentLoader.js").default || require("./componentLoader.js");
363
+ const { trackScopeClose } = require("./scopeShutdown.js");
364
+ let lastError;
365
+ const priorErrorReporter = componentLoader.getErrorReporter?.();
366
+ componentLoader.setErrorReporter((error) => (lastError = error));
367
+ emit('phase', { phase: 'load', status: 'start' });
368
+ // This load exists only to surface load-time errors early; the Scopes it creates are
369
+ // throwaway. They are collected (instead of registered for worker-shutdown auto-close) so we
370
+ // can close them here once validation completes — otherwise each deploy leaks the Scope's
371
+ // deploy-lifecycle listeners on this worker, eventually tripping MaxListenersExceededWarning
372
+ // (#1462).
373
+ const validationScopes = new Set();
374
+ // Process-wide `server.*` registrations (registerOperation, setMcpQuotaHandler) are not owned by
375
+ // a Scope, so a candidate's top-level registration during this throwaway load would otherwise
376
+ // outlive it and pollute the live worker on a failed/rolled-back deploy. The guard makes those
377
+ // registration methods no-op for the duration of the load.
378
+ const { runWithDeployValidationGuard } = require("../server/serverHelpers/deployValidationState.js");
379
+ // The candidate loads under the REAL component's name, so a candidate that throws would mark the live
380
+ // component ERROR. Its status writes are diverted into the guard's throwaway sink instead — see
381
+ // `deployValidationState.ts` for why this is context-scoped rather than captured and reverted here.
382
+ const componentName = path.basename(candidateDirPath);
383
+ // Extension modules the candidate load pulls in are registered in the loader's module registry keyed by
384
+ // module, so forgetting only the candidate's realpath leaves those behind — one set per deploy. Their
385
+ // identities are collected by the load itself; diffing the global registry instead would delete a live
386
+ // module registered by an interleaving real load, since validations serialize only with each other.
387
+ const validationModules = new Set();
388
+ const validation = runWithDeployValidationGuard(async () => {
389
+ try {
390
+ await componentLoader.loadComponent(candidateDirPath, pseudoResources, undefined, {
391
+ collectScopes: validationScopes,
392
+ collectLoadedModules: validationModules,
393
+ });
394
+ }
395
+ finally {
396
+ const closeResults = await Promise.allSettled(Array.from(validationScopes, (scope) => scope.close()));
397
+ const failedCloses = closeResults.filter((result) => result.status === 'rejected');
398
+ for (const result of failedCloses) {
399
+ log.warn('Failed to close a deploy-validation Scope', result.reason);
400
+ }
401
+ // A rejected close is a REJECTED VALIDATION, not a warning. `Scope.close()` stops at the
402
+ // throwing listener, so its remaining internal listener removal and subscription-hold release
403
+ // never run and the throwaway scope stays partially live — one leak per deploy, on the worker
404
+ // that serves the component.
405
+ if (failedCloses.length) {
406
+ throw new AggregateError(failedCloses.map((result) => result.reason), `Could not tear down deploy validation for ${componentName}: ${failedCloses.length} scope(s) failed to close`);
407
+ }
408
+ }
409
+ });
410
+ // Track the load+close so a concurrent worker shutdown waits for these scopes to finish
411
+ // disposing — a plugin may start a native runtime in handleApplication — before realExit.
412
+ trackScopeClose(validation);
413
+ try {
414
+ await validation;
415
+ }
416
+ finally {
417
+ componentLoader.setErrorReporter(priorErrorReporter);
418
+ // The candidate path is unique per deploy, so leaving it in the loader's realpath registry leaks
419
+ // one dead entry per deploy for the life of the process.
420
+ componentLoader.forgetLoadedPath?.(candidateDirPath);
421
+ componentLoader.forgetLoadedModules?.(validationModules);
422
+ }
423
+ emit('phase', { phase: 'load', status: 'done' });
424
+ if (lastError)
425
+ throw lastError;
426
+ }
427
+ }
428
+ const BRANCH_STORAGE_RETAINED = '. Any branched database storage this application owns was left in place; drop it again with restart: true to discard that data';
429
+ /** Report a restart outcome the operation's own success message cannot convey. */
430
+ function logRestartOutcome(restart, what) {
431
+ const { RESTART_IDLE_TIMEOUT_MS, RESTART_WAIT_CEILING_MS } = require("./awaitRestart.js");
432
+ if (restart.replacementsNotStarted)
433
+ log.warn(`${restart.replacementsNotStarted} replacement worker thread(s) did not report starting after ${what}; the pool is short until they are restarted`);
434
+ if (restart.error)
435
+ log.error(`Restart after ${what} failed`, restart.error);
436
+ else if (restart.workersKeptOnOldCode)
437
+ log.warn(`${restart.workersKeptOnOldCode} worker thread(s) could not be replaced after ${what} and are still running the previous code`);
438
+ else if (restart.declined)
439
+ log.warn(`No restart was performed after ${what}: the process is already shutting down`);
440
+ else if (restart.handedOff)
441
+ log.debug?.(`The restart after ${what} was handed to the main thread, which reports its own completion`);
442
+ else if (restart.stalled)
443
+ log.warn(`The restart after ${what} stopped making progress for ${RESTART_IDLE_TIMEOUT_MS}ms; worker threads may still be running the previous code`);
444
+ else if (!restart.completed)
445
+ log.warn(`The restart after ${what} was still running after ${RESTART_WAIT_CEILING_MS}ms; worker threads may still be running the previous code`);
446
+ }
323
447
  /**
324
448
  * Can deploy a component in multiple ways. If a 'package' is provided all it will do is write that package to
325
449
  * harperdb-config, when HDB is restarted the package will be installed in hdb/nodeModules. If a base64 encoded string is passed it
@@ -342,12 +466,53 @@ async function deployComponent(req) {
342
466
  // replicated ciphertext (reference, not embed); already-reference entries pass through, and with
343
467
  // no custody a literal token stays as a transient, this-node-only fallback (#1158). Peers
344
468
  // re-running a replicated deploy already carry references and never re-ingest.
469
+ const { isIsolatedApplication } = require("../server/threads/isolatedApplications.js");
470
+ const requestedIsolation = req.isolated;
471
+ const isReplicatedExecution = typeof req._deploymentId === 'string';
472
+ const mode = req.deployment_id ? 'activate' : req.activate === false ? 'stage' : 'deploy';
473
+ const isActivation = mode === 'activate';
474
+ // this thread's cached config may predate an earlier deploy that changed the entry without a restart
475
+ env.initSync(true);
476
+ const initiallyIsolated = isIsolatedApplication(req.project);
477
+ if (requestedIsolation !== undefined && !req.package) {
478
+ throw handleHDBError(new Error(), `'isolated' is only supported for package deployments; set it on the application's root config entry instead`, HTTP_STATUS_CODES.BAD_REQUEST);
479
+ }
480
+ // Only the originating node rejects admission: replicated peers must commit the same desired config,
481
+ // then report any node-local inability to run it through component lifecycle status.
482
+ const assertIsolationAdmission = async (isolated) => {
483
+ if (!isolated || isReplicatedExecution)
484
+ return;
485
+ const { isolatedApplicationRefusal, isolatedApplicationCapacityRefusal, presentIsolatedApplicationNames, } = require("../server/threads/isolatedApplications.js");
486
+ let runningApplications;
487
+ try {
488
+ runningApplications = await manageThreads.getRunningIsolatedApplications(ISOLATED_TOPOLOGY_REQUEST_TIMEOUT_MS);
489
+ }
490
+ catch (error) {
491
+ throw handleHDBError(error, `Cannot deploy '${req.project}' as an isolated application: the main thread's worker topology is unavailable: ${error.message}`, HTTP_STATUS_CODES.SERVICE_UNAVAILABLE);
492
+ }
493
+ runningApplications = new Set([
494
+ ...runningApplications,
495
+ ...presentIsolatedApplicationNames().filter((application) => !isolatedApplicationRefusal(application)),
496
+ ]);
497
+ const refusal = isolatedApplicationRefusal(req.project) ?? isolatedApplicationCapacityRefusal(req.project, runningApplications);
498
+ if (refusal) {
499
+ throw handleHDBError(new Error(), `Cannot deploy '${req.project}' as an isolated application: ${refusal}`, HTTP_STATUS_CODES.CONFLICT);
500
+ }
501
+ };
502
+ // Skipped for an activation: the artifact's descriptor is the authority for the isolation it wants, and
503
+ // `admitIsolation` runs the real check under the preparation lock. Admitting the CURRENT value here would
504
+ // refuse an activation whose artifact turns isolation OFF for a component whose present isolated state is
505
+ // itself the refusal — a 409 for the request that would fix it.
506
+ if (!isActivation)
507
+ await assertIsolationAdmission(requestedIsolation ?? initiallyIsolated);
345
508
  const { ingestCredentials, resolveCredentials } = require("./secretOperations.js");
346
- req.credentials = await ingestCredentials(req, req.credentials, req.project);
509
+ // An activation resolves, fetches and installs nothing, so there is no credential for it to carry.
510
+ // The validator rejects one; this keeps the ingest itself off the path rather than relying on that.
511
+ if (!isActivation)
512
+ req.credentials = await ingestCredentials(req, req.credentials, req.project);
347
513
  // References are safe to persist (config + deployment row) and replicate; a no-custody literal
348
514
  // token is not — it is used only for this node's install below, then stripped before replication.
349
515
  const credentialReferences = (req.credentials ?? []).filter((entry) => entry && entry.secret !== undefined);
350
- // Write to root config if the request contains a package identifier
351
516
  if (req.package) {
352
517
  // Check if trying to overwrite a core component (requires force)
353
518
  // Lazy-load to avoid circular dependency with componentLoader
@@ -355,25 +520,9 @@ async function deployComponent(req) {
355
520
  if (TRUSTED_RESOURCE_PLUGINS[req.project] && !req.force) {
356
521
  throw handleHDBError(new Error(), `Cannot deploy component with name '${req.project}': this is a protected core component name. Use force: true to overwrite.`, HTTP_STATUS_CODES.CONFLICT);
357
522
  }
358
- const applicationConfig = { package: req.package };
359
- // Avoid writing an empty `install:` block
360
- if (req.install_command || req.install_timeout || req.install_allow_scripts !== undefined) {
361
- applicationConfig.install = {
362
- command: req.install_command,
363
- timeout: req.install_timeout,
364
- allowInstallScripts: req.install_allow_scripts,
365
- };
366
- }
367
- if (req.urlPath !== undefined)
368
- applicationConfig.urlPath = req.urlPath;
369
- if (req.host !== undefined)
370
- applicationConfig.host = req.host;
371
- // Persist credential references (never tokens) so every cold install of this component —
372
- // reboot, new peer, rollback — re-resolves the credential from the store.
373
- if (credentialReferences.length)
374
- applicationConfig.credentials = credentialReferences;
375
- await configUtils.addConfig(req.project, applicationConfig);
376
523
  }
524
+ let wasIsolated;
525
+ let nowIsolated;
377
526
  // Create a hdb_deployment row up front so the deploy is observable and auditable
378
527
  // even if the CLI disconnects. The row also holds the payload in a Blob attribute,
379
528
  // which doubles as the source for peer replication and (later) rollback.
@@ -382,7 +531,6 @@ async function deployComponent(req) {
382
531
  // recording so we don't accumulate one row per node for the same deploy. The row
383
532
  // reaches peers via the table's standard replication; the peer-side branch below
384
533
  // reads payload_blob back from there.
385
- const isReplicatedExecution = typeof req._deploymentId === 'string';
386
534
  // An SSE-bound caller already attached a ProgressEmitter (created in the server
387
535
  // handler so it can also drive the response stream). Reuse it; otherwise spin up a
388
536
  // fresh emitter so the recorder still gets phase events for non-SSE deploys.
@@ -395,6 +543,7 @@ async function deployComponent(req) {
395
543
  project: req.project,
396
544
  package_identifier: req.package ?? null,
397
545
  user: req.hdb_user?.username,
546
+ activated_from: req.deployment_id ?? null,
398
547
  restart_mode: req.restart === 'rolling' ? 'rolling' : req.restart ? 'immediate' : null,
399
548
  // Reference form only — the rollback source for re-resolving the credential.
400
549
  credentials: credentialReferences.length ? credentialReferences : null,
@@ -417,12 +566,19 @@ async function deployComponent(req) {
417
566
  // Bounded ring buffer of install stdout/stderr so a non-SSE caller sees the tail
418
567
  // in the thrown error. SSE callers still stream every line live.
419
568
  const installCapture = createInstallCapture();
569
+ let stagedOnOrigin = false;
420
570
  try {
421
571
  // On the origin, tee the tarball (Buffer or Readable from the multipart parser)
422
572
  // through a hash-and-size tap into the row's payload_blob, then re-source extraction
423
573
  // from the persisted blob. When `system` replicates, the blob becomes the channel
424
574
  // peers read from; when it doesn't, the blob stays local for audit and rollback.
425
- if (recorder && req.payload != null) {
575
+ if (isActivation) {
576
+ // A replicated activation has exactly the shape the peer-side blob branch below keys on — neither
577
+ // `package` nor `payload` — so without this branch every peer would wait out `deployment_timeout`
578
+ // for a payload its row never had while the origin swapped successfully.
579
+ extractionPayload = undefined;
580
+ }
581
+ else if (recorder && req.payload != null) {
426
582
  await recorder.ingestPayload(req.payload);
427
583
  extractionPayload = recorder.row.payload_blob.stream();
428
584
  }
@@ -458,9 +614,13 @@ async function deployComponent(req) {
458
614
  if (isReplicatedExecution) {
459
615
  credentialsWaitMs = coerceTimeoutMs(req.deployment_timeout, DEFAULT_AWAIT_ROW_TIMEOUT_MS);
460
616
  }
461
- const resolvedCredentials = await resolveCredentials(req.credentials, req.project, {
462
- waitMs: credentialsWaitMs,
463
- });
617
+ // Skipped for an activation, which runs no pack, clone or install: waiting for an hdb_secret row to
618
+ // replicate in would only delay a swap that has nothing to authenticate to.
619
+ const resolvedCredentials = isActivation
620
+ ? undefined
621
+ : await resolveCredentials(req.credentials, req.project, {
622
+ waitMs: credentialsWaitMs,
623
+ });
464
624
  const application = new Application({
465
625
  name: req.project,
466
626
  payload: extractionPayload,
@@ -492,52 +652,102 @@ async function deployComponent(req) {
492
652
  req.credentials = credentialReferences;
493
653
  else
494
654
  delete req.credentials;
655
+ // Validated INSIDE preparation, between build and swap, so a component that installs cleanly but
656
+ // throws at load never becomes live.
657
+ //
658
+ // Phase ORDER is unchanged for clients, but `prepare:done` means "candidate built", not "swap
659
+ // committed" — so a later failure arrives after both phases reported success. The operation's error
660
+ // is the authority on whether the deploy landed, not the phase stream.
495
661
  emit('phase', { phase: 'prepare', status: 'start' });
496
- await prepareApplication(application);
497
- emit('phase', { phase: 'prepare', status: 'done' });
498
- // now we attempt to actually load the component in case there is
499
- // an error we can immediately detect and report, but app code should not run on the main thread
500
- if (!isMainThread && !process.env.HARPER_SAFE_MODE) {
501
- const pseudoResources = new Resources();
502
- pseudoResources.isWorker = true;
503
- const componentLoader = require("./componentLoader.js").default || require("./componentLoader.js");
504
- const { trackScopeClose } = require("./scopeShutdown.js");
505
- let lastError;
506
- componentLoader.setErrorReporter((error) => (lastError = error));
507
- emit('phase', { phase: 'load', status: 'start' });
508
- // This load exists only to surface load-time errors early; the Scopes it creates are
509
- // throwaway. They are collected (instead of registered for worker-shutdown auto-close) so we
510
- // can close them here once validation completes — otherwise each deploy leaks the Scope's
511
- // deploy-lifecycle listeners on this worker, eventually tripping MaxListenersExceededWarning
512
- // (#1462).
513
- const validationScopes = new Set();
514
- // Process-wide `server.*` registrations (registerOperation, setMcpQuotaHandler) are not owned by
515
- // a Scope, so a candidate's top-level registration during this throwaway load would otherwise
516
- // outlive it and pollute the live worker on a failed/rolled-back deploy. The guard makes those
517
- // registration methods no-op for the duration of the load.
518
- const { runWithDeployValidationGuard } = require("../server/serverHelpers/deployValidationState.js");
519
- const validation = runWithDeployValidationGuard(async () => {
520
- try {
521
- await componentLoader.loadComponent(application.dirPath, pseudoResources, undefined, {
522
- collectScopes: validationScopes,
523
- });
524
- }
525
- finally {
526
- const closeResults = await Promise.allSettled(Array.from(validationScopes, (scope) => scope.close()));
527
- for (const result of closeResults) {
528
- if (result.status === 'rejected')
529
- log.warn('Failed to close a deploy-validation Scope', result.reason);
662
+ // The root-config entry a package deploy publishes. A stage records it with the artifact instead of
663
+ // publishing it, so a staged release nobody activated cannot leave config naming it — and the
664
+ // activation that eventually publishes it does so in the same order a normal deploy does.
665
+ let stagedRootConfig = null;
666
+ await prepareApplication(application, {
667
+ // `.deploy-staging/<artifactId>`. The public deployment id, so the id the caller was handed is
668
+ // the id a later `deployment_id` request can name; an activation names the artifact's own id,
669
+ // not the new row's.
670
+ artifactId: req.deployment_id ?? req._deploymentId,
671
+ mode,
672
+ describeArtifact: () => ({ rootConfig: stagedRootConfig, isolated: Boolean(nowIsolated) }),
673
+ // Returns its own undo. Publication happens BEFORE the swap, in the same order a normal deploy
674
+ // uses, so an activation that fails pre-commit would otherwise leave config naming a release that
675
+ // is not live — and `installApplications()` would resolve and install that package from scratch at
676
+ // the next boot, over a component whose certified artifact is sitting right there. Restoring is
677
+ // best-effort and not durable (that is #2315 step 3's work); it is still the difference between a
678
+ // failed activation that changed nothing and one that re-points the component.
679
+ publishRootConfig: async (entry) => {
680
+ const previous = configUtils.getConfigObj()?.[req.project];
681
+ await configUtils.addConfig(req.project, entry);
682
+ env.initSync(true);
683
+ // Read BACK, not `entry` — see `publishedEntryStillStands`.
684
+ const published = configUtils.getConfigObj()?.[req.project];
685
+ return async () => {
686
+ if (!publishedEntryStillStands(published, () => env.initSync(true), () => configUtils.getConfigObj()?.[req.project])) {
687
+ return;
530
688
  }
689
+ if (previous === undefined)
690
+ configUtils.deleteConfigFromFile([req.project]);
691
+ else
692
+ await configUtils.addConfig(req.project, previous);
693
+ env.initSync(true);
694
+ };
695
+ },
696
+ admitIsolation: async (descriptor) => {
697
+ env.initSync(true);
698
+ wasIsolated = isIsolatedApplication(req.project);
699
+ // A package artifact restores its own root-config entry, so its recorded intent is what will
700
+ // be in force. A payload artifact publishes no config, so the effective configuration is the
701
+ // only authority there — a saved `false` must not admit past an isolation the operator set
702
+ // between the stage and this call.
703
+ nowIsolated = descriptor.rootConfig ? descriptor.isolated : wasIsolated;
704
+ await assertIsolationAdmission(nowIsolated);
705
+ },
706
+ beforePrepare: async () => {
707
+ if (isActivation)
708
+ return;
709
+ env.initSync(true);
710
+ wasIsolated = isIsolatedApplication(req.project);
711
+ nowIsolated = requestedIsolation ?? wasIsolated;
712
+ await assertIsolationAdmission(nowIsolated);
713
+ if (!isReplicatedExecution && req.package)
714
+ req.isolated = nowIsolated;
715
+ if (!req.package)
716
+ return;
717
+ const applicationConfig = { package: req.package };
718
+ if (req.install_command || req.install_timeout || req.install_allow_scripts !== undefined) {
719
+ applicationConfig.install = {
720
+ command: req.install_command,
721
+ timeout: req.install_timeout,
722
+ allowInstallScripts: req.install_allow_scripts,
723
+ };
531
724
  }
532
- });
533
- // Track the load+close so a concurrent worker shutdown waits for these scopes to finish
534
- // disposing — a plugin may start a native runtime in handleApplication — before realExit.
535
- trackScopeClose(validation);
536
- await validation;
537
- emit('phase', { phase: 'load', status: 'done' });
538
- if (lastError)
539
- throw lastError;
540
- }
725
+ if (req.urlPath !== undefined)
726
+ applicationConfig.urlPath = req.urlPath;
727
+ if (req.host !== undefined)
728
+ applicationConfig.host = req.host;
729
+ if (req.branchedDatabases !== undefined)
730
+ applicationConfig.branchedDatabases = req.branchedDatabases;
731
+ if (nowIsolated)
732
+ applicationConfig.isolated = true;
733
+ if (credentialReferences.length)
734
+ applicationConfig.credentials = credentialReferences;
735
+ if (mode === 'stage') {
736
+ stagedRootConfig = applicationConfig;
737
+ return;
738
+ }
739
+ await configUtils.addConfig(req.project, applicationConfig);
740
+ env.initSync(true);
741
+ },
742
+ validateCandidate: async (candidateDirPath) => {
743
+ emit('phase', { phase: 'prepare', status: 'done' });
744
+ await validateComponentLoads(candidateDirPath, emit);
745
+ },
746
+ });
747
+ // The build is certified on disk from here on, so every later failure — a peer result, or a rejection
748
+ // thrown by the replication layer itself — still leaves an artifact this id can activate.
749
+ if (mode === 'stage')
750
+ stagedOnOrigin = true;
541
751
  const rollingRestart = req.restart === 'rolling';
542
752
  // if doing a rolling restart set restart to false so that other nodes don't also restart.
543
753
  req.restart = rollingRestart ? false : req.restart;
@@ -580,10 +790,28 @@ async function deployComponent(req) {
580
790
  // when the per-peer callback already fired for these.
581
791
  recorder.recordPeers(response.replicated);
582
792
  }
583
- if (req.restart === true) {
793
+ // A still-isolated application restarts only its own dedicated worker. Everything else restarts the
794
+ // pool: a shared application, and either direction of an isolation flip, where the reconcile in
795
+ // restartWorkers starts or stops the moving application's own worker.
796
+ const restartScope = wasIsolated && nowIsolated ? application.name : undefined;
797
+ if (mode === 'stage') {
798
+ // No restart and no restart-required flag: nothing about the running component changed. The
799
+ // marker is what tells the origin which peers understood the request — see the confirmation
800
+ // check below — and it is set on every node, origin included, since a peer returns this same
801
+ // object as its operation response.
802
+ response.staged = true;
803
+ response.message = `Staged: ${application.name}`;
804
+ }
805
+ else if (req.restart === true) {
584
806
  emit('phase', { phase: 'restart', status: 'start' });
585
- manageThreads.restartWorkers('http');
807
+ // Workers not yet replaced keep serving the pre-deploy component set, and where the OS lets
808
+ // replacements share a port they keep accepting connections for the whole rolling restart, so
809
+ // a caller that reads success as "the component is live" can be served by a worker that has
810
+ // never heard of it.
811
+ const { awaitRestart } = require("./awaitRestart.js");
812
+ const restart = await awaitRestart((onProgress) => manageThreads.restartWorkers('http', undefined, undefined, onProgress, restartScope));
586
813
  emit('phase', { phase: 'restart', status: 'done' });
814
+ logRestartOutcome(restart, `deploying ${application.name}`);
587
815
  response.message = `Successfully deployed: ${application.name}, restarting Harper`;
588
816
  }
589
817
  else if (rollingRestart) {
@@ -592,6 +820,8 @@ async function deployComponent(req) {
592
820
  const jobResponse = await serverUtilities.executeJob({
593
821
  operation: 'restart_service',
594
822
  service: 'http',
823
+ scope: manageThreads.encodeRestartScope(restartScope),
824
+ scopeFallback: restartScope === undefined ? undefined : manageThreads.encodeRestartScope(undefined),
595
825
  replicated: true,
596
826
  });
597
827
  emit('phase', { phase: 'restart', status: 'done' });
@@ -611,10 +841,8 @@ async function deployComponent(req) {
611
841
  // An existing component's watched files are handled by Scope/EntryHandler. Package
612
842
  // metadata is deliberately outside most plugin globs, so compare it across the atomic
613
843
  // swap as well: a dependency or module-entry change also invalidates loaded code.
614
- if (application.isNewComponent || application.packageMetadataChanged) {
615
- const { requestRestart } = require("./requestRestart.js");
616
- requestRestart();
617
- }
844
+ const { requestRestartAfterDeploy } = require("./requestRestart.js");
845
+ requestRestartAfterDeploy(application.isNewComponent, application.packageMetadataChanged, wasIsolated, nowIsolated);
618
846
  response.message = `Successfully deployed: ${application.name}`;
619
847
  }
620
848
  // Replication failures don't reject replicateOperation — they surface as 'failed'
@@ -629,10 +857,32 @@ async function deployComponent(req) {
629
857
  const detail = failedPeers
630
858
  .map((peer) => `${peer.node ?? 'unknown'} (${peer.error?.message ?? 'unknown error'})`)
631
859
  .join(', ');
632
- throw new ServerError(`Component '${application.name}' was deployed on the origin node but failed to replicate to ` +
633
- `${failedPeers.length} of ${recorder.row.peer_results.length} peer node(s): ${detail}. ` +
634
- `See deployment ${recorder.deploymentId} (get_deployment) for details, or pass ` +
635
- `ignore_replication_errors: true to treat replication failures as non-fatal.`);
860
+ const replicationError = new ServerError(`Component '${application.name}' was ${mode === 'stage' ? 'staged' : 'deployed'} on the origin node ` +
861
+ `but failed to replicate to ${failedPeers.length} of ${recorder.row.peer_results.length} peer ` +
862
+ `node(s): ${detail}. See deployment ${recorder.deploymentId} (get_deployment) for details, or ` +
863
+ `pass ignore_replication_errors: true to treat replication failures as non-fatal.`);
864
+ throw replicationError;
865
+ }
866
+ // A peer running a build that predates staged deploys accepts `activate: false` as an unknown
867
+ // field and performs its ordinary deploy, so it is now serving a release the operator asked only
868
+ // to stage. Nothing can prevent that — the operations channel carries no peer version or
869
+ // capability — so the origin reports it, from the one signal that separates the two cases: a node
870
+ // that staged says so in its response. Read from the raw aggregate, because the recorder's peer
871
+ // normalization keeps only node/status/error.
872
+ //
873
+ // The wording stays at "did not confirm". An unreachable peer and one that dropped the request
874
+ // look identical from here, and telling an operator those nodes went live would be a guess.
875
+ if (mode === 'stage') {
876
+ const unconfirmed = unconfirmedStagingPeers(response?.replicated);
877
+ if (unconfirmed.length > 0) {
878
+ const detail = unconfirmed.map((peer) => peer.node ?? 'unknown').join(', ');
879
+ const unconfirmedError = new ServerError(`Component '${application.name}' was staged on the origin node, but ${unconfirmed.length} peer ` +
880
+ `node(s) did not confirm staging: ${detail}. Either they are unreachable, or they run a build ` +
881
+ `that predates staged deploys — which treats this request as an ordinary deploy, so the ` +
882
+ `release is already serving there. Check those nodes before activating deployment ` +
883
+ `${recorder.deploymentId}, or pass ignore_replication_errors: true to accept the difference.`);
884
+ throw unconfirmedError;
885
+ }
636
886
  }
637
887
  }
638
888
  if (recorder) {
@@ -645,15 +895,27 @@ async function deployComponent(req) {
645
895
  // the tarball when it's still the artifact you'd debug or retry with: failed deploys
646
896
  // don't reach this branch, and a deploy that reached here only because
647
897
  // ignore_replication_errors masked failed peers keeps its payload for those peers.
898
+ // Never for a stage: the artifact on disk is not yet a release anyone can re-derive, and retention
899
+ // can evict it (it shares `deployment_stagingRetention_maxCount` with incidental dormant builds),
900
+ // so dropping the tarball here is how a staged release becomes unrecoverable — no tree, no bytes,
901
+ // and a row still reporting `staged`. An operator can still reclaim it deliberately with
902
+ // delete_deployment_payload once they accept that.
648
903
  const payloadSize = recorder.row.payload_size;
649
904
  const retentionMaxSize = getPayloadRetentionMaxSize();
650
- if (typeof payloadSize === 'number' && payloadSize > retentionMaxSize && recorder.getFailedPeers().length === 0) {
905
+ if (mode !== 'stage' &&
906
+ typeof payloadSize === 'number' &&
907
+ payloadSize > retentionMaxSize &&
908
+ recorder.getFailedPeers().length === 0) {
651
909
  const freed = recorder.dropPayload();
652
910
  if (freed > 0)
653
911
  emit('payload_dropped', { payload_size: freed, max_size: retentionMaxSize });
654
912
  }
655
- emit('phase', { phase: 'success', status: 'done' });
656
- await recorder.finish('success');
913
+ emit('phase', { phase: mode === 'stage' ? 'staged' : 'success', status: 'done' });
914
+ await recorder.finish(mode === 'stage' ? 'staged' : 'success');
915
+ if (mode === 'stage') {
916
+ response.message =
917
+ `Staged: ${application.name}. Deploy it with deploy_component ` + `deployment_id=${recorder.deploymentId}`;
918
+ }
657
919
  }
658
920
  return response;
659
921
  }
@@ -697,7 +959,7 @@ async function deployComponent(req) {
697
959
  // install output, and failed_peers the caller needs.
698
960
  if (recorder) {
699
961
  try {
700
- await recorder.finish('failed', err);
962
+ await recorder.finish(stagedOnOrigin ? 'staged' : 'failed', err);
701
963
  }
702
964
  catch (finishErr) {
703
965
  log.warn('Failed to record deployment failure row', finishErr);
@@ -706,6 +968,42 @@ async function deployComponent(req) {
706
968
  throw outErr;
707
969
  }
708
970
  }
971
+ /**
972
+ * Whether the root-config entry an activation published is still the one on disk — the condition for that
973
+ * activation's failure to take it back. A `set_configuration` acknowledged while the swap was retrying is
974
+ * not this operation's to overwrite, and restoring a snapshot taken before it would silently drop it.
975
+ *
976
+ * Split out from the caller so it can be tested without a deploy: this is the only guard on a hazard the
977
+ * preparation lock does not cover, and both ways it can regress fail silently. `refreshConfig` is called
978
+ * BEFORE the read and is load-bearing — `set_configuration` writes the config file without refreshing this
979
+ * process's config object, so a cached read compares against a value that predates the very change the
980
+ * guard exists to protect and always concludes nothing moved. It narrows the window rather than closing
981
+ * it; serializing config publication is #2315 step 3.
982
+ *
983
+ * `published` must be the entry as read back after publication, not as passed in: only two values that went
984
+ * through the same write-and-parse round trip are comparable, and comparing against the argument would read
985
+ * any serialization difference as a concurrent change and skip every undo.
986
+ */
987
+ function publishedEntryStillStands(published, refreshConfig, readCurrentEntry) {
988
+ refreshConfig();
989
+ return isDeepStrictEqual(readCurrentEntry(), published);
990
+ }
991
+ /**
992
+ * Peers that did not answer a stage with `staged: true` — either unreachable, or running a build that
993
+ * predates staged deploys and therefore treated the request as an ordinary deploy.
994
+ *
995
+ * Split out from the caller so it can be tested without a cluster: this is the only safety net for the
996
+ * mixed-version hazard #2315 records as accepted, and a regression in it fails silently. The entry shape
997
+ * belongs to harper-pro's replicator rather than this repo — `normalizePeerResult` already tolerates more
998
+ * than one — so the marker is read flat or from a wrapped body; assuming flat would report every peer in a
999
+ * fully-upgraded cluster as unconfirmed.
1000
+ */
1001
+ function unconfirmedStagingPeers(replicated) {
1002
+ if (!Array.isArray(replicated))
1003
+ return [];
1004
+ const confirmed = (peer) => peer?.staged === true || peer?.value?.staged === true || peer?.body?.staged === true;
1005
+ return replicated.filter((peer) => peer && !confirmed(peer));
1006
+ }
709
1007
  // Ring buffer of install stdout/stderr lines, capped by both line count and bytes so
710
1008
  // a chatty install can't unbounded-grow the error response. snapshot() reports whether
711
1009
  // the head was dropped so callers can flag truncation.
@@ -774,8 +1072,12 @@ async function getComponents() {
774
1072
  const list = await fs.readdir(dir, { withFileTypes: true });
775
1073
  for (let item of list) {
776
1074
  const itemName = item.name;
1075
+ // Deny-list, not a dot-prefix skip: component CONTENTS legitimately include dot-files
1076
+ // (`.aiignore`, `.env.example`) that callers expect to see, so only Harper's own
1077
+ // bookkeeping directories are excluded by name.
777
1078
  if (itemName === 'node_modules' ||
778
1079
  itemName === ASIDE_STAGING_DIR ||
1080
+ itemName === DEPLOY_STAGING_DIR ||
779
1081
  itemName === COMPONENT_PREPARATION_LOCK_DIR)
780
1082
  continue;
781
1083
  const itemPath = path.join(dir, itemName);
@@ -1054,31 +1356,90 @@ async function dropComponent(req) {
1054
1356
  }
1055
1357
  const { project, file } = req;
1056
1358
  const projectPath = req.file ? path.join(project, file) : project;
1057
- const pathToComponent = path.join(configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT), projectPath);
1058
- const componentSymlink = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'node_modules', project);
1059
- if (await fs.pathExists(componentSymlink)) {
1060
- await fs.unlink(componentSymlink);
1061
- }
1062
- if (await fs.pathExists(pathToComponent)) {
1063
- await fs.remove(pathToComponent);
1064
- }
1065
- // Remove the component from the package.json file
1066
- const packageJsonPath = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'package.json');
1067
- if (await fs.pathExists(packageJsonPath)) {
1068
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
1069
- if (packageJson?.dependencies?.[project]) {
1070
- delete packageJson.dependencies[project];
1359
+ const componentsRoot = configUtils.getConfigPath(hdbTerms.CONFIG_PARAMS.COMPONENTSROOT);
1360
+ const componentPath = path.join(componentsRoot, project);
1361
+ const pathToComponent = path.join(componentsRoot, projectPath);
1362
+ let restartScope;
1363
+ let response;
1364
+ await withComponentPreparationLock(componentPath, async () => {
1365
+ if (req.restart === true) {
1366
+ let runningApplications;
1367
+ try {
1368
+ runningApplications = await manageThreads.getRunningIsolatedApplications(ISOLATED_TOPOLOGY_REQUEST_TIMEOUT_MS);
1369
+ }
1370
+ catch (error) {
1371
+ throw handleHDBError(error, `Cannot drop '${project}' with a restart: the main thread's worker topology is unavailable: ${error.message}`, HTTP_STATUS_CODES.SERVICE_UNAVAILABLE);
1372
+ }
1373
+ if (runningApplications.includes(project))
1374
+ restartScope = project;
1071
1375
  }
1072
- await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
1073
- }
1074
- configUtils.deleteConfigFromFile([project]);
1075
- let response = await server.replication.replicateOperation(req);
1076
- if (req.restart === true) {
1077
- manageThreads.restartWorkers('http');
1376
+ const componentSymlink = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'node_modules', project);
1377
+ if (!file && (await fs.pathExists(componentSymlink))) {
1378
+ await fs.unlink(componentSymlink);
1379
+ }
1380
+ if (!file) {
1381
+ await dropComponentDirectory(componentPath, project, log);
1382
+ }
1383
+ else if (await fs.pathExists(pathToComponent)) {
1384
+ await fs.remove(pathToComponent);
1385
+ }
1386
+ const packageJsonPath = path.join(env.get(hdbTerms.CONFIG_PARAMS.ROOTPATH), 'package.json');
1387
+ if (!file && (await fs.pathExists(packageJsonPath))) {
1388
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
1389
+ if (packageJson?.dependencies?.[project]) {
1390
+ delete packageJson.dependencies[project];
1391
+ }
1392
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8');
1393
+ }
1394
+ if (!file)
1395
+ configUtils.deleteConfigFromFile([project]);
1396
+ if (!file && isMainThread)
1397
+ env.initSync(true);
1398
+ response = await server.replication.replicateOperation(req);
1399
+ const { applicationHasBranchStorage, removeBranchesForApplication } = require("../resources/branchDatabase.js");
1400
+ const branched = !file && applicationHasBranchStorage(project);
1401
+ if (req.restart !== true) {
1402
+ response.message = `Successfully dropped: ${projectPath}`;
1403
+ if (branched)
1404
+ response.message += BRANCH_STORAGE_RETAINED;
1405
+ return;
1406
+ }
1407
+ // The branches go after the restart, under this lock; a worker is one of the threads being
1408
+ // replaced, so it hands both to the main thread.
1078
1409
  response.message = `Successfully dropped: ${projectPath}, restarting Harper`;
1079
- }
1080
- else
1081
- response.message = `Successfully dropped: ${projectPath}`;
1410
+ if (!isMainThread) {
1411
+ parentPort.postMessage({
1412
+ type: hdbTerms.ITC_EVENT_TYPES.RESTART,
1413
+ workerType: 'http',
1414
+ scope: manageThreads.encodeRestartScope(restartScope),
1415
+ removeBranchesFor: file ? undefined : project,
1416
+ });
1417
+ if (branched) {
1418
+ response.message +=
1419
+ '; the main thread removes its branched database storage after the restart and logs the outcome';
1420
+ }
1421
+ return;
1422
+ }
1423
+ const { awaitRestart } = require("./awaitRestart.js");
1424
+ const restart = await awaitRestart((onProgress) => manageThreads.restartWorkers('http', undefined, undefined, onProgress, restartScope));
1425
+ logRestartOutcome(restart, `dropping ${projectPath}`);
1426
+ if (!branched)
1427
+ return;
1428
+ if (restart.completed && !restart.workersKeptOnOldCode) {
1429
+ try {
1430
+ await removeBranchesForApplication(project);
1431
+ }
1432
+ catch (error) {
1433
+ log.error(`Branched database storage of ${project} could not be removed`, error);
1434
+ throw new Error(`Successfully dropped: ${projectPath}, but its branched database storage could not be removed and ` +
1435
+ `was left in place: ${error.message}. Drop it again with restart: true to retry.`);
1436
+ }
1437
+ }
1438
+ else {
1439
+ log.warn(`Branched database storage of ${project} was left in place: the restart did not complete`);
1440
+ response.message += BRANCH_STORAGE_RETAINED;
1441
+ }
1442
+ }, componentDropLockOptions(project));
1082
1443
  return response;
1083
1444
  }
1084
1445
  exports.customFunctionsStatus = customFunctionsStatus;
@@ -1090,6 +1451,8 @@ exports.addComponent = addComponent;
1090
1451
  exports.dropCustomFunctionProject = dropCustomFunctionProject;
1091
1452
  exports.packageComponent = packageComponent;
1092
1453
  exports.deployComponent = deployComponent;
1454
+ exports.unconfirmedStagingPeers = unconfirmedStagingPeers;
1455
+ exports.publishedEntryStillStands = publishedEntryStillStands;
1093
1456
  exports.getComponents = getComponents;
1094
1457
  exports.getComponentFile = getComponentFile;
1095
1458
  exports.setComponentFile = setComponentFile;